VHDL-functies onder de knie krijgen: een praktische gids voor efficiënt ontwerp
Functies zijn subprogramma's in VHDL die gebruikt kunnen worden voor het implementeren van veelvoorkomende algoritmen. Een functie neemt nul of meer invoerwaarden en retourneert altijd een waarde. Naast de returnwaarde, wat een functie onderscheidt van een procedure, is dat het geen Wait-statements mag bevatten. Dit betekent dat functies altijd nul simulatiетijd consumeren.
Als je bekend bent met functies of methoden uit andere programmeertalen, zouden VHDL-functies makkelijk te begrijpen zijn. In VHDL kunnen we de returnwaarde niet weglaten of void retourneren; een functie moet altijd iets retourneren en de returnwaarde moet aan iets toegewezen worden.
Deze blogpost is onderdeel van de Basic VHDL Tutorials-reeks.
In VHDL zijn er twee soorten functies: pure en impure functies. Dat een functie pure is betekent dat het niet mag wijzigen of lezen van extern signalen. We kunnen zeker zijn dat wanneer we een pure functie aanroepen met bepaalde argumenten, het altijd dezelfde waarde retourneert. We zeggen dat de functie geen bijwerkingen heeft.
De syntax voor het declareren van een functie in VHDL is:
[pure|impure] function <function_name> (<parameter1_name> : <parameter1_type> := <default_value>; <parameter2_name> : <parameter2_type> := <default_value>; ... ) return <return_type> is <constant_or_variable_declaration> begin <code_performed_by_the_function> return <value> end function;
De pure/impure keyword is optioneel, hoewel het standaard naar pure gaat als het sleutelwoord weggelaten wordt. Alle parameters worden binnen de functie als constanten behandeld. Dus ze kunnen niet worden gewijzigd. De standaardwaarden zijn optioneel, en de functie moet altijd eindigen op een return-statement.
Functies hebben hun eigen declaratieve regio tussen de is en begin sleutelwoorden. Constanten, signalen of variabelen die hier gedeclareerd zijn, zijn alleen geldig binnen de functie zelf, en ze behouden hun waarden niet door opeenvolgende aanroepen van de functie.
Oefening
In deze tutorial richten we ons op de pure functie; impure functies worden in een latere tutorial in deze reeks behandeld.
In de vorige tutorial maakten we een verkeerslichtcontroller-module met een eindige-toestandsmachine (FSM). We kopieerden en plakten veel regels met timerberekeningen van de ene toestand naar de andere, waarbij we slechts één constante lichtjes veranderden.
Ontdek hoe je de toestandsmachinecode kunt vereenvoudigen door een functie te gebruiken:
De uiteindelijke code voor de functie testbench:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity T21_FunctionTb is end entity; architecture sim of T21_FunctionTb is -- We gebruiken een lage klokfrequentie om de simulatie te versnellen constant ClockFrequencyHz : integer := 100; -- 100 Hz constant ClockPeriod : time := 1000 ms / ClockFrequencyHz; signal Clk : std_logic := '1'; signal nRst : std_logic := '0'; signal NorthRed : std_logic; signal NorthYellow : std_logic; signal NorthGreen : std_logic; signal WestRed : std_logic; signal WestYellow : std_logic; signal WestGreen : std_logic; begin -- Het Device Under Test (DUT) i_TrafficLights : entity work.T21_TrafficLights(rtl) generic map(ClockFrequencyHz => ClockFrequencyHz) port map ( Clk => Clk, nRst => nRst, NorthRed => NorthRed, NorthYellow => NorthYellow, NorthGreen => NorthGreen, WestRed => WestRed, WestYellow => WestYellow, WestGreen => WestGreen); -- Proces voor klokgeneratie Clk <= not Clk after ClockPeriod / 2; -- Testbench-sequentie process is begin wait until rising_edge(Clk); wait until rising_edge(Clk); -- Haal de DUT uit reset nRst <= '1'; wait; end process; end architecture;
De uiteindelijke code voor de verkeerslichten module:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity T21_TrafficLights is generic(ClockFrequencyHz : natural); port( Clk : in std_logic; nRst : in std_logic; -- Negatieve reset NorthRed : out std_logic; NorthYellow : out std_logic; NorthGreen : out std_logic; WestRed : out std_logic; WestYellow : out std_logic; WestGreen : out std_logic); end entity; architecture rtl of T21_TrafficLights is -- Enumerated type declaratie en state signaal declaratie type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest); signal State : t_State; -- Bereken het aantal klokcycli in minuten/seconden function CounterVal(Minutes : integer := 0; Seconds : integer := 0) return integer is variable TotalSeconds : integer; begin TotalSeconds := Seconds + Minutes * 60; return TotalSeconds * ClockFrequencyHz -1; end function; -- Teller voor het tellen van klokperiodes, 1 minuut max signal Counter : integer range 0 to CounterVal(Minutes => 1) +1; begin process(Clk) is begin if rising_edge(Clk) then if nRst = '0' then -- Reset waarden NorthRed <= '1'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '1'; WestYellow <= '0'; WestGreen <= '0'; State <= NorthNext; Counter <= 0; else -- Standaard waarden NorthRed <= '0'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '0'; WestYellow <= '0'; WestGreen <= '0'; Counter <= Counter + 1; case State is -- Rood licht in alle richtingen when NorthNext => NorthRed <= '1'; WestRed <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= StartNorth; end if; -- Geel licht in noord/zuid richtingen when StartNorth => NorthRed <= '1'; NorthYellow <= '1'; WestRed <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= North; end if; -- Groen licht in noord/zuid richtingen when North => NorthGreen <= '1'; WestRed <= '1'; -- Als 1 minuut is verstreken if Counter = CounterVal(Minutes => 1) then Counter <= 0; State <= StopNorth; end if; -- Rood en geel licht in noord/zuid richting when StopNorth => NorthYellow <= '1'; WestRed <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= WestNext; end if; -- Rood licht in alle richtingen when WestNext => NorthRed <= '1'; WestRed <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= StartWest; end if; -- Geel licht in west/oost richting when StartWest => NorthRed <= '1'; WestRed <= '1'; WestYellow <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= West; end if; -- Groen licht in west/oost richting when West => NorthRed <= '1'; WestGreen <= '1'; -- Als 1 minuut is verstreken if Counter = CounterVal(Minutes => 1) then Counter <= 0; State <= StopWest; end if; -- Rood en geel licht in west/oost richting when StopWest => NorthRed <= '1'; WestYellow <= '1'; -- Als 5 seconden zijn verstreken if Counter = CounterVal(Seconds => 5) then Counter <= 0; State <= NorthNext; end if; end case; end if; end if; end process; end architecture;
De golfform na invoeren van het run 5 min commando in de ModelSim-console:

De golfform met cursors toegevoegd bij de overgangen naar en van de StartNorth-toestand:

Analyse
We hebben de timerberekeningen uit de vorige tutorial if Counter = ClockFrequencyHz * 5 -1 then vervangen door een aanroep van de nieuwe CounterVal-functie die we maakten: if Counter = CounterVal(Seconds => 5) then.
We zien uit de eerste golfform-screenshot dat de functionaliteit van de module ongewijzigd is. Functies gebruiken voor herhalende taken is een goede ontwerppraktijk. Vooral als je berekeningen kunt vervangen door leesbaardere regels met termen als Minutes en Seconds.
Een ander voordeel van functies is dat we de implementatie van alle timers tegelijk kunnen wijzigen, in plaats van regel voor regel. Als we bijvoorbeeld return TotalSeconds * ClockFrequencyHz; in de CounterVal-functie hadden geschreven, zouden alle timers één klokcyclus te lang duren. We kunnen dit dan wijzigen in return TotalSeconds * ClockFrequencyHz -1; in de CounterVal-functie, en alle timers worden tegelijk gefixt.
Als we de laatste golfform-screenshot bekijken, zien we waarom we 1 aftrekken van de timerwaarde die geretourneerd wordt door de CounterVal-functie. Deze golfform onderzoekt de duur van de StartNorth-toestand, die precies vijf seconden moet duren. Wanneer het State-signaal verandert naar StartNorth, is de Counter-waarde 0, en verandert hij pas na de volgende klokcyclus. Als we tot 500 klokcycli hadden geteld, zou de StartNorth-toestand eigenlijk 501 cycli geduurd hebben. Met onze testbench draaiend op 100 Hz, is 500 klokcycli precies vijf seconden.

Kernpunten
- Functies kunnen nul of meer parameters nemen, maar retourneren altijd een waarde
- Functies mogen geen
wait-statements bevatten - Pure functies hebben geen bijwerkingen, impure functies wel.
Ga naar de volgende tutorial »
VHDL
- Freesmes Slijper Technieken Beheersen: Een Praktische Gids
- Hoe molybdeen de moderne industrie vormt: toepassingen, voordelen en leveringsdynamiek
- realloc() in C gebruiken: Syntaxis, best practices & voorbeeld
- Beheersing van C's free() functie: Praktische gids & voorbeeld
- Dynamische string-lijsten in VHDL: Best Practices & Voorbeeld
- Een VHDL-testbench netjes beëindigen: methoden en best practices
- Een PWM-controller implementeren in VHDL: Ontwerp, Simulatie en FPGA-demo
- Willekeurige getallen genereren in VHDL: Van Uniform naar OSVVM
- In-process procedures benutten voor schonere VHDL FSM-ontwerpen
- Impure functies in VHDL gebruiken: FSM-leesbaarheid en onderhoudbaarheid verbeteren
- Een eindige-toestandsmachine in VHDL ontwerpen: een praktisch verkeerslichtvoorbeeld