单端口RAM的Verilog设计(同步读、同步写)

  在学习最近的过程中,需要用到单端口RAM来存储我们设计过程中产生的数据,我们可以调用vivado内部的IP核,也可以在自己用Verilog写一个单端口RAM。原理在这里不进行讲解。

一、例程一

1.1 顶层代码

  参考一:Intel官方给出的简易单端口RAM设计例程

module single_port_ram
(
	input [7:0] data,
	input [5:0] addr,
	input we, clk,
	output [7:0] q
);

	// Declare the RAM variable
	reg [7:0] ram[63:0];
	
	// Variable to hold the registered read address
	reg [5:0] addr_reg;
	
	always @ (posedge clk)
	begin
	// Write
		if (we)
			ram[addr] <= data;
		
		addr_reg <= addr;
		
	end
		
	// Continuous assignment implies read returns NEW data.
	// This is the natural behavior of the TriMatrix memory
	// blocks in Single Port mode.  
	assign q = ram[addr_reg];
	
endmodule

1.2 TestBench

  这里简单写个TestBench对其进行仿真。

`timescale 1ns / 1ps

module tb_single_port_ram(
 
    );
	
	reg 			clk; 
	reg  [7 : 0] 	address; 
	reg 			we; 
	
	reg  [7 : 0] 	data_in;
	wire [7 : 0] 	data_out;     
	
	initial begin
		clk = 0;
		forever 
			#10 clk = ~clk;
	end
	
	initial begin
		we <= 1'b0;
		address <= 8'd0;
		data_in <= 8'h00;
		
		#10
		#20
		we <= 1'b1;
		address <= 8'd32;
		data_in <= 8'd12;
		
		#60
		we <= 1'b1;
		address <= 8'd39;
		data_in <= 8'd77;
		
		#40
		we <= 1'b0;
		address <= 8'd32;
		
		#80
		we <= 1'b0;
		address <= 8'd39;
		
		#20
		we <= 1'b0;
		address <= 8'd30;
	
	end
	
	single_port_ram single_port_ram_inst
	(
		.data(data_in),
		.addr(address),
		.we	 (we), 
		.clk (clk),
		.q   (data_out)
	);
	
	
endmodule

1.3 仿真结果

  仿真结果如下:

在这里插入图片描述

二、例程二

1.1 顶层代码

  参考二:CSDN博主给出的《单端口RAM的设计(同步读、同步写)》
  其顶层代码如下:

`timescale 1ns / 1ps
 
module ram_sp_sr_sw (
clk         , // Clock Input
address     , // Address Input
data        , // Data bi-directional
cs          , // Chip Select
we          , // Write Enable/Read Enable
oe            // Output Enable
); 
 
parameter DATA_WIDTH = 8 ;
parameter ADDR_WIDTH = 8 ;
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
 
//--------------Input Ports----------------------- 
input                  clk         ;
input [ADDR_WIDTH-1:0] address     ;
input                  cs          ;
input                  we          ;
input                  oe          ; 
 
//--------------Inout Ports----------------------- 
inout [DATA_WIDTH-1:0]  data       ;
 
//--------------Internal variables---------------- 
reg [DATA_WIDTH-1:0] data_out ;
reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];
 
//initialization
// synopsys_translate_off
integer i;
initial begin
    for(i=0; i < RAM_DEPTH; i = i + 1) begin
        mem[i] = 8'h00;
    end
end
// synopsys_translate_on

//--------------Code Starts Here------------------ 
// Tri-State Buffer control 
// output : When we = 0, oe = 1, cs = 1
assign data = (cs && oe && !we) ? data_out : 8'bz; 
 
// Memory Write Block 
// Write Operation : When we = 1, cs = 1
always @ (posedge clk)
begin : MEM_WRITE
   if ( cs && we ) begin
       mem[address] <= data;
   end
end
 
// Memory Read Block 
// Read Operation : When we = 0, oe = 1, cs = 1
always @ (posedge clk)
begin : MEM_READ
  if (cs && !we && oe) begin
    data_out <= mem[address];
  end
end
 
endmodule 

1.2 TestBench

`timescale 1ns / 1ps

module tb_single_port_ram(
 
    );
	
	reg clk;          // Clock Input
	reg [7 : 0] address;      // address Input
	wire [7 : 0] data;         // Data bi-directional
	reg cs;           // Chip Select
	reg we;          // Write Enable/Read Enable
	reg oe;            // Output Enable
	
	reg [7 : 0] data_in;
	
	assign data = (cs && we && !oe) ? data_in : 'dz;
	
	integer i;
	
	initial begin
		clk = 0;
		forever 
			#10 clk = ~clk;
	end
	
	
	initial begin
	
		cs <= 1'b0;
		we <= 1'b0;
		oe <= 1'b0;
		address <= 8'd0;
		data_in <= 8'h00;
		
		#10
		
		#20
		cs <= 1'b1;
		we <= 1'b1;
		oe <= 1'b0;
		address <= 8'd32;
		data_in <= 8'd12;
		
		#60
		cs <= 1'b1;
		we <= 1'b1;
		oe <= 1'b0;
		address <= 8'd39;
		data_in <= 8'd77;
		
		#40
		cs <= 1'b0;
		
		
		#40
		cs <= 1'b1;
		we <= 1'b0;
		oe <= 1'b1;
		address <= 8'd32;
		
		#20
		oe <= 1'b0;
		cs <= 1'b0;
		
		#80
		cs <= 1'b0;
		oe <= 1'b0;

		#80
		cs <= 1'b1;
		we <= 1'b0;
		oe <= 1'b1;
		address <= 8'd39;
		
		#20
		cs <= 1'b1;
		we <= 1'b0;
		oe <= 1'b1;
		address <= 8'd30;
	
	end
	
	single_port_ram  u_ram(
		.clk(clk)         , // Clock Input
		.address(address)     , // address Input
		.data(data)        , // Data bi-directional
		.cs(cs)          , // Chip Select
		.we(we)          , // Write Enable/Read Enable
		.oe(oe)            // Output Enable
	);

1.3 仿真结果

  这里仿真后结果如下,写数据是正常的,但是读数据会出现读出数据是高阻态的情况。

在这里插入图片描述
  分析过后,发现原因是由于读数据是由oe进行控制的,data_out始终晚于读数据控制信号oe一拍,但是oe又作为data的输出条件,所以这时取值必然导致读出的值不是我们想要的值,所以这里有两个办法解决上诉问题:

1.4 修改版

1.4.1 方法一

  修改读使能信号oe、读地址address、片选信号cs、写使能信号we均为阻塞赋值,且这四个的输入时间完全一致,即将testbench修改如下:

`timescale 1ns / 1ps

module tb_single_port_ram(
 
    );
	
	reg clk;          // Clock Input
	reg [7 : 0] address;      // address Input
	wire [7 : 0] data;         // Data bi-directional
	reg cs;           // Chip Select
	reg we;          // Write Enable/Read Enable
	reg oe;            // Output Enable
	
	reg [7 : 0] data_in;
	
	assign data = (cs && we && !oe) ? data_in : 'dz;
	
	integer i;
	
	initial begin
		clk = 0;
		forever 
			#10 clk = ~clk;
	end
	
	
	initial begin
	
		cs = 1'b0;
		we = 1'b0;
		oe = 1'b0;
		address = 8'd0;
		data_in = 8'h00;
		
		#10
		#20
		cs = 1'b1;
		we = 1'b1;
		oe = 1'b0;
		address = 8'd32;
		data_in = 8'd12;
		
		#60
		cs = 1'b1;
		we = 1'b1;
		oe = 1'b0;
		address = 8'd39;
		data_in = 8'd77;
		
		#40
		cs = 1'b0;
		#40
		cs = 1'b1;
		we = 1'b0;
		oe = 1'b1;
		address = 8'd32;
		
		#20
		oe = 1'b0;
		cs = 1'b0;
		
		#80
		cs = 1'b0;
		oe = 1'b0;

		#80
		cs = 1'b1;
		we = 1'b0;
		oe = 1'b1;
		address = 8'd39;
		
		#20
		cs = 1'b1;
		we = 1'b0;
		oe = 1'b1;
		address = 8'd30;
	end
	
	single_port_ram  u_ram(
		.clk(clk)         , // Clock Input
		.address(address)     , // address Input
		.data(data)        , // Data bi-directional
		.cs(cs)          , // Chip Select
		.we(we)          , // Write Enable/Read Enable
		.oe(oe)            // Output Enable
	);

  仿真结果如下,可见读出数据的时间与读地址输入的时间是一致的。
在这里插入图片描述

1.4.2 方法二

  在RAM的顶层代码中,将读使能信号oe与片选信号cs打一拍,用于RAM内部data的取值,修改后的RAM顶层代码如下:

 `timescale 1ns / 1ps
//
// Engineer: Shaosheng Wei
// Create Date: 2022/07/25 15:46:55
// Design Name: single_port_ram
// Module Name: single_port_ram
// Function    : Synchronous read write RAM 
// Revision 0.01 - File Created
// Additional Comments:
//
 
module single_port_ram (
clk         , // Clock Input
address     , // Address Input
data        , // Data bi-directional
cs          , // Chip Select
we          , // Write Enable/Read Enable
oe            // Output Enable
); 
 
parameter DATA_WIDTH = 8 ;
parameter ADDR_WIDTH = 8 ;
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
 
//--------------Input Ports----------------------- 
input                  clk         ;
input [ADDR_WIDTH-1:0] address     ;
input                  cs          ;
input                  we          ;
input                  oe          ; 
 
//--------------Inout Ports----------------------- 
inout [DATA_WIDTH-1:0]  data       ;
 
//--------------Internal variables---------------- 
reg [DATA_WIDTH-1:0] data_out ;
reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];

reg	cs_reg,we_reg,oe_reg;

always @(posedge clk)
  begin
	oe_reg <= oe;
	cs_reg <= cs;
	we_reg <= we;
  end
  
//initialization
 
// synopsys_translate_off
integer i;
initial begin
	for(i=0; i < RAM_DEPTH; i = i + 1) begin
        mem[i] = 8'h00;
    end
end
// synopsys_translate_on
 
//--------------Code Starts Here------------------ 
 
// Tri-State Buffer control 
// output : When we = 0, oe = 1, cs = 1
assign data = (cs_reg && oe_reg && !we_reg) ? data_out : 'hz; 
 
// Memory Write Block 
// Write Operation : When we = 1, cs = 1
always @ (posedge clk)
begin : MEM_WRITE
   if ( cs && we ) begin
       mem[address] <= data;
   end
end
 
// Memory Read Block 
// Read Operation : When we = 0, oe = 1, cs = 1
always @ (posedge clk)
begin : MEM_READ
  if (cs && !we && oe) begin
    data_out <= mem[address];
  end
end
 
endmodule // End of Module ram_sp_sr_sw

  仿真结果如下:

在这里插入图片描述
  相比之下,修改版中的方法二更适合于我们日常FPGA设计中直接调用,因为在很多情况下,读使能信号oe和读地址address都是由其他模块产生的,我们无法保证其输入符合方法一中的情况,即使通过打一拍的方式,依然无法保证其输入的读使能信号oe和读地址address在上升沿处进行输入,而不是在上升沿之后才进行输入。

Tip:对变量进行阻塞赋值,变量是在边沿之后才发生变化;而对变量进行阻塞赋值,变量是在边沿处就发生变化。

总结

这里参考一和参考二的区别在于:
(1)参考一只有一个写使能信号we,而参考二除了写使能信号we,还有片选信号cs(控制是否进行读写)和读使能信号oe;
(2)参考一有分开的两个数据写入端口和数据读出端口;而参考二只有一个inout类型数据端口,支持数据的写入和读出;
(3)参考一写数据是由we进行控制的,而读数据则不由任何控制信号进行控制,输入地址,则立即输出对应地址下的数据,在写数据的同时,也在读出该地址对应数据;而参考二写数据是由写使能信号we进行控制的,读数据由读使能信号oe进行控制的,而且需要注意的是:在读数据时不进行写数据,写数据时则不进行读数据,即we=!oe;
(4)参考一读数据输出时间与读地址输入时间同步,参考二(修改版:方法二)读数据输出时间晚于读地址输入时间一个时钟周期;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值