一、Basics
1. 端口类型
- input
- output
2. 符号
- 与:& 或:| 非:~
- 异或:^ 同或:~(^)
二、vectors
3. 声明vectors
type [upper:lower] vector_name;
type 指定了vectors的数据类型,通常是wire或reg型。如果要声明一个输入或输出端口,则该类型还可以包括端口类型(例如,input或output)。
wire [7:0] w; // 8比特wire
reg [4:1] x; // 4比特reg
output reg [0:0] y; // 1比特输出reg
input wire [3:-2] z; // 6bit输入wire (负范围也是可以的)
output [3:0] a; // 4比特输出wire. type默认是wire,除非另有说明。
wire [0:7] b; // 8比特wire,b[0]是最高有效位。
在verilog中,一旦用特定的字符顺序声明了一个向量,就必须始终以相同的方式使用它。例如,当vec声明为wire [3:0] vec;时,写入vec [0:3]是非法的。
out = my_vector[2]; // 选择my_vector向量中的一个元素流向out
4. implicit net
隐式网络通常是难以检测的漏洞的来源。在Verilog中,可以通过ASSIGN语句或通过将未声明的内容附加到模块端口来隐式创建网络类型的信号。
隐式网络始终是一位导线,如果您打算使用向量,则会导致错误。可以使用`DEFAULT_NETTYPE NONE指令禁用隐式网络的创建。
wire [2:0] a, c; // 两个向量
assign a = 3'b101; // a = 101
assign b = a; // b = 1 implicitly-created wire
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); // d和e如果未声明,则隐式为1bit宽
// 如果该端口是一个向量,这里就会出现bug
//添加`DEFAULT_nettype NONE会使代码的第二行出错,从而使错误更加明显。
5. Unpacked vs. Packed Arrays(未打包数组与打包数组)
在声明中,向量索引写在向量名称之前。这声明了数组的“压缩”维度,其中bits被“打包”到一个blob中(这在模拟器中是相关的,但在硬件中不相关)。未打包的维度在名称之后声明。它们通常用于声明内存阵列。
reg [7:0] mem [255:0]; // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
reg mem2 [28:0]; // 29 unpacked elements, each of which is a 1-bit reg.
6. 访问vectors中的部分元素
访问整个向量是使用向量名称完成的。例如:Assign w=a;获取整个4位向量a,并将其赋给整个8位向量w(声明取自上面)。如果左右两侧的长度不匹配,则根据需要对其进行零扩展或截断。
零件选择运算符可用于访问向量的一部分:
w[3:0]; // 只访问w中的低4位
x[1]; // The lowest bit of x
x[1:1]; // ...also the lowest bit of x
z[-1:-2]; // z中的最低的2位
wire [0:7] b;
b[3:0]; // Illegal. Vector部分访问时必须匹配声明时的方向
b[0:3]; // b的前4比特
assign w[3:0] = b[0:3]; // 将b的前4高位bits赋值给w的低4位bits. w[3]=b[0], w[2]=b[1], etc.
7. 位运算符与逻辑运算
各种布尔运算符有按位和逻辑两种版本。在使用向量时,两种运算符类型之间的区别变得很重要。两个N位向量之间的逐位运算对向量的每一位重复该运算并产生N位输出,而逻辑运算将整个向量视为布尔值(true=非零,false=零)并产生1位输出。
input [2:0] a,b;
output [2:0] out_or_bitwise;
output out_or_logical;
output [5:0] out_not;
assign out_or_bitwise = a|b; //a和b按位or
assign out_or_logical = a || b; //a和b逻辑or
assign out_not[5:3] = ~b; //b按位非
assign out_not[2:0] = ~a;
8. vectors的串联
{3'b111, 3'b000} => 6'b111000
{1'b1, 1'b0, 3'b101} => 5'b10101
{4'ha, 4'd10} => 8'b10101010
串联需要知道每个部分的宽度(或者如何知道结果的长度?)。因此,{1,2,3}是非法的,并导致错误消息:串联中不允许未设置大小的常量。
串联运算符可以在赋值的左侧或右侧使用:
input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in; // 交换两个字节
assign out[15:0] = {in[7:0], in[15:8]}; // 和上一句结果相同
assign out = {in[7:0], in[15:8]}; // 左右两端长度不同out[23:16] are zero.
// 在前两个例子中out[23:16]是未赋值的状态
9. vector复制
//{num{vector}} 将向量vector复制num次
{5{1'b1}} // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}} // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110.
input [15:0] a;
assign a = '1; //表示将a的所有bits全部设为1。‘0表示将a的所有bits全部设为0。
三、modules:
10. 将信号连接到模块端口有两种常用的方法
按位置:
实例化模块时,根据模块的声明从左到右连接端口。例如:
mod_a instance1(wa,wb,wc);
这将实例化一个mod_a类型的模块,并为其提供实例名称\“instance1\”,然后将信号Wa(新模块外部)连接到新模块的第一个端口(in1),将wb连接到第二个端口(in2),将wc连接到第三个端口(out)。
这种语法的一个缺点是,如果模块的端口列表发生更改,则还需要找到并更改模块的所有实例化以匹配新模块。
按名称:
按名称将信号连接到模块的端口允许导线保持正确连接,即使端口列表发生更改。然而,这个语法更加冗长。
mod_a instance2(.out(wc),.in1(wa),.in2(wb));
上面的代码行实例化了一个名为\“instance2\”的mod_a类型的模块,然后将信号wa(模块外部)连接到名为in1的端口,将wb连接到名为in2的端口,将wc连接到名为out的端口。请注意,端口的顺序在这里是无关紧要的,因为无论其在子模块的端口列表中的位置如何,都将连接到正确的名称。
注意此语法中的端口名之前的符号' . '。
四、 Preocedures
11. 对于合成硬件,有两种类型的Always块是相关的:
Combinational: always @(*)
Clocked: always @(posedge clk)
组合Always块等价于assign语句。用assign 和 combinational always block描述相同的一个电路:
assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;
关于wire和reg的说明:
assign语句的左侧必须是Net类型(例如,wire)
过程赋值(在Always块中)的左侧必须是变量类型(例如,reg)。
这些类型(wire和reg)与硬件合成无关,只是Verilog作为硬件模拟语言使用时遗留下来的语法。
12. Blocking vs. Non-Blocking Assignment
Verilog中有三种类型的赋值:
连续赋值(assign x = y)。不能再always block中使用。
Procedural blocking赋值:(x = y;)。只能在procedure内部使用。
Procedural non-blocking赋值:(x <= y;)。只能在procedure内部使用。
13. initial语句与always语句
过程结构语句有 2 种,initial 与 always 语句。它们是行为级建模的 2 种基本语句。但 2 种语句不能嵌套使用。
initial 语句或 always 语句内部可以理解为是顺序执行的(非阻塞赋值除外)。
每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。
initial 只执行一次,多个 initial 块之间是相互独立的。
如果 initial 块内包含多个语句,需要使用关键字 begin 和 end 组成一个块语句。
initial 理论上来讲是不可综合的,多用于初始化、信号检测等。
与 initial 语句相反,always 语句是重复执行的。
但always是否运行看触发条件是否满足,满足运行一次,再次满足执行一次,直到仿真结束。
由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。
14. always if
选择语句
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
注意:一个“ begin ”对应一个“ end ”。
这等效于对条件运算符使用连续赋值:
assign out = (condition) ? x : y;
注意:语法正确的代码不一定会产生合理的电路(组合逻辑+触发器)。
通常的原因是:\“除了您指定的情况外,还会发生什么?\”Verilog的答案是:保持输出不变。
“保持输出不变”的这种行为意味着需要记住当前状态,因此会产生一个闩锁。组合逻辑(例如,逻辑门)不能记住任何状态。当心警告(10240):...。推断闩锁\“消息。除非闩锁是故意的,否则它几乎总是指示错误。组合电路必须在所有条件下为所有输出赋值。必须要为输出分配else子句或缺省值。
举例如下:
always @(*) begin
if (cpu_oberheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
这里会出现报错,因为当cup_overheated = 0时,此时shut_off_computer仍等于1。
修正bug后的代码如下:
always @(*) begin
if (cpu_overheated) begin
shut_off_computer = 1;
end
else begin
shut_off_computer = 0;
end
end
always @(*) begin
if (~arrived) begin
keep_driving = ~gas_tank_empty;
end
else begin
keep_driving = 0; //注意这里不能写gas_tank_empty
end
end
注意: 上述注释除不能写keep_driving = gas_tank_empty; 因为gas_tank_empty是一个变量,它的值会改变。
这里的另一个修改方法就是case前就赋给shut_off_computer=0;
15. always case
always @(*) begin // This is a combinational circuit
case (in)
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
每个case项只能执行一条语句。如果需要多个语句,则必须使用begin...end。
16. always casez
case语句中的case项支持无关位。它在比较中具有值z的位视为无关。
例如:
always @(*) begin
casez (in[3:0]) // 注意这里是casez
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
注意:这里写的是“casez”。
17. always for
循环语句
always @(*) begin
for(初始值; 条件; 步长) begin
...
end
end
注意:这里的步长只能写作i = i+k
18. 条件运算符
(condition ? if_true : if_false)
19. 约简运算
约简运算符可以对向量的位进行AND、OR和XOR,从而产生一位输出。
& a[3:0] // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0] // OR: b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0] // XOR: c[2]^c[1]c[0]
~b[3:0]; //将b的4bits按位翻转。
还可以反转这些门的输出以创建NAND、NOR和XNOR门,例如( ~& d[7:0] )。