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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Stynis/article/details/80555825

目的

        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的内存数据;


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,得到图像如下:
完整波形图ram1.1
       图像从上至下依次为clk时钟,rst复位信号,wr_en写使能,rd_en读使能,addr地址线,data双向数据线。
       下面开始具体分析每一次读写。总共四次写入,四次读取。
       为了检验方便,使输入的数据为对应地址的5倍。

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


ram1.2

读取数据的部分:


ram1.3

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

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

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

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


ram1.4

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

注:

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

猜你喜欢

转载自blog.csdn.net/Stynis/article/details/80555825