Programming lesson
VHDL-Tutorial: Entwurf einer sequentiellen Stoppuhr für das Altera DE1-SoC Board
Lerne, wie du mit VHDL eine Stoppuhr im 10-Millisekunden-Takt entwirfst – inklusive Zähler, Taktteiler und 7-Segment-Anzeige. Perfekt für das ECSE 222 Lab #2.
Einführung in sequentielle Schaltungen mit VHDL
In diesem Tutorial lernst du, wie man sequentielle Logikschaltungen in VHDL beschreibt. Sequentielle Schaltungen – im Gegensatz zu kombinatorischen – besitzen einen internen Zustand, der sich mit jedem Taktzyklus ändern kann. Ein alltägliches Beispiel ist eine Stoppuhr, wie sie etwa bei Sportevents oder im Gaming zur Zeitnahme verwendet wird. Stell dir vor, du streamst ein E-Sport-Turnier und musst die Rundenzeiten in Echtzeit anzeigen – genau solche Systeme lassen sich mit VHDL auf einem FPGA realisieren.
Das Ziel dieses Labors ist es, eine Stoppuhr zu entwerfen, die alle 10 Millisekunden zählt und die vergangene Zeit auf sechs 7-Segment-Anzeigen darstellt. Die Steuerung erfolgt über drei Taster: Start (PB0), Pause (PB1) und Reset (PB2). Der gesamte Entwurf wird auf dem Altera DE1-SoC Board implementiert. Dabei durchläufst du die Schritte: Zähler entwerfen, Taktteiler bauen, Stoppuhr zusammensetzen und simulieren.
1. Grundlagen: Zähler in VHDL
Ein Zähler ist die einfachste sequentielle Schaltung. Er zählt bei jedem Taktimpuls hoch (oder runter) und speichert den aktuellen Wert. In der Praxis begegnen uns Zähler überall: in digitalen Uhren, in der Prozesssteuerung oder in der Spieleentwicklung für Punktestände. Für unser Projekt benötigen wir einen 4-Bit-Aufwärtszähler mit asynchronem Reset (aktiv low) und einem Enable-Signal.
Entity-Deklaration des Zählers
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity gNN_counter is
Port ( enable : in std_logic;
reset : in std_logic;
clk : in std_logic;
count : out std_logic_vector(3 downto 0));
end gNN_counter;Die Architektur beschreibt das Verhalten: Bei steigender Taktflanke wird der Zähler erhöht, wenn Enable='1' ist. Bei Reset='0' wird der Zähler zurückgesetzt.
architecture Behavioral of gNN_counter is
signal cnt : unsigned(3 downto 0) := (others => '0');
begin
process(clk, reset)
begin
if reset = '0' then
cnt <= (others => '0');
elsif rising_edge(clk) then
if enable = '1' then
cnt <= cnt + 1;
end if;
end if;
end process;
count <= std_logic_vector(cnt);
end Behavioral;Beachte: Der Reset ist asynchron, d.h. er reagiert sofort, unabhängig vom Takt. Das ist wichtig, um die Stoppuhr jederzeit zurücksetzen zu können.
2. Taktteiler: Von 50 MHz zu 10 ms
Das DE1-SoC Board liefert einen 50-MHz-Takt. Für eine Zeitauflösung von 10 ms benötigen wir ein Enable-Signal, das alle 500.000 Taktzyklen einmal aktiv wird (50 MHz × 10 ms = 500.000). Der Taktteiler zählt von T-1 bis 0 herunter und setzt den Ausgang en_out auf '1', wenn der Zähler 0 erreicht.
Berechnung von T
Bei 50 MHz und 10 ms: T = 50.000.000 Hz × 0,01 s = 500.000. Der Taktteiler verwendet also einen 19-Bit-Zähler (2^19 = 524.288 > 500.000).
Entity des Taktteilers
entity gNN_clock_divider is
Port ( enable : in std_logic;
reset : in std_logic;
clk : in std_logic;
en_out : out std_logic);
end gNN_clock_divider;Die Implementierung nutzt einen Down-Counter. Bei jedem Takt wird der Zähler dekrementiert; bei Erreichen von 0 wird en_out='1' und der Zähler auf T-1 zurückgesetzt.
architecture Behavioral of gNN_clock_divider is
constant T : integer := 500000;
signal cnt : integer range 0 to T-1 := 0;
begin
process(clk, reset)
begin
if reset = '0' then
cnt <= 0;
en_out <= '0';
elsif rising_edge(clk) then
if enable = '1' then
if cnt = 0 then
cnt <= T-1;
en_out <= '1';
else
cnt <= cnt - 1;
en_out <= '0';
end if;
end if;
end if;
end process;
end Behavioral;3. Stoppuhr-Architektur
Die Stoppuhr besteht aus sechs Zählern (für Zehntelsekunden, Sekunden und Minuten) und einem Taktteiler. Die Taster steuern einen Zustandsautomaten, der die Modi Running, Paused und Reset umschaltet. Die 7-Segment-Decoder aus Lab #1 wandeln den 4-Bit-Zählerstand in die Ansteuerung der LED-Segmente um.
Top-Level-Entity
entity gNN_stopwatch is
Port ( start : in std_logic;
stop : in std_logic;
reset : in std_logic;
clk : in std_logic;
HEX0 : out std_logic_vector(6 downto 0);
HEX1 : out std_logic_vector(6 downto 0);
HEX2 : out std_logic_vector(6 downto 0);
HEX3 : out std_logic_vector(6 downto 0);
HEX4 : out std_logic_vector(6 downto 0);
HEX5 : out std_logic_vector(6 downto 0));
end gNN_stopwatch;Die Architektur instanziiert die Komponenten und verbindet sie. Die Taster sind aktiv low (gedrückt = '0'). Ein Zustandsregister speichert den aktuellen Modus.
architecture Structural of gNN_stopwatch is
signal en_10ms : std_logic;
signal cnt_centi0, cnt_centi1, cnt_sec0, cnt_sec1, cnt_min0, cnt_min1 : std_logic_vector(3 downto 0);
signal enable_chain : std_logic_vector(5 downto 0);
type state_type is (IDLE, RUNNING, PAUSED);
signal state : state_type := IDLE;
begin
-- Taktteiler
clk_div: entity work.gNN_clock_divider
port map (enable => '1', reset => reset, clk => clk, en_out => en_10ms);
-- Zustandsautomat
process(clk, reset)
begin
if reset = '0' then
state <= IDLE;
elsif rising_edge(clk) then
case state is
when IDLE =>
if start = '0' then state <= RUNNING; end if;
when RUNNING =>
if stop = '0' then state <= PAUSED; end if;
if reset = '0' then state <= IDLE; end if;
when PAUSED =>
if start = '0' then state <= RUNNING; end if;
if reset = '0' then state <= IDLE; end if;
end case;
end if;
end process;
-- Enable für Zählerkette
enable_chain(0) <= en_10ms when state = RUNNING else '0';
-- Zähler und Decoder (vereinfacht dargestellt)
-- ... (Instanzen für cnt_centi0, cnt_centi1, etc.)
end Structural;Die Zähler sind kaskadiert: Der erste Zähler (Zehntelsekunden) zählt bei jedem en_10ms-Impuls. Bei Überlauf von 9 wird der nächste Zähler erhöht, usw. Die 7-Segment-Decoder wandeln die 4-Bit-Werte in die Segmentmuster um.
4. Simulation mit ModelSim
Bevor du die Schaltung auf dem Board testest, solltest du eine funktionale Simulation durchführen. Schreibe eine Testbench, die die Taster simuliert und die Ausgänge überprüft. Achte darauf, dass der asynchrone Reset korrekt funktioniert und die Stoppuhr im Pause-Modus anhält.
Testbench-Beispiel für den Zähler
entity tb_counter is
end tb_counter;
architecture sim of tb_counter is
signal clk, enable, reset : std_logic := '0';
signal count : std_logic_vector(3 downto 0);
begin
uut: entity work.gNN_counter
port map (enable => enable, reset => reset, clk => clk, count => count);
clk_process: process
begin
clk <= '0'; wait for 10 ns;
clk <= '1'; wait for 10 ns;
end process;
stim: process
begin
reset <= '0'; wait for 20 ns;
reset <= '1'; enable <= '1';
wait for 200 ns;
enable <= '0';
wait for 100 ns;
enable <= '1';
wait;
end process;
end sim;Simuliere auch den Taktteiler und die gesamte Stoppuhr. In der Wellenform solltest du sehen, dass en_out alle 500.000 Taktzyklen für einen Takt high wird.
5. Implementierung auf dem DE1-SoC Board
Nach erfolgreicher Simulation kompilierst du das Projekt in Quartus und weist die Pins zu. Die Taster sind an GPIO-Pins angeschlossen, die 7-Segment-Anzeigen an dedizierten Ausgängen. Beachte, dass die Taster invertiert sind (gedrückt = '0'). Lade das Bitstream auf das Board und teste die Funktion: Drücke PB0 zum Starten, PB1 zum Pausieren, PB2 zum Zurücksetzen.
Fazit
Du hast erfolgreich eine sequentielle Schaltung in VHDL entworfen und auf einem FPGA implementiert. Dieses Wissen ist grundlegend für das Verständnis von digitalen Systemen und eingebetteten Anwendungen. Mit den gleichen Techniken lassen sich auch komplexere Systeme wie digitale Timer, Frequenzzähler oder Zustandsautomaten für AI-Anwendungen realisieren. Viel Erfolg bei deinem ECSE 222 Lab!