SRAM models are used quite frequently and many devices have bus interfaces which are similar to SRAMs. It's therefore valuable to have a standard approach for modeling these interfaces.
Below is the data for a simplified SRAM:
Parameter | Description | Min | Max | Unit |
tSU | A,D valid to WE_L asserted | |
|
|
tH | WE_L deasserted to A,D invalid | |
|
|
tW_WE | WE_L asserted to WE_L deasserted | |
|
|
For the purpose of this example reading from the SRAM is ignored, and only writes are implemented. It's easier to code transactors rather than models, so initially this approach is taken.
Core of the implementation is test_prg, a process which contains the write procedure and the test program. The purpose of the write procedure is to verify the timing of a write access to the SRAM as well as to verify whether data and address are as expected.
procedure write(wadd: std_logic_vector(7 downto 0); wdat: std_logic_vector(7 downto 0) ) is variable start_cycle: time;The parameters wadd and wdat specify address and data for the expected write access.
begin D <= (others => 'Z'); wait until WE_L = '0';The data bus is assigned to 'Z' (=tristate) values. This means the SRAM model will not drive the data bus and therefore will not corrupt the data input. The procedure will wait for the start of the write access which is equivalent to waiting for WE_L to be asserted (active low).
start_cycle := now; -- check setup times assert A'last_event >= tSU report "E@SIMPLE_SRAM: Address setup time violated" severity Error; assert D'last_event >= tSU report "E@SIMPLE_SRAM: Data setup time violated" severity Error;The variable start_cycle will be assigned to the simulation time. This means it will contain the time at which WE_L was first asserted in the access cycle. (The contents of that variable will be used later.) The attribute 'last_event is very useful for testbenches, it returns the time which has expired since the last event of a signal. (Example: if WE_L was asserted at 35 ns, and the current simulation time is 73 ns, then WE_L'last_event will return 73 ns - 35 ns = 38 ns.)
In this case the A'last_event returns the time A has been stable before WE_L was asserted. This is the actual setup time and should be greater or equal to tSU. If this is not the case, the assert statement will issue the message "E@SIMPLE_SRAM: Address setup time violated". The same mechanism is used to verify the setup time for the data lines.
-- report action for transaction log print("I@SIMPLE_SRAM: "& hstr(D)& "h written to "& hstr(A)& "h");In any case the access to the SRAM will be reported. The hstr function is part of the txt_util.vhd package, of course. It returns the value of a std_logic_vector in hex format.
-- verify address assert A = wadd report "E@SIMPLE_SRAM: Address incorrect, expected "& str(wadd)& " received "& str(A) severity Error;Next the address is verified, if it's incorrect a message is issued which reports the expected and the actual value. This time it's in binary format, so that it's easier to identify which bit was corrupted.
-- verify data for i in wdat'range loop if wdat(i) /= '-' and wdat(i) /= D(i) then print("E@SIMPLE_SRAM: Write Data Invalid, written data = "& str(D)& " expected data = "& str(wdat) ); exit; end if; end loop;Somewhat more effort is put into verifying the data bus. Bits which are marked with "-" (=don't care bits) in the expected data are not compared with the actual data. This can be useful especially when modeling devices with a SRAM like interface when not all control bits at a particular address are actually relevant. (Example: wdat="00010--" and D="0001011" => not error would be indicated.)
The loop will go through all bits of the expected data word, compare or skip it and issue an error message when a discrepancy is found. In that latter case the loop is exited, to avoid reporting the same error several times.
wait until WE_L = '1'; -- verify pulse width on WE_L assert now - start_cycle >= tW_WE report "E@SIMPLE_SRAM: WE_L pulse width violated" severity Error; -- verify address and data haven't changed during the cycle assert A'last_event >= (now - start_cycle) report "E@SIMPLE_SRAM: Address hold time violated" severity Error; assert D'last_event >= (now - start_cycle) report "E@SIMPLE_SRAM: Data hold time violated" severity Error;The cycle completes when WE_L is deasserted. Now it's possible to verify the pulse width by comparing the current time with the time at the beginning of the cycle (= start_cycle). The 'last_event attribute can not be used for this purpose since that would now refer to the time expired since WE_L returned to '1' (= 0 ns).
It's now also possible to verify that A hasn't changed during the cycle. If that was the case then A'last_event would be smaller than the time which has expired since the beginning of the cycle (= now - start_cycle). The same steps are taken to verify that D hasn't changed.
-- now make sure the hold times are maintained add_hold <= true, false after tH; dat_hold <= true, false after tH; add_val <= A; dat_val <= D; end write;The cycle is not totally complete yet, as the address and data hold times have not been verified. The code above activates two separate processes (the hold time monitors) to check the values of A and D.
-- hold time monitors add_monitor: process begin wait until A'event; assert not add_hold or A = add_val report "E@SIMPLE_SRAM: Address hold time violated" severity Error; end process add_monitor;
As long as add_hold is true (which it will be for a time tH after WE_L was deasserted) the hold time memory will verify the value of A, each time an event on A occurs. The simulation diagram below shows how the signals add_hold and dat_hold are true (activating the hold time monitors) for 3 ns (the value of tH) and then return to false (deactivating the monitors).
Below are the files which have been simulated in this section:
txt_util.vhd | simple_sram.vhd | stim_gen.vhd | tb_simple.vhd |