モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

VerilogでUART受信

前置き

Verilogと開発ツールの使い方の勉強のため、Verilogであれこれ作成・シミュレーションしようと思う。

UARTの受信

通信仕様は

  • データ8bit
  • Parityなし
  • Stop1bit

    今までこれ以外のUART通信をみた事がないので、拡張性も持たせない。 ボーレートはparameterで可変とする。 今回は115200bpsで作成。

大方針

スタートビットの検出方法

一般的なスタートビットの検出方法として、 UARTのボーレート用の逓倍のクロックを生成し、数回連続でLだったらスタートビットとみなす方法と、
UARTデータを基板の元クロックでモニターし、数回連続でLだったらスタートビットとみなす方法がある。

今回は後者で検出する。後者のメリットは。ボーレートが多少ずれても許容幅が大きいこと。

手順

  1. スタートビットを立下りのエッジで検出する
  2. スタートビットを検出したら、クロックのカウントを開始する。
  3. ボーレートの半周期だけ待つ。
  4. ボーレートの1周期毎にデータを取得する。
  5. 上記を9回(データ8bit + stop bit)繰り返す。
  6. 9個目のデータ(ストップビット)がHの場合にバッファにデータを送る。
  7. レジスタを初期化する。

ソースコード

Verilog-HDL

module uart_rx(
    input wire CLK,
    input wire RST,
    input wire UARTRX,
    output reg[7:0] UARTRXBUFF
);

parameter UARTTIMING = 9'd434;//CLK/Baudrate    ex)50MHz/115200
parameter WAITTIMING = 9'd217;//CLK/Baudrate/2  ex)50MHz/115200/2

reg[8:0] clkCnt = 9'b0;
reg[8:0] rxData = 9'b0;//8bit + stop bit
reg[3:0] dataCnt = 4'b0;
reg[3:0] startEdge = 4'b1111;

parameter WAITSTATE = 2'b01, RDSTATE = 2'b10, IDLESTATE = 2'b00;
reg[1:0] state = 2'b00;


//UART CLK 
always@(posedge CLK or negedge RST)begin
    if(!RST)begin
        clkCnt          <= 9'b0;
        state           <= IDLESTATE;
        rxData          <= 9'b0;
        dataCnt             <= 4'b0;
        startEdge       <= 4'b1111;
    end
    else begin
        if(state == IDLESTATE)begin
            startEdge[3:0] <= {startEdge[2:0], UARTRX};
            if(startEdge == 4'b1000)begin
                state <= WAITSTATE;
            end
        end
        else if(state == WAITSTATE)begin
            if(clkCnt != WAITTIMING)begin
                clkCnt <= clkCnt + 9'b1;
            end
            else begin//clkCounter == WAITTIMING
                clkCnt  <= 9'b0;
                state   <= RDSTATE;
            end
        end
        else if(state == RDSTATE)begin
            if(clkCnt != UARTTIMING)begin
                clkCnt      <= clkCnt + 9'b1;
            end
            else begin//clkCounter == UARTTIMING
                clkCnt      <= 9'b0;
                rxData[8:0] <= {rxData[7:0], UARTRX};
                dataCnt <= dataCnt + 4'b1;
            end
            
            if(dataCnt == 4'b1001)begin
                
                if(rxData[0])begin
                    UARTRXBUFF[7:0] <= rxData[8:1];
                end
                
                clkCnt          <= 9'b0;
                state           <= IDLESTATE;
                rxData          <= 9'b0;
                dataCnt             <= 4'b0;
                startEdge       <= 4'b1111; 
            end
        end
        else begin//state == OTHER
            clkCnt          <= 9'b0;
            state           <= IDLESTATE;
            rxData          <= 9'b0;
            dataCnt             <= 4'b0;
            startEdge       <= 4'b1111;
        end
    end
end
endmodule

テストベンチ

`timescale 1 ns/ 100 ps
module uart_rx_vlg_tst();
// constants                                           
// general purpose registers
reg eachvec;
// test vector input registers
reg CLK;
reg RST;
reg UARTRX;
// wires                                               
wire [7:0]  UARTRXBUFF;

// assign statements (if any)                          
uart_rx i1 (
// port map - connection between master ports and signals/registers   
    .CLK(CLK),
    .RST(RST),
    .UARTRX(UARTRX),
    .UARTRXBUFF(UARTRXBUFF)
);

always #10 CLK = ~CLK;

initial                                                
begin                                                  
     
CLK = 0;
RST = 1;
UARTRX = 1;

#100 RST = 0;
#100 RST = 1;
#1000 UARTRX = 0;//start
#8680 UARTRX = 1;//1bit
#8680 UARTRX = 0;//2bit
#8680 UARTRX = 1;//3bit
#8680 UARTRX = 1;//4bit
#8680 UARTRX = 0;//5bit
#8680 UARTRX = 0;//6bit
#8680 UARTRX = 1;//7bit
#8680 UARTRX = 0;//8bit
#8680 UARTRX = 1;//stop

#20000 UARTRX = 0;//start
#8680 UARTRX = 1;//1bit
#8680 UARTRX = 1;//2bit
#8680 UARTRX = 1;//3bit
#8680 UARTRX = 0;//4bit
#8680 UARTRX = 0;//5bit
#8680 UARTRX = 0;//6bit
#8680 UARTRX = 1;//7bit
#8680 UARTRX = 0;//8bit
#8680 UARTRX = 1;//stop

// --> end                                             
$display("Running testbench");                       
end 

always                                                  
begin                                                                                                                     
@eachvec;                                              
end                                                    
endmodule

気づき

4bitのレジスタに3bit と1bitのレジスタを連結させ代入する場合、わざわざ4bitであることを明記しないとデータが正しく代入できなかった。
手元にある書籍には明確な記載はないので、別に原因があったのかも?

//正しい
startEdge[3:0] <= {startEdge[2:0], UARTRX};

//間違い
startEdge <= {startEdge[2:0], UARTRX};

テストベンチで、不要と思っていた以下の記述を削除すると、波形が表示されなかった。

always                                                  
begin                                                                                                                     
    @eachvec;                                              
end  

身についたこと

  • parameterの使い方
  • テストベンチの書き方
  • シミュレーションの内部信号の表示方法