牛客网存储器部分的题目有RAM的实现,我把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)+1
bit,比waddr
和raddr
多了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