UARTレシーバ
#fpga #verilog #uart
先日作成したUART通信用のサンプリングクロック生成機を利用して、今度はUARTのレシーバを作成。
モジュール名
uart_rx
入出力
input rx
input clk
input s_tick
サンプリング用のクロック。baud_genで生成したtickをs_tickへ渡す
output dout
読み込んだ値。読み込み中にどんどん更新されていくので rx_done_tick が立ったタイミングの値を利用する
output rx_done_tick
8bitを読み込み終わったら1が立つ
ASMDチャート
https://gyazo.com/4d4ff19eddde853c35fa77bbb857f39a
Chu, Pong P.. FPGA Prototyping by SystemVerilog Examples (p.291). Wiley. Kindle 版.
参考
FPGA Prototyping by SystemVerilog Examples
作業メモ
スタートビット、データビット、ストップビットの長さは何ナノ秒?
104000ナノ秒(10万4000)
求め方
6.5マイクロ秒(9600Hz * 16サンプリング)ごとにサンプリング用のs_tickが立つ(see: UARTのボーレートの計算方法)
6500ナノ秒 * 16回 = 104000ナノ秒 = 9600Hzの1周期
実装とテストベンチ
code:uart_rx.sv
// iverilog -g 2012 -s uart_rx_testbench uart_rx.sv uart_rx_testbench.sv baud_gen.sv && ./a.out
module uart_rx(
input logic clk,
input logic reset,
input logic s_tick,
input logic rx,
output logic N-1:0 dout,
output logic rx_done_tick
);
localparam N = 8;
localparam SB_TICK = 16;
typedef enum {idle, start, data, stop} rx_state;
rx_state rx_state_reg, rx_state_next;
logic 3:0 s_reg, s_next;
logic 2:0 n_reg, n_next;
logic 7:0 b_reg, b_next;
assign dout = b_reg;
always_ff @(posedge clk, posedge reset) begin
if (reset)
begin
rx_state_reg <= idle;
s_reg <= 0;
n_reg <= 0;
b_reg <= 0;
end
else
begin
rx_state_reg <= rx_state_next;
s_reg <= s_next;
n_reg <= n_next;
b_reg <= b_next;
end
end
always_comb begin
rx_state_next = rx_state_reg;
s_next = s_reg;
n_next = n_reg;
b_next = b_reg;
rx_done_tick = 0;
case (rx_state_reg)
idle: begin
if (rx == 0) begin
s_next = 0;
rx_state_next = start;
end
end
start: begin
if (s_tick == 1) begin
if (s_reg == 4'd7)
begin
s_next = 4'd0;
n_next = 3'b0;
rx_state_next = data;
end
else
s_next = s_reg + 4'd1;
end
end
data: begin
if (s_tick == 1) begin
if (s_reg == 4'd15)
begin
s_next = 4'd0;
// NOTE: 以下のようなエラーが出るのでなんとかしたい...
// `
// constant selects in always_* processes are not currently supported
// (all bits will be included).
// `
b_next = {rx, b_reg7:1};
if (n_reg == (N - 1))
rx_state_next = stop;
else
n_next = n_reg + 3'd1;
end
else
s_next = s_reg + 4'd1;
end
end
stop: begin
if (s_tick == 1) begin
if (s_reg == (SB_TICK - 1))
begin
rx_done_tick = 1;
rx_state_next = idle;
end
else
s_next = s_reg + 4'd1;
end
end
endcase
end
endmodule
code:uart_rx_testbench.sv
`timescale 1ns/1ps
module uart_rx_testbench();
logic clk, reset;
logic s_tick, rx;
logic 7:0 dout;
logic rx_done_tick;
baud_gen bg (
.clk(clk),
.reset(reset),
.dvsr(11'd651), // 100MHz / (16サンプリング * 9600Hz)
.tick(s_tick)
);
uart_rx dut (
.clk(clk),
.reset(reset),
.s_tick(s_tick),
.rx(rx),
.dout(dout),
.rx_done_tick(rx_done_tick)
);
initial begin
$dumpfile("uart_rx.vcd");
$dumpvars(0, dut);
clk = 0;
rx = 1;
reset = 1; #10
reset = 0; #10
// 9600Hzの1周期が約10400ナノ秒
#104000
// スタートビットを立てる
rx = 0;
#104000
// rx_state_reg が 0(idle) から 1(start) になること
// 以下のデータビット(0b01010101 = 0x55)を送信
// 1, 0, 1, 0, 1, 0, 1, 0
rx = 1;
#104000
rx = 0;
#104000
rx = 1;
#104000
rx = 0;
#104000
rx = 1;
#104000
rx = 0;
#104000
rx = 1;
#104000
rx = 0;
#104000
// ストップビットを立てる
rx = 1;
#104000
// dout = 0x55 かつ rx_done_tick に1が立つこと
// スタートビットを立てる
rx = 0;
#104000
// 以下のデータビット(0x55)を送信
// 1, 1, 1, 1, 1, 1, 1, 1
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
rx = 1;
#104000
// ストップビットを立てる
rx = 1;
#104000
// dout = 0xFF かつ rx_done_tick に1が立つこと
#200000 $finish;
end
// 5nsごとにclkを反転することで100MHzのクロックを生成
always #5
clk <= ~clk;
endmodule
出力された波形
rxのラインから0x55と0xFFを送信、doutとrx_done_tickにちゃんと出力されているみたい。
https://gyazo.com/fe8b841b6af97c132ad58f23a1e271c9