Testbenches and Simulation
Writing VHDL testbenches to verify your circuit behavior before synthesis.
What is a Testbench?
A testbench (TB) is a VHDL simulation file that:
- Instantiates the Device Under Test (DUT)
- Generates stimuli (input signals)
- Observes and verifies outputs
A testbench has no ports - it is a closed environment.
entity tb_mux4to1 is
-- No ports!
end entity tb_mux4to1;Typical Testbench Structure
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity tb_mux4to1 is
end entity tb_mux4to1;
architecture tb of tb_mux4to1 is
-- Simulation constants
constant c_CLK_PERIOD : time := 10 ns; -- 100 MHz
-- Signals to connect to the DUT
signal w_d0, w_d1, w_d2, w_d3 : std_logic := '0';
signal w_sel : std_logic_vector(1 downto 0) := "00";
signal w_y : std_logic;
begin
-- DUT instantiation
DUT : entity work.mux4to1
port map (
i_d0 =>
Clock Generation
-- Clock generation process
p_clk : process
begin
i_clk <= '0';
wait for c_CLK_PERIOD / 2;
i_clk <= '1';
wait for c_CLK_PERIOD / 2;
end process p_clk;
-- Concurrent alternative
i_clk <= not i_clk after c_CLK_PERIOD / 2;Complete Testbench for Sequential Circuit
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity tb_counter is
end entity tb_counter;
architecture tb of tb_counter is
constant c_CLK_PERIOD : time := 10 ns;
signal i_clk : std_logic := '0';
signal i_rst : std_logic := '1';
signal o_cnt : std_logic_vector(3 downto 0);
begin
-- Clock generation
i_clk <= not i_clk after c_CLK_PERIOD / 2;
-- DUT
DUT : entity work.counter_4bit
port map
Useful Simulation Instructions
| Instruction | Usage |
|---|---|
wait for 10 ns; | Wait a fixed duration |
wait until rising_edge(i_clk); | Wait for a clock edge |
wait until i_valid = '1'; | Wait for a condition |
wait; | Stop simulation |
assert condition report "msg" severity level; | Verification |
Severity Levels
| Level | Effect |
|---|---|
note | Informational message |
warning | Warning, simulation continues |
error | Error, simulation may continue |
failure | Immediate simulation stop |
Factor Verification
When a testbench grows, avoid repeating the same assertion everywhere. A small local procedure keeps messages consistent.
Declaration (in the testbench architecture declarative region, before begin, next to simulation constants and signals):
-- Before the begin of the tb architecture
procedure check_slv(
constant i_name : in string;
constant i_observed : in std_logic_vector;
constant i_expected : in std_logic_vector
) is
begin
assert i_observed = i_expected
report i_name
& " expected=0x" & to_hstring(i_expected)
& " observed=0x" & to_hstring(i_observed)
severity error;
end procedure check_slv;Usage (inside a test process, after the architecture begin):
wait until rising_edge(i_clk);
check_slv("counter after reset", o_cnt, x"0");The testbench stays easy to read: stimuli describe the scenario, verification procedures describe the expected rules. If the procedure must be shared by several testbenches, it can later move into a package.
End Simulation with std.env
With VHDL-2008, std.env.finish cleanly terminates a simulation once the scenario is done.
library STD;
use STD.ENV.ALL;
-- ...
report "Testbench completed successfully" severity note;
finish;wait; is still acceptable for a small testbench. finish is clearer when the simulation is launched automatically by a script or CI.
Best Practices
- Name assertions clearly: clear message to identify the failure
- Verify after stabilization: wait with
wait forbeforeassert - Test edge cases: min/max values, state transitions
- Separate stimuli and verification: separate processes for clarity
- Explicit end: finish with
report "... success"thenwait;orfinish;
📝 Test your knowledge - Chapter quiz