2

I have various designs that use an FPGA on a parallel bus with a Microcontroller. For each design I have a testbench were I simulate several Read/write operations on the bus, using procedures that emulates the MCU timing.

I'd like to know a good way to put these procedures in a package for easier reuse. Right now, the procedures are defined and acts on signals within the scope of the testbench entity. I'd rather have something like this.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.mcu_sim.all; -- contains MCU component and procedures for bus R/W operations

entity tb is
end tb;

architecture a of tb is
    -- DUT
    component fpga is 
        port (
        clk, rst: in std_logic;
        Data: inout std_logic_vector(7 downto 0);
        Addr: in std_logic_vector(15 downto 0);
        wr: in std_logic;
        rd: in std_logic);
    end component;

signal      clk, rst: std_logic;
-- Bus signals 
signal      Data: std_logic_vector(7 downto 0);
signal      Addr: std_logic_vector(15 downto 0);
signal      rd: std_logic;
signal      wr: std_logic;

begin

    dut: fpga
    port map (

        clk => clk,
        rst => rst,
        Data => Data,
        Addr => Addr,
        wr => wr,
        rd => rd
    );

    mcu1: mcu
    port map (

        clk => clk,
        rst => rst,
        Data => Data,
        Addr => Addr,
        wr => wr,
        rd => rd
    );


    process
    begin
        clk <= '0';
        wait for 0.5 us;
        clk <= '1';
        wait for 0.5 us;
    end process;

    stimulus: process
    begin
        rst <= '1', '0' after 1 us; 

        -- A list of nice, easy-to-read procedure calls to control the MCU
        -- Defined in package mcu_sim: procedure buswrite(data: in std_logic_vector(7 downto 0); addr: in std_logic_vector(15 downto 0));

        buswrite(X"01", X"0000"); -- Command for mcu to take control of bus and do a write operation to the fpga
        buswrite(X"02", X"0001"); -- Command for mcu to take control of bus and do a write operation to the fpga

        wait;
    end process;

end a;

The package mcu_sim would contain everything needed for emulating the MCU bus operations, and I can easily taylor my stimulus program using the procedure calls. I realize that it would require the procedures to control whats happening inside mcu1. Is it possible to do this?

If not, how would you make reusable procedures for test stimuli?

Lasse
  • 25
  • 3

2 Answers2

3

You can put procedures in a package. However, to do so, you need to do two things:

i) You must split the procedure into two parts. The procedure declaration, including the name, parameters and return type, goes in the package declaration. The procedure body, which repeats the subprogram declaration and adds the implementation of the subprogram, goes in the package body.

ii) Your procedure must have a complete parameter list: the parameter list must include all signals and variables read by the procedure and all signals and variables assigned to by it.

package mcu_sim is

  procedure buswrite(
    data_in       : in  std_logic_vector(7 downto 0); 
    addr_in       : in  std_logic_vector(15 downto 0);
    -- you will need to add all the MCU I/O here to give you a complete parameter list, eg
    signal Data   : out std_logic_vector(7 downto 0);
    signal Addr   : out std_logic_vector(15 downto 0);
    signal rd     : out std_logic;
    signal wr     : out std_logic    
    );

end package mcu_sim;

package body mcu_sim is

  procedure buswrite(
    data_in       : in  std_logic_vector(7 downto 0); 
    addr_in       : in  std_logic_vector(15 downto 0);
    -- you will need to add all the MCU I/O here to give you a complete parameter list, eg
    signal Data   : out std_logic_vector(7 downto 0);
    signal Addr   : out std_logic_vector(15 downto 0);
    signal rd     : out std_logic;
    signal wr     : out std_logic    
    ) is
  begin
    -- the code
  end procedure buswrite;

end package body mcu_sim;

So, your stimulus process would become something like:

stimulus: process
begin
    rst <= '1', '0' after 1 us; 

    buswrite(X"01", X"0000", Data, Addr, rd, wr); 
    buswrite(X"02", X"0001", Data, Addr, rd, wr); 

    wait;
end process;
Matthew Taylor
  • 13,365
  • 3
  • 17
  • 44
  • This has a problem : `data` and `Data` form a name clash, because VHDL is case-insensitive, thanks to its roots going back to a time before you could assume lower-case was available. (And also because relying on case sensitivity to differentiate names is just ASKING for confusion!) –  Oct 18 '16 at 12:45
  • So it does! I'll change the names. – Matthew Taylor Oct 18 '16 at 13:11
  • Wow, this is the first time I've asked a question in here, impressed by the quick and good replies from both of you! It was a tough call but Matthew got the points for correct answer as he got closest to what I wanted for the end result. I have also given you both upvotes. – Lasse Oct 19 '16 at 10:05
  • @Lasse looks like you "accept"ed mine by mistake - Matthew's is the big step, mine just a refinement (and not original either, I believe the credit goes to Jonathan Bromley on comp.arch.fpga, ca 2005) –  Oct 19 '16 at 10:10
2

Matthew Taylor's answer is essentially the right approach on how to package the procedures.

It has the disadvantage of resulting in a more cluttered testbench such as

buswrite(X"01", X"0000", Data, Addr, rd, wr); 
buswrite(X"02", X"0001", Data, Addr, rd, wr); 

when you wanted to write something much cleaner, like

buswrite(X"01", X"0000"); 
buswrite(X"02", X"0001"); 

but can't, because the signals Data, Addr, rd, wr aren't in scope in the package.

If you remember that VHDL allows operator and subprogram overloading based on argument and return type signatures, you'll see that you can add simpler procedure calls that call the packaged forms, without ambiguity.

Either in your testbench's declarative region (after the signal declaration), or your stimulus process declarative region, all these signals ARE in scope.

So you can write a trivial procedure in either of these declaration regions (depending on how many processes need to see it)

procedure buswrite(
    data_in : in std_logic_vector(7 downto 0); 
    addr_in: in std_logic_vector(15 downto 0)) is
begin
    buswrite( data_in, addr_in, Data, Addr, rd, wr); 
end busWrite;

and now you can keep your testbench code clean.