FPGA中简单双端口RAM 跨时钟域数据传输
因为在我们系统中,各个模块之间工作的时钟频率不一定一样,所以很多时候牵扯跨时钟域的数据传输,由快时钟域到慢时钟域,由慢时钟域到快时钟域,低位宽到高位宽等,例如采样系统时钟频率10MHz,数据处理系统时钟频率50MHz,此时就需要进行跨时钟域的数据传输。模式选择普通模式(Native),双端口简单RAM(Simple Dual Port RAM)数据位宽设置为16位宽,数据深度为2048。简单双端口
·
一,引言
因为在我们系统中,各个模块之间工作的时钟频率不一定一样,所以很多时候牵扯跨时钟域的数据传输,由快时钟域到慢时钟域,由慢时钟域到快时钟域,低位宽到高位宽等,例如采样系统时钟频率10MHz,数据处理系统时钟频率50MHz,此时就需要进行跨时钟域的数据传输。
二,简单双端口RAM IP核简介
如下图

简单双端口RAM的a端口为只写端口,b端口为只读端口。
各主要端口定义如下:
简单双端口RAM的A端口为只写端口,B端口为只读端口。
- DINA:数据输入端口,用于向 A 端口输入待写入 RAM 的数据。
- ADDRA:数据地址写入端口,指定 A 端口数据写入 RAM 的存储单元地址。
- WEA:写入使能信号,控制 A 端口是否允许在时钟边沿将数据写入 RAM。
- ENA:A 端口使能信号,控制 A 端口整体是否可进行写入操作。
- CLKA:数据写入时钟,为 A 端口数据写入操作提供时序基准。
- ADDRB:读数据端口,指定 B 端口从 RAM 读取数据的存储单元地址。
- ENB:B 端口使能信号,控制 B 端口是否可进行读取操作。
- CLKB:数据读出时钟,为 B 端口数据读取操作提供时序基准。
- DOUTB:数据输出端口,用于输出 B 端口从 RAM 读取的数据。
三、系统框图
本次实际项目为,adc采样模块以10.24MHz的速率采样,RAM模块以10.24MHz的速率写入,再以50MHz的速率读出(后面的模块速率为50MHz)


四、IP核详细配置




模式选择普通模式(Native),双端口简单RAM(Simple Dual Port RAM)数据位宽设置为16位宽,数据深度为2048。
五、详细代码
1.top文件
`timescale 1ns / 1ps
module top (
input wire sys_clk,
input wire sys_rst_n,
input wire [15:0] ad_data,
input wire start,
output wire AD_clk
);
ADC_sample u_ADC_sample
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.AD_clk (AD_clk),
.locked () //锁定信号
);
wire ADC_RAM_last;
wire [15:0] doutb;
ADC_RAM u_ADC_RAM
(
.ADC_RAM_clka (AD_clk), // 写操作时钟
.sys_clk (sys_clk), // 读操作时钟
.sys_rst_n (sys_rst_n), // 系统复位
.AD_data (ad_data), // ADC 输入数据
.start (start),
.doutb (doutb), // RAM 输出数据
.ADC_RAM_last (ADC_RAM_last) // 读地址到达最后一个标志
);
endmodule
2.ADC_sample.v
`timescale 1ns / 1ps
module ADC_sample
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire AD_clk ,
output wire locked //锁定信号
);
AD_CLK u_AD_CLK
(
// Clock out ports
.clk_out1(AD_clk), // output clk_out1
// Status and control signals
.reset(~sys_rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(sys_clk) // input clk_in1
);
endmodule
3.ADC_RAM.v
`timescale 1ns / 1ps
module ADC_RAM
(
input wire ADC_RAM_clka , // 写操作时钟
input wire sys_clk , // 读操作时钟
input wire sys_rst_n , // 系统复位
input wire [15:0] AD_data , // ADC 输入数据
input wire start , // 开始信号
output wire [15:0] doutb , // RAM 输出数据
output reg ADC_RAM_last // 读地址到达最后一个标志
);
reg ena; //a端口使能
reg [0:0] wea; //写使能
reg enb; //b端口使能(读使能)
reg [10:0] addra;
reg rd_flag; //读标记
wire rd_flag_clkb; //读标记
reg [15:0] dina;
reg [10:0] addrb;
(* keep = "true" *) reg start_meta; //(* keep = "true" *) 防止信号被优化
(* keep = "true" *) reg start_sync;
always @(posedge ADC_RAM_clka or negedge sys_rst_n) begin
if (!sys_rst_n) begin
start_meta <= 1'b0;
start_sync <= 1'b0;
end else begin
start_meta <= start; // 第一级采样
start_sync <= start_meta; // 第二级稳定输出
end
end
wire start_pos_edge = (!start_sync) && start_meta; // 上升沿检测
reg wr_started;
always @(posedge ADC_RAM_clka or negedge sys_rst_n) begin
if (!sys_rst_n) begin
addra <= 11'd0;
rd_flag <= 1'b0;
wr_started <= 1'b0;
end else begin
if (start_pos_edge && !wr_started) begin
// 第一次检测到 rd_flag_clkb 拉高,启动读地址计数
addra <= 11'd0; // 或者从 0 开始
wr_started <= 1'b1;
rd_flag <= 1'b0;
end else if (wr_started) begin
if (addra == 11'd2047) begin
addra <= 11'd0;
rd_flag <= 1'b1;
wr_started <= 1'b0;
end else begin
addra <= addra + 1'b1;
rd_flag <= 1'b0;
end
end else begin
addra <= 11'd0;
rd_flag <= 1'b0;
end
end
end
////addra a端口地址跟新逻辑
//always @(posedge ADC_RAM_clka or negedge sys_rst_n)
//begin
// if (!sys_rst_n)
// begin
// addra <= 11'd0;
// rd_flag <= 1'b0;
// end
// else if(addra == 11'd2047)
// begin
// addra <= 11'd0;
// rd_flag <= 1'b1;
// end
// else
// begin
// addra <= addra + 1'b1;
// rd_flag <= 1'b0;
// end
//end
//a端口信号使能
always @(posedge ADC_RAM_clka or negedge sys_rst_n)
if (!sys_rst_n)
ena <= 1'b0;
// else if(addra < 11'd2047) ///注意
// ena <= 1'b1;
else
ena <= (wr_started && addra < 11'd2047);
//写入信号使能
always @(posedge ADC_RAM_clka or negedge sys_rst_n)
if (!sys_rst_n)
wea <= 1'b0;
else if(addra < 11'd2047) //注意
wea <= 1'b1;
else
wea <= 1'b0;
//写入数据
always @(posedge ADC_RAM_clka or negedge sys_rst_n)
if (!sys_rst_n)
dina <= 15'd0;
else if(addra < 11'd2047&& ena == 1)
dina <= AD_data;
else
dina <= 15'd0;
//读标记信号(在clka时钟下)
always @(posedge ADC_RAM_clka or negedge sys_rst_n)
if (!sys_rst_n)
rd_flag <= 1'b0;
else if(addra == 11'd2046)
rd_flag <= 1'b1;
else
rd_flag <= 1'b0;
reg rd_flag_meta, rd_flag_sync;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rd_flag_meta <= 1'b0;
rd_flag_sync <= 1'b0;
end else begin
rd_flag_meta <= rd_flag; // 第一级采样
rd_flag_sync <= rd_flag_meta; // 第二级稳定输出
end
end
assign rd_flag_clkb = rd_flag_sync;
//addrb b端口地址跟新逻辑
reg rd_started;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
addrb <= 11'd0;
ADC_RAM_last <= 1'b0;
rd_started <= 1'b0;
end else begin
if (rd_flag_clkb && !rd_started) begin
// 第一次检测到 rd_flag_clkb 拉高,启动读地址计数
addrb <= 11'd0; // 或者从 0 开始
rd_started <= 1'b1;
ADC_RAM_last <= 1'b0;
end else if (rd_started) begin
if (addrb == 11'd2047) begin
addrb <= 11'd0;
ADC_RAM_last <= 1'b1;
rd_started <= 1'b0;
end else begin
addrb <= addrb + 1'b1;
ADC_RAM_last <= 1'b0;
end
end else begin
addrb <= 11'd0;
ADC_RAM_last <= 1'b0;
end
end
end
////b端口信号使能
//always @(posedge sys_clk or negedge sys_rst_n)
// if (!sys_rst_n)
// enb <= 1'b0;
// else if(addrb <= 11'd2047)
// enb <= 1'b1;
// else
// enb <= 1'b0;
//reg enb_enable;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
enb <= 1'b0;
end else begin
if (rd_flag_clkb)
enb <= 1'b1;
else if (ADC_RAM_last || addrb > 11'd2047)
enb <= 1'b0;
else begin
enb <= enb; // 保持状态
end
end
end
adc_RAM_16x2048 u_adc_RAM_16x2048 (
.clka(ADC_RAM_clka), // input wire clka
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [10 : 0] addra
.dina(dina), // input wire [15 : 0] dina
.clkb(sys_clk), // input wire clkb
.enb(enb), // input wire enb
.addrb(addrb), // input wire [10 : 0] addrb
.doutb(doutb) // output wire [15 : 0] doutb
);
endmodule
4.dds.v
`timescale 1ns / 1ps
// 定义时间单位:1ns 为仿真精度的基本单位,1ps 为时间分辨率
module dds(
// 模块定义:DDS(直接数字频率合成器)模块
input i_clk, // 输入时钟信号
input i_rst, // 输入复位信号(高电平有效)
output signed[15:0]o_sin, // 输出正弦波数据(16位有符号数)
output signed[15:0]o_cos // 输出余弦波数据(16位有符号数)
);
reg [31:0]addr;
// 定义一个32位寄存器变量 addr,用于控制相位累加器的频率调谐字(Frequency Tuning Word)
always @(posedge i_clk or posedge i_rst)
// 在时钟上升沿或复位上升沿触发
begin
if(i_rst)
begin
addr <= 32'd0;
// 如果复位有效,将 addr 清零
end
else
begin
addr <= 32'd250000;
// 否则,将 addr 设置为固定值 50000,表示设定的频率参数
end
end
wire[31:0]m_axis_data_tdata;
// 定义一个32位线网型信号,用于接收 DDS IP 核输出的数据(包含 sin 和 cos)
// 实例化 Xilinx DDS IP 核:dds_compiler_0
dds_compiler_0 dds_compiler_0(
.aclk(i_clk), // 输入时钟
.aresetn(~i_rst), // 异步复位,低电平有效(取反后变为低电平复位)
.s_axis_config_tvalid(1'b1), // 配置通道始终有效
.s_axis_config_tdata(addr), // 频率调节字输入
.m_axis_data_tvalid(), // 输出数据有效标志(未使用)
.m_axis_data_tdata(m_axis_data_tdata), // 输出数据总线(32位,包含 sin 和 cos)
.m_axis_phase_tvalid(), // 相位输出有效标志(未使用)
.m_axis_phase_tdata() // 相位输出数据(未使用)
);
assign o_sin = m_axis_data_tdata[31:16];
// 将输出数据的高16位赋给 o_sin,表示正弦波幅值
assign o_cos = m_axis_data_tdata[15:0];
// 将输出数据的低16位赋给 o_cos,表示余弦波幅值
endmodule
// 模块结束
5.tb_top(仿真文件)
`timescale 1ns / 1ps
module tb_top();
reg sys_clk ;
reg sys_rst_n ;
wire [15:0] sin ;
wire [15:0] ad_data;
reg start;
assign ad_data = sin[15:0];
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
start = 1'b0;
#200
sys_rst_n <= 1'b1;
#20
start = 1'b1;
#5000
start = 1'b0;
end
always #10 sys_clk = ~sys_clk ; //50MHz系统时钟
dds dds_inst
(
// 模块定义:DDS(直接数字频率合成器)模块
.i_clk(sys_clk), // 输入时钟信号
.i_rst(~sys_rst_n), // 输入复位信号(高电平有效)
.o_sin(sin), // 输出正弦波数据(16位有符号数)
.o_cos() // 输出余弦波数据(16位有符号数)
);
top top_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.ad_data (ad_data),
.start (start),
.AD_clk ()
);
endmodule
六、仿真结果

如上图所示,2048个数据从10.24MHz的时钟域成功传输到50MHz的时钟域。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)