Verilogでシリアル通信入力

FPGAでシリアル通信を受信する回路です。
ソースを行方不明にしてしまい毎回作ってい気がするので記事として投稿します。


# 条件

* シリアル通信フォーマット
    * データ長　　：8bit
    * ストップbit　：1bit
    * パリティ　　：なし
* ボーレートとクロック
    * ボーレート：115200 
    * クロック　：20MHz

# 仕様
* 8bitデータをパラレル出力
* 受信完了するとデータと同時にイネーブルをアサート
* イネーブルは１サイクル幅でネゲート


# テストベンチ

## テストベンチトップ

テストベンチのトップモジュールです。
UART模擬入力を生成するタスクをインクルードし、
検証対象のモジュール(UART受信回路)をインスタンスしています。


```verilog:UART_RX_tb.v
`timescale 1ns/1ps

`default_nettype none

module UART_RX_tb();
	
	reg			i_rst_n		=1'b0	;//リセット(L有意)
	reg			i_clk		=1'b0	;//クロック
	wire[7:0]	o_dat				;//受信データ
	wire		o_ena				;//受信データイネーブル

	
	//UART模擬タスクをインクルード
	`include "./TB/task_UART.v"
	
	//クロック
	always #25 i_clk=~i_clk;
	
	//テストシナリオ
	initial begin
		#120 i_rst_n=1'b1;//リセット解除
		
		//適当にウェイトを挟んでUART模擬入力を入れる
		#5000;
		T_UART_RX (8'h65);
		#50000;
		T_UART_RX (8'h39);
	end
	
	//検証対象(UART受信回路)
	UART_RX
	DUT
	(.i_rst_n	(i_rst_n	)//リセット(L有意)
	,.i_clk		(i_clk		)//クロック
	,.i_UART_RX	(uart_rx_in	)//UART受信入力
	,.o_dat		(o_dat		)//受信データ
	,.o_ena		(o_ena		)//受信データイネーブル
	);
	
endmodule
```
## UART入力模擬タスク

UART入力信号を模擬するタスクです。

* パラメータ(RATE)を書き換えることで異なるボーレートに対応
* timescaleが　__1ns__　である前提でき記述してあります
* 8bitをLABファーストで出力

```verilog:task_UART.v
reg	uart_rx_in=1'b1;//UART入力模擬信号

//-------------------------------------------
//UART受信入力模擬タスク
//	FPGAがRXとしてFPGAに入れる模擬入力
//-------------------------------------------
task T_UART_RX;
	parameter	RATE=115200	;//ボーレート[bps]
	input[7:0]	dat			;//送るデータ	
	integer 	i			;//ループ用変数
	time		BIT_CYC		;//1ビットの周期
	
	begin
		$display("UART input start : 0x%2X",dat);
		//ボーレートからbit周期[ns]を算出
		BIT_CYC=1000000000/RATE;//timescaleが1nsの前提
		//スタートビット
		uart_rx_in	=1'b0;
		#BIT_CYC;
		//データ:LSBファースト
		for(i=0; i<8; i=i+1)begin
			uart_rx_in	=dat[i];
			#BIT_CYC;
		end
		//ストップビット
		uart_rx_in	=1'b1;
		#BIT_CYC;
	end
endtask
```

# UART受信回路
## 回路構成
* 微分回路で立下り（スタートビット）を検出
* スタートビット検出したら"busy"状態にする
* busyの間分周カウンタを回してデータの中央でサンプリング
* 10bit受信したbusyを解除
* busy解除した次のサイクルでスタートbitをストップbitが正常であれば8bitデータを出力する

## ソースコード
```verilog:UART_RX.v"
//-------------------------------------------------------------------
//UART受信回路
//	・クロック			20[MHz]     => 50[ns]	
//	・ボーレート		115200[bps] => 8.68[us]
//	・分周カウント数	174(0xAE)[カウント/bit]
//	・パリティ 			なし
//	・ストップbit	 	1bit
//	・metastable対策	３段FF
//	・ノイズ対策		実装しない(バッファICと接続を想定)
//-------------------------------------------------------------------
module UART_RX #
	(parameter	DIV_WID=8		//分周カウンタbit幅
	,parameter	DIV_CNT=8'hAE	//データサンプル周期(クロックサイクル数)
	)
	(input	wire		i_rst_n		//リセット(L有意)
	,input	wire		i_clk		//クロック
	,input	wire		i_UART_RX	//UART受信入力
	,output	wire[7:0]	o_dat		//受信データ
	,output	wire		o_ena		//受信データイネーブル
	);
	
	reg[2:0]			rx		;//エッジ検出微分FF
	wire				start	;//開始パルス
	wire				fin		;//終了パルス
	reg					busy	;//処理中ビジー
	reg[DIV_WID-1:0]	div		;//分周カウンタ
	reg[4:0]			cnt		;//ビットカウント
	wire				dt_get	;//データラッチトリガ
	reg[9:0]			sp_ff	;//シリパラFF
	reg					chk_trg	;//データチェックトリガ
	reg[7:0]			dat		;//受信データ
	reg					ena		;//受信データイネーブル
	
	//エッジ検出微分FF
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			rx	<=3'b111;
		else
			rx	<={rx[1:0],i_UART_RX};
	end
	
	//開始パルス：立下り∧非ビジー
	assign start	=(rx[2:1]==2'b10)&(busy==1'b0) ? 1'b1 : 1'b0;

	//終了パルス：10bit目のサンプルタイミング
	assign	fin		=(cnt==5'd9)&(dt_get==1'b1) ? 1'b1 : 1 'b0;
	
	//受信中ビジーフラグ
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			busy	<=1'b0;
		else if(start)
			busy	<=1'b1;
		else if(fin)
			busy	<=1'b0;
	end
	
	//分周カウンタ
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			div	<=8'd0;
		else if(start)
			div	<={ 1'b0,DIV_CNT[DIV_WID-1:1] };//スタートは1/2周期で設定
		else if(busy)
			begin
				if(div==8'd0)
					div	<=DIV_CNT;
				else
					div	<=div-1;
			end
		else
			div	<=8'd0;
	end
	
	//ビットカウンタ
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			cnt	<=5'd0;
		else if(start)
			cnt	<=5'd0;//開始時ゼロクリア
		else if(dt_get)
			cnt	<=cnt+1;//データゲット毎にインクリメント
	end
	
	//データラッチトリガ
	assign dt_get	=(busy==1'b1)&(div==8'd0) ? 1'b1 : 1'b0;
	
	//シリパラFF
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			sp_ff	<=10'h3FF;
		else if(dt_get)
			sp_ff	<={ rx[2],sp_ff[9:1] };//LSBファーストを受信
	end
	
	//データチェックトリガ
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			chk_trg	<=1'b0;
		else
			chk_trg	<=fin;//終了トリガの次のサイクルでデータチェック
	end
	
	//受信データ＆イネーブル
	always@(posedge i_clk or negedge i_rst_n)begin
		if(~i_rst_n)
			begin
				dat	<=8'h00;	//リセット
				ena	<=1'b0;
			end
		else if	((chk_trg==1'b1)	//データチェックトリガが来たとき
				&(sp_ff[0]==1'b0)	//スタートbitが0で
				&(sp_ff[9]==1'b1))	//ストップbitが1ならOK
			begin
				dat	<=sp_ff[8:1];
				ena	<=1'b1;
			end
		else
			begin
				dat	<=dat	;//保持
				ena	<=1'b0	;//1サイクル幅パルスにする
			end
	end
	
	//出力ポート接続
	assign	o_dat	=dat;
	assign	o_ena	=ena;
	
endmodule
```


# シミュレーション結果

テストシナリオで設定した値が受信できています

![image.png](https://qiita-image-store.s3.amazonaws.com/0/204268/bc2a5ee2-19cc-1d2a-f652-0b37726b7493.png)