Full adder module and testbench¶
Unlike Heartbeat, the target hardware design in this example is written using the
synthesisable subset of VHDL. It is a full adder described in a file
named adder.vhdl
:
entity adder is
-- `i0`, `i1`, and the carry-in `ci` are inputs of the adder.
-- `s` is the sum output, `co` is the carry-out.
port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
end adder;
architecture rtl of adder is
begin
-- This full-adder architecture contains two concurrent assignments.
-- Compute the sum.
s <= i0 xor i1 xor ci;
-- Compute the carry.
co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
end rtl;
You can analyse this design file, ghdl -a adder.vhdl
, and try to execute the adder
design. But this is useless, since nothing externally visible will happen. In order to check this full adder, a
testbench has to be run. The testbench is a description of how to generate inputs and how to check the
outputs of the Unit Under Test (UUT). This one is very simple, since the adder is also simple: it checks exhaustively
all inputs. Note that only the behaviour is tested, timing constraints are not checked. A file named
adder_tb.vhdl
contains the testbench for the adder:
-- A testbench has no ports.
entity adder_tb is
end adder_tb;
architecture behav of adder_tb is
-- Declaration of the component that will be instantiated.
component adder
port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
end component;
-- Specifies which entity is bound with the component.
for adder_0: adder use entity work.adder;
signal i0, i1, ci, s, co : bit;
begin
-- Component instantiation.
adder_0: adder port map (i0 => i0, i1 => i1, ci => ci, s => s, co => co);
-- This process does the real job.
process
type pattern_type is record
-- The inputs of the adder.
i0, i1, ci : bit;
-- The expected outputs of the adder.
s, co : bit;
end record;
-- The patterns to apply.
type pattern_array is array (natural range <>) of pattern_type;
constant patterns : pattern_array :=
(('0', '0', '0', '0', '0'),
('0', '0', '1', '1', '0'),
('0', '1', '0', '1', '0'),
('0', '1', '1', '0', '1'),
('1', '0', '0', '1', '0'),
('1', '0', '1', '0', '1'),
('1', '1', '0', '0', '1'),
('1', '1', '1', '1', '1'));
begin
-- Check each pattern.
for i in patterns'range loop
-- Set the inputs.
i0 <= patterns(i).i0;
i1 <= patterns(i).i1;
ci <= patterns(i).ci;
-- Wait for the results.
wait for 1 ns;
-- Check the outputs.
assert s = patterns(i).s
report "bad sum value" severity error;
assert co = patterns(i).co
report "bad carry out value" severity error;
end loop;
assert false report "end of test" severity note;
-- Wait forever; this will finish the simulation.
wait;
end process;
end behav;
As usual, you should analyze the file, ghdl -a adder_tb.vhdl
.
Hint
Then, if required, elaborate the testbench: ghdl -e adder_tb
. You do not need to
specify which object files are required, since GHDL knows them and automatically adds them.
Now, it is time to run the testbench, ghdl -r adder_tb
, and check the result on screen:
adder_tb.vhdl:52:7:(assertion note): end of test
If your design is rather complex, you’d like to inspect signals as explained in Heartbeat.
See section Simulation options, for more details on other runtime options.