---------------------------------------------------------------------------------- -- Company: -- Engineer: Professor Jim Plusquellic -- -- Create Date: -- Design Name: -- Module Name: Histo - Behavioral -- Project Name: -- Target Devices: -- Tool versions: -- Description: -- -- Dependencies: -- -- Revision: -- Revision 0.01 - File Created -- Additional Comments: -- ---------------------------------------------------------------------------------- -- =================================================================================================== -- =================================================================================================== -- Histo bins and counts the integer portion of the PNs as a means of determining the width of the -- distribution. We use a 'bounded range' method to measure the width of the distribution, e.g., 6.25% -- to 93.75% to approx. the 3 sigma actual range to avoid the negative impact caused by an outlying -- PNs. -- The PNs are signed values between -2048.9375 and 2047.9375 (SIGNED 12-bit integer + 4 bits of precision) -- We have 2K words of PNL BRAM to utilize (addresses 2048 to 4095) for the histogram so we'll need to make -- sure the integer PNs do not exceed -1024 to +1023. To maximize the ranges that can be handled and to -- deal with the negative PNs (if they occur), we first find the smallest value and I use that to offset -- the distribution. The range is a relative value (difference of two addresses in the histogram portion of -- the PNL_BRAM) that represent the 6.25% to 93.75% bounds so subtracting the two addresses gives us the -- range of the PNs. -- Each cell is 16 bits so we can count to 2^16 (plenty given we only have 4096 total values to bin). Once -- we've created the histogram, we parse it from left to right, adding each of the cell counts to a global sum, -- and record the point where the sum equals or exceeds the LV bound count. This address becomes the lower bound -- on the range. We keep parsing until the sum becomes >= the HV bound count. This address becomes the upper -- bound on the range. library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.all; library work; use work.DataTypes_pkg.all; entity Histo is port( Clk: in std_logic; RESET: in std_logic; start: in std_logic; ready: out std_logic; HISTO_ERR: out std_logic; PNL_BRAM_addr: out std_logic_vector(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); PNL_BRAM_din: out std_logic_vector(PNL_BRAM_DBITS_WIDTH_NB-1 downto 0); PNL_BRAM_dout: in std_logic_vector(PNL_BRAM_DBITS_WIDTH_NB-1 downto 0); PNL_BRAM_we: out std_logic_vector(0 to 0) ); end Histo; architecture beh of Histo is type state_type is (idle, clear_mem, find_smallest, compute_addr, inc_cell, get_next_PN, init_dist, sweep_BRAM, check_histo_error, write_range); signal state_reg, state_next: state_type; signal ready_reg, ready_next: std_logic; -- Address registers for the PNs and histogram portions of memory signal PN_addr_reg, PN_addr_next: unsigned(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); signal histo_addr_reg, histo_addr_next: unsigned(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); -- For selecting between PN or histo portion of memory during memory accesses signal do_PN_histo_addr: std_logic; -- Stores the full 16-bit PN that is the smallest among all in the data set signal smallest_val_reg, smallest_val_next: signed(PNL_BRAM_DBITS_WIDTH_NB-1 downto 0); -- These are 12 bits each to hold only the 12-bit integer portion of the PNs signal shifted_dout: signed(PN_INTEGER_NB-1 downto 0); signal shifted_smallest_val: signed(PN_INTEGER_NB-1 downto 0); -- These signals used in the calculation of the address in the histogram memory of the cell to add 1 to during the histo -- contruction. They are addresses and therefore need to match the address width of the memory. signal offset_addr: signed(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); signal histo_cell_addr: unsigned(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); -- These variables will store the PNL_BRAM addresses when the LV and HV bounds are met. signal LV_addr_reg, LV_addr_next: unsigned(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); signal HV_addr_reg, HV_addr_next: unsigned(PNL_BRAM_ADDR_SIZE_NB-1 downto 0); -- Use for error checking when the range of the distribution is computed. signal LV_set_reg, LV_set_next: std_logic; signal HV_set_reg, HV_set_next: std_logic; -- These signals store the lower and upper count that represents the fractional limits of the histogram. They are constants -- that signal the state machine when to record an address reference in the histogram memory. These hold constants that -- represent a 'count', where the maximum value can be 4096 (one bigger than 4095), so we need 13 bits (not 12). signal LV_bound, HV_bound: unsigned(NUM_PNS_NB downto 0); -- The register used to sum up the counts in the histogram as it is parsed left to right. It WILL count up to the number of -- PNs stored, which is currently 4096, so we need 13-bit here, not 12. signal dist_cnt_sum_reg, dist_cnt_sum_next: unsigned(NUM_PNS_NB downto 0); -- Storage for the mean must be able to accommodate a sum of 4096 values (NUM_PNS) each of which is 16-bits (PNL_BRAM_DBITS_WIDTH_NB) -- wide. The number of values summed is 4096 so we need 12-bits, NUM_PNS_NB) where each value is 16-bits (PN_SIZE_NB) so we need -- an adder that is 28 bits (27 downto 0). The sum is likely to require much fewer bits -- this is worst case. signal dist_mean_sum_reg, dist_mean_sum_next: signed(NUM_PNS_NB+PN_SIZE_NB-1 downto 0); -- The final mean and range computed from the histogram. Written to memory below. signal dist_mean: std_logic_vector(PNL_BRAM_DBITS_WIDTH_NB-1 downto 0); signal dist_range: std_logic_vector(HISTO_MAX_RANGE_NB-1 downto 0); -- Error flag is set to '1' if distribution is too narrow to be characterized with the specified bounds, or the integer portion of -- a PN value is outside the range of -1023 and 1024. signal HISTO_ERR_reg, HISTO_ERR_next: std_logic; begin -- Compute the mean with full precision. Divide through by 2^12 or 4096 since that's the number of PNs we add to the sum. dist_mean <= std_logic_vector(resize(dist_mean_sum_reg/(2**NUM_PNS_NB), PNL_BRAM_DBITS_WIDTH_NB)); -- The range of the distribution is computed as the difference in the addresses which were set when the running sum of the counts in -- the histo (as we sweep left to right) became equal to the percentages we defined as the limits, e.g., 6.25% and 93.75%. -- NOTE: 'HISTO_MAX_RANGE_NB" is 12 because the number of memory elements allocated for histo memory is 2^12 = 2048, so 12 bits are -- needed to allow the range to reach 2048 (one bigger than 2047, which is 2^11). dist_range <= std_logic_vector(resize(HV_addr_reg - LV_addr_reg + 1, HISTO_MAX_RANGE_NB)); -- ============================================================================================= -- State and register logic -- ============================================================================================= process(Clk, RESET) begin if ( RESET = '1' ) then state_reg <= idle; ready_reg <= '1'; PN_addr_reg <= (others => '0'); histo_addr_reg <= (others => '0'); smallest_val_reg <= (others => '0'); LV_addr_reg <= (others => '0'); HV_addr_reg <= (others => '0'); LV_set_reg <= '0'; HV_set_reg <= '0'; dist_cnt_sum_reg <= (others => '0'); dist_mean_sum_reg <= (others => '0'); HISTO_ERR_reg <= '0'; elsif ( Clk'event and Clk = '1' ) then state_reg <= state_next; ready_reg <= ready_next; PN_addr_reg <= PN_addr_next; histo_addr_reg <= histo_addr_next; smallest_val_reg <= smallest_val_next; LV_addr_reg <= LV_addr_next; HV_addr_reg <= HV_addr_next; LV_set_reg <= LV_set_next; HV_set_reg <= HV_set_next; dist_cnt_sum_reg <= dist_cnt_sum_next; dist_mean_sum_reg <= dist_mean_sum_next; HISTO_ERR_reg <= HISTO_ERR_next; end if; end process; -- Convert the two quantities that will participate in computing the address of appropriate distribution cell that we will -- add 1 to to create the histogram. these trim off the low order 4 bits of precision of the current word on the output -- of the BRAM and the smallest_val computed in the loop below. NOTE: the RANGE MUST NEVER EXCEED +/- 1023 since we have -- ONLY 2048 memory locations dedicated to the distribution. shifted_dout <= resize(signed(PNL_BRAM_dout)/16, PN_INTEGER_NB); shifted_smallest_val <= resize(smallest_val_reg/16, PN_INTEGER_NB); -- Compute the offset address in the histo portion of memory by taking the integer portion of 'dout' - the integer portion -- of the smallest value among all PNs. This address MUST fall into the range 0 to 2047. offset_addr <= resize(shifted_dout, PNL_BRAM_ADDR_SIZE_NB) - resize(shifted_smallest_val, PNL_BRAM_ADDR_SIZE_NB); -- Add the offset computed above to the base address of the histogram portion of BRAM. histo_cell_addr <= unsigned(offset_addr) + to_unsigned(HISTO_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB); -- Compute the bounds of the distribution by adding up histo cells from left to right until the sum becomes larger/smaller than -- a 'fraction' of the total number of values counted in the histogram (which is 4096). Use 4 here to set the fraction limits to -- 6.25% and 93.75% for the LV and HV bounds. With a total count across histo cells of 4096, the bounds become 256 and 3840. LV_bound <= to_unsigned(NUM_PNs, NUM_PNS_NB+1) srl HISTO_BOUND_PCT_SHIFT_NB; HV_bound <= to_unsigned(NUM_PNs, NUM_PNS_NB+1) - LV_bound; -- ============================================================================================= -- Combo logic -- ============================================================================================= process (state_reg, start, ready_reg, PN_addr_reg, histo_addr_reg, PNL_BRAM_dout, histo_cell_addr, LV_bound, HV_bound, LV_addr_reg, HV_addr_reg, LV_set_reg, HV_set_reg, dist_cnt_sum_reg, dist_cnt_sum_next, dist_mean_sum_reg, smallest_val_reg, dist_mean, dist_range, HISTO_ERR_reg) begin state_next <= state_reg; ready_next <= ready_reg; PN_addr_next <= PN_addr_reg; histo_addr_next <= histo_addr_reg; smallest_val_next <= smallest_val_reg; LV_addr_next <= LV_addr_reg; HV_addr_next <= HV_addr_reg; LV_set_next <= LV_set_reg; HV_set_next <= HV_set_reg; dist_cnt_sum_next <= dist_cnt_sum_reg; dist_mean_sum_next <= dist_mean_sum_reg; HISTO_ERR_next <= HISTO_ERR_reg; -- Default value is 0 -- used during memory initialization. PNL_BRAM_din <= (others=>'0'); PNL_BRAM_we <= "0"; do_PN_histo_addr <= '0'; case state_reg is -- ===================== when idle => ready_next <= '1'; if ( start = '1' ) then ready_next <= '0'; -- Reset error flag HISTO_ERR_next <= '0'; -- Zero the register that will eventually define the mean. dist_mean_sum_next <= (others=>'0'); -- Allow histo_addr to drive PNL_BRAM do_PN_histo_addr <= '1'; -- Assert 'we' to zero out the first cell at 2048. PNL_BRAM_we <= "1"; histo_addr_next <= to_unsigned(HISTO_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB); state_next <= clear_mem; end if; -- ===================== -- Clear out the histo portion of memory. 'histo_addr_reg' tracks BRAM cells in (2048 to 4095) portion of memory when clear_mem => if ( histo_addr_reg = HISTO_BRAM_UPPER_LIMIT - 1 ) then -- Reset PN_addr and get first value PN_addr_next <= to_unsigned(PN_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB); state_next <= find_smallest; -- On the first iteration here, we've already initialized the first memory location in the previous state. -- Do the second, etc. else do_PN_histo_addr <= '1'; PNL_BRAM_we <= "1"; histo_addr_next <= histo_addr_reg + 1; end if; -- ===================== -- Find smallest value (this works for signed PN values, e.g., positive or negative). when find_smallest => -- Check PN value to see if it is smaller than current. On first iteration, assign 'dout' to smallest. if ( PN_addr_reg = to_unsigned(PN_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB) ) then smallest_val_next <= signed(PNL_BRAM_dout); elsif ( signed(PNL_BRAM_dout) < smallest_val_reg ) then smallest_val_next <= signed(PNL_BRAM_dout); end if; -- PN_addr_reg tracks BRAM cells in (4096 to 8191) portion of memory if ( PN_addr_reg = PN_UPPER_LIMIT - 1 ) then -- Reset PN_addr and get first value for histo construction below PN_addr_next <= to_unsigned(PN_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB); state_next <= compute_addr; else PN_addr_next <= PN_addr_reg + 1; end if; -- ===================== -- Start constructing the histogram. PN portion of memory is selected and driving 'dout' since 'do_PN_histo_addr' was set to '0' -- in previous state. when compute_addr => -- Force address to histo portion for next write to memory do_PN_histo_addr <= '1'; -- histo_cell_addr is computed outside this process. It is the integer portion of the 'dout' value minus the smallest_val among -- all PNs. THIS IS ALWAYS an address in the range of 2048 and 4095. histo_addr_next <= histo_cell_addr; -- Error check. Be sure address NEVER exceeds upper limit of histogram memory. This 'if stmt' ASSUMES histogram is NOT in the -- upper-most portion of memory (histo_addr_next in this case would wrap back to 0). if ( histo_cell_addr > HISTO_BRAM_UPPER_LIMIT - 1 ) then HISTO_ERR_next <= '1'; end if; -- Add the current PN to a sum for the mean calculation. dist_mean_sum_next <= dist_mean_sum_reg + signed(PNL_BRAM_dout); state_next <= inc_cell; -- ===================== -- Add 1 to the memory location addressed by histo_addr_next/reg when inc_cell => -- Maintain address in histo memory for the write operation do_PN_histo_addr <= '1'; -- Add 1 to the cell pointed to by histo_addr and store it back. NOTE: I DO NOT need to check for OVERFLOW here b/c it is impossible -- under the current parameters where we have at most 4096 total PN. Each cell is 16-bits so we can count to at least 2^16 = 65,536 -- unsigned so even if the entire distribution appears in one cell, it will not overflow. PNL_BRAM_we <= "1"; PNL_BRAM_din <= std_logic_vector(unsigned(PNL_BRAM_dout) + 1); state_next <= get_next_PN; -- ===================== -- Allow PN_addr to drive PNL_BRAM with new address, increment address and get next PN value when get_next_PN => -- Check for exit condition if ( PN_addr_reg = PN_UPPER_LIMIT - 1 ) then state_next <= init_dist; else PN_addr_next <= PN_addr_reg + 1; state_next <= compute_addr; end if; -- ===================== -- With all the counts computed and stored in the histo portion of memory, commense the parse from left to right. when init_dist => -- Select histo memory do_PN_histo_addr <= '1'; -- Re-initialize histo address to first element of distribution. NOTE: The first cell is guaranteed to store at least a count of 1 -- since we offset all integer portions of the PN in the distribution by subtracting the smallest value. histo_addr_next <= to_unsigned(HISTO_BRAM_BASE, PNL_BRAM_ADDR_SIZE_NB); -- Clear the addresses that will be used to compute the dist_range LV_addr_next <= (others => '0'); HV_addr_next <= (others => '0'); -- Set the 'done' indicators to '0' and sum LV_set_next <= '0'; HV_set_next <= '0'; dist_cnt_sum_next <= (others => '0'); state_next <= sweep_BRAM; -- ===================== -- With all the counts computed and stored in the upper portion of the PNL_BRAM, commense the parse from left to right -- to determine the range of the distribution. when sweep_BRAM => -- Select histo memory do_PN_histo_addr <= '1'; -- Add the count in the histo cell to the sum. NOTE: The counts are unsigned values. Note we resize to 13 bit to accomodate -- the value 4096 dist_cnt_sum_next <= dist_cnt_sum_reg + resize(unsigned(PNL_BRAM_dout), NUM_PNS_NB+1); -- Assign the LV address just when dist_cnt_sum_next becomes >= than the LV bound. LV_set also used to handle case -- where the dist_sum never becomes >= LV_bound and is therefore, never assigned (which would happen if ALL the PNs are the -- same value or span a very small range). if ( LV_set_reg = '0' and dist_cnt_sum_next >= LV_bound ) then LV_addr_next <= histo_addr_reg; LV_set_next <= '1'; end if; -- Keep assigning HV address while dist_cnt_sum_next is smaller than the bound. If it happens that the first address has a -- count that's larger than the HV_bound, then HV_set is never set. if ( dist_cnt_sum_next <= HV_bound ) then HV_addr_next <= histo_addr_reg; HV_set_next <= '1'; end if; -- Check if entire distribution has been swept. if ( histo_addr_reg = HISTO_BRAM_UPPER_LIMIT - 1 ) then state_next <= check_histo_error; else histo_addr_next <= histo_addr_reg + 1; end if; -- ===================== -- Check if the distribution is TOO narrow to be characterized by our bounds. Set the error flag if true. when check_histo_error => if ( LV_set_reg = '0' or HV_set_reg = '0' ) then HISTO_ERR_next <= '1'; state_next <= idle; -- Just store the mean and range in the lowest portion of memory (addresses 0 and 1) for transfer to C program. else do_PN_histo_addr <= '1'; histo_addr_next <= to_unsigned(HISTO_BRAM_UPPER_LIMIT - 2, PNL_BRAM_ADDR_SIZE_NB); PNL_BRAM_we <= "1"; PNL_BRAM_din <= dist_mean; state_next <= write_range; end if; -- ===================== -- Write range at address 1 when write_range => do_PN_histo_addr <= '1'; histo_addr_next <= to_unsigned(HISTO_BRAM_UPPER_LIMIT - 1, PNL_BRAM_ADDR_SIZE_NB); PNL_BRAM_we <= "1"; PNL_BRAM_din <= (PNL_BRAM_DBITS_WIDTH_NB-1 downto HISTO_MAX_RANGE_NB => '0') & dist_range; state_next <= idle; end case; end process; -- Using _reg here (not the look-ahead _next value). with do_PN_histo_addr select PNL_BRAM_addr <= std_logic_vector(PN_addr_next) when '0', std_logic_vector(histo_addr_next) when others; HISTO_ERR <= HISTO_ERR_reg; ready <= ready_reg; end beh;