Opdracht - testbench

Zoals bij voorgaande opdrachten, wordt ook bij deze opdracht een testbench voorzien. Aangezien de DUT (device-under-test) iets complexer is, is de gebruikte testbench ook iets complexer.

De gewoonlijke structuur is natuurlijk wel behouden: 1) libraries, 2) entity (zonder poorten), en 3) de architecture. Tussen de architecture en de begin staan de declaraties van signalen en componenten. Tussen de begin en end staat het ontwerp. Dit ontwerp staat hiernaast weergegeven.

Er is een klein stukje code dat een clock genereert en een klein stukje code dat een reset genereert.

Ter herinnering … alles wordt in parallel uitgevoerd. Beide stukjes code (voor clock en reset) zijn tegelijk actief.

Uiteraard is er de instantiatie van de DUT.

Tenslotte zijn er nog twee instantiaties van mem_model. Iets wat vaker gebeurt voor complexere blokken is dat er een model beschreven wordt. Dit model probeert de functionaliteit na te bootsen van een effectief hardware-blokje. In dit geval wordt er gebruikt gemaakt van een model voor het geheugen. Een geheugen heeft typisch de volgende poorten:

  • (clock & reset)
  • binnenkomend adres
  • binnenkomende data
  • binnenkomend signaal dat aanduidt of het om een lees/schrijf operatie gaat
  • buitengaande data

Testbench block diagram

Dit model van een geheugen is zó beschreven dat het, tijdens reset, zichzelf initialiseert met de inhoud van een bestand. Om dit te kunnen maken, wordt er gebruik gemaakt van een speciale set van instructies uit VHDL. Dit is niet-synthetiseerbare code!! Bekijk deze code zeker eens in detail.

Een ander stukje niet-synthetiseerbare code is het volgende:

    signal addr_int : integer range 0 to ((2**DATA_DEPTH_LOG2)-1);

Dit gebruikt de operatie “machtsverheffing”. De maximale waarde van de integer addr_int is (2DATA_DEPTH_LOG2-1). Dit is perfect acceptabel om te gebruiken in deze situatie, maar is geen alternatief voor de implementatie van een machtsverheffing in hardware!!

--------------------------------------------------------------------------------
-- KU Leuven - ESAT/COSIC - Emerging technologies, Systems & Security
--------------------------------------------------------------------------------
-- Module Name:     cpu_tb - Behavioural
-- Project Name:    Testbench for cpu
-- Description:     
--
-- Revision     Date       Author     Comments
-- v0.1         20240314   VlJo       Initial version
--
--------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
-- use IEEE.NUMERIC_STD.ALL;

entity cpu_tb is
end entity cpu_tb;

architecture Behavioural of cpu_tb is

    component cpu is
        port(
            clock : IN STD_LOGIC;
            reset : IN STD_LOGIC;
            instruction : IN STD_LOGIC_VECTOR(15 downto 0);
            inM : IN STD_LOGIC_VECTOR(15 downto 0);
            outM : OUT STD_LOGIC_VECTOR(15 downto 0);
            PC : OUT STD_LOGIC_VECTOR(15 downto 0);
            addressM : OUT STD_LOGIC_VECTOR(15 downto 0);
            writeM : OUT STD_LOGIC
        );
    end component;

    component mem_model is
        generic (
            DATA_WIDTH : natural := 16;
            DATA_DEPTH_LOG2 : natural := 10;
            FNAME : string := "data.dat"
        );
        port (
            reset : IN STD_LOGIC;
            clock : IN STD_LOGIC;
            ADDR : IN STD_LOGIC_VECTOR(DATA_DEPTH_LOG2-1 downto 0);
            WE : IN STD_LOGIC;
            WDATA : IN STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
            RDATA : OUT STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0)
        );
    end component;


    signal clock : STD_LOGIC;
    signal reset : STD_LOGIC;

    signal instruction : STD_LOGIC_VECTOR(15 downto 0);
    signal inM : STD_LOGIC_VECTOR(15 downto 0);
    signal outM : STD_LOGIC_VECTOR(15 downto 0);
    signal PC : STD_LOGIC_VECTOR(15 downto 0);
    signal addressM : STD_LOGIC_VECTOR(15 downto 0);
    signal writeM : STD_LOGIC;

    constant zeroes : STD_LOGIC_VECTOR(15 downto 0) := x"0000";
    constant clock_period : time := 10 ns;

begin

    -------------------------------------------------------------------------------
    -- DUT
    -------------------------------------------------------------------------------
    DUT: component cpu port map(
        clock => clock,
        reset => reset,
        instruction => instruction,
        inM => inM,
        outM => outM,
        PC => PC,
        addressM => addressM,
        writeM => writeM
    );

    -------------------------------------------------------------------------------
    -- SIMULATION MODELS
    -------------------------------------------------------------------------------
    dmem: component mem_model generic map(
        FNAME => "/home/jvliegen/vc/github/KULeuven-Diepenbeek/course_disch_internal/cpu/firmware/empty.dat"
    ) port map(
        reset => reset,
        clock => clock,
        ADDR => addressM(9 downto 0),
        WE => writeM,
        WDATA => outM,
        RDATA => inM
    );

    imem: component mem_model generic map(
        FNAME => "/home/jvliegen/vc/github/KULeuven-Diepenbeek/course_disch_internal/cpu/firmware/firmware.dat"
    ) port map(
        reset => reset,
        clock => clock,
        ADDR => PC(9 downto 0),
        WE => zeroes(0),
        WDATA => zeroes,
        RDATA => instruction
    );


    -------------------------------------------------------------------------------
    -- CLOCK
    -------------------------------------------------------------------------------
    PCLK: process
    begin
        clock <= '1';
        wait for clock_period/2;
        clock <= '0';
        wait for clock_period/2;
    end process PCLK;

    -------------------------------------------------------------------------------
    -- RESET
    -------------------------------------------------------------------------------
    PRESET: process
    begin
        reset <= '1';
        wait for clock_period*9;
        wait for clock_period/2;
        reset <= '0';
        wait;
    end process PRESET;

end Behavioural;


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- MEM MODEL
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.NUMERIC_STD.ALL;
    use IEEE.STD_LOGIC_MISC.or_reduce;
    use STD.textio.all;
    use ieee.std_logic_textio.all;

entity mem_model is
    generic (
        DATA_WIDTH : natural := 16;
        DATA_DEPTH_LOG2 : natural := 10;
        FNAME : string := "data.dat"
    );
    port (
        reset : IN STD_LOGIC;
        clock : IN STD_LOGIC;
        ADDR : IN STD_LOGIC_VECTOR(DATA_DEPTH_LOG2-1 downto 0);
        WE : IN STD_LOGIC;
        WDATA : IN STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
        RDATA : OUT STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0)
    );
end entity mem_model;

architecture Behavioural of mem_model is

    -- localised inputs
    signal reset_i : STD_LOGIC;
    signal clock_i : STD_LOGIC;
    signal ADDR_i : STD_LOGIC_VECTOR(DATA_DEPTH_LOG2-1 downto 0);
    signal WE_i : STD_LOGIC;
    signal WDATA_i : STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
    signal RDATA_i : STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);

    file fh : text;

    type T_memory is array(0 to (2**DATA_DEPTH_LOG2)-1) of STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
    signal mem : T_memory;

    signal outgoing_data : STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
    signal addr_int : integer range 0 to ((2**DATA_DEPTH_LOG2)-1);

begin

    -------------------------------------------------------------------------------
    -- (DE-)LOCALISING IN/OUTPUTS
    -------------------------------------------------------------------------------
    reset_i <= reset;
    clock_i <= clock;
    ADDR_i <= ADDR after 1 ns;
    WE_i <= WE after 1 ns;
    WDATA_i <= WDATA after 1 ns;
    RDATA <= RDATA_i;
    
    -------------------------------------------------------------------------------
    -- COMBINATORIAL
    -------------------------------------------------------------------------------
    -- RDATA_i <= outgoing_data;
    RDATA_i <= mem(addr_int);
    addr_int <= to_integer(unsigned(ADDR_i));

    -------------------------------------------------------------------------------
    -- MEMORY
    -------------------------------------------------------------------------------
    PMEM: process(reset_i, clock_i)
        variable v_line : line;
        variable v_temp : STD_LOGIC_VECTOR(DATA_WIDTH-1 downto 0);
        variable v_pointer : integer;
    begin
        if reset_i = '1' then 
            outgoing_data <= (others => '0');
            mem <= (others => (others => '0'));

            -- init the firmware
            v_pointer := 0;
            file_open(fh, FNAME, read_mode);

            while not endfile(fh) loop
                readline(fh, v_line);
                -- hread(v_line, v_temp);
                read(v_line, v_temp);
                mem(v_pointer) <= v_temp;
                v_pointer := v_pointer + 1;
            end loop;

            file_close(fh);
        elsif rising_edge(clock_i) then 
            -- write to memory
            if WE_i = '1' then 
                mem(addr_int) <= WDATA_i;
                outgoing_data <= (others => '0');
            else
                -- read from memory 
                outgoing_data <= mem(addr_int);
            end if;

        end if;
    end process;


end Behavioural;