引言
最近在看數字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