Sök…


Anmärkningar

VHDL är en sammansatt akronym för VHSIC (Very High Speed Integrated Circuit) HDL (Hardware Description Language). Som hårdvarubeskrivningsspråk används det främst för att beskriva eller modellera kretsar. VHDL är ett idealiskt språk för att beskriva kretsar eftersom det erbjuder språkkonstruktioner som enkelt beskriver både samtidigt och sekventiellt beteende tillsammans med en exekveringsmodell som tar bort tvetydighet som introduceras vid modellering av samtidigt beteende.

VHDL tolkas vanligtvis i två olika sammanhang: för simulering och för syntes. När den tolkas för syntes, konverteras (syntetiseras) koden till motsvarande hårdvara som är modellerade. Endast en delmängd av VHDL är vanligtvis tillgänglig för användning under syntes, och språkstrukturer som stöds är inte standardiserade; Det är en funktion av den använda syntesmotorn och målhårdvaru-enheten. När VHDL tolkas för simulering är alla språkkonstruktioner tillgängliga för att modellera hårdvarans beteende.

versioner

Version Utgivningsdatum
IEEE 1076-1987 1988/03/31
IEEE 1076-1993 1994/06/06
IEEE 1076-2000 2000/01/30
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 2009-01-26

Installation eller installation

Ett VHDL-program kan simuleras eller syntetiseras. Simulering är det som liknar mest exekveringen på andra programmeringsspråk. Syntesen översätter ett VHDL-program till ett nätverk av logiska grindar. Många VHDL-simulerings- och syntesverktyg är delar av kommersiella EDA-sviter. De hanterar ofta även andra hårdvarubeskrivningsspråk (HDL), som Verilog, SystemVerilog eller SystemC. Vissa gratis- och open source-applikationer finns.

VHDL-simulering

GHDL är förmodligen den mest mogna gratis och open source VHDL-simulatorn. Den finns i tre olika smaker beroende på vilken backend som används: gcc , llvm eller mcode . Följande exempel visar hur man använder GHDL ( mcode version) och Modelsim, den kommersiella HDL-simulatorn från Mentor Graphics, under ett GNU / Linux-operativsystem. Det skulle vara mycket likt med andra verktyg och andra operativsystem.

Hej världen

Skapa en fil hello_world.vhd innehåller:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

En VHDL-kompilationsenhet är ett komplett VHDL-program som kan sammanställas ensam. Enheter är VHDL-kompileringsenheter som används för att beskriva det externa gränssnittet i en digital krets, det vill säga dess ingångs- och utgångsportar. I vårt exempel heter entity hello_world och är tom. Kretsen vi modellerar är en svart låda, den har inga ingångar och inga utgångar. Arkitekturer är en annan typ av sammanställningsenhet. De är alltid kopplade till en entity och de används för att beskriva den digitala kretsens beteende. En enhet kan ha en eller flera arkitekturer för att beskriva enhetens beteende. I vårt exempel är enheten kopplad till endast en arkitektur med namnet arc som endast innehåller ett VHDL-uttalande:

  assert false report "Hello world!" severity note;

Uttalandet kommer att köras i början av simuleringen och skriva ut Hello world! meddelandet på standardutgången. Simuleringen avslutas då eftersom det inte finns något mer att göra. VHDL-källfilen vi skrev innehåller två sammanställningsenheter. Vi kunde ha separerat dem i två olika filer men vi kunde inte ha delat upp någon av dem i olika filer: en sammanställningsenhet måste vara helt inne i en källfil. Observera att denna arkitektur inte kan syntetiseras eftersom den inte beskriver en funktion som kan direkt översättas till logiska grindar.

Analysera och köra programmet med GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!

gh_work är där GHDL lagrar filerna den genererar. Det --workdir=gh_work alternativet --workdir=gh_work . Analysfasen kontrollerar syntaxens korrekthet och producerar en textfil som beskriver kompilationsenheterna som finns i källfilen. Körfasen sammanställer, länkar och kör verkligen programmet. Observera att i mcode versionen av GHDL genereras inga binära filer. Programmet kompileras om varje gång vi simulerar det. gcc eller llvm versionerna beter sig annorlunda. Observera också att ghdl -r inte tar namnet på en VHDL-källfil, som ghdl -a gör, men namnet på en sammanställningsenhet. I vårt fall skickar vi det namnet på entity . Eftersom den bara har en architecture associerad, finns det inget behov att ange vilken som ska simuleras.

Med Modelsim:

$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
#    Time: 0 ns  Iteration: 0  Instance: /hello_world
...

vlib , vmap , vcom och vsim är fyra kommandon som Modelsim tillhandahåller. vlib skapar en katalog ( ms_work ) där de genererade filerna kommer att lagras. vmap en katalog skapad av vlib med ett logiskt namn ( work ). vcom samman en VHDL källfilen och som standard lagrar resultatet i katalogen associerade till work logiskt namn. Slutligen simulerar vsim programmet och producerar samma typ av output som GHDL. Lägg märke till att det som vsim begär inte är en källfil, men namnet på en redan sammanställd kompilationsenhet. Alternativet -c berättar för simulatorn att köra i kommandoradsläget i stället för standard grafiskt användargränssnitt (GUI). Alternativet -do används för att skicka ett TCL-skript som ska köras efter att designen laddats. TCL är ett skriptspråk som används ofta i EDA-verktyg. Värdet på alternativet -do kan vara namnet på en fil eller, som i vårt exempel, en sträng TCL-kommandon. run -all; quit instruera simulatorn att köra simuleringen tills den naturligt slutar - eller för evigt om den varar för evigt - och sedan sluta.

Synkron räknare

-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
  port(
    clock: in  bit;    -- We are using the rising edge of CLOCK
    reset: in  bit;    -- Synchronous and active HIGH
    data:  out natural -- The current value of the counter
  );
end entity counter;

-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
  -- The internal signals we use to count. Natural is another standard
  -- type. VHDL is not case sensitive.
  signal current_value: natural;
  signal NEXT_VALUE:    natural;
begin
  -- A process is a concurrent statement. It is an infinite loop.
  process
  begin
    -- The wait statement is a synchronization instruction. We wait
    -- until clock changes and its new value is '1' (a rising edge).
    wait until clock = '1';
    -- Our reset is synchronous: we consider it only at rising edges
    -- of our clock.
    if reset = '1' then
      -- <= is the signal assignment operator.
      current_value <= 0;
    else
      current_value <= next_value;
    end if;
  end process;

  -- Another process. The sensitivity list is another way to express
  -- synchronization constraints. It (approximately) means: wait until
  -- one of the signals in the list changes and then execute the process
  -- body. Sensitivity list and wait statements cannot be used together 
  -- in the same process.
  process(current_value)
  begin
    next_value <= current_value + 1;
  end process;

  -- A concurrent signal assignment, which is just a shorthand for the
  -- (trivial) equivalent process.
  data <= current_value;
end architecture sync;

Hej världen

Det finns många sätt att skriva ut den klassiska "Hello world!" meddelande i VHDL. Det enklaste av allt är förmodligen något som:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

En simuleringsmiljö för den synkrona räknaren

Simuleringsmiljöer

En simuleringsmiljö för en VHDL-design (Design Under Test eller DUT) är en annan VHDL-design som, åtminstone:

  • Deklarerar signaler som motsvarar ingångs- och utgångsportarna för DUT.
  • Instantierar DUT och ansluter dess portar till de deklarerade signalerna.
  • Omedelbar processer som driver signalerna anslutna till ingångsportarna för DUT.

Eventuellt kan en simuleringsmiljö instansera andra konstruktioner än DUT, som till exempel trafikgeneratorer på gränssnitt, monitorer för att kontrollera kommunikationsprotokoll, automatiska verifierare av DUT-utgångar ...

Simuleringsmiljön analyseras, utarbetas och körs. De flesta simulatorer erbjuder möjligheten att välja en uppsättning signaler för att observera, plotta sina grafiska vågformer, sätta brytpunkter i källkoden, klicka i källkoden ...

Helst bör en simuleringsmiljö vara användbar som ett robust test för icke-regression, det vill säga den ska automatiskt upptäcka kränkningar av DUT-specifikationerna, rapportera användbara felmeddelanden och garantera en rimlig täckning av DUT-funktionerna. När sådana simuleringsmiljöer finns tillgängliga kan de återköras vid varje ändring av DUT för att kontrollera att den fortfarande är funktionellt korrekt utan behov av tråkiga och felaktiga visuella inspektioner av simuleringsspår.

I praktiken är det utmanande att utforma idealiska eller till och med bra simuleringsmiljöer. Det är ofta som, eller ännu svårare, än att utforma själva DUT.

I det här exemplet presenterar vi en simuleringsmiljö för Synchronous counter- exemplet. Vi visar hur man kör det med GHDL och Modelsim och hur man observerar grafiska vågformer med GTKWave med GHDL och den inbyggda vågformvisaren med Modelsim. Vi diskuterar sedan en intressant aspekt av simuleringar: hur man stoppar dem?

En första simuleringsmiljö för den synkrona räknaren

Den synkrona räknaren har två ingångsportar och en utgångsportar. En mycket enkel simuleringsmiljö kan vara:

-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;

architecture sim of counter_sim is

  -- One signal per port of the DUT. Signals can have the same name as
  -- the corresponding port but they do not need to.
  signal clk:  bit;
  signal rst:  bit;
  signal data: natural;

begin

  -- Instantiation of the DUT
  u0: entity work.counter(sync)
  port map(
    clock => clk,
    reset => rst,
    data  => data
  );

  -- A clock generating process with a 2ns clock period. The process
  -- being an infinite loop, the clock will never stop toggling.
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
  end process;

  -- The process that handles the reset: active from beginning of
  -- simulation until the 5th rising edge of the clock.
  process
  begin
    rst  <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst  <= '0';
    wait; -- Eternal wait. Stops the process forever.
  end process;

end architecture sim;

Simulering med GHDL

Låt oss sammanställa och simulera detta med GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"

Sedan berättar felmeddelanden om två viktiga saker:

  • GHDL-analysatorn upptäckte att vår design instarterar en enhet som heter counter men denna enhet hittades inte i bibliotekets work . Det beror på att vi inte kompilerade counter före counter_sim . När man sammanställer VHDL-mönster som instanterar enheter måste bottennivåerna alltid sammanställas före de översta nivåerna (hierarkiska mönster kan också sammanställas uppifrån och ner men bara om de instanserar component , inte enheter).
  • Den rising_edge funktion som används av vår design definieras inte. Detta beror på att den här funktionen introducerades i VHDL 2008 och vi sa inte till GHDL att använda den här versionen av språket (som standard använder den VHDL 1993 med tolerans för VHDL 1987-syntax).

Låt oss fixa de två felen och starta simuleringen:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C

Observera att --std=08 behövs för analys och simulering. Observera också att vi lanserade simulering på enheten counter_sim , arkitektur sim , inte på en källfilen.

Eftersom vår simuleringsmiljö har en aldrig slutande process (processen som genererar klockan) stannar inte simuleringen och vi måste avbryta den manuellt. Istället kan vi ange en stopptid med --stop-time :

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time

Som simuleringen berättar inte så mycket om DUT: s beteende. Låt oss dumpa värdeförändringarna för signalerna i en fil:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time

(ignorera felmeddelandet, det här är något som måste fixas i GHDL och det har ingen konsekvens). En counter_sim.vcd fil har skapats. Den innehåller i VCD (ASCII) -format alla signalförändringar under simuleringen. GTKWave kan visa oss motsvarande grafiska vågformer:

$ gtkwave counter_sim.vcd

där vi kan se att räknaren fungerar som förväntat.

En GTKWave vågform

Simulering med Modelsim

Principen är exakt densamma med Modelsim:

$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'

ange bildbeskrivning här

Notera -voptargs="+acc" alternativet skickas till vsim : det hindrar simulatorn från optimera ut data signalen och tillåter oss att se den på vågformer.

Graciöst slutande simuleringar

Med båda simulatorerna var vi tvungna att avbryta den aldrig slutande simuleringen eller specificera en stopptid med ett dedicerat alternativ. Detta är inte särskilt bekvämt. I många fall är sluttiden för en simulering svår att förutse. Det skulle vara mycket bättre att stoppa simuleringen inuti VHDL-koden i simuleringsmiljön, när ett visst tillstånd uppnås, till exempel när räknarens nuvarande värde når 20. Detta kan uppnås med ett påstående i process som hanterar återställningen:

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    loop
      wait until rising_edge(clk);
      assert data /= 20 report "End of simulation" severity failure;
    end loop;
  end process;

Så länge data skiljer sig från 20 fortsätter simuleringen. När data når 20 kraschar simuleringen med ett felmeddelande:

$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
  from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed

Observera att vi bara kompilerade bara simuleringsmiljön: det är den enda designen som har ändrats och det är den översta nivån. Hade vi bara modifierat counter.vhd , skulle vi behöva kompilera båda igen: counter.vhd eftersom det förändrats och counter_sim.vhd eftersom det beror på counter.vhd .

Att krascha simuleringen med ett felmeddelande är inte särskilt elegant. Det kan till och med vara ett problem när du automatiskt analyserar simuleringsmeddelanden för att avgöra om ett automatiskt icke-regressionstest klarat eller inte. En bättre och mycket mer elegant lösning är att stoppa alla processer när ett villkor uppnås. Detta kan till exempel göras genom att lägga till en boolean End Of Simulation ( eof ) signal. Som standard initialiseras den till false i början av simuleringen. En av våra processer kommer att sätta den till true när tiden är inne för att avsluta simuleringen. Alla andra processer kommer att övervaka denna signal och sluta med en evig wait när den kommer att bli true :

  signal eos:  boolean;
...
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
    if eos then
      report "End of simulation";
      wait;
    end if;
  end process;

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    eos <= true;
    wait;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation

Sist men inte minst finns det en ännu bättre lösning introducerad i VHDL 2008 med standardpaketet env och stop och finish som de förklarar:

use std.env.all;
...
  process
  begin
    rst    <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst    <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    finish;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns

Signaler kontra variabler, en kort översikt av simuleringssemantiken för VHDL

Detta exempel behandlar en av de mest grundläggande aspekterna av VHDL-språket: simuleringssemantiken. Det är avsett för VHDL-nybörjare och presenterar en förenklad vy där många detaljer har utelämnats (uppskjutna processer, VHDL-procedurgränssnitt, delade variabler ...) Läsare som är intresserade av den verkliga kompletta semantiken ska referera till Language Reference Manual (LRM).

Signaler och variabler

De flesta klassiska nödvändiga programmeringsspråk använder variabler. Det är värdeskåp. En tilldelningsoperatör används för att lagra ett värde i en variabel:

a = 15;

och värdet som för närvarande lagras i en variabel kan läsas och användas i andra påståenden:

if(a == 15) { print "Fifteen" }

VHDL använder också variabler och de har exakt samma roll som i de flesta viktiga språk. Men VHDL erbjuder också en annan typ av behållare: signalen. Signaler lagrar också värden, kan också tilldelas och läsas. Typen av värden som kan lagras i signaler är (nästan) densamma som i variabler.

Så varför har du två typer av värdekontainrar? Svaret på denna fråga är väsentligt och i hjärtat av språket. Att förstå skillnaden mellan variabler och signaler är det allra första man gör innan man försöker programmera något i VHDL.

Låt oss illustrera denna skillnad på ett konkret exempel: bytet.

Obs: alla följande kodavsnitt är delar av processer. Vi kommer senare att se vilka processer som är.

    tmp := a;
    a   := b;
    b   := tmp;

byter variabler a och b . Efter att ha genomfört dessa 3 instruktioner är det nya innehållet i a det gamla innehållet i b och omvänt. Liksom i de flesta programmeringsspråk behövs en tredje temporär variabel ( tmp ). Om vi i stället för variabler ville byta signaler skulle vi skriva:

    r <= s;
    s <= r;

eller:

    s <= r;
    r <= s;

med samma resultat och utan behov av en tredje tillfällig signal!

Obs: VHDL-signaltilldelningsoperatören <= skiljer sig från variabeltilldelningsoperatören := .

Låt oss titta på ett andra exempel där vi antar att print skriver decimal representation av dess parameter. Om a är en heltalvariabel och dess nuvarande värde är 15, kör:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);

kommer att skriva ut:

5

Om vi utföra detta steg för steg i en debugger kan vi se värdet av a förändring från den ursprungliga 15 till 30, 25 och slutligen 5.

Men om s är en heltalssignal och dess nuvarande värde är 15, kör:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);

kommer att skriva ut:

15
3

Om vi utför detta steg för steg i en felsökare ser vi ingen värdeförändring på s förrän efter wait . Dessutom kommer det slutliga värdet på s inte att vara 15, 30, 25 eller 5 utan 3!

Detta till synes konstiga beteende beror på den grundläggande parallella karaktären hos digital hårdvara, som vi kommer att se i följande avsnitt.

parallel~~POS=TRUNC

VHDL är ett hårdvarubeskrivningsspråk (HDL) och är parallellt av naturen. Ett VHDL-program är en samling sekventiella program som körs parallellt. Dessa sekventiella program kallas processer:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;

Processerna, precis som hårdvaran de modellerar, slutar aldrig: de är oändliga slingor. Efter att ha genomfört den sista instruktionen fortsätter exekveringen med den första.

Som med alla programmeringsspråk som stöder en eller annan form av parallellism, är en schemaläggare ansvarig för att bestämma vilken process som ska utföras (och när) under en VHDL-simulering. Dessutom erbjuder språket specifika konstruktioner för kommunikation och synkronisering mellan processer.

schemaläggning

Schemaläggaren upprätthåller en lista över alla processer och för var och en av dem registrerar dess nuvarande tillstånd som kan running , run-able eller suspended . Det finns högst en process i running : den som för närvarande körs. Så länge den aktuella processen inte kör en wait fortsätter den att köras och förhindrar att någon annan process körs. VHDL-schemaläggaren är inte förebyggande: det är varje processansvar att stänga av sig själv och låta andra processer köras. Detta är ett av problemen som VHDL-nybörjare ofta stöter på: den kostnadsfria körprocessen.

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;

Obs: variabel a deklareras lokalt medan signalerna s och r deklareras på annat håll, på en högre nivå. VHDL-variabler är lokala för processen som deklarerar dem och kan inte ses av andra processer. En annan process kan också deklarera en variabel med namnet a , den skulle inte vara samma variabel som processen P3 .

Så snart schemaläggaren kommer att återuppta P3 processen kommer simuleringen att fastna, simuleringens nuvarande tid kommer inte att fortskrida längre och det enda sättet att stoppa detta är att döda eller avbryta simuleringen. Anledningen är att P3 inte har wait uttalande och därmed kommer att förbli i running alltid, slingra över sina tre instruktioner. Ingen annan process kommer någonsin att ges en chans att köra, även om den är run-able .

Till och med processer som innehåller ett wait kan orsaka samma problem:

  P4: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    if a = 16 then
      wait on s;
    end if;
    r <= a;
  end process P4;

Obs: VHDL-jämställdhetsoperatören är = .

Om process P4 återupptas medan värdet på signalen s är 3, kommer den att köra för alltid eftersom a = 16 villkoret aldrig kommer att vara sant.

Låt oss anta att vårt VHDL-program inte innehåller sådana patologiska processer. När körprocessen utför en wait avbryts den omedelbart och schemaläggaren sätter den i suspended tillstånd. wait innehåller också villkoret för att processen ska kunna run-able igen. Exempel:

    wait on s;

betyder att stänga av mig tills värdet på signal s ändras . Detta villkor registreras av schemaläggaren. Schemaläggaren väljer sedan en annan process bland de run-able , sätter den i running och kör den. Och samma upprepas tills alla run-able processer har körts och avbrutits.

Viktig anmärkning: När flera processer kan run-able anger VHDL-standarden inte hur schemaläggaren ska välja vilken som ska köras. En konsekvens är att beroende på simulatorn, simulatorns version, operativsystemet eller något annat, kan två simuleringar av samma VHDL-modell vid en tidpunkt göra olika val och välja en annan process att utföra. Om detta val hade påverkan på simuleringsresultaten kan vi säga att VHDL är icke-deterministiskt. Eftersom icke-determinism vanligtvis är oönskat, skulle det vara programmerarnas ansvar att undvika icke-deterministiska situationer. Lyckligtvis tar VHDL hand om detta och det är där signaler kommer in i bilden.

Signaler och kommunikation mellan processer

VHDL undviker icke-determinism med hjälp av två specifika egenskaper:

  1. Processer kan bara utbyta information genom signaler
  signal r, s: integer;  -- Common to all processes
...
  P5: process
    variable a: integer; -- Different from variable a of process P6
  begin
    a := s + 1;
    r <= a;
    a := r + 1;
    wait on s;
  end process P5;

  P6: process
    variable a: integer; -- Different from variable a of process P5
  begin
    a := r + 1;
    s <= a;
    wait on r;
  end process P6;

Obs: VHDL-kommentarer sträcker sig från -- till slutet av raden.

  1. Värdet på en VHDL-signal förändras inte under genomförandet av processer

Varje gång en signal tilldelas, registreras det tilldelade värdet av schemaläggaren men signalens aktuella värde förblir oförändrad. Detta är ytterligare en stor skillnad med variabler som tar sitt nya värde omedelbart efter tilldelningen.

Låt oss titta på en utförande av process P5 ovan och anta att a=5 , s=1 och r=0 när den återupptas av schemaläggaren. Efter körning av instruktionen a := s + 1; , värdet på variabeln a ändras och blir 2 (1 + 1). När du kör nästa instruktion r <= a; det är det nya värdet på a (2) som tilldelas r . Men när r är en signal är det aktuella värdet för r fortfarande 0. Så, när du kör a := r + 1; , variabel a tar (omedelbart) värdet 1 (0 + 1), inte 3 (2 + 1) som intuitionen skulle säga.

När kommer signal r verkligen att ta sitt nya värde? När schemaläggaren kommer att ha kört alla körbara processer och alla kommer att stängas av. Detta kallas också: efter en delta- cykel . Det är först då som schemaläggaren ser på alla värden som har tilldelats signaler och faktiskt uppdaterar värdena på signalerna. En VHDL-simulering är en växling av exekveringsfaser och signaluppdateringsfaser. Under exekveringsfaser fryses signalernas värde. Symboliskt säger vi att mellan en exekveringsfas och följande signaluppdateringsfas förflutit ett delta av tiden. Detta är inte realtid. En delta- cykel har ingen fysisk varaktighet.

Tack vare denna försenade signaluppdateringsmekanism är VHDL deterministisk. Processer kan bara kommunicera med signaler och signaler förändras inte under processerna. Så, ordningen för exekvering av processerna spelar ingen roll: deras externa miljö (signalerna) förändras inte under exekveringen. Låt oss visa detta på föregående exempel med processerna P5 och P6 , där det initiala tillståndet är P5.a=5 , P6.a=10 , s=17 , r=0 och där schemaläggaren beslutar att köra P5 först och P6 nästa . Följande tabell visar värdet på de två variablerna, de aktuella och nästa värdena på signalerna efter att ha utfört varje instruktion i varje process:

process / instruktion P5.a P6.a s.current s.next r.current r.next
Initialtillstånd 5 10 17 0
P5 / a := s + 1 18 10 17 0
P5 / r <= a 18 10 17 0 18
P5 / a := r + 1 1 10 17 0 18
P5 / wait on s 1 10 17 0 18
P6 / a := r + 1 1 1 17 0 18
P6 / s <= a 1 1 17 1 0 18
P6 / wait on r 1 1 17 1 0 18
Efter signaluppdatering 1 1 1 18

Med samma initiala villkor, om schemaläggaren beslutar att köra P6 först och P5 nästa:

process / instruktion P5.a P6.a s.current s.next r.current r.next
Initialtillstånd 5 10 17 0
P6 / a := r + 1 5 1 17 0
P6 / s <= a 5 1 17 1 0
P6 / wait on r 5 1 17 1 0
P5 / a := s + 1 18 1 17 1 0
P5 / r <= a 18 1 17 1 0 18
P5 / a := r + 1 1 1 17 1 0 18
P5 / wait on s 1 1 17 1 0 18
Efter signaluppdatering 1 1 1 18

Som vi kan se, efter genomförandet av våra två processer, är resultatet detsamma oavsett ordningsföljd.

Denna motintuitiva signaltilldelningssemantik är orsaken till en andra typ av problem som VHDL-nybörjare ofta stöter på: uppdraget som uppenbarligen inte fungerar eftersom det är försenat med en delta-cykel. När man kör process P5 steg-för-steg i en felsökare, efter att r har tilldelats 18 och a har tilldelats r + 1 , kan man förvänta sig att värdet på a är 19 men felsökaren säger ständigt att r=0 och a=1 ...

Obs: samma signal kan tilldelas flera gånger under samma exekveringsfas. I detta fall är det den sista tilldelningen som bestämmer nästa värde på signalen. De andra uppgifterna har ingen effekt alls, precis som om de aldrig hade utförts.

Det är dags att kontrollera vår förståelse: gå tillbaka till vårt allra första byteexempel och försöka förstå varför:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;

byter faktiskt signaler r och s utan behov av en tredje tillfällig signal och varför:

  process
  begin
    ---
    r <= s;
    s <= r;
    ---
  end process;

skulle vara strikt likvärdiga. Försök också förstå varför, om s är en heltalssignal och dess nuvarande värde är 15, och vi kör:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;

de första två tilldelningar av signalen s har ingen effekt, varför s slutligen delad 3 och varför de två tryckta värden är 15 och 3.

Fysisk tid

För att modellera hårdvara är det mycket användbart att kunna modellera den fysiska tiden som en operation tar. Här är ett exempel på hur detta kan göras i VHDL. Exemplet modellerar en synkron räknare och det är en fullständig, fristående VHDL-kod som kan kompileras och simuleras:

-- File counter.vhd
entity counter is
end entity counter;

architecture arc of counter is
  signal clk: bit; -- Type bit has two values: '0' and '1'
  signal c, nc: natural; -- Natural (non-negative) integers
begin
  P1: process
  begin
    clk <= '0';
    wait for 10 ns; -- Ten nano-seconds delay
    clk <= '1';
    wait for 10 ns; -- Ten nano-seconds delay
  end process P1;

  P2: process
  begin
    if clk = '1' and clk'event then
      c <= nc;
    end if;
    wait on clk;
  end process P2;

  P3: process
  begin
    nc <= c + 1 after 5 ns; -- Five nano-seconds delay
    wait on c;
  end process P3;
end architecture arc;

I process P1 den wait instruktionen inte används för att vänta tills värdet på en signal förändringar, som vi såg fram till nu, men vänta på en viss tid. Denna process modellerar en klockgenerator. Signal clk är klockan i vårt system, det är periodiskt med period 20 ns (50 MHz) och har en arbetscykel.

Process P2 modellerar ett register som, om en stigande kant av clk just inträffat, tilldelar värdet på dess ingång nc till dess utgång c och sedan väntar på nästa clk av clk .

Process P3 modellerar en inkrementer som tilldelar värdet på sin ingång c , ökad av en, till dess utgång nc ... med en fysisk fördröjning på 5 ns. Den väntar sedan tills värdet på dess ingång c ändras. Detta är också nytt. Hittills har vi alltid tilldelat signaler med:

  s <= value;

som av de skäl som förklaras i de föregående avsnitten implicit kan vi översätta till:

  s <= value; -- after delta

Detta lilla digitala hårdvarusystem kan representeras av följande figur:

En synkron räknare

Med introduktionen av den fysiska tiden, och att veta att vi också har en symbolisk tid uppmätt i delta , har vi nu en tvådimensionell tid som vi kommer att beteckna T+D där T är en fysisk tid uppmätt i nano-sekunder och D ett tal av deltor (utan fysisk varaktighet).

Den kompletta bilden

Det finns en viktig aspekt av VHDL-simuleringen som vi inte diskuterade ännu: efter en exekveringsfas är alla processer i suspended tillstånd. Vi informerade informellt om att schemaläggaren sedan uppdaterar värdena på signalerna som har tilldelats. Men, i vårt exempel på en synkron räknare, ska den uppdatera signalerna clk , c och nc samtidigt? Vad sägs om de fysiska förseningarna? Och vad händer härnäst med alla processer i suspended tillstånd och ingen i run-able tillstånd?

Den kompletta (men förenklade) simuleringsalgoritmen är följande:

  1. initiering
    • Ställ aktuell tid Tc på 0 + 0 (0 ns, 0 delta-cykel)
    • Initiera alla signaler.
    • Kör varje process tills den avbryts på ett wait uttalande.
      • Spela in värden och förseningar för signaltilldelningar.
      • Registrera villkoren för att processen ska återupptas (fördröjning eller signaländring).
    • Beräkna nästa gång Tn som den tidigaste av:
      • Återställningstid för processer avstängd av en wait for <delay> .
      • Nästa gång då ett signalvärde ska ändras.
  1. Simuleringscykel
    • Tc=Tn .
    • Uppdatera signaler som måste vara.
    • Sätt i run-able tillstånd alla processer som väntade på en värdeförändring av en av signalerna som har uppdaterats.
    • Sätt i run-able tillstånd alla processer som har avbrutits med en wait for <delay> uttalande och för vilken återupptagningstiden är Tc .
    • Kör alla körbara processer tills de avbryts.
      • Spela in värden och förseningar för signaltilldelningar.
      • Registrera villkoren för att processen ska återupptas (fördröjning eller signaländring).
    • Beräkna nästa gång Tn som den tidigaste av:
      • Återställningstid för processer avstängd av en wait for <delay> .
      • Nästa gång då ett signalvärde ska ändras.
    • Om Tn är oändligt, stoppa simuleringen. Annars, starta en ny simuleringscykel.

Manuell simulering

Avslutningsvis, låt oss nu manuellt utöva den förenklade simuleringsalgoritmen på den synkrona räknaren som presenteras ovan. Vi godtar godtyckligt att ordningen kommer att vara P3 > P2 > P1 när flera processer kan köras. Följande tabeller visar utvecklingen av systemets tillstånd under initialiseringen och de första simuleringscyklerna. Varje signal har sin egen kolumn där det aktuella värdet indikeras. När en signaltilldelning utförs läggs det schemalagda värdet till det aktuella värdet, t.ex. a/b@T+D om det aktuella värdet är a och nästa värde kommer att vara b vid tiden T+D (fysisk tid plus delta-cykler) . De tre sista kolumnerna anger villkoret för att återuppta de avstängda processerna (namn på signaler som måste ändras eller när processen ska återupptas).

Initieringsfas:

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 0 + 0
Initiera alla signaler 0 + 0 '0' 0 0
P3/nc<=c+1 after 5 ns 0 + 0 '0' 0 0/1 @ 5 + 0
P3/wait on c 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/if clk='1'... 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/end if 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/wait on clk 0 + 0 '0' 0 0/1 @ 5 + 0 clk c
P1/clk<='0' 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 clk c
P1/wait for 10 ns 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Beräkna nästa gång 0 + 0 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

Simuleringscykel # 1

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Uppdatera signaler 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Beräkna nästa gång 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

Obs! Under den första simuleringscykeln finns det ingen exekveringsfas eftersom ingen av våra 3 processer har sitt CV-tillstånd uppfyllt. P2 väntar på en värdeförändring om clk och det har varit en transaktionclk , men som de gamla och nya värden är desamma, är detta inte en värdeförändring.

Simuleringscykel # 2

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Uppdatera signaler 5 + 0 '0' 0 1 10 + 0 clk c
Beräkna nästa gång 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

Obs! Återigen finns det ingen exekveringsfas. nc ändrats men ingen process väntar på nc .

Simuleringscykel # 3

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 10 + 0 '0' 0 1 10 + 0 clk c
Uppdatera signaler 10 + 0 '0' 0 1 10 + 0 clk c
P1/clk<='1' 10 + 0 '0' / '1' @ 10 + 1 0 1 clk c
P1/wait for 10 ns 10 + 0 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Beräkna nästa gång 10 + 0 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c

Simuleringscykel # 4

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Uppdatera signaler 10 + 1 '1' 0 1 20 + 0 clk c
P2/if clk='1'... 10 + 1 '1' 0 1 20 + 0 c
P2/c<=nc 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/end if 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Beräkna nästa gång 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

Simuleringscykel # 5

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Uppdatera signaler 10 + 2 '1' 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk c
Beräkna nästa gång 10 + 2 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c

Obs: man kan tro att nc uppdateringen skulle schemaläggas vid 15+2 , medan vi planerade den vid 15+0 . När du lägger till en fysisk fördröjning utan noll (här 5 ns ) till en aktuell tid ( 10+2 ) försvinner delta-cyklerna. I själva verket är delta-cykler bara användbara för att skilja olika simuleringstider T+0 , T+1 ... med samma fysiska tid T Så snart den fysiska tiden förändras kan delta-cyklerna återställas.

Simuleringscykel # 6

Operationer Tc Tn clk c nc P1 P2 P3
Ställ in aktuell tid 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c
Uppdatera signaler 15 + 0 '1' 1 2 20 + 0 clk c
Beräkna nästa gång 15 + 0 20 + 0 '1' 1 2 20 + 0 clk c

Obs! Återigen finns det ingen exekveringsfas. nc ändrats men ingen process väntar på nc .

Simulering ... växla till engelska för att fortsätta läsa

Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow