之前在博客中实现了位宽相同的同步FIFO,然而,实际的应用场景往往更加复杂,它不仅需要FIFO实现先入先出的功能,有时候还需要实现数据位宽的转换,本文就介绍了如何通过Verilog实现一个具有位宽转换功能的同步FIFO。
写数据低位宽,读数据高位宽
在这种情况下,需要写入RDATA_WIDTH/WDATA_WIDTH个数据,才能读出一个数据,相应的RTL代码如下(主要设置了一个wr_cnt计数器):
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/28 23:17:35
// Design Name:
// Module Name: fifo_low2high
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module fifo_low2high
#(parameter WDATA_WIDTH = 8,
parameter RDATA_WIDTH = 32,
parameter WDEPTH =128)
(
input logic clk,
input logic rst,
input logic wr_en,
input logic rd_en,
input logic [WDATA_WIDTH-1:0] wdata,
output logic [RDATA_WIDTH-1:0] rdata,
output logic full,
output logic empty
);
parameter RDEPTH=WDATA_WIDTH*WDEPTH/RDATA_WIDTH;
parameter ADDR_LEN=$clog2(RDEPTH);
//
logic [ADDR_LEN-1:0] wr_addr;
logic [ADDR_LEN-1:0] rd_addr; //以读数据位宽为单位的地址
logic [ADDR_LEN:0] wr_ptr;
logic [ADDR_LEN:0] rd_ptr;
logic [31:0] wr_cnt;
logic [RDATA_WIDTH-1:0] RAM [0:RDEPTH-1]; //32/8=4,需要二位
//
assign wr_addr=wr_ptr[ADDR_LEN-1:0];
assign rd_addr=rd_ptr[ADDR_LEN-1:0];
//
always_ff@(posedge clk,posedge rst)
if(rst)
wr_ptr<=0;
else if(wr_en&&~full&&wr_cnt==RDATA_WIDTH/WDATA_WIDTH-1) //!
wr_ptr<=wr_ptr+1;
//rd_ptr
always_ff@(posedge clk,posedge rst)
if(rst)
rd_ptr<=0;
else if(rd_en&&~empty)
rd_ptr<=rd_ptr+1;
//read data
always_ff@(posedge clk)
if(rd_en&&~empty)
rdata<=RAM[rd_addr];
//write data
always_ff@(posedge clk)
if(wr_en&&~full)
case(wr_cnt)
2'b00:RAM[wr_addr][31:24]<=wdata;
2'b01:RAM[wr_addr][23:16]<=wdata;
2'b10:RAM[wr_addr][15:8]<=wdata;
2'b11:RAM[wr_addr][7:0]<=wdata;
endcase
//wr_cnt
always_ff@(posedge clk,posedge rst)
if(rst)
wr_cnt<=0;
else if(wr_en&&~full)
if(wr_cnt==RDATA_WIDTH/WDATA_WIDTH-1)
wr_cnt<=0;
else
wr_cnt<=wr_cnt+1;
//full
assign full=(wr_ptr=={~rd_ptr[ADDR_LEN],rd_ptr[ADDR_LEN-1:0]})?1'b1:1'b0;
assign empty=(wr_ptr==rd_ptr)?1'b1:1'b0;
endmodule
相应的测试平台:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/28 23:38:01
// Design Name:
// Module Name: test_tb_low2high
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module test_tb_low2high;
parameter WDATA_WIDTH = 8;
parameter RDATA_WIDTH = 32;
logic clk;
logic rst;
logic wr_en;
logic rd_en;
logic [WDATA_WIDTH-1:0] wdata;
logic [RDATA_WIDTH-1:0] rdata;
logic full;
logic empty;
logic wr_en_r;
logic rd_en_r;
logic error;
logic rd_en_ff;
logic [RDATA_WIDTH-1:0] ref_data;
//clk
initial begin
clk=0;
forever begin
#5 clk=~clk;
end
end
//rst
initial
begin
rst=1;
#100
rst=0;
end
//wr_en_r
always_ff@(posedge clk,posedge rst)
if(rst)
wr_en_r<=0;
else if($urandom%100<90)
wr_en_r<=1;
else
wr_en_r<=0;
//rd_en_r
always_ff@(posedge clk,posedge rst)
if(rst)
rd_en_r<=0;
else if($urandom%100<20)
rd_en_r<=1;
else
rd_en_r<=0;
//rd_en,wr_en
assign rd_en=(~empty)?rd_en_r:1'b0;
assign wr_en=(~full)?wr_en_r:1'b0;
//wdata
always_ff@(posedge clk,posedge rst)
if(rst)
wdata<=0;
else if(wr_en&&~full)
wdata<=wdata+1;
//rd_en_ff
always_ff@(posedge clk)
rd_en_ff<=rd_en;
//error
always_ff@(posedge clk,posedge rst)
if(rst)
error<=0;
else if(rd_en_ff&&rdata!=ref_data)
error<=1;
//ref_data
always_ff@(posedge clk,posedge rst)
if(rst)
ref_data<=32'h00010203;
else if(rd_en_ff)
ref_data<={ref_data[31:24]+8'd4,ref_data[23:16]+8'd4,ref_data[15:8]+8'd4,ref_data[7:0]+8'd4};
//inst
fifo_low2high
#(.WDATA_WIDTH(WDATA_WIDTH),
.RDATA_WIDTH(RDATA_WIDTH),
.WDEPTH (128))
U(.*
// input logic clk,
// input logic rst,
// input logic wr_en,
// input logic rd_en,
// input logic [WDATA_WIDTH-1:0] wdata,
// output logic [RDATA_WIDTH-1:0] rdata,
// output logic full,
// output logic empty
);
endmodule
写数据高位宽,读数据低位宽
这种情况下,平均读取WDATA_WIDTH/RDATA_WIDTH个数据,才能将写入的一个数据清空。其相应的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/28 20:59:43
// Design Name:
// Module Name: fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module fifo
#(parameter WDATA_WIDTH = 32, //assert WDATA_WIDTH>RDATA_WIDTH
parameter RDATA_WIDTH = 8,
parameter WDEPTH = 32)
(
input logic clk,
input logic rst,
input logic wr_en,
input logic [WDATA_WIDTH-1:0] wdata,
input logic rd_en,
output logic [RDATA_WIDTH-1:0] rdata,
output logic full,
output logic empty
);
//
parameter RDEPTH=WDATA_WIDTH*WDATA_WIDTH/RDATA_WIDTH;
//
logic [WDATA_WIDTH-1:0] RAM [0:WDEPTH-1];
logic [$clog2(WDEPTH)-1:0] wr_addr; //无论读写,地址按照高位宽(此处是WDATA_WIDTH)计算
logic [$clog2(WDEPTH)-1:0] rd_addr; //RAM地址
logic [$clog2(WDEPTH):0] wr_ptr; //增加了额外bit的指针
logic [$clog2(WDEPTH):0] rd_ptr;
logic [31:0] rd_cnt;
//rd_cnt
always_ff@(posedge clk,posedge rst)
if(rst)
rd_cnt<=0;
else if(rd_en&&~empty)
if(rd_cnt==WDATA_WIDTH/RDATA_WIDTH-1)
rd_cnt<=0; //0->1->2->3->0
else
rd_cnt<=rd_cnt+1;
//wr_addr,rd_addr
assign wr_addr=wr_ptr[$clog2(WDEPTH)-1:0];
assign rd_addr=rd_ptr[$clog2(WDEPTH)-1:0];
//wr_ptr
always_ff@(posedge clk,posedge rst)
if(rst)
wr_ptr<=0;
else if(wr_en&&~full) //非满且写使能
wr_ptr<=wr_ptr+1; //自增1,指向下一个要写的数据
//rd_ptr
always_ff@(posedge clk,posedge rst)
if(rst)
rd_ptr<=0;
else if(rd_en&&~empty&&rd_cnt==WDATA_WIDTH/RDATA_WIDTH-1) //
rd_ptr<=rd_ptr+1; //自增1,指向下一个要读的数据,读出4个数据才能自增1
//full,empty
assign full=(rd_ptr=={~wr_ptr[$clog2(WDEPTH)],wr_ptr[$clog2(WDEPTH)-1:0]})?1'b1:1'b0;
assign empty=(rd_ptr==wr_ptr)?1'b1:1'b0;
//rdata
always_ff@(posedge clk)
if(rd_en&&~empty)
begin
case(rd_cnt)
2'b00:rdata<=RAM[rd_addr][31:24]; //先读取高字节
2'b01:rdata<=RAM[rd_addr][23:16];
2'b10:rdata<=RAM[rd_addr][15:8];
2'b11:rdata<=RAM[rd_addr][7:0];
endcase
end
//wdata
always_ff@(posedge clk)
if(wr_en&&~full)
RAM[wr_addr]<=wdata;
endmodule
相应的测试平台:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/28 21:21:38
// Design Name:
// Module Name: test_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module test_tb;
parameter WDATA_WIDTH = 32;
parameter RDATA_WDITH = 8;
logic clk;
logic rst;
logic [WDATA_WIDTH-1:0] wdata;
logic [RDATA_WDITH-1:0] rdata;
logic wr_en;
logic rd_en;
logic full;
logic empty;
logic wr_en_r;
logic rd_en_r;
logic error;
logic rd_en_ff;
logic [RDATA_WDITH-1:0] ref_rdata;
//clk
initial begin
clk=0;
forever begin
#5 clk=~clk;
end
end
//rst
initial
begin
rst=1;
#100
rst=0;
end
//wr_en,rd_en
assign wr_en=(~full)?wr_en_r:1'b0;
assign rd_en=(~empty)?rd_en_r:1'b0;
//ref_rdata
always_ff@(posedge clk,posedge rst)
if(rst)
ref_rdata<=0;
else if(rd_en_ff)
ref_rdata<=ref_rdata+1;
//
always_ff@(posedge clk)
rd_en_ff<=rd_en;
//compare
always_ff@(posedge clk,posedge rst)
if(rst)
error<=0;
else if(rd_en_ff&&ref_rdata!=rdata)
error<=1;
//wr_en_r
always_ff@(posedge clk,posedge rst)
if(rst)
wr_en_r<=0;
else
begin
if($urandom%100<25)
wr_en_r<=1;
else
wr_en_r<=0;
end
//rd_en_r
always_ff@(posedge clk,posedge rst)
if(rst)
rd_en_r<=0;
else
begin
if($urandom%100<80)
rd_en_r<=1;
else
rd_en_r<=0;
end
//wdata
always_ff@(posedge clk,posedge rst)
if(rst)
wdata<={8'd0,8'd1,8'd2,8'd3};
else if(wr_en&&~full)
wdata<={wdata[31:24]+8'd4,wdata[23:16]+8'd4,wdata[15:8]+8'd4,wdata[7:0]+8'd4};
//inst
fifo
#(.WDATA_WIDTH(32), //assert WDATA_WIDTH>RDATA_WIDTH
.RDATA_WIDTH(8),
.WDEPTH(32)
)U
(.*
// input logic clk,
// input logic rst,
// input logic wr_en,
// input logic [WDATA_WIDTH-1:0] wdata,
// input logic rd_en,
// output logic [RDATA_WIDTH-1:0] rdata,
// output logic full,
// output logic empty
);
endmodule