verilog-统计n位数据中1的个数
引言
最近在看数字IC面经,遇见一个很有趣的题目:输入一个32位的数据,判断数据中0/1的个数,如果1比0多则下一个时钟周期输出一个标志信号。
我一开始的思路是要在一个时钟周期内完成计算,应该是要用生成循环语句generate,但是平时的项目中几乎没用过这个语句,实在是不熟悉,并且如何用组合逻辑在一拍内完成计算也没想清楚。
后来在网上搜索到一个很不错的思路,现整理如下:
方法一
设计思路
首先要有一个计数器来进行1的累加,计数器的位宽取决于输入数据的位宽,比如输入一个32位的数据,那么最多是32个1,因此位宽为5。这里需要注意如果是输入一个10位的数据,那么应该在计数器设计上留有余量,即设置一个4位的计数器。核心原则就是cnt_width = ceil(log2data_width)。
接着进行计算,将输入数据第一位与第二位相加,结果存在第一个计数器中。再是将第一个计数器与第三位相加,结果存在第二个计数器中。以此类推,最后第32位与第30个计数器相加,结果存在第31个计数器中。这里我们可以声明一个(data_width-1)x(cnt_width)位宽的计数器,在本例中就是31x5=155,所以最终计算的1的数目的大小存在cnt[154:150]里。
最后再用一个时序逻辑进行判断来输出标志信号即可。
RTL代码
1 module cal1num( 2 input wire clk, 3 input wire rst, 4 input wire [31:0] din, 5 output reg flag 6 ); 7 8 parameter data_len = 32; 9 parameter data_wid = 5; 10 11 wire [(data_len-1)*data_wid-1:0] cnt; 12 13 14 generate 15 genvar i; 16 for(i=0;i<data_len-1;i=i+1)begin:get_1_num 17 if (i==0) begin 18 assign cnt[data_wid*(i+1)-1 -: data_wid] = din[i] + din[i+1]; 19 end 20 else begin 21 assign cnt[data_wid*(i+1)-1 -: data_wid] = cnt[data_wid*(i)-1 -: data_wid] + din[i+1]; 22 end 23 end 24 endgenerate 25 26 always @(posedge clk) begin 27 if (rst==1'b1) begin 28 // reset 29 flag <= 1'b0; 30 end 31 else if (cnt[(data_len-1)*data_wid-1 -: data_wid]>16) begin 32 flag <= 1'b1; 33 end 34 else begin 35 flag <= 1'b0; 36 end 37 end 38 39 endmodule
tips:
这里还有个比较独特的书写方式:
关于[-:]和[+:]的含义,比如cnt[7-:1],意思就是从第8位往下减1位也就是cnt[7:6],cnt[7+:1],意思就是加1位,等价于cnt[7:8]
个人认为这种书写方式也是很清晰的,只需要根据冒号后面的数字大小就能确定位宽,有时候按照普通的写法没这么清晰,有时候还需要计算一下。因此也是值得借鉴学习的。
测试代码
1 `timescale 1ns/1ps 2 module tb_cal1num(); 3 4 reg clk; 5 reg rst; 6 reg [31:0] din; 7 wire flag; 8 9 initial begin 10 clk=0; 11 rst=1; 12 din=0; 13 end 14 15 always #10 clk = ~clk; 16 17 initial begin 18 #100; 19 rst=0; 20 #11 21 din=32'hffff00ff; 22 #20 23 din=0; 24 #10 25 din=32'hff000000; 26 end 27 28 //inst cal1num 29 cal1num inst_cal1num ( 30 .clk (clk), 31 .rst (rst), 32 .din (din), 33 .flag (flag) 34 ); 35 36 37 endmodule
仿真结果
由图可以看出输入ffff00ff时cnt高5位结果是24,标志信号在下一个时钟上升沿来临时拉高,而输入是ff000000时,1的数目只有8个,因此标志信号不会拉高。
方法二
这种方法非常简单,就是对输入的每一位进行判断。代码如下所示:
RTL代码
1 module cal1num_simple( 2 input wire clk, 3 input wire rst, 4 input wire [31:0] din, 5 output reg flag 6 ); 7 8 integer i; 9 reg [5:0] ones; 10 11 always @(*) begin 12 ones = 'd0; 13 for (i=0;i<32;i=i+1) begin 14 if (din[i]==1'b1) begin 15 ones = ones + 1; 16 end 17 end 18 end 19 20 always @(posedge clk) begin 21 if (rst==1'b1) begin 22 // reset 23 flag <= 1'b0; 24 end 25 else if (ones > 16) begin 26 flag <= 1'b1; 27 end 28 else begin 29 flag <= 1'b0; 30 end 31 end 32 33 endmodule
测试代码
与前文testbench文件相同
仿真结果
这种方式实际上还是占用了大量资源
方法三
这种方法是对第二种方法的改进,仅仅是改动了如下部分:
经过优化后资源大幅下降
方法四
大道至简:assign ones = din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
参考资源
https://wenku.baidu.com/view/1bafd4b2657d27284b73f242336c1eb91a373390.html
https://blog.csdn.net/feiliantong/article/details/107782129
https://verilogcodes.blogspot.com/search/label/Simple%20Verilog%20codes