前言
在IP核RAM调用及验证(1)中初步使用ip核RAM进行数据写入与输出,用到单端口RAM进行实验。既然提到单端口,则有双端口与之对应,单端口RAM和双端口RAM都是用于存储数据的内存类型,但它们在设计和功能上有一些不同。下面总结二者异同之点:
单端口RAM: 单端口RAM只有一个读写端口,通常是一个读端口和一个写端口的组合。在同一时刻,它只能进行读取或写入操作,而不能同时进行两者。也就是说,在任何给定时刻,你只能读取数据或者写入数据,但不能同时进行。设计相对简单,成本较低。适合于对存取操作要求不高的应用,如缓存、简单的数据存储等。
双端口RAM:双端口RAM有两个独立的读写端口。这两个端口可以同时进行读操作或写操作,而互不干扰。可以同时进行读和写操作,或者在两个不同的端口上进行不同的读写操作。这样可以提高并发处理能力和数据交换效率。设计更复杂,成本较高,因为需要处理两个端口的数据冲突和同步问题。适合于对数据处理和访问速度要求较高的应用,比如高速缓存、双端口数据缓冲等。
两者都用于存储数据,可以进行读取和写入操作。都是使用类似的存储技术,如静态随机存取内存(SRAM)或动态随机存取内存(DRAM)。
单端口RAM适用于较为简单的数据存储需求,而双端口RAM则适用于需要高并发数据访问的场景。选择哪种RAM取决于具体应用的需求和预算。
正文
一、IP核RAM调用及验证(2)
1.项目需求
依照双端口RAM的特点,对数据进行跨时钟域数据信号的处理,实现简单数据加速。
2.技术介绍
双端口ram进行跨时钟域数据信号的处理。
单端口地址信号包含了两种含义:一个是写地址,一个是读地址;双端口是根据单端口地址信号的两种含义,将写地址和读地址独立出来,也就是说利用两个地址信号标志写地址和读地址。双端口ram也可以设置为两个独立的单端口ram进行应用。
控制写入ram的时钟与控制读取ram的时钟频率不同,进行数据处理时就会产生跨时钟域的问题,那么如何进行跨时钟域数据信号的处理?
首先考虑如何控制将数据写入到ram,写地址变量和写数据变量必须在写时钟下保持同步,同时在控制写入时写使能信号必须为高电平,其对应的时序形式为:
在T0到T1时刻之间写地址变量为有效地址,写数据变量为有效数据。在T0时刻之前或者在T1时刻之后,写使能无效,那么写数据变量和写地址变量为无效变量。同理,在读取数据时,读使能,读地址时序关系和写入ram中的时序关系一致。数据与地址的时序关系至少延迟一个读时钟周期。
如果进行跨时钟域时,写入数据和读取数据同时进行,写使能信号和读使能信号一直有效,如果写时钟快,读时钟慢,在ram的存储深度固定的情况下,读取数据时会丢失数据;如果写时钟慢,读时钟快,在ram的存储深度固定的情况下,读取数据时会丢失数据;
根据以上分析,如果数据量大的话,不能够通过读取同时进行对数据进行跨时钟域处理,在不考虑数据量大小时,如何进行跨时钟域处理,将写入的数据完整的读取出来?
一般进行这种跨时钟域数据处理时,需要控制数据先完整写入,然后在控制读取数据。就需要通过控制写使能来进行数据的写入,写使能控制写地址的变化,当将写入的数据完成以后再进行数据的读取,对于读取数据来说,如何确定写入数据完成?通过判断写使能信号有没有拉低。
双端口RAM的调用:
配置为一个读端口,一个写端口。
调用双端口RAM,配置总线宽度8,数据深度256
生成inst.v文件,其他保持默认。
3.顶层架构
通过锁相环产生不同时钟,不同的时钟分别驱动写逻辑模块与读数据模块,两模块产生对应的数据信号传入RAM后,RAM进行对应信号的处理。
4.端口描述
clk | 时钟(50Mhz) |
rst_n | 复位按键(低电平有效 |
[7:0] data | 数据读出 |
二、代码验证
rd_doubleram_ctrl模块:读逻辑模块,读取写使能信号,产生读数据地址与读使能信号。
module rd_doubleram_ctrl(
input clk,
input rst_n,
input wren,//写使能信号
output reg rden,//读使能信号
output reg [7:0] rd_addr//读地址
);
reg wren_r;//当前写使能信号
reg wren_rr;//前一时钟写使能信号
wire neg_flag;
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
begin
wren_r <= 1'b1;
wren_rr <= 1'b1;
end
else
begin
wren_r <= wren;//暂存写使能信号
wren_rr <= wren_r;
end
end
assign neg_flag = ~wren_r & wren_rr;
//当前写使能取反后与前一时钟写使能信号,进行下降沿判断,当出现下降沿时,产生一个时钟周期高电平
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
rden <= 1'b0;
else
if(neg_flag == 1)//写使能拉低时立即开始读
rden <= 1'b1;
else
if(rd_addr == 255)//读完,读使能信号拉低
rden <= 1'b0;
else
rden <= rden;
end
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
rd_addr <= 8'd0;
else
if(rden == 1)
rd_addr <= rd_addr + 8'd1;//地址变换驱动
else
rd_addr <= 8'd0;
end
endmodule
wr_dobuleram_ctrl模块:写逻辑控制模块:读取读使能信号,产生写地址与写数据,写使能信号。
module wr_dobuleram_ctrl(
input clk,
input rst_n,
input rden,//读使能信号
output reg wren,//写使能信号
output reg [7:0] wr_addr,//写地址
output [7:0] wr_data//写数据
);
reg rden_r;
reg rden_rr;
wire neg_flag;//读使能信号下降沿标志信号
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
begin
rden_r <= 1'b0;
rden_rr <= 1'b0;
end
else
begin
rden_r <= rden;//同步
rden_rr <= rden_r;//寄存
end
end
assign neg_flag = ~rden_r & rden_rr;
//当前读使能取反后与前一时钟读使能信号,进行下降沿判断,当出现下降沿时,产生一个时钟周期高电平
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
wren <= 1'b1;
else
if(wr_addr == 255)//写完,写使能信号拉低
wren <= 1'b0;
else
if(neg_flag == 1)//读完,检测到读使能信号下降沿,写使能信号拉高
wren <= 1'b1;
else
wren <= wren;
end
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
wr_addr <= 8'd0;
else
if(wren == 1)
wr_addr <= wr_addr + 8'd1;//写地址变换驱动
else
wr_addr <= 8'd0;
end
assign wr_data = wr_addr + 8'd1;//写数据赋值
endmodule
double_ram_test模块:顶层连线
module double_ram_test(
input clk,
input rst_n,
output [7:0] data
);
wire wr_clk;
wire rd_clk;
wire locked;
wire wren;
wire [7:0] wr_data;
wire [7:0] wr_addr;
wire rden;
wire [7:0] rd_addr;
reg wr_clk_r;
reg rd_clk_r;
wire wr_clk_rr;
wire rd_clk_rr;
always @(posedge wr_clk,negedge locked)
begin
if(locked == 0)
wr_clk_r <= 1'b0;
else
wr_clk_r <= wr_clk;
end
assign wr_clk_rr = (wr_clk_r == 1)?wr_clk:1'b0;
//确保在 locked 信号不稳定时(例如失锁),输出的时钟信号 rd_clk_rr 会被安全地置为 0
//避免错误的时钟信号传递。
always @(posedge rd_clk,negedge locked)
begin
if(locked == 0)
rd_clk_r <= 1'b0;
else
rd_clk_r <= rd_clk;
end
assign rd_clk_rr = (rd_clk_r == 1)?rd_clk:1'b0;
//确保在 locked 信号不稳定时(例如失锁),输出的时钟信号 rd_clk_rr 会被安全地置为 0
//避免错误的时钟信号传递。
my_pll my_pll_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( wr_clk ),
.c1 ( rd_clk ),
.locked ( locked )
);
wr_dobuleram_ctrl wr_dobuleram_ctrl_inst(
.clk(rd_clk_rr),
.rst_n(locked),
.rden(rden),
.wren(wren),
.wr_addr(wr_addr),
.wr_data(wr_data)
);
rd_doubleram_ctrl rd_doubleram_ctrl_inst(
.clk(wr_clk_rr),
.rst_n(locked),
.wren(wren),
.rden(rden),
.rd_addr(rd_addr)
);
double_ram double_ram_inst (
.data ( wr_data ),
.rdaddress ( rd_addr ),
.rdclock ( wr_clk_rr ),
.rden ( rden ),
.wraddress ( wr_addr ),
.wrclock ( rd_clk_rr ),
.wren ( wren ),
.q ( data )
);
endmodule
仿真代码
`timescale 1ns/1ps
module ram_test_tb;
reg clk;
reg rst_n;
wire [7:0] data;
double_ram_test ram_test_inst(
.clk(clk),
.rst_n(rst_n),
.data(data)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
rst_n = 0;
#200
rst_n = 1;
#10000
$stop;
end
endmodule
三、仿真验证
观看仿真波形图,有数据读出。
调用中间信号分析
总体观察,使用快时钟写入数据,慢时钟读出数据,完成跨时钟域数据处理。
在写使能信号拉低后产生一个时钟周期的高电平,进行信号下降沿检测,从而拉高读使能信号
同理在读使能信号拉低后产生一个时钟周期的高电平,进行信号下降沿检测,从而拉高写使能信号