Many of complex IPs and components nowadays use AXI4-Lite interface for configuration. Its a simple and sufficient protocol, but one may wonder how to easily configure such components with these types of interfaces from a Test – Bench.
One of the options is to manually write a well-timed transactions from what is usually a “Stimuli” process in a TB. If there are however plenty of required register writes, this becomes somewhat a messy solution and one may start to think of a better alternative. One of the options is to include a simple AXI4 Lite master in a TB a define only the Addresses and Data to be written there. I personally found this solution quite simple and efficient. The same could be eventually made for read channel, but there are usually no requirements to use read channels at all in a TB. Code below however requires the usage of VHDL2008 standard.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
-------------------------------------------------------- ------- Beechwood.eu || Author: Vojtech Ters 2021 ------- vojta.ters@gmail.com / vojta.ters@beechwood.eu -------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; entity AXI4Lite_TB is -- Port ( ); end AXI4Lite_TB; architecture Behavioral of AXI4Lite_TB is subtype sl is std_logic; subtype slv is std_logic_vector; constant PERIOD : time := 10 ns; constant AXI_WIDTH : natural := 32; signal CLK : sl; signal RST : sl; ------------------------------------ ------- AXI4L Write Signals -------- signal AWAddr : slv(AXI_WIDTH - 1 downto 0); signal AWReady : sl; signal AWValid : sl; signal WData : slv(AXI_WIDTH - 1 downto 0); signal WValid : sl; signal WReady : sl; signal BResp : slv(1 downto 0); signal BValid : sl; signal BReady : sl; type State_t is (AXI4L_AWADDR,AXI4L_AWDATA,AXI4L_BRESP); signal AXI4L_W_State : State_t; -------------------------------------------------- -------- Custom Register Write Definitions ------- constant MAX_REGS : natural := 7; constant IND_ADDR : natural := 0; constant IND_DATA : natural := 1; constant PTR_W : natural := natural(ceil(log2(real(MAX_REGS)))); signal Trans_Cnt : slv(PTR_W - 1 downto 0); signal Trans_Max : slv(PTR_W - 1 downto 0) := slv(to_unsigned(MAX_REGS,PTR_W)); type RegWrites_t is array(0 to MAX_REGS - 1,0 to 1) of slv(AXI_WIDTH - 1 downto 0); signal RegWrites : RegWrites_t := ( (0) => (X"C0FE_BABE",X"0000_0001"), (1) => (X"BEEF_BABE",X"0000_0010"), (2) => (X"DEAD_C0DE",X"0000_0100"), (3) => (X"BAAD_F00D",X"0000_1000"), (4) => (X"DEAD_BABE",X"0001_0000"), (5) => (X"DEAD_BEAF",X"0010_0000"), (6) => (X"FEED_C0DE",X"0100_0000") ); ------------------------------------------------------------------------------------------- begin ------- -------- -------- ------- Architecture begins ------- ------- ------- ------- ------------------------------------------------------------------------------------------- TB_AXI4Lite_W_Master:process(CLK) begin if rising_edge(CLK) then if RST = '1' then WData <= (others => '0'); AWAddr <= (others => '0'); AWValid <= '0'; WValid <= '0'; BReady <= '0'; AXI4L_W_State <= AXI4L_AWADDR; Trans_Cnt <= (others => '0'); else case AXI4L_W_State is -------------------------------------------------------------- when AXI4L_AWADDR => ----------------------------------------- if unsigned(Trans_Cnt) < unsigned(Trans_Max) then AWValid <= '1'; AWAddr <= RegWrites(to_integer(unsigned(Trans_Cnt)),IND_ADDR); if AWValid = '1' and AWReady = '1' then AXI4L_W_State <= AXI4L_AWDATA; AWValid <= '0'; AWAddr <= (others => '0'); end if; end if; -------------------------------------------------------------- when AXI4L_AWDATA => ---------------------------------------- WValid <= '1'; WData <= RegWrites(to_integer(unsigned(Trans_Cnt)),IND_DATA); if WValid = '1' and WReady = '1' then Trans_Cnt <= slv(unsigned(Trans_Cnt) + 1); AXI4L_W_State <= AXI4L_BRESP; WValid <= '0'; WData <= (others => '0'); end if; -------------------------------------------------------------- when AXI4L_BRESP => ---------------------------------------- BReady <= '1'; if BReady = '1' and BValid = '1' then AXI4L_W_State <= AXI4L_AWADDR; BReady <= '0'; end if; end case; end if; end if; end process; TB_AXI4Lite_W_Slave:process(CLK) variable WriteAddress : slv(AXI_WIDTH - 1 downto 0); begin if rising_edge(CLK) then if RST = '1' then AWReady <= '0'; WReady <= '0'; BValid <= '0'; BResp <= (others => '0'); else AWReady <= '1'; WReady <= '1'; BValid <= '1'; -------------------------------------- -- Just Latch Address for Reporting -- if AWReady = '1' and AWValid = '1' then WriteAddress := AWAddr; end if; ---------------------------- -- AWADDR and AWDATA Done -- if WReady = '1' and WValid = '1' then report "Write to Address: " & to_hstring(WriteAddress) & " With Data: " & to_hstring(WData) severity note; end if; end if; end if; end process; Stimuli:process begin Rst <= '1'; wait for 100 ns; Rst <= '0'; wait; end process; ClkGen:process begin CLK <= '0'; wait for PERIOD/2; CLK <= '1'; wait for PERIOD/2; end process; end Behavioral; |
Of course: If you simulate the TB, you should see the following output from a console:
Note: Write to Address: C0FEBABE With Data: 00000001
Time: 135 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: BEEFBABE With Data: 00000010
Time: 195 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: DEADC0DE With Data: 00000100
Time: 255 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: BAADF00D With Data: 00001000
Time: 315 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: DEADBABE With Data: 00010000
Time: 375 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: DEADBEAF With Data: 00100000
Time: 435 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
Note: Write to Address: FEEDC0DE With Data: 01000000
Time: 495 ns Iteration: 1 Process: /AXI4Lite_TB/TB_AXI4Lite_W_Slave
If this solution is not enough,then an alternative would be to use VHDL textio and define addresses and data to be written in a .txt file. This would be a more re-usable solution. Note that the AXI4 Lite Slave shown in the TB supports a minimum of the AXI4 Signals and is always ready to accept data. In a real TB,this process should be switched for a custom DUT. You can read more about AXI4 / AXI4Lite specification at ARM Developer site.