2021-07-22


本工程是通过vivado中的IP的实例化利用多个RAM IP 核 实现数据的串行写入,并行读取。

一, RAM的分类以及原理

  1. single port RAM(单端口RAM)
  2. simple dual port RAM(简单双端口RAM)
  3. 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

文中的不足指出,敬请各位大神指出,我这只能算是抛砖引玉。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值