vhdlAan de slag met vhdl


Opmerkingen

VHDL is een samengesteld acroniem voor VHSIC (Very High Speed Integrated Circuit) HDL (Hardware Description Language). Als een Hardware Description Language wordt het voornamelijk gebruikt om circuits te beschrijven of te modelleren. VHDL is een ideale taal voor het beschrijven van circuits, omdat het taalconstructies biedt die zowel gelijktijdig als opeenvolgend gedrag gemakkelijk beschrijven, samen met een uitvoeringsmodel dat dubbelzinnigheid verwijdert die wordt geïntroduceerd bij het modelleren van gelijktijdig gedrag.

VHDL wordt meestal geïnterpreteerd in twee verschillende contexten: voor simulatie en voor synthese. Wanneer geïnterpreteerd voor synthese, wordt code geconverteerd (gesynthetiseerd) naar de equivalente hardware-elementen die worden gemodelleerd. Gewoonlijk is slechts een subset van de VHDL beschikbaar voor gebruik tijdens synthese en ondersteunde taalconstructies zijn niet gestandaardiseerd; het is een functie van de gebruikte synthese-engine en het doelhardwareapparaat. Wanneer VHDL wordt geïnterpreteerd voor simulatie, zijn alle taalconstructies beschikbaar voor het modelleren van het gedrag van hardware.

versies

Versie Publicatiedatum
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

Een simulatieomgeving voor de synchrone teller

Simulatie omgevingen

Een simulatieomgeving voor een VHDL-ontwerp (het Design Under Test of DUT) is een ander VHDL-ontwerp dat minimaal:

  • Declareert signalen die overeenkomen met de invoer- en uitvoerpoorten van de TU Delft.
  • Instantieert de TU Delft en verbindt zijn poorten met de aangegeven signalen.
  • Instantieert de processen die de signalen aansturen die zijn aangesloten op de invoerpoorten van de TU Delft.

Optioneel kan een simulatieomgeving andere ontwerpen dan de DUT instantiëren, zoals bijvoorbeeld verkeersgeneratoren op interfaces, monitoren om communicatieprotocollen te controleren, automatische verificateurs van de DUT-uitgangen ...

De simulatieomgeving wordt geanalyseerd, uitgewerkt en uitgevoerd. De meeste simulatoren bieden de mogelijkheid om een reeks signalen om te observeren te selecteren, hun grafische golfvormen te plotten, breekpunten in de broncode te plaatsen, in de broncode te stappen ...

Idealiter zou een simulatieomgeving bruikbaar moeten zijn als een robuuste non-regressietest, dat wil zeggen dat deze automatisch overtredingen van de DUT-specificaties moet detecteren, nuttige foutmeldingen moet rapporteren en een redelijke dekking van de DUT-functies moet garanderen. Wanneer dergelijke simulatieomgevingen beschikbaar zijn, kunnen ze bij elke wijziging van de TU Delft opnieuw worden uitgevoerd om te controleren of deze nog steeds functioneel correct is, zonder de noodzaak van vervelende en foutgevoelige visuele inspecties van simulatiesporen.

In de praktijk is het een uitdaging om ideale of zelfs goede simulatieomgevingen te ontwerpen. Het is vaak zo, of zelfs moeilijker dan het ontwerpen van de TU Delft zelf.

In dit voorbeeld presenteren we een simulatieomgeving voor het voorbeeld van de synchrone teller . We laten zien hoe het uit te voeren met GHDL en Modelsim en hoe grafische golfvormen te observeren met behulp van GTKWave met GHDL en de ingebouwde golfvormviewer met Modelsim. Vervolgens bespreken we een interessant aspect van simulaties: hoe kunnen ze worden gestopt?

Een eerste simulatieomgeving voor de synchrone teller

De synchrone teller heeft twee invoerpoorten en één uitvoerpoorten. Een zeer eenvoudige simulatieomgeving kan zijn:

-- 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;
 

Simuleren met GHDL

Laten we dit compileren en simuleren met 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"
 

Dan vertellen foutmeldingen ons twee belangrijke dingen:

  • De GHDL analyzer ontdekt dat ons ontwerp concretiseert een entiteit genaamd counter maar deze entiteit werd niet gevonden in de bibliotheek work . Dit komt omdat we counter niet eerder hebben gecompileerd dan counter_sim . Bij het compileren van VHDL-ontwerpen die entiteiten instantiëren, moeten de onderste niveaus altijd vóór de hoogste niveaus worden gecompileerd (hiërarchische ontwerpen kunnen ook van boven naar beneden worden samengesteld, maar alleen als ze component instantiëren, geen entiteiten).
  • De functie rising_edge die door ons ontwerp wordt gebruikt, is niet gedefinieerd. Dit komt omdat deze functie in VHDL 2008 is geïntroduceerd en we GHDL niet hebben verteld deze versie van de taal te gebruiken (standaard wordt VHDL 1993 gebruikt met een tolerantie van de syntaxis van VHDL 1987).

Laten we de twee fouten oplossen en de simulatie starten:

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

Merk op dat de optie --std=08 nodig is voor analyse en simulatie. Merk ook op dat we de simulatie hebben gelanceerd op entiteit counter_sim , architecture sim , niet op een bronbestand.

Omdat onze simulatieomgeving een eindeloos proces heeft (het proces dat de klok genereert), stopt de simulatie niet en moeten we deze handmatig onderbreken. In plaats daarvan kunnen we een stoptijd opgeven met de optie --stop-time :

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

Zoals gezegd, vertelt de simulatie ons niet veel over het gedrag van onze TU Delft. Laten we de waardeveranderingen van de signalen in een bestand dumpen:

$ 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
 

(negeer de foutmelding, dit is iets dat moet worden opgelost in GHDL en dat heeft geen gevolgen). Er is een counter_sim.vcd bestand gemaakt. Het bevat in VCD (ASCII) -formaat alle signaalveranderingen tijdens de simulatie. GTKWave kan ons de overeenkomstige grafische golfvormen tonen:

$ gtkwave counter_sim.vcd
 

waar we kunnen zien dat de teller werkt zoals verwacht.

Een GTKWave-golfvorm

Simuleren met Modelsim

Het principe is precies hetzelfde met 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'
 

voer hier de afbeeldingsbeschrijving in

Let op de optie -voptargs="+acc" is doorgegeven aan vsim : het voorkomt dat de simulator het data optimaliseert en stelt ons in staat om het op de golfvormen te zien.

Sierlijk eindigende simulaties

Met beide simulatoren moesten we de eindeloze simulatie onderbreken of een stoptijd opgeven met een speciale optie. Dit is niet erg handig. In veel gevallen is de eindtijd van een simulatie moeilijk te voorspellen. Het zou veel beter zijn om de simulatie te stoppen vanuit de VHDL-code van de simulatieomgeving, wanneer een bepaalde voorwaarde wordt bereikt, zoals bijvoorbeeld wanneer de huidige waarde van de teller 20 bereikt. Dit kan worden bereikt met een bewering in de proces dat de reset uitvoert:

  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;
 

Zolang de data verschillen van 20, gaat de simulatie door. Wanneer de data 20 bereiken, crasht de simulatie met een foutmelding:

$ 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
 

Merk op dat we alleen de simulatieomgeving opnieuw hebben gecompileerd: het is het enige ontwerp dat is veranderd en het is het hoogste niveau. Als we alleen counter.vhd hadden gewijzigd, hadden we beide opnieuw moeten compileren: counter.vhd omdat het was veranderd en counter_sim.vhd omdat het afhangt van counter.vhd .

De simulatie crashen met een foutmelding is niet erg elegant. Het kan zelfs een probleem zijn bij het automatisch parseren van de simulatieberichten om te beslissen of een automatische niet-regressietest is geslaagd of niet. Een betere en veel elegantere oplossing is om alle processen te stoppen wanneer een voorwaarde is bereikt. Dit kan bijvoorbeeld door een boolean End Of Simulation-signaal ( eof ) toe te voegen. Standaard wordt deze aan het begin van de simulatie op false geïnitialiseerd. Een van onze processen zal het true wanneer het tijd is om de simulatie te beëindigen. Alle andere processen zullen dit signaal volgen en stoppen met een eeuwige wait wanneer het 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
 

Last but not least is er een nog betere oplossing geïntroduceerd in VHDL 2008 met het standaardpakket env en de stop en finish die het verklaart:

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
 

Hallo Wereld

Er zijn veel manieren om de klassieke "Hallo wereld!" bericht in VHDL. Het eenvoudigste van alles is waarschijnlijk zoiets als:

-- 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;
 

Installatie of instellingen

Een VHDL-programma kan worden gesimuleerd of gesynthetiseerd. Simulatie lijkt het meest op de uitvoering in andere programmeertalen. Synthesis vertaalt een VHDL-programma in een netwerk van logische poorten. Veel VHDL-simulatie- en synthesetools maken deel uit van commerciële EDA-suites (Electronic Design Automation). Ze verwerken vaak ook andere Hardware Description Languages (HDL), zoals Verilog, SystemVerilog of SystemC. Er bestaan enkele gratis en open source-applicaties.

VHDL-simulatie

GHDL is waarschijnlijk de meest volwassen gratis en open source VHDL-simulator. Het komt in drie verschillende smaken, afhankelijk van de gebruikte backend: gcc , llvm of mcode . De volgende voorbeelden laten zien hoe GHDL ( mcode versie) en Modelsim, de commerciële HDL-simulator van Mentor Graphics, kunnen worden gebruikt onder een GNU / Linux-besturingssysteem. Dingen zouden erg op elkaar lijken met andere tools en andere besturingssystemen.

Hallo Wereld

Maak een bestand hello_world.vhd met:

-- 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;
 

Een VHDL-compilatie-eenheid is een compleet VHDL-programma dat alleen kan worden gecompileerd. Entiteiten zijn VHDL-compilatie-eenheden die worden gebruikt om de externe interface van een digitaal circuit te beschrijven, dat wil zeggen de invoer- en uitvoerpoorten. In ons voorbeeld heet de entity hello_world en is deze leeg. Het circuit dat we modelleren is een zwarte doos, het heeft geen ingangen en geen uitgangen. Architecturen zijn een ander type compilatie-eenheid. Ze worden altijd geassocieerd met een entity en ze worden gebruikt om het gedrag van het digitale circuit te beschrijven. Eén entiteit kan een of meer architecturen hebben om het gedrag van de entiteit te beschrijven. In ons voorbeeld is de entiteit gekoppeld aan slechts één architectuur met de naam arc die slechts één VHDL-instructie bevat:

  assert false report "Hello world!" severity note;
 

De verklaring wordt uitgevoerd aan het begin van de simulatie en drukt de Hello world! bericht op de standaarduitvoer. De simulatie eindigt dan omdat er niets meer te doen is. Het VHDL-bronbestand dat we schreven bevat twee compilatie-eenheden. We hadden ze in twee verschillende bestanden kunnen scheiden, maar we konden ze niet in verschillende bestanden hebben opgesplitst: een compilatie-eenheid moet volledig in één bronbestand zijn opgenomen. Merk op dat deze architectuur niet kan worden gesynthetiseerd omdat het geen functie beschrijft die direct kan worden vertaald naar logische poorten.

Analyseer en voer het programma uit met 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!
 

De gh_work directory is waar GHDL de bestanden die het genereert worden opgeslagen. Dit is wat de optie --workdir=gh_work zegt. De analysefase controleert de syntaxis-correctheid en produceert een tekstbestand dat de compilatie-eenheden beschrijft die in het bronbestand zijn gevonden. De run-fase compileert, koppelt en voert het programma daadwerkelijk uit. Merk op dat in de mcode versie van GHDL geen binaire bestanden worden gegenereerd. Het programma wordt elke keer opnieuw samengesteld als we het simuleren. De versies gcc of llvm gedragen zich anders. Merk ook op dat ghdl -r niet de naam van een VHDL-bronbestand ghdl -a , zoals ghdl -a , maar de naam van een compilatie-eenheid. In ons geval geven we het de naam van de entity . Omdat er slechts één architecture gekoppeld, hoeft u niet op te geven welke te simuleren.

Met 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 en vsim zijn vier opdrachten die Modelsim biedt. vlib maakt een map ( ms_work ) aan waar de gegenereerde bestanden worden opgeslagen. vmap koppelt een door vlib aangemaakte vlib met een logische naam ( work ). vcom compileert een VHDL-bronbestand en slaat standaard het resultaat op in de map die is gekoppeld aan de logische naam van het work . Ten slotte simuleert vsim het programma en produceert het dezelfde soort output als GHDL. Merk nogmaals op dat wat vsim vraagt geen bronbestand is, maar de naam van een al gecompileerde compilatie-eenheid. De optie -c geeft de simulator opdracht in de opdrachtregelmodus te worden uitgevoerd in plaats van de standaard grafische gebruikersinterface (GUI). De optie -do wordt gebruikt om een TCL-script door te geven dat moet worden uitgevoerd na het laden van het ontwerp. TCL is een scripttaal die veel wordt gebruikt in EDA-tools. De waarde van de optie -do kan de naam van een bestand zijn of, zoals in ons voorbeeld, een reeks TCL-opdrachten. run -all; quit instrueer de simulator om de simulatie uit te voeren totdat deze vanzelf eindigt - of voor altijd als het voor altijd duurt - en dan te stoppen.

Signalen versus variabelen, een kort overzicht van de simulatie-semantiek van VHDL

Dit voorbeeld behandelt een van de meest fundamentele aspecten van de VHDL-taal: de simulatie-semantiek. Het is bedoeld voor VHDL-beginners en biedt een vereenvoudigde weergave waarbij veel details zijn weggelaten (uitgestelde processen, VHDL-procedurele interface, gedeelde variabelen ...) Lezers die geïnteresseerd zijn in de echte complete semantiek, verwijzen naar de Language Reference Manual (LRM).

Signalen en variabelen

De meeste klassieke imperatieve programmeertalen gebruiken variabelen. Het zijn waardecontainers. Een toewijzingsoperator wordt gebruikt om een waarde in een variabele op te slaan:

a = 15;
 

en de waarde die momenteel is opgeslagen in een variabele kan worden gelezen en gebruikt in andere instructies:

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

VHDL gebruikt ook variabelen en ze spelen exact dezelfde rol als in de meeste imperatieve talen. Maar VHDL biedt ook een ander soort waardecontainer: het signaal. Signalen slaan ook waarden op, kunnen ook worden toegewezen en gelezen. Het type waarden dat in signalen kan worden opgeslagen, is (bijna) hetzelfde als in variabelen.

Dus, waarom twee soorten waardecontainers? Het antwoord op deze vraag is essentieel en vormt de kern van de taal. Het verschil tussen variabelen en signalen begrijpen is het eerste wat je moet doen voordat je iets probeert te programmeren in VHDL.

Laten we dit verschil illustreren op een concreet voorbeeld: het ruilen.

Opmerking: alle volgende codefragmenten zijn onderdeel van processen. We zullen later zien welke processen zijn.

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

wisselt variabelen a en b . Na het uitvoeren van deze 3 instructies is de nieuwe inhoud van a de oude inhoud van b en omgekeerd. Zoals in de meeste programmeertalen, is een derde tijdelijke variabele ( tmp ) nodig. Als we in plaats van variabelen signalen wilden ruilen, zouden we schrijven:

    r <= s;
    s <= r;
 

of:

    s <= r;
    r <= s;
 

met hetzelfde resultaat en zonder een derde tijdelijk signaal!

Opmerking: de VHDL-signaaltoewijzingsoperator <= verschilt van de variabele toewijzingsoperator := .

Laten we een tweede voorbeeld bekijken waarin we aannemen dat het print de decimale weergave van zijn parameter afdrukt. Als a een geheel getal-variabele is en de huidige waarde 15 is, voert u het volgende uit:

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

zal afdrukken:

5
 

Als we deze stap voor stap uitvoeren in een debugger, zien we de waarde van a wijziging van de initiële 15 naar 30, 25 en uiteindelijk 5.

Maar als s een geheel getal is en de huidige waarde 15 is, voert u het volgende uit:

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

zal afdrukken:

15
3
 

Als we deze stap voor stap uitvoeren in een debugger, zien we pas na de wait een waardeverandering van s . Bovendien is de uiteindelijke waarde van s niet 15, 30, 25 of 5 maar 3!

Dit ogenschijnlijk vreemde gedrag is te wijten aan het fundamenteel parallelle karakter van digitale hardware, zoals we in de volgende paragrafen zullen zien.

Parallelism

VHDL is een Hardware Description Language (HDL) en is van nature parallel. Een VHDL-programma is een verzameling opeenvolgende programma's die parallel worden uitgevoerd. Deze opeenvolgende programma's worden processen genoemd:

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

P2: process
begin
  ...
end process P2;
 

De processen eindigen, net als de hardware die ze modelleren, nooit: ze zijn oneindige lussen. Na het uitvoeren van de laatste instructie gaat de uitvoering door met de eerste.

Zoals bij elke programmeertaal die een of andere vorm van parallellisme ondersteunt, is een planner verantwoordelijk voor het beslissen welk proces (en wanneer) moet worden uitgevoerd tijdens een VHDL-simulatie. Bovendien biedt de taal specifieke constructies voor communicatie en synchronisatie tussen processen.

Het roosteren

De planner houdt een lijst bij van alle processen en registreert voor elk proces de huidige status die running , run-able of suspended . Er is maximaal één proces in running status: het proces dat momenteel wordt uitgevoerd. Zolang de momenteel lopende proces niet een uit te voeren wait instructie, blijft rennen en voorkomt dat een ander proces van wordt uitgevoerd. De VHDL-planner is niet preventief: het is elke procesverantwoordelijkheid om zichzelf op te schorten en andere processen te laten lopen. Dit is een van de problemen die VHDL-beginners vaak tegenkomen: het vrijloopproces.

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

Opmerking: variabele a wordt lokaal gedeclareerd, terwijl signalen s en r elders worden gedeclareerd, op een hoger niveau. VHDL-variabelen zijn lokaal voor het proces dat ze declareert en kunnen niet door andere processen worden gezien. Een ander proces zou ook een variabele met de naam a kunnen declareren, het zou niet dezelfde variabele zijn als die van proces P3 .

Zodra de planner het P3 proces hervat, loopt de simulatie vast, de huidige simulatietijd loopt niet meer op en de enige manier om dit te stoppen is door de simulatie te doden of te onderbreken. De reden is dat P3 geen verklaring van wait heeft en dus voor altijd in running staat blijft en de 3 instructies doorloopt. Geen enkel ander proces zal ooit een kans krijgen om te worden uitgevoerd, zelfs als het run-able .

Zelfs processen met een wait kunnen hetzelfde probleem veroorzaken:

  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;
 

Opmerking: de operator VHDL gelijkheid is = .

Als proces P4 wordt hervat terwijl de waarde van signaal s 3 is, zal het voor altijd blijven lopen omdat de voorwaarde a = 16 nooit waar zal zijn.

Laten we aannemen dat ons VHDL-programma dergelijke pathologische processen niet bevat. Wanneer het lopende proces uitvoert een wait instructie wordt onmiddellijk onderbroken en de planner zet deze in de suspended toestand. De wait ook de voorwaarde dat het proces opnieuw run-able wordt. Voorbeeld:

    wait on s;
 

betekent schors me totdat de waarde van signaal s verandert . Deze voorwaarde wordt vastgelegd door de planner. De planner selecteert vervolgens een ander proces onder de run-able , zet het in de running status en voert het uit. En hetzelfde herhaalt zich totdat alle run-able processen zijn uitgevoerd en opgeschort.

Belangrijke opmerking: als er meerdere processen worden run-able , is de VHDL standaard niet aan hoe de planner zal kiezen welke om te draaien. Een gevolg is dat, afhankelijk van de simulator, de versie van de simulator, het besturingssysteem of iets anders, twee simulaties van hetzelfde VHDL-model op een gegeven moment verschillende keuzes kunnen maken en een ander uit te voeren proces kunnen selecteren. Als deze keuze invloed had op de simulatieresultaten, zouden we kunnen zeggen dat VHDL niet-deterministisch is. Aangezien niet-determinisme meestal ongewenst is, is het de verantwoordelijkheid van de programmeurs om niet-deterministische situaties te vermijden. Gelukkig zorgt VHDL hiervoor en komen hier signalen in beeld.

Signalen en communicatie tussen processen

VHDL vermijdt non-determinisme met behulp van twee specifieke kenmerken:

  1. Processen kunnen alleen informatie uitwisselen via signalen
  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;
 

Opmerking: VHDL-opmerkingen lopen van -- tot het einde van de regel.

  1. De waarde van een VHDL-signaal verandert niet tijdens de uitvoering van processen

Elke keer dat een signaal wordt toegewezen, wordt de toegewezen waarde vastgelegd door de planner, maar de huidige waarde van het signaal blijft ongewijzigd. Dit is een ander groot verschil met variabelen die hun nieuwe waarde direct na toewijzing aannemen.

Laten we een uitvoering van proces P5 hierboven bekijken en aannemen dat a=5 , s=1 en r=0 wanneer het wordt hervat door de planner. Na het uitvoeren van instructie a := s + 1; , de waarde van variabele a verandert en wordt 2 (1 + 1). Bij het uitvoeren van de volgende instructie r <= a; het is de nieuwe waarde van a (2) die is toegewezen aan r . Maar omdat r een signaal is, is de huidige waarde van r nog steeds 0. Dus, bij het uitvoeren van a := r + 1; , variabele a neemt (onmiddellijk) waarde 1 (0 + 1), niet 3 (2 + 1) zoals de intuïtie zou zeggen.

Wanneer krijgt signaal r echt zijn nieuwe waarde? Wanneer de planner alle uitvoerbare processen heeft uitgevoerd en deze allemaal worden opgeschort. Dit wordt ook wel genoemd: na één deltacyclus . Alleen dan zal de planner alle waarden bekijken die aan signalen zijn toegewezen en de waarden van de signalen daadwerkelijk bijwerken. Een VHDL-simulatie is een afwisseling van uitvoeringsfasen en signaalupdatefasen. Tijdens uitvoeringsfasen wordt de waarde van de signalen bevroren. Symbolisch, zeggen we dat tussen een uitvoeringsfase de volgende signaal updatefase een delta verstreken tijd. Dit is geen realtime. Een deltacyclus heeft geen fysieke duur.

Dankzij dit vertraagde signaalupdatemechanisme is VHDL deterministisch. Processen kunnen alleen communiceren met signalen en signalen veranderen niet tijdens de uitvoering van de processen. Dus de volgorde van uitvoering van de processen doet er niet toe: hun externe omgeving (de signalen) verandert niet tijdens de uitvoering. Laten we dit laten zien in het vorige voorbeeld met processen P5 en P6 , waarbij de begintoestand P5.a=5 , P6.a=10 , s=17 , r=0 en waar de planner besluit eerst P5 en vervolgens P6 . De volgende tabel toont de waarde van de twee variabelen, de huidige en volgende waarden van de signalen na het uitvoeren van elke instructie van elk proces:

proces / instructie P5.a P6.a s.current s.next r.current r.next
Oorspronkelijke toestand 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
Na signaalupdate 1 1 1 18

Met dezelfde beginvoorwaarden, als de planner besluit eerst P6 en vervolgens P5 :

proces / instructie P5.a P6.a s.current s.next r.current r.next
Oorspronkelijke toestand 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
Na signaalupdate 1 1 1 18

Zoals we kunnen zien, is het resultaat na de uitvoering van onze twee processen hetzelfde, ongeacht de volgorde van uitvoering.

Deze contra-intuïtieve semantiek van de signaaltoewijzing is de reden voor een tweede soort problemen die VHDL-beginners vaak tegenkomen: de toewijzing die blijkbaar niet werkt omdat deze wordt vertraagd door een deltacyclus. Bij het stap voor stap uitvoeren van proces P5 in een debugger, nadat r is toegewezen aan 18 en a is toegewezen aan r + 1 , zou men kunnen verwachten dat de waarde van a 19 is, maar de debugger zegt koppig dat r=0 en a=1 ...

Opmerking: hetzelfde signaal kan meerdere keren worden toegewezen tijdens dezelfde uitvoeringsfase. In dit geval is het de laatste toewijzing die de volgende waarde van het signaal bepaalt. De andere opdrachten hebben helemaal geen effect, net alsof ze nooit zijn uitgevoerd.

Het is tijd om ons begrip te controleren: ga terug naar ons allereerste ruilvoorbeeld en probeer te begrijpen waarom:

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

verwisselt eigenlijk signalen r en s zonder een derde tijdelijk signaal en waarom:

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

zou strikt gelijkwaardig zijn. Probeer ook te begrijpen waarom, als s een geheel getal is en de huidige waarde 15 is, en we uitvoeren:

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

de eerste twee toewijzingen van signaal s geen effect hebben, waarom s eindelijk toegewezen 3 en waarom de twee gedrukte waarden 15 en 3.

Fysieke tijd

Om hardware te modelleren, is het erg handig om de fysieke tijd te kunnen modelleren die een bewerking nodig heeft. Hier is een voorbeeld van hoe dit kan worden gedaan in VHDL. Het voorbeeld modelleert een synchrone teller en het is een volledige, zelfstandige, VHDL-code die kan worden gecompileerd en gesimuleerd:

-- 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;
 

In proces P1 de wait niet gebruikt om te wachten tot de waarde van een signaal verandert, zoals we tot nu toe zagen, maar om te wachten op een bepaalde duur. Dit proces modelleert een klokgenerator. Signal clk is de klok van ons systeem, het is periodiek met periode 20 ns (50 MHz) en heeft een duty cycle.

Proces P2 modelleert een register dat, als een stijgende flank van clk net optrad, de waarde van zijn ingang nc toewijst aan zijn uitgang c en dan wacht op de volgende waardeverandering van clk .

Proces P3 modelleert een incrementer die de waarde van zijn input c , met één verhoogd, toewijst aan zijn output nc ... met een fysieke vertraging van 5 ns. Hij wacht dan tot de waarde van zijn invoer c verandert. Dit is ook nieuw. Tot nu toe hebben we altijd signalen toegewezen met:

  s <= value;
 

die we om de in de vorige paragrafen uiteengezette redenen impliciet kunnen vertalen in:

  s <= value; -- after delta
 

Dit kleine digitale hardwaresysteem kan worden weergegeven met de volgende afbeelding:

Een synchrone teller

Met de introductie van de fysieke tijd, en wetende dat we ook een symbolische tijd hebben gemeten in delta , hebben we nu een tweedimensionale tijd die we T+D zullen aangeven, waarbij T een fysieke tijd is gemeten in nano-seconden en D een getal van delta's (zonder fysieke duur).

Het complete plaatje

Er is één belangrijk aspect van de VHDL-simulatie dat we nog niet hebben besproken: na een uitvoeringsfase bevinden alle processen zich in een suspended status. We hebben informeel verklaard dat de planner vervolgens de waarden van de toegewezen signalen bijwerkt. Maar zal het in ons voorbeeld van een synchrone teller tegelijkertijd signalen clk , c en nc updaten? Hoe zit het met de fysieke vertragingen? En wat gebeurt er vervolgens met alle processen in suspended staat en geen in run-able staat?

Het complete (maar vereenvoudigde) simulatie-algoritme is het volgende:

  1. initialisatie
    • Stel de huidige tijd Tc op 0 + 0 (0 ns, 0 delta-cyclus)
    • Initialiseer alle signalen.
    • Voer elk proces uit totdat het wordt onderbroken bij een wait .
      • Noteer de waarden en vertragingen van signaaltoewijzingen.
      • Noteer de voorwaarden voor het hervatten van het proces (vertraging of signaalverandering).
    • Bereken de volgende keer Tn als de vroegste van:
      • De hervattijd van processen die zijn uitgesteld door een wait for <delay> .
      • De volgende keer waarop een signaalwaarde zal veranderen.
  1. Simulatie cyclus
    • Tc=Tn .
    • Update signalen die moeten zijn.
    • Zet in run-able staat alle processen die wachtten op een waardeverandering van een van de signalen die is bijgewerkt.
    • Zet in run-able staat alle processen die werden opgeschort door een wait for <delay> statement en waarvoor de hervattijd Tc .
    • Voer alle uitvoerbare processen uit totdat ze worden onderbroken.
      • Noteer de waarden en vertragingen van signaaltoewijzingen.
      • Noteer de voorwaarden voor het hervatten van het proces (vertraging of signaalverandering).
    • Bereken de volgende keer Tn als de vroegste van:
      • De hervattijd van processen die zijn uitgesteld door een wait for <delay> .
      • De volgende keer waarop een signaalwaarde zal veranderen.
    • Als Tn oneindig is, stopt u de simulatie. Anders start u een nieuwe simulatiecyclus.

Handmatige simulatie

Laten we tot slot nu handmatig het vereenvoudigde simulatie-algoritme op de hierboven gepresenteerde synchrone teller oefenen. We besluiten willekeurig dat, wanneer verschillende processen uitvoerbaar zijn, de volgorde P3 > P2 > P1 . De volgende tabellen geven de evolutie van de toestand van het systeem weer tijdens de initialisatie en de eerste simulatiecycli. Elk signaal heeft zijn eigen kolom waarin de huidige waarde wordt aangegeven. Wanneer een signaaltoewijzing wordt uitgevoerd, wordt de geplande waarde toegevoegd aan de huidige waarde, bijvoorbeeld a/b@T+D als de huidige waarde a en de volgende waarde b op tijdstip T+D (fysieke tijd plus deltacycli) . De laatste 3 kolommen geven de voorwaarde aan om de onderbroken processen te hervatten (naam van signalen die moeten veranderen of tijdstip waarop het proces moet worden hervat).

Initialisatie fase:

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 0 + 0
Initialiseer alle signalen 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
Bereken de volgende keer 0 + 0 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

Simulatiecyclus # 1

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Update signalen 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Bereken de volgende keer 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

Opmerking: tijdens de eerste simulatiecyclus is er geen uitvoeringsfase omdat aan geen van onze 3 processen de cv-toestand is voldaan. P2 is wachten op een waarde verandering van de clk en is er een transactie is op clk , maar als de oude en nieuwe waarden hetzelfde zijn, dit is niet een waarde verandering.

Simulatiecyclus # 2

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Update signalen 5 + 0 '0' 0 1 10 + 0 clk c
Bereken de volgende keer 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

Opmerking: nogmaals, er is geen uitvoeringsfase. nc gewijzigd maar er wacht geen proces op nc .

Simulatiecyclus # 3

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 10 + 0 '0' 0 1 10 + 0 clk c
Update signalen 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
Bereken de volgende keer 10 + 0 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c

Simulatiecyclus # 4

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Update signalen 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
Bereken de volgende keer 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

Simulatiecyclus # 5

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Update signalen 10 + 2 '1' 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 '1' 1 02/01 @ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 '1' 1 02/01 @ 15 + 0 20 + 0 clk c
Bereken de volgende keer 10 + 2 15 + 0 '1' 1 02/01 @ 15 + 0 20 + 0 clk c

Opmerking: men zou kunnen denken dat de nc update zou zijn gepland op 15+2 , terwijl we deze hadden gepland op 15+0 . Bij het toevoegen van een niet-nul fysieke vertraging (hier 5 ns ) aan een huidige tijd ( 10+2 ), verdwijnen de deltacycli. Deltacycli zijn inderdaad alleen nuttig om verschillende simulatietijden T+0 , T+1 ... met dezelfde fysieke tijd T . Zodra de fysieke tijd verandert, kunnen de deltacycli worden gereset.

Simulatiecyclus # 6

Activiteiten Tc Tn clk c nc P1 P2 P3
Stel de huidige tijd in 15 + 0 '1' 1 02/01 @ 15 + 0 20 + 0 clk c
Update signalen 15 + 0 '1' 1 2 20 + 0 clk c
Bereken de volgende keer 15 + 0 20 + 0 '1' 1 2 20 + 0 clk c

Opmerking: nogmaals, er is geen uitvoeringsfase. nc gewijzigd maar er wacht geen proces op nc .

Simulatie ... schakel over naar Engels om verder te lezen

Synchrone teller

-- 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;