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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM