GPU Introduction

GPU ist ein Grafikprozessor. Also ein Prozessor, mit spezifischem Profil. Hersteller sind Intel, AMD und Nvidia.

CPU <-> GPU
Wird von einem Host gesprochen, meint man damit die CPU (inkl. Memory). Ein C-Programm läuft auf dem Host.

Cuda <-> Metal
Cuda ist das Framework und die Sprache von Nvidio. Will man eine GPU für ein iOS konfigurieren, so braucht man metal als Framework und swift als Sprache.

Testbench compile.do ausführen

Die Testbench wird am einfachsten über die Konsole über das Script gestartet. (Mit tab selbständig vervollständigen)

QuestaSim>   do <pfad>./compile_<projekt>.do
QuestaSim>   do ../simulation/script/compile_<projekt>.do


Script

Das Script compile.do , gibt den Befehl zum Einbinden der Bibliothek, zum Kompilieren der Dateien und startet schlussendlich die testbench.
Das Script liegt im Ordner Work . Alle Pfade bezieht sich vom Ort des Scripts aus.

# create work library
vlib work

# compile files
vcom -2008 -explicit -work work ../../source/counter.vhdl
vcom -2008 -explicit -work work <pfad>/componente_1.vhd
vcom -2008 -explicit -work work <pfad>/top_level.vhd

# compile testbench
vcom -2008 -explicit -work work <pfad>/testbench_top_level.vhd

# run the simulation
vsim -t 1ns -lib work work.testbench_entity

do ../scripts/wave_projekt.do

run 3000.0 ns

Die Reihenfolge der zu kompilierenden Dateien spielt eine Rolle:
– Zuerst die Bibliotheken und Packete
– Dann VHDL: tiefstes Level (spezfischste Komponente)
– Dann VHDL: höheres Level (Zusammenfügender Block)
– Am Schluss: Top-Level
– Testbench

Beim Kompilieren spielt die Version eine Rolle. Gebraucht man process(all), so wird mit der Version 2008 kompiliert. Für die std_textio-Bibliothek braucht man oft 2002, damit man keine Probleme der Doppeldefinition erhält.

Testbench Aufbau

In VHDL gehört die Simulation (schreiben einer Testbench) fix zur Entwicklung dazu. Die Testbench ist in VHDL geschrieben und liegt im Ordner vhdl.
Achtung: Nie in Quartus-Projekt einbinden.  Quartus kann waits und asserts nicht kompilieren.
Ausgeführt (kompiliert) wird die Testbench über die Konsole vom Simlationsprogramm (per Script):

Simulationsprogramm
Bei altera ist questasim die Vollversion des Simulationsprogramms.

work
Einbinden der Bibliothek

Ablage
Im Projektordner wird ein Ordner simulation angelegt. In diesen Ordner kommt – über das Einbinden der Bibliothek  work – der Unterordner Work.
In diesen legt man das Script zum Starten der Simulation compile.do .


Testbench Datei

– die entity hat keine Ports. Sie besteht nur aus Signalen
– die IOs jeder Componente werden an Signale gehängt
– die Testsignale (stimuli) werden in processen gesetzt
– Signale dürfen nur Signalen zugewiesen werden
– die Tests (Vergleichsergebnis von Signalen)
.  finden in den Prozessen statt. Die Processe haben keine Klammern

stimuli:   sind selbst entworfene Signalverläufe
waits:     bestimmen die zeitliche Abfolge unter den
.                  Signalen
reports: geben die Resultat aus
.                  (Konsole oder in File (textbasiertes Testing).
clk:         der clk muss simuliert (erzeugt) werden.
.                 Dies geschieht durch einen eigenen clk-prozess

clk_generator  : process
begin
.   wait for 1 * clk_halfp;
.       clock_sig <= '1';
.   wait for 1 * clk_halfp;
.       clock_sig <= '0';
end process;
entity ram_tb is
end ram_tb;


architecture struct of ram_tb is

---------------------------------------------------------------
-- component declaration
---------------------------------------------------------------
component ram
port(
.    address     : IN STD_LOGIC_VECTOR (15 DOWNTO 0);
.    clock       : IN STD_LOGIC  := '1';
.    data        : IN STD_LOGIC_VECTOR (7 DOWNTO 0);
.    wren        : IN STD_LOGIC ;
.    q           : OUT STD_LOGIC_VECTOR (7 DOWNTO 0)
);
end component;


---------------------------------------------------------------
-- signal instantiation
---------------------------------------------------------------

signal address_sig : STD_LOGIC_VECTOR (15 DOWNTO 0);
signal clock_sig   : STD_LOGIC  := '1';
signal data_sig    : STD_LOGIC_VECTOR (7 DOWNTO 0);
signal wren_sig    : STD_LOGIC ;
signal q_sig       : STD_LOGIC_VECTOR (7 DOWNTO 0);

signal clk_halfp   : time := 10 ns;
signal clk_period  : time := 20 ns;

-- eventuell constanten mit resultaten definieren

begin
---------------------------------------------------------------
-- component instantiation
---------------------------------------------------------------

inst_1 : ram
port map (
.   address  => address_sig,
.   clock    => clock_sig,
.   data     => data_sig,
.   wren     => wren_sig,
.   q        => q_sig
);

---------------------------------------------------------------
-- Set stimuli in process with timing
---------------------------------------------------------------
test_1: process(all)
.    -- Initialwerte setzen

.    -- Signale an Komponenten legen (Stimulation)

.   -- Warten

.   -- Signal von Ausgang Komponente abfangen

.   -- Vergleichen des Outputs mit Erwartung

.   -- Ausgabe Testresulat

end; -- test_1

end; -- architektur

Wie man die Tests startet, wird unter dem Eintrag Testbench ausführen erklärt. Eine professioneller Form des Testens basiert auf textbasierten Scripts.

Hier wird die Syntax der Testbench-Fehler (assert, report, severity,..) beschrieben.

VHDL: textbasierte Test: Aufbau File

File-Variablen definieren
Sie werden in dem Process definiert.

use std.textio.all;
use work.std_logic_textio.all;  // Kompilieren mit 2002

// other code bevor....

--------------------------------------------------------------------
-- testbased test 
--------------------------------------------------------------------
execute_file: process

    -------------------------------------
    -- File handling
    -------------------------------------
    file input_file: TEXT;       
    file output_file: TEXT;    
    
    variable line_in,line_out: Line; 
    variable good: boolean;   
    
    -- token structure: 
    -- type | note_1 | velc_1 | note_2 | velc_2 | numb_note | other
    VARIABLE token_type_n: string(1 to 5);    
    VARIABLE token_note_1: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_velocity_1: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_note_2: std_logic_vector(7 downto 0) :=(OTHERS => '0');
    VARIABLE token_nmbr_note_out: std_logic_vector(3 downto 0) :=(OTHERS=> '0');
    VARIABLE token_others: std_logic_vector(3 downto 0) :=(OTHERS=> '0');
    

Wie ein Inputfile aufgebaut werden soll, steht im Beitrag zum Inputfile.

 

File öffnen und Ende erkennen
– In VHDL werden Files zeilenweise eingelesen (siehe std_logic_textio)
– Eingelesen wird in einem Process

execute_file: process

-- zuerst File variablen (siehe oben)

BEGIN
    FILE_OPEN(input_file,"../simulation/script/input_midi.txt", READ_MODE);
    FILE_OPEN(output_file,"../simulation/script/result_midi.txt", WRITE_MODE);

 loop    
        ---------------------------------
        -- Check end of file
        --------------------------------
        if endfile(input_file) then  
                
                -- output console read line numbers      
                write(line_out, string'("Number of read lines from file:"));
                writeline(OUTPUT,line_out);                                  
                write(line_out, line_cnt);            
                writeline(OUTPUT,line_out);      
                assert false
                report "End of test"
                severity FAILURE;
            exit;
        end if;

Unmittelbar danach folgt (ebenfalls im loop)

Tokens einlesen

        -----------------------------------
        -- Read 10 token in (and check for error)
        ------------------------------------
        readline(input_file, line_in);     
        line_cnt <= line_cnt + 1;
        
        -- Skip empty lines
        next when line_in'length = 0;

        read(line_in, token_type_n, good);    
            assert good
            report "Error command (type) failed."
            severity ERROR;   
         
        hread(line_in, token_note_1, good);  
            assert good
            report "Error token (note_1) failed."
            severity ERROR;

Zuerst wird zeilenweise eingelsen (und die Zeilen gezählt.)
Dann teilt man die Wörter in Tokens ein. Je nach Tokentyp wird eine andere read-Funktion genommen.

Test durchführen
– Das Vergleichen der Werte einen getakteten Process
– Signale werden ausgelesen und mit Variablenwerten verglichen

test: process(all)
begin

 loop
    if rising_edge(clk) then
        -- Abarbeiten 1. Test ----------------------------------
        if (testtyp_n = 1) then

          -- Signale einlesen
          sig_wert1   <= puls_1;
          sig_wert2   <= data_slc;
          sig_result1 <= adder_out;

          wait for 2 * period;

          -- Test Wert
          if ( sig_wert1 /= wert_n) then

             -- Falls Fehler: Ausgabe in Zeile
             hwrite(line_out, wert_n); 
             write(line_out, string'(" nicht korrekt") ); 

          -- Test Ergebnis     
          elsif ( sig_result1 /= ergebnis_n) then

             -- Falls Fehler: Ausgabe in Zeile
             hwrite(line_out, ergebnis_n);
             write(line_out, string'("Ergebnis falsch"));

          -- Test fehlerfrei
          else
             zeilen_nr <= zeilen_nr + 1;
             hwrite(line_out, testtyp_n)
             write(line_out, string'(" fehlerfrei")); 
          end if;

       -- Abarbeiten 2. Test -----------------------------------
       elsif (testtyp_n = 2) then

            -- Signalwerte auslesen 
            
            -- Vergleich mit Variablen

            -- Ergebnis in Zeile schreiben

      -- Ende Abarbeiten Tests
      else
            assert false
            report "Unbekannter Testtyp"
            severity FAILURE;

     end if;    

     -- Zeile in File schreiben
     writeline(out_file, line_out);

 end loop; 

wait; 
end process;

Good Practice
Arbeitet man mit textbasiertem Debugging, so lässt man das Konsolendebugging (mit ASSERTS) weitmöglichst weg.

Textbasiertes Testing: Aufbau Inputfile

File mit Testwerten

1 00000001 00000001 00000002 0
1 FFFFFFFF FFFFFFFF 00000000 1
2 00000004 00000005 FFFFFFFE 1
2 FFFFFFFF FFFFFFFF 00000000 0

wr_ram 0001 00F1  11
wr_ram 0002 0015  11
wr_rm  0002 F632  10

rd_ram 0001 00F1  99
rd_ram 0002 F615  99

reset 00 00 00 00 00 00 00 00 00
check 00 00 00 00 00 00 00 00 00
singl 13 01 00 00 00 00 00 00 00
check 13 00 00 00 00 00 00 00 00

Die Daten werden entweder als string  mit der Funktion read(<von>,<zu>)  oder als Hex-Zahl hread(<von>,<zu>)  eingelesen (vgl. std_logic_textio.vhd).

Richtige Zahlenwerte eintragen
Weil die Zahlen als Hex interpretiert werden, entspricht eine 0  4 Bits. Und die Zahl 10  entspricht dem dezimalen Wert 15.

Aus diesem Grund enstehen folgende Anzahl Bits:
00  im File    ->      std_logic_vector(7 downto 0)  beim Einlesen in der Testbench
0   im File    ->     std_logic_vector(3 downto 0)
7  im File     ->     std_logic_vector(3 downto 0)
0F5  im File  ->     std_logic_vector(11 downto 0)


Aufbau der Token
Die Token können alle vom selben Datentyp sein und über alle Testtyps die gleiche Struktur beinhalten. Das vereinfacht ihre Verarbeitung.

<Testtyp>  <Wert1>   <Wert2>    <Ergebnis_Addition> <Ueberlaufbit>
1          00000001  00000001   00000002             0

Im zweiten Beispiel hängt die Funktion der Token von dem Testtyp ab.

<Testtyp>  <adress>  <value>   <byte enable>
wr_ram     0001      00F1      11
<Testtyp>  <address>  <result>  <byte enable>
rd_ram     0001       00F1      01

Tipp: Fixe Tokenstruktur mit Auslassungen

singl 14 01 00 00 00 00 00 00 00   // verwerfen
polyp 13 01 14 01 15 01 14 00 02

Zum Decodieren wird es schwer, wenn unterschiedliche Verarbeitungs-Strukturen je nach Testtyp aufgebaut werden müssen.

Zu empfehlen ist, dass alle Test-Werte in einer Linie stehen und beim Verarbeiten gewisse Token verworfen werden.

Umsetzung in VHDL

Metastabilität bei Flip-Flop

Wechselt das Inputsignal eines Flip-Flops zur falschen Zeit, ist das Ausgangssignal unsicher. Im besten Fall nimmt der Ausgang dann einen selbst gewählten Wert an (Q ode Q), im schlechten Fall „hängt“ sich das Flip-Flop „auf“ und toggelt permanent zwischen Q und Q.

Kritische Zeit beim Eingang
t_setup:   so lange müssen Daten vor dem Clock da sein
.                  (3 bis 12 ns. Cyclone IV: 8 – 10 ns)
t_hold:     so lange müssen Daten nach dem Clock anliegen  (0 bis 1 ns)
.                  Diese Zeit muss grösser sein, als die Durchlaufverzögerung des FF.
t_pd:         Durchlaufverzögerung: Zeit der Daten, bis sie am Ausgang anliegen.
kritscheZeit_FF

 

Treffen neue Daten vor der minimalen Haltezeit ein, so ist unklar, was am Ausgang anliegt.

Ursache für das Nichteinhalten der t_su
Die Clk-Frequenz hängt vom längsten Verzögerungspfad ab. Ist der eingestellte CLK schneller, so  kommt bei mindestens einem FF die Daten zu spät an. Die t_su kann dort nicht eingehalten werden.

Warum ist (nur ein) FF  bereits ein Problem?
Weil das FF im schlimmsten Fall zu Toggeln beginnt. Dadurch erhalten alle nachfolgenden FF toggelnde Signale und die ganze Linie ist unbrauchbar.
Ein metastabiles FF zieht die anderen FF mit sich.

Artikel 1

Timing Durchlaufverzögerung t_pd

Durchlaufverzögerung (propagation delay)
Ein Signal am Eingang erscheint (theortisch) direkt Ausgang. Real jedoch hat jedes Signal eine Gatter- oder Flip-Flop-Verzögerung.

Verzögerung bei Logik
Jedes Gatter braucht eine kurze Zeit, um die Logik auszugeben. Diese nennt man Gatter-Verzögerung bzw. auf englisch progagation delay.
delay_gatter
Nimmt man pro Gatter eine Verzögerung von 5 ns, so hat man nach 3 Gattern einen Pfad von 15 ns.

Verzögerung bei Flip-Flop
Auch jedes Flip-Flop hat nach dem Clk noch eine Verzögerung. Das Signal liegt nicht unmittelbar nach dem Clk am Ausgang an.
Durchlaufverzoegerung_FF

Die Verzögerung (t_pd) darf nicht zu lange sein, weil sonst das Signal nicht mehr anliegt. Die Regel ist, dass die Verzögerung küzer sein muss, als die minimale Haltezeit des Signal (t_hold). Bei den Flip-Flops muss zusätzlich noch die Zeit vor dem Clock berücksichtig werden (t_setup). Mehr dazu unter Metastabilität.

 

 

 

VHDL Latch

In Zustand hängen bleiben
Ein Latch ist ein Verharren in einem Zustand (latch = Zuschnappen, Schliessen). Dies geschieht z.B. wenn bei einer switch-case-Struktur nicht alle Fälle abgefragt werden. Trifft ein nicht definierter Fall ein, so verharrt das System in diesem.

Urachen im Code

// kein else nach if
// .................
begin
        if (cnt = 15) then
            pulse <= '1';
        end if;
 end process;
// kein others in case
// ....................
case state is
            when s0 =>   LEDR_0  <= '1';
            when s1 =>   LEDR_1  <= '1'; GPIO_0_0 <= '1';
            when OTHERS =>  LEDG_7 <= '1';
        
    end case;

 

VHDL State Machine One Hot

One Hot
Ist ein VHDL-Endcoding-BEgriff und bezieht sich auf die Umsetzung der Zustände in Hardware.
Bei One Hot wird für jeden Zustand s ein weiteres Bit gebraucht wird bzw. ein neues FF eingebaut.

s1 = 0001    -> 4 Flip-Flop
s2 = 0010
s3 = 0100
s4 = 1000

Der Vorteil von One Hot ist, dass sich die Input-Logik (das Decodieren der Zustände) vereinfacht. Man muss nur 1 Bit kontrollieren.

Minimal Bit Endocding
Ist der Gegenbegriff. Die Zustände werden mit möglichst wenigen Bits in VHDL umgesetzt.

s1 = 00    -> 2 Flip-Flop
s2 = 01
s3 = 10
s4 = 11

Einstellen in quartus
Menu Settings/ Analyse & Synthese/   Klicken auf Feld: More Settings:
Scrollen  bis zum Betriff  State Machine Processing:
Dort unter diversen Optionen One-Hot oder minimal-Bit auswählen