理论知识
fifo(first in first out, 先入先出 ),是一种数据缓冲器,用来实现数据先入先出的读写方式。数据按顺序写入 fifo,先被写入的数据同样在读取的时候先被读出,所以 fifo存储器没有地址线,有一个写端口和一个读端口。
fifo 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,分为 scfifo(同步 fifo)和 dcfifo(异步 fifo)。后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了fifo。
同步fifo-scfifo
scfifo ip核配置
full:写满标志位,有效表示 fifo 已经存储满了,此时禁止再往fifo中写入数据,防止数据溢出丢失。当写入数据量达到fifo设置的最大空间时,时钟上升沿写入最后一个数据同时full拉高;读取数据时随时钟上升沿触发同时拉低。
empty:读空标志位,有效表示 fifo 中已经没有数据了,此时禁止fifo继续再读出数据,否则读出的将是无效数据。写入数据同时拉低;读到最后一个数据同时拉高。
usedw:显示当前fifo中已存数据个数,写第一个数据时就置1,空或满时值为0(满是因为寄存器溢出)。
almost full:几乎满标志信号,我们可以控制fifo快要被写满的时候和full信号的作用一样。
almost empty:几乎空标志信号,我们可以控制fifo快要被读空的时候和empty信号的作用一样。
asynchronous clear:异步复位信号,用于清空fifo。
synchronous clear:同步复位信号,用于清空fifo。
后面三个没有使用
scfifo ip核调用我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证scfifo ip核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化
输入信号有:sys_clk、输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,写请求信号rdreq。输出信号有:读取的数据po_data、空标志信号empty、满标志信号full、指示fifo中存在数据个数的信号usedw。
编写代码
module fifo(input wire sys_clk , input wire [7:0] pi_data ,input wire pi_flag , input wire rdreq , output wire [7:0] po_data , output wire empty , output wire full , output wire [7:0] usedw ); scfifo_256x8 scfifo_256x8_inst( .clock (sys_clk ), .data (pi_data ), .rdreq (rdreq ), .wrreq (pi_flag ), .empty (empty ), .full (full ), .q (po_data ), .usedw (usedw ) ); endmodule
编写testbench
//reg definereg sys_clk ;reg [7:0] pi_data ;reg pi_flag ;reg rdreq ;reg sys_rst_n ;reg [1:0] cnt_baud ; //wire define wire [7:0] po_data ; wire empty ; wire full ; wire [7:0] usedw ; //初始化系统时钟、复位 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #100; sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50mhz always #10 sys_clk = ~sys_clk; //cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_baud <= 2'b0; else if(&cnt_baud == 1'b1) cnt_baud <= 2'b0; else cnt_baud <= cnt_baud + 1'b1; //pi_flag:输入数据有效标志信号,也作为fifo的写请求信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_flag <= 1'b0; else if((cnt_baud == 2'd0) && (rdreq == 1'b0)) pi_flag <= 1'b1; else pi_flag <= 1'b0; //pi_data:输入顶层模块的数据,要写入到fifo中的数据 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_data <= 8'b0; else if((pi_data == 8'd255) && (pi_flag == 1'b1)) pi_data <= 8'b0; else if(pi_flag == 1'b1) pi_data <= pi_data + 1'b1; //rdreq:fifo读请求信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) rdreq <= 1'b0; else if(full == 1'b1) rdreq <= 1'b1; else if(empty == 1'b1) rdreq <= 1'b0; fifo fifo_inst( .sys_clk (sys_clk ), .pi_data (pi_data ), .pi_flag (pi_flag ), .rdreq (rdreq ), .po_data (po_data ), .empty (empty ), .full (full ), .usedw (usedw ) ); endmodule初始化:初始时钟为高电平,复位有效,延迟100ns后复位释放
模拟时钟:每隔10ns翻转,时钟周期20ns,频率50mhz
输入间隔计数器cnt_baud:从0-3计数,复位和溢出归0,其他情况+1,这里溢出判断条件(cnt_baud=11)用的是位与为1
输入有效标志信号pi_flag:也是写请求信号,变化条件是时钟上升沿和复位下降沿。复位有效时归0;计数0且没有读请求时拉高相当于四个时钟周期产生一次;其他情况归0
输入数据pi_data:是要写到fifo中的数据,变化条件是时钟上升沿和复位下降沿。复位有效时归0;输入255且pi_flag有效时归0;其他情况+1,意味着输入数据从0-255循环
读请求rdreq:变化条件是时钟上升沿和复位下降沿。复位有效时归0;full信号拉高时也拉高说明存满该读了;empty拉高时就归0说明读空禁读了
实例化
波形变化
我们选择的是普通模式,读出数据比读使能晚一拍
对比一下先出数据fifo模式的波形,就可以看出延迟不延迟一拍的区别,这里没有演示,实际上只需要在下面这一步时选择先出数据模式即可
异步fifo-scfifo
dcfifo ip核配置命名为dcfifo_256x8to128x16,我们调用的dcfifo是输入256个深度8位宽、输出128个深度16位宽
dscfifo ip核调用我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证dcfifo ip核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化
输入信号 :50mhz写时钟wrclk,输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,25mhz的读时钟rdclk,写请求信号rdreq。 输出信号 :同步于wrclk的空标志信号wrempty,同步于wrclk的满标志信号wrfull,同步于wrclk指示fifo中存在数据个数的信号wrusedw,从fifo中读取的数据po_data,同步于rdclk的fifo空标志信号rdempty,同步于rdclk 的fifo满标志信号rdfull,同步于rdclk指示fifo中存在数据个数的信号rdusedw。
编写代码
module fifo(//同步于fifo写时钟input wire wrclk , input wire [7:0] pi_data , input wire pi_flag , //同步于fifo读时钟 input wire rdclk , input wire rdreq , //同步于fifo写时钟 output wire wrempty , output wire wrfull , output wire [7:0] wrusedw , //同步于fifo读时钟 output wire [15:0] po_data , output wire rdempty , output wire rdfull , output wire [6:0] rdusedw ); dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst ( .data (pi_data), //input [7:0] data .rdclk (rdclk ), //input rdclk .rdreq (rdreq ), //input rdreq .wrclk (wrclk ), //input wrclk .wrreq (pi_flag), //input wrreq .q (po_data), //output [15:0] q .rdempty(rdempty), //output rdempty .rdfull (rdfull ), //output rdfull .rdusedw(rdusedw), //output [6:0] rdusedw .wrempty(wrempty), //output wrempty .wrfull (wrfull ), //output wrfull .wrusedw(wrusedw) //output [7:0] wrusedw ); endmodule编写testbench
`timescale 1ns/1nsmodule tb_fifo();//reg definereg wrclk ;reg [7:0] pi_data ;reg pi_flag ;reg rdclk ;reg rdreq ;reg sys_rst_n ;reg [1:0] cnt_baud ;reg wrfull_reg0 ;reg wrfull_reg1 ;//wire definewire wrempty ;wire wrfull ;wire [7:0] wrusedw ;wire [15:0] po_data ;wire rdempty ;wire rdfull ;wire [6:0] rdusedw ;//初始化时钟、复位initial beginwrclk = 1'b1;rdclk = 1'b1;sys_rst_n <= 1'b0;#100;sys_rst_n <= 1'b1;end//wrclk:模拟fifo的写时钟,每10ns电平翻转一次,周期为20ns,频率为50mhzalways #10 wrclk = ~wrclk;//rdclk:模拟fifo的读时钟,每20ns电平翻转一次,周期为40ns,频率为25mhzalways #20 rdclk = ~rdclk;//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔always@(posedge wrclk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_baud <= 2'b0;else if(&cnt_baud == 1'b1)cnt_baud <= 2'b0;elsecnt_baud <= cnt_baud + 1'b1;//pi_flag:输入数据有效标志信号,也作为fifo的写请求信号always@(posedge wrclk or negedge sys_rst_n)if(sys_rst_n == 1'b0)pi_flag <= 1'b0;else if((cnt_baud == 2'd0) && (rdreq == 1'b0))pi_flag <= 1'b1;elsepi_flag <= 1'b0;//pi_data:输入顶层模块的数据,要写入到fifo中的数据always@(posedge wrclk or negedge sys_rst_n)if(sys_rst_n == 1'b0)pi_data <= 8'b0;else if((pi_data == 8'd255) && (pi_flag == 1'b1))pi_data <= 8'b0;else if(pi_flag == 1'b1) pi_data <= pi_data + 1'b1;//将同步于rdclk时钟的写满标志信号wrfull在rdclk时钟下打两拍always@(posedge rdclk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginwrfull_reg0 <= 1'b0;wrfull_reg1 <= 1'b0;endelsebeginwrfull_reg0 <= wrfull;wrfull_reg1 <= wrfull_reg0;end//rdreq:fifo读请求信号同步于rdclk时钟always@(posedge rdclk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rdreq <= 1'b0;//如果wrfull信号有效就立刻读,则不会看到rd_full信号拉高,//所以此处使用wrfull在rdclk时钟下打两拍后的信号else if(wrfull_reg1 == 1'b1)rdreq <= 1'b1;else if(rdempty == 1'b1)//当fifo中的数据被读空时停止读取fifo中的数据rdreq <= 1'b0; fifo fifo_inst( .wrclk (wrclk ), .pi_data(pi_data), .pi_flag(pi_flag), .rdclk (rdclk ), .rdreq (rdreq ), .wrempty(wrempty), .wrfull (wrfull ), .wrusedw(wrusedw), .po_data(po_data), .rdempty(rdempty), .rdfull (rdfull ), .rdusedw(rdusedw) ); endmodule初始化:和同步fifo的区别在于读写时钟是异步的,需要初始化两个时钟
时钟模拟:写时钟50mhz每隔10ns翻转一次,读时钟25mhz每隔20ns翻转一次
cnt_baud:计数器是基于写时钟的,将变化条件中时钟上升沿改为写时钟的上升沿
pi_flag:输入数据有效标志信号,基于写时钟,同上
pi_data:输入数据,基于写时钟,同上
wrfull:写满标志信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。wrfull在读时钟下打两拍,(always块中的语句是顺序执行的,使用两个寄存器b,c,令b=输入a,c=b,并输出c,那么c相对于a而言波形不会产生变化,只是有两个时钟周期的延迟,这个就叫打拍,使用几个寄存器就是延迟几个周期就是打几拍),这里打两拍的原因是如果wrfull有效立刻就读,就看不到rd_full拉高
rdreq:读请求信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。复位时归0;写满信号打两拍之后的信号拉高就拉高表示写满需要读,读空信号拉高就拉低表示读空不能读
波形变化
可以看到当pi_flag为高且pi_data为255的同时wrfull满标志信号先拉高了,延后一段时间rdfull满标志信号也拉高了,说明fifo的存储空间已经满了。wrfull满标志信号和rdfull满标志信号同步于 不同的时钟 ,所以拉高的时间不同步。
还可以看到wrusedw信号是8位的计数到255,而rdusedw信号是7位的计数到127,因为输入是8btit的,输出是16bit的,8256=16128。wrusedw信号从255变成了0、rdusedw信号从127变成0的原因和scfifo中的情况一样,都是因为数据存储满了,fifo内部的计数器溢出所导致的。
另外可以看出读出的16bit数据,是两次的输入8bit数据拼凑而成,先输入的在低位,后输入的在高位,例如输入00,01,02,03...,两次输入的数据拼凑为01_00,03_02进行输出。
如果不打两拍会发生什么呢?我们来看波形
去耦电容越大越好吗,去耦电容值的选择
双向全双工VoIP对讲音频模块--SIP2402V
欧洲各国突击安装太阳能系统源于FIT即将下调
PCB设计的过孔时需要注意哪些问题
《一步一步学PLC编程》全套书籍的介绍让你学好PLC编程
FPGA学习笔记:FIFO IP核的使用方法
batch normalization时的一些缺陷
变送器工作原理
重型板式给料机操作流程详解
一款漏洞PoC框架图形版
做成功的投资者,拒绝持续亏损,赚的金银钵满
电子设计教程: 电平转换电路
区块链科技下的制造业革命
J-Link连接MCU失败解决办法
女性互联网平台美柚终止创业板IPO 或赴境外上市
英特尔如何推动数据驱动型创新的下一波浪潮?
步入式恒温恒湿试验箱的介绍,它的特点是什么
成功使用RAIN RFID基于持续改进
三星电子新型CMOS传感器量产
电池系统集中的充电管理,有利于解决电动汽车安全问题