起因 大三了。从原本的ISRL实验室里逃了出来,属实是喘不过气。没有人带+教授不好约+问题无法解决,在这样的实验室呆着怎么能做好项目呢(恼)
一次偶然的机会,发现了NBS里面隐藏的CICS实验室。环境非常好,并且supervisor非常和蔼可亲。总算能静下心来做点有意义的东西了。
进实验室做的第一个项目是用现有的FPGA板子,实现UART协议。每周去一次,大概三周把这玩意搞下来了,非常有趣。
本文部分参考于https://www.cnblogs.com/liujinggang/p/9535366.html
什么是UART UART,即 universal asynchronous receiver-transmitter ,是最简单也最常见的串口通信协议。从这个名字可以看出,这个协议是非同步的,并且支持全双工通信。
虽然说是非同步的,但是这不代表两个通信设备不需要时钟连接。时钟线,发送线,接收线,就构成了最基本的UART通讯线,只需要三根。因为是串行通讯设备,所以一般来说UART的速度很慢。但是对于简单的物联网IoT设备来说,一秒钟10kB/s级别的传输已经非常足够了。
对于UART协议而言,一个byte的数据会串行化之后,分成八份发送。通常,数据线是处于高电平状态的,在开始发送的时候会将电平拉低。之后会依次发送八个对应的bit,并在发送完成之后将电平拉高。这就是最基础的UART传输方式了。
而传输速度上,一个重要的参数是波特率(Baud Rate)。不同的波特率决定了不同的传输速率。标准的波特率包括了:9600,19200,38400,57600,115200. 波特率的单位是bps,即bit per second。如果说一秒钟能够发送115200bits的信息,那么信息的传输速率就是115200/1000/8=14.4kB/s。这一般也是UART协议能够达到的最高传输速率。然而,真实情况并不能达到这么高的速率。实际上,假如说波特率为115200bits,这就说明了一个bit的持续事件为1/115200=8.7us,那么发送一个byte所需的时间就是其的八倍,69us。但是,别忘了刚才提到的开始位和停止位;为了达成可靠的传输,需要加上两个bit位数的包头和包尾;这样发送一个byte就需要8.7us的十倍即87us的时间。因此,在一秒钟的时间内,这样的循环可以执行1s/87us=11,494.25次,也就是说最大传输速率降低到了11.49kB/s.实际情况下,传输速率可能会比这更低。
verilog实现 接下来讨论如何使用verilog和Nexyz DDR4 FPGA来实现一个标准的UART协议。
时钟发生器 对于一块比较标准的FPGA板子来说,我们假设FPGA的基准频率是50MHz,即每秒钟完成五千万次时钟周期,每个周期20ns。那么要在一秒钟发送115200bits,就相当于说每个bit占用了8.7us.相除得到每个bit需要占用8.7us/20ns=435个周期。因此,如果要完成一次成功的发送,使能信号应该每隔435个周期置1.
我们的目标是实现这样的一个模块,他接收标准的时钟,重置信号,以及发送以及接收器的使能信号,产生发送器和接收器的发送与读取时钟信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 module baudrate_gen ( input I_clk , input I_rst_n , input I_bps_tx_clk_en , input I_bps_rx_clk_en , output O_bps_tx_clk , output O_bps_rx_clk );parameter C_BPS9600 = 5207 , C_BPS19200 = 2603 , C_BPS38400 = 1301 , C_BPS57600 = 867 , C_BPS115200 = 433 ; parameter C_BPS_SELECT = C_BPS115200 ; reg [12 :0 ] R_bps_tx_cnt ;reg [12 :0 ] R_bps_rx_cnt ;always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) R_bps_tx_cnt <= 13'd0 ; else if (I_bps_tx_clk_en == 1'b1 ) begin if (R_bps_tx_cnt == C_BPS_SELECT) R_bps_tx_cnt <= 13'd0 ; else R_bps_tx_cnt <= R_bps_tx_cnt + 1'b1 ; end else R_bps_tx_cnt <= 13'd0 ; end assign O_bps_tx_clk = (R_bps_tx_cnt == 13'd1 ) ? 1'b1 : 1'b0 ;always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) R_bps_rx_cnt <= 13'd0 ; else if (I_bps_rx_clk_en == 1'b1 ) begin if (R_bps_rx_cnt == C_BPS_SELECT) R_bps_rx_cnt <= 13'd0 ; else R_bps_rx_cnt <= R_bps_rx_cnt + 1'b1 ; end else R_bps_rx_cnt <= 13'd0 ; end assign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1 ) ? 1'b1 : 1'b0 ;endmodule
这样就完成了接收和发送时钟的基本逻辑。
发送器 对于发送器而言,他要做的事情就是根据时钟发生器发来的信号来发送自己的bit。此外,还需要额外的使能信号来启动。
发送器有如下的输入和输出:
输入INPUT
输出OUTPUT
时钟信号 全局复位 发送使能信号 发送时钟 并行数据流
串行数据流 发送使能信号 发送结束
其中,输出部分的发送使能信号与时钟发生器的I_bps_tx_clk_en相连,即如果这个值不是高电平则时钟发生器无需为发送器产生发送时钟。这个逻辑也适用于接收器。
verilog代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 module uart_txd ( input I_clk , input I_rst_n , input I_tx_start , input I_bps_tx_clk , input [7 :0 ] I_para_data , output reg O_rs232_txd , output reg O_bps_tx_clk_en , output reg O_tx_done );reg [3 :0 ] R_state ;reg R_transmiting ; always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) R_transmiting <= 1'b0 ; else if (O_tx_done) R_transmiting <= 1'b0 ; else if (I_tx_start) R_transmiting <= 1'b1 ; end always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) begin R_state <= 4'd0 ; O_rs232_txd <= 1'b1 ; O_tx_done <= 1'b0 ; O_bps_tx_clk_en <= 1'b0 ; end else if (R_transmiting) begin O_bps_tx_clk_en <= 1'b1 ; if (I_bps_tx_clk) begin case (R_state) 4'd0 : begin O_rs232_txd <= 1'b0 ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd1 : begin O_rs232_txd <= I_para_data[0 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd2 : begin O_rs232_txd <= I_para_data[1 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd3 : begin O_rs232_txd <= I_para_data[2 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd4 : begin O_rs232_txd <= I_para_data[3 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd5 : begin O_rs232_txd <= I_para_data[4 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd6 : begin O_rs232_txd <= I_para_data[5 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd7 : begin O_rs232_txd <= I_para_data[6 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd8 : begin O_rs232_txd <= I_para_data[7 ] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd9 : begin O_rs232_txd <= 1'b1 ; O_tx_done <= 1'b1 ; R_state <= 4'd0 ; end default :R_state <= 4'd0 ; endcase end end else begin O_bps_tx_clk_en <= 1'b0 ; R_state <= 4'd0 ; O_tx_done <= 1'b0 ; O_rs232_txd <= 1'b1 ; end end endmodule
接收器 对于接收器而言,它需要这些IO:
输入INPUT
输出OUTPUT
时钟信号 全局复位 接收使能信号 接收时钟 串行数据流
并行数据流 接收使能信号 发送结束
可以看到,发送器和接收器是对称的,在串并行的程度上,以及在信号处理的方面。
然而,对于接收器而言,还有一段逻辑需要处理。接收使能信号并不像发送使能信号那样,想发就置1.还记得关于UART设定的讨论吗?在没有发送信息的时候,接收线是高电平。在开始接收到信息时,电平置0.所以对于接收器来说,需要一个逻辑模块来判断接收使能信号是否为1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) begin R_rs232_rx_reg0 <= 1'b0 ; R_rs232_rx_reg1 <= 1'b0 ; R_rs232_rx_reg2 <= 1'b0 ; R_rs232_rx_reg3 <= 1'b0 ; end else begin R_rs232_rx_reg0 <= I_rs232_rxd ; R_rs232_rx_reg1 <= R_rs232_rx_reg0 ; R_rs232_rx_reg2 <= R_rs232_rx_reg1 ; R_rs232_rx_reg3 <= R_rs232_rx_reg2 ; end end assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;
如果接收使能信号一直是高电平,那么要开启接收器的方法就是观察这里的W_rs232_rxd_neg是否被置1.由于一个bit占用的周期数相比四个周期大得多,所以中间产生的时钟偏移可以忽略不计。
完整的接收器代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 module uart_rxd ( input I_clk , input I_rst_n , input I_rx_start , input I_bps_rx_clk , input I_rs232_rxd , output reg O_bps_rx_clk_en , output reg O_rx_done , output reg [7 :0 ] O_para_data );reg R_rs232_rx_reg0 ;reg R_rs232_rx_reg1 ;reg R_rs232_rx_reg2 ;reg R_rs232_rx_reg3 ;reg R_receiving ;reg [3 :0 ] R_state ;reg [7 :0 ] R_para_data_reg ;wire W_rs232_rxd_neg ;always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) begin R_rs232_rx_reg0 <= 1'b0 ; R_rs232_rx_reg1 <= 1'b0 ; R_rs232_rx_reg2 <= 1'b0 ; R_rs232_rx_reg3 <= 1'b0 ; end else begin R_rs232_rx_reg0 <= I_rs232_rxd ; R_rs232_rx_reg1 <= R_rs232_rx_reg0 ; R_rs232_rx_reg2 <= R_rs232_rx_reg1 ; R_rs232_rx_reg3 <= R_rs232_rx_reg2 ; end end assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) R_receiving <= 1'b0 ; else if (O_rx_done) R_receiving <= 1'b0 ; else if (I_rx_start && W_rs232_rxd_neg) R_receiving <= 1'b1 ; end always @(posedge I_clk or negedge I_rst_n)begin if (!I_rst_n) begin O_rx_done <= 1'b0 ; R_state <= 4'd0 ; R_para_data_reg <= 8'd0 ; O_bps_rx_clk_en <= 1'b0 ; end else if (R_receiving) begin O_bps_rx_clk_en <= 1'b1 ; if (I_bps_rx_clk) begin case (R_state) 4'd0 : begin R_para_data_reg <= 8'd0 ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd1 : begin R_para_data_reg[0 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd2 : begin R_para_data_reg[1 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd3 : begin R_para_data_reg[2 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd4 : begin R_para_data_reg[3 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd5 : begin R_para_data_reg[4 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd6 : begin R_para_data_reg[5 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd7 : begin R_para_data_reg[6 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd8 : begin R_para_data_reg[7 ] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd9 : begin O_para_data <= R_para_data_reg ; O_rx_done <= 1'b1 ; R_state <= 4'd0 ; end default :R_state <= 4'd0 ; endcase end end else begin O_rx_done <= 1'b0 ; R_state <= 4'd0 ; R_para_data_reg <= 8'd0 ; O_bps_rx_clk_en <= 1'b0 ; end end endmodule
对于接收到的并行信号,可以对其做写入or打印的处理。
what after? verilog的基本实现已经完成了。我们可以例化一个顶层模块,把这三个东西都放进去,然后连接输入输出。现实中,我们将用FPGA开发板实现这一点。但是在完成这一步之前,我们可以使用vivado的仿真工具先验证一下我们的猜想和代码是否正确。这一块我会再下一个博客中写出。