2. Schritt: Simulation und Inbetriebnahme

In diesem Schritt werden wir die Testbench anpassen um die Blinkfrequenz nach Power On zu überprüfen.

Anschließend werden wir die UUT so weit anpassen, dass der Test erfolgreich durchläuft.

Das Programmgerüst enthält bereits eine einfache Ausgabe für einen Testreport. Die generierte Datei befindet sich in dem gleichen Verzeichnis, wie die Vivado-Projektdatei.

Des weiteren befinde sich dort zwei Unterprogramme zum Messen und Testen der Blinkperiode. Das Messen der Blinkperiode erfolgt in MeasureLedPeriod;

    --! measure LED period
    procedure MeasureLedPeriod(constant maxCycles : in integer; signal Led_period : out time) is
        variable i : integer;
        variable s : integer;
        variable rise_1 : integer;
        variable rise_2 : integer;
    begin
        s := 0; -- initial state
        Led_period <= 0 ns; -- timeout return value
        for i in 1 to maxCycles loop    -- timeout condition
            wait until rising_edge(Ext_Clk);        -- measure in clock ticks
            case s is
                when 0 =>               -- wait for initial condition
                    if (Led = '0') then
                        s := 1;         -- initial condition reached
                    end if;
                when 1 =>               -- wait for rising edge
                    if (Led = '1') then
                        s := 2;         -- first rising edge reached
                        rise_1 := i;    -- remember clock tick
                    end if;
                when 2 =>               -- wait for LED being off again
                    if (Led = '0') then
                        s := 3;         -- LED is off again reached
                    end if;
                when 3 =>               -- wait for rising edge
                    if (Led = '1') then
                        s := 4;         -- nothing more to do
                        rise_2 := i;    -- remember clock tick
                        Led_period <= (rise_2 - rise_1) * Clk_period;
                        wait for 10 ps; -- Let variable value settle
                        return;         -- got result
                    end if;
                when others =>
                    return;
            end case;
        end loop;
    end MeasureLedPeriod;

Ohne jetzt auf zu viele Details einzugehen wartet dieses Unterprogramm auf die erste steigende Flanke des LED Signals, merkt sich den Zeitpunkt, wartet auf die zweite steigende Flanke und errechnet daraus die Periode, die im Ausgangssignal Led_period abgelegt wird.

Das zweite Unterprogramm testet ob die erwartete Periode eingehalten wird und kümmert sich entsprechend des Ergebnisses um die Ausgabe auf den Report und den Fehlerausgang:

    --! measure LED period
    procedure CheckLedPeriod(constant expected : in time; signal LED_period : inout time;
                            signal TestErr : out std_logic) is
    begin
        MeasureLedPeriod(2*expected/Clk_period, LED_period);
        if (LED_period = expected) then
            tb_report("  Expected " & time'Image(expected) & ": passed.");
        else
            TestErr <= '1';
            tb_report("  Expected " & time'Image(expected) & "; Found " & time'Image(LED_per) & ": Failed!");
        end if;
    end CheckLedPeriod;

In der Testbench sind auch noch zwei weitere Prozesse enthalten:

    --! Generate system clock
    p_clk : process
    begin
        Ext_Clk <= not Ext_Clk;
        wait for Clk_period/2;
    end process;

    --! Check for test bench time out
    p_control : process
    begin
        wait for 20 ms;
        -- the test bench should already have terminated!
        assert false
        report "Test timed out!"
        severity failure;
    end process;

p_clk erzeugt das Taktsignal. Es handelt sich hierbei also um den externen Oszillator.

p_control sorgt dafür, dass, falls die Testbench in einer Endlosschleife gefangen wäre, der Test nach einer gewissen Zeit abgebrochen würde.

Damit ist das Handwerkszeug gegeben, um einen ersten Test durchführen zu können. Es soll getestet werden, ob die LED nach dem Start des FPGAs mit der richtigen Frequenz blinkt.

Die Zeitdefinitionen dafür sind in der Testbench bereits vorhanden. Dabei ist zu beachten, dass die Simulation ja um den Faktor 10000 schneller ablaufen soll als die Realität, 250 Hz entsprechen dadurch in der Simulation 400 ns.

    -- Time definitions
    constant Clk_period     : time := 8 ns;             --! ZYBO Clock frequency is 125 MHz
--  constant Clk_period     : time := 10 ns;            --! ARTY Clock frequency is 100 MHz
    constant LED_min_per    : time := 400 ns;           --! LED period in fastest mode
    constant LED_res_per    : time := LED_min_per*256;  --! LED period after reset
    constant LED_max_per    : time := LED_res_per*16;   --! LED period in slowest mode

In der Testbench muss nun also der Testcase 1 angepasst werden:

        TestCase <= 1;
        tb_report("Test Case 1: LED Period after power on...");
        expected_time := LED_res_per;
        CheckLedPeriod(expected_time, LED_per, TestErr);

Und die UUT muss natürlich auch angepasst werden um dem Simulationsfaktor 10.000 wenigstens in etwa gerecht zu werden. Der binäre Logarithmus von 10000 ist ca. 13,3. Also wird in BlinkingLed.vhd anstatt Bit 26 das Bit 13 auf die LED ausgegeben.

    LED <= ToggleCntR(13);

Jetzt kann die Simulation durchgeführt werden. Diese schlägt natürlich fehl, weil auf das genaue Timing keine Rücksicht genommen worden ist.

Simulationsergebnis

TestBlinkingLed.log

Testing BlinkingLed:
====================
Test Case 1: LED Period after power on...
  Expected 102400000 ps; Found 131072000 ps: Failed!
Test completed with error(s)

Inbetriebnahme der UUT

Wie in der Spezifikation angegeben soll ja ein Taktgeber mit 500 Hz (5 MHz in der Simulation) erzeugt werden, der dann einen 13 Bit Frequenzgenerator ansteuert.

Auf dem ZYBO, bei 125 MHz, muss der Taktgeber von 0 bis 249999 (in der Simulation bis 24) zählen. D.h. nach der entsprechenden Periode wird der Taktgeber zurückgesetzt und der Frequenzgenerator inkrementiert. Die LED wird dann zunächst von Bit 8 des Frequenzgenerators ausgegeben.

Folgende Anpassungen in BlinkingLed.vhd müssen also durchgeführt werden:

signal FreqGenR			: std_logic_vector(12 downto 0);	--! Generate blinking frequencies

...

    --! Flipflops with synchronous reset
    p_sync : process (Clk)
    begin
        if rising_edge(Clk) then
            if (InitR = '1') then
                ToggleCntR      <= (others => '0');
            else
--              if (ToggleCntR = 249999) then
                if (ToggleCntR = 24) then
                    ToggleCntR      <= (others => '0');
                else
                    ToggleCntR      <= ToggleCntR + 1;
                end if;
            end if;
        end if;
    end process;

    --! Flipflops with synchronous reset and clock enable
    p_ce : process (Clk)
    begin
        if rising_edge(Clk) then
            if (InitR = '1') then
                FreqGenR        <= (others => '0');
            elsif (ToggleCntR = 24) then
                FreqGenR        <= FreqGenR + 1;
            end if;
        end if;
    end process;
    LED <= FreqGenR(8);

Dabei ist natürlich zu beachten, dass die Signaldeklaration vor dem begin der architecture stehen muss. Jetzt kann die Simulation erneut durchgeführt werden und diesmal läuft sie durch.

Simulationsergebnis bei Erfolg

TestBlinkingLed.log

Testing BlinkingLed:
====================
Test Case 1: LED Period after power on...
  Expected 102400000 ps: passed.
Test completed successfully

Weiter zum 3. Schritt