FPGA从入门到精通(2) - DRAM

本文深入探讨了FPGA中RAM的实现方式,包括单端口、双端口、简单双端口及四端口DRAM的设计原理,并通过实例对比了Verilog代码与原语的资源消耗。此外,还介绍了如何利用SRL实现移位寄存器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  之前我在学习的过程中,在找资料的过程中花费了大量时间,但也找到一些大佬的优质文章,为了减少各位粉丝查找优质资源的时间,在这里开创一个转载个人认为内容比较优质的文章,学习一下大佬阅读、分析手册的思路,提升对FPGA底层、应用的知识。

  如果觉得对你有帮助,可以订阅该专栏,后续会一直转载优质内容。

  以下为大佬文章正文,如有侵权,请通知删除:


  开发软件:VIVADO2019.1.3

  FPGA型号:xc7a35tcsg325-2

  看完这篇文章你将收获以下内容:

  1、理解SLICEL,SLICEM最本质的区别。

  2、理解什么是单端口DRAM,双端口DRAM,简单双端口DRAM,以及四端口DRAM,SRL。

  3、通过对比调用DRAM 原语/IP产生DRAM的结果与直接运用Verilog来产生RAM的结果来加深DRAM的认识。

  4、通过对比调用SRL原语/IP产生DRAM的结果与直接运用Verilog reg来产生RAM的结果来加深DRAM的认识。

  好的进入正题,今天我来带大家认识LUT的另一种形态,DRAM(Distributed RAM,翻译为分布式随机存储器,而不是我们平时看到的动态随机存储器)。在上一节课中,我们讲到LUT6可以用作64bit的ROM(Read Only Memory),ROM里面的内容是只读的,因此它里面的内容在我们将bit文件烧录到FPGA中就已经确定好的了,无法修改。

  有没有方法将LUT6里面的内容修改呢,有在XILINX 7系中,有两种SLICE。分别为SLICEL,和SLICEM。SLICEM 除了具备完整的SLICEL功能之外,它还具有写存储数据功能。结构决定功能,那么SLICEM与SLICEL是结构上存在哪些不同,我们通过图1进行对比一下。我们可以看到,两者只有红框里的LUT6是不同的,其他结构完全一样。

在这里插入图片描述

图1:左边为SLICEM,右边为SLICEL

  接下来我们分别将两者的LUT6结构(图2)放大进行进一步对比。两者的LUT6相同点和不同点如下:

  相同点:都具有地址输入线(A1-A6),两个输出口(O5-O6)。

  不同点:SLICEM的LUT6具有写地址输入线(WA1-WA8),写数据端(DI1 DI2),写使能端(WE),而SLICEL的LUT6没有。

  因此SLICEM的LUT6可以读写存储数据,而SLICEL的LUT6只能读数据不能写。这就是为什么SLICEM的LUT6可以做RAM,而SLICEL的LUT6只能做ROM。

在这里插入图片描述

图2:左为SLICEM的LUT6,右为SLICEL的LUT6

  从图1 中我们可以看到,1个SLICEM中有4个LUT6,它们可以单独使用或者组合使用来形成多种形态的DRAM。如:

    32 x1 或 64x1的单端口DRAM (占用1个LUT6)

    32 x1 或 64x1的双端口DRAM (占用2个LUT6 )

    32 x6 或 64x3的简单双端口DRAM(占用4个LUT6)

    32 x2或 64x1的四端口DRAM (占用4个LUT6)

  除此之外这些LUT6还可以配合MUX来使用组成更大深度的DRAM如:

    128 x 1 的单端口DRAM(占用2个LUT6+1个MUX)

    128 x 1 的双端口DRAM (占用4个LUT6+2个MUX)

    256 x 1 的单端口DRAM (占用4个LUT6+3个MUX)

  以下是单端口DRAM、双端口DRAM、简单双端口DRAM、四端口DRAM的读写特性(下面图中,所有的的寄存器都是SLICE中四大件(LUT ,MUX,进位链,寄存器)的寄存器,不是DRAM内部结构,有关寄存器的内容在后面章节会讲) :

  单端口DRAM:同步读,同步写(均以A[5:0]为输入地址),结构如图3所示。D为数据输入,O为数据输出,WCLK为同步时钟,WE为写使能(当要写数据到DRAM时置高,当要从DRAM中读数据时置低)
在这里插入图片描述

图3:单端口DRAM结构

  双端口DRAM : 一个端口(A[5:0]为地址输入)可同步写,异步读。另一个端口(DPRA[5:0]为输入地址)只能异步读,其结构如图4所示。两个LUT6中存放着相同的数据,其实上面的LUT6就是一个单端DRAM,它的输出(SPO)取决于输入地址A[5:0]。下面的LUT6的不同之处就是它的输入端口A[6:1]连的是DRPA[5:0],因此它的输出取决于地址DPRA[5:0]。

在这里插入图片描述

图4:双端口DRAM结构

  简单双端口DRAM:一个端口(WADDR为地址输入地址)只可同步写,另一端口(RADDR为地址输入)只能异步读。在64x3简单双端口DRAM(图5)中,3个数据输入口DATA[3:1]并行输入,3个数据输出口O[3:1]并行输出。在32x6简单双端口DRAM(图6)中,6个数据输入口DATA[6:1]并行输入,6个数据输出口O[6:1]并行输出。

在这里插入图片描述

图5:64x3简单双端口DRAM

在这里插入图片描述

图6:32x6简单双端口DRAM

  四端口DRAM(图7): 一个端口(ADDRD为地址输入)可同步写,异步读。另外三个端口(ADDRA,ADDRB,ADDRC为输入地址)只能异步读。结构与双端口DRAM相似,4个LUT所存放着着相同的数据,只不过每个端口都可以单独读不同地址的内容。
在这里插入图片描述

图7:四端口DRAM

  128x1单端口DRAM(图8),它由2个64x1单端口DRAM+1个MUX组成,结构与上一章2个LUT5组成1个LUT6类似,不同之处就是这里只有一个输出口O。要注意:这里的MUX是SLICE中四大件(LUT ,MUX,进位链,寄存器)中的MUX,不是LUT6中的MUX。

在这里插入图片描述

图8:128x1单端口DRAM

  32位移位寄存器,SRL32E(图9)。它支持最高32位的移位输出,可以选择普通数据(D)输入,也可以选择由上一级SRLC32E的SHIFTOUT作为输入。

  如果想用SRLC32E做12位移位输出,只需要将A[4:0]设置为5’d12,移位输出结果在SHIFTOUT口输出,与此同时O6也将会输出地址5’d12的结果,相当于一个32x1单端口DRAM(这地址是对于SRLC32E外部接口A[4:0]来说的,对于SRL32内部来说它是将高5位地址A[6:2]设为5’d12)。

  特别注意:SRLC32E只能在同一时钟域使用,不能做跨时钟域打拍使用,因为它内部移位操作不是用32个级联的触发器来做的,而是将上一级的电荷转移到下一级,如果第一级发生出现亚稳态,它将会一直传递到最后一级。

在这里插入图片描述

图9:SRLC32E

  SRL32E级联为64位移位寄存器(图10) ,上一级的SHIFTOUT输出连到下一级的SHIFTIN输入即可。如果要实现它64x1单端口DRAM功能,需要额外增加一个MUX。可以参考128x1单端口DRAM(图8)的情况,这里不加以累赘。

在这里插入图片描述

图10:SRL32级联组成64位移位寄存器

  下面我将通过实例来讲解SLICE中的DRAM和 移位寄存器。

  单端口DRAM,使用verilog 中的reg来写(例1),综合后一共耗费了54个LUT6,64个寄存器,8个MUX_F7,4个MUX_F4,肯用那么多资源去写一个64x1的DRAM十有八九是土豪。当然造成资源使用过多有可能是我写法比较随意,vivado优化不了,在这里不细纠。

//以下是例1
module dram64_1(
    input clk,  //input clk
    input [5:0]a, //input address
    input d,    //input data
    input wr_en,//input write enable
    output o//output 
);
    //这种写法结果和使用DRAM原语一样,但是会耗费很多资源,当然EDA软件机智点也是可以优化成只用一个LUT的
    reg dram64x1 [63:0] ;
    
    always@(clk)begin
        if(wr_en)begin
            dram64x1[a]<=d;
        end
    end
    
    assign o=dram64x1[a];
    
endmodule

  单端口DRAM,使用verilog 中的原语来写(例2),综合后一共使用了1个LUT6,资源利用率十分亲民。

//以下是例2
module dram64_1(
    input clk,  //input clk
    input [5:0]a, //input address
    input d,    //input data
    input wr_en,//input write enable
    output o//output 
);
    RAM64X1S #(
        .INIT (64’h0000000000000000) // Initial contents of RAM
    ) RAM64X1S_inst (
        .O      ( o     ),//1-bit data output
        .A0     ( a[0]  ),//Address[0] input bit
        .A1     ( a[1]  ),//Address[1] input bit
        .A2     ( a[2]  ),//Address[2] input bit
        .A3     ( a[3]  ),//Address[3] input bit
        .A4     ( a[4]  ),//Address[4] input bit
        .A5     ( a[5]  ),//Address[5] input bit
        .D      ( d     ),//1-bit data input
        .WCLK   ( clk   ),//Write clock input
        .WE     ( wren  ) //Write enable input
    );
    
endmodule

  接下来我将对例(1),例(2)进行仿真,由于我们的例(1),例(2)的顶层的输入输出是一样的,所以两者的testbench一模一样,我们往地址0~15依次写入1111_0000_0000_1111。

  图11为例(1)仿真结果,两个红色框中输入d的内容和输出o的内容是一样的。至于蓝色框出现不定态x,是因为我没有将例(1)中的RAM初始化。图12为例(2)的仿真结果,结果与例(1)类似,这里写入时,o的输出为我们预先写入的初始值0。

//以下是例(1),例(2)的仿真测试用例
`timescale 1ns / 1ps
module dram64_1_tb();
    reg clk;
    reg [5:0]a;
    reg d;
    reg wr_en;
    reg [5:0]i;

    wire o;
    dram64_1 dram64_1_inst(.clk(clk),.a(a),.d(d),.wr_en(wr_en),.o(o));

    initial begin
        clk = 1'b1;
        a = 6’h0;
        d = 1'b1;
        wr_en = 1'b1;
        i = 6’h0;
    end

    always begin
        #10 clk=~clk;
    end

    always@(posedge clk)begin
        i<=i+6'b1;
    end

    always@(posedge clk)begin
        if(i<16)begin
            wr_en<=1'b1;
            a<=i;
            case(i)
                0,1,2,3:begin d<=1'b1;end
                4,5,6,7:begin d<=1'b0;end
                8,9,10,11:begin d<=1'b0;end
                11,12,13,14,15:begin d<=1'b1;end
            endcase
        end
        else begin
            wr_en<=1'b0;
            a<=i-16;
        end
    end

endmodule

在这里插入图片描述

图11:例(1)的仿真结果

在这里插入图片描述

图12:例(1)的仿真结果

  例(3)是用SLICEM中的SRLC32E来做成32位移位寄存器,总共使用了1个LUT。它所谓的移位就是将上一级的电荷转移到下一级。如果电荷只有二分之一的高电平,它输出也是二分之一的高电平,这样很容易将逻辑错误传递下去,具体内容我会在有关亚稳态的章节讲。

module shift(
    input clk,
    input ce,
    input shift_in,
    input [4:0] a,
    output Q,
    output shift_out
);
    SRLC32E #(
        .INIT(32’h00000000) // Initial Value of Shift Register
    ) 
    SRLC32E_inst (
        .Q  ( Q         ),//SRL data output
        .Q31( shift_out ),//SRL cascade output pin
        .A  ( a         ),//5-bit shift depth select input
        .CE ( ce        ),//Clock enable input
        .CLK( clk       ),//Clock input
        .D  ( shift_in  ) //SRL data input
    );
endmodule

  例(4)是用SLICE中的reg来做32位移位寄存器(为避免综合成SRLC32E,要在reg前使用(* SHREG_EXTRACT = “no” )语句,否则会被VIVADO优化成用SRLC32E), 它总共使用了9个LUT ,32个移位寄存器,4个MUX_F7。

module shift(
    input clk,
    input ce,
    input shift_in,
    input [4:0] a,
    output Q,
    output shift_out
);
    ( SHREG_EXTRACT = “no” *)reg [31:0] dff;
    always@(posedge clk) begin
        if(ce) begin
            dff[31:0]<={dff[30:0],shift_in};
        end
    end

    assign shift_out=dff[31];
    assign Q=dff[a];
endmodule

  接下俩我们对例(3),例(4)进行仿真。我们在测试用例中每个时钟(周期10ns)上升沿依次写入10101010。。。(记住,第一个数是1)。忽略寄存器未初始化所带来的不定态,正常来说我们会在第32拍得到移位后的结果10101010。。。

  我们可以看到例(3)仿真结果(图13)在310ns时输出第一个1,但是例(4)仿真结果(图14)在330ns时才输出第1个1,那么哪一个才是对的呢?对此我个人理解例(3)的仿真结果是正确的,在0ns的时候我们可以看到shift_in是0,而不是我们的初始值1,因此寄存器已经在0ns时打了1拍,在310ns输出1,刚好打了32拍。

  例(4)与预期不符的原因应该是仿真工具在0ns中没有打拍,而是在10ns时打第一拍(初学者应注意:10ns时的输入应该10ns前已经稳定好的那个数值,不是10ns下面所对应的数值),此时输入时0,所以在打第32拍(320ns)时输出是0。

`timescale 1ns / 1ps
module top_tb();
    reg clk;
    reg ce;
    reg shift_in;
    reg [4:0] a;
    wire Q;
    wire shift_out;

    shift shift_inst(
        .clk        ( clk       ),
        .ce         ( ce        ),
        .shift_in   ( shift_in  ),
        .a          ( a         ),
        .Q          ( Q         ),
        .shift_out  ( shift_out )
    );

    initial begin
        clk=1;ce=1;shift_in=1;a=5’d31;
    end

    always begin
        #5 clk=~clk;
    end

    always@(posedge clk)
        shift_in<=~shift_in;
        
endmodule

在这里插入图片描述

图13:例(3)的仿真输出结果

在这里插入图片描述

图14:例4的仿真结果

  在实际的应用过程中,我们不可能只用到宽度只有几位的输入,我们不可能例化多个原语来将多个64x1DRAM单元拼在一起,也不太推荐直接用verilog语句定义寄存器的方式去写。

  此时我们应该学会使用与DRAM有关的IP,来自定义不同深度不同位宽的DRAM。下面我简单介绍一下VIVADO 中 DRAM IP的使用(生成 64x16的简单双端口RAM)。

  在VIVADO工程下点击打开IP Catalog->搜索RAM->找到Distributed Memory Generator 并点击打开。(图15)
在这里插入图片描述

图15:DRAM IP 生成步骤1

  在memory config ,先给这个IP起个帅气,让你印象深刻的,有代表意义的名字,如 SDP。然后再设置深度为64,宽度16。再将Memory Type设置为Simple dual port。

  如果有需要添加输入输出寄存器的话在Port config中设置,如果要初始化DRAM的值我们在RST&Initialization中设置,这里我们暂时没这需要,直接点OK就行(图16)

在这里插入图片描述

图16:DRAM IP生成步骤2

  期间有OK点OK,然后选择Out of context per IP(Global和Out of context per IP的区别就是一个随整个工程一起编译,另一个是作为一个模块单独编译),点击Generate等待IP编译完成。(图17)
在这里插入图片描述

图17:DRAM IP生成步骤3

  最后在IP SOURCE 中点击SDP->Instantiation Template-> SDP.veo(veo 为verilog,vho为vhdl)打开SDP这个IP的模板,并复制到自己的工程,根据需求自己做相应的修改。(图18)

在这里插入图片描述

图18:生成DRAM IP步骤4

  好的本章节的内容就到这里。记住本章节的DRAM 是 Distributed RAM 不是Dynamic RAM !!!


  如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!

  如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!

"FPGA入门精通PDF"是一本关于Field-Programmable Gate Array(可编程逻辑门阵列)的入门精通指南电子书。FPGA是一种先进的数字电路实现技术,它允许用户根据自己的需要编程配置硬件,从而实现特定的功能。这本电子书提供了系统化的学习路径,帮助读者理解FPGA的基本概念原理,并逐步掌握FPGA设计开发。 该电子书第一部分介绍了FPGA的基本概念,包括FPGA的结构、原理工作原理。读者可以了解FPGA与传统固定功能集成电路的区别,以及其灵活性可重构性的优势。 第二部分重点介绍了FPGA设计开发的关键技术工具。读者将学习如何使用HDL(硬件描述语言)编写FPGA的设计代码,以及如何使用常见的开发工具进行仿真验证。此外,该部分还讲解了FPGA的时序设计、时钟管理、资源配置等关键技术。 第三部分涵盖了FPGA的高级主题,如嵌入式处理器的使用、高级调试技术设计优化策略。读者将进一步了解如何将FPGA与其他外设接口集成,以实现更复杂的应用。 整本电子书通过理论实践相结合的方式来讲解FPGA的相关知识,同时提供了丰富的实例练习,以帮助读者加深对FPGA设计开发的理解。无论是初学者还是有一定经验的设计工程师,都能从中获得实用而深入的知识。 总之,"FPGA入门精通PDF"是一本全面介绍FPGA设计开发的电子书,适合那些想要了解掌握FPGA技术的人士阅读。通过学习该电子书,读者可以逐步提升自己的FPGA设计能力,从入门精通
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值