verilog~UART通信送信機(TX)編~

初めに

お久しぶりです、Keymaleです。これからは頑張って毎週記事を更新していきたいと思います。pythonの記事も書いているのですが、競合する記事が多くなかなか閲覧数が増えませんでした。verilog系の記事は書いている人が少なく(見てる人も少ない?)結構検索していただいて閲覧している方が多いので、限界までverilogの記事を書いていこうかと思います。

UART規格

UARTの規格ですが、基本的なとトポロジはRS232と同じようです。違いは信号電圧で、UARTが0~3.3 V(5 Vもある)なのに対して、RS232は-15~15 Vのようです。FPGAで使うならUARTのほうがよさそうですね。変換機等も売られており、電圧を調整してくれるようですが、この記事では基本的にUARTで話を進めます。

UARTはUniversal Asynchronous Receiver/Transmitterの略で、全二重通信式の非同期通信方式です。

全二重通信とは送信と受信の線がそれぞれ用意されているもので、SPIなどもそうです。対して半二重通信というものがありまして、これは送信と受信の配線が同一で、送受信を切り替えて通信します。I2Cなどがそうです。

UARTは非同期通信ですが、非同期通信があるということは同期通信もあります。同期通信は別に線を用意してクロックを送信して、そのクロックタイミングに合わせてデータを送り、受信側では受信したクロックでデータを取得する方式で、高速通信が可能です。SPIもI2Cもこの方式です。同期通信方式でもクロックを別の配線ではなく、データにクロックを重畳して送る方式もあり、マンチェスター符号などで符号化し、CDRで復元します。配線数が少なくて済むメリットがあります。

非同期通信方式はクロックを送らずにデータを取得します。ですので、受信データを高いクロックで何回もたたいて、多数決方式でデータが1か0か判定します。UART、RS232などはこの方式です。クロックの配線がいらないですが、データレートが遅くなってしまう欠点があります。また、タイミングずれによるデータが間違って受信されてしまう可能性もあります。

UARTは基本的にデータは8bitで、ストップビットとスタートビットがあります。それとは別にパリティビットがある場合もあります。

通信レート(ボーレート)は
9600, 19200, 38400, 76800, 115200 kbpsなどがあります。ただし、FPGAのクロックが50 MHzとかだとうまく割り切れず、ぴったりのボーレートを出せない可能性があります。その場合は少しぐらいずれていても問題ないです。何倍ものクロックで何回もたたくので、少しのレートの差は問題ありません。

UART_TX module

それでは早速UARTのTXモジュールを見ていきましょう。以下にコードを記載しました。


module UART_TX(
input clk,
input reset_n,
input active,
input [7:0]send_data,
output reg UART_TXD = 1
);

//////////UART TX clk generate/////////
reg [15:0]cnt_p;
reg [15:0]cnt_n;
reg out_p;
reg out_n;

parameter rate_div = 5208;//50M/9600 = 5208

assign uart_tx_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 TX clk generate end/////////

//////////UART TX module generate/////////
reg[4:0] state;

initial begin
    state       = 0;
    UART_TXD    = 1;
end
always@(posedge uart_tx_clk or negedge reset_n)begin
    if(!reset_n)begin
        state       <= 0;
        UART_TXD <= 1;
    end else begin
        if(!active && state == 0)begin
            state <= 1;
        end else if(state != 0)begin
            if(state == 1 && active)begin
                state <= 1;
            end else begin
                state <=state + 1;
                if(state == 2)begin
                    UART_TXD <= 0;
                end else if (state > 2 && state <=10)begin
                    UART_TXD <= send_data[state-3];
                end else if (state == 11)begin
                    state <= 0;
                    UART_TXD <= 1;                
                end
            end
        end
    end
end
endmodule

ボーレートは9600Hzにしています。メインクロックが50MHzでそこから生成しているので、5208で割っています。分周回路についてはこちらを参照してください。この回路は上位回路からactiveの信号の立ち上がりを検知して、信号を送信する仕組みになっています。

ModelSimでの検証結果

以下にModelSimでのシミュレーション結果を示します。

信号は001110011を送っています。LSBファーストで遅れているのが確認できますね。activeは9600の3倍の3200Hzの周波数を生成して、その立ち上がりのたびに信号を伝送しています。今回は待機中はHighになるようにしており、スタートビットは0です。これは機器によってノーマリーHighとかLowとかスタートビットがHighとかLowとか選択することになるので、適宜変えてください。ボーレートもrate_divの変数を変えることで、より高速のものも選択することができます。反省点としてはIF文が多くなり、深い構造となってしまったのですが、verilogのcase文が使い勝手が悪いので、IF文で頑張ったのですが、CASE文のほうが視認性はよかったかもしれないです。

今回は送信機までにしておき、次回に受信機について紹介していこうと思います。最後までみてくださりありがとうございました。ご意見ご感想、ご質問等受け付けておりますので、よろしくお願いします。