Now that we have a design that changes millions of times a second, testing becomes hard. In this module we will use the ISIM simulator - a tool that allows you to \'run' the logical design and see how it behaves as it is poked and prodded with external signals.
When you debug software you are actually running the code on the processor, with all the access to the system resources such as the OS, memory, communications and file systems. Unlike debugging software, simulating an FPGA project doesn’t run it on the actual hardware - the closest equivalent you may have experience with is the simulation of a microcontroller in MPLAB or WinAVR.
Although no FPGA hardware is involved, simulation is very powerful - it is very much like having the most powerful logic analyser at your fingertips. The downside is that if your idea of how an external device works isn’t accurate you will not be able to spot the problems.
The initially confusing bit about simulation is that it requires another VHDL module to drive the input signals and receives the outputs from your design - a module that is called a "test bench". They are pretty easy to spot - the ENTITY declaration has no "IN" or "OUT" signals, just something like this:
ENTITY TestBench IS END TestBench;
In effect, the test bench is a module that ties up all the loose ends of your design so that the simulator can run it.
Here is how to create a test bench using the wizard in WebPack.
Right-click on the top level of the hierarchy and select to add a new source module into the project:
Select the "VHDL Test Bench" and assign it a name (I just add 'tb_' to the name of the component being tested), then click \'Next':
You will then need to select which component of the design you wish to test and then click \'Next':
A summary screen will be presented - review the details and then click \'Finish'.
Here is the resulting VHDL with most of the comments removed, to reduce its size:
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
ENTITY tb_Switches_LEDs IS
END tb_Switches_LEDs;
ARCHITECTURE behavior OF tb_Switches_LEDs IS
COMPONENT Switches_LEDs
PORT(
switches : IN std_logic_vector(7 downto 0);
LEDs : OUT std_logic_vector(7 downto 0);
clk : IN std_logic
);
END COMPONENT;
--Inputs
signal switches : std_logic_vector(7 downto 0) := (others => '0');
signal clk : std_logic := '0';
--Outputs
signal LEDs : std_logic_vector(7 downto 0);
-- Clock period definitions
constant clk_period : time := 20 ns;
BEGIN
-- Instantiate the Unit Under Test (UUT)
uut: Switches_LEDs PORT MAP (
switches => switches,
LEDs => LEDs,
clk => clk
);
-- Clock process definitions
clk_process :process
begin
clk <= '0';
wait for clk_period/2;
clk <= '1';
wait for clk_period/2;
end process;
-- Stimulus process
stim_proc: process
begin
wait for 100 ns;
wait for clk_period*10;
wait;
end process;
END;
This has a few more language structures that have not been seen so far. First is a component declaration, which defines the project that is being tested - much like a C function prototype:
COMPONENT Switches_LEDs
PORT(
switches : IN std_logic_vector(7 downto 0);
LEDs : OUT std_logic_vector(7 downto 0);
clk : IN std_logic
);
END COMPONENT;
There is a "constant" declaration, which is of a "time" data type - this data type is exclusively used in simulation. If your design has a timing constraint, the value here is usually set correctly, but it pays to check:
constant clk_period : time := 20 ns;
The next stanza is creating an instance of the Switches_LEDs component, and attaching its signals to the signals within the test bench:
uut: Switches_LEDs PORT MAP (
switches => switches,
LEDs => LEDs,
clk => clk
);
And finally, two processes that contain "wait" statements. These two processes control the timing of signals within the simulation:
clk_process :process
begin
clk <= '0';
wait for clk_period/2;
clk <= '1';
wait for clk_period/2;
end process;
-- Stimulus process
stim_proc: process
begin
wait for 100 ns;
wait for clk_period*10;
wait;
end process;
The first process ('clk_process') defines the clock signal - which will stay '0' for ten (simulated) nanoseconds, then flip to '1' for ten nanoseconds - giving a 20ns (50MHz) clock. The second process ('stim_proc') is where you add statements to change the inputs of the unit under test - for example, you could use "switches ⇐ "11111111" to simulate the switches being turned on. When initially created, all inputs (other than the clock signal) are set to '0'.
Warning
|
The "wait for [time period]" cannot be realized inside an FPGA, so it is only useful inside simulations. If you use this statement outside of a testbench your design will simulate perfectly but you will not be able to implement your design in the FPGA. |
From top to bottom, switch to "Simulation" view, select the desired test bench (you can have more than one), expand the "Processes" tree, and then double-click on "Simulate Behavioral Model" - as a quirk, if you have just finished a simulation, you may need to right-click on this and choose "Run all".
The simulation will be compiled, and then the simulator tool is launched. On start-up the simulator will simulate the first microsecond:
From left to right you have the following panes:
-
Instances and processes - the design hierarchy being simulated
-
Objects - what signals are in the selected instance
-
Waveform window - a list of signals being recorded, and a graphical display of their values over time
Expand the tb_switches_leds instance in the \'Instances and processes' pane, and click on the "uut". In the "Objects" pane you will then see all the signals in your design:
The default timescale is very small - 10 or so picoseconds; You can click "zoom out" on the toolbar until you can see the clock signal ticking away:
As desired, you can drag a signal from the "Objects" pane into the waveform window, but as the signal has not been recorded you will need to click the "Reset" and then "Run for specified time" to get values displayed in the window. In this screenshot, I have dragged "counter[29:0]" from \'uut' into the waveform window and reran the simulation:
When you drag and click on the Waveform pane, the value of that signal at that time is shown - unlike when debugging code, in ISIM you can trace backwards in time!
-
Make some part of the design dependent on the state of one of the switches. Simulate the design after adding assignments to change the switch signal in the stimulus process
-
Right-click on some of the signals in the waveform window and explore the "radix" and cursor options
-
Click and drag over the waveform window to measure the duration of a signal from transition to transition
-
Click on the triangle to the left of a bus’s name. What happens?