前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何实现一个RAM?(单端口RAM、伪双端口RAM、真双端口RAM|verilog代码|Testbench|仿真结果)

如何实现一个RAM?(单端口RAM、伪双端口RAM、真双端口RAM|verilog代码|Testbench|仿真结果)

原创
作者头像
Loudrs
发布2023-05-31 08:59:29
6.6K0
发布2023-05-31 08:59:29
举报
文章被收录于专栏:数字IC经典电路设计


数字IC经典电路设计

经典电路设计是数字IC设计里基础中的基础,盖大房子的第一部是打造结实可靠的地基,每一篇笔者都会分门别类给出设计原理、设计方法、verilog代码、Testbench、仿真波形。然而实际的数字IC设计过程中考虑的问题远多于此,通过本系列希望大家对数字IC中一些经典电路的设计有初步入门了解。能力有限,纰漏难免,欢迎大家交流指正。快速导航链接如下:

个人主页链接

1.数字分频器设计

2.序列检测器设计

3.序列发生器设计

4.序列模三检测器设计

5.奇偶校验器设计

6.自然二进制数与格雷码转换

7.线性反馈移位寄存器LFSR

8.四类九种移位寄存器总结

9.串并转换

10.七种常见计数器总结

11.异步复位同步释放

12.边沿检测

13.毛刺消除与输入消抖



一、前言

RAM(随机存取存储器)是计算机系统中的一种主要存储器件,用于存储和读取数据。在RAM中,单端口RAM(Single-port RAM)和双端口RAM(Dual-port RAM)是两种常见的类型,双端口RAM又分为真双端口(True dual-port RAM)和伪双端口RAM(Simple dual-port RAM)。

那么什么是单端口和双端口?又该如何区分真双端口和伪双端口?

  • 单端口RAM(Single-port RAM): 输入只有一组数据线和一组地址线,读写共用地址线,输出只有一个端口。这意味着,如果CPU需要读取RAM中的数据并将其写入另一个位置,必须先执行读取操作,然后执行写入操作。这种延迟可能会影响计算机系统的性能。单端口RAM通常用于低端计算机系统或嵌入式系统中。如下图所示:
  • 伪双端口RAM(Simple dual-port RAM): 输入有一组数据线,两组地址线,输出只有一个端口。伪双端口RAM可以提供并行读写操作,避免了传统单端口RAM的等待时间,因此有更快的访问速度和响应时间。伪双端口RAM通常广泛应用于高性能数字信号处理器、图像处理器、视频采集卡等领域,以提高存储器的访问速度和效率,满足高速处理的需求。如下图所示:
  • 真双端口RAM(True dual-port RAM): 输入有两组地址线和两组数据线,输出有两个端口。所以双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。这种RAM通常用于高端计算机系统中,因为它可以提高系统性能。例如,在多处理器系统中,多个处理器可以同时访问同一块双端口RAM,从而提高系统的并行处理能力。如下图所示:

总的来说:

单端口RAM:A不能同时读写,即A写时不可读,B读时不可写。

伪双端口RAM:AB可同时读写,但仅A写B读。

真双端口RAM:AB可同时读写,A可写可读,B可写可读。

在功能上与伪双端口RAM与FIFO较为相似,两者有何区别?

FIFO也是一个端口只读,另一个端口只写。FIFO与伪双口RAM的区别在于,FIFO为先入先出,没有地址线,不能对存储单元寻址;而伪双口RAM两个端口都有地址线,可以对存储单元寻址。伪双端口RAM主要用于高速数字信号处理,如通讯协议、图像处理等,因为它可以实现非常快速的读/写操作。而FIFO常用于缓冲和转换两个数据流之间的数据,例如音视频捕捉、交换机队列、路由器缓存等应用场景。实际上FIFO可由伪双端口RAM例化而成

RAM和FIFO中的深度(Depth)和宽度(Width)指的是什么?

除了弄清单端口与双端口的区别,还得理解存储器最重要的两个参数——位宽、深度。存储器深度和位宽都是数字电路中的重要参数,两者具有不同的含义。

存储器深度(Depth)指存储器中可存储数据的位数或存储单元的个数(比如2的n次幂),即存储器的容量大小。而位宽(Width)则指存储器中每个存储单元所能存储的二进制值的位数。

例如,有一个 8 位宽、256 深度的存储器,意味着这个存储器可以存储 256 个 8 位的二进制数据。

本文将会从4位宽、16深度的三种存储器为例展开设计。

二、单端口

2.1 原理

输入只有一组数据线和一组地址线,读写共用地址线,输出只有一个端口。这意味着,如果CPU需要读取RAM中的数据并将其写入另一个位置,必须先执行读取操作,然后执行写入操作。

2.2 verilog代码

实现一个深度为16、位宽为4的单端口RAM。

代码语言:c
复制
//深度为16、位宽为4的端端口RAM
module ram_single_port#(
    parameter 		DATA_WIDTH = 4,//RAM数据位宽
    parameter 		ADDR_WIDTH = 4,//RAM地址位宽
    parameter 		DEPTH = 16	//RAM深度
    )(
    input				clk,
    input 				rst_n,
    input 				wr_en,//写使能
    input 	     [ADDR_WIDTH-1:0]	addr,//读写共用地址
    input 	     [DATA_WIDTH-1:0]	wr_data,//写入数据
    output  reg  [DATA_WIDTH-1:0]	re_data//读出数据
    );

//定义一个深度为16、位宽为4的单端口RAM
reg   [DATA_WIDTH-1:0]     ram_data [DEPTH-1:0];
 
//数据写入存储在RAM中   
genvar i;
generate
    for(i=0;i<DEPTH;i=i+1)
        always@(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                ram_data[i] <= 0;
            end
            else if(wr_en) begin	//使能高电平时写入	
				ram_data[addr] <= wr_data;	
	    	end
	    	else begin
				ram_data[addr] <= ram_data[addr]; 
	    	end
        end
endgenerate
 
//读出数据         
always@(*) begin
    if(wr_en) begin
        re_data <= 0;
    end
    else if(!wr_en) begin
		re_data <= ram_data[addr];
    end
    else begin
		re_data <= re_data;
    end
end


endmodule

2.3 Testbench

代码语言:c
复制
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_single_port_tb();

parameter 	DATA_WIDTH = 4;
parameter 	ADDR_WIDTH = 4;
parameter 	DEPTH      = 16;

//信号申明
reg				clk;
reg				rst_n;
reg				wr_en;
reg	[ADDR_WIDTH-1:0]	addr;
reg	[DATA_WIDTH-1:0]	wr_data;
wire[DATA_WIDTH-1:0]	re_data;

//例化
ram_single_port u_ram_single_port(
    .clk    	(clk),
    .rst_n		(rst_n),
    .wr_en		(wr_en),
    .addr		(addr),
    .wr_data	(wr_data),
    .re_data	(re_data)
    );

//时钟生成
always  #5 clk = ~clk;

//信号初始化以及赋值
integer i;
initial begin
    clk = 1;
    rst_n = 1;
    wr_en = 0;
    wr_data = 0;
    #5;
    rst_n = 0;
    wr_en = 0;
    #5;
    rst_n = 1;
    wr_en = 1;
    for (i = 0; i < DEPTH; i = i + 1) begin//写入数据赋初值
        @(posedge clk) begin
	    	addr = i;
	   		wr_data = wr_data  + 1;
		end
    end
    #5;wr_en = 0;
    for (i = 0; i < 64; i = i + 1) begin//设置读出地址
		@(posedge clk) begin
	    	addr = i;
		end
    end
end

endmodule

2.4 仿真结果

以165ns为分界线:左侧为数据的写入,此时无法进行读取数据;右侧为数据的读取,此时无法进行写入数据。RAM存储的数据为0-15总计16个数字,按照为此依次递增。

三、真双端口

3.1 原理

输入有两组地址线和两组数据线,输出有两个端口。所以双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。

3.2 verilog代码

实现一个深度为16、位宽为4的真双端口RAM。

代码语言:c
复制
//深度为16、位宽为4的真双端口RAM
module ram_true_dual_port #(
    parameter 		DATA_WIDTH = 4,//RAM数据位宽
    parameter 		ADDR_WIDTH = 4,//RAM地址位宽
    parameter 		DEPTH = 16	//RAM深度
    )(
    input           clk,
    input           rst_n,
    
    input           wr_en_a,//a端写使能
    input           re_en_a,//a端读使能
    input           [ADDR_WIDTH-1:0]   addr_a,//a端地址
    input   	    [DATA_WIDTH-1:0]   data_in_a,//a端输入数据
    output   reg    [DATA_WIDTH-1:0]   data_out_a,//a端输出数据

    input           wr_en_b,//b端写使能
    input           re_en_b,//b端读使能
    input           [ADDR_WIDTH-1:0]   addr_b,//b端地址
    input   	    [DATA_WIDTH-1:0]   data_in_b,//b端输入数据
    output   reg    [DATA_WIDTH-1:0]   data_out_b//b端输出数据
    );

//定义一个深度为16、位宽为4的真双端口RAM
reg [DATA_WIDTH-1:0]    ram_data [DEPTH-1:0];  

//a端、b端数据写入存储在RAM中   
genvar i;
generate
    for(i = 0; i < DEPTH; i = i + 1)
		always @(posedge clk or negedge rst_n)begin
    	    if(!rst_n)begin
                ram_data[i] = 0;						
            end
            else if (wr_en_a) begin//a端使能高电平时写入		
                 ram_data[addr_a] <= data_in_a; 
	    	end
            else if (wr_en_b) begin //b端使能高电平时写入	 		
                 ram_data[addr_b] <= data_in_b; 
	    	end
	    	else begin
				ram_data[addr_a] =ram_data[addr_a];
				ram_data[addr_b] =ram_data[addr_b];
	    	end
		end
endgenerate

//a端、b端读出数据 
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin							
        data_out_a <= 0;
        data_out_b <= 0;
    end
    else if(re_en_a)begin		
        data_out_a <= ram_data[addr_a];					
        data_out_b <= data_out_b;
    end
    else if(re_en_b)begin
        data_out_b <= ram_data[addr_b];					
        data_out_a <= data_out_a;
    end
    else begin
        data_out_a <= data_out_a;
        data_out_b <= data_out_b;
    end
end

endmodule

3.3 Testbench

代码语言:c
复制
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_true_dual_port_tb();

parameter 	DATA_WIDTH = 4;
parameter 	ADDR_WIDTH = 4;
parameter 	DEPTH      = 16;

//信号申明
reg           clk;
reg           rst_n;

reg           wr_en_a;
reg           re_en_a;
reg           [ADDR_WIDTH-1:0]   addr_a;
reg   	      [DATA_WIDTH-1:0]   data_in_a;
wire          [DATA_WIDTH-1:0]   data_out_a;

reg           wr_en_b;
reg           re_en_b;
reg           [ADDR_WIDTH-1:0]   addr_b;
reg    	      [DATA_WIDTH-1:0]   data_in_b;
wire	      [DATA_WIDTH-1:0]   data_out_b;

//例化
ram_true_dual_port u_ram_true_dual_port(
    .clk			(clk),
    .rst_n			(rst_n),
    .wr_en_a		(wr_en_a),
    .re_en_a		(re_en_a),
    .addr_a			(addr_a),
    .data_in_a		(data_in_a),
    .data_out_a		(data_out_a),
    .wr_en_b		(wr_en_b),
    .re_en_b		(re_en_b),
    .addr_b			(addr_b),
    .data_in_b		(data_in_b),
    .data_out_b		(data_out_b)
    );

//时钟生成
always #5 clk = ~clk;

//信号初始化以及赋值
integer i;
initial begin
    clk        = 1'b1;
    rst_n      = 1'b0;
    wr_en_a    = 1'b0;
    wr_en_b    = 1'b0;
    re_en_a    = 1'b0;
    re_en_b    = 1'b0;
    addr_a     = 1'b0;
    addr_b     = 1'b0;
    data_in_a  = 1'b0;
    data_in_b  = 1'b0;
    #30
    rst_n = 1'b1;

//A写A读
    #20;
    wr_en_a= 1'b1; 
    @(posedge clk) 
    for (i=0;i<4;i=i+1)begin
            @(posedge clk) begin
                addr_a = i;					
                data_in_a = data_in_a + 1;    
            end
    end

    #20;
    wr_en_a = 1'b0;
    re_en_a = 1'b1;  
    @(posedge clk) 
    for (i=0;i<4;i=i+1)begin
            @(posedge clk)begin
                addr_a = i;       
            end
    end

//A写B读
    #20;
    re_en_a = 1'b0; 
    wr_en_a = 1'b1; 
    @(posedge clk) 
    for (i=4;i<8;i=i+1)begin
            @(posedge clk) begin
                addr_a = i;					
                data_in_a = data_in_a + 1;    
            end
    end

    #20;
    wr_en_a = 1'b0;
    re_en_b = 1'b1;  
    @(posedge clk) 
    for (i=4;i<8;i=i+1)begin
            @(posedge clk)begin
                addr_b = i;       
            end
    end

//B写A读
    #20;
    re_en_b = 1'b0; 
    wr_en_b = 1'b1; 
    @(posedge clk) 
    for (i=8;i<12;i=i+1)begin
            @(posedge clk) begin
                addr_b = i;					
                data_in_b = data_in_b + 2;    
            end
    end

    #20;
    wr_en_b = 1'b0; 
    re_en_a = 1'b1;  
    @(posedge clk) 
    for (i=8;i<12;i=i+1)begin
            @(posedge clk)begin
                addr_a = i;       
            end
    end

//B写B读
    #20;
    re_en_a = 1'b0;  
    wr_en_b = 1'b1; 
    @(posedge clk) 
    for (i=12;i<16;i=i+1)begin
            @(posedge clk) begin
                addr_b = i;					
                data_in_b = data_in_b + 2;    
            end
    end

    #20;
    wr_en_b = 1'b0; 
    re_en_b = 1'b1;  
    @(posedge clk) 
    for (i=12;i<16;i=i+1)begin
            @(posedge clk)begin
                addr_b = i;       
            end
    end

end

endmodule

3.4 仿真结果

(1)整体

50ns—170ns:A写入数据A读出数据

170ns—290ns:A写入数据B读出数据

290ns—410ns:B写入数据A读出数据

410ns—530ns:B写入数据B读出数据

(2)ram_data

B端写入的数据与A端不同,A是连续的数据输入,B是间隔的数据输入(即是在自身的基础上不断+2得到的),整个RAM写入的数据如上所示。

(3)A写A读

可以看到data_in_a输入数据为1234,data_out_a输入数据为1234,A写A读正常。

(4)A写B读

可以看到data_in_a输入数据为5678,data_out_b输入数据为5678,A写B读正常。

(5)B写A读

可以看到data_in_b输入数据为2468,data_out_a输入数据为2468,B写A读正常。

(6)B写B读

可以看到data_in_b输入数据为ace0,data_out_b输入数据为ace0,B写B读正常。

四、伪双端口

4.1 原理

输入有一组数据线,两组地址线,输出只有一个端口。伪双端口RAM可以提供并行读写操作。

4.2 verilog代码

实现一个深度为16、位宽为4的伪双端口RAM。

代码语言:c
复制
//深度为16、位宽为4的伪双端口RAM
module ram_simple_dual_port #(
    parameter 	ADDR_WIDTH=4,
    parameter 	DATA_WIDTH=4,
    parameter 	DEPTH=16
    )(    
    input             clk,
    input             rst_n,

    input      	      				   wr_en, //写使能
    input      	      [ADDR_WIDTH-1:0] addr_a, //a端写地址            
    input  	      	  [DATA_WIDTH-1:0] data_a,//a端写数据

    input                              re_en, //读使能  
    input      	      [ADDR_WIDTH-1:0] addr_b, //b端读地址         
    output   reg      [DATA_WIDTH-1:0] data_b//b端读数据
    );

//定义一个深度为16、位宽为4的伪双端口RAM
reg [DATA_WIDTH-1:0]    ram_data [DEPTH-1:0];  

//a端写入数据存储在RAM中
genvar i;
generate
    for(i = 0;i < DEPTH;i = i + 1)
            if(!rst_n) begin             
                 ram_data[i] <= 0;
            end
            else if (wr_en) begin   
                 ram_data[addr_a] <= data_a;   
            end
	    	else begin
				ram_data[addr] <= ram_data[addr];
	    end 
endgenerate

//b端读出数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin       
        data_b <= 0;
    end
    else if(re_en) begin 
        data_b <= ram_data[addr_b];  
    end
    else begin
        data_b <= data_b;
    end
end

endmodule

4.3 Testbench

代码语言:c
复制
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_simpel_dual_port_tb();

parameter 	DATA_WIDTH = 4;
parameter 	ADDR_WIDTH = 4;
parameter 	DEPTH      = 16;

//信号申明
reg				clk;
reg				rst_n;
reg				wr_en;
reg				re_en;
reg     [ADDR_WIDTH-1:0]	addr_a;
reg     [ADDR_WIDTH-1:0]	addr_b;
reg     [DATA_WIDTH-1:0] 	data_a;
wire    [DATA_WIDTH-1:0] 	data_b;

//例化
ram_simple_dual_port u_ram_simple_dual_port(
    .clk		(clk),
    .rst_n		(rst_n),
    .wr_en		(wr_en),
    .re_en		(re_en),
    .addr_a		(addr_a),
    .addr_b		(addr_b),
    .data_a		(data_a),
    .data_b		(data_b)
    );

//时钟生成
always #5 clk = ~clk;

//信号初始化以及赋值
integer i;
initial begin
    clk     = 1'b1;
    rst_n   = 1'b0;
    wr_en   = 1'b0;
    re_en   = 1'b0;
    addr_a  = 1'b0;
    addr_b  = 1'b0;
    data_a  = 1'b0;
    #30
    rst_n = 1'b1;
    #5
    wr_en = 1'b1; 
    @(posedge clk) 
    for (i=0;i<DEPTH;i=i+1)begin
            @(posedge clk) begin
                addr_a = i;					
                data_a = data_a + 1;    
            end
    end

    #5 wr_en = 1'b0;
    re_en = 1'b1;  
    @(posedge clk) 
    for (i=0;i<DEPTH;i=i+1)begin
            @(posedge clk)begin
                addr_b = i;        
            end
    end
end

endmodule

4.4仿真结果

(1)整体波形

205ns前,wr_en = 1,;205ns后,re_en = 1;以205ns为分界线,左右两侧分别是写数据和读数据,当时钟信号处于上升沿时,分别写入和读取当前地址的数据,但是写入数据与读写数据不能同时进行,因为此处设计的是simple_dual_port RAM,即伪双端口RAM。

(2)寄存器数据ram_data

在Testbench中,我们借用for循环,在时钟上升沿时触发使得写入的数据data_a存储到RAM寄存器ram_data中,如上图所示

(3)写数据

在90ns到110ns间是写入数据,此时读出数据停。止可以看到,在前半部分写入的地址addr_a = 4,写入数据data_a =5,所以在下一个上升沿将数据5写入ram_data4中,此时ram_data为12345成功写入。后续的数据同理。

(4)读数据

在205ns后是读出数据,此时写入数据停止。可以看到,在初始读出的地址addr_b = 0,此时ram_data =fedcba987654321,所以在下一个上升沿读出数据ram_data0,此时data_b成功读出1。后续的数据同理。

五、总结

总的来说:

单端口RAM:只有一个口,此口可读可写,但不能同时读写,即写时不可读,读时不可写。

伪双端口RAM:两个口,每个口只会读(或写),AB可同时读写,但仅A写B读。

真双端口RAM:两个口,每个口都可读可写,AB可同时读写,A可写可读,B可写可读。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、单端口
    • 2.1 原理
      • 2.2 verilog代码
        • 2.3 Testbench
          • 2.4 仿真结果
          • 三、真双端口
            • 3.1 原理
              • 3.2 verilog代码
                • 3.3 Testbench
                  • 3.4 仿真结果
                  • 四、伪双端口
                    • 4.1 原理
                      • 4.2 verilog代码
                        • 4.3 Testbench
                          • 4.4仿真结果
                          • 五、总结
                          相关产品与服务
                          数据保险箱
                          数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档