HDL的RAM与FIFO <===> C的数组与链表

作者:高视 来源:FPGA2嵌入式

我们知道FPGA中通过内部RAM存储数据,而FIFO是由双口RAM衍生而来;同样C中可以通过数组来存储数据,而链表也可由数组衍生来。那么二者是否是相通的,是否有着同样的设计思想?

1. 数据的基本存储(RAM与数组)

在FPGA中通过内部RAM来存储数据可以有2种方式:一种是IP核,另一种是HDL代码。用IP实现RAM可以配置为内部BRAM或者REG来实现,而HDL代码实现的一般是由内部REG实现。使用时,只需在时钟边沿下操作地址、数据和使能等信号,来对对应地址中的数进行读写。

而C中的数据也是用来存储一些变量数据,使用时只需声明后直接使用,当然也可以通过指针指到数组首地址后操作指针来操作数组,对数组的读写实质是对地址中的值进行读写。在这里,你有没有发现:RAM和数据都是通过操作地址中的数来实现读写,只是HDL是直接对底层RAM操作,而C是对内存中的寄存器操作。

它们的结构比较如下图:

对应的代码操作比较如下:

HDL C
wr_en <= 1; data_buf[0] = 5;
wr_addr <= addr0;
wr_data <= 5;

rd_en <= 1; data = data_buf[0];
rd_addr <= addr0;
rd_data <= data;

2. 延伸的数据结构(FIFO与链表)

虽然我们有了基本的数据存储RAM和数组,但是实际应用起来并不是很方便,比如:在FPGA中暂时缓存输入数据到合适时刻输出(SDRAM前端的数据读写缓存),在C中循环输入数据存储的同时处理数据(如串口命令的解析),这些处理就需要我们通过RAM和数组来构建新的数据结构。

当我们在FPGA设计中需要缓存数据时,可以用RAM + 控制写和读数据的逻辑来实现,然后将整个模块封装为一个模块。此时,对于用户来说,只需要关心何时可以写入数据和何时可以读出数据(对应FIFO的各种空满等标志信号),读写操作尽量简单(对应FIFO读写只是简单的使能+数据,而不用关心RAM内部的地址、控制等),缓存数据的长度是多少(对应FIFO深度),同时数据是先进先出,也就是First In First Out,这就是我们常说的FIFO。

在RAM的基础上实现FIFO主要关注的是RAM读写控制逻辑:读写按循环地址来读写的(循环长度就是FIFO对应深度),此时写入和读取数据的位置(内部的2个读写计数器来指定读写此刻位置)、可写入和可读取数据的长度(对应FIFO的各种空满等标志信号),简洁的数据结构如下图。

同样,在C中如果要用数组来存储输入的数据同时也要处理这些数据时,需要和FIFO相似的操作,也就是需要记住输入数据的位置,记为in_pos,同时记住处理后数据的位置,记为do_pos。如定义数组data_buf[n],初始化in_pos=do_pos=0,当输入5个数据后,in_pos=5,do_pos=0,如果处理一次需要3个数据时,判断(in_pos - do_pos)>=3时即处理这些数据后do_pos = do_pos + 3。上述数据存储的移动是循环移动的,也就是到了数组最后一个元素后又回到第一个元素,仔细想想有没有发现这和FIFO的设计是同样的思路,如下图:

上面是通过操作数组中的数据来实现:存储输入的数据同时也要处理这些数据。但在C中,其实有另一种结构链表可以实现同样的操作。首先,定义一个包含2个元素结构体:数据和指向结构体的指针,在这里,数据当然是存储处理的数据,而结构体指针用来指向下一个结构体,这样就可以把N个存储的数据串联起来;然后,再同样用一个指针指向待输入的结构体位置,用另一个指针指向待处理的结构体位置;最后,操作这些结构体中的数据和指针,就能实现和上面一样的功能,当然也能实现更复杂的功能(如复杂的上下左右多级链表)。其中,链表结构体的大致定义使用如下图:

链表结构体的结构图如下图,通过图可以发现:这和上述数组循环存储的设计思路是相通的,各个数据通过结构体指针串联,并通过另外2个结构体指针来实现输入数据的同时处理数据,这对串口协议的解析是非常有用的,因为串口不断接受数据同时解析数据执行操作,只有接受数据字节个数达到协议长度才开始解析并响应。

3. 总结

在FPGA中通过内部通过RAM来存储数据需要操作地址来读写数据,一些应用只需要缓存数据,不关心数据存放在具体地址中,只关心先缓存的数据先输出处理,这是就需要RAM + 读写的控制逻辑来实现数据先进先出的功能并封装为模块方便用户使用,这就是我们常用的FIFO,至于异步FIFO的跨时钟域处理则运用了格雷码计数器单比特翻转的特性。而FIFO的读写控制逻辑的核心思想是2个循环计数器来记住读写的位置,运算后来显示FIFO的各种状态(包括可读写长度、空满标志等)。

在C语言中运用同样的设计思想,可以用数组来存储数据,进而添加2个循环计数器位置指示来实现存储输入的数据同时也要处理这些数据,也可以通过链表结构体来实现,它们的思路都是相通的,而且更加复杂的链表也是在它的基础上延伸的,如复杂链表结构来实现复杂上下左右多级菜单切换。

文章来源:FPGA2嵌入式