HDLBits_Verilog语法基础

HDLBits链接


线信号

对于刚接触Verilog的朋友来说,理解线信号(wire)是第一步。和我们日常生活中的物理电线不同,Verilog中的线信号是有方向性的。这就好比水管里的水流,只能从一个方向流向另一个方向——从驱动端流向接收端。

在Verilog的”连续赋值”语句(assign left_side = right_side;)中,右侧信号的值被驱动到左侧的连接上。这里有个很重要的特点:assign赋值是”连续的”,右侧的值一旦发生变化,左边的值会立即更新,就像水在水管里流动一样,不会有延迟。

理解了这一点,我们就能明白两个重要的规则:

  1. 一个线信号不能由多个驱动源同时驱动——就像一根水管不能同时从两头注水
  2. 如果一个线信号没有任何驱动,输出就是未知的(用x表示)

当存在多个assign语句时,assign出现的顺序和位置不影响结果。这就好比实际电路中连线的顺序不影响最终功能,需要和软件编程的顺序思维区别开。


基础的门操作

区分按位取反(~) 和逻辑取反(!)

这是初学者最容易混淆的地方!让我们用生活中的例子来理解:

  • 按位取反(~): 就像把一排开关逐个按反,每个开关都从0变1,从1变0
  • 逻辑取反(!): 就像判断”这一整排开关是否有打开的”,然后给出一个相反的答案

1

区分按位与(&)和逻辑与(&&)

同样,这两个操作符也有本质区别:

  • 按位与(&): 两个数字的每一位逐一进行与操作,得到一个新的数字
  • 逻辑与(&&): 只关心”两个数是否都非零”,结果只有1位(0或1)

2

区分按位或(|)和逻辑或(||)

A NOR gate is an OR gate with its output inverted.

3

按位异或

异或操作可以理解为”不同则为1,相同则为0”,就像两个开关状态不一样时灯才亮。

4


7458芯片

题目描述:

按照电路图,用Verilog语言描述输入输出间的关系。

5

Solution 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module top_module (
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y
);
// 定义中间连接线
wire and1_out, and2_out, and3_out, and4_out;

// 第一个与门: p2a & p2b
assign and1_out = p2a & p2b;
// 第二个与门: p2c & p2d
assign and2_out = p2c & p2d;
// 第三个与门: p1a & p1b & p1c
assign and3_out = p1a & p1b & p1c;
// 第四个与门: p1d & p1e & p1f
assign and4_out = p1d & p1e & p1f;

// 输出或门
assign p2y = and1_out | and2_out;
assign p1y = and3_out | and4_out;
endmodule

Solution 2:

1
2
3
4
5
6
7
8
9
10
module top_module (
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y
);
// 直接用表达式连接,无需中间变量
assign p2y = (p2a & p2b) | (p2c & p2d);
assign p1y = (p1a & p1b & p1c) | (p1d & p1e & p1f);
endmodule

入门者避坑指南

在学习Verilog语法基础的过程中,初学者最容易犯以下几个错误:

错误1: 多个assign驱动同一个wire

错误表现:

1
2
3
4
5
6
7
module bad_example (
input a, b, c,
output y
);
assign y = a & b;
assign y = b | c; // 错误!y被两个assign同时驱动
endmodule

错误原因:
就像一根水管不能同时从两个方向注水,一个wire也不能有多个驱动源。这会导致综合工具报错,或者产生不确定的结果。

正确做法:

1
2
3
4
5
6
module good_example (
input a, b, c,
output y
);
assign y = (a & b) | (b | c); // 合并成一个assign
endmodule

调试技巧:
如果综合工具报告”multiple drivers”错误,搜索所有assign语句,看看是否有多个语句赋值给同一个信号。


错误2: 混淆按位操作和逻辑操作

错误表现:

1
2
3
4
5
6
7
8
module bad_example (
input [1:0] a, b,
output [1:0] y1,
output y2
);
assign y1 = a && b; // 错误!期望按位与,用了逻辑与
assign y2 = !a; // 错误!期望按位取反,用了逻辑取反
endmodule

错误原因:

  • &&!只产生1位结果(0或1)
  • &~会对每一位进行操作

正确做法:

1
2
3
4
5
6
7
8
module good_example (
input [1:0] a, b,
output [1:0] y1,
output y2
);
assign y1 = a & b; // 按位与
assign y2 = ~|a; // 按位或后取反,或者用&a等其他操作
endmodule

调试技巧:

  • 如果需要N位结果,使用按位操作符(~, &, |, ^)
  • 如果只需要1位布尔结果,使用逻辑操作符(!, &&, ||)

错误3: 忘记assign语句的连续特性

错误表现:

1
2
3
4
5
6
7
8
9
10
module bad_example (
input a, b,
output reg c, d
);
// 错误的理解:以为这是顺序执行的
always @(*) begin
c = a & b;
d = c;
end
endmodule

虽然这个例子本身语法是对的,但初学者经常误解assign和always块的执行方式。

正确理解:

  • assign语句是并行的,同时执行
  • always @()块内的语句是*顺序执行的,但从硬件角度看,它们仍然是组合逻辑

正确做法:

1
2
3
4
5
6
7
8
module good_example (
input a, b,
output c, d
);
// 两个assign同时执行,没有先后顺序
assign c = a & b;
assign d = c;
endmodule

调试技巧:

  • 不要用软件的”顺序执行”思维来理解硬件描述语言
  • 把assign语句想象成电路中的连线,它们是同时工作的

错误4: 不指定位宽的常量

错误表现:

1
2
3
4
5
6
module bad_example (
input [3:0] a,
output [3:0] y
);
assign y = a + 1; // 危险!1没有指定位宽
endmodule

错误原因:
虽然这个例子在大多数情况下能工作,但当位宽不匹配时可能会产生意外结果。显式指定位宽是更好的编码习惯。

正确做法:

1
2
3
4
5
6
module good_example (
input [3:0] a,
output [3:0] y
);
assign y = a + 4'd1; // 明确指定4位宽的1
endmodule

调试技巧:

  • 养成习惯:所有常量都指定位宽
  • 格式: 位宽'进制数值, 例如 8'd255, 4'hf, 1'b1

本章小结

让我们回顾一下这一章学到的核心知识点:

  1. assign连续赋值: 赋值顺序不影响结果,就像电路中的连线是并行工作的
  2. wire驱动规则: 一个wire类型有且只能有一个驱动源,多个驱动会导致错误
  3. 基础门操作: 理解按位操作和逻辑操作的区别,这是设计组合逻辑的基础
  4. 硬件思维: 开始学会从硬件电路的角度思考问题,而不是软件编程的顺序思维

这些基础概念虽然简单,但却是后续设计复杂电路的基石。一定要理解透彻,特别是线信号的方向性和assign语句的并行特性!