これは、このような状態遷移図によって動作を表現できる回路で、
いくつかの内部状態(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;