考研上微电子入了材料坑,开始自学verilog
此文很多也是在其他优秀的博文中复制过来
(在此也非常感谢大家在网上分享的笔记)
仅为方便记录自己学习的笔记
习题链接:https://hdlbits.01xz.net/wiki/Main_Page
本文大部分参考链接:https://www.cnblogs.com/LhTian/p/16608404.html
一,开始
1.1,输出
assign one = 1'b1
给output one赋值1'b1,表示1bit数值
b=二进制,o=八进制,d=十进制,h=十六进制(十六进制数前面加0x表示)
二,verilog语言
2.1,基础
赋值
assign 输出 = 输入;
门电路
非门 ~(逐位取反)和 !(逻辑取反)
与门 and &
或门 or |
或非 nor ~|
与非 nand ~&
异或 xor ^
同或 xnor ~^
Verilog中&与&&(||和!同理)的区别为:
一、性质不同
1、&:&是位运算符,表示是按位与。
2、&&:&&是逻辑运算符,表示是逻辑与。
(逐位:对一个n bit输入向量进行逻辑运算,在n bit上逐位进行,并产生一个n bit长的结果;
逻辑:任何类型的输入都会被视作布尔值,零->假,非零->真,将布尔值进行逻辑比较后,输出一个 1 bit的结果。)
二、计算结果不同
1、&:&的计算结果为十进制数。&:5'b10000 & b'b10001 结果为5'b10000。
2、&&:&&的计算结果为true或false。&&:5'b10000 && 5'b10001 结果为1。
三、参数不同
1、&:&的参数为进制数,可以是二进制、十进制、十六进制数,也可以是整数、负数。
2、&&:&&的参数为进制数,也可以是比较公式,将比较公式值作为最终的参数。
(2023.10.10)
2.2,向量
向量用于使用一个名称对相关信号进行分组,以使其更易于操作。
type [upper:lower] vector_name;
Type指定vector的数据类型。这通常是wire或reg
eg:wire [7:0] w ;声明了一个 8 bit 位宽的信号,向量名为 w ,该向量等价于 8 个 1bit 位宽的 wire 信号。
wire[0:7]b; //8-bit wire where b[0] is the most-significant bit;
input wire [3:-2] z; // 6-bit wire input (negative ranges are allowed);
output [3:0] a; // 4-bit output wire. Type is 'wire' unless specified otherwise;
assign w[3:0] = b[0:3]; // Assign upper 4 bits of b to lower 4 bits of w. w[3]=b[0], w[2]=b[1], etc;
向量
隐式声明
若向量为模块的输入/输出,可在向量前声明(eg:input wire[3:0] z;)
Verilog区分向量的字节序,即 [3:0] ≠ [0:3]
变量的隐式声明:若将一个未定义声明的信号连接到模块的输入输出端口时,综合器会自动为你声明这个信号,但问题是,综合器只会声明为 1 bit wire 型信号
unpack数组和pack数组
向量的位宽一般写在向量名之前(eg: [3:0] ),这定义了数组的packed维度,于是在仿真中(硬件中有所不同),这个向量中的每一位信号都被压缩为一整块来进行操作
向量的长度一般写在向量名之后,这定义了数组的unpacked维度,这通常用于声明内存数组
简单得来说,定义在向量名之前的是向量的位宽,定义在向量名之后的维度可以理解为向量数组的长度,同 C 语言中的数组长度概念相同,一般用来对存储器建模
可以将数组等效为矩形,宽即为packed,长即为unpacked
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.
向量部分选取
在 assign 赋值操作中,如果等号左右两侧信号的位宽不同,那么就会进行截断或者补零操作
左侧信号位宽大于右侧信号位宽,右值的低位赋予左值对应的低位,左值高位的部分赋零
左侧信号位宽小于右侧信号位宽,右值的低位赋予左值对应的低位,右值高位的部分直接被截断,即保留右值的低位
总体而言,就是以左边信号位宽为标准
使用 [ ] 可以对信号进行片选,选择信号中特定几位比特
w[3:0] // w 中较低的四位
x[1] // x 最低位
x[1:1] // x 最低位
z[-1:-2] // Z 最低两位
b[3:0] // 如果 b 在声明时 声明为 wire [0:3] b;则不能使用 b [3:0]进行选择
b[0:3] // b的高四位.
assign w[3:0] = b[0:3]; // 将 b 的高位赋予 w 的低位 w[3]=b[0], w[2]=b[1], etc.
串联向量
将较小的向量连接在一起,创建更大的向量
基本语法:assign {a,b}=c;
一定要标注位宽,否则综合器不知道结果需要多少位宽
input [15:0] in;
output [23:0] out;
assign {out[7:0], out[15:8]} = in; // Swap two bytes. Right side and left side are both 16-bit vectors.
assign out[15:0] = {in[7:0], in[15:8]}; // This is the same thing.
assign out = {in[7:0], in[15:8]}; // This is different. The 16-bit vector on the right is extended to
// match the 24-bit vector on the left, so out[23:16] are zero.
// In the first two examples, out[23:16] are not assigned.
for与generate-for语句
参考链接:https://blog.csdn.net/weixin_44544687/article/details/107793235
以下题为例
eg:Given an 8-bit input vector [7:0], reverse its bit ordering.
module top_module(
input [7:0] in,
output [7:0] out
);
integer i; //先创建循环变量i
//注意,HDLBits 上的 solution 中,i 定义于 for 循环的括号中,这在 Verilog 的语法中是不被允许的,是 SystemVerilog 的语法,Verilog 的语法需要提前定义 integer 变量,即整形变量
always@(*)begin //再创建组合逻辑always块
for(i=0;i<8;i=i+1)
out[i] = in[7-i];
end
endmodule
always块是Verilog中用来描述组合逻辑以及时序逻辑的语法,组合逻辑语法为:
always @ (level event) begin //可以用 * 代替敏感信号,编译器会很具内部内容自动识别,达到简化的目的
[multiple statements]
end
值得注意的是,for 循环中的“循环”指的是代码层面的循环,而电路是不存在循环的,无论是信号而是门电路,都不存在循环。实际上,for 循环表示的代码将被综合器解析,for 循环将被分别解析为硬件电路。
除此之外,还可以使用generate 生成块(HDLBits提供的solution)
generate
genvar i;
for (i=0; i<8; i = i+1) begin: my_block_name
assign out[i] = in[7-i];
end
endgeneate
eg:https://hdlbits.01xz.net/wiki/Adder100i
构建一百位加法器
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
generate
genvar i;
assign {cout[0],sum[0]} = a[0]+b[0]+cin;
for(i=1;i<100;i=i+1)begin:add
add hhy(a[i],b[i],cout[i-1],cout[i],sum[i]);
//定义向量的位置需和下面定义模块一致
end
endgenerate
endmodule
module add(
input a, b, cin,
output cout, sum );
assign {cout,sum} = a+b+cin;
endmodule
generate语法
定义genvar,作为generate种的循环变量
generate语句中定义的for语句,必须要有begin,为后续增加标签做准备
begin必须要有名称,也就是必须要有标签,因为标签会作为generate循环的实例名称
复制向量
语法:{num{vector}},这将向量复制了num次,Num必须是常数,这两组大括号都是必需的。
eg:Build a circuit that sign-extends an 8-bit number to 32 bits. This requires a concatenation of 24 copies of the sign bit (i.e., replicate bit[7] 24 times) followed by the 8-bit number itself.
module top_module (
input [7:0] in,
output [31:0] out );
assign out = {{24{in[7]}},in};
endmodule
(2023.10.11)
2.3,模块
模块
module mod_a ( input in1, input in2, output out );
// Module body
endmodule
定义模块语法有两种:
按位置
端口根据模块的声明从左到右连接
mod_a instance1 ( wa, wb, wc );
其中“instance1”是模块mod_a的名称,然后将信号(在新模块外部)连接到新模块的第一个端口 (in1)、第二个端口 (in2) 和第三个端口 (out)。
此语法的一个缺点是,如果模块的端口列表发生更改,则还需要查找并更改模块的所有实例化以匹配新模块。
按名称
通过按名称将信号连接到模块的端口,即使端口列表发生变化,电线也能保持正确连接
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
将信号 wa,wb,wc (模块外部)连接到名为 in1 的端口、名为 in2 的端口和名为 out 的端口。请注意,端口的排序在这里无关紧要,因为无论它在子模块的端口列表中的位置如何,都将连接到正确的名称。另请注意此语法中紧靠端口名称前面的句点。
2.4,程序
always模块
- 组合逻辑: always @(*)
- 时序逻辑: always @(posedge clk)
对于组合逻辑电路,一般会用 * 代替所有输入变量,防止出现错误
关于 wire vs. reg 的说明:assign 语句的左侧必须是网络类型(例如,连线),而过程赋值的左侧(在 always 块中)必须是变量类型(例如,reg)
always与assign存在一个细微的区别,
wire a;
reg b;
assign a = 1'b0;
always@(*)
b = 1'b0;
在这种情况下,做仿真时a将会正常为0, 但是b却是不定态。always@(*)中的*是指该always块内的所有输入信号的变化为敏感列表,也就是仿真时只有当always@(*)块内的输入信号产生变化,该块内描述的信号才会产生变化,而像always@(*) b = 1'b0;
这种写法由于1'b0一直没有变化,所以b的信号状态一直没有改变,由于b是组合逻辑输出,所以复位时没有明确的值(不定态),而又因为always@(*)块内没有敏感信号变化,因此b的信号状态一直保持为不定态。
三种赋值方法:
连续赋值:assign a=b
不能在过程块中实现
过程阻塞赋值:x=y
只能在过程块中实现,用于组合逻辑
过程非阻塞赋值:x<=y
只能在过程块中实现,用于时序逻辑
在组合的always块中,使用阻塞赋值。在时钟始终块中,使用非阻塞赋值。
- Continuous assignments (assign x = y;). Can only be used when not inside a procedure ("always block").
- Procedural blocking assignment: (x = y;). Can only be used inside a procedure.
- Procedural non-blocking assignment: (x <= y;). Can only be used inside a procedure.
In a combinational always block, use blocking assignments. In a clocked always block, use non-blocking assignments.
always if
if 语句通常创建一个 2 比 1 多路复用器,如果条件为真,则选择一个输入,如果条件为假,则选择另一个输入。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
这等效于使用带有条件运算符的连续赋值:
(condition ? if_true : if_false)
assign out = (condition) ? x : y;
然而,过程性的if语句提供了一种新的出错方式。只有当out总是被赋值时,电路才是组合电路。
常见的错误来源:如何避免制造闩锁
在设计电路时,必须首先考虑电路:
我想要这个逻辑门
我想要一个组合逻辑块,它有这些输入并产生这些输出
我想要一个组合逻辑块,后面跟着一组触发器
语法正确的代码不一定产生合理的电路(组合逻辑+触发器)。通常的原因是:“除了你指定的那些情况之外,会发生什么?”Verilog的答案是:保持输出不变。
这种“保持输出不变”的行为意味着需要记住当前状态,从而产生锁存。除非闩锁是故意的,否则它几乎总是表明有bug。在任何条件下,组合电路的所有输出都必须有一个值。这通常意味着您总是需要else子句或为输出分配默认值。
always case
Verilog中的Case语句几乎等同于if-else if-else序列,它将一个表达式与其他表达式的列表进行比较。
当可能的情况较多时,适合使用 case 语句而非 if-else 语句
case 语句以 case 开头,每个 case 项以冒号结束,不需要 switch
case 项后的执行语句可以是单条,也可以是多条,但多条需要用 begin-end 进行说明
case项允许重复和部分重叠,执行程序匹配到的第一个
当 case 项均不符合,则执行 default 项
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
(2023.10.12)
always casez
给定一个8位向量,输出应该报告该向量中的第一个(最低有效)位为1。如果输入向量没有高位数,则报告零。例如,输入的8'b10010000应该输出3'd4,因为位[4]是第一个高的位。
casez:在比较中将具有z的位是为无关项,例如:无论是 10 还是 11 都会匹配到 1z 项
casez (in[3:0])
4'bzzz1: ...
4'bzz10: ...
4'bz100: ...
4'b1000: ...
default: ...
endcase
为避免产生锁存器,必须在所有可能的条件下为所有输出分配一个值,仅仅有一个默认案例是不够的,必须为所有四种情况和默认情况下的所有四个输出分配一个值。这可能涉及大量不必要的键入。解决此问题的一种简单方法是在always和case之间设置默认值来避免锁存器的生成并减少代码量。
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
2.5,其他功能
三元运算符
(condition ? if_true : if_false)
这可用于根据一行上的条件(多路复用!)选择两个值之一,而无需在组合始终块中使用 if-then。
eg:
(0 ? 3 : 5) // This is 5 because the condition is false.
(sel ? b : a) // A 2-to-1 multiplexer between a and b selected by sel.
always @(posedge clk) // A T-flip-flop.
q <= toggle ? ~q : q;
always @(*) // State transition logic for a one-input FSM
case (state)
A: next = w ? B : A;
B: next = w ? A : B;
endcase
assign out = ena ? q : 1'bz; // A tri-state buffer
((sel[1:0] == 2'h0) ? a : // A 3-to-1 mux
(sel[1:0] == 2'h1) ? b :
c )
归约运算符
归约运算符可以对向量的位执行 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]
~& a[2:0] // a[2]~&a[1]~&a[0]
奇偶校验通常被用作通过不完美信道传输数据时检测错误的一种简单方法。创建一个电路,计算一个8位字节的奇偶校验位(这将为字节添加第9位)。我们将使用“偶数”奇偶校验,其中奇偶校验位只是所有8个数据位的异或。奇偶校验是检验传输数据中1的个数,当然有奇数有偶数,,这时候就需要用我们的校验位了,通过检验位将传输1的个数变成奇数就是奇校验,变成偶数就是偶校验。比如:
8'b01100100 //原数据
9'b01100100_0 //奇校验
9'b01100100_1 //偶校验
(2023.10.13)