上一节中,我们详细讨论了UART的协议内容并从设计组件的角度给出了UART协议中所需要的诸多内容,以供读者参考
这一节中,我们自定义如下标准的UART进行设计,需要注意的是,本篇文章中所涉及的UART仅供初学者学习参考,并没有采用实际工业开发中所涉及到的代码标准和思考,也并未进行综合与后仿。
【数字IC】深入浅出理解UART协议
【数字IC手撕代码】Verilog边沿检测电路(上升沿,下降沿,双边沿)|题目|原理|设计|仿真
【数字IC手撕代码】Verilog奇偶校验|题目|原理|设计|仿真
【数字IC手撕代码】Verilog奇数分频|题目|原理|设计|仿真
【数字IC手撕代码】Verilog偶数分频|题目|原理|设计|仿真
从top level来看,整个UART的涉及应该包含三个module
首先是baud_generate module
对于波特率生成器而言,我们需要在这个模块中将全局时钟信号100Mhz进行分频,按照约定的波特率如300,1200,2400,9600等要求进行分频处理,以得到所要求的波特率要求。
其次是tx module
对于发送模块而言,我们要从状态机的角度对其进行划分,按起始位,数据位,校验位,停止位进行状态划分,并区分状态转移的条件。
最终是rx module
对于发送模块而言,我们也从状态机的角度对其进行划分,按起始位,数据位,校验位,停止位进行状态划分,并区分状态转移的条件。不过按照《深入浅出理解UART协议》所言,在rx模块中我们需要使用发送频率的十六倍频进行采样,使用多路选择的方法避免数据传输中可能会出现的错误。
time_frequency = 100_000_000
用以确定全局时钟频率
baud_rate = 9_600
用以确定RX,TX的波特率
data_width = 8
用以确定数据位宽
test = 1
用以确定奇偶校验,其中0为无校验位,1为偶校验,2为奇校验
stop_width = 2
用以确定停止位位宽
module baud_generator #(
parameter clk_rate = 100_000_000, //全局时钟频率
parameter baud_rate = 9_600 //波特率
)(input clk,
input rst_n,
output rx_clk,
output tx_clk);
localparam tx_rate = clk_rate / (baud_rate * 2); //发送模块分频系数
localparam rx_rate = clk_rate / (baud_rate * 2 * 16); //接收模块分频系数
reg [$clog2(rx_rate)-1:0] rx_count;
reg [$clog2(tx_rate)-1:0] tx_count;
reg rx_clk_reg;
reg tx_clk_reg;
// rx_clk分频
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rx_count <= 'b0;
rx_clk_reg <= 1'b0;
end
else if(rx_count == rx_rate - 1'b1)
begin
rx_clk_reg <= !rx_clk_reg;
rx_count <= 'b0;
end
else
rx_count = rx_count + 1'b1;
end
//tx_clk分频
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
tx_count <= 'b0;
tx_clk_reg <= 1'b0;
end
else if(tx_count == tx_rate - 1'b1)
begin
tx_clk_reg = !tx_clk_reg;
tx_count <= 'b0;
end
else
tx_count= tx_count +1'b1;
end
assign rx_clk = rx_clk_reg;
assign tx_clk = tx_clk_reg;
endmodule
`timescale 1ns / 1ps
module baud_generator_tb ();
reg clk;
reg rst_n;
wire rx_clk;
wire tx_clk;
baud_generator #(10_000_000,9600) u1 (clk,rst_n,rx_clk,tx_clk);
initial clk = 0;
always #5 clk = !clk; //生成10ns全局时钟
initial
begin
rst_n = 1;
rst_n = 0;
#45
rst_n = 1;
#4000000000;
$stop;
end
endmodule
根据波形图我们发现,设定为9600波特率的输出,即:用于tx模块与rx模块的分频时钟信号,符合预期。
我们希望tx端的采样频率遵循9.6khz,但是我们这里最终分频输出的是9.599kHz,这会影响最终设计的正确结果吗?大家可以在评论区讨论一波。
IDLE:默认态,无数据传输,输出高电平,当enable信号到来时跳转到S1。
S1:起始位,无数据传输,输出低电平,无条件跳转到S2。
S2:数据位,数据传输发生在S2,根据数据输出高低电平,假如有校验位,跳到S3,假如数据传输不设校验位,跳转到S4
S3:校验位,根据要求,输出奇数校验或者偶数校验的值,下一个状态无条件跳转到S4。
S4:停止位,根据要求,输出1个或2个周期的高电平,下一个状态无条件跳转到IDLE。
module tx
#(parameter data_width = 8,
parameter test = 2,
parameter stop_width = 1)
(
input tx_clk,
input rst_n,
input [data_width-1:0] data_in,
input enable,
output reg tx_out);
localparam IDLE = 3'b000;
localparam S1 = 3'b001;
localparam S2 = 3'b010;
localparam S3 = 3'b011;
localparam S4 = 3'b100;
reg [3:0] state, nstate;
reg [3:0] count_data;
reg [1:0] count_stop;
reg check_bit;
//状态机第一段
always@(posedge tx_clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else
state <= nstate;
//状态机第二段
always@(*)
begin
case(state)
IDLE: nstate = (enable ? S1 : IDLE);
S1: nstate = S2;
S2: nstate = (test == 'b0 && count_data == (data_width - 1)? S4 : count_data == (data_width - 1)? S3 : S2);
S3: nstate = S4 ;
S4: nstate = count_stop == (stop_width - 1) ? IDLE : S4 ;
default: nstate = IDLE;
endcase
end
//状态机的输出
always@(*)
case(state)
IDLE : tx_out = 1'b1;
S1 : tx_out = 1'b0;
S2 : tx_out = data_in[count_data]; //由低位到高位依次输出
S3 : tx_out = check_bit;
S4 : tx_out = 1'b1;
default : tx_out = 1'b1;
endcase
//S2状态,输出数据位的计数器
always@(posedge tx_clk or negedge rst_n)
if(!rst_n)
count_data <= 4'b0000;
else if (count_data < data_width && state == S2)
count_data <= count_data + 1'b1;
else
count_data <= 4'b0000;
//UART的奇偶校验位的生成
always@(posedge tx_clk or negedge rst_n)
begin
if(!rst_n)
check_bit <= 1'b0;
else if (state == S2)
case(test)
2'b00 : check_bit <= 1'b0;
2'b01 : check_bit <= !(^data_in);
2'b10 : check_bit <= (^data_in);
default:check_bit <= 1'b0;
endcase
else check_bit <= check_bit;
end
//UART停止位计数器
always@(posedge tx_clk or negedge rst_n)
begin
if(!rst_n)
count_stop <= 2'b00;
else if( state == S4 && count_stop <stop_width)
count_stop <= count_stop + 1'b1;
else
count_stop <= 2'b00;
end
endmodule
`timescale 1ns / 1ps
module tx_tb();
reg clk;
reg rst_n;
reg enable;
reg [7:0] data_in;
wire tx_out;
//端口例化与数据传输
parameter A=8;
parameter B=1;
parameter C=2;
tx #(A,B,C) u1 (clk,rst_n,data_in,enable,tx_out);
//时钟生成
initial clk = 0;
always #5 clk = !clk;
//数据发送task
task write_data;
input [7:0] task_data_in;
begin
@(negedge clk);
enable = 1;
@(negedge clk) ;
data_in = task_data_in;
enable =0;
repeat(12)@(negedge clk);
end
endtask
//正式测试
initial
begin
rst_n=1;
enable = 0;
#15
rst_n=0;
#50
rst_n=1;
#30;
write_data(8'h0a);
write_data(8'h24);
write_data(8'h33);
write_data(8'h14);
end
endmodule
我们这里测试的是8位,奇校验,2位停止位的发送module,可见在S2时,输出位从低位到高位将input的数据逐位发出,校验位和停止位也都符合预期
IDLE:默认态,不需要接收数据传输
S1:起始位接收,接收起始位数据传输,无条件跳转到S2。
S2:数据位接收,数据接收发生在S2,根据数据接收情况输出高低电平,假如有校验位,跳到S3,假如数据传输不设校验位,跳转到S4
S3:校验位接收,根据要求,接收奇数校验或者偶数校验的值,并进行检验,下一个状态无条件跳转到S4。
S4:停止位接收,根据要求,接收1个或2个周期的高电平,下一个状态无条件跳转到IDLE。
module rx #(
parameter data_width = 8,
parameter test = 2,
parameter stop_width = 1)
(input rx_clk,
input rst_n,
input data_in,
output [data_width-1:0]rx_out,
output fail);
//状态机定义
localparam IDLE = 3'b000;
localparam S1 = 3'b001;
localparam S2 = 3'b010;
localparam S3 = 3'b011;
localparam S4 = 3'b100;
reg [2:0] state, nstate;
//
reg [3:0] frq_6;
reg data_in_reg;
reg [15:0] filter_reg;
reg filter_out;
reg [data_width-1:0] rx_out_reg;
reg test_reg;
wire start;
//????????????
reg [3:0] count_data;
reg [1:0] count_stop;
//三段式状态机的第一段
always@(posedge rx_clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else
state <= nstate;
//三段式状态机的第二段
always@(*)
begin
case(state)
IDLE: nstate = (start ? S1 : IDLE);
S1: nstate = (frq_6 == 4'b1111 ? S2 : S1);
S2: nstate = (test == 'b0 && frq_6 == 4'b1111 && count_data == data_width - 1) ? S4 : (frq_6 == 4'b1111 && count_data == data_width - 1)? S3 : S2 ;
S3: nstate = (frq_6 == 4'b1111 ? S4 : S3);
S4: nstate = (frq_6 == 4'b1111 && (count_stop == stop_width - 1)? IDLE : S4);
default:nstate = IDLE;
endcase
end
//下降沿检测电路,输出start信号,激活UART接收
always@(posedge rx_clk or negedge rst_n)
if(!rst_n)
data_in_reg <= 1'b0;
else
data_in_reg <= data_in;
assign start = data_in_reg & !data_in;
//数据接收个数的计数器
always@(posedge rx_clk or negedge rst_n)
if(!rst_n || state !== S2)
count_data <= 4'b0000;
else if (count_data == data_width - 1 && frq_6 ==4'b1111)
count_data <= 4'b0000;
else if (frq_6 == 4'b1111)
count_data <= count_data + 1'b1;
else
count_data <= count_data;
//停止位接受个数的计数器
always@(posedge rx_clk or negedge rst_n)
if(!rst_n || state !== S4)
count_stop <= 2'b00;
else if (count_stop == stop_width - 1 && frq_6 ==4'b1111)
count_stop <= 2'b00;
else if(frq_6 == 4'b1111)
count_stop <= count_stop + 1'b1;
else
count_stop <= count_stop;
//16倍的采样计数器
always@(posedge rx_clk or negedge rst_n)
if(!rst_n)
frq_6 <= 4'b0000;
else if(frq_6 == 4'b1111 && state == IDLE)
frq_6 <= 4'b0000;
else if(state == S1 || state == S2 || state == S3 || state == S4)
frq_6 <= frq_6 + 1'b1;
else
frq_6 <= frq_6;
//16倍频的采样结果存储在filter_reg中
always@(posedge rx_clk or negedge rst_n)
if(!rst_n)
filter_reg <= 16'h0000;
else if(state == S1 || state == S2 || state == S3 || state == S4)
filter_reg[frq_6] <= data_in;
else
filter_reg <= 16'h0000;
//存储后的多路选择,结果输出位filter_out
always@(posedge rx_clk or negedge rst_n)
if(!rst_n || state == IDLE)
filter_out <= 1'b0;
else if ( frq_6 == 4'b1100)
filter_out <= (filter_reg[7] & filter_reg[8]) ^ (filter_reg[7] & filter_reg[9]) ^ (filter_reg[8] & filter_reg[9]);
else
filter_out <= filter_out;
//S2状态时将数据依次存入寄存器
always@(posedge rx_clk or negedge rst_n)
if(!rst_n || state == IDLE)
rx_out_reg <= 'b0;
else if (state == S2 && frq_6 == 4'b1111)
rx_out_reg[count_data] <= filter_out;
else
rx_out_reg <= rx_out_reg;
//S3状态时判断校验位是否正确
always@(posedge rx_clk or negedge rst_n)
if(!rst_n)
test_reg <= 1'b0;
else if(state == S3)
case(test)
2'b00 : test_reg <= 1'b0;
2'b01 : test_reg <= !(^rx_out_reg);
2'b10 : test_reg <= (^rx_out_reg);
default : test_reg <= 1'b0;
endcase
else
test_reg <= 1'b0;
assign fail = (state == S3 && frq_6 == 4'b1110 && filter_out !== test_reg) ? 1 : 0;
//数据接受完毕,输出传入RX的值
assign rx_out =(state == S4 && count_stop == stop_width - 1) ? rx_out_reg : 'b0 ;
endmodule
`timescale 1ns / 1ps
module rx_tb();
reg clk;
reg rst_n;
reg data_in;
wire rx_out;
reg test;
parameter A=8;
parameter B=1;
parameter C=2;
rx #(A,B,C) u1 (clk,rst_n,data_in,rx_out);
initial clk = 0;
always #5 clk = !clk;
task receive_data;
input [7:0] A;
begin
repeat(16)@(negedge clk);
data_in = 0;
test = ^A;
repeat(8)
begin
repeat(16)@(negedge clk);
data_in = A[0];
A = A>>1;
end
repeat(16)@(negedge clk);
data_in = test;
repeat(16)@(negedge clk);
data_in = 1;
repeat(16)@(negedge clk);
data_in = 1;
#200;
end
endtask
initial
begin
rst_n=1;
data_in = 1;
#15
rst_n=0;
#50
rst_n=1;
#30;
receive_data(8'h34);
receive_data(8'ha8);
receive_data(8'hb4);
end
endmodule
在仿真中,我们例化RX的时候,位宽选用的八位,校验形式选择的偶校验,停止位选用的为两位
输入数据位宽八位,停止位两位,校验形式为奇校验的数据34,a8,都正确的传入到了输出端,但因校验方式的错误,fail信号拉高一个周期,符合设计标准
module uart_top#(
parameter time_frequency = 100_000_000,
parameter baud_rate = 9_600,
parameter data_width = 8,
parameter test = 1,
parameter stop_width = 2
)(
input clk,
input rst_n,
input enable,
input [data_width-1:0] data_in,
output [data_width-1:0] rx_out);
wire rx_clk;
wire tx_clk;
wire tx_out;
baud_generator #(time_frequency,baud_rate) u1 (.clk(clk),.rst_n(rst_n),.rx_clk(rx_clk),.tx_clk(tx_clk));
tx #(data_width,test,stop_width) u2(.tx_clk(tx_clk),.rst_n(rst_n),.data_in(data_in),.enable(enable),.tx_out(tx_out));
rx #(data_width,test,stop_width) u3(.rx_clk(rx_clk),.rst_n(rst_n),.data_in(tx_out),.rx_out(rx_out),.fail());
endmodule
`timescale 1ns / 1ps
module uart_top_tb();
reg clk;
reg rst_n;
reg enable;
reg [7:0] data_in;
wire [7:0] rx_out;
uart_top u4(clk,rst_n,enable,data_in,rx_out);
initial clk = 0;
always #5 clk = !clk;
initial
begin
rst_n = 1;
#2000;
rst_n = 0;
enable = 1;
#2000;
rst_n = 1;
data_in = 8'h34;
#20000000;
$stop;
end
endmodule
TX输入8’h34之后,经过1.2ms,RX输出相同的数值,设计符合要求
最后再讨论一下本设计与实际工程中的UART的差异性在哪,以供读者补充参考。
不过本设计仅为学习参考使用,配合【数字IC】深入浅出理解UART协议使读者对于URAT的协议理解和电路实现有基本的认识才是本篇博文的目的所在。
收藏并关注作者,获取最新的更新动态