verilog~UART通信受信機(RX)編~
- 2019.06.05
- Quartus verilog
- altera, FPGA, intel, MAX10, quartus, RS232, RX, UART, verilog, verilog HDL, ボーレート, やり方, 入門, 初心者, 受信機, 通信, 通信規格
初めに
お久しぶりです、Keymaleです。先週はUARTの送信機について紹介しました。結構検索して見ていただいたみたいなので、今回は受信機のほうを紹介していきます。受信機のコード内に分周器が出てきますが、詳細は過去記事を見てください。
UART規格
規格についても詳細はこちらを見てください。
通信レート(ボーレート)は9600, 19200, 38400, 76800, 115200 bpsなどがあります。今回は前回の送信機の レートと合わせて 9600 bpsとします。メインクロックは50 MHzとして、9600 bpsにするには5208で分周しますが、受信機側は送信側クロックの4倍のクロックで受信データをたたきます。受信方法を二つ紹介します。一つ目はスタートビットを検出したら、4回クロックをずらして、次のデータを取得します。スタートビットでクロックの同期を簡易的にと取る方法です。通信レートが低ければ送受信間でクロック周波数がよほど差がない限りこれで通信できます。もう一つの方法は4回たたいて、3回以上HIGHなら1、LOWなら0と多数決論理にて、受信ビットを決定していきます。此方のほうがロバスト性が高いですが、少しだけコードが増えます。どちらにせよ受信クロックは4倍のクロックになるように、50 MHzで1302で分周します。
UART_RX module
それでは早速UARTのRXモジュールを見ていきましょう。まずは簡易クロック同期による方法を以下に記載します。
module UART_RX(
input clk, //50MHz
input reset_n,
input UART_RXD,
output reg[7:0]get_data );
//////////UART RX clk generate/////////
reg [15:0]cnt_p;
reg [15:0]cnt_n;
reg out_p;
reg out_n;
parameter rate_div = 1302;/////(50M/9600)/4 = 1302
assign uart_rx_clk = out_p ^ out_n;
initial begin
cnt_p = 16'd1;
cnt_n = 16'd1;
out_p = 0;
out_n = 0;
end
always@(posedge clk)begin
cnt_p <= cnt_n + 16'd1;
if(cnt_n == rate_div)begin
cnt_p <= 16'd1;
out_p <= ~out_p;
end
end
always@(negedge clk)begin
cnt_n <= cnt_p + 16'd1;
if(cnt_p == rate_div)begin
cnt_n <= 16'd1;
out_n <= ~out_n;
end
end
//////////UART RX clk generate end/////////
//////////UART RX module generate/////////
reg[4:0] state;
reg[2:0] clk_cnt;
reg[2:0] get_startbit;
initial begin
get_data = '0;
state = '0;
clk_cnt = '0;
get_startbit = '1;
end
always@(posedge uart_rx_clk)begin
if(!reset_n)begin
state <= 0;
get_data <= 0;
clk_cnt <= 0;
get_startbit <= 1;
end else begin
get_startbit <= {get_startbit[1:0], UART_RXD};
if(state == 0)begin
if(get_startbit == 3'b000)begin
state <= 5'd1;
end
end else if(state <= 5'd8)begin
if(clk_cnt == 3'd3)begin
state <= state + 5'd1;
clk_cnt <= 0;
get_data[state-1] <= UART_RXD;
end else begin
clk_cnt <= clk_cnt + 1;
end
end else if(state == 5'd9)begin
if(clk_cnt == 3'd3)begin
state <= 0;
clk_cnt <= 0;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
end
end
endmodule
以上になります。CASE文で書く方法もあるのですが、重複が多くなり無駄に行数が多くなってしまうので、多少深くなってしまいますが、IF文でごり押しで書いてみました。
次に多数決論理を入れたものを以下に記載します
module UART_RX(
input clk, //50MHz
input reset_n,
input UART_RXD,
output reg[7:0]get_data
);
//////////UART RX clk generate/////////
reg [15:0]cnt_p;
reg [15:0]cnt_n;
reg out_p;
reg out_n;
parameter rate_div = 1302;//(50M/9600)/4 = 1302
assign uart_rx_clk = out_p ^ out_n;
initial begin
cnt_p = 16'd1;
cnt_n = 16'd1;
out_p = 0;
out_n = 0;
end
always@(posedge clk)begin
cnt_p <= cnt_n + 16'd1;
if(cnt_n == rate_div)begin
cnt_p <= 16'd1;
out_p <= ~out_p;
end
end
always@(negedge clk)begin
cnt_n <= cnt_p + 16'd1;
if(cnt_p == rate_div)begin
cnt_n <= 16'd1;
out_n <= ~out_n;
end
end
//////////UART RX clk generate end/////////
//////////UART RX module generate/////////
reg[4:0] state;
reg[2:0] clk_cnt;
reg[3:0] get_bit;
reg[3:0] bit_sum;
reg bit_value;
initial begin
get_data = '0;
state = '0;
clk_cnt = '0;
get_bit = '1;
end
always@(posedge uart_rx_clk)begin
if(!reset_n)begin
state <= 0;
get_data <= 0;
clk_cnt <= 0;
get_bit <= 1;
end else begin
get_bit <= {get_bit[2:0], UART_RXD};
bit_sum <= get_bit[0] + get_bit[1] +get_bit[2] + get_bit[3];
if(bit_sum <= 4'd1)begin
bit_value <= 1'b0;
end else if(bit_sum >= 4'd3)begin
bit_value <= 1'b1;
end
if(state == 0)begin
if(bit_value == 1'b0)begin
state <= 5'd1;
end
end else if(state <= 5'd8)begin
if(clk_cnt == 3'd3)begin
state <= state + 5'd1;
clk_cnt <= 0;
get_data[state-1] <= bit_value;
end else begin
clk_cnt <= clk_cnt + 1;
end
end else if(state == 5'd9)begin
if(clk_cnt == 3'd3)begin
state <= 0;
clk_cnt <= 0;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
end
end
endmodule
bit_sumで過去4回分のbitの合計値を計算し、1以下ならばbit_valueを0に、3以上ならば1にしています。簡易同期とは違いデータを確定するために4クロック分見てから判断するので受信データが数クロック分遅延します。
ModelSimでの検証結果
一応modelsimで動作を確認しましたので、以下に結果を載せておきます。まずは簡易同期の方法です。
00111011のデータを前回作った送信機から送信して、今回作った受信機側で同じで00111011を受信できていることが確認できるかと思います。
次に多数決論理の方法も検証してみます。
こちらも00111011がきちんと受信できていますね。
最後まで見てくださってありがとうございます。最近は育児ブログも書いているのでそちらのほうもよかったら見てください。verilog記事はいろいろ書いてきましたが、次はsystem-verilogで実装されたenumlateの使い方について説明していこうかと思います。後はwordpressでのTeX,LaTexの使い方とか、pythonでのグラフ描画(Pyplot)などを掲載してく予定です。
お手すきでしたらコメントもよろしくお願いします。
それでは次回も見てくださいね。
-
前の記事
verilog~論理演算~ 2019.05.26
-
次の記事
verilog~if文の使い方とif文優先度 2019.11.18
参考になります。
以下だけ、System Verilogの書き方?になっていますでしょうか。
“`
initial begin
get_data = ‘0;
state = ‘0;
clk_cnt = ‘0;
get_startbit = ‘1;
end
“`
下記のようにしてVerilog-HDLの書き方として使えるようになりました (Vivado v2019.1にて使用)。
“`
initial begin
get_data = 0;
state = 0;
clk_cnt = 0;
get_startbit = 1;
end
“`
System VerilogはVivadoでBlock Designに使用できないため、このような変更が必要でした。
コメントありがとうございます。
ご指摘の通り、 ‘0と ‘1はsystem verilogの書き方で、全ビット0、1という書き方の省略形です。
Vivadoは触っていないので、Quartusだとコンパイラにsystem verilogを指定すると、すべてに適応できるためこのような書き方になっていました。
適宜読み替えてご利用お願いします。
コード拝見しました。
多数決回路のところが実装できていないように思えます。
修正しておいたほうが良いと思います。
新谷さん
コメントありがとうございます。ブログを始めて初めてまともにコメントを頂き感動しております。
ご指摘の点確かに、記述されているコードは多数決回路が入っておらず、スタートビットから受信クロックを
同期させているだけになっていました。
多数決回路を追加したものも記載しましたので、機会があればまた拝見してご意見よろしくお願いします。