1. Schritt (a: System Services VHDL Modul)

Im ersten Schritt wird ein VHDL-Modul erstellt welches:

  • Einen Systemtakt ausgibt
  • Einen synchronen Reset erzeugt
  • Eine Systemzeit (Zeitstempel) erzeugt
  • Eine Zeitbasis erzeugt 

Eine Testbench zu diesem Modul wird im Schritt 1b erstellt.

Der Grund, diese Funktionen in ein Modul zu verlagern ist, dass es so einfacher zu testen ist und nach noch folgenden Erweiterungen in vielen FPGAs eingesetzt werden kann.

Oft hat man Prozesse in FPGAs abzubilden, welche sich auf die reale Zeit beziehen, z.B. eine Entprellung von Taster-Eingängen für die Glitches von weniger als z.B. 5 ms ausgeblendet werden sollen. Man kann natürlich jedes mal einen Zähler auf 100 MHz loslaufen lassen und wenn er 500000 erreicht hat reagieren. Oder man benutzt die Zeitbasis und zählt in ms Schritten bis 5.

Source-Code

Der Ausgangscode für diesen Schritt kann hier eingesehen werden:

Im weiteren Verlauf wird wo nötig der Ausgangscode und die notwendigen Änderungen erklärt.

Systemtakt erzeugen

Derzeit wird der Oszillatoreingang als Systemtakt weitergereicht. Damit hat man aber schon einen Eingriffspunkt um später in diesem Projekt eine PLL einzubauen, die dann für das ZYBO-Board aus den 125 MHz 100 MHz generiert.

    -- Clock generation
    ProcClk     <= ExtClk;
    Clk         <= ProcClk;

Da Clk als Ausgang definiert ist, kann man es nicht im Modul als Taktsignal verwenden. Deswegen wird das interne Signal ProcClk dazwischengeschaltet. ProcClk kann intern verwendet werden und ist mit dem Clk-Ausgang identisch.

Synchronen Reset erzeugen

Die Reset-Erzeugung ist mehr oder minder vom Projekt BlinkingLed übernommen worden. Der Grund, das Reset-Signal hier zu erzeugen hängt auch mit der (später verwendeten) PLL zusammen. So lange die PLL nicht eingeschwungen ist, muss nämlich das FPGA im Reset gehalten werden.

    --! Reset generation
    p_reset : process (ProcClk)
    begin
        if rising_edge(ProcClk) then
            GenInitR    <= GenInitR and not InitR;  -- Release InitR after it was activated
            InitR       <= InitC;
            ResetR      <= Reset;
        end if;
    end process;

    InitC               <=  ResetR or GenInitR;         -- Reset on button and power on
    Init                <=  InitR;

Zeitstempel

Als Zeitstempel wird hier ein binärer Zähler verwendet, welcher jeden Takt inkrementiert wird. Es muss nur eine sinnvolle Länge für den Zähler festgelegt werden. In diesem Fall werden wir einen 55 Bit-Zähler verwenden, der bei 100 MHz Taktfrequenz über 10 Jahre benötigt um überzulaufen.

Es sind natürlich auch andere Zeitstempel denkbar, z.B. Sekunden und Sekundenbruchteile, oder Zähler für die einzelnen Dekaden.

Zuerst werden die notwendigen Signale deklariert:

-- Time stamp
signal	TimeStampR		: std_logic_vector(54 downto 0);	--! Timestamp counter

Anschließend wird im Prozess p_timebase der Zeitstempel entsprechend der Spezifikation erzeugt.

--! Time stamp
p_timestamp : process (ProcClk)
begin
	if rising_edge(ProcClk) then
		if (InitR = '1') then
			TimeStampR        <= (others => '0');
		else
			TimeStampR        <= TimeStampR + 1;
		end if;
	end if;
end process;
TimeStamp        <= TimeStampR;

Die Zeitbasis erzeugen

Bei der Zeitbasis handelt es sich um einen Vektor von Signalen welche synchron zueinander in definierten Zeitabständen für jeweils einen Takt aktiv sind.

Im Programmgerüst wurden schon Konstanten definiert, um auf die einzelnen Signale dieses Vektors zugreifen zu können. Das hat den Vorteil, dass man später noch den Vektor erweitern kann um z.B. innerhalb jeder Dekade eine 1, 2, 5 Aufteilung zu machen ohne dadurch Code-Änderungen nach sich zu ziehen.

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;

Da der angenommene Grundtakt 100 MHz (= 10 ns) ist, müssen also eine Kette von 8 gleichartigen Teilern für die Dekaden bis herunter auf 1 s erzeugt werden.

Dazu werden erst einmal die Signale für die Struktur dieser Teilerkette erzeugt. In VHDL können allerdings mehrdimensionale Signale nicht einfach aus bereits bekannten Signalen erzeugt werden. Es müssen jeweils neue Signaltypen definiert werden.

-- Time base
type	tDecadeChain is array(7 downto 0) of integer range 0 to 9;
signal	DecadeChainR 	: tDecadeChain;					--! one counter for each decade
signal	IncDecadeC	 	: std_logic_vector(8 downto 0);	--! increment decade counter
signal	IncDecadeR	 	: std_logic_vector(8 downto 0);	--! increment decade counter (registered)
signal	ResDecadeC	 	: std_logic_vector(7 downto 0);	--! reset decade counter
signal	ResDecadeR	 	: std_logic_vector(7 downto 0);	--! reset decade counter (registered)

Jetzt haben wir also die Interface-Signale einer Teilerkette erzeugt, wobei jeder einzelne Teiler über seinen Index angesprochen werden kann.

Ein normaler Teiler würde in etwa so implementiert werden:

if (ResDividerC = '1') then
	DividerR	<= 0;
else
	if (IncDividerC = '1') then
		DividerR	<= DividerR + 1;
	end if;
end if;

Diese Funktionalität muss jetzt nur noch in eine Schleife gepackt werden. Des weiteren werden aus Optimierungsgründen anstatt der kombinatorischen Signalen Registersignale verwendet.

-- Divider chain handling
for i in 0 to 7 loop
	if ((ResDecadeR(i) = '1') or (InitR = '1')) then
		DecadeChainR(i)	<= 0;
	else
		if (IncDecadeR(i) = '1') then
			DecadeChainR(i)	<= DecadeChainR(i) + 1;
		end if;
	end if;
end loop;

Um auf die einzelnen Teiler der Kette zuzugreifen wird eine for Schleife verwendet. Die Laufvariable i muss dabei nicht extra deklariert werden.

Damit ist steht jetzt die Struktur, die nur noch entsprechend stimuliert werden muss. Ohne sich große Gedanken machen zu müssen können zuerst noch die Register für Reset und Inkrement eingebaut werden.

-- Register stages for divider contr
if (InitR = '1') then
	IncDecadeR		<= "000000001";
	ResDecadeR		<= (others => '0');
else
	IncDecadeR		<= IncDecadeC;
	ResDecadeR		<= ResDecadeC;
end if;

Der Reset Wert für IncDecadeR ist im untersten Bit '1'. Der erste Teiler soll ja die 100 MHz teilen, inkrementiert also andauernd, wenn er nicht am Ende der Dekade zurückgesetzt wird.

Somit ist die Struktur der Teilerkette definiert. Jetzt folgt die eigentliche Denkarbeit. Diese begrenzt sich darauf wann ein Teiler inkrementiert, wann er zurückgesetzt und wann das Ausgangssignal aktiviert werden muss:

  1. Da wir eine Kette von Teilern haben ist erst einmal klar, dass immer wenn ein Teiler auf 0 zurückgesetzt wird, der nächste Teiler inkrementiert werden muss.
  2. Ein Teiler muss zurückgesetzt werden, wenn er auf 9 steht und inkrementiert werden soll.
  3. Das Ausgangsignal der Zeitbasis ist identisch mit dem Inkrement des zugehörigen Teilers.

Für den ersten Teiler ergeben sich folgende Bedingungen:

IncDecadeC(0)	<= '1';
ResDecadeC(0)	<= '1' when	(DecadeChainR(0) = 8)							else '0';
IncDecadeC(1)	<= '1' when (ResDecadeC(0) = '1')							else '0';

Der Reset wird, wie wir hier sehen schon beim Zählerstand 8 generiert. Das liegt daran, dass ja noch eine Registerstufe durchlaufen wird, und einen Takt später ist der Zähler dann eben 9.

Die Signale für die weiteren Zähler werden in einer for generate Schleife erzeugt:

g_inc_dec : for i in 1 to 7 generate
	ResDecadeC(i)	<= '1' when (DecadeChainR(i) = 9) and (IncDecadeC(i) = '1')	else '0';
	IncDecadeC(i+1)	<= '1' when (ResDecadeC(i) = '1')							else '0';
end generate;

Nun fehlt nur noch die Verknüpfung mit dem Ausgang des Moduls:

TimeBase <= IncDecadeR;

Weiter zum 1. Schritt (b)