摘要:Verilog 位宽转换 参数化设计
1、应用场景
Verilog位宽转换应用场景很多,比如ADC数据采集存储,CameraLink数据采集存储等很多需要进行位宽转换的地方都需要用到。换句话说,只要输入输出位宽不匹配,就会用到。常规的位宽转换方法有FIFO IP、以及移位寄存器等。但如果输入输出位宽之间不是整数倍的关系又该如何处理?大概都会想到用移位寄存器+计数器+数据选择器实现。那么有没有一种通用写法呢?这里说的通用写法也就是可不可以实现参数化。
2、工程实现
我想到的一种实现方式是参考FIFO的思想,如果有人有其他更好的方法欢迎交流学习。和一般的FIFO只有一维指针不同,这里用到了二维指针。大致示意图如下:
模块代码:
// 时间:2023-03-17
// 作者:xiaoxingdlx(1957158385@qq.com)
// 模块:位宽转换示例
module unit_wide_convert #(
parameter simTdly = 1, // 仿真延时参数
parameter dinWidth = 4, // 输入数据位宽
parameter doutWidth = 8, // 输出数据位宽
parameter bufWidth = 16, // 缓存数据位宽(输入和输出位宽最小公倍数的整数倍)
parameter bufDeepth = 1, // 缓存数据深度(2^N)
parameter ptrWidthW = 2, // 写指针位宽(内部指针,根据缓存数据位宽/输入数据位宽-1为最大值计算)
parameter ptrWidthR = 1 // 读指针位宽(内部指针,根据缓存数据位宽/输出数据位宽-1为最大值计算)
) (
input wire clk, // 时钟
input wire reset, // 复位,高有效
input wire [dinWidth-1:0] din, // 数据输入
input wire din_vld, // 数据输入valid信号
output wire din_rdy, // 数据输入ready信号
output wire [doutWidth-1:0] dout, // 数据输出
output wire dout_vld, // 数据输出valid信号
input wire dout_rdy // 数据输出ready信号
);
// 获取二维指针内部指针的最大计数值
localparam WR_PTR_1_MAX = bufWidth/dinWidth - 'd1;
localparam RD_PTR_1_MAX = bufWidth/doutWidth - 'd1;
// 数据缓存
reg [bufWidth-1:0] buffer [2**bufDeepth-1:0];
// 二维写指针
reg [bufDeepth:0] wr_ptr_0; // 外部指针
reg [ptrWidthW-1:0] wr_ptr_1; // 内部指针
// 二维读指针
reg [bufDeepth:0] rd_ptr_0; // 外部指针
reg [ptrWidthR-1:0] rd_ptr_1; // 内部指针
// 二维写指针控制逻辑
always @(posedge clk ) begin
if (reset) begin
wr_ptr_0 <= #simTdly 'd0;
wr_ptr_1 <= #simTdly 'd0;
end else begin
if (din_vld && din_rdy && wr_ptr_1 == WR_PTR_1_MAX) begin
wr_ptr_0 <= #simTdly wr_ptr_0 + 'd1;
end
if (din_vld && din_rdy && wr_ptr_1 == WR_PTR_1_MAX) begin
wr_ptr_1 <= #simTdly 'd0;
end else if (din_vld && din_rdy) begin
wr_ptr_1 <= #simTdly wr_ptr_1 + 'd1;
end
end
end
// 二维读指针控制逻辑
always @(posedge clk ) begin
if (reset) begin
rd_ptr_0 <= #simTdly 'd0;
rd_ptr_1 <= #simTdly 'd0;
end else begin
if (dout_vld && dout_rdy && rd_ptr_1 == RD_PTR_1_MAX) begin
rd_ptr_0 <= #simTdly rd_ptr_0 + 'd1;
end
if (dout_vld && dout_rdy && rd_ptr_1 == RD_PTR_1_MAX) begin
rd_ptr_1 <= #simTdly 'd0;
end else if (dout_vld && dout_rdy) begin
rd_ptr_1 <= #simTdly rd_ptr_1 + 'd1;
end
end
end
// 数据缓存写入读出
always @(posedge clk ) begin
if (din_vld && din_rdy) begin
buffer[wr_ptr_0[0 +: bufDeepth]] <= #simTdly (buffer[wr_ptr_0[0 +: bufDeepth]] << dinWidth) | din;
end
if (dout_vld && dout_rdy) begin
buffer[rd_ptr_0[0 +: bufDeepth]] <= #simTdly (buffer[rd_ptr_0[0 +: bufDeepth]] << doutWidth);
end
end
// 握手信号
assign din_rdy = (wr_ptr_0[bufDeepth] != rd_ptr_0[bufDeepth] && wr_ptr_0[0 +:bufDeepth] == rd_ptr_0[0 +:bufDeepth]) ? 1'b0 : 1'b1;
assign dout_vld = (wr_ptr_0 == rd_ptr_0) ? 1'b0 : 1'b1;
// 数据输出
assign dout = buffer[rd_ptr_0[0 +:bufDeepth]][(bufWidth-doutWidth) +: doutWidth];
//
/*
unit_wide_convert #(
.simTdly (simTdly ),
.dinWidth (dinWidth ),
.doutWidth (doutWidth ),
.bufWidth (bufWidth ),
.bufDeepth (bufDeepth ),
.ptrWidthW (ptrWidthW ),
.ptrWidthR (ptrWidthR )
)
u_unit_wide_convert(
.clk (clk ),
.reset (reset ),
.din (din ),
.din_vld (din_vld ),
.din_rdy (din_rdy ),
.dout (dout ),
.dout_vld (dout_vld ),
.dout_rdy (dout_rdy )
);
*/
endmodule
仿真模块:
//~ `New testbench
`timescale 1ns / 1ps
module tb_unit_wide_convert;
// unit_wide_convert Parameters
parameter PERIOD = 10;
parameter simTdly = 1; // 仿真延时参数
parameter dinWidth = 4; // 输入数据位宽
parameter doutWidth = 8; // 输出数据位宽
parameter bufWidth = 16; // 缓存数据位宽(输入和输出位宽最小公倍数的整数倍)
parameter bufDeepth = 1; // 缓存数据深度(2^N)
parameter ptrWidthW = 2; // 写指针位宽(内部指针,根据缓存数据位宽/输入数据位宽-1为最大值计算)
parameter ptrWidthR = 1; // 读指针位宽(内部指针,根据缓存数据位宽/输出数据位宽-1为最大值计算)
// unit_wide_convert Inputs
reg clk = 1 ;
reg reset = 1 ;
reg [dinWidth-1:0] din = 0 ;
reg din_vld = 1 ;
reg dout_rdy = 1 ;
// unit_wide_convert Outputs
wire din_rdy ;
wire [doutWidth-1:0] dout ;
wire dout_vld ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2+1) reset = 0;
end
always @(posedge clk ) begin
if (reset) begin
din <= #simTdly 'd0;
end else if (din_vld && din_rdy) begin
din <= #simTdly din + 'd1;
end
end
unit_wide_convert #(
.simTdly (simTdly ),
.dinWidth (dinWidth ),
.doutWidth (doutWidth ),
.bufWidth (bufWidth ),
.bufDeepth (bufDeepth ),
.ptrWidthW (ptrWidthW ),
.ptrWidthR (ptrWidthR )
)
u_unit_wide_convert(
.clk (clk ),
.reset (reset ),
.din (din ),
.din_vld (din_vld ),
.din_rdy (din_rdy ),
.dout (dout ),
.dout_vld (dout_vld ),
.dout_rdy (dout_rdy )
);
endmodule
仿真波形:
仿真模块:
//~ `New testbench
`timescale 1ns / 1ps
module tb_unit_wide_convert;
// unit_wide_convert Parameters
// parameter PERIOD = 10;
// parameter simTdly = 1; // 仿真延时参数
// parameter dinWidth = 4; // 输入数据位宽
// parameter doutWidth = 8; // 输出数据位宽
// parameter bufWidth = 16; // 缓存数据位宽(输入和输出位宽最小公倍数的整数倍)
// parameter bufDeepth = 1; // 缓存数据深度(2^N)
// parameter ptrWidthW = 2; // 写指针位宽(内部指针,根据缓存数据位宽/输入数据位宽-1为最大值计算)
// parameter ptrWidthR = 1; // 读指针位宽(内部指针,根据缓存数据位宽/输出数据位宽-1为最大值计算)
parameter PERIOD = 10;
parameter simTdly = 1; // 仿真延时参数
parameter dinWidth = 5; // 输入数据位宽
parameter doutWidth = 8; // 输出数据位宽
parameter bufWidth = 40; // 缓存数据位宽(输入和输出位宽最小公倍数的整数倍)
parameter bufDeepth = 1; // 缓存数据深度(2^N)
parameter ptrWidthW = 3; // 写指针位宽(内部指针,根据缓存数据位宽/输入数据位宽-1为最大值计算)
parameter ptrWidthR = 3; // 读指针位宽(内部指针,根据缓存数据位宽/输出数据位宽-1为最大值计算)
// unit_wide_convert Inputs
reg clk = 1 ;
reg reset = 1 ;
reg [dinWidth-1:0] din = 0 ;
reg din_vld = 1 ;
reg dout_rdy = 1 ;
// unit_wide_convert Outputs
wire din_rdy ;
wire [doutWidth-1:0] dout ;
wire dout_vld ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2+1) reset = 0;
end
always @(posedge clk ) begin
if (reset) begin
din <= #simTdly 'd0;
end else if (din_vld && din_rdy) begin
din <= #simTdly din + 'd1;
end
end
unit_wide_convert #(
.simTdly (simTdly ),
.dinWidth (dinWidth ),
.doutWidth (doutWidth ),
.bufWidth (bufWidth ),
.bufDeepth (bufDeepth ),
.ptrWidthW (ptrWidthW ),
.ptrWidthR (ptrWidthR )
)
u_unit_wide_convert(
.clk (clk ),
.reset (reset ),
.din (din ),
.din_vld (din_vld ),
.din_rdy (din_rdy ),
.dout (dout ),
.dout_vld (dout_vld ),
.dout_rdy (dout_rdy )
);
endmodule
仿真波形:
上面标注的是0,1,2,3,4,5,6,7(后面忘标了)
上面标注的是8,9,10,11,12,13,14,15,16...