数字电路设计与verilog基础知识
引言
最近在看数字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];