Die Berlin-Uhr (Mengenlehreuhr) aus dem Jahr 1975 eignet sich natürlich ideal für eine kleine VHDL-Spielerei.
Hier gibt es Infos dazu: Berlin-Uhr – Wikipedia
Hinter der Uhr steckt ein Fünfersystem bei den Einerstellen von Minuten und Stunden: es werden mit 4 Lampen jeweils 0..4 Minuten bzw. 0..4 Stunden gezählt, dann kommt ein Überlauf in die nächst höhere Stelle. Dort gibt es dann 11 Lampen für die 5er-Minuten , womit 55 Minuten aufsummiert werden können. Und es gibt 4 Lampen für die 5er-Stunden, was in Summe 20 Stunden ergibt.
Das ursprüngliche Design war asynchron mit Schieberegistern aufgebaut: das Überlauf-Bit der jeweils vorhergehenden Stufe sorgte dafür, dass die nachfolgende Stufe hochgezählt und die vorhergehende Stufe zurückgesetzt wird. So ein asynchrones Design hat prinzipiell das Problem, dass der Rücksetz-Impuls im Grunde nur ein kurzer Glitch ist, dieser Glitch aber auch als Zähl-Impuls verwendet werden muss. Es muss also z.B. durch eine Verzögerung des Rücksetzens garantiert werden, dass dieser Glitch ausreichend lang für das Weiterschieben/Hochzählen der nachfolgenden Stufe ist.
So etwas lässt sich nicht ohne berechtigte Warnungen aus VHDL-Code in ein FPGA synthetisieren, deshalb habe ich hier ein taktsynchrones Design für einen 50 MHz Takt aufgesetzt, das diese Glitch-Problematik sicher umgeht:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity berlinuhr is Generic ( fosc : integer := 50000000 ); Port ( clk : in STD_LOGIC; setslow: in STD_LOGIC; setfast: in STD_LOGIC; sec : buffer STD_LOGIC := '0'; min : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0'); min5 : buffer STD_LOGIC_VECTOR (10 downto 0) := (others=>'0'); hr : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0'); hr5 : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0') ); end berlinuhr; architecture verhalten of berlinuhr is signal spresc : integer range 0 to fosc-1 := 0; -- Prescaler für Sekunde signal mpresc : integer range 0 to 60-1 := 0; -- Prescaler für Minute = 60 Sekunden signal cntup : std_logic; begin process begin wait until rising_edge(clk); cntup <= '0'; if spresc < fosc-1 then spresc <= spresc + 1; else -- 1 s vorbei spresc <= 0; sec <= not sec; if mpresc < 60-1 then mpresc <= mpresc +1; else cntup <= '1'; -- 1 min vorbei mpresc <= 0; end if; end if; -- Uhrzeit stellen hat Vorrang if (setslow='1' and spresc=fosc-1) -- langsam stellen / 1 min vorwärts pro sec or (setfast='1' and spresc>=(fosc/60)-1) -- schnell stellen / 60 min (= 1 h) pro sec then cntup <= '1'; spresc <= 0; mpresc <= 0; sec <= '0'; end if; end process; process begin wait until rising_edge(clk); if cntup='1' then min <= min(2 downto 0) & '1'; if min(3)='1' then min <= (others=>'0'); min5 <= min5(9 downto 0) & '1'; if min5(10) = '1' then min5 <= (others=>'0'); hr <= hr(2 downto 0) & '1'; if hr(3) = '1' then hr <= (others=>'0'); hr5 <= hr5(2 downto 0) & '1'; end if; if hr5(3)='1' and hr(2)='1' then -- Tagesüberlauf extra und bevorzugt abhandeln hr <= (others=>'0'); hr5 <= (others=>'0'); end if; end if; end if; end if; end process; end verhalten;
Mit dieser Testbench lässt sich dann kontrollieren, ob die Uhr wie erwartet reagiert:
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.numeric_std.ALL; ENTITY tb_berlinuhr IS Generic ( fosc : integer := 5000; -- debug tosc : time := 200 us); -- debug END tb_berlinuhr; ARCHITECTURE behavior OF tb_berlinuhr IS COMPONENT berlinuhr GENERIC ( fosc : integer := fosc); PORT( clk, setslow,setfast : IN std_logic; sec : BUFFER std_logic; min : BUFFER std_logic_vector(3 downto 0); min5 : BUFFER std_logic_vector(10 downto 0); hr : BUFFER std_logic_vector(3 downto 0); hr5 : BUFFER std_logic_vector(3 downto 0) ); END COMPONENT; signal clk : std_logic := '0'; signal setslow : std_logic := '0'; signal setfast : std_logic := '0'; signal sec : std_logic; signal min : std_logic_vector(3 downto 0); signal min5 : std_logic_vector(10 downto 0); signal hr : std_logic_vector(3 downto 0); signal hr5 : std_logic_vector(3 downto 0); BEGIN uut: berlinuhr PORT MAP ( clk => clk, setslow => setslow, setfast => setfast, sec => sec, min => min, min5 => min5, hr => hr, hr5 => hr5 ); clk <= not clk after tosc/2; set_test: process begin wait for 1000000 ms; setfast <= '1'; wait for 100000 ms; setfast <= '0'; wait for 1000000 ms; setslow <= '1'; wait for 100000 ms; setslow <= '0'; wait; end process; END;
In der Testbench wird der Takt auf 5 kHz reduziert, damit die Simulation nicht so viele Taktflanken zu bearbeiten hat und deshalb langsam wird. Weil diese Taktzykluszeit über ein Genric an die Uhr gegeben wird, kann die ihre Zähler und Skalierungen entsprechend anpassen und so wird in der Testbench trotzdem alles in realen Zeiten angezeigt.
Auf dem Mikrocontroller.net wird das Thema im Thread Berlin Uhr aus ICs bauen etwas ausgiebiger diskutiert.
Dort ist der Code für einen 32,768 kHz Takt ausgelegt und braucht deswegen natürlich wesentlich weniger Flipflops. Natürlich könne man auch im obigen Code eine Taktfrequenz von 32 kHz angeben, dann kommen gleich viele Flipflops heraus, allerdings wird trotzdem mehr Logik für die Rücksetzbedingungen benötigt, als wie auf der expliziten Lösung auf µC.net nur 1 Bit abzufragen.