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:
- 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.
- Ein Teiler muss zurückgesetzt werden, wenn er auf 9 steht und inkrementiert werden soll.
- 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)