前言
上次学习使用了xilinx的IP核 Clock Wizard,这次我们继续使用IP核----Block Memory Generator(块存储生成器)生成单端口RAM(可读可写)。
一、Block Memory Generator介绍
Vivado 软件自带IP核Block Memory Generator(块存储生成器), 可以配置成 RAM 或者ROM。RAM 是一种随机存取存储器,不仅仅可以存储数据, 同时支持对存储的数据进行修改;而 ROM 是一种只读存储器,在工作时只能读出数据,而不能写入数据。
此外需要注意的是,不管使用IP核配置成 RAM 或者 ROM 使用的资源都是 FPGA 内部的 BRAM。
addra:端口A的地址信号,读或者写数据的地址都是用该信号
clka:端口A的时钟信号
dina:端口A的数据输入端口,可以向RAM写入数据
douta:端口A的数据输出端口,可以从RAM中读出数据,需要注意的是该信号输出需要过一个时钟周期,如果打开了Primitives Output Register这个选项,打开输出总线后的寄存器,就会使得数据输出经过两个时钟周期(一般为了改善时序才打开功能,在这次实验中我们关闭,更好观察信号输出)。
ena:端口A的使能信号,高电平使能,低电平禁止(写入读出都无法操作)。
wea:端口A的写入使能信号,高电平对应的地址写入数据,低电平对应的地址读出数据。
二、使用步骤
1.IP核设置
建立新工程之后,打开IP Catalog,双击Block Memory Generator打开IP核的设置。
Basic页面最关键的设置是Interface Type设置为Native,设置总线接口类型为Native(标准RAM接口总线),Memory Type设置为Single Port RAM (单端口RAM)。
其他设置保持默认就可以。
端口A的设置页面,需要设置的主要是读写的宽度和深度,宽度决定着地址能存储的最大数据是多少,深度决定着能存储的最多数目是多少,在这里我设置的深度是1024个,宽度是18位。
Operating Mode: RAM 读写操作模式设置为No Change,不变模式下读写分开操作,不能同时进行读取和写入。
Port A Optional Output Register:端口 A 输出寄存器选项。此选项关闭,用于更好观察时序。
在Other Option设置页面中最主要是设置RAM的初始值,本次实验不需要,因此默认即可。
最后总结页面展示了消耗的资源。
2.IP核使用
首先我对IP核进行了封装,将IP核的端口一直打开,并且地址在复位的时候等于0其他情况直接赋值。
代码如下:
module rom_ctrl(
input sys_clk,
input sys_rst_n,
input [9:0] rom_addr,
input [17:0] rom_data_in,
input rom_wr_en,
output [17:0] rom_data_out
);
reg [9:0] rom_addra;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rom_addra <= 10'd0;
else
rom_addra <= rom_addr;
end
blk_mem_gen_0 mem_ram (
.clka(sys_clk), // input wire clka
.ena(1'b1), // input wire ena
.wea(rom_wr_en), // input wire [0 : 0] wea
.addra(rom_addra), // input wire [9 : 0] addra
.dina(rom_data_in), // input wire [17 : 0] dina
.douta(rom_data_out) // output wire [17 : 0] douta
);
endmodule
2.测试代码
在这个测试代码中,首先复位一次,然后每5ns时钟信号翻转一次,也就是周期为10ns。
然后在时钟信号的控制下,有限状态机进行状态的迁移,一开始状态机处于2’d0的状态,这个状态将完成数据的写入,地址和输入的数据一起加,直到第512个地址,也就是511的时候,状态转移到2’d1,此时开始读出数据,待到地址自加到511时候,有限状态机重新进入2’d0状态,完成数据写入。
代码如下:
`timescale 1ns / 1ps
module test();
reg sys_clk;
reg sys_rst_n;
reg [9:0] rom_addr;
reg [17:0] rom_data_in;
reg rom_wr_en;
wire [17:0] rom_data_out;
rom_ctrl rom_ctrl(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rom_addr (rom_addr),
.rom_wr_en (rom_wr_en),
.rom_data_in (rom_data_in),
.rom_data_out (rom_data_out)
);
initial begin
sys_rst_n = 0;
sys_clk = 0;
#10
sys_rst_n = 1;
forever begin
sys_clk = ~sys_clk;
#5;
end
end
reg [1:0] state_now;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
state_now <= 2'd0;
rom_addr <= 10'd0;
rom_data_in <= 18'd0;
rom_wr_en <= 1'b0;
end
else
case (state_now)
2'd0: begin
if(rom_addr < 10'd512 - 1'b1) begin
rom_wr_en <= 1'b1;
rom_addr <= rom_addr + 1'b1;
rom_data_in <= rom_data_in + 1'b1;
end
else begin
rom_wr_en <= 1'b0;
rom_addr <= 10'd0;
rom_data_in <= 18'd0;
state_now <= 2'd1;
end
end
2'd1: begin
if(rom_addr < 10'd512 - 1'b1) begin
rom_addr <= rom_addr + 1'b1;
end
else begin
rom_addr <= 10'd0;
state_now <= 2'd0;
end
end
endcase
end
endmodule
该代码比较清晰,因此未写注释。
3.功能仿真波形图
上图是功能仿真之后的波形图,从图中可以看着状态机一直在0和1状态跳转,完成数据的写入和读取,第二张图可以看出当地址为511时候状态发生迁移,读取的时候,地址和读取的数据相差1个时钟周期和预期一致。
总结
以上就是今天要讲的内容,本文简单介绍了Block Memory Generator IP,以及使用IP核做了一个简单的写入读取测试文件。