Formele verificatie van VHDL-modules met PSL meesteren
Bij het ontwerpen van VHDL voor veiligheidskritische FPGA-toepassingen is het simpelweg schrijven van testbenches niet genoeg. Ingenieurs moeten bewijzen dat een module zich precies gedraagt zoals gespecificeerd, vrij van verborgen bijwerkingen. Formele verificatie biedt die wiskundige zekerheid.
Formele verificatie overbrugt de kloof tussen specificaties en implementatie door te bewijzen dat elke mogelijke invoerreeks de ontwerpeigenschappen voldoet. Het is onmisbaar voor het certificeren van medische apparaten, avionica-systemen, of elk domein waar DO‑254-certificering vereist is.
In dit gastartikel deelt Michael Finn Jørgensen – een ervaren formele verificatie-praktijk – praktische inzichten en actionable tips. Het onderstaande voorbeeld bouwt voort op de bekende AXI FIFO-tutorial How to make an AXI FIFO in block RAM using the ready/valid handshake en demonstreert hoe PSL toe te passen op een echt VHDL-ontwerp.
Wat is Formele Verificatie?
Formele verificatie (FV) is een systematische, pre-synthese methode die een ontwerp's correctheid garandeert voor alle invoercombinaties. In tegenstelling tot conventionele testbenches, die vertrouwen op handmatig gemaakte stimuli, specificeren FV eigenschappen die elke klokcyclus moeten gelden. Een SAT-solver bewijst dan uitputtend dat deze eigenschappen voldaan worden – of wijst een tegenvoorbeeld aan als ze niet voldaan worden.
Twee kerntechnieken sturen FV:
- Bounded Model Checking (BMC) – controleert een eindig aantal cycli, startend vanaf reset.
- Inductie Bewijs – verifieert dat als een staat geldig is, alle opvolgende staten geldig blijven. Dit vereist een complete set eigenschappen, maar levert garanties voor oneindig veel cycli.
Omdat BMC uitgevoerd kan worden met slechts een paar eigenschappen, is het vaak de eerste verdedigingslinie. Inductie, hoewel meer vereistend, levert absolute zekerheid.
Waarom Formele Verificatie Gebruiken?
FV blinkt uit in het ontdekken van onvoorstelbare bugs omdat het de volledige invoerruimte automatisch verkent. Wanneer een fout ontdekt wordt, produceert het tool een beknopte golfvorm die de exacte cyclus aangeeft waar de eigenschap geschonden werd – veel makkelijker te debuggen dan een lange simulatietrace.
Hoewel FV een krachtig unit-test tool is, vervangt het geen handgemaakte integratietests. Naarmate de module groter wordt, kan de solver's runtime verbodelijk worden. Toch vermindert het investeren van tijd in FV de algehele debug-inspanning en versnelt time-to-market.
Eigenschappen Schrijven in PSL
PSL (Property Specification Language) is de lingua franca van formele verificatie in VHDL. Eigenschappen worden typisch opgeslagen in een apart .psl bestand dat de solver leest tijdens de verificatie-fase.
Drie PSL-trefwoorden beschrijven eigenschappen:
assert– garandeert dat een voorwaarde altijd geldt.assume– stelt aannames over de invoeren.cover– controleert of een bepaald scenario ooit kan voorkomen.
PSL-bestanden kunnen ook gewone VHDL-declaraties en processen bevatten, wat u in staat stelt auxilière signalen te creëren voor complexe assertions.
Elk PSL-bestand begint met een vunit blok dat het bestand koppelt aan een specifieke entiteit:
vunit INSTANCE_NAME(ENTITY_NAME(ARCHITECTURE_NAME)) { }
Gevolgd standaard klokdeclaratie:
default clock is rising_edge(clk);
Eigenschapssyntaxis ziet er zo uit:
LABEL : assert always {PRECONDITION} |-> {POSTCONDITION} LABEL : assume always {PRECONDITION} |-> {POSTCONDITION}
Gebruik |=> voor next-cycle assertions:
LABEL : assert always {PRECONDITION} |=> {POSTCONDITION}
Binnen voorwaarden kunt u stable gebruiken om af te dwingen dat een signaal niet verandert tussen cycli, en u kunt voorwaarden koppelen met {C1; C2} om sequentiële constraints uit te drukken.
Benodigde Tools Installeren
Toonaangevende commerciële tools (bijv. ModelSim) ondersteunen formele verificatie, maar de gratis Student Edition mist deze feature. Gelukkig is een robuuste open-source stack beschikbaar voor Linux (en via WSL op Windows). De volgende gids gebruikt Ubuntu 20.04 LTS, maar werkt op elke recente Debian-gebaseerde distro.
Vereisten
sudo apt update && sudo apt upgrade
Installeer essentiële ontwikkelbibliotheken:
sudo apt install build-essential clang bison flex libreadline-dev \ gawk tcl-dev libffi-dev git mercurial graphviz \ xdot pkg-config python python3 libftdi-dev gperf \ libboost-program-options-dev autoconf libgmp-dev \ cmake gnat gtkwave
Yosys en SymbiYosys
git clone https://github.com/YosysHQ/yosys cd yosys make sudo make install cd .. git clone https://github.com/YosysHQ/SymbiYosys cd SymbiYosys sudo make install cd .. git clone https://github.com/SRI-CSL/yices2 cd yices2 autoconf ./configure make sudo make install cd ..
GHDL en Yosys Plugin
git clone https://github.com/ghdl/ghdl cd ghdl ./configure --prefix=/usr/local make sudo make install cd .. git clone https://github.com/ghdl/ghdl-yosys-plugin cd ghdl-yosys-plugin make sudo make install cd ..
Voorbeeldproject Downloaden
Vul het onderstaande formulier in om een ZIP-archief te ontvangen met de complete AXI FIFO-bron, PSL-bestand, en verificatie-scripts.
Uitgewerkt Voorbeeld: Formele Verificatie van een AXI FIFO
Hieronder schetsen we de belangrijkste eigenschapscategorieën voor een FIFO en illustreren hoe ze in PSL te coderen.
Reset Afhandeling
f_reset : assume {rst};
Omdat het always trefwoord weggelaten is, geldt deze aanname alleen voor de eerste cyclus na reset. Een labelconventie (f_) helpt falen te identificeren.
f_after_reset_valid : assert always {rst} |=> {not out_valid}; f_after_reset_ready : assert always {rst} |=> {in_ready}; f_after_reset_head : assert always {rst} |=> {count = 0};
Let op dat interne signalen zoals count gerefereerd kunnen worden omdat de vunit declaratie de architectuurnaam bevat.
FIFO Vol en Leeg Signalering
f_empty : assert always {count = 0} |-> {not out_valid}; f_full : assert always {count = ram_depth-1} |-> {not in_ready};
AXI Protocol Compliance
f_in_data_stable : assume always {in_valid and not in_ready and not rst} |=> {stable(in_valid) and stable(in_data)}; f_out_data_stable : assert always {out_valid and not out_ready and not rst} |=> {stable(out_valid) and stable(out_data)};
Voor de ready-signaal stabiliteit:
f_out_ready_stable : assume always {out_ready and not out_valid and not rst} |=> {stable(out_ready)}; f_in_ready_stable : assert always {in_ready and not in_valid and not rst} |=> {stable(in_ready)};
FIFO Volgorde
Volgorde garandeert dat eerdere writes vóór latere writes verlaten. We modelleren dit met auxilière signalen die onthouden wanneer specifieke datawaarden binnenkwamen of verlaten.
signal f_sampled_in_d1 : std_logic := '0'; signal f_sampled_in_d2 : std_logic := '0'; p_sampled : process (clk) begin if rising_edge(clk) then if in_valid then if in_data = f_value_d1 then f_sampled_in_d1 <= '1'; end if; if in_data = f_value_d2 then f_sampled_in_d2 <= '1'; end if; end if; if out_valid then if out_data = f_value_d1 then f_sampled_out_d1 <= '1'; end if; if out_data = f_value_d2 then f_sampled_out_d2 <= '1'; end if; end if; if rst = '1' then f_sampled_in_d1 <= '0'; f_sampled_in_d2 <= '0'; f_sampled_out_d1 <= '0'; f_sampled_out_d2 <= '0'; end if; end if; end process p_sampled;
Declareer de datawaarden als onbeperkte constanten zodat de solver elk bitpatroon kan kiezen:
signal f_value_d1 : std_logic_vector(ram_width - 1 downto 0); signal f_value_d2 : std_logic_vector(ram_width - 1 downto 0); attribute anyconst : boolean; attribute anyconst of f_value_d1 : signal is true; attribute anyconst of f_value_d2 : signal is true;
Ten slotte, codeer de volgorde-constraints:
f_fifo_ordering_in : assume always {f_sampled_in_d2} |-> {f_sampled_in_d1}; f_fifo_ordering_out : assert always {f_sampled_out_d2} |-> {f_sampled_out_d1};
Formele Verificatie Uitvoeren
Maak een axi_fifo.sby script voor de sby tool:
[tasks] bmc [options] bmc: mode bmc bmc: depth 10 [engines] smtbmc [script] ghdl --std=08 -gram_width=4 -gram_depth=8 axi_fifo.vhd axi_fifo.psl -e axi_fifo prep -top axi_fifo [files] axi_fifo.psl axi_fifo.vhd
Voer de verificatie uit:
sby --yosys ";yosys -m ghdl" -f axi_fifo.sby
Een succesvolle run print:
[axi_fifo_bmc] DONE (PASS, rc=0)
Het aanpassen van depth boven de FIFO-diepte zorgt ervoor dat de solver volledige-capaciteit scenario's verkent.
Mutatie Testen
Om de compleetheid van uw eigenschappenset te beoordelen, injecteer deliberateer een kleine bug – zoals het verwijderen van de logica die out_valid aandrijft – en voer FV opnieuw uit. Als de solver een assertion-fout rapporteert, hebt u geverifieerd dat uw eigenschappen expressief genoeg zijn om die fout te vangen.
Voorbeeld foutmelding:
Assert failed in axi_fifo: i_axi_fifo.f_out_data_stable
Open de gegenereerde trace.vcd in GTKWave om de exacte cyclus te pinpointen waar de eigenschap geschonden werd.
Cover Statements
Cover statements zijn nuttig om te bevestigen dat een bepaald scenario bereikbaar is. Bijvoorbeeld, om te verifiëren dat de FIFO gevuld en daarna geleegd kan worden:
f_full_to_empty : cover { rst = '1'; rst = '0'[*]; rst = '0' and count = ram_depth-1; rst = '0'[*]; rst = '0' and count = 0};
Pas het sby script aan om de cover-taak te includeren:
[tasks] bmc cover [options] bmc: mode bmc bmc: depth 10 cover: mode cover
Succesvolle coverage levert:
Reached cover statement at i_axi_fifo.f_full_to_empty in step 15.
Het wijzigen van count = ram_depth-1 naar count = ram_depth maakt de cover onbereikbaar, wat de tool's gevoeligheid voor eigenschapsnauwkeurigheid demonstreert.
Volgende Stappen
VHDL
- De Moderne Smoking: Geschiedenis, Ontwerp en Productie-inzichten
- Een Freesmachine Omzetten In Een Draaibank: Eenvoudige Stappen Voor Metaalbewerkers
- C# using-statements: imports, aliasen en static directives
- Inleiding tot VHDL: Uw Eerste AND-poort Bouwen
- Uitgebreide Gids voor VHDL Type Conversies met Numeric_Std en Std_Logic_Arith
- VHDL Procedure Voorbeeld: Een Standard Logic Vector Incrementeren
- VHDL Records Gebruiken voor Schone, Herbruikbare FIFO-interfaces
- Signed vs. Unsigned Typen in VHDL: Praktische Inzichten
- VHDL Variabelen Uitgelegd: Praktische Voorbeelden & Regels voor Betrouwbaar Ontwerp
- Dynamische string-lijsten in VHDL: Best Practices & Voorbeeld
- Een VHDL-testbench netjes beëindigen: methoden en best practices