Vivado verilog 利用N个RAM实现N路数据的串入并出读取
本工程是通过vivado中的IP的实例化利用多个RAM IP 核 实现数据的串行写入,并行读取。
一, RAM的分类以及原理
- single port RAM(单端口RAM)
- simple dual port RAM(简单双端口RAM)
- real dual port RAM(真双端口RAM)
具体的RAM的原理,可以参考以下文章内容。
https://blog.csdn.net/Reborn_Lee/article/details/104367728
本模块采用简单双端口RAM模块进行编写。
二, 数据串并转换
要求输入串行数据,串行写入N路M bit 的RAM中,并将数据通过并行的方式读取出来。是一个串并转换模块。
串并转换的原理可以参考一下文章
https://blog.csdn.net/weixin_43237539/article/details/90943797?depth_1-
在RAM相同的地址位,每一个RAM的写使能端口通过打拍的方式延迟一个时钟周期分别打开,对同一个地址写入不同的数据,实现数据的串行写入,在同一个时钟的控制下,实现数据的并行读取,每一个RAM读使能端口都同时打开读取数据,并将读取的数据暂存在寄存器中,最后将每一个寄存器的数据拼合起来,验证数据。
根据输出验证,数据的读取是正常,除了读取的数据打拍延迟时间特别长以外,红框的数据应该是最多延迟几个时钟周期就会对应,但是为什么数据的读取输出会延迟那么长的时钟周期还有待解决。
如果有大神能够知道原因的话,还敬请告知一下解决的办法,在此谢谢。
三, 使用generate for循环,参数实例化IP核
本工程应用了generate for循环实例化生成IP核,generate for 循环的具体使用可以参考一下链接的内容。
https://blog.csdn.net/weixin_38197667/article/details/90401400
运用参数化的方式,可以生成自己想要的任何数量的IP,方便快捷,可移植性强。
//*************generator for循环实例化 RAM IP核*******************
genvar i;
generate
for(i = 0; i <= DATA_PATH-1; i=i+1)
begin: RAM_inst //例化生成的名字
RAM_U1 i(
.clka (clk_wr ),
.ena (en_wr[i] ),
.wea (we_wr[i] ),
.addra (addr_wr ),
.dina (wr_data ),
.clkb (clk_rd ),
.enb (en_rd[i] ),
.addrb (addr_rd ),
.doutb (rd_data[i] )
);
end
endgenerate
注:因为时钟的原因,读和写使用的同一个时钟信号。
本文存在诸多不足的地方,这也是第一次编写这么完整的工程文件,文中的不足之处,还望大神能够海涵,轻喷。并希望大神能够不吝赐教,指出我的不足,文中的读取时许延迟的问题,如果能有大神知道原因,敬请指导,谢谢。
以下是工程源码,贴出来共同学习。
顶层模块源码
`timescale 1ns / 1ps
//
// Company:
// Engineer: yejun
//
// Create Date: 2021/07/18 13:58:29
// Design Name:
// Module Name: module_RAM
//
module module_RAM
#(
parameter DATA_WIDTH = 8,//RAM的位宽,8位。
parameter DATA_PATH = 4//输入数据的路径个数,也等于RAM的个数。
)
(
input clk ,
input rst_n ,
input [DATA_WIDTH-1:0] data_in , //8位
input data_in_valid ,
input data_rd ,
output [0:DATA_WIDTH*DATA_PATH-1] data_out //32位
);
//PLL 模块例化
wire clk_wr ;//25mhz
wire clk_rd ;//50mhz
wire sys_rst_n ;//PLL输出的locked信号,作为FPGA内部的复位信号,
//低电平复位,高电平正常工作
//**************实例化PLL锁相环**********************//
clk_PLL clk_PLL_inst(
.clk_in1 (clk ) , //输入系统时钟
.reset (reset ) , //时钟复位信号
.clk_wr (clk_wr ) , //输出写时钟25mhz
.clk_rd (clk_rd ) , //输出读时钟50mhz
.locked (locked )
);
//****************RAM读写数据测试模块***************//
RAM_TEST uut_ram_test(
.clk_wr (clk_wr ) , //25mhz 时钟信号
.clk_rd (clk_rd ) , //50mhz时钟信号
.rst_n (sys_rst_n ) , //复位信号,低电平有效
.data_in (data_in ) , //外部数据的输入
.data_in_valid (data_in_valid) ,
.data_rd (data_rd ) ,
.data_out (data_out ) //顶层模块最后的数据输出
);
endmodule
子模块
`timescale 1ns / 1ps
//
// Company:
// Engineer: yejun
//
// Create Date: 2021/07/16 15:53:26
// Design Name:
// Module Name: RAM_TEST
/
module RAM_TEST
#(
parameter DATA_WIDTH = 8,//RAM的位宽,8位。 M位RAM位宽
parameter DATA_PATH = 4,//输入数据的路径个数,也等于RAM的个数。N个RAM并行输出
parameter ADDR_WIDTH = 4,//地址位宽4位
parameter CNT_WIDTH = 2//计数器位宽
//parameter DEPTH = 2**ADDR_WIDTH
)
(
input clk_wr ,//写时钟信号,PLL输出的25mhz时钟
input clk_rd ,//读时钟信号,PLL输出的50mhz时钟
input rst_n ,//RAM_test控制模块的复位信号,低电平有效
input [ DATA_WIDTH-1:0] data_in ,//数据输入,外部激励源的输入数据信号 8位
input data_in_valid ,
input data_rd ,//数据读使能信号
output wire [ 0:DATA_WIDTH*DATA_PATH-1] data_out //顶层模块最后的数据输出 32位
);
//RAM IP 模块
reg [DATA_WIDTH-1:0] wr_data ;//写入数据8 位
wire [DATA_WIDTH-1:0] rd_data [0:DATA_PATH-1];//RAM B端口输出,读出数据 //8位的位宽,4个数据输出端口
reg [DATA_WIDTH-1:0] rd_reg [0:DATA_PATH-1];//将ra_data中的数据暂存在rd_reg中 //8位的位宽,4个数据输出端口
reg en_wr [0:DATA_PATH-1];//四个端口a的使能信号,用于写数据 //4个写使能端口
reg we_wr [0:DATA_PATH-1];//4个ram的写使能信号//4个写使能信号
reg en_rd [DATA_PATH-1:0];//四个端口b的使能信号,用于读数据 //4个读使能端口信号
//产生地址的计时器
reg [ADDR_WIDTH-1:0] addr_wr = 0; //RAM写地址
reg [ADDR_WIDTH-1:0] addr_rd = 0; //RAM读地址
reg [CNT_WIDTH-1:0] cnt = 0;//计时器
//****************计时器产生地址位**********
always@(posedge clk_wr )
begin
if(!rst_n)begin
cnt <= 'd0;
end
else if(cnt == 'd3)begin
cnt <= 'd0;
end
else begin
cnt <= cnt+1;
end
end
//*****************写地址信号控制在0-16***********
always @ (posedge clk_wr ) //写地址信号控制0~15
begin
if (!rst_n) begin
addr_wr <= 0;
end
else if (addr_wr == 16)begin//16
addr_wr <= 0;
end
else if(cnt[1:0] == 3) begin//截位操作,cnt[1:0]==3等价于&cnt[1:0]
addr_wr <= addr_wr + 1'b1 ;
end
end
//***************RAM复位、写使能控制信号****************
integer m;
always@(posedge clk_wr )
begin
if(!rst_n)
begin
for (m = 0; m <= DATA_PATH-1; m = m + 1) begin
en_wr[m] <= 1'b0 ;
we_wr[m] <= 1'b0 ;
end
end
else if(cnt[1:0] == 3 && data_in_valid == 1)begin
en_wr[0] <= 1'b1 ;
we_wr[0] <= 1'b1 ;
wr_data <= data_in ; //第一个RAM的写使能打开,将输入数据写入RAM1中
end
else
begin
en_wr[0] <= 1'b0 ;
we_wr[0] <= 1'b0 ;
end
end
//*************写使能打拍******************
integer j;
always@(posedge clk_wr)
begin
for(j = 1; j <= DATA_PATH-1 ; j = j + 1) begin
en_wr[j] <= en_wr[j-1] ;
we_wr[j] <= we_wr[j-1] ;
end
wr_data <= data_in ;//第二个RAM的写使能打开,将输入数据的低四位写入RAM2中
end
//**************读端口操作*****************
//*************读地址*********************
always @ (posedge clk_wr ) //写地址信号控制0~15
begin
if (!rst_n) begin
addr_rd <= 0;
end
else if (addr_rd == 16) begin //16
addr_rd <= 0;
end
else if(cnt[1:0] == 3) begin //读地址不用控制,每次自加1
addr_rd <= addr_rd +1;
end
end
//*************读使能打开**************
integer n,k,c;
always@(posedge clk_wr )
begin
if(!rst_n) begin
for (n = 0; n <= DATA_PATH-1; n = n + 1) begin
en_rd[n] <= 1'b0 ;
end
end
else if(cnt[1:0] == 3 && data_rd) begin
for (k = 0; k <= DATA_PATH-1; k = k + 1) begin
en_rd[k] <= 1'b1 ;//读使能打开,读取数据
rd_reg[k] <= rd_data[k] ;
end
end
else begin
for (c = 0; c <= DATA_PATH-1; c = c + 1) begin
en_rd[c] <= 1'b0 ;
end
end
end
//*********************拼合输出数据*****************
//assign data_out = {rd_reg[3],rd_reg[2],rd_reg[1],rd_reg[0]};
genvar t;
generate
for(t = 0; t <= DATA_PATH-1; t = t + 1 )
begin
assign data_out[DATA_WIDTH*t:DATA_WIDTH*(t+1)-1] = rd_reg[t] ;//数据拼合
end
endgenerate
//*************generator for循环实例化 RAM IP核*******************
genvar i;
generate
for(i = 0; i <= DATA_PATH-1; i=i+1)
begin: RAM_inst //例化生成的名字
RAM_U1 i(
.clka (clk_wr ),
.ena (en_wr[i] ),
.wea (we_wr[i] ),
.addra (addr_wr ),
.dina (wr_data ),
.clkb (clk_rd ),
.enb (en_rd[i] ),
.addrb (addr_rd ),
.doutb (rd_data[i] )
);
end
endgenerate
endmodule
testbench 测试仿真模块
`timescale 1ns / 1ps
//
// Company:
// Engineer: yejun
//
// Create Date: 2021/07/19 16:40:12
// Design Name:
// Module Name: RAM_TESTBENCH
//
module RAM_TESTBENCH
#(
parameter DATA_WIDTH = 8,//RAM的位宽,8位。
parameter DATA_PATH = 4,//输入数据的路径个数,也等于RAM的个数。
parameter clk_period = 10//时钟周期10ns,100mhz
)
(
);
//*********定义输入端口*****************
reg clk ;
reg rst_n ;
reg [DATA_WIDTH-1:0] data_in ;
reg data_in_valid ;
reg data_rd ;
//*********定义输出端口****************
wire [0:DATA_WIDTH*DATA_PATH-1] data_out ;
//**********例化测试单元***************
module_RAM uut(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.data_in_valid(data_in_valid),
.data_rd(data_rd),
.data_out(data_out)
);
//************输入信号初始化*************
initial begin
rst_n = 0;
data_in = 0;
data_in_valid = 0;
data_rd = 0;
# 100;
data_in_valid = 1;
rst_n = 1;
# 200;
data_rd = 1;
end
initial
clk = 0;
always #(clk_period/2) clk = ! clk;
//*************输入数据的读取****************
integer i; //数组坐标
reg [DATA_WIDTH-1:0] temp[1:300]; //数组形式存储读出的数据
initial begin
$readmemh("E:/Vivado project/ARM_TEST/sin_wave_h.txt", temp); //将txt文件中的数据存储在数组中
i = 0;
repeat(300) begin //重复读取数组中的数据
i = i + 1;
data_in = temp[i];
#40;//循环读取
end
end
endmodule
文中的不足指出,敬请各位大神指出,我这只能算是抛砖引玉。