VHDLのステートマシンを細かく説明

「ステートマシンの構想」でステートマシンの概略を解説しましたので、ソースコードの説明をします。今回はVHDLで解説しますが、内容はVerilog編と同じです。
まず、状態遷移図を次に示します。

大きなステートマシンですが、動作は単純です。各ステートでシリアルクロックを生成しているので、ステートが多くなっています。シリアルのクロックをHi,Loで2ステート使っていますので、慣れているエンジニアであれば、ループを作ってもっとスマートに設計することができるでしょう。
今回は解説のため。という事もありますが、ステートマシンはロジックが複雑になる傾向にありますので、メンテナンス性を考えても単純に作る方が良いと考えています。
-- TITLE: "MCP3002drv.vhd" -- MODULE NAME: -- PROJECT CODE: -- AUTHOR: (****@nakaharagiken.com) -- CREATION DATE: 2020.7.20 -- SOURCE: -- LICENSE: Copyright (c) 2020 nakaharagiken All rights reserved. -- DESCRIPTION: -- NOTES TO USER: MicroChip MCP3002用ドライバ -- SOURCE CONTROL: -- REVISION HISTORY: v0.1 2020.7.25 First edition -- -- -- -- 入力クロック 10MHz = 100nsec -- MCP3002はシングルエンドモード 2chを連続してリードする -- library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; use IEEE.std_logic_arith.all; entity MCP3002drv is port ( clock: in std_logic; -- system clock nreset: in std_logic; -- asynchronous reset ____reset___|~~~normal~~~ enable: in std_logic; -- イネーブル _________|~|_____________ SPI_ncs: out std_logic; -- SPIチップセレクト信号:プルアップ ~~~|_________________|~~~ SPI_clk: out std_logic; -- SPIクロック信号 SPI_MOSI: out std_logic; -- SPI MOSI SPI_MISO: in std_logic; -- SPI MISO :プルダウン ch0_dat: out std_logic_vector(9 downto 0); -- ADCデータチャンネル0 ch1_dat: out std_logic_vector(9 downto 0); -- ADCデータチャンネル1 dataset: out std_logic; -- ADCデータチャンネル0と1のデータ確定 _____|~|_________________ busy: out std_logic -- 本ブロック動作中 _________|~~~busy~~~~~~~~ ); end entity MCP3002drv; architecture rtl of MCP3002drv is constant pWAIT : std_logic_vector(3 downto 0) := "0101"; -- 10MHzをウェイトしてSPIクロックの周期に合わせる constant pSTART : std_logic := '1'; -- SPI送信データ startビット constant pSPI_SGL_DIFF : std_logic := '1'; -- SPI送信データ 2ビット目 Single-Ended Mode constant pMSBF : std_logic := '1'; -- SPI送信データ MSBFビット type SSTATE_T is (S00,S01,S02,S03,S04,S05,S06,S07,S08,S09,S10,S11,S12, S13,S14,S15,S16,S17,S18,S19,S20,S21,S22,S23,S24,S25, S26,S27,S28,S29,S30,S31,S32,S33,S34,S35); --------------------------------------------------------------- -- SIGNALS --------------------------------------------------------------- signal rDFF: std_logic_vector(7 downto 0); -- 8bitシフトレジスタ signal rSPI_ncs: std_logic := '1'; -- SPI nCS signal rSPI_clk: std_logic := '0'; -- SPI clock signal rbusy: std_logic; -- busyフラグ signal rwait: std_logic_vector(3 downto 0); -- SPI Clock 生成用ウェイトカウンタ signal rSendDat: std_logic_vector(3 downto 0); -- 送信データ signal rRecDat: std_logic_vector(9 downto 0); -- 受信データ signal rRecDatA: std_logic_vector(9 downto 0); -- 受信データ signal rRecDatB: std_logic_vector(9 downto 0); -- 受信データ signal rSPI_ODD_SIGN: std_logic_vector(1 downto 0); -- ADCのチャンネルを数えるカウンタ signal rCountClock: std_logic_vector(3 downto 0); -- SPIクロックの数を数えるカウンタ signal rdatset: std_logic; -- rRecDatA,rRecDatBのデータセットOK ______|~|______ signal rstate: SSTATE_T; -- ステートマシン begin ------------------------------------------ -- ステートマシン ------------------------------------------ process (clock,nreset) begin if nreset = '0' then -- 初期化 rSPI_ncs <= '1'; -- SPI nCS rSPI_clk <= '0'; -- SPI clock rbusy <= '1'; -- busyフラグ rwait <= (others => '0'); -- SPI Clock 生成用ウェイトカウンタ rCountClock <= (others => '0'); rSPI_ODD_SIGN <= (others => '0'); rSendDat <= (others => '0'); rdatset <= '0'; rRecDat <= (others => '0'); rRecDatA <= (others => '0'); rRecDatB <= (others => '0'); elsif clock' event and clock = '1' then -- clock 立ち上がり指示 case rstate is when S00 => rSPI_ncs <= '1'; -- SPI nCS rSPI_clk <= '0'; -- SPI clock rbusy <= '0'; -- busyフラグ rwait <= (others => '0'); -- SPI Clock 生成用ウェイトカウンタ rSPI_ODD_SIGN <= (others => '0'); rCountClock <= (others => '0'); rdatset <= '0'; rSendDat <= (others => '0'); rRecDat <= (others => '0'); if (enable = '1') then -- START rstate <= S01; else rstate <= S00; end if; when S01 => if (rSPI_ODD_SIGN(1) = '1') then -- 2ch読んだら終わり rstate <= S00; else rstate <= S02; end if; rbusy <= '1'; -- busyフラグ rdatset <= '0'; when S02 => rSPI_ncs <= '0'; -- tSUCS = 100nsec rSendDat <= (pSTART & pSPI_SGL_DIFF & rSPI_ODD_SIGN(0) & pMSBF); -- 送信データ組み立て rwait <= (others => '0'); -- SPI Clock 生成用ウェイトカウンタ rstate <= S03; when S03 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 1bit目送信 rwait <= rwait + 1; rstate <= S03; else rwait <= (others => '0'); rstate <= S04; end if; when S04 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S04; else rwait <= (others => '0'); rSendDat(3 downto 0) <= (rSendDat(2 downto 0) & '0'); -- シフトレジスタ rstate <= S05; end if; when S05 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 2bit目送信 rwait <= rwait + 1; rstate <= S05; else rwait <= (others => '0'); rstate <= S06; end if; when S06 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S06; else rwait <= (others => '0'); rSendDat(3 downto 0) <= (rSendDat(2 downto 0) & '0'); -- シフトレジスタ rstate <= S07; end if; when S07 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 3bit目送信 rwait <= rwait + 1; rstate <= S07; else rwait <= (others => '0'); rstate <= S08; end if; when S08 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S08; else rwait <= (others => '0'); rSendDat(3 downto 0) <= (rSendDat(2 downto 0) & '0'); -- シフトレジスタ rstate <= S09; end if; when S09 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 4bit目送信 rwait <= rwait + 1; rstate <= S09; else rwait <= (others => '0'); rstate <= S10; end if; when S10 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S10; else rwait <= (others => '0'); rSendDat(3 downto 0) <= (rSendDat(2 downto 0) & '0'); -- シフトレジスタ rstate <= S11; end if; when S11 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 1bit目受信 NULL rwait <= rwait + 1; rstate <= S11; else rwait <= (others => '0'); rRecDat <= (others => '0'); rstate <= S12; end if; when S12 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S12; else rwait <= (others => '0'); rstate <= S13; end if; when S13 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 2bit目受信 rwait <= rwait + 1; rstate <= S13; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S14; end if; when S14 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S14; else rwait <= (others => '0'); rstate <= S15; end if; when S15 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 3bit目受信 rwait <= rwait + 1; rstate <= S15; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S16; end if; when S16 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S16; else rwait <= (others => '0'); rstate <= S17; end if; when S17 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 4bit目受信 rwait <= rwait + 1; rstate <= S17; else rwait <=(others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S18; end if; when S18 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S18; else rwait <= (others => '0'); rstate <= S19; end if; when S19 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 5bit目受信 rwait <= rwait + 1; rstate <= S19; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S20; end if; when S20 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S20; else rwait <= (others => '0'); rstate <= S21; end if; when S21 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 6bit目受信 rwait <= rwait + 1; rstate <= S21; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S22; end if; when S22 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S22; else rwait <= (others => '0'); rstate <= S23; end if; when S23 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 7bit目受信 rwait <= rwait + 1; rstate <= S23; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S24; end if; when S24 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S24; else rwait <= (others => '0'); rstate <= S25; end if; when S25 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 8bit目受信 rwait <= rwait + 1; rstate <= S25; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0),SPI_MISO); rstate <= S26; end if; when S26 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S26; else rwait <= (others => '0'); rstate <= S27; end if; when S27 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 9bit目受信 rwait <= rwait + 1; rstate <= S27; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S28; end if; when S28 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S28; else rwait <= (others => '0'); rstate <= S29; end if; when S29 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 10bit目受信 rwait <= rwait + 1; rstate <= S29; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0) & SPI_MISO); rstate <= S30; end if; when S30 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S30; else rwait <= (others => '0'); rstate <= S31; end if; when S31 => rSPI_clk <= '0'; if (rwait < pWAIT) then -- クロックウェイト 11bit目受信 rwait <= rwait + 1; rstate <= S31; else rwait <= (others => '0'); rRecDat <= (rRecDat(8 downto 0),SPI_MISO); rstate <= S32; end if; when S32 => rSPI_clk <= '1'; if (rwait < pWAIT) then -- クロックウェイト rwait <= rwait + 1; rstate <= S32; else rwait <= (others => '0'); rstate <= S33; end if; when S33 => rSPI_clk <= '0'; rSPI_ncs <= '0'; if (rSPI_ODD_SIGN(0) = '0') then -- 受信データコピー rRecDatA <= rRecDat; else rRecDatB <= rRecDat; end if; rstate <= S34; when S34 => rSPI_ncs <= '1'; -- nCS = Hi if (rwait < pWAIT) then -- tCSH時間待機 rwait <= rwait + 1; rstate <= S34; else rwait <= (others => '0'); rstate <= S35; end if; when S35 => if (rSPI_ODD_SIGN(0) = '1') then -- 2ch読んだら終わり rdatset <= '1'; end if; rSPI_ODD_SIGN <= rSPI_ODD_SIGN + 1; rstate <= S01; when others => rstate <= S00; end case; end if; end process; SPI_clk <= rSPI_clk; -- SPIクロック信号 SPI_ncs <= rSPI_ncs; -- SPIチップセレクト信号:プルアップ ~~~|_________________|~~~ SPI_MOSI <= rSendDat(3); -- SPI MOSI ch0_dat <= rRecDatA; -- out [9:0] ADCデータチャンネル0 ch1_dat <= rRecDatB; -- out [9:0] ADCデータチャンネル1 dataset <= rdatset; -- ADCデータチャンネル0と1のデータ確定 _____|~|_________________ busy <= rbusy; -- 本ブロック動作中 _________|~~~busy~~~~~~~~ end architecture rtl;
ステートマシンはカウンタとロジックに分けることができます。それゆえ、カウンタ部とロジック部で分けて設計する方法もあります。私はロジックの遷移が分かりやすいので、同時に記載する方法をとっています。
66行目のsignal rstate: SSTATE_T; がステートマシンで使うカウンタレジスタの宣言です。SSTATE_TはTYPE宣言といって、ユーザーが作ることができる型です。ステートマシンの各ステートに名前を付けるのに使用しました。46行目でTYPE宣言しています。ここでは、カッコの中の順番が重要です。今回S00からS35までの名前を付けています。ただ順番に記述しているだけなので、あまり意味は無いかも知れません。
73行目からがステートマシンの本体ですが、87行目からのcase分の中で各ステート毎の動作を記述しています。
ステートマシンの場合、初期値を入れるのを忘れない事です。74行目でリセット信号で初期値が入るようにしていますが、rstateにスタートするステートを入れておくことが大切です。
rwaitはSPI_clkのHiとLowの時間をカウントしているレジスタです。pWAITの定数でカウント数を設定しています。
rSPI_ODD_SIGNは2ビットのカウンタになっていて、0ビット目でADCデータチャンネルの0と1を判別しています。このステートマシンは2周で1シーケンスになるようになっています。1周目でADC0のシリアルデータを受信し、2週目でADC1のデータを受信します。S01で3週目を検出して、3週目であれば、S00に戻ります。この1~3週目を数えているのがrSPI_ODD_SIGNレジスタです。
S00ではenable信号によって、スタート、ストップを検出しています。S00のステートでしか、enable信号を見ていませんので、一旦スタートしてしまえば、2周するまで止まることはありません。
シリアルの送信データはrSendDatの4ビット目になっていて、396行目のSPI_MOSI <= rSendDat(3); でMOSI信号に接続しています。rSendDatは4ビットのシフトレジスタになっていて、110行目のrSendDat <= (pSTART & pSPI_SGL_DIFF & rSPI_ODD_SIGN(0) & pMSBF);でデータを組み立てています。VHDLの場合、データを連結するにはこのように&で区切って記述します。
127行目がシフトレジスタの記述でrSendDat(3 downto 0) <= (rSendDat(2 downto 0) & ‘0’);のように右辺は & を使ってデータが1ビット左にズレるように記述しています。全部シフトが終わると全ビット0になります。
受信も同じようにシフトレジスタを使っています。204行目のrRecDat <= (rRecDat(8 downto 0) & SPI_MISO);です。右辺が同じように&を使って1ビット左にズレるようになっています。MSB(最下位ビット)にはSPI_MISOが入るようになっていますので、徐々に受信データは左にズレていきます。最後は全部が受信データで埋まります。
全部データを受信終えるとS33で1週目か2週目かを判断して、1週目はrRecDatAに2週目はrRecDatBにコピーします。
デバッグする時にはrstateを観察すると状態遷移図のように動作しているのかがすぐにわかります。
本ソースコートはステートマシンの説明用に作成したもので、実機での動作確認を行っていません。実機で使用しないようにお願いします。
-
前の記事
Verilogステートマシンのテストベンチを細かく解説 2020.07.25
-
次の記事
VHDLステートマシンのテストベンチを細かく解説 2020.07.26

コメントを書く