Getting Start
数字电路中,1对应高电平信号,0对应低电平信号。
Verilog Language
Basic
关键词assign,用于连续赋值
assign left_side = right_side
NOT gate(非门),逻辑取反符号!,按位取反符号~
AND gate(与门),逻辑与符号&&,按位与符号&
OR gate(或门),逻辑或符号||,按位或符号|
下图为或非门,即在或门的基础上加个非
XNOR gate(同或门),逻辑同或符号^~
XOR gate(异或门),逻辑同或符号^
下图为同或门,可以看作在异或门的基础上加个非
Vectors
向量声明格式:类型 [高位:低位] 向量名
wire [99:0] my_vector; // Declare a 100-element vector
未声明的向量在使用过程中会产生Bug,且它固定为一个位宽
wire [2:0] a, c; // Two vectors
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 and e are implicitly one-bit wide if not declared.
// This could be a bug if the port was intended to be a vector.
禁止隐式声明功能需要添加一个宏定义
`default_nettype none
声明reg存储器类型时,可以在reg向量名后加存储单元个数
reg [7:0] mem [255:0]; // 有256个存储单元,每个存储单元是一个8bit的reg
reg mem2 [28:0]; // 有29个存储单元,每个存储单元是一个1bit的reg
向量部分选择操作符 [:]
assign out = my_vector[10]; // Part-select one bit out of the vector
向量连接操作符 {,},连接运算符中的每一个元素都需要标注位宽,否则是非法的
assign {w,x,y,z} = {a,b,c,d,e,f,2'b11};
向量复制操作符格式 {num{Vector}}
assign out = ~{{5{a}},{5{b}},{5{c}},{5{d}},{5{e}}} ^ {5{a,b,c,d,e}};
Modules:Hierarchy
父子模块的连接,需要在父模块底下实例化子模块
module mod_a ( input in1, input in2, output out ); //定义了一个mod_a
// Module body
endmodule
模块的实例化有两种方式,分为按位置和按名称
按位置实例化是从左到右将模块的端口和外部的信号一一对应,显然,这种方式的缺点是一旦端口列表发生改变,所有模块实例化中的端口连接都需要调整。
例化格式:模块名 实例名 (外部信号1, 外部信号2, 外部信号3, ……,外部信号n);
mod_a instance1 ( wa, wb, wc );
按名称实例化就是将模块实例外部的信号直接对应于该模块的端口名称,这种方式下的端口顺序可以是任意的。
例化格式:模块名 实例名 ( .端口名(外部信号), .端口名(), .端口名(外部信号));
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
全加器表达式
assign sum = a^b^cin;
assign cout = a&b | a&cin | b&cin;
当然,为了更为贴近加法器,上述代码可以写成下面这样
assign {cout,sum} = a + b + cin;
再来一个半加器表达式
assign sum = a^b;
assign cout = a&b;
Procedures
关键字 always
always 语句是重复执行的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。
Combinational: always @(*)
Clocked: always @(posedge clk)
由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。
always if语句
always @(*) begin
if (condition) begin
out = x;
...
end
else begin
out = y;
...
end
end
相当于下面的连续assign语句
assign out = (condition) ? x : y;
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
always casez语句
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // 输入in的高三位可以为任意值
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
Latch(锁存器)
电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值。仅当锁存器处于使能状态时,输出才会随着数据输入发生变化。
当电平信号无效时,输出信号随输入信号变化,就像通过了缓冲器;当电平有效时,输出信号被锁存。激励信号的任何变化,都将直接引起锁存器输出状态的改变,很有可能会因为瞬态特性不稳定而产生振荡现象。
Latch 的主要危害:
- 输入状态可能多次变化,容易产生毛刺,增加了下一级电路的不确定性;
- 在大部分 FPGA 的资源中,可能需要比触发器更多的资源去实现 Latch 结构;
- 锁存器的出现使得静态时序分析变得更加复杂。
避免Latch的方法:
- if-else 或 case 语句,结构一定要完整
- 不要将赋值信号放在赋值源头,或条件判断中
- 敏感信号列表建议多用 always@(*)
More Verilog Features
三目运算符,跟C语言中的类似
(condition ? if_true : if_false)
之前学习了在两个值之间的按位运算符,但有时需要对一个向量的所有位进行按位运算操作
其实就是在向量前面加上按位运算符,如下
& 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]
这样的好处就在于不用写冗长的按位运算逻辑代码了
循环for语句
for(initial_assignment; condition ; step_assignment) begin
…………
end