Testing a DUT coded in VHDL

mtv produces a Verilog testbench (this file is called test.v by default). This Verilog testbench can instantiate and test a VHDL DUT (an entity or a configuration), but only if you have a dual-language simulator.

There is no standardised way for Verilog code to instantiate VHDL code, or vice-versa. However, there does appear to be a common set of requirements among different simulators. The specific requirements for ModelSim, for example, are as follows:

  • the VHDL design unit is an entity/architecture pair or a configuration declaration
  • the entity ports are of type bit, bit_vector, std_ulogic, std_ulogic_vector, vl_ulogic, vl_ulogic_vector, or their subtypes. The port clause may have any mix of these types
  • The generics are of type integer, real, time, physical, enumeration, or string. String is the only composite type allowed.

If the VHDL DUT conforms to the requirements for your own simulator, then the DUT can be tested if your simulators.conf file contains an entry for your dual-language simulator. If your simulator is not listed, you can construct a new entry simply by listing the required steps to carry out a dual-language batch-mode simulation. The simulators.conf entry for dual-language ModelSim is reproduced here; this should be modified as required for other simulators:

# -----------------------------------------------------------------------------
# Run a mixed-language ModelSim simulation with the Maia-generated Verilog
# testbench, and additional VHDL sources, as follows. See the 'modelsim'
# section for additional options.
# $ export RTV_SIMULATOR=modelsim_mixed
# $ rtv mytest.tv a.vhd b.vhd c.vhd ...etc
# -----------------------------------------------------------------------------
modelsim_mixed verilog {
    # set additional variables
    $set workdir = work
    $set topmod  = $workdir.$root

    # run simulation
    rm -rf $workdir
    vlib   $workdir
    echo "(rtv: log)   compiling..."
       vcom -work $workdir $sim_args
    vlog -work $workdir $vfile
    echo "run -all
          quit -f" > $workdir/runfile
    echo "(rtv: log)   running vectors..."
    vsim -quiet $topmod < $workdir/runfile
    rm $vfile
    rm -rf $workdir
    rm -f transcript

The configuration entry in this example is named modelsim_mixed. The name is arbitrary, but should not be the same as the name of any other entry in the configuration file. The 'verilog' refers to the code generator, and should be left unchanged. The commands in this entry are suitable for Unix-like operating systems. If your shell is actually Windows/cmd.exe, then you will need to use the DOS equivalents (see the MXE_DOS entry in simulators.conf for an example). The modelsim_mixed entry carries out these steps:

  • it removes any existing work directory
  • it creates a new work directory using vlib
  • if there any any VHDL files on the rtv command line ($if($sim_args)), it compiles the VHDL DUT into the work directory (which has been given the name "work"), passing any rtv arguments after the VHDL filename directly to vcom
  • it compiles the Verilog testbench produced by Maia, also into the work directory
  • it creates a batch run script in the work directory
  • it carries out a batch-mode simulation using vsim, taking the commands from the batch run script
  • it removes the Maia output file
  • it removes the work directory and the transcript. Any transcript output was stored in the Maia logfile.

Assuming that this entry is named modelsim_mixed, the testbench can now be run as:

> rtv --sim modelsim_mixed my_testbench.tv my_DUT.vhd [more VHDL files]

Or, if the RTV_SIMULATOR environment variable has the value modelsim_mixed, simply as

> rtv my_testbench.tv my_DUT.vhd [more VHDL files]

When rtv is run without switches, it expects the testbench file to have a .tv extension. Everything after the tv file is passed directly to vcom for compilation (this is the $sim_args argument); this might include multiple VHDL sources and vcom switches.

The final step is to ensure that the module declaration in your Maia DUT section correctly instantiates your VHDL entity or configuration. If we assume that the VHDL entity declaration is:

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

then the module declaration is instead simply:

   module MUX8TO1(input [2:0] SEL, input [7:0] I, output O);

The HDL coding styles article contains a number of examples of VHDL DUTs which are tested in this way.

Instantiating a configuration declaration

The DUT section can alternatively instantiate a configuration declaration, rather than an entity. This complete example is for the MUX8TO1 entity from the 'HDL coding styles' article, which now has two alternative architectures. The VHDL source includes a configuration declaration which selects one of these architectures. The Maia testbench, shown below the VHDL code, instantiates the configuration and, in this case, tests architecture A.

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;               -- unsigned and to_integer

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

architecture A of MUX8TO1 is
  -- architecture A uses a concurrent (selected) signal assignment
  with to_integer(SEL) select
    O <= I(0) when 0,
    I(1)      when 1,
    I(2)      when 2,
    I(3)      when 3,
    I(4)      when 4,
    I(5)      when 5,
    I(6)      when 6,
    I(7)      when others;
end architecture A;

architecture B of MUX8TO1 is
  -- architecture B uses a process with a case statement
  process(SEL, I) is
    case SEL is
      when "000"  => O <= I(0);
      when "001"  => O <= I(1);
      when "010"  => O <= I(2);
      when "011"  => O <= I(3);
      when "100"  => O <= I(4);
      when "101"  => O <= I(5);
      when "110"  => O <= I(6);
      when others => O <= I(7);
    end case;
  end process;
end architecture B;

-- The rest of the code is only required if you want to explicitly select the
-- required multiplexer architecture (A or B). In normal circumstances, the
-- last analysed architecture (in this case, B) is used.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity VHDL_TOP is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity VHDL_TOP;

architecture ONLY of VHDL_TOP is
  component MUX8TO1 is
    port (
      SEL : in  unsigned(2 downto 0);
      I   : in  std_logic_vector(7 downto 0);
      O   : out std_logic);
  end component MUX8TO1;
  U1 : MUX8TO1 port map (SEL, I, O);
end architecture ONLY;

configuration VHDL_CONFIG of VHDL_TOP is
  for ONLY
    for U1 : MUX8TO1
      use entity work.MUX8TO1(A);
    end for;
  end for;
end configuration VHDL_CONFIG;

The testbench is:

   module VHDL_CONFIG (input [2:0] SEL, input [7:0] I, output O);
   [SEL, I] -> [O];

void main() {
   bit3 sel;
   bit8 data;
   for all data                     // cycles data from 0 to 255
      for all sel {                 // cycles sel from 0 to 7
         bit expected = data.(sel); // or data >> sel
         [sel, data] -> [expected];

If the VHDL code is saved as mux8to1_a6.vhd , and the testbench as mux8to1_a6.tv , then the test can be run as:

  $ export RTV_SIMULATOR=modelsim_mixed
  $ rtv mux8to1_a6.tv mux8to1_a6.vhd

This should report 2048 passes, and no failures.

Current limitations

There are currently some limitations with VHDL support:

  • when instantiating an entity in the DUT section, it is not possible to directly specify which architecture to test. In principle, the MUX8TO1 entity could be specified as MUX8TO1(arch-name) but this is not currently supported. The entity name must be specified without an architecture, and the last analysed architecture is tested. If this is not suitable, a configuration should be used.
  • internal signals within the VHDL hierarchy cannot currently be tested or forced.