简介
使用verilog实现uart协议,能够和pc进行通信,实现串口回环功能,各参数设置如下:
- 波特率:115200
- 数据位:8
- 停止位:任意
- 校验位:无
系统时钟为50M,115200波特率下,每一个bit占50M/115200 = 434时钟周期。
停止位任意即不考虑停止位。代码追求简洁,能用就行。
注意:uart协议是LSB优先的
接收模块
接收模块从pc端接收到异步的串行信号,解析为valid信号+一字节并行数据,传递到其他模块,回环的话,直接传递到发送模块。
流程:
- 首先对输入的异步信号rx打两拍防止亚稳态
- 检测rx下降沿,拉高计数器使能
- 波特计数器循环计数0到433,比特计数器根据波特计数器溢出,从0计数到8,其中0是起始位,1-8是数据位
- 在每一位中间进行采样,即波特计数器=433/2时,数据比较稳定,所以在此刻采样进移位寄存器
- 波特计数器和比特计数器都结束时,拉低计数器使能,拉高valid一时钟周期,输出移位寄存器中的数据。
代码:
module uart_rx(input clk, // 50Minput rst_n,// uart sideinput rx,// host sideoutput reg valid,output reg [7:0] data
);
//---------------------------------信号声明-----------------------------------localparam BAUD_RATE = 115200;localparam BAUD_CNT_MAX = (50_000_000 / BAUD_RATE) - 1;// rx打两拍,第三拍检测下降沿reg [2:0] rx_shift;// 计数器使能reg cnt_ena;// baud计数器reg [12:0] baud_cnt;// bit计数器reg [3:0] bit_cnt;
//---------------------------------------------------------------------------// rx打两拍,第三拍检测下降沿always @(posedge clk, negedge rst_n) beginif(!rst_n)rx_shift <= 3'b111;elserx_shift <= {rx, rx_shift[2:1]};end// 计数器使能always @(posedge clk, negedge rst_n) beginif(!rst_n)cnt_ena <= 0;else if(~rx_shift[1] & rx_shift[0])// rx下降沿cnt_ena <= 1'b1;else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)cnt_ena <= 1'b0;end// baud cntalways @(posedge clk, negedge rst_n) beginif(!rst_n)baud_cnt <= 0;else if(cnt_ena) beginif(baud_cnt == BAUD_CNT_MAX)baud_cnt <= 0;elsebaud_cnt <= baud_cnt + 13'd1;end end// bit cntalways @(posedge clk, negedge rst_n) beginif(!rst_n)bit_cnt <= 0;else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) beginif(bit_cnt == 4'd8)bit_cnt <= 0;elsebit_cnt <= bit_cnt + 4'd1;endend// output validalways @(posedge clk, negedge rst_n) beginif(!rst_n)valid <= 0;else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)valid <= 1'b1;elsevalid <= 1'b0;end// output dataalways @(posedge clk, negedge rst_n) beginif(!rst_n)data <= 0;else if(cnt_ena && baud_cnt == (BAUD_CNT_MAX / 2)) // 在每个bit中间采样进入移位寄存器data <= {rx_shift[0], data[7:1]};end
endmodule
对该模块进行简单的波形仿真,对其发送三次数据,分别是8’h01, 8’hab, 8’hff, tb如下:
`timescale 1ps/1ps
module uart_rx_tb;reg clk = 1'b1;always #10 clk = ~clk;reg rst_n = 1'b0;reg rx = 1'b1;wire valid;wire [7:0] data;integer i = 0;initial begin#30 rst_n = 1'b1;#10;#10000;receieve_data(8'h01);receieve_data(8'hab);receieve_data(8'hff);#100 $stop(2);// $finish;endtask receieve_data;input [7:0] sim_data;begin// 起始位rx <= 1'b0;#(20 * 434);// 8bit数据for(i = 0; i < 8; i = i + 1) beginrx <= sim_data[i];#(20 * 434);end// 停止位rx <= 1'b1;#(20 * 434);#1000;endendtaskuart_rx inst_uart_rx (.clk (clk), .rst_n (rst_n), .rx (rx), .valid (valid), .data (data));endmodule
波形图如下,可以观察到,三次valid拉高时的data正好对应了8’h01, 8’hab, 8’hff:
发送模块
发送模块接收内部的valid+data,转化为uart串行信号通过io输出。
流程:
- 检测到valid信号,则对data进行寄存,并拉高计数器使能
- 波特计数器循环计数0到433,比特计数器根据波特计数器溢出,从0计数到8,其中0是起始位,1-8是数据位
- 根据比特计数器值输出对应的数据位
代码:
module uart_tx(input clk, // 50Minput rst_n,// host sideinput valid,input [7:0] data,// uart sideoutput tx
);
//------------------------------------信号声明---------------------------localparam BAUD_RATE = 115200;localparam BAUD_CNT_MAX = (50_000_000 / BAUD_RATE) - 1;// 数据寄存 + 起始位reg [8:0] data_start_reg;// 计数器使能reg cnt_ena;// baud计数器reg [12:0] baud_cnt;// bit计数器reg [3:0] bit_cnt;
//----------------------------------------------------------------------// 数据寄存 + 起始位always @(posedge clk, negedge rst_n) beginif(!rst_n)data_start_reg <= 0;else if(valid)data_start_reg <= {data, 1'b0};end// cnt_enaalways @(posedge clk, negedge rst_n) beginif(!rst_n)cnt_ena <= 0;else if(valid)cnt_ena <= 1'b1;else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)cnt_ena <= 0;end// baud_cntalways @(posedge clk, negedge rst_n) beginif(!rst_n)baud_cnt <= 0; else if(cnt_ena) beginif(baud_cnt == BAUD_CNT_MAX)baud_cnt <= 0;elsebaud_cnt <= baud_cnt + 13'd1;endend// bit_cntalways @(posedge clk, negedge rst_n) beginif(!rst_n)bit_cnt <= 0;else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) beginif(bit_cnt == 4'd8)bit_cnt <= 0;elsebit_cnt <= bit_cnt + 4'd1;endend// txassign tx = cnt_ena ? data_start_reg[bit_cnt] : 1'b1;
endmodule
对该模块进行简单的波形仿真,让其发送三次数据,分别是8’h01, 8’hab, 8’hff, tb如下:
`timescale 1ps/1ps
module uart_tx_tb;reg clk = 1'b1;always #10 clk = ~clk;reg rst_n = 1'b0;reg valid = 1'b0;reg [7:0] data = 8'd0;wire tx;initial begin#30 rst_n = 1'b1;#10;#10000;uart_send(8'h01);uart_send(8'hab);uart_send(8'hff);#100 $stop(2);// $finish;endtask uart_send;input [7:0] send_data;beginvalid <= 1'b1;data <= send_data;#20;valid <= 1'b0;data <= 8'd0;#(20 * 434 * 10); // 10baud#1000;endendtaskuart_tx inst_uart_tx (.clk (clk),.rst_n (rst_n),.valid (valid),.data (data),.tx (tx));endmodule
波形如下,可以看到,valid信号后,tx端会将数据转化为uart串行数据发送出去,由于data只持续1时钟周期,所以图片看不清楚,三次valid对应的data分别是8’h01, 8’hab, 8’hff:
效果演示
顶层模块就不细说了,将接收模块的valid+data接到发送模块的valid+data即可
串口软件使用正点原子的XCOM,发送字符串hello world!,接收如下: