首先来解释一下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视图来检测连线上是否正确,还可以借助仿真工具,但是我现在仿真工具用的还不熟,就比如这段代码,在板子上运行时正确的,但是我用软件仿真时输出端一直是高电平不变,也不知道为什么。反正要学的东西还很多,这点水平还是远远不够,继续加油吧!