数字IC设计\FPGA 职位经典笔试面试整理--语法篇 Verilog System Verilog(部分)
注: 资料都是基于网上一些博客分享和自己学习整理而成的
Verilog
1. 数据类型
Verilog一共有19种数据类型
基础四种数据类型:reg型,wire型,integer型,parameter型
- reg型
reg类型是寄存器数据类型的关键字。寄存器是数据存储单元的抽象,通过赋值语句可以改变寄存器存储的值,其作用相当于改变触发器存储器的值。reg类型常用在always模块中指定信号,代表触发器。在always块内被赋值的每一个信号都必须定义为reg型,即赋值操作符的右端变量必须是reg型。通常在设计中要由always模块通过使用行为描述语句来表达逻辑关系。
reg 型信号的定义格式如下:
reg [n-1:0] 数据名 1,数据名2,……数据名N; 共定义了N个寄存器变量,每个寄存器的位宽为n。 - wire型
wire类型是可以看成是单向的物理连线,通过wire连接输入输出的情况。Verilog程序模块中输入、输出信号类型默认为wire型。wire型信号可以用做方程式的输入,也可以用做“assign”语句或者实例元件的输出。
wire 型信号的定义格式如下:
wire [n-1:0] 数据名 1,数据名2,……数据名N; 共定义了N条线,每条线的位宽为n。 - integer型
integer也是一种寄存器数据类型,integer 类型的变量为有符号数,而 reg 类型的变量则为无符号数,除非特别声明为有符号数。 integer 的位宽为宿主机的字的位数,但最小为32位,用integer的变量都可以用
reg 定义。 - parameter型
parameter 定义常量,即用parameter来定义一个标志符表示一个常数。采用该类型可以提高程序的可读性和可维护性。
parameter 型信号的定义格式如下:
parameter 参数名 1 = 数据;
其他数据类型: large 型、medium 型、small 型、scalared 型、time 型、tri 型、trio 型、tril 型、triand 型、trior 型、trireg 型、vectored 型、wand 型和 wor 型。
2. 常用关键字
关键字 | 含义 |
---|---|
module | 模块开始定义 |
input | 输入端口定义 |
output | 输出端口定义 |
inout | 双向端口定义 |
parameter | 信号的参数定义 |
wire | wire信号定义 |
reg | reg信号定义 |
always | 产生reg信号语句的关键字 |
assign | 产生wire信号语句的关键字 |
begin | 语句的起始标志 |
end | 语句的结束标志 |
posedge/negedge | 时序电路的标志 |
case | Case语句起始标记 |
default | Case语句的默认分支标志 |
endcase | Case语句结束标记 |
if | if/else语句标记 |
else | if/else语句标记 |
for | for语句标记 |
endmodule | 模块结束定义 |
3. 运算符
类型 | 运算符 |
---|---|
算术运算符 | +,-,×,/,% |
赋值运算符 | =,<= |
关系运算符 | >,<,<=,>= |
逻辑运算符 | &&,||,! |
条件运算符 | (?:)三目运算符 |
位运算符 | |,^,&,^~ |
移位运算符 | >>,<< |
拼接运算符 | { } |
特别的 缩减运算:是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。 位运算则是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是几位数。
缩减运算的具体运算过程是这样的:
- 第一步先将操作数的第一位与第二位进行或与非运算,
- 第二步将运算结果与第三位进行或与非运算,
- 依次类推,直至最后一位。
4. if-else
设计要点
- 条件语句必须在过程块中使用。所谓过程块语句是指由initial、always引导的执行语句
集合。除了这两个语句块引导的begin end块中可以编写条件语句外,模块中的其他地
方都不能编写。 - if 语句中的表达式一般为逻辑表达式或者关系表达式。系统对表达式的值进行判断;
若为0,z,X;按照假处理;若为1按照真处理,执行指定的语句; - if(a)等价于 if(a == 1);
- if语句可以·嵌套·使用
- end总是与离它最近的一份else配对。
注:如果if语句使用不当,没有else, 可能会综合出来意想不到的锁存器。在always 块里面,如果在给定的条件下变量没有被赋值,这个变量将会保持原来的值,也就是说会生成一个锁存器。
5. case
case 语句检查给定的表达式是否与列表中的其他表达式之一相匹配,并据此进行分支。 它通常用于实现一个多路复用器。 如果要检查的条件很多,if-else结构可能不合适,因为它会综合成一个优先编码器而不是多路复用器。
特别的: casez与casex语句是case语句的两种变体, 在写testbench时用到。综合工具并不会认识x,z这个状态,所以综合出来的电路是一样case类型的。
- 在case语句中,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配。
- 在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就会忽略,不予考虑,而只关注其他位的比较结果。
- 在casex语句中,则把这种处理方式进一步扩展到对x的处理,即如果比较双方有一方的某些位的值是z或x,那么这些位的比较就不予考虑。
Verilog case 语句以 case 关键字开始,以 endcase 关键字结束。在括弧内的表达式将被精确地评估一次,并按其编写顺序与备选方案列表进行比较,与给定表达式匹配的备选方案的语句将被执行。一块多条语句必须分组,并在 begin 和 end 范围内。
// Here 'expression' should match one of the items (item 1,2,3 or 4)
case (<expression>)
case_item1 : <single statement>
case_item2,
case_item3 : <single statement>
case_item4 : begin
<multiple statements>
end
default
endcase
: <statement>
如果所有的 case 项都不符合给定的表达式,则执行缺省项内的语句,缺省语句是可选的,在case 语句中只能有一条缺省语句。case语句可以嵌套。 如果没有符合表达式的项目,也没有给出缺省语句,执行将不做任何事情就退出case块。 同 if else,case 应当加上default,以避免锁存器出现,如果case的情况是完备的,可以不加。
6. for 循环 && generate 语句
在硬件描述语言中for语句的使用较C语言等软件描述语言有较大的区别。 for 循环会被综合器展开为所有变量情况的执行语句,每个变量独立占用寄存器资源。 简单的说就是:for 语句循环几次,就是将相同的电路复制几次,因此循环次数越多,占用面积越大,综合就越慢。配合 generate可以用for语句进行相同模块的多次例化
Verilog 中的 generate 语句常用于编写可配置的、可综合的RTL的设计结构。它可用于创建
模块的多个实例化,或者有条件的实例化代码块。
module top(
input [8*4-1 : 0] din0,
input [8*4-1 : 0] din1,
output logic [8*4-1 : 0] dout0,
output logic [8*4-1 : 0] dout1
);
genvar i; //genvar 循环变量名,只能用在generate(多例化语句)中
//例化出来的模块名编译器自动用begin后面的名字来区分,比如上面这个例子编译后模块名叫inst[0]、inst[1]......
generate for(i=0; i<4; i++) begin:inst
sub u_sub_0 (
//output
.dout(dout0[i*8 +: 8]),
//input
.din(din0[i*8 +: 8])
);
sub u_sub_1 (
//output
.dout(dout1[i*8 +: 8]),
//input
.din(din1[i*8 +: 8])
);
end
endgenerate
endmodule
7. function 函数 && tesk 任务
这两个东西经常放在一起判别
function
function <返回值的类型或范围> <函数名>
<端口说明语句>
<变量类型说明>
begin
<语句>
…
end
endfunction
// 示例
function [7:0] getbyte ; //默认 getbyte为同名返回寄存器
input [15:0] address ; //这个也可以放在声明处
begin
<说明语句> //从地址字节提取低字节的程序
getbyte = result_expression ; //把结果赋给函数的返回字节
end
endfunction
说明
① <返回值的类型或范围>这一项为可选项,如果缺失,则返回值为一位寄存器类型数据。
② 从函数的返回值:函数的定义蕴含声明了与函数同名、位宽一致的内部寄存器。例子中,
getbyte被赋予的值就是调用函数的返回值。
③ 函数的调用:函数的调用是通过将函数作为表达式中的操作数来实现的。其调用格式:
<函数名> (<表达式> ,…, <表达式>); 其中函数名作为确认符。下面的例子中,两次调用getbyte,把两次调用的结果进行位拼接运算,以生成一个字。
word = control ? {getbyte(msbyte),getbyte(lsbyte)} : 8’d0 ;
④ 函数使用的规则
1 函数定义不能包含有任何的时间控制语句,即任何用#、@、wait来标识的语句。
2 函数不能调用“task”。
3 定义函数时至少要有一个输入参数。
4 在函数的定义中必须有一条赋值语句给函数中与函数名同名、位宽相同的内部寄存器赋值。
5 verilog中的function只能用于组合逻辑;
tesk
任务就是一段封装在“task-endtask”之间的程序。任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。 调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。另外,任务可以彼此调用,而且任务内还可以调用函数。
任务定义的形式如下:
task task_id; // task 标志着一个 任务定义结构的开始;task_id 是任务名;
[declaration]
//可选项 declaration 是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;
procedural_statement
//procedural_statement 是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;
endtask //endtask 为任务定义结构体结束标志。下面给出一个任务定义的实例。
task task_demo; //任务定义结构开头,命名为 task_demo
input [7:0] x,y; //输入端口说明
output [7:0] tmp; //输出端口说明
if(x>y) //给出任务定义的描述语句
tmp = x;
else
tmp = y;
endtask
注:任务中不能出现 initial 语句和 always 语句语句,但任务调用语句可以在 initial 语句 和 always 语句中使用
其语法形式如下:task_id[(端口 1, 端口 2, …, 端口 N)]; 其中 task_id 是要调用的任务名,端口 1、端口 2,…是参数列表。
参数列表给出传入任 务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果)。**任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。**任务调用语句是过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。
例:通过 Verilog HDL 的任务调用实现一个 4 比特全加器。
module EXAMPLE (A, B, CIN, S, COUT);
input [3:0] A, B;
input CIN;
output [3:0] S;
output COUT;
reg [3:0] S;
reg COUT;
reg [1:0] S0, S1, S2, S3;
task ADD;
input A, B, CIN;
output [1:0] C;
reg [1:0] C;
reg S, COUT;
begin
S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
C = {COUT, S};
end
endtask
always @(A or B or CIN) begin
ADD (A[0], B[0], CIN, S0);
ADD (A[1], B[1], S0[1], S1);
ADD (A[2], B[2], S1[1], S2);
ADD (A[3], B[3], S2[1], S3);
S = {S3[0], S2[0], S1[0], S0[0]};
COUT = S3[1];
end
endmodule
8. 过程块 initial && always
过程块是行为模型的基础,过程块有两种: initial 块,只能执行一次;always 块,循环执行
initial 块
initial块常用于测试文件和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。用initial语句来生成激励波形作为电路的测试仿真信号。一个模块中可以有多个initial块,它们都是并行运行的。
always 块
边沿触发的always 块常常描述时序逻辑,如果符合可综合风格要求可用综合工具自动转换为表示时序逻辑的寄存器组和门级逻辑,而电平触发的always 块常常用来描述组合逻辑和带锁存器的组合逻辑,如果符合可综合风格要求可转换为表示组合逻辑的门级逻辑或带锁存器的组合逻辑。一个模块中可以有多个always块,它们都是并行运行的。
9. Verilog中的可综合和不可综合语句
Verilog是描述硬件电路的,一个语言描述的程序映射成实际硬件电路中的结构时时可实现即为可综合语句。
task和function都是可综合的,不过综合出来的都是组合逻辑电路。要想可综合,task和function内部必须是组合逻辑。 循环语句(repeat、while、for)也可以用于可综合电路设计,当采用循环语句进行计算和赋值操作时,可以综合得到逻辑电路。
Verilog中可综合语句: input、output、parameter、reg、wire、always、assign、begin…end、case、posedge、negedge、or、and、default、if、function、generate、integer、`define,while、repeat 、for (while、repeat循环可综合时,要具有明确的循环表达式和循环条件,for可综合时也要有具体的循环范围)
不可综合语句: initial、fork… join、wait、time、real、display、forever。
要保证Verilog HDL 赋值语句的可综合性,应注意以下要点:
(1)不使用initial。
(2)不使用#10。 以#开头的延时不可综合成硬件电路延时,综合工具会忽略所有延时代码,但不会报错。 如:a=#10 b; 这里的#10是用于仿真时的延时,在综合的时候综合工具会忽略它。也就是说,在综合的时候上式等同于a=b;
(3)不使用循环次数不确定的循环语句,如forever、while等。
(4)不使用用户自定义原语(UDP元件)。
(5)尽量使用同步方式设计电路。
(6)除非是关键路径的设计,一般不采用调用门级元件来描述设计的方法,建议采用行为语句来完成设计。
(7)用always过程块描述组合逻辑,应在敏感信号列表中列出所有的输入信号。
(8)所有的内部寄存器都应该能够被复位,在使用FPGA实现设计时,应尽量使用器件的全局复位端作为系统总的复位。
(9)对时序逻辑描述和建模,应尽量使用非阻塞赋值方式。对组合逻辑描述和建模,既可以用阻塞赋值,也可以用非阻塞赋值。但在同一个过程块中,最好不要同时用阻塞赋值和非阻塞赋值。
(10)不能在一个以上的always 过程块中对同一个变量赋值。而对同一个赋值对象不能既使用阻塞式赋值,又使用非阻塞式赋值。
(11)如果不打算把变量推导成锁存器,那么必须在if语句或case语句的所有条件分支中都对变量明确地赋值。
(12)避免混合使用上升沿和下降沿触发的触发器。
(13)同一个变量的赋值不能受多个时钟控制,也不能受两种不同的时钟条件(或者不同的时钟沿)控制。
(14)避免在case语句的分支项中使用x值或z值。
10. 阻塞/非阻塞
阻塞赋值
阻塞赋值使用的赋值运算符为“=”。阻塞赋值的过程是立刻执行的,即阻塞赋值运算符右侧表达式求值完后立刻会更新至运算符左侧,并且这个执行的过程不受其他语句执行的影响,其后的语句只有当前的赋值操作执行完成后才能顺序执行。
非阻塞赋值
非阻塞赋值使用的赋值运算符为“<=”。非阻塞赋值执行过程为:在当前仿真时间槽(timeslot)开始分析计算获得右侧表达式的值,在当前时间槽执行结束时更新左侧表达式的值,在右侧表达式分析计算和左侧表达式被更新之间,任何其他事件都可以执行,同时也有可能修改已经计算完成的右侧表达式的值,即非阻塞赋值的过程不影响其他语句的执行。
11. Verilog 模块编程的8个原则:
- 时序电路建模时,用非阻塞赋值。
- 锁存器电路建模时,用非阻塞赋值。
- 用always块建立组合逻辑模型时,用阻塞赋值。
- 在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。
- 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
- 不要在一个以上的always块中为同一个变量赋值。
- 用$strobe 系统任务来显示用非阻塞赋值的变量值。
- 在赋值时不要使用#0延时。
12. 时间尺度 timescale
timescale 是 Verilog HDL 中的一种时间尺度预编译指令,它用来定义模块的仿真时的时间
单位和时间精度。格式如下:
` timescale<时间单位>/<时间精度>
注:
- 用于说明仿真时间单位和时间精度的数字只能是1、10、100,不能为其它的数字。
而且,时间精度不能比时间单位还要大,最多两则一样大。`timescale 100ns/100ns - 在编译过程中,timescale 指令影响这一编译器指令后面所有模块中的时延值,直至遇到另一个timescale 指令resetall 指令。 在verilog 中是没有默认timescale的,一个没有指定timescale的verilog模块就有可能错误的继承了前面编译模块的无效timescale参数。
13. 存储器设计
Verilog 中提供了两维数组来帮助我们建立内存的行为模型。具体来说,就是可以将内存定义为一个reg类型的数组,这个数组中的任何一个单元都可以通过一个下标去访问。这样的数组的定义方式如
数组(内存)定义
reg [wordsize : 0] array_name [0 : arraysize];
例如:
reg [7:0] my_memory[0:255];
其中 [7:0] 是内存的宽度(位宽),而 [0:255] 则是 内存的深度(也就是有多少存储单元),其中宽度为8位,深度为256。地址0对应着数组中的0存储单元。
写操作: 如果要存储一个值到某个单元中去,可以这样做: my_memory[address] = data_in;
读操作: 而如果要从某个单元读出值,可以这么做: data_out = my_memory[address];
14. 三态门设计
三态指其输出既可以是一般二值逻辑电路,即正常的高电平(逻辑 1)或低电平(逻辑 0),又可以保持特有的高阻抗状态,高阻态相当于隔断状态(电阻很大,相当于开路)。
//下面是三态门的 Verilog 代码实现:
module Tri(
input din,
input en,
output reg dout );
always @(din or en)
if (en)
dout <= din;
else
dout <= 1'bz;
// 数据流描述 assign dout = en ? din : 1'bz;
endmodule
15. 原语
原语,即primitive。不同的厂商,原语不同;同一家的FPGA,不同型号的芯片,可以也不一样;原语类似最底层的描述方法。 使用原语的好处,可以直接例化使用,不用定制IP;即可通过复制原语的语句,然后例化IP,就可使用。
常见原语:
- IBUF 和 IBUFDS(IO)
IBUF 是输入缓存,一般vivado会自动给输入信号加上,IBUFDS是IBUF的差分形式,支持低压差分信号(如LVCMOS、LVDS等 )。 在 IBUFDS中,一个电平接口用两个独特的电平接口(I和IB)表示。一个可以认为是主信号,另一个可以认为是从信号。主信号和从信号是同一个逻辑信号,但是相位相反。 - IDDR(Input/Output FuncTIons)
设计用来接收DDR数据,避免额外的时序复杂性。 - IBUFG 和 IBUFGDS(IO)
IBUFG 即输入全局缓冲,是与专用全局时钟输入管脚相连接的首级全局缓冲。所有从全局时钟管脚输入的信号必须经过IBUF元,否则在布局布线时会报错。 IBUFGDS是IBUFG的差分形式支持低压差分信号(如LVCMOS、LVDS等 )
System Verilog (还不熟悉暂时写这些)
1. 数据类型
四值变量:(0、1、x、z)四种状态
四值逻辑类型:integer、reg、logic、reg、net-type(如wire、tri);
SV 并不太常用变量类型是wire(assign语句中),还有reg(initial和always语句中)。
logic 用的比较多, 可以被连续赋值语句驱动,可用在assign、initial、always语句中。在SystemVerilog 中,可以在过去verilog 中用 reg 型或是wire型的地方用logic 型来代替。
二值变量:(0、1) 两种状态
二值逻辑类型:byte、shortint、int、longint、bit。
四值变量与二值变量的特性:
四值变量的默认初始值为x,二值变量的默认初始值为0,在initial中可以直接使用~clk变成1,但是如果是logic,必须设置初值为0、或者1。将四值变量赋值给二值变量,x和z状态会转变为0;
符号数分类
有符号类型:byte、shortint、int、longint、integer。
无符号类型:bit、logic、reg、net-type(如wire、tri)。
对于转换方式,可以分为隐式转换和显式转换。显式转换又可以分为静态转换和动态转换
静态转换:unsigned’(signed);注意单引号。
动态转换:$cast(tgt,src)
2. 类class
在SystemVerilog 中,class 也是一种类型(type),你可以把类定义在program、module、package 中,或者在这些块之外的任何地方定义。类可以在程序或者模块中使用。 类可以被声明成一个参数(方向可以是input、output、inout或者ref),此时被拷贝的是这个对象的句柄,而不是这个对象的内容。
3. 结构体
Verilog 中没有结构体,用户常常以相同的字符开始或结尾来命名信号名,以此表示一组相
关的信号。 SystemVerilog 中增加了类似 C 语言中的结构体类型,可以方便的表示一组相关的信号。结构体表示如下:
struct {
int a,b;
opcode_t opcode;
logic[23:0] address;
bit error;
} Instruction_Word;
4. 构造函数
new() 的作用有三点:
1.例化(创建)对象,也就是申请新的内存块来保存对象的变量
2.初始化变量(二值→0;四值→x)
3.返回句柄
class 只有经过了new()函数才真正开辟了内存,否则只是一个空的、没有实际存在。