module

  • module是verilog中的基础封装,表示设计的一个模块

  • module的参数表定义了input 和 output

1
2
3
4
5
6
7
8
9
10

module Test (
input in, // input 关键字表示他是input的接口
output out, // output同理
);

/* 这里是线路连接和逻辑处理的地方 */

endmodule

assign

  • verilog中所有线路都是单向的,通过assign语句连接
1
2
3
4
5
6
7
8
9
10
module Test (
input in, // input 关键字表示他是input的接口
output out, // output同理
);

/* 这里是线路连接和逻辑处理的地方 */
assign out = in; // 表示构建一个单向线路 in -> out

endmodule

  • 注意,这里的 = 是描述硬件的链接的,和传统语言中的赋值不一样,他是永久有效的,表示一个单向连接线!

  • 所以同一个连接方法,不同的assign顺序不一样的!

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
/*以下两种链接方法等价!*/
module Test (
input a,
input b,
input c,
output w,
output x,
output y,
output z
);

/* 这里是线路连接和逻辑处理的地方 */
assign w = a;
assign x = b;
assign y = b;
assign z = c;

endmodule

module Test (
input a,
input b,
input c,
output w,
output x,
output y,
output z
);

/* 这里是线路连接和逻辑处理的地方 */
assign w = a;
assign y = b; // 只是硬件描述,和顺序无关!
assign x = b;
assign z = c;

endmodule

基础逻辑门

  • verilog 的逻辑门实现很简单,因为verilog支持所有的c++运算符

  • not gate:

1
2
3
4
5
6
7
8
9
module Invertor (
input in, // input 关键字表示他是input的接口
output out, // output同理
);

/* 这里是线路连接和逻辑处理的地方 */
assign out = ~in; // in 取 ~ 按位取反之后赋值给out

endmodule
  • and/or/xor gate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module AND( 
input a,
input b,
output out );
assign out = a & b;
endmodule

module OR(
input a,
input b,
output out );
assign out = a | b; // 你可以用c++运算符来计算两个线值经过逻辑门后的结果,然后assign给out
endmodule

module XOR(
input a,
input b,
output out );
assign out = a ^ b;
endmodule
  • nor 复合运算
1
2
3
4
5
6
7
8

module NOR(
input a,
input b,
output out );
assign out = ~(a | b); // 甚至支持复合运算!
endmodule


内部连接线 wire

  • 以上运算都是可以通过单个逻辑门实现的(input直接assign到output上面),但是对于复杂逻辑电路,我们却不能这么做

  • 我们需要通过 wire 来设计内部连接线

  • eg:

  • wire 本质上是一个中转用的“临时变量”(本质是导线,并不储存数据!!!)
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
// wire 相当于提供一个中转(也是单向的!)
module top_module (
input in, // Declare an input wire named "in"
output out // Declare an output wire named "out"
);

wire not_in; // wire本质上是一个中转用的“变量”
// 注意,以下链接顺序可变
assign out = ~not_in; // Assign a value to out (create a NOT gate).
assign not_in = ~in; // Assign a value to not_in (create another NOT gate).

endmodule // End of module "top_module"

// assign顺序没有关系,反正只是描述硬件内部!
module top_module (
input in, // Declare an input wire named "in"
output out // Declare an output wire named "out"
);

wire not_in; // Declare a wire named "not_in"

assign not_in = ~in;
assign out = ~not_in;

endmodule // End of module "top_module"

  • 练习: wire 的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
`default_nettype none // 没有强调 wire 的 `变量` 原本会默认为wire,这个加上后就禁止默认了
module top_module(
input a,
input b,
input c,
input d,
output out,
output out_n );

wire w1; // a_and_b
wire w2; // c_and_d
wire w3; // w1_or_w2
// wire w1, w2, w3; //也可以合并定义!
assign w1 = a & b;
assign w2 = c & d;
assign w3 = w1 | w2;
assign out = w3;
assign out_n = ~w3;
endmodule

vector向量(bitwise 操作符 与 logical 操作符)

  • vector 是用于组合多个比特的数,比如传递 3 bit 的数的线 就要定义成 wire [2:0] a;, 这里 [2:0] 可以自己改,但是从MSB -> LSB递减,从n-1 : 0 是规范

  • 你甚至可以定义负数索引! wire [2:-1] a

  • vecotor 可以单独取出某个值(或者某组值)传递给复合这组值位宽的线:

1
2
3
4
5
6
7
8
9
10
11

module MyOR(
input [7:0] a,
input [7:0] b,
output[7:0] out
output out_logical);

assign out[3:0] = a[4:1] | b[5:2]; // 运算符可以对bitwise有效!
assign out_logical = a[4:1] || b[5:2]; // 和c++一样,也有logical 操作符,他把整个多比特数当做整体,除非全0,否则为true!

endmodule
  • logical 和 bitwise: 与 C++中的相同 (logical 操作符除非 整个vector都是0(此时为false值),否则为true值)

  • 操作符两端位宽最好一致,不一致会进行extend: 如果操作数的位宽不一致,Verilog 会对较短的操作数进行位扩展以匹配较长的操作数的位宽。扩展的方式取决于操作数的类型:

    • 对于无符号数,扩展的位会用 0 填充。
    • 对于有符号数,扩展的位会用符号位(最左边的位)的值填充,这种方式称为符号扩展。

有符号与无符号

  • 所有reg, wire,input,output值默认无符号,但是可以通过signed关键字添加符号!
1
wire signed [15:0] signed_wire;

vector 拼接运算符 {}:

  • vector 可以被非常简单的拼接在一起 (用集线器实现):
1
2
3
4
5

wire [7:0] a;

assign a = {c[3:0], b[3:0]}; // a = [c3, c2, c1, c0, b3, b2, b1, b0]

  • eg:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module( 
input [2:0] a,
input [2:0] b,
output [2:0] out_or_bitwise,
output out_or_logical,
output [5:0] out_not
);
assign out_or_bitwise = a | b;
assign out_or_logical = a || b;

assign out_not = {~b, ~a}; //与下文等价
// assign out_not[2:0] = ~a; // Part-select on left side is o.
// assign out_not[5:3] = ~b; //Assigning to [5:3] does not conflict with [2:0]
endmodule
  • 拼接运算符可以作为左值也可以作为右值!!!!

  • 按照以下逻辑分配:

    • out1 会得到拼接向量的最高4位。
    • out2 会得到拼接向量的低5位。
1
2
3
4
5
6
7
8
9
10
module top_module(  
input [2:0] a,
input [2:0] b,
output [3:0]out1,
output [4:0]out2,
);

assign {out1, out2} = {1'b1, a, 2'b01, b};

endmodule
  • eg:
1
2
3
4
5
6
7
8
module top_module (
input [4:0] a, b, c, d, e, f,
output [7:0] w, x, y, z );//

// assign { ... } = { ... };
assign{w,x,y,z} = {a,b,c,d,e,f,2'b11};
endmodule


重复运算符 {num{...}}:

  • 可以用{num{...}}重复一段字符:
1
2
3
4
5
6
7
8
9
10
11

{5{2'b01}} // 01 01 01 01 01 (01 重复5次)

wire a; // 甚至可以重复wire或者reg的值

assign a = 2'b01;

assign b = 2'b11;

assign out = {2{a, b}} // out: 0111 0111


output 与 output wire, output reg

  • output 默认 output wire,但是也可以定义为 output reg, 输出寄存器!

wire [0:0] x, wire [1:1] x 与 wire x 的区别:

在 Verilog 中,定义 wire [0:0] x;wire [1:1] x;wire x; 实际上涉及到了信号的位宽和索引范围,但它们都用于表示单比特的线(wire)。这些声明之间的区别主要体现在索引和表达的形式上,下面是对每种声明方式的解析:

wire [0:0] x;

  • 这种声明方式定义了一个单比特的线 x,其位索引为 [0:0],表示 x 只有一个位,且这个位的索引是 0。这是一种显式地指出位索引范围的声明方式。
  • 这样声明的线可以在需要强调位索引时使用,比如在多位信号与单比特信号之间进行位选择或赋值时增加代码的清晰度。

wire [1:1] x;

  • 类似地,wire [1:1] x; 定义了一个单比特的线 x,但是这里的位索引被显式地指定为 1。这不影响 x 作为一个单比特线的本质,但是在与其他信号交互时,比如进行位连接操作时,索引为 1。
  • 这种声明方式较少见,但可能在需要与特定位宽信号的某个位进行对齐操作时有其特定的用途。

wire x;

  • wire x; 声明了一个单比特的线 x,没有显式地指定位索引。这是最简单也是最常见的单比特线声明方式。
  • 这种方式足够用于大多数情况,特别是当你不需要显式地处理信号的多个位或者位索引时。

区别和用途

  • 所有这三种声明方式本质上都创建了一个单比特的线 x。区别在于,[0:0][1:1] 显式地指定了位的索引,而 wire x; 没有指定位索引。在大多数实际应用中,简单地使用 wire x; 就足够了,除非你有特定的原因需要显式地表达或操作信号的某个特定位。
  • 使用 [0:0][1:1] 等形式可能在某些特定上下文中有其用途,例如,当你需要与特定的位进行操作或强调位的索引时。然而,它们增加了代码的复杂度,通常只在需要明确指出信号的位索引时使用。

总的来说,选择哪种方式取决于具体需求和个人偏好,但在大多数情况下,简单的 wire x; 对于单比特信号已经足够。


BUG: 隐式网表

隐式网表(implicit nets)的概念:

在 Verilog 中,如果你在没有事先声明的情况下使用了一个信号名,这个信号会被隐式地创建为一个单比特宽的 wire 类型。这种行为可能会引入难以发现的错误,尤其是当你本意想要使用一个多位向量(vector)而不是单个比特时。

示例解释

1
2
3
4
wire [2:0] a, c;   // 两个三位向量
assign a = 3'b101; // 将 a 赋值为 3位二进制数101
assign b = a; // 隐式创建了一个单比特的 wire b,并将 a 的值赋给了它,但实际只赋了 a 的最低位
assign c = b; // 将单比特 b 的值赋给了向量 c,造成了错误。期望 c 是101,实际上 c 变成了001。

在这个例子中,因为 b 没有被显式声明,所以它被隐式创建为一个单比特的 wire。当 b = a; 执行时,实际上只将 a 的最低位赋给了 b。随后,当 c = b; 执行时,c 被错误地赋值为单比特 b 的值扩展到 c 的位宽,即 001,而不是预期的 101

my_module i1 (d,e);

这里提到的 my_module i1 (d,e); 示范了如何通过模块端口连接隐式创建的单比特 wire。如果 de 没有在之前被显式声明,并且你本意是将它们作为多位向量使用,那么这会导致错误。

避免隐式网表的创建

使用 default_nettype none 指令可以禁止隐式网表的创建。这意味着所有信号都必须显式声明其类型。如果试图使用未声明的信号,编译器会报错,从而帮助快速发现并修复潜在的问题。

1
`default_nettype none

加入这一行后,任何未声明的信号使用都会导致编译错误,从而使得上面 b 的隐式创建变成一个明显的错误,有助于提早发现和修正问题。这是一种提高代码质量和避免难以察觉的错误的好方法。


vector 部分赋值问题

verilog 向量赋值方法: 4'b1011

在 Verilog 中,如果你声明了一个多位宽的 wire,如 wire [7:0] a;,这意味着 a 是一个8位宽的向量。如果之后的代码中你只对这个向量的一部分进行了定义,例如只定义了 a[3:0],这将影响到 a 的部分位,而其余位将保持未定义状态,除非你另外为它们赋值。

示例和行为

考虑以下代码:

1
2
wire [7:0] a;
assign a[3:0] = 4'b1010; // 只定义了 a 的低4位

在这个例子中:

  • a[3:0] 被赋值为二进制数 1010
  • a[7:4](即 a 的高4位)未被显式定义,因此它们的值在逻辑上是 ‘z’(高阻态)或 ‘x’(未知),具体取决于电路的其余部分以及是否有其他地方对这些位进行了赋值。

未定义的高位

对于未被显式赋值的部分(在此例中为 a[7:4]),它们的状态是不确定的:

  • 如果这是模拟或综合的一部分,这些未定义的位可能会被综合工具视为 ‘x’,这可能影响到电路的行为,尤其是在涉及条件判断时。
  • 在实际的硬件实现中(如 FPGA 或 ASIC),未初始化的信号通常默认为 ‘0’,但这是一种不安全的做法,不应依赖于这种行为进行设计。

最佳实践

为了编写可靠和确定行为的 Verilog 代码,最佳实践是:

  • 显式定义所有位:确保你的所有 wirereg 变量都被完全定义,避免未定义的行为。
  • 使用 'default_nettype none:通过在文件开头添加这条指令,强制所有信号都必须显式声明,有助于避免隐式创建单比特线导致的问题。

这样做可以确保代码的可读性和电路的可预测性,降低因为未定义或未知状态带来的风险。


自定义模块的使用:

如何使用我自定义的module呢?比如下图:

1
2
3
4
5
6
7

module MyAND(input a, input b, output c);

assign c = a & b;

endmodule

  • 如何使用我的module?有两种方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 按照参数表顺序接线
module M1(input in1, input in2, output out);

// <module 名称> <实例名称>(<接线参数表>);
MyAND AND_instance(in1, in2, out); // ok, 这样就行了,我把线接进去就行了!这时候input和output要严格按照参数顺序接线!

// in1 对应 input a
// in2 对应 input b
// out 对应 input c

/**这样就算接完了,无需其他操作!**/

endmodule

// 2. 自定义顺序接线

module M1(input in1, input in2, output out);

// <module 名称> <实例名称>(.[实例参数]([我的命名]));
MyAND AND_instance(.b(in1), .b(in2), .c(out)); // 注意 .后面跟的是实例参数的名称,括号里才是我们接线的名称!

// 用这种方法可以自定义顺序接线,更明确!
endmodule

  • 练习:单向shift register
1
2
3
4
5
6
module top_module ( input clk, input d, output q );
wire q1,q2;
my_dff ins1(.clk(clk), .d(d), .q(q1));
my_dff ins2(.clk(clk), .d(q1), .q(q2));
my_dff ins3(.clk(clk), .d(q2), .q(q));
endmodule

如何导入其他文件中的module?

  • ``include"path"来导入.v`文件
1
`include "a.v"

三目运算符:

  • 三目运算符是唯一可以在always过程块之外实现 if-else 逻辑的方法!
1
2
3
4
5
6
7
8
9
10

/*在主体部分(非always)实现mux:*/

/*连续三目运算符实现if-elif-else 语句: (条件1) ? a : (条件2) ? b : c */

assign q = (sel == 2'd0) ? d :
(sel == 2'd1) ? q1 :
(sel == 2'd2) ? q2 :
q3; // 默认情况


reg 关键词: 不止register

在 Verilog 中,reg 关键字可能会引起一些混淆,因为它的名字暗示它与"寄存器"相关,但实际上它的含义更广泛。

reg 的本质

  • 存储功能:在 Verilog 语言中,reg 并不一定代表硬件中的物理寄存器。reg 类型的变量用于存储在过程块(如 always 块)中赋值的值。它可以用来表示组合逻辑和时序逻辑中的变量。

  • 过程块中的赋值reg 类型的变量可以在 always 块中通过阻塞(=)或非阻塞(<=)赋值操作符进行赋值。这意味着它们可以被用来实现时序逻辑(如触发器或寄存器的行为)以及可以被重新赋值的组合逻辑。

regwire

  • wire 类型:相比之下,wire 类型的变量用于表示电路中的物理连接和那些通过连续赋值(使用 assign 语句或直接连接到模块端口)获得值的信号。wire 默认不能在过程块中赋值,因为它们代表的是连续驱动的信号。

使用 reg

  • 在描述时序逻辑时,reg 用于存储状态信息,如触发器和寄存器的输出。
  • 在描述组合逻辑时,即使逻辑输出并不需要"记忆"功能,reg 也可以用来在 always 块中临时存储计算结果。这样的使用并不会导致硬件中生成物理寄存器,而是由综合工具根据逻辑的实际需要来决定是否需要物理存储元件。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module example (
input wire clk,
input wire reset,
input wire [3:0] data,
output reg [3:0] out
);

always @(posedge clk or posedge reset) begin
if (reset)
out <= 4'b0000; // 使用非阻塞赋值表示时序逻辑
else
out <= data; // 在时钟的上升沿更新输出
end

endmodule

在这个示例中,out 被声明为 reg 类型,因为它在 always 带有敏感列表的过程块中被赋值。这里 out 的使用体现了时序逻辑的典型应用——它在每个时钟周期存储一个值,这种行为类似于硬件中的一个寄存器。

总之,reg 在 Verilog 中的使用并不局限于表示物理寄存器,它更广泛地用于在过程块中需要存储或更新值的任何地方。

为什么 always 中的左值不能用 wire 只能用 reg?(右值随便)

在 Verilog 中,always 块是一种过程控制结构,用于描述在某些条件或事件发生时应该如何改变信号的值。这些条件或事件被称为"敏感列表",可以是时钟信号的上升沿或下降沿(用于描述时序逻辑),或者是任何信号的变化(用于描述组合逻辑)。always 块使得硬件描述语言(HDL)能够模拟电路中的逻辑行为。

为什么需要 reg

always 块通常用于实现那些基于特定事件发生时需要改变状态的逻辑。由于 Verilog 是一种用于硬件描述的语言,其变量类型和用法与传统的编程语言有所不同,特别是在描述硬件行为时对变量的存储和驱动方式的要求上。

  • reg 类型:在 always 块中,reg 类型用于声明那些需要在过程控制块内赋值的变量。reg 不仅仅用来表示物理上的寄存器,它更广泛地用于表示需要在过程块中持有或更新值的变量。这包括时序逻辑中的状态寄存器,以及组合逻辑中的临时变量。

  • wire 类型wire 类型用于表示两点之间的物理连接,它们的值通常由外部信号驱动,例如来自模块端口的信号或者 assign 语句的结果。因为 wire 类型的信号是连续被驱动的,它们不适合在 always 块中赋值,因为 always 块表示的是在特定条件下才执行的逻辑。

为什么不能用 wire

  • 在 Verilog 的早期版本中,规范要求在 always 块内赋值的变量必须声明为 reg 类型,即使这个变量用于描述组合逻辑。这是因为 reg 类型的变量可以在仿真过程中改变其值,而 wire 类型的变量则不能。

  • 使用 wire 类型的变量进行连续赋值,意味着其值是由外部信号直接驱动的,不适合用于 always 块内的逻辑描述,因为 always 块内的逻辑可能只在特定条件下才执行。

  • eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_ssign = a & b; // wire 用 assign

always @(*) begin out_alwaysblock = a & b; end // always必须用reg!

endmodule


reg 与 wire 之间的连接

  • reg不能直接 assign给wire,但是wire可以在always中赋值给reg,但是module的wire参数可以接上reg为什么呢?

  • 核心:这样好生成电路!

  • 所以如果要连接 wire 和 reg 的话,通过模块来间接连接就行了!

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
module Reg2Wire (
input wire in, // 这里的in会传入一个 reg 线,但是这是合法的!
output wire out
);
assign out = in;
endmodule

module top_module (
input wire clk,
input wire reg_input, // 假设这是从其他地方来的信号,用于示例
output wire wire_output
);

reg reg_var;
wire wire_var;

// 某个过程块,更新 reg_var
always @(posedge clk) begin
reg_var <= reg_input;
end

// 使用封装的 Reg_To_Wire 模块来传递 reg 类型的信号
Reg_To_Wire reg_to_wire_inst(.in(reg_var), .out(wire_var));

// wire_var 现在可以用在需要 wire 类型信号的地方
assign wire_output = wire_var;

endmodule

always 过程块基础

由于数字电路由逻辑门和导线相连接构成,任何电路都可以表达为一些模块和 assign 语句的组合。然而,有时这并不是描述电路的最便捷方式。过程(always 块就是一个例子)提供了一种替代的语法来描述电路。

在硬件综合方面,有两种类型的 always 块是相关的:

  • 组合逻辑:always @(*)
  • 时钟触发:always @(posedge clk)

组合逻辑的 always 块等同于 assign 语句,因此总是有办法用这两种方式来表达一个组合电路。在两者之间的选择主要是哪种语法更方便的问题。过程块内部的代码语法与外部代码不同。过程块拥有更丰富的语句集(例如,if-thencase),不能包含连续赋值*,但也引入了许多新的非直观的错误制造方式。(*过程连续赋值确实存在,但与连续赋值有所不同,并且不可综合。)

例如,assign 和组合逻辑的 always 块描述了相同的电路。两者都创建了相同的组合逻辑块。每当任何输入(右侧)的值发生变化时,两者都会重新计算输出。

1
2
3
4
assign out1 = a & b | c ^ d;
always @(*) begin
out2 = a & b | c ^ d; // out2一定是reg才行!
end

在这个例子中,assign 语句和使用 always @(*) 的组合逻辑块都实现了相同的功能:根据输入 abcd 的值来计算 out1out2。这表明在设计组合逻辑时,可以根据具体情况选择使用 assign 语句或组合逻辑的 always 块。


always 过程块的本质

  • always @(...) 会监视括号中的信号(被称为敏感列表,* 代表监视所有module中的信号),如果信号变了我们就会更新左值(一定是reg)的值

always @(*) 语句是 Verilog 中的一种过程控制结构,用于描述在任何输入变化时需要重新执行的逻辑。这种结构体现了硬件描述语言(HDL)的一大特点:与传统的编程语言不同,它不是顺序执行的,而是用于描述电路的行为特性——即电路是如何响应各种输入变化的。

本质

  • 敏感列表:在 always @(*) 中,* 代表自动敏感列表,意味着该过程块将对其内部使用的所有信号进行监视。一旦这些信号中的任何一个发生变化,过程块内的逻辑就会被重新评估和执行。这种机制使得 Verilog 能够模拟组合逻辑的行为,组合逻辑的输出是当前输入的直接函数,与之前的状态或时间无关。

  • 描述组合逻辑:使用 always @(*) 常见于描述组合逻辑电路,它确保了输出能够实时反应输入的变化。这与使用 assign 语句达到的效果相同,但允许使用更复杂的控制流语句(如 ifcase 等),使得在描述较为复杂的逻辑时更加灵活和强大。

assign 语句的对比

虽然 always @(*)assign 语句在功能上有所重叠,都能用于描述组合逻辑,但在具体应用上有一些区别:

  • assign 语句:更适合描述简单的、直接的逻辑关系,特别是单一的表达式或直接的信号映射。

  • always @(*):由于可以包含复杂的控制流语句,因此更适合描述复杂的组合逻辑。当逻辑包含多个条件分支或需要根据多个输入信号计算输出时尤其有用。

使用注意

尽管 always @(*) 提供了强大的功能来描述组合逻辑,但在使用时也要注意一些问题,以避免引入非预期的行为或综合问题:

  • 明确区分组合逻辑和时序逻辑:确保不要在旨在描述组合逻辑的 always @(*) 块中引入时序逻辑元素(如基于时钟的状态更新),因为这可能会导致逻辑行为与预期不符或难以综合。

  • 变量类型:在 always @(*) 块中赋值的变量通常应声明为 reg 类型,即使是用于组合逻辑。这可能与传统的寄存器(reg)概念有所不同,但在 Verilog 中,reg 类型的变量可以用于组合逻辑的描述,其关键在于它们是否在时钟边缘触发的过程块中被赋值。

总之,always @(*) 的本质在于为 Verilog 提供了一种描述组合逻辑的强大机制,使设计者能够以灵活、直观的方式实现基于各种条件和输入信号的逻辑操作。


always 时钟激发

  • always 过程块可以被上升/下降沿激发(flip-flop)
1
2
3
4
5
6
7
always @(posedge clk) begin
a <= b; // 注意, always中不可以有 assign (i.e. always 不可以套always!)
end

always @(negedge clk) begin
a <= b; // 注意, always中不可以有 assign (i.e. always 不可以套always!)
end

<= 非阻塞赋值 和 = 阻塞赋值:

  • 在 时钟激发的always中用 <=, 非时钟激发的always用 = !

always中的 if-elseif-else 逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13

always @(*) begin
if (a == 2'b00) begin
/*...*/
end
else if (a == 2'b01) begin
/*...*/
end
else begin
/*...*/
end
end


always 导致的未遇见 latch

  • 始终注意,我们是在设计电路,不是在写代码!(所以要把你想的电路用代码表示出来,而不是写逻辑)

  • 比如过热关机电路逻辑:

1
2
3
4
5
always @(*) begin
if (cpu_overheated) begin
shut_off_computer = 1;
end
end
  • 实际上会创建如下电路:
  • 为了防止这种情况出现,我们要先想好电路,然后再写!(正确版本如下:)
1
2
3
4
5
6
7
8
always @(*) begin
if (cpu_overheated) begin
shut_off_computer = 1;
end else begin
shut_off_computer = 0;
end

end

Case: 和C++ 类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module MUX ( 
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//

always@(*) begin // This is a combinational circuit
case(sel)
3'b000 : out = data0;
3'b001 : out = data1;
3'b010 : out = data2;
3'b011 : out = data3;
3'b100 : out = data4;
3'b101 : out = data5;
default: out = 4'b0000; // 记得default
endcase // 记得 endcase

end

endmodule