Verilog HDL入门(1)
Verilog 基础语言1
编译器指令、数据类型与操作符
编译器指令
`define、`undef
指令与 C 类似,可以在多个文件中使用,但是 `ifdef、`ifndef、`else、`elsif、`endif 属于条件编译指令。
注意:编译器指令以反引号 ` 开头,不是以 $ 开头($ 开头的是系统任务/系统函数),两者不要混淆。
`define 使用
与 C 语言使用方式有所不同
1 |
|
`default_nettype
该指令用于为隐式线网指定线网类型,就是为那些没有被声明类型的线网定义类型
1 |
注意:`default_nettype 会改变其后所有未声明线网的默认类型,综合时通常设为 `default_nettype none,强制显式声明每一个线网,避免拼写错误导致隐式生成 1 位线网。
`include
类似于 C 语言,在代码中包含任何其他文件的内容。包含的文件,可以使用相对路径和绝对路径
1 |
编译时,这一行由文件 "../../primitives.v" 的内容替换
`resetall
将所有的编译指令重新设置为缺省值
`timescale
这个是用在测试文件中的,该值是设置时间单位和时间精度
1 |
这里时间延时是 1ns,精度是 100ps,测试文件使用方法 #100 即延时 100 纳秒
注意:`timescale 只能出现在测试文件中,综合的设计文件(RTL)不要写,否则可能被综合工具忽略或告警。
`unconnected_drive 与 `nounconnected_drive
1 |
|
`celldefine 与 `endcelldefine(未使用过)
编译器指令用于把模块标记为单元(Cell)模块。它们一般包含模块的定义
`line
注意:`line 指令用于改变后续代码的行号(一般由预处理工具自动生成,用于调试定位),手写代码几乎不会用到。
整型数
两种格式:简单十进制、基数格式
基数格式:指定位数 指定进制 数值
例:5'D25
注意:无位宽声明的整数默认为 32 位有符号数,赋值给窄位宽信号时会截断高位,这是常见的综合不一致来源。
实数(浮点数)会采用四舍五入的方法隐式地转换成整数
字符串:字符串不能分多行书写,其他和 C 语言相同
1 | "INTER REACHED->HERE" |
存储字符串
1 | reg [1:8*4] message = "abcd"; |
数据类型
数据类型:线网类型、变量类型
- 线网类型:实际的物理连线,线网类型没有被驱动,那么就处于高阻态
- 变量类型:与 C 语言类似
线网数据类型
1 | wire trior trireg |
注释:有 tri 的都是三态,即 0、1、x
声明线网的简单语法
1 | net_kind [signed] [[msb:lsb]] net1, net2... netN; |
net_kind:是一种线网数据类型signed:是一个关键字,声明具有符号值的线网;在声明线网类型时,缺省该值,线网是没有符号值的msb、lsb:是用于指定线网位宽,当没有指定时,位宽为 1
1 | wire signed [7:0] pwdata; // 线网类型 8根有符号 其数的形式为2的补码 |
wire 和 tri(语法语义完全相同)
用于连接电路元件;tri 类型线网可用于描述多个信号源驱动的线网;
若 wire 类型(或者 tri 类型)的线网由多个源驱动,则线网的有效值如下表
| wire (tri) | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | x | x | 0 |
| 1 | x | 1 | x | 1 |
| x | x | x | x | x |
| z | 0 | z | x | z |
例如:
1 | wire [2:0] mode_endable,clk_enable,clk_mode; // 假设 clk_enable 的值 01x;clk_mode 的值 11z |
则 mode_endable 的有效值是 x1x
wor 和 trior 线网(与或逻辑相似)
线或(wor)和三态线或(trior)在语法和功能上一致
若多个驱动源驱动该线网,则线网的有效值由下表决定
| wor (trior) | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | 0 |
| 1 | 1 | 1 | 1 | 1 |
| x | x | 1 | x | x |
| z | 0 | 1 | x | z |
wand 和 triand 线网(与与逻辑相似)
线与(wand)和三态线与(triand)在语法和功能上一致
若多个驱动源驱动该线网,则线网的有效值由下表决定
| wand (triand) | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | x | 1 |
| x | 0 | x | x | x |
| z | 0 | 1 | x | z |
trireg 线网(建模只能用于测试平台)
该类线网类型能储存数值(类似于变量)(可用于电容节点的建模(没理解))
当三态变量的驱动源都属于 z 时,三态变量类型的线网保存该线网上的最后一个值;三态变量线网的缺省初始化为 x(未知)
tri0 和 tri1 线网
可以用于线逻辑线网的建模,可以多个源驱动线网,tri0(tri1)特征无驱动则其值为 0(1)
若多个驱动源驱动该线网,tri0 或 tri1 类型线网的有效值
| tri0 (tri1) | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | x | x | 0 |
| 1 | x | 1 | x | 1 |
| x | x | x | x | x |
| z | 0 | 1 | x | 0(1) |
supply0 和 supply1 线网supply0 线网用于对地建模,低电平;supply1 线网用于对电源线网建模,高电平
未声明的线网
向量线网和标量线网
定义向量线网时可选用关键词 scalared(标量)或 vectored(向量)vectored 定义线网,不允许选择该向量的某位或部分位赋值,必须整体赋值;若没有指定关键字,则缺省值是 scalared
变量类型
1 | reg integer time |
reg 变量
1 | reg [signed][msb:lsb] reg1; |
msb:lsb 指定了范围,且均是常数表达式,范围是可选的;若没有定义范围,则缺省值是 1 位的 reg 变量reg 变量的值通常被解释为无符号数;除非用关键字 signed,则是有符号和 C 语言类似
未初始化的 reg 变量的缺省值为 x
存储器(memories)
由 reg 变量组成的数组,如下形式声明
1 | reg [msb:lsb] memory1[upper1:lower1]; |
举例
1 | reg[0:3]ebi_mem[0:63]; // ebi_mem 由64个4位reg变量组成的数组 |
将一个存储器内容复制到另一个存储器,可用循环语句每次复制一条。举例:
1 | parameter WORD_LENTH = 8, NUM_WORDS = 64; |
整数型(integer)变量(建模使用,只能用在测试文件中)
可以作普通变量使用,一个整型数至少 32 位,可以存储有符号
时间变量(time)
用于存储时间和处理时间值;
1 | time time_idn[msb:lsb]; |
若定义位宽,为指定位宽;未定义位宽,则每个标识存储至少 64 位。
时间变量只能存储无符号值。[msb:lsb] 是指数组
实型(real)和实型时间(realtime)变量
这两个定义变量完全相同
1 | real swing_margin; |
实型(real)变量缺省值 0,声明中不允许对位宽或字界做任何指定。
若将 x、z 赋予实型变量,则这些值将被当作 0 处理
数组
reg 与 tri 的不同点
参数
参数(parameter)
是常量,通常用于指定延时和变量的位宽,用参数声明语句,只能对参数赋一次值。
参数值可以在编译时被改变,参数值的改变可以使用 defparam(即重新定义参数)语句实现;
参数声明可以选择指定一种类型,有(integer、real、realtime、time),不允许参数指定范围和使用 signed 关键字
parameter 与 `define 不同之处(作用范围不同)
参数是局部的,只在其定义的模块内部起作用;
而宏定义对同时编译的多个文件起作用,即使在某一模块内指定的宏定义,在编译过程中仍旧对多个文件起作用,直到遇到重新定义为止。
建议:`define 严格用于全局变量和全局文本替换;而将参数用于某个模块内部所需要的常数
局部参数(localparam)(模块内部参数)
在模块例化(引用)时不能通过参数传递或重新定义参数值(defparam)语句对局部参数进行修改;不同于 parameter;
局部参数与普通参数声明语句完全相同。
若局部参数是用其他非局部参数定义的,则外部赋值使得参数值发生变化时,局部参数值间接地发生改变,例:
1 | parameter BYTE = 8; // (非局部参数) |
在编译期间,参数 BYTE 发生改变,则局部参数 NIBBLE 也将随之发生改变
表达式
操作数可以是以下类型
| 操作数类型 | 操作数类型 |
|---|---|
| 常数 | 参数 |
| 线网 | 变量 |
| 位选(Bit-select) | 部分位选(Part-select) |
| 存储器和数组元素 | 函数调用 |
常数
12:有符号数5'b01100:无符号数
-44 和 -6’o54 存储形式相同,但是处理方式不同,例如:
1 | reg1 = -44/4 // => -11 作为有符号数处理 |
线网
标量线网(1 位)、向量线网(多位)
1 | assign gpio_prot = -3; // gpio_prot被赋予向量 1011,即十进制数13 |
变量(标量变量、向量变量)
整型变量的值被解释为有符号二进制数;
若 reg 声明语句中有 signed,则变量中的值被解释为有符号数;否则无符号数
位选(Bit-select)
单个位就是位选,多个位就是部分位选。
从一个向量中抽取特定的位,语法
1 | net_or_reg_vextor[bit_select_expr] |
表达式中如何进行位选
1 | mis_state[1] && mis_state[4] // 变量的位选 |
部分位选(Part-select)
在向量中选取相邻的若干位,部分位选语法格式:
1 | net_or_reg_vextor[msb_const_expr:lsb_const_expr] |
msb_const_expr:lsb_const_expr:范围必须是常数表达式
用索引的部分位选的格式
1 | net_or_reg_vextor[base_expr+:const_width_expr] |
base_expr 不必是常数,但宽度必须是常数
位选的范围是由基表达式加上或减去宽度(const_width_expr)所表示的位的个数。+:表示部分位选以基表达式作为起点增加若干位。-:表示部分位选以基表达式作为起点减小若干位
例:
1 | reg [0:15]mark; |
若索引越界或计算时遇到 x 或 z,则值为 x
存储器和数组元素
格式:memory[word_address]
存储器和数组元素部分位选或位选是允许的。
1 | hdlc_ram[20][2] // 第20个元素第2位的值 第2位就是位选 |
按照格式,在数组中,下标在前,位选在后
符号
表达式中所有操作数都是有符号,则结果也是有符号,否则无符号数
1 | 8'd2+8sb0101 // 结果无符号,因为8'd2是一个无符号 |
操作符
操作符(Verilog HDL 中操作符有 9 类)
| 操作符 | 操作符 |
|---|---|
| 算术操作符 | 关系操作符 |
| 相等操作符 | 逻辑操作符 |
| 按位操作符 | 缩减操作符 |
| 移位操作符 | 条件操作符 |
| 拼接和复制操作 |
| 操作符 | 名称 |
|---|---|
| + | 一元加(正 |
| - | 一元减(负 |
| ! | 一元逻辑非 |
| ~ | 一元按位取反 |
| & | 缩减与 |
| ~& | 缩减与非 |
| ^ | 缩减异或 |
| ^~或~^ | 缩减同或 |
| | | 缩减或 |
| ~| | 缩减或非 |
| ** | 指数幂 |
| * | 乘 |
| / | 除 |
| % | 求模 |
| + | 二元加 |
| - | 二元减 |
| <<< | 算术左移 |
| >>> | 算术右移 |
| << | 逻辑左移 |
| >> | 逻辑右移 |
| < | 小于 |
| <= | 小于等于 |
| > | 大于 |
| >= | 大于等于 |
| == | 逻辑相等 |
| != | 逻辑不等 |
| === | 全等 |
| !== | 非全等 |
| & | 按位与 |
| ^ | 按位异或 |
| ^ | 按位同或 |
| | | 按位或 |
| && | 逻辑与 |
| || | 逻辑或 |
| ?: | 条件判断 |
| {} | 拼接 |
{{}} | 重复 |
条件操作符是从右向左处理,其他都是从左至右处理
a?b:c?d:f == > a?b:(c?d:f)
算术操作符
算术操作符(其余操作与 C 语言类似,除去幂运算)
| 操作符 | 操作符 |
|---|---|
+(一元加和二元加) | -(一元减和二元减) |
*(乘) | /(除) |
%(取模) | **(幂运算) |
整数除法截断任何小数部分,例如:7/4 的运算结果为 1
** 是求幂运算符:a**b 表示 a 的 b 次方,即 a 表示底数,b 表示指数;% 是取模运算符:a % b 按照 a 和 b 中的长度长的补齐。
除法只能取整数;
算术操作符中任意操作数中只要有一位为 x 或 z,则整个结果为 x。例如:4'b10x1+5'b01111 结果为不确定数 5'bxxxxx
算术运算结果的位宽
算术运算结果的位宽由最大操作数的位宽决定。赋值语句下,算术运算结果的位宽也由赋值等号左边目标变量的位宽决定
1 | reg [0B:3] mask_intr,test_ctrl,raw_intr; // 位宽为四,0B的代表,二进制标识,0位 |
较大表达式中,运算中间结果的位宽确定的规则:表达式中的所有中间结果应取最大操作数的位宽(在赋值时,此规则也包括赋值等号左端的目标变量)
1 | wire [4:1]intra_ena,test_reg; |
有符号和无符号数
执行算术和赋值时,哪些操作当无符号操作,哪些操作当有符号操作非常重要
无符号数值存储:线网中、reg(寄存器)变量中、用普通(没有有符号标记 s)的基数格式表示的整型中
有符号数值存储:用 s(有符号)标记的基数格式表示的整型中、十进制形式的整数中、有符号的 reg 变量中、有符号线网中
1 | reg[0:5]burst_data; |
burst_data 是无符号寄存器,右边表达式值为 6'd110100(-12 二进制补码),因此赋值后,burst_data 存储的是十进制 52。
关系运算符(> < >= <=)
若操作数中有一位为 x 或 z,则结果为 x
1 | 52<16'hxFF // 结果为x |
操作数位宽不同,所有操作数都是无符号,则位宽较小的操作数高位补 0 对齐
1 | 'b1000 >= 'b01100 // => 'b01000 >= 'b01100 |
若两个操作数都是有符号数,则用符号位将位数较小的操作数的位数补齐
1 | 4'sb1011 <= 8'sh1A // => 8'sb11111011 <= 8'sb00011010 结果为真 |
若表达式中一个操作数是无符号,则该表达式的其余操作数均被当作无符号数处理
1 | (8'sb9*4'd2) < 4 // => 假(18 < 4) |
相等操作符(结果假为 0,结果真为 1)
| 操作符 | 操作符 |
|---|---|
==(逻辑相等) | !=(逻辑不等) |
===(全等,case equality) | !==(非全等) |
全等比较中,把 x 和 z 当作数值(而不考虑其物理含义)严格地按字符值进行比较,结果不是 1 就是 0,不会出现其他的值。
逻辑比较中,值 x 和 z 具有物理含义(物理含义:有大小区别),结果可能出现不确定值;== 参与比较的两个操作数必须逐位相对,其结果才为 1,如果某些值是不定态 X 或高阻态 Z,那么得到的结果是不定值 X;
例:
1 | sw_data = 'b11x0; // 没有指定位数,默认32位数据比较 |
1 | 'b010x != 'b11x0 |
上式中的两个操作数中都有 x,但比较结果为真,是因为第一位不同
若操作数的位宽不相等,位宽较小的操作数在左侧以 0 补齐,例:2'b10 == 4'b0010 ==> 4'b0010 == 4'b0010 ==> 结果为真
若操作数位宽不同,两个操作数都是有符号数,较小的操作数用符号位扩位补齐,例
1 |
|
1 | reg [4:0] a = 5'b11x01; |
a == b 返回的结果是 X;a === b 的返回结果为 1
(!=)和(!==)的用法类似;
归约运算符
归约运算符:归约与(&)、归约与非(~&)、归约或(|)、归约或非(~|)、归约异或(~^)
注意:归约运算符的操作数只有一个,并只产生一位结果:举例 a=0101,则 &a=0(a 中的所有位进行与操作);|a=1(a 中的所有位进行或操作);
逻辑操作符(逻辑与 或 非)
&&、||、!
对逻辑值 0 或 1 进行操作,逻辑操作产生的结果为 0 或 1
1 | mlock = 'b0 |
对向量操作数而言,非 0 向量被当作 1 处理
1 | rdy_bus = 'b0110; |
并且 !rdy_bus !intr_bus 的结果相同,均为 0
操作数内某一位为 x 或 z,若逻辑操作的结果是未定的,则运算的结果为 x
1 | 1'b1& 1'bx // 结果 x |
按位操作符(5 种)
~(一元非)&(二元与)|(二元或)^(二元异或)~^、^~(二元同或)
这些操作符对输入的操作数进行逐位操作,逐位操作就是对应位进行操作,产生一个向量的结果
与 逐位操作结果
| & | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | x | x |
| x | 0 | x | x | x |
| z | 0 | x | x | x |
或 逐位操作结果
| | | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 1 | 1 | 1 |
| x | x | 1 | x | x |
| z | x | 1 | x | x |
异或 逐位操作结果
| ^ | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 0 | x | x |
| x | x | x | x | x |
| z | x | x | x | x |
同或 逐位操作结果
| ^~ | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 1 | 0 | x | x |
| 1 | 0 | 1 | x | x |
| x | x | x | x | x |
| z | x | x | x | x |
取反逐位操作结果
| ~ | 0 | 1 | x | z |
|---|---|---|---|---|
| 1 | 0 | x | x |
按位运算中,两个操作数的位宽不等,且其中一个操作数是无符号操作数,则位宽较小的操作数高位补 0 对齐;(最终奥义就是不改变值的大小)
若两个操作数为有符号数,则位宽小的操作数用符号位在高位进行补齐,然后再操作
1 | 'b0110^'b10000 // ==> 'b00110^'b10000 => 'b10110 |
缩减操作符(对单个操作数进行操作)
对单一操作数上的所有位进行操作,产生 1 位的操作结果,缩减操作符有 6 种。
&(缩减与)操作数只要有任意一位的值为 0,则结果为 0,操作数中只要有任意一位为 x 或 z,则结果为 x;否则操作结果为 0;~&(缩减与非)缩减与操作结果取反|(缩减或)操作数中只要有任意一位的值为 1,则该操作的结果便为 1,操作数中只要有任意一位为 x 或 z,则结果为 x;否则操作结果为 1;~|(缩减或非)缩减或操作结果取反^(缩减异或)操作数中只要有任意一位为 x 或 z,则结果为 x;若操作数中有偶数个 1,则结果为 0,否则结果为 1~^(缩减同或)缩减异或操作结果取反
1 | a = 'b0110; |
缩减异或操作符可以用来确定向量中是否存在值 x 的位
1 | addr_port = 4'b01x0; // 对addr_port异或结果为x |
对上述功能,可以使用如下的 if 语句进行检查;
1 | if(addr_port === 1'bx) begin |
移位操作符
<<(逻辑左移)>>(逻辑右移)<<<(算术左移)>>>(算术右移)
移位操作符右侧操作数是一个无符号数,若右侧操作数值为 x 或 z,则移位操作结果为 x。
逻辑移位,移动的位置总是填 0;算术移位,左移空出位置填 0,右移位,若操作数为无符号数,空出来位填 0,若操作数为有符号数,则空出来的位总是填写符号位。
1 | reg[7:0] qreg = 8'h17; // 0001 0111 |
移位操作符可以用来完成指数(幂运算);
例如,若计算 2 的 num_bits 幂,
1 | 32'b1<< num_bits // num_bits必须小于32 |
也可以用移位操作符为 2-4 译码器建立 Verilog 模型
1 | wire [3:0]decode_out = 4'b1 << address[1:0]; |
| address | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| decode_out | 4’d0001 | 4’d0010 | 4’d0100 | 4’d1000 |
算术右移是有符号的除 2 运算,分数部分被移位到下一个最低位的整型中。
1 | qreg>>>2 // 运算结果5(23/4) |
算术移位操作符场合,存在一种特殊情况,操作数是有符号,移位却是 0,
这是一个操作符有符号,一个无符号,按无符号数处理
1 | reg signed [3:0]xfer_port; |
条件操作符
1 | cond_expr?expr1:expr2; |
和 C 语言类似,不同处若 cond_expr 为 x 或 z,则操作结果按以下逻辑执行:expr1 与 expr2 按位操作;0 与 0 得到结果 0,1 与 1 得到结果 1,其余情况下的结果为 x;
例:
1 | wire [2:0] student = (marks>18)?grade_a:grade_c; |
计算 (marks>18) 为真则赋值 grade_a,若 marks <= 18 则赋值 grade_c
拼接和复制操作符
拼接(concatenation)是将小表达式中的位拼接起来形成一个由多个位组成的大表达式的操作,语法:{expr1,expr2,...,exprn}
例:
1 | wire [7:0]dbus; |
未指定位宽的常数其位数是未知的,拼接不允许出现未指定位宽的常数。
1 | {dbus,5} // 不允许在拼接操作中出现5,这样未指定位宽的常数 |
拼接是非法的。
1 | abus = {3{4'b1011}}; // 位向量12'b101110111011 就是3个4位数拼接 |
1 | {3{1'b1}} // 结果为111 |
重复操作也可以被参数化,
1 | parameter LENGTH = 8; |
拼接操作计算 2 的幂指数、符号的扩展以及 0 的扩展
1 | parameter POWER_OF = 4, PAD_BY = 5; |
表达式的类型
操作符对操作数进行逐位操作。https://www.runoob.com/w3cnote/verilog-expression.html
缩减操作符