输入描述:
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata
输出描述:
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
双口RAM和代码框架:
`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
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
endmodule
同步FIFO,就是我们学习其他经典计算机语言(如C语言)的数据结构时,常说的“队列”了。FIFO即first in first out,数据向内压入堆叠,读取时读下方先压入的数据。不懂的先去了解“队列”!
1. 读写指针控制块:
需要注意“允许写入”和“允许读取”的条件!
reg [$clog2(DEPTH)-1:0] rd_pt; //读指针rd_pt,指向下一个要读出的数据地址。
reg [$clog2(DEPTH)-1:0] wt_pt; //写指针wt_pt,指向下一个要写入的数据地址。
//长度设置成$clog2(DEPTH)而不是$clog2(DEPTH)-1,是为了后面处理存满溢出。
//读写指针控制块
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin //initialize
rd_pt <= 0;
wt_pt <= 0;
end
else begin
if(!wfull && winc) //没有写满,并且写使能:允许写入,写指针+1
wt_pt <= wt_pt + 1;
else if(!rempty && rinc) //没有取空,并且读使能:允许读出,读指针+1
rd_pt <= rd_pt + 1;
else begin
wt_pt <= wt_pt;
rd_pt <= rd_pt;
end
end
end
2. 存满指示wfull和存空指示rempty控制块:
指示当前队列是否存满,可以用读写指针的大小判断来书写。但如果我们另外用一个计数器cnt来记录当前存入了多少,判断就变得非常简单了。但是
//记录当前存放数据量控制块
reg [$clog2(DEPTH):0] data_inside_cnt; //记录当前存放有多少数据的计数器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) data_inside_cnt <= 0;
else if(!wfull && winc) data_inside_cnt <= data_inside_cnt + 1;
else if(!rempty && rinc) data_inside_cnt <= data_inside_cnt - 1;
else data_inside_cnt <= data_inside_cnt;
end
//存满指示wfull和存空指示rempty控制块
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
wfull <= 0;
rempty <= 0;
end
else if(data_inside_cnt == DEPTH) wfull <= 1;
else if(data_inside_cnt == 0) rempty <= 1;
else begin
wfull <= 0;
rempty <= 0;
end
end
3. 实例化双口RAM:
//具体数据存储和读写,交给实例化双向RAM
wire wen_con; //写使能连接线
wire ren_con; //读使能连接线
assign wen_con = ~wfull & winc;
assign ren_con = ~rempty & rinc;
//由于模块间连线只能用wire,现在用wire存储一遍wt_pt和rd_pt
wire [$clog2(DEPTH)-1:0]wt_pt_con;
wire [$clog2(DEPTH)-1:0]rd_pt_con;
assign wt_pt_con = wt_pt;
assign rd_pt_con = rd_pt;
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
u_RAM
(
.wclk(clk), //同步FIFO:wclk---clk
.wenc(wen_con),
.waddr(wt_pt), //写指针wt_pt-----写地址
.wdata(wdata),
.rclk(clk), //同步FIFO:rclk---clk
.renc(ren_con),
.raddr(rd_pt), //读指针rd_pt-----读地址
.rdata(rdata)
);
/**********************************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
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull , //存满指示
output reg rempty , //存空指示
output wire [WIDTH-1:0] rdata
);
reg [$clog2(DEPTH)-1:0] rd_pt; //读指针rd_pt,指向下一个要读出的数据地址。
reg [$clog2(DEPTH)-1:0] wt_pt; //写指针wt_pt,指向下一个要写入的数据地址。
//长度设置成$clog2(DEPTH)而不是$clog2(DEPTH)-1,是为了后面处理存满溢出。
//读写指针控制块
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin //initialize
rd_pt <= 0;
wt_pt <= 0;
end
else begin
if(!wfull && winc) //没有写满,并且写使能:允许写入,写指针+1
wt_pt <= wt_pt + 1;
else if(!rempty && rinc) //没有取空,并且读使能:允许读出,读指针+1
rd_pt <= rd_pt + 1;
else begin
wt_pt <= wt_pt;
rd_pt <= rd_pt;
end
end
end
//记录当前存放数据量控制块
reg [$clog2(DEPTH):0] data_inside_cnt; //记录当前存放有多少数据的计数器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) data_inside_cnt <= 0;
else if(!wfull && winc) data_inside_cnt <= data_inside_cnt + 1;
else if(!rempty && rinc) data_inside_cnt <= data_inside_cnt - 1;
else data_inside_cnt <= data_inside_cnt;
end
//存满指示wfull和存空指示rempty控制块
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
wfull <= 0;
rempty <= 0;
end
else if(data_inside_cnt == DEPTH) wfull <= 1;
else if(data_inside_cnt == 0) rempty <= 1;
else begin
wfull <= 0;
rempty <= 0;
end
end
//具体数据存储和读写,交给实例化双向RAM
wire wen_con; //写使能连接线
wire ren_con; //读使能连接线
assign wen_con = ~wfull & winc;
assign ren_con = ~rempty & rinc;
//由于模块间连线只能用wire,现在用wire存储一遍wt_pt和rd_pt
wire [$clog2(DEPTH)-1:0]wt_pt_con;
wire [$clog2(DEPTH)-1:0]rd_pt_con;
assign wt_pt_con = wt_pt;
assign rd_pt_con = rd_pt;
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
u_RAM
(
.wclk(clk), //同步FIFO:wclk---clk
.wenc(wen_con),
.waddr(wt_pt), //写指针wt_pt-----写地址
.wdata(wdata),
.rclk(clk), //同步FIFO:rclk---clk
.renc(ren_con),
.raddr(rd_pt), //读指针rd_pt-----读地址
.rdata(rdata)
);
endmodule