これは、このような状態遷移図によって動作を表現できる回路で、
いくつかの内部状態(state)をもち、クロックにあわせて、
入力によって、次の状態が決まる、というものです。
この例では、IDLE, SET, RUN, DONEという4つの状態を持ち、
例えば、現在の状態がIDLEのときは、setという入力が0のときは
次の状態もIDLE、set=1ならば次の状態はSETとなる、という動作を
あらわしています。
もちろんカウンタなども、このステートマシンとして
考えることができます。
(内部状態=現在のカウント値、と考えればよい)
このようなステートマシンは、一般にはこのような構成の回路によって
実現することができます。
現状態stateを保持しているレジスタ(実体は、ほとんどの場合はD-FF)、
現状態と入力から次の遷移先の状態n_stateを決める次状態決定回路、
そして現状態と入力から、全体としての出力を決める
出力信号決定部、からなります。
一般には、現状態と次状態、入力・出力の対応を状態遷移表にまとめ、 それを真理値表とみなして次状態決定回路、出力信号決定部の 論理回路を設計することになります。
これから、上記のようなデータパスを設計することができます。 ここでREGは、時間カウント値を保持しておく12ビットのレジスタ(変数)で、 その実体はクロックclkに同期した12ビットのD-FFと考えてください。 このレジスタの出力はcntという名前の出力になっています。 またADDは加算回路で、ADDの下側の入力は、s10などの信号に応じて 10, 60, 0のいずれかが与えられます。
またカウントダウン終了を検出するために、 時間カウント値cntが0となったときに、カウントダウン終了を 示す出力done=1とするために、cntと0とをコンパレータCMPによって 比較しています。
entity datapath is port( clk, rst, p10, p60, m1: in std_logic; done: out std_logic; cnt: out std_logic_vector(11 downto 0)); end datapath architecture arch of datapath is signal reg12_out, reg12_in: std_logic_vetor(11 downto 0); signal sel_out: std_logic_vector(6 downto 0); function sign_extend (a: std_logic_vector(6 downto 0)) return std_logic_vector is begin if (a(6) = '1') then return("11111" & a); else return("00000" & a); end if end sign_extend begin process (p10, p60, m1) begin -- selector if (p10 = '1') then sel_out = "0001010"; -- "10" elsif (p60 = '1') then sel_out = "0111100"; -- "60" elsif (m1 = '1') then sel_out = "1111111"; -- "-1" else sel_out = "0000000"; end if; end process; process (reg12_out, sel_out) begin -- adder reg12_in <= reg12_out + sign_extend(sel_out); end process; process (clk, rst) begin -- register if (rst = '0') then reg12_out = "000000000000"; elsif (clk'event and clk = '1') then re12_out <= reg_in; end if; end process; process (reg12_out) begin -- comparator if (reg12_out = "000000000000") then done <='1'; else done <= '0'; end if end process; cnt <= reg12_out; end arch;
この制御部のVHDL記述は次のようになります。(p.103: 要点のみ抜粋) 前半のcase文が、まさに状態遷移図に対応している 次状態決定回路になっていて、 また後半のcase文が、出力決定部になっていることに注意しましょう。 またステートマシンの本質である状態遷移は、2つ目のprocess文で 記述されていることにも注意しておきましょう。
entity control is port( clk, rst, set, run, s10, s60, done: in std_logic; p10, p60, m1: out std_logic); end control; architecture arch of control is type t_state is (IDLE_ST, SET_ST, RUN_ST, DONE_ST); signal state, next_state: t_state; begin process (state, set, run, done) begin -- next state calc case state is when IDLE_ST => if (set = '1') then next_state <= SET_ST; else next_state <= IDLE_ST; end if; when SET_ST => if (run = '1') then next_state <= RUN_ST; else next_state <= SET_ST; end if; when RUN_ST => if (done = '1') then next_state <= DONE_ST; else next_state <= RUN_ST; end if; when DONE_ST => next_state <= IDLE_ST; end case; end process; process (clk, rst) begin -- state register if (rst = '0') then state <= IDLE_ST; elsif (clk'event and clk = '1') then state <= next_state; end if; end process; process (state, s10, s60, done) begin -- control signals p10 <= '0'; p60 <= '0'; m1 <= '0'; case state is when IDLE_ST => null; when SET_ST => if (s10 = '1') then p10 <= '1'; elsif (s60 = '1') then p60 <= '1'; end if; when RUN_ST => if (done /= '1') then m1 <= '1'; -- /= : Not Equal ※←教科書の記述が間違っているようです end if; when DONE_ST => null; end case; end process; end arch;
entity timer is port( clk, rst, set, run, s10, s60: in std_logic; done: out std_logic; cnt: std_logic_vector(11 downto 0)); end timer; architecture arch of timer is component control port( clk, rst, set, run, s10, s60, done: in std_logic; p10, p60, m1: out std_logic); end component; component datapath port( clk, rst, p10, p60, m1: in std_logic; done: out std_logic; cnt: out std_logic_vector(11 downto 0)); end component; signal p10, p60, m1, done_tmp: std_logic; begin i0: control port map( clk, rst, set, run, s10, s60, done_tmp, p10, p60, m1); i1: datapath port map( clk, rst, p10, p60, m1, done_tmp, cnt); done <= done_tmp; end arch;
ただし、一般に、このような構成を意識しない記述から 論理合成によって得られる回路は、構成を意識した記述から 得られる回路よりも、「質」が低くなる傾向があります。 すなわち回路規模が大きく、消費電力が大きくなる傾向があります。 そのため、なるべくデータパスと制御部を分けて記述するほうが、 とりあえず動く回路、では不十分な場合で、 より質の高い論理回路を得たい場合は、得策となるようです。
architecture arch2 of timer is type t_state is (IDLE_ST, SET_ST, RUN_ST, DONE_ST); signal state: t_state; signal cnt_tmp: std_logic_vector(11 downto 0); signal sel_out: std_logic_vector(6 downto 0); begin process (clk, rst) begin if (rst = '0') then cnt_tmp <= "000000000000"; done <= '0'; state <= IDLE_ST; elsif (clk'event and clk = '1') then case state is when IDLE_ST => if (set = '1') then state <= SET_ST; else state <= IDLE_ST; end if; when SET_ST => if (s10 = '1') then cnt_tmp <= cnt_tmp + "000000001010"; elsif (s60 = '1') then cnt_tmp <= cnt_tmp + "000000111100"; end if; if (run = '1') then state <= RUN_ST; else state <= SET_ST; end if; when RUN_ST => cnt_tmp <= cnt_tmp - "000000000001"; if (cnt_tmp = "000000000001") then done <= '1'; state <= DONE_ST; else state <= RUN_ST; end if; when DONE_ST => done = '0'; state <= IDLE_ST; end case; end if; end process; cnt <= cnt_tmp; end arch2;