所使用EDA软件:VIVADO2019.1.3
FPGA型号:xc7a35tcsg325-2
看完这篇文章你将收获以下内容:
好的进入正题,今天我来带大家认识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是不同的,其他结构完全一样。
接下来我们分别将两者的LUT6结构放大进行进一步对比。两者的LUT6相同点和不同点如下:
相同点:都具有地址输入线(A1-A6),两个输出口(O5-O6)。
不同点:SLICEM的LUT6具有写地址输入线(WA1-WA8),写数据端(DI1 DI2),写使能端(WE),而SLICEL的LUT6没有。
因此SLICEM的LUT6可以读写存储数据,而SLICEL的LUT6只能读数据不能写。这就是为什么SLICEM的LUT6可以做RAM,而SLICEL的LUT6只能做ROM。
从图1 中我们可以看到,1个SLICEM中有4个LUT6,它们可以单独使用或者组合使用来形成多种形态的DRAM。如:
除此之外这些LUT6还可以配合MUX来使用组成更大深度的DRAM如:
以下是单端口DRAM、双端口DRAM、简单双端口DRAM、四端口DRAM的读写特性(下面图中,所有的的寄存器都是SLICE中四大件(LUT ,MUX,进位链,寄存器)的寄存器,不是DRAM内部结构,有关寄存器的内容在后面章节会讲) :
下面我将通过实例来讲解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
例(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
在实际的应用过程中,我们不可能只用到宽度只有几位的输入,我们不可能例化多个原语来将多个64x1DRAM单元拼在一起,也不太推荐直接用verilog语句定义寄存器的方式去写。此时我们应该学会使用与DRAM有关的IP,来自定义不同深度不同位宽的DRAM。下面我简单介绍一下VIVADO 中 DRAM IP的使用(生成 64x16的简单双端口RAM)。
好的本章节的内容就到这里。记住本章节的DRAM 是 Distributed RAM 不是Dynamic RAM !!!
最后安利下我的公众号,内容会在本周末上线。
谢谢客官大老爷,点个赞再走呗(●'◡'●)