In part one of this series, I went over my AXI4 Lite logic, and gave some idea as to what else I’ll be covering throughout the series. Now that basic register access has been established, I’ll review the core “countbits” logic.
Table of Contents
Count Bits Logic
All source covered is available here on my GitHub page. countbits.vhd is a rather simple block of RTL which takes in an AXI4 Full data bus and accumulates the number of bits set at the bus (blue), word (green), and byte (yellow) level, shown in the graphic below.
The usage would be to initiate a transfer of arbitrary length, and once complete, read the desired accumulation registers for post analysis. Again, this logic was largely selected for demonstration purposes, though it does potentially have real world value such as determining how random a bus really is.
Within this block resides the actual accumulation registers.
signal total_bits_reg : std_logic_vector(AXIL_DATA_WIDTH-1 downto 0);
signal word_bits_reg : register_file_typ(0 to (AXI_DATA_WIDTH/32)-1);
signal byte_bits_reg : register_file_typ(0 to (AXI_DATA_WIDTH/8)-1);
These registers are ultimately fed out to the AXI Lite control block. If you’re unfamiliar with generate statements, they basically let you create blocks of logic using loops. In my case, I succinctly assign the accumulation registers to the outgoing ports for both bytes and words.
gen_words_reg : for w in 0 to (AXI_DATA_WIDTH/32)-1 generate
word_bits(w) <= word_bits_reg(w);
end generate gen_words_reg;
gen_bytes_reg : for b in 0 to (AXI_DATA_WIDTH/8)-1 generate
byte_bits(b) <= byte_bits_reg(b);
end generate gen_bytes_reg;
total_bits <= total_bits_reg;
One last thing before we move onto the data_bits_proc, and that is the logic required to know when the AXI data bus is valid, and when it’s not. The master must have its wvalid high, as well as the slave having its wready high.
data_valid <= maxii_wvalid and maxio_wready;
Accumulation Process
First to note is the use of variables. Variables immediately take on the value of their assignment, as opposed to signals which take on the value of their assignment on the following clock (when in a clocked process). This is particularly useful when doing “intermediary” math or logic, in my case, counting.
variable accum_bus : std_logic_vector(31 downto 0);
variable accum_word : std_logic_vector(31 downto 0);
variable accum_byte : std_logic_vector(31 downto 0);
variable cnt : std_logic_vector(31 downto 0);
Notice I have only a single accumulator variable for word and byte, even though there are many bytes and words within the bus. This is because these variables are assigned off at each section of the bus they pertain to and zeroed back out.
The way in which bits are accumulated is by using a for loop:
--loop over all bits in the data bus
bitloop : for bt in 1 to AXI_DATA_WIDTH loop
Accumulation can now be done simply by checking the bit position for ‘1’ and incrementing the accumulator variables.
-- update counters when bit set
if(maxii_wdata(bt-1) = '1') then
accum_bus := accum_bus + 1;
accum_word := accum_word + 1;
accum_byte := accum_byte + 1;
end if;
There needs to be a way in which one can determine one byte from another, and one word from another. I chose to solve this by creating a std_logic_vector count variable to check the lower bits. If the lower 3 bits are zero, then we’re at a byte boundary. If the lower 5 bits are zero, then it’s a word boundary.
-- this is used to interpret lower bits for segregating bus into bytes/words
cnt := std_logic_vector(to_unsigned(bt, cnt'length));
...
-- if we rollover at a byte boundary (2^3 = 8)
if((cnt(2 downto 0) = "000") and (bt /= 0)) then
byte_bits_reg(byt_idx) <= byte_bits_reg(byt_idx) + accum_byte;
byt_idx := byt_idx + 1;
accum_byte := (others => '0');
end if;
-- if we rollover at a word boundary (2^5 = 32)
if((cnt(4 downto 0) = "00000") and (bt /= 0)) then
word_bits_reg(wrd_idx) <= word_bits_reg(wrd_idx) + accum_word;
wrd_idx := wrd_idx + 1;
accum_word := (others => '0');
end if;
Up Next…
Next topic will be verification of what has been covered so far. I’ll begrudgingly be using Xilinx’ AXI VIP (verification IP) for simulating an AXI Master/Slave to see how all of the logic discussed so far functions.