原创
零基础学FPGA(十二)一步一脚印之基于FIFO的串口发送机设计全流程及常见错误详解
记得在上几篇博客中,有几名网友提出要加进去错误分析这一部分,那我们就从今天这篇文章开始加进去我在消化这段代码的过程中遇到的迷惑,与大家分享。
今天要写的是一段基于FIFO的串口发送机设计,之前也写过串口发送的电路,这次写的与上次的有几分类似。这段代码也是我看过别人写过的之后,消化一下再根据自己的理解写出来的,下面是我写这段代码的全部流程和思路,希望对刚开始接触的朋友来说有一点点的帮助,也希望有经验的朋友给予宝贵的建议。
首先来解释一下FIFO的含义,FIFO就是First Input First Output的缩写,就是先入先出的意思,按照我的理解就是,先进去的数据先出,例如一个数组的高位先进,那么读出来的时候也就高位先出。下面是百度百科的解释。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端是计算机的PCI总线,假设其AD采集的速率为16位 100K SPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
下面我们开始设计。
这次设计我们要设计一个串口发送机,想一下的话,我们要发送数据,总得有一个数据产生模块和数据发送模块吧。好,那么在我们的脑海里就出现了这两个模块。由于我们这次是借用Altera公司提供的IP核FIFO来完成,所以要加入这个模块,这个模块作为一个数据缓冲器,需要我们例化,等会我们按照思路来例化它。
好,模块出来了,我们将这三个模块分别定义为dataoutput块,fifo_ctrl块和uart_ctrl块。现在考虑连线。我个人感觉在设计之前,把要设计的东西在草稿纸上将大体框图画出来,具体到每一根连线,这样根据图来写代码要比直接用脑子构图要方便的多。三个模块,先考虑时钟和复位信号线,三个模块都有,然后,数据产生模块要将产生的数据发给FIFO模块,所以要有数据写入线,我们定义它为wr-datain,数据写入FIFO块后总要输出,这些数据就是我们要发送的数据,所以定义输出数据线tx_data,先不管FIFO,我们再来定义数据发送模块的连线,数据发送总要有个启动信号,所以我们定义变量tx_start,之后,还要有一个输出端给PC机,我们定义这个输出端位rs232,对于FIFO模块的例化过程很简单就不做过多的说明,只把接口说一下,FIFO模块除了时钟,复位信号外,还有数据输入端口,这个端口要和之前的数据产生模块的数据输出端口相连,还有写请求端口,高电平有效,数据发送模块每隔1秒钟产生一个16位的数据,并发送写请求命令给FIFO,还有读请求命令,高电平有效数据发送模块在发送数据时要发送一个读请求给FIFO,从中读取数据后再发送给PC机,还有空信号empty,只要检测到FIFO中有数据,empty就为低电平,我们可用这个信号来启动数据发送模块。这样一来,我们的整体框架就出来了有了这个整体框架,再写代码就容易多了。
下面是RTL视图
下面我们来写代码
按照这个框架,先把接口定义出来,中间的连线用wire型
设计完端口之后我们就来设计底层模块,先设计数据产生模块dataoutput
这个部分主要是产生数据,可用一个分频电路实现每1s发送一次的数据,产生这16位数据的时候,需要16个时钟,每个时钟数据自加1,总体来说比较简单
写完一个模块之后养成好习惯,马上把端口例化
数据产生以后就要进入缓冲器FIFO,由于这段代码我们是调用的,所以只要例化接口就好了,只需要将产生的fifo_ctrl_inst文件中例化好的代码拷贝粘贴就好
最后我们要写数据发送部分
之前已经讲过,数据发送部分还要包括两个子模块,一个是波特率匹配模块,一个是发送模块,既然又包括两个子模块,那么我们还要构建一个框图
按照之前的例子,当FIFO当中有数据时empty就会拉低,我们把它取反后送给发送模块,告诉发送模块准备发送,这样,发送模块就会产生一个波特率计数器启动信号bps_start给波特率匹配模块,波特率匹配模块收到信号后立马开始匹配计数,并产生采集信号,将采集信号传给发送模块,发送模块根据采集信号,将数据一位一位发送出去。知道了这个原理之后,我们构建起这样一个框架
根据这个框图,我们定义端口和线
定义完端口之后,开始写发送模块,用边沿脉冲检测法检测启动信号tx_start信号的上升沿来启动发送部分,波特率配置模块具体代码在前面也文章中有给出,就不在说明,写完之后例化端口,这两个模块作为数据发送模块的子模块,要在数据发送模块下例化
这样一来,我们整个设计就完成了,看上去很简单,但是从我自己实践的角度来说还是有点挑战的,包括中间出现的各种问题,下面就来分享一下我在做这个设计时遇到的问题
1.例化问题
在例化端口时,要注意括号里面的才是本层模块的端口,也就是说在本层模块上面已经定义过的变量,括号外面的才是被调用模块的端口,也在下层模块的顶部被声明,我在写这段程序的时候将二者颠倒了,导致连线不成功,最终是通过查看RTL视图知道了哪根线有问题才修改成功的
2.同一个变量不能在多个always语句中被赋值
我们可能习惯这么写
always @ (posedge clk or negedge rst_n)
if(!rst_n) num <= 1'b0;
else num <= num+1'b1;
那么,num的值在其他always语句中就不允许再被赋值或者清零,我在写的时候在其他always语句中将num 清零了,导致编译不成功
3. 定义变量之前不要出现该变量,即使后面又定义了
例如,我先进性num的运算,之后再定义num,reg [3:0] num,这样写的话虽然编译没有错误,但是在调用modelsim仿真的时候它会出现编译错误,所以为了规范,不要这样写
4. 在边沿脉冲检测的时候,我习惯于检测下降沿,而这里是检测tx_en 的上升沿,所以我在复位清零的时候错误的将两级寄存器赋值为0,实际上在检测上升沿时要对两级寄存器复位时置一,再把最后一级寄存器取反后与上一级相与。
5. 在发送数据部分,由于受到上次写接收部分程序的影响,没有将起始位发送出去,因为在接收部分,是不需要接收起始位的,是从第一位开始,而在发送部分只有先发送起始位才能和上位机握手通信,还有在发送完数据后要发送停止位,其他情况下都发送高电平来阻止通信的进行
6.最后一个问题是最棘手的问题,我找了好大一半天也没发现,最后还是根据源代码找出来的,不过我还是不知道将这两条语句颠倒了对程序有什么影响,只知道颠倒后数据会一直在发送,不会像预设一样,每隔一秒发送一次,至今还是搞不清楚,希望大神指点迷津
总之我觉得语法上的错误到不至于太难,写的多了就不会出错了,关键是逻辑上的错误很隐蔽,也很难发现,可以通过RTL视图来检测连线上是否正确,还可以借助仿真工具,但是我现在仿真工具用的还不熟,就比如这段代码,在板子上运行时正确的,但是我用软件仿真时输出端一直是高电平不变,也不知道为什么。反正要学的东西还很多,这点水平还是远远不够,继续加油吧!
今天就到此为止了,谢谢大家!
用户1870125 2016-2-1 15:54
用户1615506 2015-10-16 12:00
用户1821079 2015-6-24 21:28
用户465147 2015-2-6 16:25
用户1635984 2014-11-23 12:07
用户1532482 2014-9-7 16:17
lebeige_786182725 2014-7-29 08:59
很不错的心得体会,博主加油!
用户1543914 2014-7-19 17:59
用户1747154 2014-7-16 00:25