最近在工程实践中用到了onehot2bin这个模块,发现它的写法和直观上的理解有一点差距,并不是很好理解,有必要记录一下。
首先要提出我们的需求:给定一个任意长度的onehot编码,也就是只有1bit为1的一串二进制数,如何用组合逻辑找到这个硕果仅存的1的位置?
本着尽可能提高代码重用性的精神,我们创建一个参数化的模块:
module onehot2bin #(
parameter WIDTH = 8,
parameter BIN_WD = $clog2(WIDTH)
)(
input [WIDTH-1:0] onehot_num,
output [BIN_WD-1:0] bin_num
);
endmodule
因为WIDTH有可能并不等于2^n,这会导致后面的处理比较难受,所以我们很自然地要去把这个onehot_num高位补齐到2^n个bit:
// power_two function
function integer power_two;
input integer number; begin
power_two = (number <= 0) ? 1 : 2 << (number - 1);
end
endfunction
localparam TRUE_ONEHOT_WD = power_two(BIN_WD);
wire [TRUE_ONEHOT_WD-1:0] true_onehot_num;
assign true_onehot_num = {TRUE_ONEHOT_WD{1'b0}} | onehot_num;
这里的true_onehot_num就是高位补0之后的onehot_num,接下来就是产生bin_num的功能逻辑了,乍一看简直是一段magic code:
wire [TRUE_ONEHOT_WD-1:0] or_bit [BIN_WD-1:0];
generate
genvar i, j;
for(i = 0; i < BIN_WD; i = i + 1) begin : loop_1
for(j = 0; j < TRUE_ONEHOT_WD; j = j + 1) begin : loop_2
if((j % power_two(i+1)) < power_two(i))
assign or_bit[i][j] = true_onehot_num[j];
else
assign or_bit[i][j] = 1'b0;
end
assign bin_num[i] = ~|(or_bit[i]);
end
唯象地看,bin_num的每个bit为1,表示对应的一行or_bit(长度和onehot码相同)全为0,而每一行or_bit都在固定位置存在一些0,其他位置则是在寻找onehot码的那个1,wtf?
个人理解,这段代码难懂之处在于它是从bin_num的每个bit出发,倒推它们的组合逻辑,这是纯硬件思想,和我们惯常的正向思考是相悖的(毕竟谁会首先想到自己拎起自己)。
为了简便,我们先假设WIDTH = 8,BIN_WD = 3,来分析一下每一层循环都做了什么事情:
i = ? | if判断条件 | or_bit检查onehot 的位置 | or_bit固定为0 的位置 | or_bit全0的含义 |
0 | j % 2 < 1 | 0, 2, 4, 6 | 1, 3, 5, 7 | bin_num是奇数 |
1 | j % 4 < 2 | 0, 1, 4, 5 | 2, 3, 6, 7 | bin_num包含奇数个2 |
2 | j % 8 < 4 | 0, 1, 2, 3 | 4, 5, 6, 7 | bin_num包含奇数个4 |
这样看起来就很清楚了,这个模块相当于把bin_num的每个bit理解为1/2/4个数的奇偶性,例如bin_num = 6 = 0*1 + 1*2 + 1*4 = 3‘b110,也就是每个bit的在二进制当中的本来含义,完整代码如下:
module onehot2bin #(
parameter WIDTH = 8,
parameter BIN_WD = $clog2(WIDTH)
)(
input [WIDTH-1:0] onehot_num,
output [BIN_WD-1:0] bin_num
);
// power_two function
function integer power_two;
input integer number; begin
power_two = (number <= 0) ? 1 : 2 << (number - 1);
end
endfunction
localparam TRUE_ONEHOT_WD = power_two(BIN_WD);
genvar i, j;
wire [TRUE_ONEHOT_WD-1:0] true_onehot_num;
wire [TRUE_ONEHOT_WD-1:0] or_bit [BIN_WD-1:0];
assign true_onehot_num = {TRUE_ONEHOT_WD{1'b0}} | onehot_num;
generate
for(i = 0; i < BIN_WD; i = i + 1) begin : loop_1
for(j = 0; j < TRUE_ONEHOT_WD; j = j + 1) begin : loop_2
if((j % power_two(i+1)) < power_two(i))
assign or_bit[i][j] = true_onehot_num[j];
else
assign or_bit[i][j] = 1'b0;
end
endgenerate
endmodule
日拱一卒,今天又是有收获的一天。