1.功能描述
设计一个串口数据接收模块。能够以设定的波特率(与发射端口速率匹配)接收数据,并输出保存到一个寄存器中。
2.过程描述
①边沿检测器,识别出起始位时让接收使能端有效。这里需要排除边沿脉冲的干扰,识别出的起始位不能是个瞬时脉冲。
②采样脉冲:区别于发射端,接收端需要对接收的数据进行采样。为保证接受到的数据的准确性,需要设定采样频率(奈奎斯特采样频率)。如下:
遵循uart协议的串口通信,每个数据为10位,包含8个数据位,1起始位,1结束位。在接收端接收时也要按照这个规律接收。具体思路是先判断起始位,然后逐个接收数据位,在结束位出现后把接收的数据赋给寄存器。
如上图所示 ,在采样时,我们把一位划分成16份,舍弃前5后4取中间七份(中心采样)(要求是奇数份,用来判断接收的数据是零还是一),在每一个1/16位中心产生一个采样脉冲,当此脉冲出现时,把它加到寄存器上,最终根据寄存器的值确定接收的数据大小(大于3为1,否则为0)。
编程的思路:此电路仍是时序电路,所以要确定用到的数据和时钟信号的关系。
①根据设定的波特率确定每一位的持续时间,进而确定每一位的计数值bit_tim。
②根据每一位的计数值确定每1/16位的计数值bit_tim_16,使用div_cnt寄存器计数到bit_tim_16-1.
③利用bit_tim_16产生采样脉冲bit16_pulse。
④利用bit_tim_16设计16*10个 状态,用bit16_cnt存储计数.
⑤设计一个寄存器edge_detect检测下降沿来判断起始位,产生接收脉冲,进而设置接收使能端receive_en。
⑥利用状态计数器bit16_cnt和采样脉冲bit16_pulse采样,保存采样值r_data。
⑦采样完成后设置结束信号rx_done。
⑧把储存的采样值r_data经过判决后存进输出寄存器data。
调试的经验:
①对design source 和 testbench 文件修改之后,不需要重新综合,可以直接relaunch stimulation 。这样子就不用重新添加变量,可以提高调试速度。
②Verilog在作运算时,结果取整数,舍弃小数位,因此在设置状态持续时间时,用运算得到的值可能会偏大/小,导致仿真时,误差使得结束位提前出现,tb中@检测不到,从而出错。在实 际中不会出错,但仍然有误差。具体看误差是否可以忽略。
③在仿真验证时,延时可以人为给定,波形出错的原因可能是跟design的时间对不上,不代表design source 设计的逻辑错误,可以对两者进行对比检查。
④注意多语句有无遗漏begin-end语句。
⑤开始检测起始位时,由于需要寄存器存值,出现一个时钟周期的误差,检测到边沿后再给receive_en赋值,又有一个时钟周期的误差。但由于采样取的是位中心,误差对本次设计的影响 可以忽略。
注意:
①采样脉冲的意义:让保存采样值的寄存器r_data在一个状态里只加一次。不然由于一个状态不止一个时钟周期,在代码设计上容易加很多次。
②保存到输出寄存器的时间可以在所有位都采样完之后,对所有位的采样寄存器进行判决即可。
③为保证接收到了起始位,要在边沿检测的基础上,对起始位进行判决,是0才对。
④用一个位宽为二的 寄存器作为边沿检测器,在整个过程中都会一直进行检测。但是不影响结果。
⑤每接受完一个数据后,要对保存采样的寄存器清零,不然会影响下一次接收。
新语法:
①设置二维寄存器格式:类型 [ 位宽 ] 名字 [ 寄存器个数]
位宽[2:0]指的是三个位宽,范围0~7。寄存器个数[2:0]是指三个寄存器,不是指7个寄存器。
引用时,格式为:名字【第几个寄存器】【哪个/哪几个数据】。注意,引用时不能直接对多个寄存器赋值,而定义时可以。
②case里面对于没用到的状态,无需动作,可以直接写default ; 。
③task系统任务。待补充。见语法书。可以在task中不止可以修改task中定义的变量值,而且可以修改task外,本文件里面定义的变量的值。注意task和function的区别,对比记忆。
④<=在always中可以是赋值语句,非阻塞赋值,也可以作为判断语句中,意为小于等于。
3.设计输入
接口: 1.输入端:
数据输入端口uart_tx,接受波特率设置端口baud_rate,时钟信号端口,复位信号端口。
2.输出端:
输出接收的数据端口data,接收完成信号端口rx_done。
4.代码
module uart_receive_1( clk , reset , baud_rate , uart_tx, data , rx_done ); input clk ; input reset ; input [2:0]baud_rate ; input uart_tx ; output reg [7:0]data ; output reg rx_done ; reg [2:0]r_data[7:0] ;//接收每一位数据 reg [2:0]sta_bit ; reg [2:0]sto_bit ; reg [17:0]bit_tim ;//每一位持续的时间(计数) always@(baud_rate) //在这里一个 码元由一位组成,所以波特率=比特率 begin case(baud_rate) //常见的串口传输波特率 3'd0 : bit_tim = 1000000000/300/20 ; //波特率为300 3'd1 : bit_tim = 1000000000/1200/20 ; //波特率为1200 3'd2 : bit_tim = 1000000000/2400/20 ; //波特率为2400 3'd3 : bit_tim = 1000000000/9600/20 ; //波特率为9600 3'd4 : bit_tim = 1000000000/19200/20 ; //波特率为19200 3'd5 : bit_tim = 1000000000/115200/20 ; //波特率为115200 default bit_tim = 1000000000/9600/20 ; //多余的寄存器位置放什么:默认速率 endcase end wire [17:0]bit_tim_16 ;//每1/16位的持续时间(计数) assign bit_tim_16 = bit_tim / 16; wire [8:0]bit16_mid ; //在中心点产生采样脉冲 assign bit16_mid = bit_tim_16 / 2 ; //边沿检测 reg [1:0]edge_detect ; always @( posedge clk or negedge reset ) begin if (!reset ) edge_detect <= 2'd0 ; else begin edge_detect[0] <= uart_tx ; edge_detect[1] <= edge_detect[0] ; end end wire byte_sta_neg ; assign byte_sta_neg = ( edge_detect == 2'b10 ) ? 1 : 0 ;//输入的数据开始出现下降沿,说明出现了起始位(一直运行?) reg receive_en ;//接收使能端 reg [17:0]div_cnt ;//每1/16bit内的计数 reg [7:0]bit16_cnt ;//计数到了第几个状态(10位,每位分成16份,总共160个状态) always @( posedge clk or negedge reset ) begin if (!reset ) receive_en <= 1'd0 ; else if ( byte_sta_neg ) //检测到下降沿,使能段有效(只要有下降沿就使能?) receive_en <= 1'd1 ; else if ( (rx_done) || (sta_bit >= 3'd4 )) receive_en <= 1'd0 ; //检测到结束信号,使能端无效 else if ( ( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 ) )//跑完159后re_en置零 receive_en <= 1'd0 ; end always@( posedge clk or negedge reset ) begin if ( ! reset ) div_cnt <= 18'd0 ; else if (receive_en) begin if ( div_cnt == bit_tim_16 - 1'd1 )//计数,每1/16bit清零 div_cnt <= 18'd0 ; else div_cnt <= div_cnt + 1'b1 ; end else div_cnt <= 18'd0 ; end reg bit16_pulse ;//产生采样脉冲 always@( posedge clk or negedge reset ) begin if ( ! reset ) bit16_pulse <= 18'd0 ; else if (receive_en) if ( div_cnt == bit16_mid ) bit16_pulse <= 1'd1 ; else bit16_pulse <= 1'd0 ; else bit16_pulse <= 1'd0 ; end always@( posedge clk or negedge reset ) begin if ( ! reset ) bit16_cnt <= 8'd0 ; else if (receive_en) begin if (( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 )) bit16_cnt <= 8'd0 ; else if ( div_cnt == bit_tim_16 - 1'd1 ) bit16_cnt <= bit16_cnt + 1'b1 ; end end always@(posedge clk or negedge reset) begin if(!reset) begin sta_bit <= 3'd0 ; r_data[0] <= 3'd0 ; r_data[1] <= 3'd0 ; r_data[2] <= 3'd0 ; r_data[3] <= 3'd0 ; r_data[4] <= 3'd0 ; r_data[5] <= 3'd0 ; r_data[6] <= 3'd0 ; r_data[7] <= 3'd0 ; sto_bit <= 3'd0 ; end else if (bit16_pulse)//舍弃前5后4取中7 case(bit16_cnt) 0: begin sta_bit <= 3'd0 ; r_data[0] <= 3'd0 ; r_data[1] <= 3'd0 ; r_data[2] <= 3'd0 ; r_data[3] <= 3'd0 ; r_data[4] <= 3'd0 ; r_data[5] <= 3'd0 ; r_data[6] <= 3'd0 ; r_data[7] <= 3'd0 ; sto_bit <= 3'd0 ; end 5,6,7,8,9,10,11 : sta_bit <= sta_bit + uart_tx ; 21,22,23,24,25,26,27 : r_data[0] <= r_data[0] + uart_tx ; 37,38,39,41,42,43,44 : r_data[1] <= r_data[1] + uart_tx ; 53,54,55,56,57,58,59 : r_data[2] <= r_data[2] + uart_tx ; 69,70,71,72,73,74,75 : r_data[3] <= r_data[3] + uart_tx ; 85,86,87,88,89,90,91 : r_data[4] <= r_data[4] + uart_tx ; 101,102,103,104,105,106,107 : r_data[5] <= r_data[5] + uart_tx ; 117,118,119,120,121,122,123 : r_data[6] <= r_data[6] + uart_tx ; 133,134,135,136,137,138,139 : r_data[7] <= r_data[7] + uart_tx ; 149,150,151,152,153,154,155 : sto_bit <= sto_bit + uart_tx ; default ; endcase end always@( posedge clk or negedge reset ) begin if ( ! reset ) rx_done <= 8'd0 ; else if ( ( bit16_cnt == 8'd159 ) && (div_cnt == bit_tim_16 - 1'd1 ) )//跑完159后产生一个rx_done信号 rx_done <= 8'd1 ; else if (rx_done <= 8'd1 ) rx_done <= 8'd0 ; end always@( posedge clk or negedge reset )//接收完数据发出rx_done后,把数据从r_data传递给data begin if ( ! reset ) data <= 8'd0 ; else if ( rx_done ) begin data[0] = ( r_data[0] >3 ) ? 1 : 0 ; data[1] = ( r_data[1] >3 ) ? 1 : 0 ; data[2] = ( r_data[2] >3 ) ? 1 : 0 ; data[3] = ( r_data[3] >3 ) ? 1 : 0 ; data[4] = ( r_data[4] >3 ) ? 1 : 0 ; data[5] = ( r_data[5] >3 ) ? 1 : 0 ; data[6] = ( r_data[6] >3 ) ? 1 : 0 ; data[7] = ( r_data[7] >3 ) ? 1 : 0 ; end else if ( receive_en ) data <= 8'd0 ; end endmodule
`timescale 1ns/1ns module uart_receive_tb(); reg clk ; reg reset ; reg [2:0]baud_rate ; reg uart_tx ; wire [7:0]data ; wire rx_done ; uart_receive_1 uart_receive_1( clk , reset , baud_rate , uart_tx, data , rx_done ); initial clk = 1 ; always #10 clk = ! clk ; initial begin reset = 0 ; baud_rate = 0 ; uart_tx = 1 ; #201 ; reset = 1 ; baud_rate = 3'd5 ; uart_input(8'h4f) ; @(posedge rx_done) ; #200 ; uart_input(8'h3a) ; @(posedge rx_done) ; #200 ; uart_input(8'hbc) ; @(posedge rx_done) ; #200; $stop; end task uart_input ;//设定一个任务uart_inpt,有一个输入端uart_tx_data_stm 。在这个task里可以对task外的变量进行赋值 input [7:0]uart_tx_data_stm ;//不返回值,所以不能用x=uart_input。而是直接uart_input。 begin //结构简单的begin-end uart_tx = 1 ; #20 ; uart_tx = 0 ; #8640 ; uart_tx = uart_tx_data_stm[0] ; #8640 ; uart_tx = uart_tx_data_stm[1] ; #8640 ; uart_tx = uart_tx_data_stm[2] ; #8640 ; uart_tx = uart_tx_data_stm[3] ; #8640 ; uart_tx = uart_tx_data_stm[4] ; #8640 ; uart_tx = uart_tx_data_stm[5] ; #8640 ; uart_tx = uart_tx_data_stm[6] ; #8640 ; uart_tx = uart_tx_data_stm[7] ; #8640 ; uart_tx = 1 ; #8640 ; end endtask endmodule