Verilog基础教程学习笔记。
verilog基础教程
1 数值表示
1. 逻辑电平
0:逻辑0或假
1:逻辑1或真
x或X:未知
z或Z:高阻
2. 数字
- 十进制:8’d1(负数:-8’d1)
- 二进制:8’b0(8’b0000_0000)
- 八进制:6’o0
- 十六进制:8’h0(8’h00)
3. 字符串
每个字符用单字节ASCII码表示
reg [5*8-1:0] str;
initial begin
str = "hello";
end
2 数据类型
1. wire(线网)
- 定义:表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动
- 赋值:不能过程赋值(initial,always),只能连续赋值(assign),即需要驱动
- 连接:用于模块间的连接,input必须是wire类型
wire clk;
2. reg(寄存器)
- 定义:表示存储单元
- 赋值:不需要驱动,可在任何时候可以赋值
reg addr
3. 向量
字面意思,如下定义8bit寄存器
reg [7:0] addr
4. parameter
相当于常量
parameter SIZE 2'd10
3 操作符
优先级由高到低为
操作符 | 操作符号 |
---|---|
单目运算 | + - ! ~ |
乘、除、取模 | * / % |
加减 | + - |
移位 | « » |
关系 | < <= > >= |
等价 | == != === !=== |
归约 | & ~& |
^ ~^ | |
| ~| | |
逻辑 | && |
|| | |
条件 | ?: |
特殊操作符:{}
拼接操作符,可以拼接多个操作数为一个向量
data = {2'b01, 1'b0, 3'b101}
// data = 6'b010101
4 编译指令
- `define:类似C语言的#define,定义一个宏
- `undef:取消一个宏
- `include:类似C语言的#include,引入一个.v文件
- `timescale:定义时延、仿真的单位和精度
- `resetall:如其名,重置所有编译指令为缺省值
5 连续赋值
用于组合逻辑设计
对wire类型变量进行赋值,一旦设定了一个连续赋值后,每当等号右边的任何一个值发生变化,赋值将自动重新赋值,即wire每时每刻都等于右边的表达式结果
如下,Z一直等于X&Y
wire X, Y, Z
assign Z = X & Y
6 时延
可以用#+时延秒数实现时延
assign #10 Z = X & Y
// 当X或Y发生变化后会时延10秒才会赋值给Z
7 过程结构
1. initial
只执行一次,一帮用于给reg赋初值
reg clk;
reg [3:0] data;
initial begin
clk = 0;
data = 4'b0001;
end
2. always
重复执行,只要触发了设定的条件就执行一次
reg clk;
initial begin
clk = 0;
end
// 表示每隔1秒clk就取反
always #1 begin
clk = ~clk;
end
reg clk;
reg rst;
initial begin
clk = 0;
rst = 0;
end
// 表示每隔1秒clk就取反
always #1 begin
clk = ~clk;
end
// 表示每当clk处于上升沿就让rst取反
always @(posedge clk) begin
rst = ~rst;
end
注意
- initial不能与always嵌套嵌套
- 每个过程结构快都是并行执行,即initial与always是同时开始的
- 每个过程结构块内是顺序执行
8 过程赋值
1. 过程赋值与连续赋值
- 过程赋值:在initial或always块里的赋值,可以描述组合逻辑或时序逻辑,对象一般为寄存器
- 连续赋值:一般是assign关键字标志的赋值,用于描述组合逻辑,对象一般是wire
2. 阻塞赋值与非阻塞赋值
- 阻塞赋值:用=赋值,顺序执行,会阻塞
- 非阻塞赋值:用<=赋值,并行执行,不会阻塞
3. 避免竞争
如下例子,由于两个always块同时执行,使用阻塞赋值会导致两个对寄存器的操作竞争,使用非阻塞赋值则可以实现交换两个寄存器的值
// 竞争
always @(posedge clk) begin
a = b ;
end
always @(posedge clk) begin
b = a;
end
// 不竞争,结果是交换两个寄存器的值
always @(posedge clk) begin
a <= b ;
end
always @(posedge clk) begin
b <= a;
end
9 时序控制
1. 时延控制
用时延#10实现时延控制
2. 事件控制
事件一般是指某个reg或者wire发生变化,事件控制用@或wait触发
- 边沿触发事件控制:
一般事件:
- 关键字 posedge 指信号发生边沿正向跳变
- negedge 指信号发生负向边沿跳变
- 未指明跳变方向时,则 2 种情况的边沿变化都会触发
- 如果有多个,任何一个都可以触发
- *可以表示语句块中所有变量
always @(posedge clk) begin
data = 1'b0;
end
always @(negedge clk) begin
data = 1'b1;
end
always @(clk) begin
data = 1'b0;
end
// 等价于上一个
always @(posedge clk, negedge clk) begin
data = 1'b0;
end
// 当语句块中任何变量变化时都触发,A、B、C任一变化都触发
always @(*) begin
Z = A | (B & C);
end
命名事件:命名事件用event关键字,用->触发,用法不好解释,看例子
event reset;
always @(posedge clk) begin
-> reset;
end
//触发,重置data
always @(reset) begin
data = 1'b0;
end
- 电平敏感事件控制:用wait等待某一个信号为真
initial begin
// 等待start_signal
wait (start_signal);
data = 1'b0;
end
10 语句块
1. 顺序块
用begin与end标识,顺序块中语句顺序执行,但用非阻塞赋值可以实现并行执行
2. 并行块
用fork和join标识,即使语句是阻塞赋值,也会强制并行执行
3. 块命名
module test();
reg data;
// 命名为init
initial begin: init
data = 0'b0;
end
// 访问
test.init.data = 0'b1;
// 禁用该块
disable init;
endmodule
11 条件语句
if,else
reg select;
reg clk;
reg data;
if (clk) begin
if (select) begin
data = 1'b0;
end
else begin
data = 1'b1;
end
end
12 多路分支
case
always @(*) begin
case(select)
2'b00: begin
data = a;
end
2'b01: begin
data = b;
end
2'b10: begin
data = c;
end
default:begin
data = d;
end
endcase
end
13 循环
注意,循环只能在initial与always块中使用
1. while
initial begin
counter = 2'd0;
while (counter <= 10) begin
#10;
counter = counter + 1'd1;
end
end
2. for
// 整形变量i,之前没说
integer i;
initial begin
counter = 2'd0 ;
for (i = 0; i <= 10; i = i+1) begin
#10;
counter = counter + 1'd1;
end
end
3. repeat
固定次数
initial begin
counter = 2'd0;
repeat (10) begin //重复10次
#10;
counter = counter + 1'd1 ;
end
end
4. forever
永久执行,可以用来做一个时钟
initial begin
clk = 0;
forever begin
clk = ~clk;
#5;
end
end
// 其实等价于
always #5 begin
clk = ~clk;
end
14 模块
- 有点像class,但含义不同
- 用input表示模块输入,input只能是wire类型,用output表示模块输出,output可以是reg或wire
一个全加器模块
module full_adder_1bit(
input wire A,
input wire B,
input wire Cin,
output wire F,
output wire Cout
);
assign F = A^B^Cin;
assign Cout = (A&B) | ((A^B)&Cin);
endmodule
15 模块例化
- 有点像class对象实例化,但含义不同
- 对接input的可以是wire或reg,对接output的只能是wire
- genvar是一个特殊的变量类型,专门用于generate语句中的循环控制。genvar只能在编译时赋值,并且只能在generate语句中使用
- generate用来重复生成实例(注意,不能用for循环直接生成实例,需要用generate包起来)
一个32位加法器模块,用上述全加器实例化实现,注意一下接口写法
module full_adder_32bit(
input wire [31:0] A,
input wire [31:0] B,
input wire Cin,
output wire [31:0] F,
output wire Cout
);
wire [31:0] Cout_tmp;
full_adder_1bit u_full_adder_1bit(
.A (A[0]),
.B (B[0]),
.Cin (Cin),
.F (F[0]),
.Cout (Cout_tmp[0])
);
genvar i;
generate
for (i = 1; i <= 31 ; i = i+1) begin
full_adder_1bit u_full_adder_1bit(
.A (A[i]),
.B (B[i]),
.Cin (Cout_tmp[i-1]),
.F (F[i]),
.Cout (Cout_tmp[i])
);
end
endgenerate
assign Cout = Cout_tmp[31];
endmodule