FPGAでシリアル通信を受信する回路です。
ソースを行方不明にしてしまい毎回作ってい気がするので記事として投稿します。
条件
- シリアル通信フォーマット
- データ長 :8bit
- ストップbit :1bit
- パリティ :なし
- ボーレートとクロック
- ボーレート:115200
- クロック :20MHz
仕様
- 8bitデータをパラレル出力
- 受信完了するとデータと同時にイネーブルをアサート
- イネーブルは1サイクル幅でネゲート
テストベンチ
テストベンチトップ
テストベンチのトップモジュールです。
UART模擬入力を生成するタスクをインクルードし、
検証対象のモジュール(UART受信回路)をインスタンスしています。
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ファーストで出力
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データを出力する
ソースコード
UART_RX.v"
//-------------------------------------------------------------------
//UART受信回路
// ・クロック 20[MHz] => 50[ns]
// ・ボーレート 115200[bps] => 8.68[us]
// ・分周カウント数 174(0xAE)[カウント/bit]
// ・パリティ なし
// ・ストップbit 1bit
// ・metastable対策 3段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
シミュレーション結果
テストシナリオで設定した値が受信できています