FPGA主要的几种功能:逻辑设计(接口),协议(总线、通信协议),处理器架构,算法加速实现,从ASIC到FPGA的原型验证。
这个比较简单,配置好单双口,用地址+wr_en写/rd_en读即可。读的时候会延迟1-2clk送出数据,这可以通过ip核控制修改。BRAM IP核本质是使用寄存器堆的概念,使用mem类型来存储数据,因此需要延迟最少1clk。
fifo是高电平复位!(asic才是异步,低电平复位) 复位之后一段时间40ns内无法读写。
fifo ip核的empty端口要在读时钟有的时候才能正常输出。也就是说,fifo复位后empty初始为高电平,只有在有读时钟的时候,empty端口才能正常输出。否则,如果没有读时钟,即使成功写入数据,empty依然输出高电平。
prog_full 可以比full信号早几个clk拉高,almost_full比full早一个clk拉高。(高电平有效)
关于empty信号问题:(高电平有效)
看IP SPEC默认不设置的话,复位full,empty信号都会拉高一段时间(3clk),复位信号完成同步以后,**读完1次fifo,empty信号才会工作。(对于full来说是写完一次fifo开始工作)。**所以在没有读之前emtpy其实是无效的。
empty是有延时的,如下图所示。具体可以看:xilinx论坛
参考代码1
参考代码2
用格雷码和两级D触发器来消除亚稳态。两级触发器代码如下:
//将读指针rp同步到写时钟域
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{wr2_rp,wr1_rp} <= 0;
else
{wr2_rp,wr1_rp} <= {wr1_rp,rp};
//将写指针wp同步到读时钟域
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{rd2_wp,rd1_wp} <= 0;
else
{rd2_wp,rd1_wp} <= {rd1_wp,wp};
二进制转格雷码:
assign gray_code = (bin_code>>1) ^ bin_code;
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
这里直接给出结论:
判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;
判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;
其中第二篇文章出处: 异步fifo的verilog实现 值得好好学习。下图为异步fifo图。
其中代码如下:assign 得到next信号,然后块语句中得到当前信号,值得玩味。
module fifo
#(
parameter WSIZE = 8;
parameter DSIZE = 32;
)
(
input wr_clk,
input rst,
input wr_en,
input [WSIZE-1 : 0]din,
input rd_clk,
input rd_en,
output [WSIZE-1 : 0]dout,
output reg rempty,
output reg wfull
);
//定义变量
reg [WSIZE-1 :0] mem [DSIZE-1 : 0];
reg [WSIZE-1 : 0] waddr,raddr;
reg [WSIZE : 0] wbin,rbin,wbin_next,rbin_next;
reg [WSIZE : 0] wgray_next,rgray_next;
reg [WSIZE : 0] wp,rp;
reg [WSIZE : 0] wr1_rp,wr2_rp,rd1_wp,rd2_wp;
wire rempty_val,wfull_val;
//输出数据
assign dout = mem[raddr];
//输入数据
always@(posedge wr_clk)
if(wr_en && !wfull)
mem[waddr] <= din;
//1.产生存储实体的读地址raddr; 2.将普通二进制转化为格雷码,并赋给读指针rp
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{rbin,rp} <= 0;
else
{rbin,rp} <= {rbin_next,rgray_next};
assign raddr = rbin[WSIZE-1 : 0];
assign rbin_next = rbin + (rd_en & ~rempty);
assign rgray_next = rbin_next ^ (rbin_next >> 1);
//1.产生存储实体的写地址waddr; 2.将普通二进制转化为格雷码,并赋给写指针wp
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{wbin,wp} <= 0;
else
{wbin,wp} <= {wbin_next,wgray_next};
assign waddr = wbin[WSIZE-1 : 0];
assign wbin_next = wbin + (wr_en & ~wfull);
assign wgray_next = wbin_next ^ (wbin_next >> 1);
//将读指针rp同步到写时钟域
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{wr2_rp,wr1_rp} <= 0;
else
{wr2_rp,wr1_rp} <= {wr1_rp,rp};
//将写指针wp同步到读时钟域
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{rd2_wp,rd1_wp} <= 0;
else
{rd2_wp,rd1_wp} <= {rd1_wp,wp};
//产生读空信号rempty 这个很重要!!!
assign rempty_val = (rd2_wp == rgray_next);
always@(posedge rd_clk or negedge rst_n)
if(rst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
//产生写满信号wfull 这个很重要!!!
assign wfull_val = ({~(wr2_rp[WSIZE : WSIZE-1]),wr2_rp[WSIZE-2 : 0]} == wgray_next);
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
这里分析一下,为什么 判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同
读写指针都是指向下一个要读写的数据位置,所以可以提前判断空满。
例如:读指针01100(8),写指针00100(7)走了一圈后,标志位为1=>10100(15+7);
这时候,写指针追上了读指针,full信号需要拉高。高二位相反,其余位相同。
但是按二进制的判断(只看最高位不同)是不行的,因为格雷码是镜像的,除了最高位不同,次高位也不同。01100(8)和10100(15+7)
写指针00000(0),走了一圈后,11000(15+0),读指针00000(0),这时候写满。
再如:写指针10000(15+1),读指针01000(15),读了一步之后10000(15+1)。两个指针完全相同,所以读空。
写指针0011(2),读指针0001(1),读了一步之后0011(2),两个指针完全相同,读空(在二进制的时候,rp=wp-1才对)
建议先了解下DDR原理:DDR3驱动原理与FPGA实现
DDR中的 突发长度概念:
其实突发就是一次连续读几个clk,一般是1,2,4,8的突发长度,其实和计算机体系结构有点关系,地址+偏移地址寻址(是不是有点感觉)
一个存储阵列称为一个bank,一个内存一般4个bank。
目前内存的读写基本都是连续的,因为与CPU交换的数据量以一个Cache Line(即CPU内Cache的存储单位)的容量为准,一般为64字节。而现有的P-Bank位宽为8字节,那么就要一次连续传输8次,这就涉及到我们也经常能遇到的突发传输的概念。突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度(Burst Lengths,简称BL)。
emmm,还是看原创比较香,DDR3原理
了解下DDR时序:
使用Vivado 2015.4在Nexys4 DDR开发板上实现DDR的读写例程
vivado2018.1 保留了ISE的IP核创建形式(略有不同)
DDR3的简述,IP核的建立和时序
vivado2019.1 和ISE的IP核创建形式(不太一样了)
VIVADO 中mig IP的调用与仿真环境的搭建
选择2500ps,400Mhz是DDR3的工作频率,4:1指这时候芯片主时钟以100Mhz运行。
哎,看了那么多资料(包括csdn,文库介绍,也查找了ddr的ip核手册,还是不会)(找到代码,也很难入手)
最后跟着一个博主:(有点感觉了,对小白友好)DDR3。但是这篇博客并没有实际地使用ddr3。只是给出了控制ddr3的方法。同时引发了我的思考,实际上控制ddr3的方式有很多,弄懂其中的道理即可,没有必要看太多的文章(每篇文章讲的都不一样,越看越懵)。
vivado 中mig使用
nexys4 资料下载地址
在填入ddr的ucf时遇到了一个bug:
ERROR : The port ddr2_ck_n[0] is allocated in the bank 34 where the input ports are allocated.
Enable the Internal Vref to use the Vref as GPIO.
ERROR : The port <b>"ddr2_dq[7]"</b> is the <b>"inout"</b> port.
Enable the Internal Vref to use the Vref pin as GPIO.
查xilinx社区,发现:
对于以800 Mb / s(400 MHz)或更低的速度运行的DDR3 SDRAM接口,用户可以选择内部VREF保存两个I /O引脚或使用外部VREF。包含DDR3接口输入引脚(DQ / DQS)的存储体需要VREF。
7系列MIG工具在FPGA选项屏幕上包括一个内部VREF选项。选择此选项可正确设置UCF约束以启用内部Vref。
回退mig设置,才发现,有个 Internal VREF 的选项没有打勾。勾上后一切ok!
这里提供 xilinx地址
先放个帖子,(CSDN上太多只能仿真的,或者编码风格不良的)不知道有没有用 :I2C