This seems like an example where using multiple architectures of the same entity would help. You have a file along the lines of:
entity TestBench
end TestBench;
architecture SimpleTest of TestBench is
-- You might have a component declaration for the UUT here
begin
-- Test bench code here
end SimpleTest;
You can easily add another architecture. You can have architectures in separate files. You can also use direct entity instantiation to avoid the component declaration for the UUT (halving the work required if the UUT changes):
architecture AnotherTest of TestBench is
begin
-- Test bench code here
UUT : entity work.MyDesign (Behavioral)
port map (
-- Port map as usual
);
end AnotherTest ;
This doesn't save having duplicate code, but at least it removes one of the port map lists.
Another point if you have a lot of signals in your UUT port map, is that this can be easier if you try to make more of the signals into vectors. For example, you might have lots of serial outputs of the same type going to different chips on the board. I have seen lots of people will name these like SPI_CS_SENSORS
, SPI_CS_CPU
, SPI_CS_FRONT_PANEL
, etc. I find it makes the VHDL a lot more manageable if these are combined to SPI_CS (2 downto 0)
, with the mapping of what signal goes to what device specified by the circuit diagram. I suppose this is just preference, but maybe this sort of approach could help if you have really huge port lists.
Using a TestControl entity
A more sophisitcated approach would involve using a test control entity to implement all your stimulus. At the simplest level, this would have as ports all of the signals from the UUT you are interested in. A more sophisticated test bench would have a test control entity with interfaces that can control bus functional models that contain the actual pin wiggling required to exercise your design. You can have one file declaring this entity, say TestControl_Entity.vhd
:
entity TestControl is
port (
clk : out std_logic;
UUTInput : out std_logic;
UUTOutput : in std_logic
);
Then you have one or more architecture files, for example TestControl_SimpleTest.vhd
:
architecture SimpleTest of TestControl is
begin
-- Stimulus for simple test
end SimpleTest;
Your top level test bench would then look something like:
entity TestBench
end TestBench;
architecture Behavioral of TestBench is
signal clk : std_logic;
signal a : std_logic;
signal b : std_logic;
begin
-- Common processes like clock generation could go here
UUT : entity work.MyDesign (Behavioral)
port map (
clk => clk,
a => a,
b => b
);
TestControl_inst : entity work.TestControl (SimpleTest)
port map (
clk => clk,
UUTInput => a,
UUTOutput => b
);
end SimpleTest;
You can now change the test by changing the architecture selected for TestControl.
Using configurations
If you have a lot of different tests, you can use configurations to make it easier to select them. To do this, you first need to make the test control entity instantiation use a component declaration as opposed to direct instantiation. Then, at the end of each test control architecture file, create the configuration:
use work.all;
configuration Config_SimpleTest of TestBench is
for Behavioral
for TestControl_inst : TestControl
use entity work.TestControl (TestControl_SimpleTest);
end for;
end for;
end Config_SimpleTest;
Now when you want to simulate, you simulate a configuration, so instead of a command like sim TestBench
, you would run something like sim work.Config_SimpleTest
. This makes it easier to manage test benches with a large number of different tests, because you don't have to edit any files in order to run them.