HDLBits刷题笔记

最近试了一下Verilog刷题网站HDLBits,感觉还是蛮有意思的。这里记录一下刷题的一些收获。

Bit slicing的简化语法

Bit slicing ("Indexed vector part select", since Verilog-2001) has an even more compact syntax.

来源:Circuits-Multiplexers-Mux256to1v

1
2
3
4
5
6
7
8
9
module top_module(
input [1023:0] in, // 1024-bit input
input [7:0] sel, // 8-bit select input
output [3:0] out // 4-bit output
);

assign out = in[sel*4 +: 4]; // Compact syntax for bit slicing

endmodule

in[sel*4 +: 4] 是 Verilog-2001 中引入的 “indexed part select” 语法:
sel*4 表示选择从哪个位开始(包含该位)。
+: 4表示从 sel*4 开始,选择 4 位宽的数据。
这个语法非常简洁,并且可以避免合成工具无法推断切片宽度为常数的问题。

类似的还有assign out = in[sel*4+3 -: 4];

加法器

Create a 100-bit binary adder. The adder adds two 100-bit numbers and a carry-in to produce a 100-bit sum and carry out.

正解:没有必要重新构造全加器模块了,直接使用现有的运算符号。

1
2
3
4
5
6
7
8
9
10
11
12
module top_module (
input [99:0] a,
input [99:0] b,
input cin,
output cout,
output [99:0] sum
);

// The concatenation {cout, sum} is a 101-bit vector.
assign {cout, sum} = a+b+cin;

endmodule

卡诺图的两种形式

77.A single-output digital system with four inputs (a,b,c,d) generates a logic-1 when 2, 7, or 15 appears on the inputs, and a logic-0 when 0, 1, 4, 5, 6, 9, 10, 13, or 14 appears. The input conditions for the numbers 3, 8, 11, and 12 never occur in this system. For example, 7 corresponds to a,b,c,d being set to 0,1,1,1, respectively.

Determine the output out_sop in minimum SOP form, and the output out_pos in minimum POS form.

1
2
3
4
5
6
7
8
9
10
11
module top_module (
input a,
input b,
input c,
input d,
output out_sop,
output out_pos
);
assign out_sop = (c&d) | (~a&~b&c&~d);
assign out_pos = c & (~b|d) & (~a|d);
endmodule

Dff8

Create 8 D flip-flops. All DFFs should be triggered by the positive edge of clk.

1
2
3
4
5
6
7
8
9
10
module top_module(
input clk,
input [7:0] d,
output reg [7:0] q);

// Because q is a vector, this creates multiple DFFs.
always @(posedge clk)
q <= d;

endmodule

因为q在这里是一个向量,所以可以一次性创建出8个D触发器。

异步复位

异步复位和同步复位相比,唯一的区别在于always的敏感列表中包含了复位信号reset。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module top_module(
input clk,
input [7:0] d,
input areset,
output reg [7:0] q);

// The only difference in code compared to synchronous reset is in the sensitivity list.
always @(posedge clk, posedge areset)
if (areset)
q <= 0;
else
q <= d;


// In Verilog, the sensitivity list looks strange. The FF's reset is sensitive to the
// *level* of areset, so why does using "posedge areset" work?
// To see why it works, consider the truth table for all events that change the input
// signals, assuming clk and areset do not switch at precisely the same time:

// clk areset output
// x 0->1 q <= 0; (because areset = 1)
// x 1->0 no change (always block not triggered)
// 0->1 0 q <= d; (not resetting)
// 0->1 1 q <= 0; (still resetting, q was 0 before too)
// 1->0 x no change (always block not triggered)

endmodule

Problem 85 : DFF with byte enable(Dff16e)

Create 16 D flip-flops. It's sometimes useful to only modify parts of a group of flip-flops. The byte-enable inputs control whether each byte of the 16 registers should be written to on that cycle. byteena[1] controls the upper byte d[15:8], while byteena[0] controls the lower byte d[7:0].

resetn is a synchronous, active-low reset.

All DFFs should be triggered by the positive edge of clk.

这道题的要求是创建一个 16 路 D触发器。部分情况下,只需要多路触发器中的一部分触发器工作,此时可以通过 ena 使能端进行控制。使能端 ena 信号有效时,触发器在时钟上升沿工作。

注意:当byteena=2'b11时,两个部分均可被写入,因此要慎重选择if~else的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module top_module (
input clk,
input resetn,
input [1:0] byteena,
input [15:0] d,
output [15:0] q
);

always @(posedge clk) begin
if(~resetn)
q <= 16'b0;
else if(byteena[1] || byteena[0] ) begin//注意这个地方要兼顾2'b11的情况
if(byteena[1])
q[15:8] <= d[15:8];
if(byteena[0])
q[7:0] <= d[7:0];
end
end

endmodule

边沿捕获

96.当reset信号为高时,强制输出为低电平;当输入信号in出现下降沿时,输出out保持为1,也就是说只有reset能将out拉低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module top_module (
input clk,
input reset,
input [31:0] in,
output [31:0] out
);
reg [31:0] temp;

always @(posedge clk) begin

temp <= in;//record the previous state.

if(reset)
out <= 32'b0;
else
out = (temp & ~in) | (out);//这里是关键,|out 保证了原来是1的位仍保持为1
end

endmodule

双边沿触发器

Verilog的always语句块的敏感变量列表不能同时支持posedge和negedge,自己单独搭建电路实现双边沿触发的功能。

时序图如下:

利用二选一数据选择器的实现思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module top_module (
input clk,
input d,
output q
);
reg q1, q2;
//这里来实现clk的上升沿与下降沿
assign q = clk?q1:q2;

always @ (posedge clk)
begin
q1 <= d;
end

always @ (negedge clk)
begin
q2 <= d;
end

endmodule

暂停计数器

Problem 101 Slow decade counter

在时钟上升沿对0~9递增计数,高电平复位,当slowena为低时暂停计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module top_module (
input clk,
input slowena,
input reset,
output [3:0] q);

always @(posedge clk) begin
if(reset)
q <= 4'b0;
else if (slowena)//the counter can increment only when slowena is '1'
begin
if (q == 4'd9) //counter overflow
q <= 4'b0;
else //normal counting
q <= q + 1'b1;
end
end
endmodule

Problem 104 4-digit decimal counter

Build a 4-digit BCD (binary-coded decimal) counter. Each decimal digit is encoded using 4 bits: q[3:0] is the ones digit, q[7:4] is the tens digit, etc. For digits [3:1], also output an enable signal indicating when each of the upper three digits should be incremented.

You may want to instantiate or modify some one-digit decade counters.

这道题要求我们设计一个四位BCD码计数器。由于每一位BCD码要用4位二进制数表示,因此输出为16位二进制数。基本思路是先建立一个一位BCD码计数器的基本单元,再实例化得到4位的。

个人觉得这里比较难搞的地方是怎么处理进位。在组合逻辑电路中,4位BCD码加法器的进位只需要把低位端的进位输出连接到高位的进位输入即可。对于时序逻辑来说,进位也可以理解为使能,计数可以理解为翻转,只有当计数溢出时才使能一次高位,使高位在时钟信号到来的时候翻转一次。

以T触发器构成的计数器为例,T触发器有以下特征:

  • 当使能信号T=1时,每次触发会使输出翻转;
  • 当使能信号T=0时,输出保持不变;

计数器在递增计数的过程中,计数值的变化体现为位的翻转,低位对高位的影响体现在只有当低位全为1时高位才可翻转(进位),用激励方程来描述就是

对应的硬件电路为

对于BCD计数器来说也是一样的道理。4位BCD计数器由4个BCD技术单元组成,每一个单元都有使能端,以最高位为例,只有当四位BCD的低三位全为4'b1001时,最高位才被使能,这只需要用与门就可以实现,类似上图中的G1,G2,G3。用Verilog来描述的话,可以是.ena(q[11:8] == 4'd9 && q[7:4] == 4'd9 && q[3:0] == 4'd9),或者直接写为.ena(q[11:0] == 12'h999),,二者是等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
module top_module (
input clk,
input reset, // Synchronous active-high reset
output [3:1] ena,
output [15:0] q);

BCDadd counter0 (
.reset(reset),
.ena(1'b1),
.clk(clk),
.q(q[3:0])
);
BCDadd counter1 (
.reset(reset),
.ena(q[3:0] == 4'b1001),
.clk(clk),
.q(q[7:4])
);
BCDadd counter2 (
.reset(reset),
.ena(q[7:0] == 8'h99),
.clk(clk),
.q(q[11:8])
);
BCDadd counter3 (
.reset(reset),
.ena(q[11:0] == 12'h999),
.clk(clk),
.q(q[15:12])
);

assign ena[3:1] = {q[11:0]==12'h999, q[7:0] == 8'h99, q[3:0] == 4'b1001};

endmodule

module BCDadd (
input reset,
input ena,
input clk,
output reg [3:0] q
);
always @(posedge clk) begin
if (reset)
q <= 4'b0;
else if (ena) begin
if (q == 4'b1001)
q <= 4'b0;
else
q <= q + 1'b1;
end
end
endmodule

LFSR

Linear-feedback shift register,线性反馈移位寄存器 未完待续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module top_module (
input [2:0] SW, // R
input [1:0] KEY, // L and clk
output reg [2:0] LEDR
); // Q

wire clk = KEY[0];
wire l = KEY[1];
wire [2:0] d = l ? SW : {LEDR[1]^LEDR[2],LEDR[0],LEDR[2]};

always @(posedge clk) begin
LEDR <= d;
end

endmodule

Rule 90

Rule90是一种一维“元胞自动机”(cellular automaton),在每个时间步骤,每个细胞的下一个状态是它两个当前邻居的异或(XOR)值。

In this circuit, create a 512-cell system (q[511:0]), and advance by one time step each clock cycle. The load input indicates the state of the system should be loaded with data[511:0]. Assume the boundaries (q[-1] and q[512]) are both zero (off).

官方解答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module top_module(
input clk,
input load,
input [511:0] data,
output reg [511:0] q);

always @(posedge clk) begin
if (load)
q <= data; // 给D触发器赋值
else begin
// 在每个时钟周期,D触发器储存的值变成它的左右邻居的异或值
// 因此对每一位进行的操作是相同的
// 这可以用数组很简单地表达出来
// 使用部分选择和连接符描述移位操作
q <= {1'b0,q[511:1]} ^ {q[510:0], 1'b0} ;
end
end
endmodule
0 q[511] q[510] q[2] q[1]
q[510] q[509] q[508] q[0] 0

可以看出每来一个时钟沿,q的每一位都会变成相邻两位的异或。


HDLBits刷题笔记
http://example.com/2024/11/28/Verilog/
作者
Ep19
发布于
2024年11月28日
许可协议