07.Verilog syntax
07.Verilog syntax
Verilog 教程 | 菜鸟教程 (runoob.com)
过一遍这个教程,分析哪些语法是自己未用但可能有用的
三种参数对比
RTL基本知识:参数三姐妹-parameter-localparam-specparam
$display
// 观察下面的代码,选出正确的输出结果。
module printval;
reg [11:0] rl;
initial begin
rl=10;
$display("rl=%0d=%0h",rl,rl);
end
endmodule
//结果:
rl=10=a
%0表示用最少位数表示
$fmonitor、$fstrobe、$fdisplay、$fwrite都是用来写文件的。
$dumpfile 的作用是选择VCD文件的名称。
动态固定位宽截取
Verilog动态固定宽度截取,vect[base±:width],base表示起始位,width表示截取宽度,base可变,width必须为常量。
截取使用方括号[],拼接使用大括号{}。
vect[base+:width]表示升序截取,vect[base-:width]表示降序截取。
1、这个语法出于IEEE的Verilog标准文档5.2.1章节,用的很少,很多FPGA或Verilog书籍都没有讲述。
2、我们以大端reg vect[7 : 0]为例,首先要摒弃他是普通的位宽截取概念,比如vect[4:2]表示vect寄存器的4~2位,而vect[base+ : width]表示:vect[base+width-1 : base],如vect[4+ : 3] = vect[4+3-1 : 0] =vect[6 : 4]。
3、vect[base- : width] = vect[base : base-width+1],如vect_testb[4-:3] = vect_testb [4:2]
4、截取位宽width不可变,起始值base可变
可综合与不可综合
**1)所有综合工具都支持的结构:**always,assign,begin,end,case,wire,tri,aupply0,supply1,reg,integer,default,for,function,and,nand,or,nor,xor,xnor,buf,not,bufif0,bufif1,notif0,notif1,if,inout,input,instantitation,module,negedge,posedge,operators,output,parameter。
**2)所有综合工具都不支持的结构:**time,defparam,$finish,fork,join,initial,delays,UDP,wait。
**3)有些工具支持有些工具不支持的结构:**casex,casez,wand,triand,wor,trior,real,disable,forever,arrays,memories,repeat,task,while。
verilog 中的系统函数和关键字
$time //可以返回一个 64 位的整数来表示的当前仿真时刻,该时刻是以模块的仿真时间尺度位基准的
$finish
和 $finish(n)
用于退出仿真器。该语句可以携带参数 0、1、2,如果不带参数,默认 $finish
的参数值为 1,不同参数命令如下:
- 0 不输出任何信息
- 1 输出当前仿真时刻和位置
- 2 输出当前仿真时刻、位置和在仿真过程中所用 memory 以及 CPU 时间的统计
$stop
用于将 EDA 工具(例如仿真器)置成暂停模式,在仿真环境下给出一个交互式的命令提示符,将控制权交给用户。格式与 $finish
类似,不同参数命令如下:
- 0:不输出任何信息;
- 1:输出当前仿真时刻和位置;
- 2:输出当前仿真时刻、位置和仿真过程中所用的 memory 及 CPU 时间的统计
$random
用于产生一个随机数,当函数被调用时返回一个 32 位的随机数,它是一个带符号的整形数。其用法是:
$random %b
(b>0),它给出了一个范围在(-b+1): (b-1)中的随机数
num = {$random}%b
则是用于产生一个随机正数
clog2函数
在Vivado 2017以后的版本中,可以直接使用系统函数$clog2()
在Verilog编写代码过程中,经常需要根据一个常量来定义一个变量的位宽,例如,要编写一个计数器,计数最大值为常量N,那么计数变量cnt的位宽应该是多少呢?
reg [$clog2(CNT_MAX+1)-1 : 0] cnt;
// 返回以2为底的n的对数
function integer clog2 (input integer n); begin
n = n - 1;
for (clog2 = 0; n > 0; clog2 = clog2 + 1)
n = n >> 1;
end
endfunction
函数
在 Verilog 中,可以利用任务(关键字为 task)或函数(关键字为 function),将重复性的行为级设计进行提取,并在多个地方调用,来避免重复代码的多次编写,使代码更加的简洁、易懂。
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:
- 1)不含有任何延迟、时序或时序控制逻辑
- 2)至少有一个输入变量
- 3)只有一个返回值,且没有输出
- 4)不含有非阻塞赋值语句
- 5)函数可以调用其他函数,但是不能调用任务
function [range-1:0] function_id;
input_declaration;
other_declaration;
procedural_statement;
endfunction
函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个变量进行传递。当该寄存器变量没有指定位宽时,默认位宽为 1。
函数通过指明函数名与输入变量进行调用。函数结束时,返回值被传递到调用处。
函数调用格式如下:
function_id(input1, input2, …);
下面用函数实现一个数据大小端转换的功能。
当输入为 4'b0011 时,输出可为 4'b1100。例如:
module endian_rvs
#(parameter N = 4)
(
input en, //enable control
input [N-1:0] a ,
output [N-1:0] b
);
reg [N-1:0] b_temp ;
always @(*) begin
if (en) begin
b_temp = data_rvs(a);
end
else begin
b_temp = 0 ;
end
end
assign b = b_temp ;
//function entity
function [N-1:0] data_rvs ;
input [N-1:0] data_in ;
parameter MASK = 32'h3 ;
integer k ;
begin
for(k=0; k<N; k=k+1) begin
data_rvs[N-k-1] = data_in[k] ;
end
end
endfunction
endmodule
1. 常数函数
常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。
这种函数能够用来引用复杂的值,因此可用来代替常量。
例如下面一个常量函数,可以来计算模块中地址总线的宽度:
parameter MEM_DEPTH = 256 ;
reg [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的宽度为8bit
function integer logb2;
input integer depth ;
//256为9bit,我们最终数据应该是8,所以需depth=2时提前停止循环
for(logb2=0; depth>1; logb2=logb2+1) begin
depth = depth >> 1 ;
end
endfunction
2. automatic 函数
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。
Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。
wire [31:0] results3 = factorial(4);
function automatic integer factorial ;
input integer data ;
integer i ;
begin
factorial = (data>=2)? data * factorial(data-1) : 1 ;
end
endfunction // factorial
任务

任务在模块中任意位置定义,并在模块内任意位置引用,作用范围也局限于此模块。
模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数。
- 1)子程序中包含时序控制逻辑,例如延迟,事件控制等
- 2)没有输入变量
- 3)没有输出或输出端的数量大于 1
task task_id ;
port_declaration ;
procedural_statement ;
endtask
task xor_oper_iner;
input [N-1:0] numa;
input [N-1:0] numb;
output [N-1:0] numco ;
//output reg [N-1:0] numco ; //无需再注明 reg 类型,虽然注明也可能没错
#3 numco = numa ^ numb ;
//assign #3 numco = numa ^ numb ; //不用assign,因为输出默认是reg
endtask
1.automatic 任务
如果一任务代码段被 2 处及以上调用,一定要用关键字 automatic 声明。
task automatic test_flag ;
数值表示
1.不指明位宽
counter = 'd100 ; //一般会根据编译器自动分频位宽,常见的为32bit
2.字符串表示
字符串是由双引号包起来的字符队列。字符串不能多行书写,即字符串中不能包含回车符。Verilog 将字符串当做一系列的单字节 ASCII 字符队列。例如,为存储字符串 "www.runoob.com", 需要 14*8bit 的存储单元。例如:
reg [0: 14*8-1] str ;
initial begin
str = "www.runoob.com";
end
3.任务操作全局变量
数据类型
1.整数(integer)
整数类型用关键字 integer 来声明。声明时不用指明位宽,位宽和编译器有关,一般为32 bit。reg 型变量为无符号数,而 integer 型变量为有符号数。
reg [31:0] data1 ;
reg [3:0] byte1 [7:0]; //数组变量,后续介绍
integer j ; //整型变量,用来辅助生成数字电路
always@* begin
for (j=0; j<=3;j=j+1) begin
byte1[j] = data1[(j+1)*8-1 : j*8];
//把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
end
end
此例中,integer 信号 j 作为辅助信号,将 data1 的数据依次赋值给数组 byte1。综合后实际电路里并没有 j 这个信号,j 只是辅助生成相应的硬件电路。
2.时间(time)
Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。
time current_time ;
initial begin
#100 ;
current_time = $time ; //current_time 的大小为 100
end
3.数组
在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。
数组维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:<数组名>[<下标>]。对于多维数组来讲,用户需要说明其每一维的索引。
integer flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组
flag [1] = 32'd0 ; //将flag数组中第二个元素赋值为32bit的0值
counter[3] = 4'hF ; //将数组counter中第4个元素的值赋值为4bit 十六进制数F,等效于counter[3][3:0] = 4'hF,即可省略宽度;
assign addr_bus[0] = 8'b0 ; //将数组addr_bus中第一个元素的值赋值为0
assign data_bit[0][1] = 1'b1; //将数组data_bit的第1行第2列的元素赋值为1,这里不能省略第二个访问标号,即 assign data_bit[0] = 1'b1; 是非法的。
data_4d[0][0][0][0][15:0] = 15'd3 ; //将数组data_4d中标号为[0][0][0][0]的寄存器单元的15~0bit赋值为3
4.存储器
存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为
reg membit[0:255] ; //256bit的1bit存储器
reg [7:0] mem[0:1023] ; //1Kbyte存储器,位宽8bit
mem[511] = 8'b0 ; //令第512个8bit的存储单元值为0
5.特殊字符
转义字符 | 显示字符 |
---|---|
\n | 换行 |
\t | 制表符 |
%% | % |
\ | \ |
\" | \ |
\ooo | 1到3个8进制数字字符 |
表达式
1.表达式
address[9:0] + 10'b1 ; //地址累加
在 Verilog 中,address[9:0] + 10'b1
表示将一个 10 位的二进制数 1
加到 address
数组的最低 10 位上。
2.操作符
Verilog 中提供了大约 9 种操作符,分别是算术、关系、等价、逻辑、按位、归约、移位、拼接、条件操作符。
运算符优先级

3.按位操作符
按位操作符对 2 个操作数的每 1bit 数据进行按位操作。
如果 2 个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。
取反操作符只有一个操作数,它对操作数的每 1bit 数据进行取反操作。
4.归约操作符
只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 1bit 结果。
A = 4'b1010 ;
&A ; //结果为 1 & 0 & 1 & 0 = 1'b0,可用来判断变量A是否全1
~|A ; //结果为 ~(1 | 0 | 1 | 0) = 1'b0, 可用来判断变量A是否为全0
^A ; //结果为 1 ^ 0 ^ 1 ^ 0 = 1'b0
5.算数操作符
// 如果操作数某一位为 X,则计算结果也会全部出现 X
b = 4'b100x ;
c = a+b ; //结果为c=4'bxxxx
6.等价操作符
等价操作符包括逻辑相等(),逻辑不等(!=),全等(=),非全等(!==)。
等价操作符的正常结果有 2 种:为真(1)或假(0)。
逻辑相等/不等操作符不能比较 x 或 z,当操作数包含一个 x 或 z,则结果为不确定值。
全等比较时,如果按位比较有相同的 x 或 z,返回结果也可以为 1,即全等比较可比较 x 或 z。所以,全等比较的结果一定不包含 x。
7.移位操作符
移位操作符包括左移(<<),右移(>>),算术左移(<<<),算术右移(>>>)。
移位操作符是双目操作符,两个操作数分别表示要进行移位的向量信号(操作符左侧)与移动的位数(操作符右侧)。
算术左移和逻辑左移时,右边低位会补 0。
逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。
A = 4'b1100 ;
B = 4'b0010 ;
A = A >> 2 ; //结果为 4'b0011
A = A << 1; //结果为 4'b1000
A = A <<< 1 ; //结果为 4'b1000
C = B + (A>>>2); //结果为 2 + (-4/4) = 1, 4'b0001
编译指令
以反引号 ` 开始的某些标识符是 Verilog 系统编译指令。
详细未展开,认为用不到
时延
连续赋值延时语句中的延时,用于控制任意操作数发生变化到语句左端赋予新值之间的时间延时。
时延一般是不可综合的。
寄存器的时延也是可以控制的,这部分在时序控制里加以说明。
连续赋值时延一般可分为普通赋值时延、隐式时延、声明时延。
过程结构 initial always
每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。
initial
initial 理论上来讲是不可综合的,多用于初始化、信号检测等。
always
由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。
`timescale 1ns/1ns
module test ;
parameter CLK_FREQ = 100 ; //100MHz
parameter CLK_CYCLE = 1e9 / (CLK_FREQ * 1e6) ; //switch to ns
reg clk ;
initial clk = 1'b0 ; //clk is initialized to "0"
always # (CLK_CYCLE/2) clk = ~clk ; //generating a real clock by reversing
always begin
#10;
if ($time >= 1000) begin
$finish ;
end
end
endmodule
时序控制
Verilog 提供了 2 大类时序控制方法:时延控制和事件控制。事件控制主要分为边沿触发事件控制与电平敏感事件控制。
1.内嵌时延
遇到内嵌延时时,该语句先将计算结果保存,然后等待一定的时间后赋值给目标信号。
reg value_test ;
reg value_embed ;
value_embed = #10 value_test ;
如果目标是实现硬件逻辑,那么应该避免使用事件,并使用同步触发信号来控制逻辑

initial begin
wait (start_enable) ; //等待 start 信号
forever begin
//start信号使能后,在clk_samp上升沿,对数据进行整合
@(posedge clk_samp) ;
data_buf = {data_if[0], data_if[1]} ;
end
end
3.语句块
- 并行块 fork join
- 嵌套块
- 命名块
- disable 可以终止命名块的执行,可以用来从循环中退出、处理错误等。
`timescale 1ns/1ns
module test;
initial begin: runoob_d //命名模块名字为runoob_d
integer i_d ;
i_d = 0 ;
while(i_d<=100) begin: runoob_d2
# 10 ;
if (i_d >= 50) begin //累加5次停止累加
disable runoob_d3.clk_gen ;//stop 外部block: clk_gen
disable runoob_d2 ; //stop 当前block: runoob_d2
end
i_d = i_d + 10 ;
end
end
reg clk ;
initial begin: runoob_d3
while (1) begin: clk_gen //时钟产生模块
clk=1 ; #10 ;
clk=0 ; #10 ;
end
end
endmodule
多路分支
1.case
case(case_expr)
condition1 : true_statement1 ;
condition2 : true_statement2 ;
……
default : default_statement ;
endcase
2.casex/casez
casex、 casez 语句是 case 语句的变形,用来表示条件选项中的无关项。
casex 用 "x" 来表示无关值,casez 用问号 "?" 来表示无关值。
两者的实现的功能是完全一致的,语法与 case 语句也完全一致。
但是 casex、casez 一般是不可综合的,多用于仿真。

casez语句用来处理不考虑高阻值z的比较过程;casex语句用来处理不考虑高阻值z和不定态x的比较过程;对于case语句,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配。
module mux4to1(
input [3:0] sel ,
input [1:0] p0 ,
input [1:0] p1 ,
input [1:0] p2 ,
input [1:0] p3 ,
output [1:0] sout);
reg [1:0] sout_t ;
always @(*)
casez(sel)
4'b???1: sout_t = p0 ;
4'b??1?: sout_t = p1 ;
4'b?1??: sout_t = p2 ;
4'b1???: sout_t = p3 ;
default: sout_t = 2'b0 ;
endcase
assign sout = sout_t ;
endmodule
循环
1.while
while (condition) begin
…
end
2.for
// for 循环语句
integer i ;
reg [3:0] counter2 ;
initial begin
counter2 = 'b0 ;
for (i=0; i<=10; i=i+1) begin
#10 ;
counter2 = counter2 + 1'b1 ;
end
end
3.repeat
可综合
repeat 的功能是执行固定次数的循环,它不能像 while 循环那样用一个逻辑表达式来确定循环是否继续执行。
repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。
即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。
always @(posedge clk or negedge rstn) begin
j = 0 ;
if (!rstn) begin
repeat (8) begin
buffer[j] <= 'b0 ; //没有延迟的赋值,即同时赋值为0
j = j + 1 ;
end
end
else if (enable) begin
repeat (8) begin
@(posedge clk) buffer[j] <= counter3 ; //在下一个clk的上升沿赋值
j = j + 1 ;
end
end
end
4. forever
forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。
reg clk ;
reg data_in, data_temp ;
initial begin
forever @(posedge clk) data_temp = data_in ;
end
模块例化
如果某些输出端口并不需要在外部连接,例化时 可以悬空不连接,甚至删除。一般来说,input 端口在例化时不能删除,否则编译报错,output 端口在例化时可以删除。
1. generate
generate-for中的begin end块必须有名字,且在同一module中不可重复;generate-for语句必须用genvar关键字,来定义for的循环变量,不可使用其他整型标量。
当例化多个相同的模块时,一个一个的手动例化会比较繁琐。用 generate 语句进行多个模块的重复例化,可大大简化程序的编写过程。
重复例化 4 个 1bit 全加器组成一个 4bit 全加器的代码如下:
module full_adder4(
input [3:0] a , //adder1
input [3:0] b , //adder2
input c , //input carry bit
output [3:0] so , //adding result
output co //output carry bit
);
wire [3:0] co_temp ;
//第一个例化模块一般格式有所差异,需要单独例化
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b1 : 1'b0),
.So (so[0]),
.Co (co_temp[0]));
genvar i ;
generate
for(i=1; i<=3; i=i+1) begin: adder_gen
full_adder1 u_adder(
.Ai (a[i]),
.Bi (b[i]),
.Ci (co_temp[i-1]), //上一个全加器的溢位是下一个的进位
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
assign co = co_temp[3] ;
endmodule
2.层次访问

//u_n1模块中访问u_n3模块信号:
a = top.u_m2.u_n3.c ;
//u_n1模块中访问top模块信号
if (top.p == 'b0) a = 1'b1 ;
//top模块中访问u_n4模块信号
assign p = top.u_m2.u_n4.d ;
3. 带参数例化
ram #(.AW(4), .DW(4))
u_ram
(
.CLK (clk),
.A (a[AW-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q)
);
Verilog 中的 FFT
快速傅里叶变换 (FFT) 在硬件电路实现中具有多种重要作用,主要体现在以下几个方面:
信号处理:FFT 是数字信号处理中的一个基本工具,用于将时域信号转换为频域信号。这在通信、音频处理和图像处理等应用中非常重要。
性能提升:与传统的傅里叶变换相比,FFT 大大提高了计算效率。FFT 将 O(N2)O(N2) 的计算复杂度降低到 O(NlogN)O(NlogN) ,这使得在实时处理环境中更易于实现。
频谱分析:FFT 能够分析信号的频谱特性,识别出信号中的主要频率成分。这在无线通信、雷达和声纳等领域有助于信号监测和分析。
滤波应用:通过对频域信号的处理,FFT 可用于设计和实现各种滤波器(如低通、高通、带通等)。基于频域的滤波可以更有效地实现信号的去噪和改善质量。
傅里叶变换的目的:时域信号通过傅立叶变换FFT得到频域,对相应的频率做过滤,再逆变换得到时域信号。
资源优化:在 FPGA 和 ASIC 设计中,FFT 的硬件实现可以有效利用可编程逻辑,并在多变的信号处理需求下提供灵活的解决方案。
并行处理能力:FFT 可以在硬件级别实现并行计算,这使得其在高吞吐量应用中尤其有效。例如,多个并行的计算单元可以同时处理不同的数据块。
数据压缩:在图像和视频处理领域,FFT 被广泛应用于数据压缩技术中,比如 JPEG 和 MPEG 标准。在频域中,某些重要信息成分会显得更为突出,有助于去除冗余数据。
调制解调:在通信系统中,FFT 用于调制和解调过程,比如 OFDM(正交频分复用)系统中,通过FFT可以实现接收信号的频域解析。
FFT 能够分析信号的频谱特性,请问影响频谱分辨率的因素有哪些?
- 信号长度:信号的采样长度越长,频谱分辨率越高。具体来说,频谱分辨率等于采样频率除以采样点数 ,其中 是采样的点数, 是采样频率。
- 采样频率:采样频率越高,能够捕捉到更高的频率成分,从而提高频率覆盖范围,但并不直接影响分辨率。
- 窗函数:选择不同的窗函数(如汉宁窗、汉明窗等)会影响频谱的旁瓣泄漏和主瓣宽度,从而影响频率分辨率和动态范围。
- 重叠与分段:在进行斜向 FFT 分析时,信号的重叠程度和分段策略也会影响频谱估计的平滑度和分辨率。
- 信号特征:信号本身的特性,如频率成分之间的距离,如果频率间隔过近,可能会导致难以分辨。