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]

个人认为这种书写方式也是很清晰的,只需要根据冒号后面的数字大小就能确定位宽,有时候按照普通的写法没这么清晰,有时候还需要计算一下。因此也是值得借鉴学习的。

 

测试代码

 

`timescale 1ns/1ps
module tb_cal1num();

reg     clk;
reg        rst;
reg [31:0]    din;
wire     flag;

initial begin
    clk=0;
    rst=1;
    din=0;
end

always #10 clk = ~clk;

initial begin
    #100;
    rst=0;
    #11
    din=32'hffff00ff;
    #20
    din=0;
    #10
    din=32'hff000000;
end

//inst cal1num
    cal1num inst_cal1num (
            .clk  (clk),
            .rst  (rst),
            .din (din),
            .flag (flag)
        );


endmodule

仿真结果

verilog-统计n位数据中1的个数

由图可以看出输入ffff00ff时cnt高5位结果是24,标志信号在下一个时钟上升沿来临时拉高,而输入是ff000000时,1的数目只有8个,因此标志信号不会拉高。

参考资源

https://wenku.baidu.com/view/1bafd4b2657d27284b73f242336c1eb91a373390.html

https://blog.csdn.net/feiliantong/article/details/107782129

上一篇:寄存器扇出太大如何解决?


下一篇:部分电线电缆产品和标准简介