I've heard belligerent opinions of testability that say you should be able to fully test your design from your test bench stimulus without need to resort to forcing internal signals. I don't subscribe to this "never" opinion. It is certainly easier and in the main more practical and hence desirable. These benefits will tend to ensure that the use of external signal forcing is minimised, perhaps so it is rarely used. In the ASIC world, manufacturing fault simulations are required to detect ≥ 95% of a design's registers toggling in both directions as proof of correct manufacture. To achieve this coverage it is sometimes necessary to add "test pins" to increase internal visibility and hence increase the detection coverage. (That is unless design "security" does not dictate no additional test pins.) Therefore I have no scruples about the use of signal spies or external signals to aid testing. This blog aims to illustrate practical ways of using both ModelSim's Signal Spies and VHDL-2008's External Signals, and explore some of the methods and features.
The code I'm going to test is a very basic single clock cycle delay on an integer in two forms, one as a VHDL integer and the other as std_logic_vector. This demonstrates both an unresolved and a resolved type being forced, mainly to check if there are any differences.
library ieee;
use ieee.std_logic_1164.all;
entity dut_register is
port(
clk : in std_logic;
reset : in std_logic;
int_in : in natural range 0 to 15;
vec_in : in std_logic_vector(3 downto 0);
int_out : out natural range 0 to 15;
vec_out : out std_logic_vector(3 downto 0)
);
end entity;
architecture rtl of dut_register is
signal int_out_i : natural range 0 to 15;
signal vec_out_i : std_logic_vector(3 downto 0);
begin
process(clk)
begin
if rising_edge(clk) then
if reset = '1' then
int_out_i <= 0;
vec_out_i <= "0000";
else
int_out_i <= int_in;
vec_out_i <= vec_in;
end if;
end if;
end process;
-- Make copies
vec_out <= vec_out_i;
int_out <= int_out_i;
end architecture;
A very basic "Device Under Test" using both a resolved type and an unresolved type.
ModelSim "Signal Spies" Library
First we'll explore the Signal Spy procedures in package modelsim_lib.util, then the Signal Force procedures, and finally a fuller test bench intended to be compared with VHDL-2008's external signals usage. The ModelSim library containing the procedures we'll investigate is given below.
package util is
-----------------------------------------------------------------------
-- SIGNAL SPY
-----------------------------------------------------------------------
type del_mode is (MTI_INERTIAL, MTI_TRANSPORT);
type forcetype is (default, deposit, drive, freeze);
alias mti_default is default [return forcetype]; -- Used for VHDL2008 compatability
attribute builtin_subprogram : string;
procedure disable_signal_spy (
source_signal : IN string ;
destination_signal : IN string ;
verbose : IN integer := 0
);
attribute builtin_subprogram of disable_signal_spy : procedure is "disable_signal_spy_vhdl";
procedure enable_signal_spy (
source_signal : IN string ;
destination_signal : IN string ;
verbose : IN integer := 0
);
attribute builtin_subprogram of enable_signal_spy : procedure is "enable_signal_spy_vhdl";
procedure init_signal_spy (
source_signal : IN string ;
destination_signal : IN string ;
verbose : IN integer := 0;
control_state : IN integer := -1
);
attribute builtin_subprogram of init_signal_spy : procedure is "init_signal_spy_vhdl_wc";
procedure init_signal_driver (
source_signal : IN string ;
destination_signal : IN string ;
delay : IN time := 0 ps;
delay_mode : IN del_mode := MTI_INERTIAL;
verbose : IN integer := 0
);
attribute builtin_subprogram of init_signal_driver : procedure is "init_signal_driver_vhdl";
procedure signal_force (
destination_signal : IN string ;
force_value : IN string;
force_delay : IN time := 0 ps;
force_type : IN forcetype := default;
cancel_period : IN time := -1 ms;
verbose : IN integer := 0
);
attribute builtin_subprogram of signal_force : procedure is "signal_force_vhdl";
procedure signal_release (
destination_signal : IN string ;
verbose : IN integer := 0
);
attribute builtin_subprogram of signal_release : procedure is "signal_release_vhdl";
-- Additional package section omitted
end;
Extract of library modelsim_lib from $MODEL_TECH\..\vhdl_src\modelsim_lib\mti_util.vhd.
Signal Spy Procedures
The purpose of this code is to explore the differences between init_signal_spy and init_signal_driver, and how enable_signal_spy and disable_signal_spy are used with the former. init_signal_driver should actually be considered alongside the signal forcing procedures for correct categorisation except the desire to demonstrate a subtle difference here, and the force procedures are more focussed on the types of force.
library ieee;
use ieee.std_logic_1164.all;
library modelsim_lib;
library local;
use local.testbench.all;
package util_commands_pkg is
constant verbose_c : integer := 0;
type spy_state is (ENABLED, DISABLED);
procedure init_spies;
procedure force_tests (
signal clk : in std_logic;
signal state : out spy_state
);
end package;
package body util_commands_pkg is
procedure init_spies is
begin
-- If we wait 15 clocks we get the following error.
-- # ** Error: (vsim-3861) enable_signal_spy [.../test_util_comands.vhdl] : Unable to find an associated init_signal_spy to enable/disable.
-- control_state
--
-- Optional integer. Possible values are -1, 0, or 1. Specifies whether or not you want the
-- ability to enable/disable mirroring of values and, if so, specifies the initial state.
--
-- * -1 - no ability to enable/disable and mirroring is enabled. (default)
-- * 0 - turns on the ability to enable/disable and initially disables mirroring.
-- * 1 - turns on the ability to enable/disable and initially enables mirroring.
modelsim_lib.util.init_signal_spy(
source_signal => "/test_util_comands/test_subject1",
destination_signal => "/test_util_comands/spy1", -- Mirror
verbose => verbose_c,
control_state => 0 -- Start disabled
);
-- For the VHDL init_signal_driver procedure, when driving a Verilog net, the only delay_type
-- allowed is inertial. If you set the delay type to mti_transport, the setting will be
-- ignored and the delay type will be mti_inertial.
modelsim_lib.util.init_signal_driver(
source_signal => "/test_util_comands/test_subject2", -- Driver
destination_signal => "/test_util_comands/spy2inertial5", -- Driven
-- With 'MTI_INERTIAL', works up to 10 ns which gives a 1 clock period delay. After that
-- the inertial effect seems to kick in with the value being some function of the signal
-- history.
delay => 5 ns,
-- Either modelsim_lib.util.MTI_INERTIAL or modelsim_lib.util.MTI_TRANSPORT
delay_mode => modelsim_lib.util.MTI_INERTIAL,
verbose => verbose_c
);
modelsim_lib.util.init_signal_driver(
source_signal => "/test_util_comands/test_subject2", -- Driver
destination_signal => "/test_util_comands/spy2inertial11", -- Driven
-- Ensure less than the rate of change!
delay => 11 ns,
-- Either modelsim_lib.util.MTI_INERTIAL or modelsim_lib.util.MTI_TRANSPORT
delay_mode => modelsim_lib.util.MTI_INERTIAL,
verbose => verbose_c
);
modelsim_lib.util.init_signal_driver(
source_signal => "/test_util_comands/test_subject2", -- Driver
destination_signal => "/test_util_comands/spy2transport", -- Driven
-- No constraint on 'delay' parameter with 'MTI_TRANSPORT'
delay => 15 ns,
-- Either modelsim_lib.util.MTI_INERTIAL or modelsim_lib.util.MTI_TRANSPORT
delay_mode => modelsim_lib.util.MTI_TRANSPORT,
verbose => verbose_c
);
-- Demonstrate the difference between init_signal_spy & init_signal_driver
modelsim_lib.util.init_signal_driver(
source_signal => "/test_util_comands/test_driver", -- Driver
destination_signal => "/test_util_comands/test_driven", -- Driven
delay => 3 ns,
delay_mode => modelsim_lib.util.MTI_INERTIAL,
verbose => verbose_c
);
end procedure;
procedure force_tests (
signal clk : in std_logic;
signal state : out spy_state
) is
begin
-- See what it looks like at the start
state <= DISABLED;
wait_nr_ticks(clk, 10);
modelsim_lib.util.enable_signal_spy(
source_signal => "/test_util_comands/test_subject1",
destination_signal => "/test_util_comands/spy1",
verbose => verbose_c
);
state <= ENABLED;
wait_nr_ticks(clk, 10);
modelsim_lib.util.disable_signal_spy(
source_signal => "/test_util_comands/test_subject1",
destination_signal => "/test_util_comands/spy1",
verbose => verbose_c
);
state <= DISABLED;
wait_nr_ticks(clk, 10);
modelsim_lib.util.enable_signal_spy(
source_signal => "/test_util_comands/test_subject1",
destination_signal => "/test_util_comands/spy1",
verbose => verbose_c
);
state <= ENABLED;
wait_nr_ticks(clk, 10);
modelsim_lib.util.disable_signal_spy(
source_signal => "/test_util_comands/test_subject1",
destination_signal => "/test_util_comands/spy1",
verbose => verbose_c
);
state <= DISABLED;
wait_nr_ticks(clk, 10);
end procedure;
end package body;
Packaged test procedure for signal spies.
entity test_util_comands is
end entity;
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench_pkg.all;
library modelsim_lib;
architecture test of test_util_comands is
constant verbose_c : integer := 0;
signal clk : std_logic;
signal test_subject1 : std_logic;
signal spy1 : std_logic;
signal test_subject2 : std_logic := '0';
signal spy2inertial5 : std_logic; -- 5 ns inertial delay
signal spy2inertial11 : std_logic; -- 11 ns inertial delay
signal spy2transport : std_logic; -- 15 ns transport delay
signal test_driven : std_logic;
signal test_driver : std_logic;
signal state : work.util_commands_pkg.spy_state;
begin
clock(clk, 5 ns, 5 ns);
wiggle_r(test_subject1, clk, 0.5);
wiggle_r(test_subject2, clk, 0.5);
wiggle_r(test_driver, clk, 0.4);
spy1 <= 'X'; -- Does not compete with mirrored value from signal spy below. Get's overridden
test_driven <= '1'; -- Competing driver causes 'X'.
-- For VHDL procedures, you should place all init_signal_spy calls in a VHDL process and code
-- this VHDL process correctly so that it is executed only once. The VHDL process should not be
-- sensitive to any signals and should contain only init_signal_spy calls and a simple wait
-- statement. The process will execute once and then wait forever, which is the desired behavior.
--
-- In practice you just need the calls to init_signal_spy before enable_signal_spy and
-- disable_signal_spy.
process
begin
-- As long as the spy is initalised *before* a call to enable_signal_spy or disable_signal_spy there's no error
wait_nr_ticks(clk, 2);
work.util_commands_pkg.init_spies;
work.util_commands_pkg.force_tests(clk, state);
stop_clocks;
wait;
end process;
end architecture;
Testing the signal spy procedures.
Starting with the init_signal_spy call, according to ModelSim User's Manual, these should be bunched together in their own sequential process so they are called at the start of the simulation once, i.e. the process hangs at the final wait. Actually the calls works just as well outside any process. The main concern seems to be that the init_signal_spy call is made before any calls to init_signal_driver and enable_signal_spy. So if you wait a short while to initialise like I did above, that is actually fine.
control_state
Description
-1
No ability to enable/disable and mirroring is enabled. (default)
0
Turns on the ability to enable/disable and initially disables mirroring.
1
Turns on the ability to enable/disable and initially enables mirroring.
Meaning of the control_state parameter.
Taking the first section of signals in the image below, you can see how when the state changes to ENABLED, spy1 mirrors the activity of test_subject1. When the state changes to DISABLED, the spy signal remains at the last value it had prior to disabling. This works only when you set the control_state parameter to 0 or 1, so that the enable_signal_spy and disable_signal_spy work without error. I am not sure when it is advantageous to enable and disable the mirroring of a remote signal's value. The default of -1 seems to be more than adequate for any real usage circumstances. That's an easy 3 out of the 4 calls covered.
Testing the two modes of "signal spy", init_signal_spy vs init_signal_driver.
The second section of signals shows the effect of the parameters for init_signal_driver. Here we do not just mirror the value of the remote signal, but make a connection that enables us to force it. The driver is the source signal in the first parameter and the driven is the destination signal in parameter 2. Then assigning the driver causes the driven to change after some delay (third procedure parameter). Above I have demonstrated the effect of this delay using both delay_modes, MTI_INERTIAL and MTI_TRANSPORT. In the simulation you can see the expected delays on both spy2intertial5 and spy2transport of 5 ns and 15 ns respectively. Remember the clock period used here is 10 ns. When I drive spy2inertial11 with an 11 ns second delay you do not get a copy of the test_subject2 signal. This strangeness happens the moment the delay is greater than the clock period. As far as I can work out, the driven signal changes state only if 11 ns previously (substitute your delay parameter) the driver was at a different state to the driven both before and after the sample point, hence it is inertial in behaviour. I am bemused as to when you might take advantage of this situation. In fact, I see no point in any delay other than 0 ns when using this in test benches to force a testable condition.
For the VHDL init_signal_driver procedure, when driving a Verilog net, the only delay_type allowed is MTI_INERTIAL. If you set the delay type to MTI_TRANSPORT, the setting will be ignored and the delay type will be MTI_INERTIAL.
Ref: ModelSim User's Manual, Software Version 10.5b
Finally, as we can now use one signal to drive another remotely I show the standard effect of a resolved type, test_driven, driven from two sources, a constant '1' and the init_signal_driver call's source signal. This is to be compared with spy1 which is driven by a concurrent assignment to value 'X' and then overridden by the mirrored value from the init_signal_spy call, never to return to its original value after the Signal Spy is disabled.
Signal Force Procedures
As you might expect, the VHDL library implementation of ModelSim's forces maps very closely to the TCL commands, and hence the existing underlying implementation. It is useful to get a clear idea of the three force types ModelSim will ask you to select from. These are listed below, and then illustrated with a VHDL test bench to produce a simulation screen shot.
Force
Definition
Deposit
Sets the item to the specified value. The value remains until there is a subsequent driver transaction, or until the item is forced again, or until it is unforced with a noforce
command.
Freeze
Freezes the item at the specified value until it is forced again or until it is unforced with a noforce command.
Drive
Attaches a driver to the item and drives the specified value until the item is forced again or until it is unforced with a noforce command.
This option is illegal for unresolved signals.
Three force type in ModelSim signal forces. Ref: ModelSim User's Manual, Software Version 10.5b
If one of the 'freeze', 'drive', or 'deposit' options is not used, then 'freeze' is the default for unresolved items and 'drive' is the default for resolved items. If you prefer 'freeze' as the default for resolved and unresolved VHDL signals, change the default force kind in the DefaultForceKind preference variable in modeslsim.ini:
; Default force kind. May be freeze, drive, deposit, or default
; or in other terms, fixed, wired, or charged.
; A value of "default" will use the signal kind to determine the
; force kind, drive for resolved signals, freeze for unresolved signals
DefaultForceKind = freeze
Where to change the default force type used.
entity test_force is
end entity;
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench_pkg.all;
library modelsim_lib;
architecture test of test_force is
constant verbose_c : integer := 0;
signal clk : std_logic;
signal deposit : std_logic;
signal freeze : std_logic;
signal drive : std_logic;
type force_state is (INITIALISED, FORCED, ASSERTED, RELEASED);
signal state : force_state;
begin
clock(clk, 5 ns, 5 ns);
process
begin
state <= INITIALISED;
deposit <= '0';
freeze <= '0';
drive <= '0';
wait_nr_ticks(clk, 3);
state <= FORCED;
report "Forcing";
modelsim_lib.util.signal_force(
destination_signal => "/test_force/deposit",
force_value => "1", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.deposit, -- freeze | drive | deposit
cancel_period => -1 ms, -- FORCE_DELAY + Increment
verbose => verbose_c
);
modelsim_lib.util.signal_force(
destination_signal => "/test_force/freeze",
force_value => "1", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze, -- freeze | drive | deposit
cancel_period => -1 ms, -- FORCE_DELAY + Increment
verbose => verbose_c
);
modelsim_lib.util.signal_force(
destination_signal => "/test_force/drive",
force_value => "1", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.drive, -- freeze | drive | deposit
cancel_period => -1 ms, -- FORCE_DELAY + Increment
verbose => verbose_c
);
wait_nr_ticks(clk, 3);
state <= ASSERTED;
report "Re-asserting";
deposit <= '0';
freeze <= '0';
drive <= '0';
wait_nr_ticks(clk, 3);
state <= RELEASED;
report "Releasing";
modelsim_lib.util.signal_release (
destination_signal => "/test_force/deposit",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_force/freeze",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_force/drive",
verbose => verbose_c
);
wait_nr_ticks(clk, 3);
stop_clocks;
wait;
end process;
end architecture;
Example of separate force and release procedure calls of different force types.
The VHDL above produces the following simulation waveform, that illustrates the difference between the three forces. deposit gets overridden the next time an assignment is made. freeze retains its value until it is removed. drive competes with any other drivers, hence only makes sense for resolved types.
Showing the three different types of ModelSim forces.
freeze feels like the most useful, where you have most control. deposit looks useful for making a state 'jump' and then continue from another part of the sequence. drive does not immediately seem to offer much utility.
Fuller Example for Comparison
This test bench simulation shows the propagation of a value through a single register stage only. The timing of the application and release of the forced signals is reflected in the state signal. This example highlights a significant difference between Signal Spies and External Signals to be explained later.
State
Application
FORCE1
Forcing the external signal on input to the component.
FORCE2
Forcing the internal signal of the input to the component.
FORCE3
Forcing the internal signal of the output from the component.
FORCE4
Forcing the external signal output from the component.
The stages of force application in this test bench.
library ieee;
use ieee.std_logic_1164.all;
library modelsim_lib;
library local;
use local.testbench.all;
package signal_spies_pkg is
constant verbose_c : integer := 0;
type force_state is (RELEASED, FORCE1, FORCE2, FORCE3, FORCE4);
procedure init_spies;
procedure force_tests (
signal clk : in std_logic;
signal state : out force_state
);
end package;
package body signal_spies_pkg is
procedure init_spies is
begin
modelsim_lib.util.init_signal_spy(
source_signal => "/test_signal_spies/dut/int_out_i",
destination_signal => "int_spy",
verbose => work.signal_spies_pkg.verbose_c,
control_state => -1
);
modelsim_lib.util.init_signal_spy(
source_signal => "/test_signal_spies/dut/vec_out_i",
destination_signal => "vec_spy",
verbose => work.signal_spies_pkg.verbose_c,
control_state => -1
);
end procedure;
procedure force_tests (
signal clk : in std_logic;
signal state : out force_state
) is
begin
state <= FORCE1;
report "Forcing 'cnt_in' part 1";
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/int_in",
force_value => "1", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.deposit, -- freeze | drive | deposit
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/vec_in",
force_value => "0001", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.deposit,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
wait_nr_ticks(clk, 5);
state <= FORCE2;
report "Forcing 'cnt_in' part 2";
-- # ** Warning: (vsim-8780) Forcing /test_signal_spies/int_in as root of /test_signal_spies/dut/int_in specified in the force.
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/dut/int_in",
force_value => "2", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
-- # ** Warning: (vsim-8780) Forcing /test_signal_spies/vec_in as root of /test_signal_spies/dut/vec_in specified in the force.
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/dut/vec_in",
force_value => "0010", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
wait_nr_ticks(clk, 5);
state <= FORCE3;
report "Forcing 'cnt_in' part 3";
-- # ** Warning: (vsim-8780) Forcing /test_signal_spies/int_out as root of /test_signal_spies/dut/int_out specified in the force.
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/dut/int_out",
force_value => "3", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
-- # ** Warning: (vsim-8780) Forcing /test_signal_spies/vec_out as root of /test_signal_spies/dut/vec_out specified in the force.
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/dut/vec_out",
force_value => "0011", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
wait_nr_ticks(clk, 5);
state <= FORCE4;
report "Forcing 'cnt_in' part 4";
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/int_out",
force_value => "4", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
modelsim_lib.util.signal_force(
destination_signal => "/test_signal_spies/vec_out",
force_value => "0100", -- NB. VHDL string type, don't use '0' for bits.
force_delay => 0 ns,
force_type => modelsim_lib.util.freeze,
cancel_period => -1 ms, -- force_delay + Increment
verbose => verbose_c
);
wait_nr_ticks(clk, 5);
report "Releasing 'cnt_in'";
state <= RELEASED;
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/int_in",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/vec_in",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/dut/int_in",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/dut/vec_in",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/dut/int_out",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/dut/vec_out",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/int_out",
verbose => verbose_c
);
modelsim_lib.util.signal_release (
destination_signal => "/test_signal_spies/vec_out",
verbose => verbose_c
);
wait_nr_ticks(clk, 5);
end procedure;
end package body;
Packaged test procedure for a fuller example of Signal Spies for comparison with External Signals (below).
entity test_signal_spies is
end entity;
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench_pkg.all;
library modelsim_lib;
architecture test of test_signal_spies is
signal clk : std_logic;
signal reset : std_logic;
signal int_in : natural range 0 to 15;
signal vec_in : std_logic_vector(3 downto 0);
signal int_out : natural range 0 to 15;
signal int_spy : natural range 0 to 15;
signal vec_out : std_logic_vector(3 downto 0);
signal vec_spy : std_logic_vector(3 downto 0);
signal state : work.signal_spies_pkg.force_state := work.signal_spies_pkg.RELEASED;
begin
dut : entity work.dut_register
port map (
clk => clk,
reset => reset,
int_in => int_in,
vec_in => vec_in,
int_out => int_out,
vec_out => vec_out
);
clock(clk, 5 ns, 5 ns);
-- For VHDL procedures, you should place all init_signal_spy calls in a VHDL process and code
-- this VHDL process correctly so that it is executed only once. The VHDL process should not be
-- sensitive to any signals and should contain only init_signal_spy calls and a simple wait
-- statement. The process will execute once and then wait forever, which is the desired behavior.
--
-- These procedures work fine outside a process too. The requirement to be inside an execute
-- once only process seems to be overstated.
process
begin
work.signal_spies_pkg.init_spies;
-- Initialise the inputs
state <= work.signal_spies_pkg.RELEASED;
reset <= '0';
int_in <= 0;
vec_in <= "0000";
wait_nr_ticks(clk, 1);
reset <= '1';
wait_nr_ticks(clk, 1);
reset <= '0';
wait_nr_ticks(clk, 3);
work.signal_spies_pkg.force_tests(clk, state);
stop_clocks;
wait;
end process;
end architecture;
Process calling signal spies test procedure.
The code above drives the port from the test bench (outside signal) and the internal signal of the DUT (inside signal) with two different signal_force calls. As you might expect the two signals are treated as a wire and change together. So when the outside signal changes from 0 to 1, so the the inside signal and when the inside signal is forced to 2, so is the outside signal. The same happens on the output port when the inside signal is driven to 3 and the outside signal is driven to 4. The release state shows the return to the initial 0, with a 2 being clocked out from the moment of release when the forced value of 4 returns to 2 which has been registered from the forced input before the outputs were forced. The same scenario is tested with External Signals later.
Signal Spies: Example use of forces along a chain of nets either side of a register. This example uses a process in the test bench.
More precisely, the connection point at which forcing has been applied to those signal on the inside of the DUT has been referred to the outside of the DUT because the outside signals are considered to be the root. You then get this warning to tell you.
** Warning: (vsim-8780) Forcing /test_signal_spies/int_in as root of /test_signal_spies/dut/int_in specified in the force.
The ModelSim warning for driving a non-root version of a signal treated as a wire.
The verror command further expands on the meaning of this warning.
$ verror vsim-8780
vsim Message # 8780:
This warning is suppressed by default in the modelsim.ini file.
Commenting out the suppress statement will activate the message.
The TCL force command follows Verilog semantics of a single wire.
This warning will appear when force applies the wire model to force
a higher level signal instead of the one specified in the force command.
verror description for vsim-8780.
Conclusions for ModelSim Signal Spies
The init_signal_spy seems the easiest way to tap an internal signal to observe from a test bench. There seems little point in enabling and disabling this signal tap, so using a control_state of -1 is simplest. Possibly the simplest way to force a signal is with a signal_force call using the freeze force type and a cancel_period specified for the force removal. Note the cancel_period is a time relative to now and must be greater than the force_delay parameter which is also a time relative to now. If you want to use signal_release at a chosen time later in your test bench then cancel_period must be set to -1 ms (the default if not specified).
Signal Spy References
Ref: ModelSim User's Manual, Software Version 10.5b
VHDL External Signals
VHDL-2008 introduced its own means to observe and force signals buried within a design from an unconnected external position, creating a standard tool agnostic way to do this. This new functionality seems to be officially called external signals, but ModelSim refers to them as hierarchical referencing and Doulos refers to them as hierarchical names.
Observers
Here's a very basic external signal observer that could be used as the equivalent of a test pin to read some internal state that is not so easy to propagate to an existing output, e.g. a condition that in theory should never occur, but that you check for anyway as an error condition.
signal int_spy : natural range 0 to 15;
-- Later
int_spy <= << signal .test_external_signals.dut.int_out_i : natural range 0 to 15 >>;
A signal assignment to continuously observe an internal signal from an external position.
You can also perform a assignment to a local signal or variable inside a process and expect the assignment to take place with the expected timings, e.g. on wait for a signal and immediately for a variable. Another way to both observe and force a signal without using <<..>> all the time is to use an alias.
alias int_spy_alias : natural range 0 to 15 is << signal .test_external_signals.dut.int_out_i : natural range 0 to 15 >>;
A local 'alias' to continuously observe an internal signal from an external position.
I find the alias method particularly convenient apart from two annoyances. Firstly you will get a pointless warning from ModelSim which seems to make absolutely no difference to the outcome except a pollution of the transcript. Secondly, I have observed what I assume must be a bug in larger designs where the use of the alias caused a component's output to be seemingly forced to a vector of zeros ("00..00"). When I changed the method of observing over to a signal assignment the problem went away, i.e. the output vector was able to change value again.
# ** Warning: (vsim-8523) Cannot reference the signal "/test_external_signals/dut/int_out_i" before it has been elaborated.
# Time: 0 ps Iteration: 0 Instance: /test_external_signals File: .../Signal_Spies/test_external_signals.vhdl
The ModelSim warning for the VHDL alias as setup in the previous figure.
The ModelSim warning for the VHDL alias defined above, but if you ignore the warning the simulation works well enough. The verror command further expands on the meaning of this warning.
$ verror vsim-8523
vsim Message # 8523:
A signal must have been elaborated before it can be referenced in an
external name.
The default value for the type of the signal has been used.
[DOC: IEEE Std 1076-2008 VHDL LRM - 8.7 External names]
[DOC: IEEE Std 1076-2008 VHDL LRM - 14.2 Elaboration of a design hierarchy]
verror description for vsim-8523.
Forcing Signals
Here's an example of using an aliased external signal as the target of a force within the standard stimulus process.
process
-- Illustration of use of aliases as a short hand when forcing
alias int_in_tb_alias : natural range 0 to 15 is << signal .test_external_signals.int_in : natural range 0 to 15 >>;
begin
-- Initialise the inputs
-- OMITTED CODE
int_in_tb_alias <= force 1;
wait until rising_edge(clk);
stop_clock;
wait;
end process;
Use of an aliased external signal with a forced assignment.
If you do not assign within a process but instead as a concurrent statement you are in for either a compiler error for unresolved types or the result of resolution from multiple drivers for the resolved types.
begin
-- # ** Error: (vsim-3344) Signal "/test_external_signals/dut/int_out_i" has multiple drivers but is not a resolved signal.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals/dut
-- # ** Note: Signal "/test_external_signals/dut/int_out_i" has an existing driver:
-- # Process: /test_external_signals/line__66
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals/dut
-- # No Design Loaded!
<< signal .test_external_signals.dut.int_out_i : natural range 0 to 15 >> <= 0;
-- Causes 'X's in multiply driven net(s)
<< signal .test_external_signals.dut.vec_out_i : std_logic_vector(3 downto 0) >> <= "0000";
Issues when forcing external signals continuously as concurrent statements.
The second assignment alone compiles fine and then competes with other drivers on the nets as shown below. This behaviour is entirely as should be expected. So make sure the force assignments are inside a process as sequential statements unless you are treating the external signal as a tri-stated bus and sourcing a variation in forced assignment from something that cooperates nicely with the internal bus transactions.
Example of a doubly forced signal.
Force Mode
Now there is supposed to be more to the force assignment than illustrated here, but you might be wondering why Doulos merely mention the existence of the force mode without actually explaining it? So firstly a bit of a literature review, then an attempt to demonstrate the difference.
v <= force in '1'; -- force effective value
v <= force out '0'; -- force driving value
v <= release in; -- release effective value
v <= release out; -- release driving value
Doulos' explanation for the two different force assignments.
I'm sure those on the VHDL standards committee know exactly what that means. I do not.
Our discussion of force assignments has so far focused on signals. We can also force and release ports of a design, since they are a form of signal. However, for a port, we distinguish between the driving value and the effective value. The driving value is the value presented externally by an entity, and is determined by the internal sources within the entity. The effective value is the value seen internally by an entity and is determined by whatever is externally connected to the port, whether that be an explicitly declared signal or a port of an enclosing entity. Depending on the port mode and the external connections, the driving and effective values may be different. For example, an inout-mode port of type std_logic might drive a '0' value, but the externally connected signal might have another source driving a '1' value. In that case, the resolved value of the signal is 'X', and that value is seen as the effective value of the inout-mode port.
If we omit the force mode (out or in) in a force or release assignment, a default force mode applies. For assignments to ports and signal parameters of mode in and to explicitly declared signals, the default force mode is in, forcing the effective value. For assignments to ports of mode out, inout, or buffer, and to signal parameters of mode out or inout, the default force mode is out, forcing the driving value.
I'm quite unconvinced by the explanation of the difference between the effective (seen by a component on its inputs) and driving values (output by a component and observed externally). What does this look like in practice and how and when do we explicitly choose these force modes?
Fuller Example for Comparison
This test bench simulation shows the propagation of a value through a single register stage only. The timing of the application and release of the forced signals is reflected in the state signal.
State
Application
FORCE1
Forcing the external signal on input to the component. Effective Value.
FORCE2
Forcing the internal signal of the input to the component.
FORCE3
Forcing the internal signal of the output from the component.
FORCE4
Forcing the external signal output from the component. Driving value.
The stages of force application in this test bench.
The force mode is explored by applying the two different types explicitly. After much experimentation, this formulation was chosen as being the only way to find a difference to demonstrate.
VHDL Signal
Force Mode
int_*
in
vec_*
out
Force mode selection for each of the two VHDL types.
Called From a Process
entity test_external_signals_process is
end entity;
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench_pkg.all;
architecture test of test_external_signals_process is
signal clk : std_logic;
signal reset : std_logic;
signal int_in : natural range 0 to 15;
signal vec_in : std_logic_vector(3 downto 0);
signal int_out : natural range 0 to 15;
signal int_spy : natural range 0 to 15;
signal vec_out : std_logic_vector(3 downto 0);
signal vec_spy : std_logic_vector(3 downto 0);
-- # ** Warning: (vsim-8523) Cannot reference the signal "/test_external_signals_process/dut/int_out_i" before it has been elaborated.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_process File: .../Signal_Spies/test_external_signals_process.vhdl
alias int_spy_alias : natural range 0 to 15 is << signal .test_external_signals_process.dut.int_out_i : natural range 0 to 15 >>;
-- # ** Warning: (vsim-8523) Cannot reference the signal "/test_external_signals_process/dut/vec_out_i" before it has been elaborated.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_process File: .../Signal_Spies/test_external_signals_process.vhdl
alias vec_spy_alias : std_logic_vector(3 downto 0) is << signal .test_external_signals_process.dut.vec_out_i : std_logic_vector(3 downto 0) >>;
-- Illustration of use of aliases as a short hand when forcing
alias int_in_tb_alias : natural range 0 to 15 is << signal .test_external_signals_process.int_in : natural range 0 to 15 >>;
alias int_in_dut_alias : natural range 0 to 15 is << signal .test_external_signals_process.dut.int_in : natural range 0 to 15 >>;
alias int_out_dut_alias : natural range 0 to 15 is << signal .test_external_signals_process.dut.int_out : natural range 0 to 15 >>;
alias int_out_tb_alias : natural range 0 to 15 is << signal .test_external_signals_process.int_out : natural range 0 to 15 >>;
type force_state is (RELEASED, FORCE1, FORCE2, FORCE3, FORCE4);
signal state : force_state := RELEASED;
begin
-- VHDL External Signal
int_spy <= << signal .test_external_signals_process.dut.int_out_i : natural range 0 to 15 >>;
vec_spy <= << signal .test_external_signals_process.dut.vec_out_i : std_logic_vector(3 downto 0) >>;
-- Causes 'X's in multiply driven net(s) when defined as a vector of '0's and '1's.
<< signal .test_external_signals_process.dut.vec_out_i : std_logic_vector(3 downto 0) >> <= "ZZZZ";
dut : entity work.dut_register
port map (
clk => clk,
reset => reset,
int_in => int_in,
vec_in => vec_in,
int_out => int_out,
vec_out => vec_out
);
clock(clk, 5 ns, 5 ns);
process
begin
-- Initialise the inputs
reset <= '0';
int_in <= 0;
vec_in <= "0000";
wait_nr_ticks(clk, 1);
reset <= '1';
wait_nr_ticks(clk, 1);
reset <= '0';
wait_nr_ticks(clk, 3);
state <= FORCE1;
report "Forcing 'cnt_in' part 1";
int_in_tb_alias <= force in 1;
<< signal .test_external_signals_process.vec_in : std_logic_vector(3 downto 0) >> <= force out "0001";
wait_nr_ticks(clk, 5);
state <= FORCE2;
report "Forcing 'cnt_in' part 2";
int_in_dut_alias <= force in 2;
<< signal .test_external_signals_process.dut.vec_in : std_logic_vector(3 downto 0) >> <= force out "0010";
wait_nr_ticks(clk, 5);
state <= FORCE3;
report "Forcing 'cnt_in' part 3";
int_out_dut_alias <= force in 3; -- Does now propagate to external output, unlike in process in 'test_external_signals_process'
<< signal .test_external_signals_process.dut.vec_out : std_logic_vector(3 downto 0) >> <= force out "0011";
wait_nr_ticks(clk, 5);
state <= FORCE4;
report "Forcing 'cnt_in' part 4";
int_out_tb_alias <= force in 4;
<< signal .test_external_signals_process.vec_out : std_logic_vector(3 downto 0) >> <= force out "0100";
wait_nr_ticks(clk, 5);
report "Releasing 'cnt_in'";
state <= RELEASED;
int_in_tb_alias <= release in;
<< signal .test_external_signals_process.vec_in : std_logic_vector(3 downto 0) >> <= release out;
int_in_dut_alias <= release in;
<< signal .test_external_signals_process.dut.vec_in : std_logic_vector(3 downto 0) >> <= release out;
int_out_dut_alias <= release in;
<< signal .test_external_signals_process.dut.vec_out : std_logic_vector(3 downto 0) >> <= release out;
int_out_tb_alias <= release in;
<< signal .test_external_signals_process.vec_out : std_logic_vector(3 downto 0) >> <= release out;
wait_nr_ticks(clk, 5);
stop_clocks;
wait;
end process;
end architecture;
Forcing internal state using an external signal reference from a test bench's stimulus process.
Illustrated below is the initial value of 0 changing with the FORCE1 value of 1 propagating to the register's output. The second FORCE2 stage value of 2 likewise propagating with one cycle delay. The FORCE3 stage clobbers the register's output until it release when it reverts back to 2. The same for the FORCE4 stage where the output changes to 4 and back to 2 on release. The unforced register output (of the forced input) being retained by int_out_i and vec_out_i signals, showing how the outputs should revert on release. With all four signal forces in place, each stage of forcing gives a different value along the standard propagation of the input value to output. This is quite different to the ModelSim Signal Spies model of working.
External signals: Example use of forces along a chain of nets either side of a register. This example uses a process in the test bench.
After much experimentation I managed to find a place where the force mode made a difference. Forcing the effective value (force in) of the internal signal attached to the output (/test_external_signals/dut/int_out) means the value does not propagate out of the component externally. This might have utility when wanting to contain the force within a component and not let it pollute the rest of the design. Otherwise I find that avoiding selection of the force mode and allowing the default to be selected generally does what you want.
Procedure Called From a Process
For large test benches, you are probably going to want to split up large sequential processes and re-use code with procedures. The good news is, the external signal references seem to work from anywhere, just as with ModeslSim Signal Spies.
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench.all;
package external_signals_pkg is
type force_state is (RELEASED, FORCE1, FORCE2, FORCE3, FORCE4);
procedure force_tests (
signal clk : in std_logic;
signal state : out force_state
);
end package;
package body external_signals_pkg is
procedure force_tests (
signal clk : in std_logic;
signal state : out force_state
) is
-- Illustration of use of aliases as a short hand when forcing
alias int_in_tb_alias : natural range 0 to 15 is << signal .test_external_signals.int_in : natural range 0 to 15 >>;
alias int_in_dut_alias : natural range 0 to 15 is << signal .test_external_signals.dut.int_in : natural range 0 to 15 >>;
alias int_out_dut_alias : natural range 0 to 15 is << signal .test_external_signals.dut.int_out : natural range 0 to 15 >>;
alias int_out_tb_alias : natural range 0 to 15 is << signal .test_external_signals.int_out : natural range 0 to 15 >>;
begin
state <= FORCE1;
report "Forcing 'cnt_in' part 1";
int_in_tb_alias <= force in 1;
<< signal .test_external_signals.vec_in : std_logic_vector(3 downto 0) >> <= force out "0001";
wait_nr_ticks(clk, 5);
state <= FORCE2;
report "Forcing 'cnt_in' part 2";
int_in_dut_alias <= force in 2;
<< signal .test_external_signals.dut.vec_in : std_logic_vector(3 downto 0) >> <= force out "0010";
wait_nr_ticks(clk, 5);
state <= FORCE3;
report "Forcing 'cnt_in' part 3";
int_out_dut_alias <= force in 3; -- Does now propagate to external output, unlike in process in 'test_external_signals'
<< signal .test_external_signals.dut.vec_out : std_logic_vector(3 downto 0) >> <= force out "0011";
wait_nr_ticks(clk, 5);
state <= FORCE4;
report "Forcing 'cnt_in' part 4";
int_out_tb_alias <= force in 4;
<< signal .test_external_signals.vec_out : std_logic_vector(3 downto 0) >> <= force out "0100";
wait_nr_ticks(clk, 5);
report "Releasing 'cnt_in'";
state <= RELEASED;
int_in_tb_alias <= release in;
<< signal .test_external_signals.vec_in : std_logic_vector(3 downto 0) >> <= release out;
int_in_dut_alias <= release in;
<< signal .test_external_signals.dut.vec_in : std_logic_vector(3 downto 0) >> <= release out;
int_out_dut_alias <= release in;
<< signal .test_external_signals.dut.vec_out : std_logic_vector(3 downto 0) >> <= release out;
int_out_tb_alias <= release in;
<< signal .test_external_signals.vec_out : std_logic_vector(3 downto 0) >> <= release out;
wait_nr_ticks(clk, 5);
end procedure;
end package body;
Moving the signal force code all the way out to a packaged procedure.
entity test_external_signals_procedure is
end entity;
library ieee;
use ieee.std_logic_1164.all;
library local;
use local.testbench_pkg.all;
architecture test of test_external_signals_procedure is
signal clk : std_logic;
signal reset : std_logic;
signal int_in : natural range 0 to 15;
signal vec_in : std_logic_vector(3 downto 0);
signal int_out : natural range 0 to 15;
signal int_spy : natural range 0 to 15;
signal vec_out : std_logic_vector(3 downto 0);
signal vec_spy : std_logic_vector(3 downto 0);
-- # ** Warning: (vsim-8523) Cannot reference the signal "/test_external_signals_procedure/dut/int_out_i" before it has been elaborated.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_procedure File: .../Signal_Spies/test_external_signals_procedure.vhdl
alias int_spy_alias : natural range 0 to 15 is << signal .test_external_signals_procedure.dut.int_out_i : natural range 0 to 15 >>;
-- # ** Warning: (vsim-8523) Cannot reference the signal "/test_external_signals_procedure/dut/vec_out_i" before it has been elaborated.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_procedure File: .../Signal_Spies/test_external_signals_procedure.vhdl
alias vec_spy_alias : std_logic_vector(3 downto 0) is << signal .test_external_signals_procedure.dut.vec_out_i : std_logic_vector(3 downto 0) >>;
signal state : work.external_signals_pkg.force_state := work.external_signals_pkg.RELEASED;
begin
-- VHDL External Signal
int_spy <= << signal .test_external_signals_procedure.dut.int_out_i : natural range 0 to 15 >>;
vec_spy <= << signal .test_external_signals_procedure.dut.vec_out_i : std_logic_vector(3 downto 0) >>;
-- # ** Error: (vsim-3344) Signal "/test_external_signals_procedure/dut/int_out_i" has multiple drivers but is not a resolved signal.
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_procedure/dut
-- # ** Note: Signal "/test_external_signals_procedure/dut/int_out_i" has an existing driver:
-- # Process: /test_external_signals_procedure/line__66
-- # Time: 0 ps Iteration: 0 Instance: /test_external_signals_procedure/dut
-- # No Design Loaded!
-- << signal .test_external_signals_procedure.dut.int_out_i : natural range 0 to 15 >> <= 0;
-- Causes 'X's in multiply driven net(s)
<< signal .test_external_signals_procedure.dut.vec_out_i : std_logic_vector(3 downto 0) >> <= "ZZZZ";
dut : entity work.dut_register
port map (
clk => clk,
reset => reset,
int_in => int_in,
vec_in => vec_in,
int_out => int_out,
vec_out => vec_out
);
clock(clk, 5 ns, 5 ns);
process
begin
-- Initialise the inputs
reset <= '0';
int_in <= 0;
vec_in <= "0000";
wait_nr_ticks(clk, 1);
reset <= '1';
wait_nr_ticks(clk, 1);
reset <= '0';
wait_nr_ticks(clk, 3);
work.external_signals_pkg.force_tests(clk, state);
stop_clocks;
wait;
end process;
end architecture;
Forcing internal state using an external signal reference from a procedure called from with a test bench's stimulus process.Example use of forces along a chain of nets either side of a register. This example uses a procedure called from within the test bench.
I do note a potential simulator bug here where in the FORCE3 stage, the force in does propagate its value to its output when previously it did not. Surely these should behave the same way? Well, it does as long as you always use the default force mode and never specify any alternative mode.
Conclusions for External Signals
If you use the same External Signal many times, create an alias for the internal signal name reference for convenience, but watch out for simulator bugs, i.e. the observed signal with an unwanted force. You can fall back to the signal assignment method.
Specifically for the force mode:
Serial buses such as I2C, USB and FireWire have bidirectional connections to the bus' physical wires. This allows a device to drive the clock and data wires when transmitting data and to sense the clock and data values when receiving. A testbench can model a broken data driver connection by forcing a 'Z' value on the output part of the bidirectional port, while allowing the input part of the port to operate normally.
The answer to where utility comes from with the force mode seems to lie in the more involved scenario of inout ports and tri-state buses. Otherwise, my own conclusion is that you ignore the force mode and always allow the default mode to be selected, which will cover most cases well enough given the intent is to top up the standard testing by driving devices under test via their inputs and measuring their outputs.
The force model is different between the two options. ModelSim Signal Spy forces can be applied in three modes. VHDL-2008's External Signals only have one mode, the equivalent of ModelSim's freeze.
With ModelSim Signal Spies, the signals either side of a port are treated as a wire with the inside net reference be referred to the outside (root) signal. With VHDL-2008 External Signals the nets either side of the port are treated as separate.
VHDL-2008's External Signals make a rather unnecessary distinction between a net's in and out force that for the most part can be ignored, but might have some practical utility for tri-state buses.
ModelSim's implementation of VHDL External Signals seems to have a minor inconsistency in behaviour, that seems to be of little consequence for most purposes. ModelSim also has a bug with the use of aliases where a signal spy affects the signal's value (not recreated here).
Both implementations allow the their procedures to be packaging up into user defined procedures without needing to remain local to the externally defined signal names, i.e. in the top level test bench.
There seems to be one significant claimed advantage of ModelSim Signal Spies over VHDL-2008 External Signals as follows:
With the VHDL-2008 standard, VHDL supports hierarchical referencing as well. However, you cannot reference from VHDL to Verilog. The Signal Spy procedures and system tasks provide hierarchical referencing across any mix of Verilog, VHDL and/or SystemC, allowing you to monitor (spy), drive, force, or release hierarchical objects in mixed designs.
Ref: ModelSim User's Manual, Software Version 10.5b
Clearly other HDL simulator software applications are available and have been omitted here. I use QuestaSim at work, and a free ModelSim at home that comes bundled with Intel's Quartus Prime WebPack. Therefore I only have the means to access one HDL simulator and no need at present to use the perfectly good offerings from other vendors.