题解 | #异步FIFO#
异步FIFO
https://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf
`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 ); //这是参照题解中"周周~"的答案的,基本就是在她的基础上进行理解了下,逻辑看起来不难,但是要我自己写的话估计要找一天bug了 //分为5部分 parameter ADDR_WIDTH =$clog2(DEPTH);//对储存器深度取2对数,这是为了得到地址的位宽,后面的地址还要+1这样就得到了有高低位的地址(就格雷码那个表,比原地址位宽多一) reg [ADDR_WIDTH:0] waddr_bin; reg [ADDR_WIDTH:0] raddr_bin;//这分别是读,取地址,后面判断读写操作地址和读空写满操作 //1.1、对读地址进行写判断, always @(posedge wclk or negedge wrstn) begin if(~wrstn) waddr_bin <= 'd0; else if(!wfull && winc)begin //没有写满标志并且写使能拉高则写地址加1 waddr_bin <= waddr_bin +1'd1; end end //1.2、对读地址进行读判断 always @(posedge rclk or negedge rrstn) begin if(~rrstn) raddr_bin <= 'd0; else if(!rempty && rinc)begin //没有读满标志并且读使能拉高则写地址加1 raddr_bin <= raddr_bin +1'd1; end end //2、将地址转为格雷码 wire [ADDR_WIDTH:0] waddr_gray; wire [ADDR_WIDTH:0] raddr_gray;//这是中间变量过程 reg [ADDR_WIDTH:0] wptr; reg [ADDR_WIDTH:0] rptr;//这是最后可用的格雷码,我在想为什么要经过一个中间过程,不能直接去掉上面的中间变量吗 assign waddr_gray = waddr_bin ^(waddr_bin>>1);//格雷码等于和右移1位的自身异或 assign raddr_gray = raddr_bin ^(raddr_bin>>1); /*试了下直接这样写不行 assign wptr = waddr_gray; assign rptr = raddr_gray; */ always @(posedge wclk or negedge wrstn) begin if(~wrstn) wptr <= 'd0; else wptr <= waddr_gray; end always @(posedge rclk or negedge rrstn) begin if(~rrstn) rptr <= 'd0; else rptr <= raddr_gray; end //3、对地址打两拍操作,消除亚稳态 reg [ADDR_WIDTH:0] wptr_buff; reg [ADDR_WIDTH:0] rptr_buff; reg [ADDR_WIDTH:0] wptr_syn; reg [ADDR_WIDTH:0] rptr_syn; always @(posedge rclk or negedge rrstn) begin //这里注意因为是写指针同步到读时钟域,所以这里的时钟用读时钟,下面同理 if(~rrstn)begin wptr_syn <= 'd0; wptr_buff <= 'd0; end else begin wptr_buff <= wptr; wptr_syn <= wptr_buff; end end always @(posedge wclk or negedge wrstn) begin if(~wrstn)begin rptr_syn <= 'd0; rptr_buff <= 'd0; end else begin rptr_buff <= rptr; rptr_syn <= rptr_buff; end end //4、判断写满和读空标志,读写地址若最高两位相反,其他位相同则写满,也就是写已经在第二个周期了读还是第一个周期 assign wfull = (wptr =={~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]}); assign rempty =(rptr==wptr_syn);//若读写地址全都一样则读空了,读写都在一个周期 //5、对ram进行读写操作 wire wen,ren; wire wren; wire [ADDR_WIDTH-1:0] waddr,raddr; assign wen = winc && !wfull;//这是读写使能端,写使能端和非满时写使能端拉高,则可以写入数据 assign ren = rinc && !rempty; assign waddr = waddr_bin[ADDR_WIDTH-1:0]; //因为前面的地址是在真实位宽的地址加了一位最高位,所以真实写入ram的时候要去掉最高位,个人理解前面多加一个最高位是为了判断读写是不是在同一个周期,例如虽然读写真实地址位相同,但写地址已经进行了第二轮而写地址才在第一轮,这和读写都在第一轮混淆了无法判断,所以加一个最高位 assign raddr = raddr_bin[ADDR_WIDTH-1:0]; dual_port_RAM inst( .wclk(wclk), .wenc(wen), .waddr(waddr), .wdata(wdata) , .rclk(rclk) , .renc(ren), .raddr(raddr) , .rdata(rdata) ); endmodule