2. Schritt (a: Simple State VHDL Modul)
Im zweiten Schritt wird das Top-Level VHDL-Modul erstellt. Es enthält folgendes:
- Eine Zustandsmaschine für das Booten bis zum operativen Modus
- Eine Zustandsmaschine für den Tastenmodus
- Die Parameteränderungen
- Die PWM
- Die Ansteuerung der LEDs
Die zugehörige Testbench wird im Schritt 2b erstellt.
Source-Code
Es wird zuerst eine neues VHDL Modul SimpleState.vhd erzeugt. Der Ausgangscode für diesen Schritt kann von hier kopiert werden:
Typen und Konstanten
Für die Zustandsmaschinen benötigt man eine Art enum und für die Tasten und Grenzen werden Konstanten definiert.
Die Zustände der Zustandsmaschinen werden einfach durch deren Aufzählung definiert:
-- ============================================================================
-- type definitions
-- ===================
--! \brief Operation mode state machine.\n
--! \b Reset Wait for release of reset.\n
--! \b BootA All LEDs on.\n
--! \b BootB Sequence LEDs.\n
--! \b BootC All LEDs off.\n
--! \b InitPrm Initialize parameters.\n
--! \b Start Start PWM.\n
--! \b Operative Check for inputs.\n
--!
--! \dotfile TOpState_dot.txt
type TOpState is (Reset, BootA, BootB, BootC, InitPrm, Start, Operative);
--! \brief Button mode state machine.\n
--! \b Nop Ignore up and down buttons.\n
--! \b Freq Modify frequency.\n
--! \b Duty Modify duty cycle.\n
--! \b Phase Modify phase of pulse.\n
--!
--! \dotfile TBtnState_dot.txt
type TBtnState is (Nop, Freq, Duty, Phase);
Für die Parameter und Zähler werden sogenannte subtypes deklariert.
-- generate special types for PWM
subtype TTimer is integer range 0 to 4; --! Timer for operation mode
subtype TCycle is integer range 0 to 99; --! PWM cycle counter
subtype TFreq is integer range 0 to 7; --! Timebase index 0f 100 times frequency
subtype TDuty is integer range 0 to 100; --! Duty cycle of PWM
subtype TPhase is integer range 0 to 99; --! Phase of PWM
Diesmal werden noch die Konstanten aus der Testbench kopiert. Normalerweise würden diese aus einem Package gelesen werden, aber das kommt noch.
-- ============================================================================
-- constant declarations
-- ===================
constant TimeIdx10ns : integer := 0;
constant TimeIdx100ns : integer := 1;
constant TimeIdx1us : integer := 2;
constant TimeIdx10us : integer := 3;
constant TimeIdx100us : integer := 4;
constant TimeIdx1ms : integer := 5;
constant TimeIdx10ms : integer := 6;
constant TimeIdx100ms : integer := 7;
constant TimeIdx1s : integer := 8;
Für das Modul werden noch sinnvollerweise Konstanten für die Funktionen der Taster sowie der Initialisierungswerte erstellt.
-- ===================
constant btn_Reset : integer := 0; --! Index of reset button
constant btn_Mode : integer := 1; --! Index of mode button
constant btn_Up : integer := 2; --! Index of up button
constant btn_Down : integer := 3; --! Index of down button
-- ===================
constant FreqInit : TFreq := 6; --! Initial value of frequency index
constant FreqInitSim : TFreq := 3; --! Initial value of frequency index for simulation
constant PhaseInit : TPhase := 0; --! Initial value of phase shift
constant DutyInit : TDuty := 50; --! Initial value of duty cycle
Signale
Für das Modul werden natürlich einige Signale benötigt. Wie immer sollte nicht mit deren Anzahl gespart werden.
-- State handling
signal OpStateC : TOpState; --! Operation mode state register (next state)
signal OpStateR : TOpState; --! Operation mode state register (actual state)
signal BootAEndC : std_logic; --! End of boot phase A
signal BootBEndC : std_logic; --! End of boot phase B
signal BootCEndC : std_logic; --! End of boot phase C
signal BtnStateC : TBtnState; --! Button mode state register (next state)
signal BtnStateR : TBtnState; --! Button mode state register (actual state)
signal OpTimerR : TTimer; --! Operation mode second timer
signal ResOpTimerC : std_logic; --! Reset OpTimerR
signal IncOpTimerC : std_logic; --! Increment OpTimerR
signal BtnModeC : std_logic_vector(1 downto 0); --! Button mode as binary
-- PWM parameter
signal SelFreq : TFreq; --! Selector for prescaler
signal PwmPhase : TPhase; --! PWM phase counter
signal PwmDuty : TDuty; --! PWM pulse length counter
signal SetParamC : std_logic; --! Set PWM parameters to initial state
signal IncParamC : std_logic; --! Increment selected parameter
signal DecParamC : std_logic; --! Decrement selected parameter
signal SetFreqC : std_logic; --! Set SelFreq to initial state
signal IncFreqC : std_logic; --! Increment SelFreq
signal DecFreqC : std_logic; --! Decrement SelFreq
signal SetPhaseC : std_logic; --! Set PwmPhase to initial state
signal IncPhaseC : std_logic; --! Increment PwmPhase
signal DecPhaseC : std_logic; --! Decrement PwmPhase
signal SetDutyC : std_logic; --! Set PwmDuty to initial state
signal IncDutyC : std_logic; --! Increment PwmDuty
signal DecDutyC : std_logic; --! Decrement PwmDuty
signal PrmChangedC : std_logic; --! A parameter will be changed
signal PrmChangedR : std_logic; --! A parameter is being changed
-- PWM
signal PwmCycle : TCycle; --! Time base for PWM cycle
signal ResCycleC : std_logic; --! Reset PwmCycle
signal IncCycleC : std_logic; --! Increment PwmCycle
signal PwmStartCnt : TPhase; --! Delay counter for phase
signal SetStartC : std_logic; --! Set PwmStartCnt to start value
signal DecStartC : std_logic; --! Decrement PwmStartCnt
signal PulseLenCnt : TCycle; --! Delay counter for phase
signal SetLengthC : std_logic; --! Set PulseLenCnt to start value
signal DecLengthC : std_logic; --! Decrement PulseLenCnt
signal PwmOutR : std_logic; --! PWM output
signal SetPwmC : std_logic; --! Set PWM output
signal ResPwmC : std_logic; --! Reset PWM output
signal PwmToggleR : std_logic; --! Toggle with PWM cycle
signal PwmToggleC : std_logic; --! Toggle PwmToggleR
Realisierung der Zustandsmaschinen
Im folgenden wird die Funktion der beiden Zustandsmaschinen erläutert und diese implementiert.
Die Hauptzustandsmaschine
Diese Zustandsmaschine kontrolliert den Boot-Prozess bis zum operativen Betrieb und die Initialisierung der Parameter.
Die Hauptzustandsmaschine startet sobald sie aus dem Reset kommt.
Über die Zeitbasis und einen zusätzlichen Zähler werden die einzelnen Boot-Phasen durchlaufen.
Der Zustand Start wird nur für einen einzigen Takt angefahren, dient zum Initialisieren der PWM-Parameter und wird sofort zum Zustand Operative verlassen.
Bei Parameteränderungen startet die Zustandsmaschine die PWM erneut und wartet ansonsten darauf dass der Reset-Taster gedrückt wird. Hier nicht eingezeichnet ist, dass der Reset-Button natürlich auch zu jedem anderen Zeitpunkt zum Zustand Reset führt.
Das Zustandsdiagramm links wird übrigens automatisch von doxygen erstellt. Die Definitionsdatei dafür - TOpState_dot.txt - muss allerdings manuell erstellt werden.
Die Zustandsmaschine für die Parametrierung
Diese Zustandsmaschine kontrolliert die einzelnen Parametriermodi.
Die Zustandsmaschine für den Parametriermodus wird durch das Ereignis OpState = Start auf Nop gestellt und läuft dann mit jeder Betätigung des Modus-Tasters zyklisch die Phasen Freq, Duty, Phase und wieder Nop durch.
Natürlich hätte in diesem einfachen Fall auch ein einfacher 2-Bit Zähler eingesetzt werden können (vielleicht wird diese Zustandsmaschine auch als 2 Bit Zähler synthetisiert).
Der Prozess der Zustandsmaschinen
Der eigentliche Prozess ist unspektakulär, da die eigentliche Logik im kombinatorischen Bereich kodiert ist.
Es gibt zwei Zustandsmaschinen, die bei Reset auf ihren Startwert gesetzt und anschließend die Werte entsprechend des Zustandsdiagramms annehmen.
Um während der Bootphase die einzelnen Sekunden zu zählen gibt es noch einen Zähler. Dieser wird beim Wechsel der Bootphasen zurückgesetzt und zählt dann einfach los.
--! State handling
p_states : process (Clk)
begin
if rising_edge(Clk) then
if (Init = '1') then
OpStateR <= Reset; -- initial state
BtnStateR <= Nop; -- initial state
else
OpStateR <= OpStateC; -- set next state
BtnStateR <= BtnStateC; -- set next state
end if;
-- boot timer
if (ResOpTimerC = '1') then
OpTimerR <= 0;
elsif (IncOpTimerC = '1') then
OpTimerR <= OpTimerR + 1;
end if;
end if;
end process;
Um die Zustandsübergänge zu programmieren wird der kombinatorische Zustandswert entsprechend des aktuellen (aus den Registern) angepasst und ergibt so den nächsten Zustand:
-- decide the next state of state machines
OpStateC <= ---------------------------------------------------------------
-- Reset: Wait for release of reset
BootA when (OpStateR = Reset) else
---------------------------------------------------------------
-- BootA: Alle LEDs an
BootB when (OpStateR = BootA) and (BootAEndC = '1') else
---------------------------------------------------------------
-- BootB: LEDs sequentiell
BootC when (OpStateR = BootB) and (BootBEndC = '1') else
---------------------------------------------------------------
-- BootC: Alle LEDs aus
InitPrm when (OpStateR = BootC) and (BootCEndC = '1') else
---------------------------------------------------------------
-- InitPrm: Initialiere Parameter
Start when (OpStateR = InitPrm) else
---------------------------------------------------------------
-- Start: Starte PWM
Operative when (OpStateR = Start) else
---------------------------------------------------------------
-- Operative: Wait for parameter change
Start when (OpStateR = Operative) and (PrmChangedR = '1') else
---------------------------------------------------------------
-- No state change
OpStateR;
Oft verharrt eine Zustandsmaschine nur für einen Takt in einem gewissen Zustand. Zum Beispiel um ein Ereignis zu generieren. In diesem Fall wird der Folgezustand ohne weitere Bedingung direkt angefahren:
Start when (OpStateR = InitPrm) else
ansonsten wird auf eine oder mehrere Bedingungen gewartet:
BootB when (OpStateR = BootA) and (BootAEndC = '1') else
Von einem Zustand aus können natürlich auch mehrere Folgezustände angefahren werden. z.B.:
FolgeZ1 when (StateR = Z) and (Bedingung1= '1') else
FolgeZ2 when (StateR = Z) and (Bedingung2= '1') else
Wenn in diesem Fall beide Bedingungen zutreffen wird FogeZ1 angefahren, da die zweite Bedingung ja im else-Zweig steht.
Die Bedingungen der Zustandsübergänge ergeben sich wie folgt:
-- BootA ends after one second so on the first timer tick
BootAEndC <= '1' when (OpStateR = BootA) and (IncOpTimerC = '1') else
'0';
-- BootB ends after fourth timer tick
BootBEndC <= '1' when (OpStateR = BootB) and (OpTimerR >= 3) and (IncOpTimerC = '1') else
'0';
-- BootC ends after second timer tick
BootCEndC <= '1' when (OpStateR = BootC) and (OpTimerR >= 1) and (IncOpTimerC = '1') else
'0';
-- Boot timer tick speed depends if running in simulation or not
IncOpTimerC <= TimeBase(TimeIdx100us) when (Simulation = true) else
TimeBase(TimeIdx1s);
-- Boot timer starts on reset, and end of states BootA or BootB
ResOpTimerC <= '1' when (Init = '1') else
'1' when (BootAEndC = '1') else
'1' when (BootBEndC = '1') else
'0';
Die Kodierung schaut etwas umständlicher aus, als notwendig. Das liegt daran, dass die Signale teilweise auch anderweitig verwendet werden.
Die zweite Zustandsmaschine und deren Anzeige auf den LEDs wird folgendermaßen realisiert:
BtnStateC <= ---------------------------------------------------------------
-- From any state
Nop when (OpStateR = InitPrm) else
---------------------------------------------------------------
-- Nop: Ignore up and down buttons
Freq when (BtnStateR = Nop) and (ButtonPressC(btn_Mode) = '1') else
---------------------------------------------------------------
-- Freq: Modify frequency
Duty when (BtnStateR = Freq) and (ButtonPressC(btn_Mode) = '1') else
---------------------------------------------------------------
-- Duty: Modify duty cycle
Phase when (BtnStateR = Duty) and (ButtonPressC(btn_Mode) = '1') else
---------------------------------------------------------------
-- Phase: Modify phase of pulse
Nop when (BtnStateR = Phase) and (ButtonPressC(btn_Mode) = '1') else
---------------------------------------------------------------
-- No state change
BtnStateR;
BtnModeC <= "00" when (BtnStateR = Nop) else
"01" when (BtnStateR = Freq) else
"10" when (BtnStateR = Duty) else
"11" when (BtnStateR = Phase) else
"XX";
Die Parametrierung
Es gibt Parameter für die Frequenz, den Duty-Cycle und die Phase des PWM-Signals. Jeder dieser Parameter kann gesetzt, inkrementiert und dekrementiert werden. Im Prozess wird eine entsprechende Hardware implementiert:
--! PWM parameter
p_pwm_param : process (Clk)
begin
if rising_edge(Clk) then
-- Delay the PrmChangedC signal one cycle to use changed
-- parameters in the next cycle
if (Init = '1') then
PrmChangedR <= '0';
else
PrmChangedR <= PrmChangedC;
end if;
if (SetFreqC = '1') then
if (Simulation) then
SelFreq <= FreqInitSim;
else
SelFreq <= FreqInit;
end if;
elsif (IncFreqC = '1') then
SelFreq <= SelFreq + 1;
elsif (DecFreqC = '1') then
SelFreq <= SelFreq - 1;
end if;
if (SetPhaseC = '1') then
PwmPhase <= PhaseInit;
elsif (IncPhaseC = '1') then
PwmPhase <= PwmPhase + 1;
elsif (DecPhaseC = '1') then
PwmPhase <= PwmPhase - 1;
end if;
if (SetDutyC = '1') then
PwmDuty <= DutyInit;
elsif (IncDutyC = '1') then
PwmDuty <= PwmDuty + 1;
elsif (DecDutyC = '1') then
PwmDuty <= PwmDuty - 1;
end if;
end if;
end process;
Das Setzen der Parameter erfolgt im operativen Fall, wenn die die Hauptzustandsmaschine den Zustand InitPrm durchfährt. Das setzt dann alle Parameter:
-- set parameters to initial state on reset and Start phase
SetParamC <= '1' when (Init = '1') else
'1' when (OpStateR = InitPrm) else
'0';
SetFreqC <= SetParamC;
SetPhaseC <= SetParamC;
SetDutyC <= SetParamC;
Für das Verändern der Parameter werden zuerst die Signale IncParamC und DecParamC erzeugt, diese sorgen dafür, dass die Auf/Ab Taster nur ausgewertet werden, wenn die Hauptzustandsmaschine im operativen Modus angekommen ist.
Die einzelnen Parameter werden dann entsprechend inkrementiert bzw. dekrementiert, wenn der richtige Modus ausgewählt ist. Des weiteren kann - durch die Verwendung der subtypes - das Inkrementieren/Dekrementieren verhindert werden, wenn ein Parameter bereits das Limit erreicht hat.
Abschließend wird noch ein Signal erzeugt, das gesetzt ist, falls sich einer der Parameter ändert.
-- correct parameters depending state and buttons
IncParamC <= '0' when (OpStateR /= Operative) else
'1' when (ButtonPressC(btn_Up) = '1') else
'0';
DecParamC <= '0' when (OpStateR /= Operative) else
'1' when (ButtonPressC(btn_Down) = '1') else
'0';
IncFreqC <= '0' when (SelFreq = TFreq'high) else
-- decrement index multiplies frequency by 10
'1' when (DecParamC = '1') and (BtnStateR = Freq) else
'0';
DecFreqC <= '0' when (SelFreq = TFreq'low) else
-- increment index reduces frequency by factor 10
'1' when (IncParamC = '1') and (BtnStateR = Freq) else
'0';
IncPhaseC <= '0' when (PwmPhase = TPhase'high) else
'1' when (IncParamC = '1') and (BtnStateR = Phase) else
'0';
DecPhaseC <= '0' when (PwmPhase = TPhase'low) else
'1' when (DecParamC = '1') and (BtnStateR = Phase) else
'0';
IncDutyC <= '0' when (PwmDuty = TDuty'high) else
'1' when (IncParamC = '1') and (BtnStateR = Duty) else
'0';
DecDutyC <= '0' when (PwmDuty = TDuty'low) else
'1' when (DecParamC = '1') and (BtnStateR = Duty) else
'0';
PrmChangedC <= '1' when (IncFreqC = '1') else
'1' when (DecFreqC = '1') else
'1' when (IncPhaseC = '1') else
'1' when (DecPhaseC = '1') else
'1' when (IncDutyC = '1') else
'1' when (DecDutyC = '1') else
'0';
Die Implementierung der PWM
Die PWM hat zwei Ausgangssignale: Die eigentliche PWM (PwmOutR) und die Zeitbasis (PwmToggleR).
Für die Generierung der PWM werden drei Zähler benötigt. Einer für den Zyklus, einer für die Phase und einer für die Länge des Pulses.
Die Hardware dafür wird im Prozess definiert:
--! PWM counter
p_pwm_cnt : process (Clk)
begin
if rising_edge(Clk) then
if (Init = '1') then
PwmOutR <= '0';
elsif (SetPwmC = '1') then
PwmOutR <= '1';
elsif (ResPwmC = '1') then
PwmOutR <= '0';
end if;
if (Init = '1') then
PwmToggleR <= '0';
elsif (PwmToggleC = '1') then
PwmToggleR <= not PwmToggleR;
end if;
if (ResCycleC = '1') then
PwmCycle <= 0;
elsif (IncCycleC = '1') then
PwmCycle <= PwmCycle + 1;
end if;
if (SetStartC = '1') then
PwmStartCnt <= PwmPhase;
elsif (DecStartC = '1') then
PwmStartCnt <= PwmStartCnt - 1;
end if;
if (SetLengthC = '1') then
PulseLenCnt <= PwmDuty;
elsif (DecLengthC = '1') then
PulseLenCnt <= PulseLenCnt - 1;
end if;
end if;
end process;
Die Steuersignale für den Prozess sind kombinatorische Signale:
-- only generate PWM in operative mode and with the frequency (timebase) selected
IncCycleC <= '0' when (OpStateR /= Operative) else
TimeBase(SelFreq);
-- reset cycle counter, when it reaches its limit
ResCycleC <= '1' when (OpStateR = Start) else
'1' when (PwmCycle = TCycle'high) and (IncCycleC = '1') else
'0';
-- phase shifter is loadad on start of cycle and decrements down to zero
SetStartC <= ResCycleC;
DecStartC <= '1' when (IncCycleC = '1') and (PwmStartCnt /= 0) else
'0';
-- Pulse lenght counter starts with PWM output and decrements while PWM is set
SetLengthC <= '1' when (SetPwmC = '1') else
'0';
DecLengthC <= '1' when (IncCycleC = '1') and (PwmOutR = '1') else
'0';
-- Start PWM output on following conditions:
-- never start when duty cycle is zero
SetPwmC <= '0' when (PwmDuty = 0) else
-- start at first cycle if phase shift is 0
'1' when (OpStateR = Start) and (PwmPhase = 0) else
-- start at following cycles if phase shift is 0
'1' when (ResCycleC = '1') and (PwmPhase = 0) else
-- start when phase shifter decrements to 0
'1' when (IncCycleC = '1') and (PwmStartCnt = 1) else
'0';
-- Stop PWM output on following conditions:
-- stop if new parameters are used (abort)
ResPwmC <= '1' when (PrmChangedC = '1') else
-- stop if length counter decrements to 0
'1' when (IncCycleC = '1') and (PulseLenCnt = 1) else
'0';
PwmToggleC <= ResCycleC;
Die LED Ausgabe
Entsprechend des Modus müssen nur noch die LED Ausgangssignale erzeugt werden:
-- Led output multiplexer: boot stages or operative output
LedC <= "1111" when (OpStateR = BootA) else
"0001" when (OpStateR = BootB) and (OpTimerR = 0) else
"0010" when (OpStateR = BootB) and (OpTimerR = 1) else
"0100" when (OpStateR = BootB) and (OpTimerR = 2) else
"1000" when (OpStateR = BootB) and (OpTimerR = 3) else
"0000" when (OpStateR = BootC) else
LedOpC;
-- operative output is the button mode, activity and pwm
LedOpC <= BtnModeC & PwmToggleR & PwmOutR;
Weiter zum 2. Schritt (b)