0%

Verilog

数字表示

基本格式:< 位宽 >’< 数制的符号 >< 数值 >

h十六进制

d十进制

o八进制

b二进制

向量

可直接进行加减等运算,方括号位于向量名前方。eg: reg [7:0] data

数组

方括号位于数组名的后面。括号内的第一个数字为第一个元素的序号,第二 个数字为最后一个元素的序号,中间用冒号隔开。

eg: wire array [15:0];

参数

可以通过 parameter 关键字声明参数以增强模块可拓展性和可读性。

在模块实例化时,可以使用 #() 将所需的实例参数覆盖模块的默认参数。

局部参数可以用 localparam 关键字声明,它不能够进行参数重载。

eg:

1
2
3
4
5
6
7
8
9
10
module adder #(
parameter WIDTH = 4 // 默认宽度为 4
) (
input [WIDTH - 1 : 0] a,
input [WIDTH - 1 : 0] b,
output [WIDTH - 1 : 0] c
);
assign c = a + b;
endmodule

在我们例化这个模块时,可以进行如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
// 覆盖宽度,修改为 6
adder #( .WIDTH (6) ) U_adder_0(
.a (a ),
.b (b ),
.c (c )
);
// 使用默认的宽度 4
adder U_adder_1(
.a (a ),
.b (b ),
.c (c )
);

局部参数 localparam 和参数类似,但是不能在例化时被覆盖。

运算

按位运算

按位取反 ~

按位与 &

按位或 |

按位异或 ^

逻辑运算

• 逻辑取反!

• 逻辑与 &&

• 逻辑或 ||

缩减运算

• 缩减与 &:对一个多位操作数进行缩减与操作,计算所有位之间的与操作结果,例如:&(4’b1011) 的结果为 0

• 缩减或 |:对一个多位操作数进行缩减或操作,计算所有位之间的或操作结果。例如:|(4’b1011) 的结果为 1

• 缩减异或 ^:对一个多位操作数进行缩减异或操作,计算所有位之间的异或操作结果。例如: ^(4’b1011) 的结果为 1

缩减或非、缩减异或、缩减同或是类似的。

移位运算

• 逻辑右移>>:1 个操作数向右移位,产生的空位用 0 填充

• 逻辑左移<<:1 个操作数向左移位,产生的空位用 0 填充

• 算术右移>>>:1 个操作数向右移位。如果是无符号数,则产生的空位用 0 填充;有符号数则用其符号 位填充

• 算术左移<<<:1 个操作数向左移位,产生的空位用 0 填充

其他运算

• 拼接 {,}:2 个操作数分别作为高低位进行拼接,例如:{2’b10,2’b11} 的结果是 4’b1011

• 重复 {n{m}}:将操作数 m 重复 n 次,拼接成一个多位的数。例如:a=2’b01,则 {2{a}} 的结果 是 4’b0101

• 条件? ::根据? 前的表达式是否为真,选择执行后面位于: 左右两个语句。例如:assign c = (a > b) ? a : b,如果 a 大于 b,则将 a 的值赋给 c,否则将 b 的值赋给 c

组合逻辑

always

不推荐使用 always 块来表示组合逻辑。不正确的使用会生成大量锁存器。

下面的例子是一个 32-5 优先编码器,如果 tlb_hit_array 全为 0,那么 tlb_hit_index 也为 0。 always 块中被赋值的变量只能是 reg 类型,但是该编码器并不会综合出寄存器或者锁存器,这是因为 Verilog 中的寄存器 (reg) 和硬件上的寄存器不能完全等价。 如果去掉 tlb_hit_index = 5’d0; 一句,则会综合出锁存器。

1
2
3
4
5
6
7
8
9
10
11
reg [4 :0] tlb_hit_index;
wire [31:0] tlb_hit_array;
integer i;
always @(*) begin
tlb_hit_index = 5'd0;
for (i = 0; i < 32; i = i + 1) begin
if (tlb_hit_array[i]) begin
tlb_hit_index = i;
end
end
end

模块声明和例化

模块被包含在关键字 module、endmodule 之内。

1
2
3
4
5
6
7
module adder ( // 模块名称声明
input [31:0] a, // 输入输出声明
input [31:0] b,
output [31:0] c
);
assign c = a + b; // 变量声明、always 语句、assign 语句等
endmodule // 模块结束

Generate 块

这里只介绍 generate for 块。

generate for 的主要功能就是对模块或组件以及 always 块、assign 语句进行复制。 使用 generate for 的时候, 必须要注意以下几点要求

• 在使用 generate for 的时候必须先声明一个 genvar 变量,用作 for 的循环变量。genvar 是 generate 语句中的一种变量类型,用于在 generate for 语句中声明一个正整数的索引变量。

• for 里面的内嵌语句, 必须写在 begin-end 里

• 尽量对 begin-end 顺序块进行命名

generate for 的语法示例如下:

1
2
3
4
5
genvar i;
generate for (i = 0; i < 4; i = i + 1) begin: gen_assign_temp
assign temp[i] = indata[2 * i + 1 : 2 * i];
end
endgenerat

测试代码

时延语句

仿真中还经常使用时延来构造合适的仿真激励。它是不可综合的,仅能够在仿真中使用。时延分两类,一是 语句内部时延,二是语句间时延,其示例如下所示:

1
2
3
4
5
6
7
// 语句内部时延
A = #5 1'b1;
// 语句间时延
begin
Temp = 1'b1;
#5 A = Temp;
end

两种方式都表达在五个时间单位后,将 A 的值赋为 1。

initial 语句

一般用来生成复位信号和激励。只在仿真开始时执行一次。

1
2
3
4
5
6
7
8
9
// 测试代码中
// 假设`timescale 1ns / 1ps
reg rst_n, clk;
always #5 clk = ~clk;
initial begin
rst_n = 1'b0;
clk = 1'b0;
#50 rst_n = 1'b1;
end

会生成一个 100MHz 的时钟,一个 50ns 有效的复位信号。

系统任务

系统任务可以被用来执行一些系统设计所需的输入、输出、时序检查、仿真控制操作。所有的系统任务名称 前都带有符号 $ 使之与用户定义的任务和函数相区分。

常见的系统任务:

• $display:用于显示指定的字符串,然后自动换行(用法类似 C 语言中的 printf 函数)

• $time:可以提取当前的仿真时间

• $stop:暂停仿真

• $finish:终止仿真

• $random:生成随机数

• $readmemh:读入一个 16 进制数的文件以初始化 reg

测试设计

测试最基本的结构包括信号声明、激励和模块例化。 测试模块声明时,一般不需要声明端口。因为激励信号一般都在测试模块内部,没有外部信号。 声明的变量应该能全部对应被测试模块的端口。当然,变量不一定要与被测试模块端口名字一样。但是被测试模块输入端对应的变量应该声明为 reg 型,输出端对应的变量应该声明为 wire 型。 仿真过程中可以使用 $display 显示当前仿真进度或者测试结果。 在测试完成或者发现错误时可以使用 $finish; 或者 $stop; 来停止仿真。