FPGA 片内 RAM 读写测试

FPGA 片内 RAM 读写测试

实验原理
Xilinx 在 VIVADO 里为我们已经提供了 RAM 的 IP 核, 我们只需通过 IP 核例化一个 RAM, 根
据 RAM 的读写时序来写入和读取 RAM 中存储的数据。 实验中会通过 VIVADO 集成的在线逻辑
分析仪 ila,我们可以观察 RAM 的读写时序和从 RAM 中读取的数据。不停的写不停的读。

创建 Vivado 工程
在添加 RAM IP 之前先新建一个 ram_test 的工程, 然后在工程中添加 RAM IP,方法如下:
1) 点击下图中 IP Catalog,在右侧弹出的界面中搜索 ram,找到 Block Memory Generator,双击
打开
在这里插入图片描述
2) 将 Component Name 改为 ram_ip,在 Basic 栏目下,将 Memory Type 改为 Simple Dual Prot
RAM,也就是伪双口 RAM。一般来讲"Simple Dual Port RAM"是最常用的,因为它是两个端
口,输入和输出信号独立。
在这里插入图片描述
3) 切换到 Port A Options 栏目下,将 RAM 位宽 Port A Width 改为 16,也就是数据宽度。将
RAM 深度 Port A Depth 改为 512,深度指的是 RAM 里可以存放多少个数据。使能管脚
Enable Port Type 改为 Always Enable。
在这里插入图片描述
4) 切换到 Port B Options 栏目下, 将 RAM 位宽 Port B Width 改为 16,使能管脚 Enable Port
Type 改为 Always Enable,当然也可以 Use ENB Pin,相当于读使能信号。 而 Primitives
Output Register 取消勾选,其功能是在输出数据加上寄存器,可以有效改善时序,但读出
的数据会落后地址两个周期。很多情况下,不使能这项功能,保持数据落后地址一个周
期。
在这里插入图片描述
5) 在 Other Options 栏目中, 这里不像 ROM 那样需要初始化 RAM 的数据,我们可以在程序中
写入,所以配置默认即可,直接点击 OK。
在这里插入图片描述
6) 点击“Generate”生成 RAM IP。
在这里插入图片描述

代码部分
先看第一个代码:

`timescale 1ns / 1ps
module ram(
input i_sysclk,
input i_sysrst
);

reg [8:0] wr_addr;
reg wr_en;
reg [15:0] wr_data;

reg [8:0] rd_addr;
wire [15:0] rd_data;

//write ram
always @(posedge i_sysclk)begin
if(i_sysrst == 1’b1)begin
wr_en <= 1’b0;
end
else if(&wr_addr)begin
wr_en <= 1’b0;
end
else begin
wr_en <=1’b1;
end
end

always @(posedge i_sysclk)begin
if(i_sysrst == 1’b1)begin
wr_addr <= 9’d0;
wr_data <= 16’d0;
end
else if(wr_en == 1’b1)begin
wr_addr <= wr_addr + 1’b1;
wr_data <= wr_data + 1’b1;
end
end

//read ram
always @(posedge i_sysclk)begin
if(i_sysrst == 1’b1)begin
rd_addr <= 9’d0;
end
else if(|wr_addr)begin
rd_addr <= rd_addr + 1’b1;
end
else begin
rd_addr <= 9’d0;
end
end

ram_ip inst_ram_ip (
.clka (i_sysclk), // input wire clka
.wea (wr_en), // input wire [0 : 0] wea
.addra(wr_addr), // input wire [8 : 0] addra
.dina (wr_data), // input wire [15 : 0] dina
.clkb (i_sysclk), // input wire clkb
.addrb(rd_addr), // input wire [8 : 0] addrb
.doutb(rd_data) // output wire [15 : 0] doutb
);

//ILA DEBUG
ILA_RAM_DEBUG
ILA_RAM_DEBUG (
.clk(i_sysclk), // input wire clk

.probe0({
            wr_addr,
            wr_en,
            wr_data,

            rd_addr,
            rd_data
 
 }) // input wire [50:0] probe0

);

endmodule

这代码虽然完成了重复读写的工作,但是有个问题,请看调试结果:
在这里插入图片描述
上图放大后
在这里插入图片描述
看到没,每一个周期完成后,会读到上一个周期中0地址的数据。那是因为当我们正在写0地址的时候,这个时候又同时去读0地址的数据,而这时候的数据还保留的是上一个周期时的数据。
//read ram
always @(posedge i_sysclk)begin
if(i_sysrst == 1’b1)begin
rd_addr <= 9’d0;
end
else if(|wr_addr)begin
rd_addr <= rd_addr + 1’b1;
end
else begin
rd_addr <= 9’d0;
end
end

这段读代码的潜在意思就是当我地址写到511时,读地址就会在下一个时钟周期切换的到0地址,而下一个时钟周期才去写0地址的数据。而在写0地址这个时钟周期,读地址还是保持在读0地址,因此当下一个时钟到来的时,读地址还是会读0地址的数据,这个时候以及后面时钟周期读出来的数据就是正确的。

因此这里需要解决的是当写完一轮地址后,写到511时,读地址在下一个时钟周期保持当前读数的操作。也就是当我在写0地址的时候,你不要来读0地址的数据,而是保持你上一个周期的读地址。

所以读ram的程序这样改:

//read ram
always @(posedge i_sysclk)begin
if(i_sysrst == 1’b1)begin
rd_addr <= 9’d0;
end
else if(wr_addr == 9’d0)begin
rd_addr <= rd_addr;
end
else if(|wr_addr)begin
rd_addr <= rd_addr + 1’b1;
end
else begin
rd_addr <= 9’d0;
end
end

改版后的测试结果:

在这里插入图片描述
不难看出,读出的数据连续。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值