正文
学习使用Xilinx FPGA的双端口RAM,其框图如图所示。
IP核的配置
BRAM的配置按照寻求配置即可,唯一需要注意的地方就是输出寄存器的配置,这里,A端口都不选择,B端口都选择,对比查看两者的区别。
整体信息如下图所示
- A端口具有使能信号,输入数据位宽为8位
- B端口一直有使能信号,数据位宽为16位
- A端口的读取Latency为1个时钟,B端口的读取Latency为3个时钟,原因在于B端口多了两个输出寄存器。
仿真验证
仿真需要验证一下几个问题:
- 读写时序如何
- 使能对读写的影响
- 不同位宽之间如何转换
数据写入
其写入时序可参考数据手册
写入ram的数据来源于提前生成的txt文件,先将该文件读取到一个内存中,在将其写入BRAM
integer index;
reg [7:0] data_in_int[0:511];
initial begin
index = 0;
$readmemh("../verilog/sim/data_in.txt",data_in_int);
$stop;
end
读写RAM通过一个task完成
task wr_memory;
input [8:0] start_addr;//写起始地址
input [8:0] len;//写数据长度
input wr_en;//1-->write,0-->read
input [8:0] en_nclk;//N*clk
begin
//a 端口 使能
ena = 1;
for(index=start_addr;index<(len + start_addr) ;index=index+1'b1)begin
if(index-start_addr > en_nclk)
ena = 0;
@(posedge clka);
addra = index;
wea = wr_en;
if(wr_en == 1)
dina = data_in_int[index][7:0];
else
dina = 8'hff;
end
@(posedge clka);
wea = 0;
end
endtask
//从0地址开始写16个字节数据,使能持续16个clk,虽然使能持续17个,但是数据长度只有16个
wr_memory(0,16,1,17);
写入16个字节完成后,通过modesim仿真,可查看写入内存的数据,和txt中的数据完全一致
读取数据
读取数据和写入数据一样,只是将写使能信号wea拉低即可。
//从8地址开始读4个字节数据
wr_memory(8,4,0,4);
读取数据仿真如下图所示,可以看到,在给出地址时,数据并非立马出现,而是相对于地址晚了一个时钟,这便是读取Latency,这里A端口为1个CLK。
使能信号
再看一下使能信号对写入数据的影响
wr_memory(16,8,1,4);
#(PERIOD*10);
wr_memory(2,4,0,2);
仿真中,向16地址写入8个字节数据,其中,使能信号持续8个时钟,写使能信号持续4个时钟。
可以看到,从地址16(0x10)开始,只写入了四个数据。
读取时序如下图所示,使能信号持续2个时钟。
可以看到,两个时钟后,使能为低,地址改变,数据也不会变化
对于使能信号,数据手册中这么解释。
也就是说,使能的优先级最高,如果没给使能信号,那么读、写、复位都将不会生效。
位宽转换
- 本仿真中,A端口数据位宽为8位,B端口位宽为16位,那么8位到16位的字节序是怎样转换的?
需要注意一点的是,对于A端口来说,其写入20个数据,相应的地址从0~19
但是对于B端口来说,其位宽是A的两倍,因此对B来说,只有10个数据,那么其地址范围为0~9
这也是为什么B端口的地址位宽比A少一位的原因。
task random_read_b;
input [8:0] start_addr;
input [8:0] end_addr;
input [8:0] rcnt;
for(index = 0;index < rcnt ; index = index + 1'b1) begin
@(posedge clkb);
addrb = {$random} % (end_addr - start_addr);
end
endtask
//一共写入20个字节数据,换算成16位只有10个
random_read_b(0,9,4);
仿真中,随机变换B端口地址,从时序中可以得到以下信息:
-
读取数据相对于地址延迟3个CLK,符合IP核中的Latency为3个时钟。
-
读取数据时,低字节在低地址,高字节在高地址,典型的小端模式。
-
B端口的地址,可以等效为 A端口地址右移一位。以仿真为例,B端口读取地址为0x08,其对于数据为A端口的0x10的数据。
具体关系如下伪代码:
wire [7:0] addra; wire [6:0] addrb; addra = addrb << 1; addrb = addra >>1; //或者 addra = {addrb[6:0],1'b0}; addrb = addra[7:1];
关于地址映射关系,在手册中也有体现。
总结一句话就是,低字节在低地址。
附录
软件版本
modelsim 使用10.4版本。
ISE为14.7版本。
参考资料
PG058 - LogiCORE IP Block Memory Generator
仿真代码
module tb_dpram;
// mem Parameters
parameter PERIOD = 10;
// mem Inputs
reg clka = 0 ;
reg ena = 0 ;
reg [0 : 0] wea = 0 ;
reg [11 : 0] addra = 0 ;
reg [7 : 0] dina = 0 ;
reg clkb = 0 ;
reg [0 : 0] web = 0 ;
reg [10 : 0] addrb = 0 ;
reg [15 : 0] dinb = 0 ;
// mem Outputs
wire [7 : 0] douta ;
wire [15 : 0] doutb ;
reg rst_n = 0 ;
initial
begin
forever #(PERIOD) clka=~clka;
end
initial
begin
#(PERIOD/2);
forever #(PERIOD/2) clkb=~clkb;
end
initial
begin
#(PERIOD*20) rst_n = 1;
end
//a port write adn read
//b port only read
mem u_mem (
.clka ( clka ),
.ena ( ena ),
.wea ( wea [0 : 0] ),
.addra ( addra [11 : 0] ),
.dina ( dina [7 : 0] ),
.clkb ( clkb ),
.web ( 'b0 ),
.addrb ( addrb [10 : 0] ),
.dinb ( 'd0 ),
.douta ( douta [7 : 0] ),
.doutb ( doutb [15 : 0] )
);
integer index;
reg [7:0] data_in_int[0:511];
initial begin
index = 0;
$readmemh("../verilog/sim/data_in.txt",data_in_int);
$stop;
end
task wr_memory;
input [8:0] start_addr;//写起始地址
input [8:0] len;//写数据长度
input wr_en;//1-->write,0-->read
input [8:0] en_nclk;//N*clk
begin
//a 端口 使能
ena = 1;
for(index=start_addr;index<(len + start_addr) ;index=index+1'b1)begin
if(index-start_addr > en_nclk)
ena = 0;
@(posedge clka);
addra = index;
wea = wr_en;
if(wr_en == 1)
dina = data_in_int[index][7:0];
else
dina = 8'hff;
end
@(posedge clka);
wea = 0;
end
endtask
task random_read_b;
input [8:0] start_addr;
input [8:0] end_addr;
input [8:0] rcnt;
for(index = 0;index < rcnt ; index = index + 1'b1) begin
@(posedge clkb);
addrb = {$random} % (end_addr - start_addr);
end
endtask
initial
begin
@(posedge rst_n);
#(PERIOD*10);
//从0地址开始写16个字节数据,使能持续16个clk,虽然使能持续17个,但是数据长度只有16个
wr_memory(0,16,1,17);
//从8地址开始读4个字节数据
wr_memory(8,4,0,4);
wr_memory(16,8,1,4);
#(PERIOD*10);
wr_memory(2,4,0,2);
#(PERIOD*10);
//一共写入20个字节数据,换算成16位只有10个
random_read_b(0,9,4);
#(PERIOD*10);
$stop;
end
endmodule