在数据的处理过程中,常常需要将数据的位宽进行转换。使用Xilinx提供的FIFO IP能够满足大部分位宽转换的使用场景,但是当面对输入与输出的位宽不是2^N(N只能为-3,-2,-1,0,1,2,3)倍时Xilinx所提供的FIFO IP就用不上了。

图1,某个功能需要将数据从32路转换到50路
这里博主提供两种方法来应对输入与输出不是2^N(N只能为-3,-2,-1,0,1,2,3)倍时的位宽转换,分别是使用移位寄存器和使用FIFO两种方式。我们先做一下简单的定义,使用I_WIDTH表示输入的位宽,O_WIDTH表示输出的位宽,N_frac = O_WIDTH/I_WIDTH表示转换倍率,这里的N_frac可以为任何的正值,使用din表示输入的数据,位宽是[I_WIDTH-1:0],din_vld为表示其有效信号,使用dout表示输出的数据,位宽是[O_WIDTH-1:0],dout_vld为表示其有效信号。
一,使用移位寄存器
使用移位寄存器的优点是结构方便,实现简单,但是当转换的位宽较大时会需要面临高扇出的问题,通常不好降低扇出。
1,直接移位
直接移位的实现方式是,对输入的数据进行计数,并且使用移位寄存器寄存,当移入的数据位宽大于输出的数据位宽的时候,移出输出的数据并给出相应的有效信号。
首先需要知道的是使用多大的移位寄存器,当I_WIDTH<O_WIDTH的时候,我们至少需要保证移位寄存器里面能够存储两次I_WIDTH的数据,一次O_WIDTH的数据,所以位宽需要定义成I_WIDTH+O_WIDTH,除此之外还需要一个计数器来计数输入数据以知道何时移出。代码实现如下:
localparam TEMP_DATA_WIDTH = I_WIDTH + O_WIDTH ;
localparam COUNT_WIDTH = $clog2(I_WIDTH + O_WIDTH+1);
reg [ COUNT_WIDTH-1:0] cnt_flop ;
reg [TEMP_DATA_WIDTH-1:0] data_shift_in;
always @(posedge clk) begin
if(din_vld) begin
data_shift_in <= {data_shift_in[TEMP_DATA_WIDTH-1-I_WIDTH:0],din};
end else begin
data_shift_in <= data_shift_in;
end
end
always @(posedge clk) begin
if(rst) begin
cnt_flop <= 'd0;
end else if(din_vld) begin
if(cnt_flop >= O_WIDTH) begin
cnt_flop <= cnt_flop + I_WIDTH - O_WIDTH;
end else begin
cnt_flop <= cnt_flop + I_WIDTH;
end
end else begin
cnt_flop <= cnt_flop;
end
end
always @(posedge clk) begin
if(din_vld && cnt_flop >= O_WIDTH) begin
dout <= data_shift_in[cnt_flop-1-:O_WIDTH];
dout_vld <= 1'b1;
end else begin
dout <= dout;
dout_vld <= 1'b0;
end
end
2,计数器移位
如果N_frac有一定规律,可以根据规律定制移位,这样可以以较低的扇出就能实现对应的功能。
比如I_WIDTH为16,O_WIDTH为25,我们可以拆成两个级联的I_WIDTH为4,O_WIDTH为5的模块,即16/25=(4/5)*(4/5),每个模块只需要一个逢5进位的计数器即可实现功能。
二,使用自建FIFO
使用FIFO的好处是时序负担较轻松,输入和输出的位宽可以更大,但是会面临着RAM占用较高的问题。由于Xilinx提供的FIFO无法胜任任意位宽转换的工作,所以自己编写FIFO是必要的。FIFO内的存储资源可以根据Xilinx提供的综合属性ram_style(例如(* ram_style=“block” *)) 来进行更改,这样可以直接使用BLOCK RAM的资源减少逻辑资源负担。FIFO不仅需要输入位宽可以配置,输入的深度也需要可以配置,并且输入数据的深度必须是输入位宽与输出位宽的最小公倍数。
三,总结
目前位宽转换功能无论写法,或多或少都有对应的缺点,这可能也是Xilinx没有提供相应IP的原因之一,所以设计过程中应该尽量减少任意倍率的位宽转换的情况,尽量使用官方提供的IP来实现。