使用verilog实现uart协议,能够和pc进行通信,实现串口回环功能,各参数设置如下:
系统时钟为50M,115200波特率下,每一个bit占50M/115200 = 434时钟周期。
停止位任意即不考虑停止位。代码追求简洁,能用就行。
注意:uart协议是LSB优先的
接收模块从pc端接收到异步的串行信号,解析为valid信号+一字节并行数据,传递到其他模块,回环的话,直接传递到发送模块。
流程:
代码:
module uart_rx(
input clk, // 50M
input rst_n,
// uart side
input rx,
// host side
output 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) begin
if(!rst_n)
rx_shift <= 3'b111;
else
rx_shift <= {rx, rx_shift[2:1]};
end
// 计数器使能
always @(posedge clk, negedge rst_n) begin
if(!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 cnt
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
baud_cnt <= 0;
else if(cnt_ena) begin
if(baud_cnt == BAUD_CNT_MAX)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 13'd1;
end
end
// bit cnt
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
bit_cnt <= 0;
else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) begin
if(bit_cnt == 4'd8)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 4'd1;
end
end
// output valid
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
valid <= 0;
else if(baud_cnt == BAUD_CNT_MAX && bit_cnt == 4'd8)
valid <= 1'b1;
else
valid <= 1'b0;
end
// output data
always @(posedge clk, negedge rst_n) begin
if(!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;
end
task receieve_data;
input [7:0] sim_data;
begin
// 起始位
rx <= 1'b0;
#(20 * 434);
// 8bit数据
for(i = 0; i < 8; i = i + 1) begin
rx <= sim_data[i];
#(20 * 434);
end
// 停止位
rx <= 1'b1;
#(20 * 434);
#1000;
end
endtask
uart_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输出。
流程:
代码:
module uart_tx(
input clk, // 50M
input rst_n,
// host side
input valid,
input [7:0] data,
// uart side
output 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) begin
if(!rst_n)
data_start_reg <= 0;
else if(valid)
data_start_reg <= {data, 1'b0};
end
// cnt_ena
always @(posedge clk, negedge rst_n) begin
if(!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_cnt
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
baud_cnt <= 0;
else if(cnt_ena) begin
if(baud_cnt == BAUD_CNT_MAX)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 13'd1;
end
end
// bit_cnt
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
bit_cnt <= 0;
else if(cnt_ena && baud_cnt == BAUD_CNT_MAX) begin
if(bit_cnt == 4'd8)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 4'd1;
end
end
// tx
assign 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;
end
task uart_send;
input [7:0] send_data;
begin
valid <= 1'b1;
data <= send_data;
#20;
valid <= 1'b0;
data <= 8'd0;
#(20 * 434 * 10); // 10baud
#1000;
end
endtask
uart_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!,接收如下: