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

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

 「ステートマシンの構想」でステートマシンの概略を解説しましたので、ソースコードの説明をします。

 まず、状態遷移図を次に示します。

 大きなステートマシンですが、動作は単純です。各ステートでシリアルクロックを生成しているので、ステートが多くなっています。シリアルのクロックをHi,Loで2ステート使っていますので、慣れているエンジニアであれば、ループを作ってもっとスマートに設計することができるでしょう。

 今回は解説のため。という事もありますが、ステートマシンはロジックが複雑になる傾向にありますので、メンテナンス性を考えても単純に作る方が良いと考えています。

//  TITLE:					"MCP3002drv.v"
//  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.20	First edition
//
//
//
//							入力クロック 10MHz = 100nsec
//							MCP3002はシングルエンドモード	 2chを連続してリードする
//
//
`timescale 1ns/1ps


module MCP3002drv (
	clock,					//	in			system clock 10MHz
	nreset,					//	in			asynchronous reset 					____reset___|~~~normal~~~
	enable,					//	in			シーケンスイネーブル				___disable__|~~enable~~~~
	SPI_ncs,				//	ouy			SPIチップセレクト信号:プルアップ	~~~|_________________|~~~
	SPI_clk,				//	out			SPIクロック信号
	SPI_MOSI,				//	out			SPI MOSI
	SPI_MISO,				//	in			SPI MISO :プルダウン
	ch0_dat,				//	out	[9:0]	ADCデータチャンネル0
	ch1_dat,				//	out	[9:0]	ADCデータチャンネル1
	dataset,				//	out			ADCデータチャンネル0と1のデータ確定	_____|~|_________________
	busy					//	out			本ブロック動作中					_________|~~~busy~~~~~~~~
);

input			clock;		//	in			system clock 10MHz
input			nreset;		//	in			asynchronous reset 					____reset___|~~~normal~~~
input			enable;		//	in			シーケンスイネーブル				___disable__|~~enable~~~~
output			SPI_ncs;	//	in			SPIチップセレクト信号:プルアップ	~~~|_________________|~~~
output			SPI_clk;	//	in			SPIクロック信号
output			SPI_MOSI;	//	out			SPI MOSI
input			SPI_MISO;	//	in			SPI MISO :プルダウン
output	[9:0]	ch0_dat;	//	out	[9:0]	ADCデータチャンネル0
output	[9:0]	ch1_dat;	//	out	[9:0]	ADCデータチャンネル1
output			dataset;	//	out			ADCデータチャンネル0と1のデータ確定	_____|~|_________________
output			busy;		//	out			本ブロック動作中					_________|~~~busy~~~~~~~~


///////////////////////////////////////////////////////////////
// SIGNALS
///////////////////////////////////////////////////////////////
reg				rSPI_ncs = 1'b1;	//	SPI nCS
reg				rSPI_clk = 1'b0;	//	SPI clock
reg				rbusy;				//	busyフラグ
reg		[3:0]	rwait;				//	SPI Clock 生成用ウェイトカウンタ
reg		[3:0]	rSendDat;			//	送信データ
reg		[9:0]	rRecDat;			//	受信データ
reg		[9:0]	rRecDatA;			//	受信データ
reg		[9:0]	rRecDatB;			//	受信データ
reg		[1:0]	rSPI_ODD_SIGN;		//	ADCのチャンネルを数えるカウンタ
reg		[3:0]	rCountClock;		//	SPIクロックの数を数えるカウンタ
reg				rdatset;			//	rRecDatA,rRecDatBのデータセットOK		______|~|______
reg		[7:0]	rstate;				// ステートマシン


parameter 	[3:0]	pWAIT = 4'd5;				// 10MHzをウェイトしてSPIクロックの周期に合わせる
parameter			pSTART = 1'b1;				// SPI送信データ startビット
parameter			pSPI_SGL_DIFF = 1'b1;		// SPI送信データ 2ビット目 Single-Ended Mode
parameter			pMSBF = 1'b1;				// SPI送信データ MSBFビット



parameter 	S00 = 8'd0,			// ステートマシン
			S01 = 8'd1,
			S02 = 8'd2,
			S03 = 8'd3,
			S04 = 8'd4,
			S05 = 8'd5,
			S06 = 8'd6,
			S07 = 8'd7,
			S08 = 8'd8,
			S09 = 8'd9,
			S10 = 8'd10,
			S11 = 8'd11,
			S12 = 8'd12,
			S13 = 8'd13,
			S14 = 8'd14,
			S15 = 8'd15,
			S16 = 8'd16,
			S17 = 8'd17,
			S18 = 8'd18,
			S19 = 8'd19,
			S20 = 8'd20,
			S21 = 8'd21,
			S22 = 8'd22,
			S23 = 8'd23,
			S24 = 8'd24,
			S25 = 8'd25,
			S26 = 8'd26,
			S27 = 8'd27,
			S28 = 8'd28,
			S29 = 8'd29,
			S30 = 8'd30,
			S31 = 8'd31,
			S32 = 8'd32,
			S33 = 8'd33,
			S34 = 8'd34,
			S35 = 8'd35;


/////////////////////////////////////////////////////
//  ステートマシン
/////////////////////////////////////////////////////
always @ (posedge  clock, negedge nreset) begin
	if (!nreset) begin								// 	初期化
		rSPI_ncs <= 1'b1;							//	SPI nCS
		rSPI_clk <= 1'b0;							//	SPI clock
		rbusy <= 1'b1;								//	busyフラグ
		rwait <= 0;									//	SPI Clock 生成用ウェイトカウンタ
		rCountClock <= 0;
		rSPI_ODD_SIGN <= 0;
		rSendDat <= 0;
		rdatset <= 0;
		rRecDat <= 0;
		rRecDatA <= 0;
		rRecDatB <= 0;
	end else begin
		case (rstate)
			S00	:	begin												// アイドル
						rSPI_ncs <= 1'b1;								//	SPI nCS
						rSPI_clk <= 1'b0;								//	SPI clock
						rbusy <= 1'b0;									//	busyフラグ
						rwait <= 0;										//	SPI Clock 生成用ウェイトカウンタ
						rSPI_ODD_SIGN <= 0;
						rCountClock <= 0;
						rdatset <= 1'b0;
						rSendDat <= 0;
						rRecDat <= 0;
						if (enable==1'b1) begin							// START
							rstate <= S01;
						end else begin
							rstate <= S00;
						end
					end
			S01	:	begin
						if (rSPI_ODD_SIGN[1] == 1'b1) begin				// 2ch読んだら終わり
							rstate <= S00;
						end else begin
							rstate <= S02;
						end
					rbusy <= 1'b1;									//	busyフラグ
					rdatset <= 1'b0;
					end
			S02	:	begin
						rSPI_ncs <= 1'b0;												// tSUCS = 100nsec
						rSendDat <= {pSTART,pSPI_SGL_DIFF,rSPI_ODD_SIGN[0],pMSBF};		// 送信データ組み立て
						rwait <= 0;														//	SPI Clock 生成用ウェイトカウンタ
						rstate <= S03;
					end
			S03	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		1bit目送信
							rwait <= rwait + 1;
							rstate <= S03;
						end else begin
							rwait <= 0;
							rstate <= S04;
						end
					end
			S04	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S04;
						end else begin
							rwait <= 0;
							rSendDat[3:0] <= {rSendDat[2:0],1'b0};							// シフトレジスタ
							rstate <= S05;
						end
					end
			S05	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		2bit目送信
							rwait <= rwait + 1;
							rstate <= S05;
						end else begin
							rwait <= 0;
							rstate <= S06;
						end
					end
			S06	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S06;
						end else begin
							rwait <= 0;
							rSendDat[3:0] <= {rSendDat[2:0],1'b0};							// シフトレジスタ
							rstate <= S07;
						end
					end
			S07	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		3bit目送信
							rwait <= rwait + 1;
							rstate <= S07;
						end else begin
							rwait <= 0;
							rstate <= S08;
						end
					end

			S08	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S08;
						end else begin
							rwait <= 0;
							rSendDat[3:0] <= {rSendDat[2:0],1'b0};							// シフトレジスタ
							rstate <= S09;
						end
					end
			S09	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		4bit目送信
							rwait <= rwait + 1;
							rstate <= S09;
						end else begin
							rwait <= 0;
							rstate <= S10;
						end
					end
			S10	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S10;
						end else begin
							rwait <= 0;
							rSendDat[3:0] <= {rSendDat[2:0],1'b0};							// シフトレジスタ
							rstate <= S11;
						end
					end
			S11	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		1bit目受信 NULL
							rwait <= rwait + 1;
							rstate <= S11;
						end else begin
							rwait <= 0;
							rRecDat <= 0;
							rstate <= S12;
						end
					end
			S12	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S12;
						end else begin
							rwait <= 0;
							rstate <= S13;
						end
					end
			S13	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		2bit目受信
							rwait <= rwait + 1;
							rstate <= S13;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S14;
						end
					end
			S14	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S14;
						end else begin
							rwait <= 0;
							rstate <= S15;
						end
					end
			S15	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		3bit目受信
							rwait <= rwait + 1;
							rstate <= S15;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S16;
						end
					end
			S16	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S16;
						end else begin
							rwait <= 0;
							rstate <= S17;
						end
					end
			S17	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		4bit目受信
							rwait <= rwait + 1;
							rstate <= S17;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S18;
						end
					end
			S18	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S18;
						end else begin
							rwait <= 0;
							rstate <= S19;
						end
					end
			S19	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		5bit目受信
							rwait <= rwait + 1;
							rstate <= S19;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S20;
						end
					end
			S20	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S20;
						end else begin
							rwait <= 0;
							rstate <= S21;
						end
					end
			S21	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		6bit目受信
							rwait <= rwait + 1;
							rstate <= S21;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S22;
						end
					end
			S22	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S22;
						end else begin
							rwait <= 0;
							rstate <= S23;
						end
					end
			S23	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		7bit目受信
							rwait <= rwait + 1;
							rstate <= S23;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S24;
						end
					end
			S24	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S24;
						end else begin
							rwait <= 0;
							rstate <= S25;
						end
					end
			S25	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		8bit目受信
							rwait <= rwait + 1;
							rstate <= S25;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S26;
						end
					end
			S26	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S26;
						end else begin
							rwait <= 0;
							rstate <= S27;
						end
					end
			S27	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		9bit目受信
							rwait <= rwait + 1;
							rstate <= S27;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S28;
						end
					end
			S28	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S28;
						end else begin
							rwait <= 0;
							rstate <= S29;
						end
					end
			S29	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		10bit目受信
							rwait <= rwait + 1;
							rstate <= S29;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S30;
						end
					end
			S30	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S30;
						end else begin
							rwait <= 0;
							rstate <= S31;
						end
					end

			S31	:	begin
						rSPI_clk <= 1'b0;
						if (rwait < pWAIT) begin		// 	クロックウェイト		11bit目受信
							rwait <= rwait + 1;
							rstate <= S31;
						end else begin
							rwait <= 0;
							rRecDat <= {rRecDat[8:0],SPI_MISO};
							rstate <= S32;
						end
					end
			S32	:	begin
						rSPI_clk <= 1'b1;
						if (rwait < pWAIT) begin		// 	クロックウェイト
							rwait <= rwait + 1;
							rstate <= S32;
						end else begin
							rwait <= 0;
							rstate <= S33;
						end
					end

			S33	:	begin											// 受信終わり
						rSPI_clk <= 1'b0;
						rSPI_ncs <= 1'b0;
						if (rSPI_ODD_SIGN[0] == 1'b0) begin			// 受信データコピー
							rRecDatA <= rRecDat;
						end else begin
							rRecDatB <= rRecDat;
						end
						rstate <= S34;
					end
			S34	:	begin
						rSPI_ncs <= 1'b1;							// nCS = Hi
						if (rwait < pWAIT) begin					// tCSH時間待機
							rwait <= rwait + 1;
							rstate <= S34;
						end else begin
							rwait <= 0;
							rstate <= S35;
						end
					end
			S35 :	begin
						if (rSPI_ODD_SIGN[0] == 1'b1) begin				// 2ch読んだら終わり
							rdatset <= 1'b1;
						end
						rSPI_ODD_SIGN <= rSPI_ODD_SIGN + 1;
						rstate <= S01;
					end
			default:begin
						rstate <= S00;
					end
		endcase
	end
end

assign	SPI_clk = rSPI_clk;				// 	out			SPIクロック信号
assign	SPI_ncs = rSPI_ncs;				//	ouy			SPIチップセレクト信号:プルアップ	~~~|_________________|~~~
assign	SPI_MOSI = rSendDat[3];			//	out			SPI MOSI
assign	ch0_dat = rRecDatA;				//	out	[9:0]	ADCデータチャンネル0
assign	ch1_dat = rRecDatB;				//	out	[9:0]	ADCデータチャンネル1
assign	dataset = rdatset;				//	out			ADCデータチャンネル0と1のデータ確定	_____|~|_________________
assign	busy = rbusy;					//	out			本ブロック動作中					_________|~~~busy~~~~~~~~

endmodule

 ステートマシンはカウンタとロジックに分けることができます。それゆえ、カウンタ部とロジック部で分けて設計する方法もあります。私はロジックの遷移が分かりやすいので、同時に記載する方法をとっています。

 63行目のreg [7:0] rstate;がステートマシンで使うカウンタレジスタの宣言です。全部で36ステートですから8ビットは必要ありませんので、削減してもOKです。

 73行目からはparameter 宣言でステートに名前を付けています。カウンタの値をそのまま使ってステートマシンを組んでも良いのですが、このように名前を割り当てると、テストベンチで名前でステートを表示することができますのでデバッグに便利です。もっと具体的な名前を付けても良いのですが、大まかな流れに番号を振った方が分かりやすいので、このような名前にしてあります。

 114行目からがステートマシンの本体ですが、128行目からのcase分の中で各ステート毎の動作を記述しています。

 ステートマシンの場合、初期値を入れるのを忘れない事です。115行目でリセット信号で初期値が入るようにしていますが、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ビット目になっていて、514行目のassign SPI_MOSI = rSendDat[3]; でMOSI信号に接続しています。rSendDatは4ビットのシフトレジスタになっていて、156行目のrSendDat <= {pSTART,pSPI_SGL_DIFF,rSPI_ODD_SIGN[0],pMSBF};でデータを組み立てています。Verilogの場合、データを連結するには{}の中に,で区切って記述します。

177行目がシフトレジスタの記述でrSendDat[3:0] <= {rSendDat[2:0],1’b0};のように右辺は{}を使ってデータが1ビット左にズレるように記述しています。全部シフトが終わると全ビット0になります。

 受信も同じようにシフトレジスタを使っています。273行目のrRecDat <= {rRecDat[8:0],SPI_MISO};です。右辺が同じように{}を使って1ビット左にズレるようになっています。MSB(最下位ビット)にはSPI_MISOが入るようになっていますので、徐々に受信データは左にズレていきます。最後は全部が受信データで埋まります。

 全部データを受信終えるとS33で1週目か2週目かを判断して、1週目はrRecDatAに2週目はrRecDatBにコピーします。

 デバッグする時にはrstateを観察すると状態遷移図のように動作しているのかがすぐにわかります。

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