Loading

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
View Code

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
View Code

仿真结果

由图可以看出输入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
View Code

测试代码

与前文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

posted @ 2022-03-02 10:21  月光小猪(已长膘)  阅读(9789)  评论(2编辑  收藏  举报