FPGA刷题——存储器(RAM和FIFO的Verilog实现)

牛客网存储器部分的题目有RAM的实现,我把FIFO的实现也放在一起:

目录

单端口RAM实现

双口RAM的实现

同步FIFO实现

异步FIFO实现

读写地址发生器

格雷码的产生与打拍

空满信号发生器


单端口RAM实现

由题目中给的条件可以知道,输入端主要有数据,地址以及写使能三个信号,对于单端口RAM,只有一端有地址和使能信号,那么使能拉高时输入数据有效并且寄存,然后地址从0-127变化,存储输入数据,输出数据时需要保证输入端不写入数据,即拉低写使能。

对比于双端口RAM,双端口RAM读写两端都有使能信号,写数据时读使能拉低,读数据时写使能拉低。而且两个端口的地址也是相互独立的。

代码流程如下

(1)首先定义题目要求的位宽和深度的数据寄存器,位宽=写数据=读数据位宽,深度按照题目要求,格式为:reg 【位宽】名称【深度】;

(2)在复位信号下,给数据寄存器赋初值0,这里用到了for语句

定义参数integer i 

for(i=0;i<深度;i++)begin

       名称【i】<=0;

end

(3)使能信号=1时,将写数据存入数据寄存器

(4)使能信号=0时,读取数据寄存器数据,否则不读取

module RAM_1port(
    input clk,
    input rst,
    input enb,
    input [6:0]addr,
    input [3:0]w_data,
    output wire [3:0]r_data
);
//*************code***********//
    reg [3:0] data_temp[127:0];//定义深度为128的4位宽数据寄存器
    
    integer i;//定义参数i
    
    always@(posedge clk or negedge rst)begin
        if(~rst)begin
            for(i=0;i<127;i++)begin
                data_temp[i]<=0;//清零数据寄存器
            end
        end
        else if(enb)begin//写使能有效
            data_temp[addr]<=w_data;//把数据存进去
        end
        
    end
    
    assign r_data=(~enb)? data_temp[addr]:4'd0;//写使能低电平时输出,高电平不输出

//*************code***********//
endmodule

双口RAM的实现

首先要实现RAM,首先要声明数据的存储空间。声明存储变量之后,需要对ram进行初始化,写入数据,当write_en有效,向write_addr写入write_data,当read_en有效,根据输入的read_addr输出read_data。需要注意的是,题目要求实现真双端口RAM,即可以同时写入和读出,所以需要使用两个always语句块实现写入和读出逻辑,不可以在同一个always块中使用if-else if-else if结果。

代码思路如下:

(1)首先定义题目要求的位宽和深度的数据寄存器,位宽=写数据=读数据位宽,深度按照题目要求,格式为:reg 【位宽】名称【深度】;

(2)在复位信号下,给数据寄存器赋初值0,这里用到了for语句

定义参数integer i 

for(i=0;i<深度;i++)begin

       名称【i】<=0;

end

(3)写使能信号=1时,将写数据存入数据寄存器

(4)读使能信号=1时,读取数据寄存器数据,否则不读取

module ram_mod(
	input clk,
	input rst_n,
	
	input write_en,
	input [7:0]write_addr,
	input [3:0]write_data,
	
	input read_en,
	input [7:0]read_addr,
	output reg [3:0]read_data
);
	
    reg[3:0] data_reg[7:0];//定义位宽为4深度为8的数据寄存器
    integer i;
    
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            for(i=0;i<7;i++)begin
                data_reg[i]<=0;
            end
        end
        else if(write_en)begin
            data_reg[write_addr]<=write_data;//写
        end
    end
    
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            read_data<=4'd0;
        end
        else if(read_en)begin
            read_data<=data_reg[read_addr];//读
        end
        else begin
            read_data<=4'd0;
        end
    end
    
endmodule

同步FIFO实现

FIFO的实现依赖双口RAM,如下图,RAM的使能和数据可以与FIFO直接连接,需要产生RAM的写地址和读地址,还要产生写满full和读空empty信号(下右图标红):

 

产生读写地址

waddr写地址+1的情况:写使能有效&&没写满

raddr读地址+1的情况:读使能有效&&没读空 

 assign wenc = winc && !wfull; //写地址+
 assign renc = rinc && !rempty;//读地址+

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) 
            waddr <= 'd0;
        else if (wenc)
            waddr <= waddr + 'd1;
    end
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) 
            raddr <= 'd0;
        else if (renc)
            raddr <= raddr + 'd1;
    end

产生空满信号

wfull写满的情况:读地址==写地址+深度

rempty读空的情况:读地址==写地址

always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wfull <= 'd0;
            rempty <= 'd0;
        end
        else begin
            wfull <= waddr == raddr + DEPTH;
            rempty <= waddr == raddr;
        end
    end

异步FIFO实现

 

异步FIFO是各大公司面试笔试的重点。难点仍然是空满信号。异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号。

 

 上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。本题已经给出了RAM部分。

读写地址发生器

产生FIFO的自然二进制读写地址。当读使能rinc==1且FIFO非空rempty==0时,读地址在读时钟rclk下自增;当写使能winc==1且FIFO非满wfull==0时,写地址在写时钟wclk下自增。这两种地址可以直接传入RAM模块。

wire                     wenc, renc;
wire [$clog2(DEPTH)-1:0] waddr, raddr;

assign wenc = winc&!wfull;
assign renc = rinc&!rempty;

always@(posedge wclk or negedge wrstn) begin
  	if(~wrstn)
    	waddr_bin <= 0;
  	else
    	waddr_bin <= wenc? waddr_bin+1: waddr_bin;
end
    
always@(posedge rclk or negedge rrstn) begin
  	if(~rrstn)
    	raddr_bin <= 0;
  	else
    	raddr_bin <= renc? raddr_bin+1: raddr_bin;
end

格雷码的产生与打拍

为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。

最后,自然二进制地址和格雷码地址都是$clog2(DEPTH)+1bit,比waddrraddr多了1bit。该bit是用来辅助产生满信号的。

reg  [$clog2(DEPTH):0] waddr_bin, raddr_bin;
wire [$clog2(DEPTH):0] waddr_gray, raddr_gray;
reg  [$clog2(DEPTH):0] waddr_gray1, raddr_gray1;
reg  [$clog2(DEPTH):0] waddr_gray2, raddr_gray2;
reg  [$clog2(DEPTH):0] waddr_gray3, raddr_gray3;

assign waddr_gray = waddr_bin^(waddr_bin>>1);
assign raddr_gray = raddr_bin^(raddr_bin>>1);
assign waddr      = waddr_bin[$clog2(DEPTH)-1:0];
assign raddr      = raddr_bin[$clog2(DEPTH)-1:0];

always@(posedge rclk or negedge rrstn) begin
    if(~rrstn)
      	raddr_gray1 <= 0;
    else
      	raddr_gray1 <= raddr_gray;
end

always@(posedge rclk or negedge rrstn) begin
    if(~rrstn) begin
      	waddr_gray2 <= 0;
      	waddr_gray3 <= 0;
    end
    else begin
      	waddr_gray2 <= waddr_gray1;
      	waddr_gray3 <= waddr_gray2;
    end
end

always@(posedge wclk or negedge wrstn) begin
    if(~wrstn) begin
      	raddr_gray2 <= 0;
      	raddr_gray3 <= 0;
    end
    else begin
      	raddr_gray2 <= raddr_gray1;
      	raddr_gray3 <= raddr_gray2;
    end
end

使用自然二进制码计数时,相邻数据之间可能会产生多bit的变化。这会产生较大的尖峰电流以及其他问题。格雷码是一种相邻数据只有1bit变化的码制

十进制 自然二进制 格雷码
0 000 000
1 001 001
2 010 011
3 011 010
4 100 110
5 101 111
6 110 101
7 111 100

空满信号发生器

当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。

assign wfull  = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
assign rempty = (raddr_gray1==waddr_gray3);

异步FIFO完整代码:

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);
    wire [$clog2(DEPTH)-1:0] waddr, raddr;
    reg  [$clog2(DEPTH)  :0] waddr_bin, raddr_bin;
    
    wire [$clog2(DEPTH)  :0] waddr_gray, raddr_gray;
    reg  [$clog2(DEPTH)  :0] waddr_gray1, raddr_gray1;
    reg  [$clog2(DEPTH)  :0] waddr_gray2, raddr_gray2;
    reg  [$clog2(DEPTH)  :0] waddr_gray3, raddr_gray3;
    wire                     wenc, renc;
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn)
            waddr_bin <= 0;
        else
            waddr_bin <= wenc? waddr_bin+1: waddr_bin;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn)
            raddr_bin <= 0;
        else
            raddr_bin <= renc? raddr_bin+1: raddr_bin;
    end
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn)
            waddr_gray1 <= 0;
        else
            waddr_gray1 <= waddr_gray;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn)
            raddr_gray1 <= 0;
        else
            raddr_gray1 <= raddr_gray;
    end
    
    always@(posedge rclk or negedge rrstn) begin
        if(~rrstn) begin
            waddr_gray2 <= 0;
            waddr_gray3 <= 0;
        end
        else begin
            waddr_gray2 <= waddr_gray1;
            waddr_gray3 <= waddr_gray2;
        end 
    end
    
    always@(posedge wclk or negedge wrstn) begin
        if(~wrstn) begin
            raddr_gray2 <= 0;
            raddr_gray3 <= 0;
        end
        else begin
            raddr_gray2 <= raddr_gray1;
            raddr_gray3 <= raddr_gray2;
        end 
    end
    
    assign waddr_gray = waddr_bin^(waddr_bin>>1);
    assign raddr_gray = raddr_bin^(raddr_bin>>1);
    assign waddr      = waddr_bin[$clog2(DEPTH)-1:0];
    assign raddr      = raddr_bin[$clog2(DEPTH)-1:0];
    assign wenc       = winc&!wfull;
    assign renc       = rinc&!rempty;
    
    assign wfull      = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
    assign rempty     = (raddr_gray1==waddr_gray3);
    
    dual_port_RAM #(
        .DEPTH(DEPTH),
        .WIDTH(WIDTH)
    )
    myRAM(
        .wclk (wclk ),
        .wenc (wenc ),
        .waddr(waddr),
        .wdata(wdata),
        .rclk (rclk ),
        .renc (renc ),
        .raddr(raddr),
        .rdata(rdata)
    );
endmodule

猜你喜欢

转载自blog.csdn.net/weixin_46188211/article/details/126042295