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

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 &amp; pSPI_SGL_DIFF &amp; rSPI_ODD_SIGN(0) &amp; 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) &amp; '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) &amp; '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) &amp; '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) &amp; '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) &amp; 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) &amp; 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) &amp; 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) &amp; 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) &amp; 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) &amp; 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) &amp; 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) &amp; 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を観察すると状態遷移図のように動作しているのかがすぐにわかります。

 本ソースコートはステートマシンの説明用に作成したもので、実機での動作確認を行っていません。実機で使用しないようにお願いします。