Dieser letzte Block besteht aus drei Teilen: zunächst aus der eigentlichen Ablaufsteuerung, die alle Steuersignale erzeugt, dann aus der Zeitbasis, die ausgehend vom 5 kHz Referenztakt die Torzeiten 0,01, 0,1, 1 und 10 Sekunden erzeugt, und aus einer kleine Handshake-Einheit, die das Vorhandensein eines neuen Zählergebnisses signalisiert. Zunächst aber das Coding der Ablaufsteuerung, die als State-Machine realisiert ist.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity controller is Port(clk : in std_logic; reset : in std_logic; counting : in std_logic; shift_load : out std_logic; cnt_reset : out std_logic; tb_reset : out std_logic); end controller; architecture Behavioral of controller is type t_state is (s0,s1,s2,s3,s4,s5,s6,s7); signal state, next_state: t_state; begin -- state shift_load cnt_reset tb_reset -- s0: running 0 0 0 -- s1: pause1 0 0 0 -- s2: load sr 1 0 0 -- s3: pause2 0 0 0 -- s4: cnt_reset 0 1 0 -- s5: pause3 0 0 0 -- s6: tb_reset 0 0 1 -- s7: wait 0 0 0 p1:process(clk, reset, next_state) begin if reset = '1' then state <= s3; elsif clk = '1' and clk'event then state <= next_state; end if; end process p1; p2:process(state, counting) begin case state is when s0 => if counting = '0' then next_state <= s1; else next_state <= s0; end if; when s1 => next_state <= s2; when s2 => next_state <= s3; when s3 => next_state <= s4; when s4 => next_state <= s5; when s5 => next_state <= s6; when s6 => if counting = '0' then next_state <= s7; else next_state <= s6; end if; when s7 => if counting = '1' then next_state <= s0; else next_state <= s7; end if; end case; end process p2; p3:process(state) begin case state is when s2 => shift_load <= '1'; cnt_reset <= '0'; tb_reset <= '0'; when s4 => shift_load <= '0'; cnt_reset <= '1'; tb_reset <= '0'; when s6 => shift_load <= '0'; cnt_reset <= '0'; tb_reset <= '1'; when others => shift_load <= '0'; cnt_reset <= '0'; tb_reset <= '0'; end case; end process p3; end Behavioral;
Der Prozess p1 überführt den zuvor in next_state ermeittelten neuen Zustand in das signal state. Außerdem setzt er den Anfangszustand bei einem Reset. Der Prozeß p2 ist die eigentliche State-Machine. Im einfachsten Fall wird nur der Zustand um 'ein' weiter geschaltet; in anderen Fällen hängt der Fortschritt davon ab, ob etwa der Zäher noch läuft oder nicht (etwa s0 nach s1 nur dann, wenn der Zähler fertig ist; andernfalls in s0 bleiben). Der Prozeß p3 übersetzt die internen Zustände s0 bis s7 in Zustände auf den leitungen shift_load (Latch/Schieberegister mit dem aktuellen Zählerstand laden), cnt_reset (Zähler auf Null setzen) und tb_reset (Zeitbasis zurücksetzen). Um Überlappungen bei den drei Steuersignalen auszuschließen, wurden jeweils Zwischenzustände eingeführt, in denen alle drei Signale auf Null sind. Siehe auch der Kommentar vor dem Prozeß p1.
Die zeitbasis stellt die vier Torzeiten zur Verfügung, die über den Eingang sel_tb (2 bit breit) ausgewählt werden können. Der einzige vorhandene Prozeß ist p1, der eine Mischung aus State-Machine und Zähler ist. Die Zustände sind prep (prepare, vorbereiten), running (Zeitbasis läuft; das Zähltor ist 'offen') und ready (das Zähltor ist 'geschlossen' und die Zählung im Hauptzähler ist beendet).
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity timebase is Port (sel_tb : in bit_vector(1 downto 0); clk : in std_logic; start : in std_logic; counting : out std_logic); end timebase; architecture Simple of timebase is type t_state is (prep,running,ready); signal x : std_logic_vector(23 downto 0); signal state : t_state; begin p1: process(clk,x,sel_tb,state,start) begin if start = '1' then state <= prep; elsif clk = '1' and clk'event then case state is when prep => case sel_tb is when "00" => x <= conv_std_logic_vector(100,24); -- 10 ms when "01" => x <= conv_std_logic_vector(1000,24); -- 100 ms when "10" => x <= conv_std_logic_vector(10000,24); -- 1 s when "11" => x <= conv_std_logic_vector(100000,24); -- 10 s end case; if start = '1' then state <= prep; else state <= running; end if; when running => x <= x-1; if x = x"000001" then state <= ready; else state <= running; end if; when ready => state <= ready; end case; end if; end process p1; counting <= '1' when state = running else '0'; end Simple;
In der prep-Phase wird der Anfangswert des Zählers auf denjenigen Wert gesetzt der der ausgewählten Torzeit entspricht. Dieser Wert wird in der running-Phases mit jedem clk-Impuls um eins heruntergezählt. Bei erreichen von 0 wechselt der Zustand auf ready. Der nebenläufige Prozeß am Ende signalisiert den Zustand running als den Wert 1 am Ausgang counting.
Das letzte Stück Coding dient dazu, dem angeschlossenen Microcontroller jeweils die Bereitstellung neuer Daten zu signalisieren. Es ist eine Art Handshake: Sind neue Daten verfügbar, was über data_ready_i angezeigt wird, dann geht data_ready_o auf 0. Der angeschlossene Controller setzt, nachdem er die Daten ausgelesen hat, ack auf 0, was dazu führt, daß data_ready_o wieder auf 1 geht. Liegt erneut ein Zählergebnis vor, dann geht data_ready_o erneut auf 0 und das Spiel wiederholt sich. Letzten Endes ein wechselseitig geschaltetes Flip-Flop.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity handshake is Port (ack : in std_logic; data_ready_i : in std_logic; data_ready_o : out std_logic); end handshake; architecture Behavioral of handshake is begin p1:process(ack, data_ready_i) begin if ack = '0' then data_ready_o <= '1'; elsif data_ready_i'event and data_ready_i = '0' then data_ready_o <= '0'; end if; end process p1; end Behavioral;
Die drei entities werden über das letzte Stück Coding, das eine Struktur definiert, zusammengeführt. Jeder der drei entities kommt genau einmal vor und sie werden intern über Signale zusammengeschaltet bzw. direkt mit den Ein- und Ausgängen verbunden. Einige interne Signale werden zusätzlich noch über einen nebenläufigen Prozeß an entsprechende Ausgänge übergeben.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity timebase_and_controller is Port (reset : in std_logic; sel_tb : in bit_vector(1 downto 0); clk_ref : in std_logic; data_ack : in std_logic; shift_load : out std_logic; cnt_reset : out std_logic; counting : out std_logic; data_ready : out std_logic); end timebase_and_controller; architecture Structure of timebase_and_controller is signal tb_start : std_logic; signal counting_int : std_logic; signal cnt_reset_int : std_logic; component timebase Port (sel_tb : in bit_vector(1 downto 0); clk : in std_logic; start : in std_logic; counting : out std_logic); end component; component controller Port (clk : in std_logic; reset : in std_logic; counting : in std_logic; shift_load : out std_logic; cnt_reset : out std_logic; tb_reset : out std_logic); end component; component handshake Port (ack : in std_logic; data_ready_i : in std_logic; data_ready_o : out std_logic); end component; for TB: timebase use entity work.timebase(simple);--Behavioral); for CT: controller use entity work.controller(Behavioral); for HS: handshake use entity work.handshake(Behavioral); begin TB: timebase port map( sel_tb => sel_tb, clk => clk_ref, start => tb_start, counting => counting_int); CT:controller Port map( clk => clk_ref, reset => reset, counting => counting_int, shift_load => shift_load, cnt_reset => cnt_reset_int, tb_reset => tb_start); HS:handshake Port map( ack => data_ack, data_ready_i => cnt_reset_int, data_ready_o => data_ready); counting <= counting_int; cnt_reset <= cnt_reset_int; end Structure;
Zum Abschluß auch hier die Constraints, die die Ein- und Ausgänge mit den Pins des XR9572XL verbindet.
NET "clk_ref" LOC = "P43" | BUFG = CLK ; NET "cnt_reset" LOC = "P32" ; NET "counting" LOC = "P5" ; NET "data_ack" LOC = "P2" ; NET "data_ready" LOC = "P3" ; NET "reset" LOC = "P33" | BUFG = SR ; NET "sel_tb<0>" LOC = "P6" ; NET "sel_tb<1>" LOC = "P7" ; NET "shift_load" LOC = "P28" ;
Natürlich ist es sinnvoll, gerade die Ablaufsteuerung zusammen mit der Zeitbasis und dem Handshake-Flipflop zu simulieren. Dazu wurde zunächst das folgende VHDL-Testfile erzeugt:
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.std_logic_unsigned.all; USE ieee.numeric_std.ALL; ENTITY timebase_and_controller_test IS END timebase_and_controller_test; ARCHITECTURE behavior OF timebase_and_controller_test IS COMPONENT timebase_and_controller PORT(reset : IN std_logic; sel_tb : IN bit_vector(1 downto 0); clk_ref : IN std_logic; data_ack : IN std_logic; shift_load : OUT std_logic; cnt_reset : OUT std_logic; counting : OUT std_logic; data_ready : OUT std_logic); END COMPONENT; signal reset : std_logic := '0'; signal sel_tb : bit_vector(1 downto 0) := (others => '0'); signal clk_ref : std_logic := '0'; signal data_ack : std_logic := '1'; signal shift_load : std_logic; signal cnt_reset : std_logic; signal counting : std_logic; signal data_ready : std_logic; -- Clock period definitions constant clk_ref_period : time := 100 us; BEGIN -- Instantiate the Unit Under Test (UUT) uut: timebase_and_controller PORT MAP ( reset => reset, sel_tb => sel_tb, clk_ref => clk_ref, data_ack => data_ack, shift_load => shift_load, cnt_reset => cnt_reset, counting => counting, data_ready => data_ready); -- Test Workbench: Clock process definition clk_ref_process :process begin wait for 1500 us; -- just wait for some time while true loop -- forever ... clk_ref <= '0'; -- low wait for clk_ref_period/2; -- for half a clock cycle and ... clk_ref <= '1'; -- high wait for clk_ref_period/2; -- for the other half of the clock cycle end loop; end process; -- Test Workbench: Reset process definitions reset_process :process begin reset <= '0'; -- no reset wait for 1000 us; -- 1 ms later reset <= '1'; -- no we have a reset for wait for 1000 us; -- ... another ms reset <= '0'; -- no reset or running mode wait; -- wait forever end process; -- Test Workbench: Simulates the microcontroller reading data handshake_process: process begin wait for 5 ms; -- wait for 5 ms. Maybe setting up the LC-display etc. while true loop -- forever ... if data_ready = '0' then -- if (new) data are available ... wait for 1 ms; -- again ... wait a little bit and pretend reading data data_ack <= '0'; -- now confirm reception of data and set data_ack to 0 wait for 100 us; -- make the ack-pulse 100 us long (active low) data_ack <= '1'; -- go back to passive state else wait for 1 ms; -- no new data available: pretend doing something else end if; end loop; end process; END;
Das Testfile simuliert den Takt mit genau 10 kHz, das Reset-Signal und den Handshakeprozeß im Rahmen der Bereitstellung der Daten zum auslesen durch die CPU. Das Ergebnis der Simulation zeigt das folgende Bild:
Die Simulation zeigt verschiedene Dinge. Die oberste Spur stellt das Signal zur Auswahl der Zeitbasis dar. Der Wert ist '00' und entspricht daher einer Torzeit von 10 ms. Des weiteren sind zwei Cursor eingezeichnet, die sich auf die vierte Spur, counting, beziehen und die genau 10.000.000.000 ps Abstand haben, was nichts anderes ist als die eingestellte Torzeit von 10 ms. Diesen Teil macht die Ablaufsteuerung schon mal ganz gut. Die zweite Spur zeigt wenig erkennbares, weil sie für den Reset steht, der hier im Bild jetzt nicht dargestellt ist. Die dritte Spur ist der 10 kHz Referenztakt. Zwischen den beiden Cursorn müßten also genau 100 Takte liegen. Betrachten wir jetzt die Region vor dem ersten Cursor. Mit der fallenden Flanke des Signals counting wurde die vorhergehende Messung beendet. Der Zähler ist verriegelt und zählt nicht mehr. Kurz danach wird, wie in der fünften Spur gezeigt, der shift_load-Impuls erzeugt, der den Zählerstand in das Latch/Schieberegister übernimmt. Nach einer klerinen Pause wird, wie die sechste Spur anzeigt, das Reset-Signal für den Zähler erzeugt, der damit auf Null gesetzt wird. Außerdem wird das data_ready-Signal (passive Logik!) auf Null gezogen um dem angeschlossenen Microcontroller zu signalisieren, daß eine neue Messung vorliegt. Der Controller liest nun die Daten aus dem Schieberegister aus (hier nicht simuliert) und signalisiert das Ende der Datenübernahme damit, daß er das data_ack-Signal (ebenfalls passive Logik) kurz auf Null zieht. Das setzt wiederum data_ready in den passiven Zustand, was einfach bedeutet: keine neuen Daten vorhanden.