牛客网存储器部分的题目有RAM的实现,我把FIFO的实现也放在一起:
目录
由题目中给的条件可以知道,输入端主要有数据,地址以及写使能三个信号,对于单端口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进行初始化,写入数据,当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的实现依赖双口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主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及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