欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用Verilog实现RAM的构造并读写数据

程序员文章站 2022-07-09 12:48:52
...

目的

        1模拟实现一个宽度为32,深度为256的内存空间,先向内存空间写一批数据,再读出这批数据,并比较数据是否正确;
        2完成ram的实现代码和tb_ram的仿真测试代码;

说明

提供输入: clk_i 时钟信号,100MHz;
       rst_i 复位信号,高电平复位;
       wr_en_i 写使能,表示data_io的数据有效;
       rd_en_i 读使能,表示要求读出内存中的数据;
       addr_i 地址信号表示要访问的数据所在地址;
要求 输出: data_io 当wr_en_i有效时,表示为写入到内存的数据;当rd_en_i有效时,表示要求读出在addr_i的内存数据;


使用Verilog实现RAM的构造并读写数据

实现

1 top模块
只需要定义好两个子模块之间的连线即可,不做过多描述。Verilog描述如下:

//tb_top
module tb_top;
  wire          clk;
  wire          rst;
  wire          wr_en;
  wire          rd_en;
  wire[7:0]     addr;
  wire[31:0]    data;
  ram inst_ram(
      .clk_i(clk),
      .rst_i(rst),
      .wr_en_i(wr_en),
      .rd_en_i(rd_en),
      .addr_i(addr),
      .data_io(data)
  );
  tb_ram inst_tb_ram(
      .clk_o(clk),
      .rst_o(rst),
      .wr_en_o(wr_en),
      .rd_en_o(rd_en),
      .addr_o(addr),
      .data_io(data)
  );

Endmodule

2 ram模块
使用双向口的话需要利用三态门进行输入输出的控制。使用条件操作符实现三态门的构造。在时钟上升沿,若写信号有效,则将当前地址线对应存储器的空间存入当前data_io上的数据;若读信号有效,则将地址线对应存储器空间的数据输出至data_io,读写无效时为高阻态。编写代码如下,部分说明包含在注释中:

//ram.v
module ram(
    input                   clk_i,
    input                   rst_i,
    input                   wr_en_i,
    input                   rd_en_i,
    input [7:0]             addr_i,
    inout [31:0]            data_io
);

    reg [31:0]          bram[255:0];    
    integer          i;   
    reg [31:0]       data;
//add implementation code here 
    always @(posedge clk_i or posedge rst_i)
    begin
       if (rst_i)   
         begin
           for(i=0;i<=255;i=i+1) //reset, 按字操作
           bram[i] <= 32'b0;
         end
       else if (wr_en_i) begin
            bram[addr_i] <= data_io;
       end
       else if (rd_en_i) begin
            data <= bram[addr_i];
       end
       else begin
        data <= 32'bz;      //读写均无效时,为高阻态。若不加此句,时序会出现问题
       end
    end

    assign data_io = rd_en_i? data : 32'bz; //三态门实现
endmodule

3 tb_ram模块
根据设计的ram,需要激励大致要遵循以下几个原则:
(1)高电平复位清空数据
(2)先给地址和数据,再给读写信号
(3)同样采用三态门结构对应与ram相连的inout线(inout端口不能单独存在)
(4)采用task简化激励组合
Verilog描述如下:

`timescale 1ns / 1ps   //1 period of clk is 10ns
module tb_ram (
    output reg          clk_o,
    output reg          rst_o,
    output reg          wr_en_o,
    output reg          rd_en_o,
    output reg [7:0]    addr_o,
    inout      [31:0]   data_io
  );
    reg[31:0] WriteRAM;  
//三态门的需要不能直接输出至data_io,使用WriteRAM实现缓存    
    initial begin
      $monitor($time,,,    //监视data_io数据线
      "Data = %d", data_io);
      WriteRAM= 0;
      clk_o   = 0;
      wr_en_o = 0;
      rd_en_o = 0;
      addr_o  = 0;
      rst_o   = 1;
      #20 rst_o = 0;
      //为方便查看,使写入的数据为地址的5倍
      write(50,250);
      write(100,500);  //地址100,写入500,下同
      write(200,1000); 
      write (250,1250);
      //读取数据
      read(50);
      read(100);
      read(200);
      read(250);

    end

    assign data_io = wr_en_o ? WriteRAM : 32'bz;

    always begin
      #5 clk_o = ~clk_o;    //提供100MHz时钟
    end

    //0 < addr <255, 0 < WriteRAM <2^32 - 1
    task write(
   input[7:0]       x_i,
   input[31:0]      y_i
   );
   begin
     #100;                  //拉开每次读写之间的间隔,简化initial内容
     @(posedge clk_o);      //时钟控制
     addr_o = x_i;
     WriteRAM = y_i;
     #1                    //先给出地址和数据,再使能
     wr_en_o = 1'b1;
     @(posedge clk_o);
     #50                   //拉长有效时间便于波形显示
     wr_en_o = 1'b0;
   end
   endtask

   task read(             //读操作只需给出地址参数
   input[7:0]       x_i
   );
    begin
     #100;
     @(posedge clk_o);
     addr_o = x_i;
     #1
     rd_en_o = 1'b1;
     @(posedge clk_o);
     #50
     rd_en_o =  1'b0;
    end
  endtask 


endmodule

仿真验证

        写好三个文件之后,打开modelsim将它们添加到一个工程中,全部编译,然后选择tb_top文件进行仿真(去掉Optimization)
        再右键最顶层的选项添加到波形,仿真时间2us,得到图像如下:
使用Verilog实现RAM的构造并读写数据
       图像从上至下依次为clk时钟,rst复位信号,wr_en写使能,rd_en读使能,addr地址线,data双向数据线。
       下面开始具体分析每一次读写。总共四次写入,四次读取。
       为了检验方便,使输入的数据为对应地址的5倍。

写入数据的操作如下图所示:


使用Verilog实现RAM的构造并读写数据

读取数据的部分:


使用Verilog实现RAM的构造并读写数据

读取数据与输入数据吻合,但输出要比读信号延后一点。

接下来说明延后的原因:
        由于采用三态门形式输出,所以需要data作为中继缓冲。造成了总线输出要在读信号给出后一个时钟周期后才响应。
        读信号到来时,data更新为将要输出的值(即对应地址的数据),在下一个clk上升沿到来之前,还不能输出更新的值。这段时间总线的输出由读信号到来时刻前data的值决定。由于代码

else begin
        data <= 32'bz;      //读写均无效时,为高阻态。

所以期间总线的输出仍未高阻态。


使用Verilog实现RAM的构造并读写数据

        监视得到双向数据线上的数据变化如图,对应波形,四次输入,四次输出,数据相符。(时间单位为微秒)
        可见RAM功能实现时序波形正确。

注:

  1. 因为地址空间较大,最好用task来实现testbench的编写,可使仿真激励清晰明了。
  2. 在testbench中加入打印输出,可用monitordisplay,这样就可以直接看到输出。
  3. 为方便观察结果,我让写入某地址的数据内容与地址addr存在一定关系。
  4. 读写均无效时输出为高阻态,应该使用三态门。
    5.代码中注释,工程目录不能含有中文。
    6.使用modelsim仿真时应选择最顶层的文件进行仿真。博主曾经因为仿真错了文件浪费了好几天时间……
    7..inout接口使用三态门结构实现,当不向外输出时置为高阻态,此时inout的值由外界输入决定。需要输出时连接内部输出缓存,以实现inout功能。
相关标签: Verilog