vhdl Handledning
Komma igång med vhdl
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 biblioteketswork
. Det beror på att vi inte kompileradecounter
förecounter_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 instanserarcomponent
, 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.
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'
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 signalernas
ochr
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 namneta
, den skulle inte vara samma variabel som processenP3
.
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:
- 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.
- 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:
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:
- 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.
- Återställningstid för processer avstängd av en
- Ställ aktuell tid
- 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 enwait for <delay>
uttalande och för vilken återupptagningstiden ärTc
. - 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.
- Återställningstid för processer avstängd av en
- 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 omclk
och det har varit en transaktion påclk
, 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 vid15+2
, medan vi planerade den vid15+0
. När du lägger till en fysisk fördröjning utan noll (här5 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 simuleringstiderT+0
,T+1
... med samma fysiska tidT
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
.