verilog~UART通信受信機(RX)編~

初めに

お久しぶりです、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)などを掲載してく予定です。

お手すきでしたらコメントもよろしくお願いします。

それでは次回も見てくださいね。