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.

Betriebsmodus State-Machine

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).

Taster State-Machine

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)