FPGA学习日记-逻辑设计(BRAM、FIFO、DDR)

文章目录

  • 简介
  • FPGA逻辑设计
    • 1.BRAM资源使用
      • 1.BRAM
        • BRAM IP核使用
      • 2.FIFO
        • FIFO IP核使用
        • 手撸FIFO代码
    • 2.常见接口协议
      • 1.DDR
        • DDR3 IP核使用。
        • DDR2 IP核(nexys4)
      • 2.I2C

简介

FPGA主要的几种功能:逻辑设计(接口),协议(总线、通信协议),处理器架构,算法加速实现,从ASIC到FPGA的原型验证。

FPGA逻辑设计

1.BRAM资源使用

1.BRAM

BRAM IP核使用

这个比较简单,配置好单双口,用地址+wr_en写/rd_en读即可。读的时候会延迟1-2clk送出数据,这可以通过ip核控制修改。BRAM IP核本质是使用寄存器堆的概念,使用mem类型来存储数据,因此需要延迟最少1clk。

2.FIFO

fifo是高电平复位!(asic才是异步,低电平复位) 复位之后一段时间40ns内无法读写。

FIFO IP核使用

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论坛
FPGA学习日记-逻辑设计(BRAM、FIFO、DDR)_第1张图片

手撸FIFO代码

参考代码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图。
FPGA学习日记-逻辑设计(BRAM、FIFO、DDR)_第2张图片
其中代码如下: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高两位不相同,其余各位完全相同
FPGA学习日记-逻辑设计(BRAM、FIFO、DDR)_第3张图片
读写指针都是指向下一个要读写的数据位置,所以可以提前判断空满。

例如:读指针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才对)

2.常见接口协议

1.DDR

建议先了解下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原理

DDR3 IP核使用。

了解下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的方式有很多,弄懂其中的道理即可,没有必要看太多的文章(每篇文章讲的都不一样,越看越懵)。

DDR2 IP核(nexys4)

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地址

2.I2C

先放个帖子,(CSDN上太多只能仿真的,或者编码风格不良的)不知道有没有用 :I2C

你可能感兴趣的:(FPGA)