用verilog实现UART协议 以此理解何为接口,协议何为模块化设计

UART是很简单的协议,也可以说是入门级的协议,它本身不值得我们花费多大的精力探讨,但是我们可以把它看作我们刚学C语言时候写下的”hello world”,刚学习单片机时候点亮的一个led灯,这样,它就有意义了,而且它的意义已经不再是实现一个功能了。我们要好好利用这个简单的协议去理解“协议”这个概念以及如何进行模块化设计,这对FPGA来说,是尤为重要的两个方面。

主要内容为:
1?何为接口协议,认识UART协议2
2?如何设计模块,设计UART逻辑模块
3?怎样设计逻辑,实现UART逻辑设计
课程附带中art测试程序,支持不大于512字节的任意长度数据收发

接口协议
为什么要单纯的去理解“协议”这个很虚的词呢?因为在我看来,对于刚接触协议或者FPGA这方面的朋友来说,知道“为什么这么做”比“知道怎么做”更重要。为什么这么说呢?

记得我在上学时候,我把好多协议列个清单有,看着清单踌躇满志,准备像打怪升级那样一个一个的攻克,这样就能成为技术牛人,走向人生巅峰了,然后我就去看技术文档,去搞懂这是个什么协议,怎么工作的,硬件怎么连的,程序怎么写的,时序是什么样子的,搞了不久,我投降了。就拿我们经常会接触到协议来举例,uart、I2C、SPI,SDRAM、DDR、serdes、TCP/IP协议栈,其中TCP/IP协议占还包括UDP、TCP、RAP等待,还分了数据链路层等等,太多了太复杂了。我就被一堆一堆专业名词搞晕了,被看不懂的时序整糊涂了,被别人的程序逻辑整残了。那为什么会这样呢?心急,太想多学点多会点,太想学习更多的协议来提升自己,因此,只想知道这个怎么实现那个怎么做,把位置占的太低而不去考虑人家为什么这么设计,也就是忽略了为什么这么做。

其实,协议是学不完的,哪怕今天你把所有的协议都学会了,明天还会有新的协议出现,但协议都是存在共性的,当我们知道为什么了这么做,并对此有深刻的体会,就会游刃有余各种协议,简单的协议,张口即来,复杂点的协议,看看就会,没有协议满足项目需求,那就造他个协议。
协议是什么呢?

协议是人们根据器件的物理特性和使用场景想出来的让器件沟通的方法。器件不会说话,聪明的人类就设定一种方法让他们去沟通,通过交流去完成一些事情。所以说协议是人想出来的,哪怕再晦涩难懂那也是人想出来的,协议是源自于人、源自于生活的,我们是主人,是这些协议的设计者,我们要去俯视它,驾驭它甚至是玩弄它,我们要将抽象的东西回归到生活,站在一个更高的位置去做宏观的理解,然后带着这种理解和好奇心去看某个协议,看某篇文档,我想就会更有效率和动力。比如说,令人比较抓狂的专业名词,可以说就是将生活中的现象用在专业领域的另一种表述方式。

什么是响应应答,就我说老师好,老师说同学们好,
什么是通讯超时,就是我叫声美女,美女没理我。
什么是三次握手,就是我看到小张了,说了句,“哟小张”,小张说嗯,我说,你真的是小张啊,小张来句,是的,我说,真的,小张说真的。这就是TCP/IP里面的三次握手也就是这个原理,握手好了就可以干其他事了,比如说,小张吃饭了没?这就是通讯内容
可以说,一些专业名词不是可以通过生活中的现象去解释,而是它就是源自于我们的生活,那什么是协议呢?举几个例子,

1?我走路上看到小张,我说句:“小张,吃饭了没?”,小张回复一句:“吃了”,这就是我和小张建立起了通讯机制,通讯链路是我们发声器官和空气,通讯协议就是汉语。
2?我又走路上,看到一条小狗,蛮可爱的,我说句:“小狗,吃饭了没?”小狗回复一句:“汪汪”,吓得我拔腿就跑,就是我和小狗通讯建立失败,虽然通讯链路建立起来了,但通讯协议确实汉语和狗语,就好用uart协议去和TA24C02通讯,对不上啊。
3?我又走路上,看到小赵我说句:“小赵”小赵回复一句:“嗯嗯“我说句:“吃了没”小赵回复一句:“吃了“,这就好比IIC协议,有起始信号,有应答信号。
4?我又走路上,看到小李,我说句:“小李,吃饭了没?”小李回了句,刚从厕所出来”。这是啥,通讯建立了,但通讯内容校验有误,这就是为啥好多通讯要做CRC校验,循环校验。
5?我走路上,看到一群美女,这把我激动的,说了句:“嗨,美女们,吃饭了没”?结果美女抬头一看这家伙怎么这么丑,没人回应我,心里想着“傻X”、“丑人多作怪”,这叫什么?多播或广播,在工程上也是用的比较多的一种方式。

所以说,通讯协议源自于生活,我们要有将抽象的东西回归到生活的心态,这样理解起来又容易又开心。
回过头来我们再说,什么是接口协议,百度说接口协议就是需要进行信息交换的接口间需要遵从的通信方式和要求。通讯方式就是这信息是用什么传递的,拿什么运过去的,通信要求就是语法,是说用汉语还是英语还是什么的。也就是说我们要让两个器件进行沟通,首先要做什么呢?确定拿什么传,然后确定说什么话,最后可以说咱想说的了。当然,这就是简单的通讯建立,复杂的通讯原理一样,但建立过程很复杂。
现在就来看看UART协议。我是用什么传的呢?用USB转串口线连接上位机与FPGA板卡,FPGA板卡用MAX232做电平转换之后与FPGA芯片相连。
用verilog实现UART协议 以此理解何为接口,协议何为模块化设计
文章图片

下面我们就看一下uart协议。
UART串口通讯的工作原理是将传输数据的每个字符一位接一位地传输。UART是用两根线进行通信的,从一端看,一个用来收别人说的,一个是发自己说的,一根线它就有两种情况,要不是高电平,要不就是低电平,一根线咱们传数据呢?这就是事先说好的规定,也就是协议。大家可以想想,如果你来设计,咱们通过两根线来进行通讯,你会怎么设计呢?咱们先看看咱们的前辈是怎么设计的。他的格式就是“起始位(0)+八位数据+奇偶校验+停止位”,他的速度是由波特率来设定,空闲状态为高电平,这就是协议。下面咱们来理解一下。
用verilog实现UART协议 以此理解何为接口,协议何为模块化设计
文章图片

什么是起始位,就是要说话了,咱们人说话得先出个声,叫个“嗨,小张”器件它不能发声,所以就要模拟一个与不出话时候不一样的情景,这就是起始位,因为空闲时候是高电平,所以当出现低电平时候表示可以说话了。
数据位,就是和小张说话的内容,就是吃饭了没,比如说,我发个5就表示问小张吃饭了没,那一根线怎么发了,人家说了,每个字符一位接一位地传输,也就是把5转为为二进制00000101,接下来就好像敲电报似得发出去。发0吧数据线拉低了,发1把数据线拉高了,这样就发完了。
奇偶校验,什么是校验,就是判断对不对。比如,我问小张吃饭了没,同时做一个吃饭的动作,这小张听到我说吃饭了没,又看到我做吃饭的动作,就明白了我是在问他吃饭了没?如果我问他吃饭了没,又做个蹲坑的动作,小张就该迷茫了,这是在干啥呢,这时候校验不通过。对于uart就一根线,怎么校验,就数数你发的数里面有几个1,如果1的个数是计数就把
停止位,话说完了,数据线拉高,我不说了。

为啥要设定波特率呢,例如我说,小张吃饭了没?如果说快了,他反应不过来,以为我问他去厕所了?说慢了,他还不耐烦。所以说,接收者和发送者速率要匹配。对于机器,你不告诉说多块,更迷茫了,所以就引出了波特率这个概念。但这只是我们一种粗浅的理解,其实波特率是一个很有历史的名词,它是为了纪念电报码的发明者法国人波特(Baudot)故单位为baud,波特率表示串口通信的速率,可以通俗的理解为一个设备在一秒钟内发送(或接收)了多少码元符号。

它是对符号传输速率的一种度量,1波特即指每秒传输1个码元。在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。大家有兴趣可以看百度百科的介绍https://baike.baidu.com/item/%E6%B3%A2%E7%89%B9%E7%8E%87/2153185?fr=aladdin

如果波特率为9600,也就是每秒发送9600个数据(这里的数据包括起始、停止、校验位等,这也就是和比特率的区别,比特率说的是有效数据),这样的话发送一个数据的时间就是1÷9600=0.0001041667s=104166ns,我们晶振是50MHZ,时钟周期是20ns,所以计数个数为104166÷20=5208。所以,当我们在计数5208过程中要完成一位数据的传输,但为了保险起见,我们在这个数的中间进行传输,也就是2604时一位发送数据。

模块化设计,模块化设计对FPGA工程师来说是非常重要的,合理的模块划分和封装不仅会使代码更友好,而且也非常利于维护,以下是我对模块划分的想法。
用verilog实现UART协议 以此理解何为接口,协议何为模块化设计
文章图片

就uart协议的模块划分,我是这样做的
用verilog实现UART协议 以此理解何为接口,协议何为模块化设计
文章图片

把这个封装一下,就是这样的,详细内容,可看视频
用verilog实现UART协议 以此理解何为接口,协议何为模块化设计
文章图片


程序设计如下:
1、波特率设置模块

module bps_set #( parameter BPS_CNT = 16'd5208 ) ( inputwiresys_clk, inputwiresys_rst_n,outputwirerx_bps_vld, inputwirerx_bps_en,outputwiretx_bps_vld, inputwiretx_bps_en ); localparamBPS_HALF_CNT =( BPS_CNT>>1) - 1'b1; reg [15:0] rx_pbs_cnt; reg [15:0] tx_pbs_cnt; //------------------------- rx cnt------------------------ always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) rx_pbs_cnt <= 16'b0; else if(rx_pbs_cnt == BPS_CNT - 1'b1) rx_pbs_cnt <= 16'b0; else if(rx_bps_en) rx_pbs_cnt <= rx_pbs_cnt + 1'b1; else rx_pbs_cnt <= 16'b0; //------------------------- tx cnt------------------------ always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) begin tx_pbs_cnt <= 16'b0; end else if(tx_pbs_cnt == BPS_CNT) tx_pbs_cnt <= 16'b0; else if(tx_bps_en) tx_pbs_cnt <= tx_pbs_cnt + 1'b1; else tx_pbs_cnt <= 16'b0; assign rx_bps_vld = (rx_pbs_cnt == BPS_HALF_CNT) ? 1'b1 : 1'b0; assign tx_bps_vld = (tx_pbs_cnt == (BPS_CNT -1)) ? 1'b1 : 1'b0; endmodule

2、发送模块
module tx_data ( inputwiresys_clk, inputwiresys_rst_n,outputwiretx_pin, inputwiretx_data_vld, inputwire[7:0]tx_data, inputwiretx_bps_vld, outputregtx_bps_en, outputwiretx_over); //-------------- localparam ----------------localparam IDLE= 4'h0; localparam START= 4'h1; localparam DATA= https://www.it610.com/article/4'h2; localparam CHECK= 4'h4; localparam STOP= 4'h8; reg[3 :0]cs; reg[3 :0]ns; regtx_pin_buf; regempty_dly1; wire[7 :0]rd_data; regrd_en; wireempty; reg[3 :0]tx_cnt; reg[15:0]stop_delay_cnt; tx_fifo tx_fifo//512 ( .clock( sys_clk), .wrreq( tx_data_vld), .data( tx_data),.rdreq( rd_en), .q( rd_data), .empty( empty) ); always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) begin empty_dly1 <= 1'b0; end else begin empty_dly1 <= empty; end always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) begin tx_bps_en <= 1'b0; end else if(!empty && empty_dly1) begin//negedge tx_bps_en <= 1'b1; end else if(empty && (cs == STOP) && (ns == IDLE) ) begin tx_bps_en <= 1'b0; end else begin tx_bps_en <= tx_bps_en; end//-------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) rd_en <= 1'b0; else if((cs == IDLE) &&(ns == START)) rd_en <= 1'b1; else rd_en <= 1'b0; //*********************************************************** always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) cs <= IDLE; else cs <= ns; always @ (*) begin ns = IDLE; case(cs) IDLE: if(tx_bps_en ) ns = START; else ns = IDLE; START: if(tx_bps_vld ) ns = DATA; else ns = START; DATA: if((tx_cnt == 7) && tx_bps_vld) ns = CHECK; else ns = DATA; CHECK: if(tx_bps_vld) ns = STOP; else ns = CHECK; STOP: if(tx_bps_vld) ns = IDLE; else ns = STOP; default:ns = IDLE; endcase end//-------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) tx_cnt <= 4'd0; else if((cs == DATA) && tx_bps_vld) tx_cnt <= tx_cnt + 4'd1; else if(cs == IDLE) tx_cnt <= 4'd0; else tx_cnt <= tx_cnt; //-------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) tx_pin_buf <= 1'b1; else if(cs == START) tx_pin_buf <= 1'b0; else if(cs == DATA) tx_pin_buf <= rd_data[tx_cnt]; else if(cs == CHECK) tx_pin_buf <= 1'b1; else if(cs == STOP) tx_pin_buf <= 1'b1; else tx_pin_buf <= 1'b1; assigntx_pin = tx_pin_buf; assigntx_over = (empty && (cs == STOP) && (ns == IDLE) ) ? 1'b1 : 1'b0; endmodule

【用verilog实现UART协议 以此理解何为接口,协议何为模块化设计】3、接收模块
module rx_data ( input wire sys_clk, input wire sys_rst_n,input wire rx_pin,input wire rx_bps_vld, output reg rx_bps_en,output reg rx_data_vld, output reg [7:0] rx_data); //---------------- parm ------------------------ localparam IDLE= 4'h0; localparam START= 4'h1; localparam DATA= https://www.it610.com/article/4'h2; localparam CHECK= 4'h4; localparam STOP= 4'h8; reg [3:0]cs; reg [3:0]ns; //---------------------------------------- regrx_pin_dly; regrx_pin_dly1; reg [3:0]data_cnt; reg [7:0]rxdata; //---------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) cs <= IDLE; else cs <= ns; always @ (*) begin ns = IDLE; case(cs) //----------------IDLE wait data--------------------- IDLE: if(rx_pin_dly1 & !rx_pin_dly) ns = START; else ns = IDLE; //---------------- start sig ------------------------ START: if(rx_bps_vld && !rx_pin_dly1) ns = DATA; else if(rx_bps_vld && rx_pin_dly1) ns = IDLE; else ns = START; //-----------------data 8bit----------------------- DATA: if(data_cnt == 4'd7) ns = STOP; else ns = DATA; //------------------ no use ---------------------- CHECK: if(rx_bps_vld) ns = STOP; else ns = CHECK; //---------------------------------------- STOP: if(rx_bps_vld) ns = IDLE; else ns = STOP; default:ns = IDLE; endcase end//------------------------ buffer ---------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) begin rx_pin_dly<= 1'b1; rx_pin_dly1 <= 1'b1; end elsebegin rx_pin_dly<= rx_pin; rx_pin_dly1 <= rx_pin_dly; end//---------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) rx_bps_en <= 1'b0; else if((ns == START)&(cs == IDLE)) rx_bps_en <= 1'b1; else if((ns == IDLE)&(cs == STOP)) rx_bps_en <= 1'b0; else rx_bps_en <= rx_bps_en; //---------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) data_cnt <= 4'd0; else if(cs == IDLE) data_cnt <= 4'd0; else if((cs == DATA)&&rx_bps_vld) data_cnt <= data_cnt + 1'b1; else data_cnt <= data_cnt; //---------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n) rxdata <= 8'd0; else if((cs == DATA) && rx_bps_vld) //dly rxdata[data_cnt] <= rx_pin_dly; else if(cs == IDLE) rxdata <= 8'd0; else rxdata <= rxdata; //---------------------------------------------------------- always @ (posedge sys_clk or negedge sys_rst_n) if(!sys_rst_n)begin rx_data_vld <= 1'b0; rx_data<= 8'd0; end else if((cs == STOP) && rx_bps_vld ) begin rx_data_vld <= 1'b1; rx_data<= rxdata; end else begin rx_data_vld <= 1'b0; rx_data<= rx_data; endendmodule


    推荐阅读