VerilogでUART受信
前置き
Verilogと開発ツールの使い方の勉強のため、Verilogであれこれ作成・シミュレーションしようと思う。
UARTの受信
通信仕様は
- データ8bit
- Parityなし
Stop1bit
今までこれ以外のUART通信をみた事がないので、拡張性も持たせない。 ボーレートはparameterで可変とする。 今回は115200bpsで作成。
大方針
スタートビットの検出方法
一般的なスタートビットの検出方法として、
UARTのボーレート用の逓倍のクロックを生成し、数回連続でLだったらスタートビットとみなす方法と、
UARTデータを基板の元クロックでモニターし、数回連続でLだったらスタートビットとみなす方法がある。
今回は後者で検出する。後者のメリットは。ボーレートが多少ずれても許容幅が大きいこと。
手順
- スタートビットを立下りのエッジで検出する
- スタートビットを検出したら、クロックのカウントを開始する。
- ボーレートの半周期だけ待つ。
- ボーレートの1周期毎にデータを取得する。
- 上記を9回(データ8bit + stop bit)繰り返す。
- 9個目のデータ(ストップビット)がHの場合にバッファにデータを送る。
- レジスタを初期化する。
ソースコード
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の使い方
- テストベンチの書き方
- シミュレーションの内部信号の表示方法