一.Verilog语法基础
1. 逻辑值
0:逻辑低电平,条件为假
1:逻辑高电平,条件为真
z:高阻态,无驱动
x:未知逻辑电平
2. 归约运算符,按位运算符
以&为例,当&作为一元运算符时表示归约与,&m是将m中所有比特位相与,最后的结果为1bit
例如:
&4'b1111=1&1&1&1=1'b1
&4b'1101=1&1&0&1=1'b0
当&作为二元运算符时表示按位与,m&n是将m的每个比特位与n的相应比特位相与,在运算时要保证m和n的比特位数相等,最后的结果与m和n的比特位数相同
例如:
4b'1010&4b'0101=4b'0000
4b'1101&4b'1111=4b'1101
其它例如'~&' , '^' , '~^' , '|', '~|' 在分别作为一元和二元运算符时也有和'&'一样的性质
3. 逻辑运算符和关系运算符
具有'&&'形式的操作符表示逻辑与
例如:
a=a'ha, b=4'd0, c=a&&b 运算结果是c=0
关系运算符(逻辑运算符)的使用方法和C语言完全类似
4. 移位运算符
移位运算符时二元运算符,左移符号为'<<',右移符号为'>>',结果是将运算符左边的操作数左移或右移指定的位数,用0来填补空位。
b <= a<<1; #作用是将a的每一位都向左移动1位,结果赋值给b
b <= a>>2; #作用是将a的每一位都向右移动1位,结果赋值给b
需要注意的是:
1)移位运算造成的空位自动用0来填充,因此任意一个二进制数只要一直移位其值最终都会变成0。
例如: a=4'b1000, a>>3的结果为 4'b0001;a>>4的结果为4'b0000
2)移位运算符在使用时可代替乘法和除法,左移一位看作是乘以2,右移移位可以看作是除以2,但是仍然要注意位宽的拓展。
5.位拼接运算符
位拼接运算符由一对花括号加逗号组成,形式为’{ , }', 拼接的不同数据之间用','隔开。
例如:
将8bit的a、3bit的b、5bit的c按顺序拼接成一个16位的d,表示方法为:d={a,b,c};
6. 条件运算符
一般格式:(逻辑判断语句)? 语句1 : 语句2
执行过程是:当[逻辑判断语句]为真,则[语句1]作为条件表达式的值,否则将[语句2]作为表达式的值。
例如: a=6, b=7, c=(a>b)?a:b 语句执行后c=7
7. 运算符优先级
归约运算符 > 算术运算符 > 移位运算符 > 关系运算符 > ’==‘ 和'!=' > 按位运算符 > '&&' 和 '||' > 条件运算符
归纳起来就是一元运算符 > 二元运算符 > 三元运算符。
运算符优先级往往不需要准确记忆,通过括号增加优先级是更为简单且准确的方法。
将各种常用运算操作符总结如下:
8. case分支语句
格式:
case(<控制表达式>)
<分支语句1> : 语句块1;
<分支语句2> : 语句块2;
......
<分支语句n> : 语句块n;
default : 语句块n+1;
endcase #这行不要漏写
注意:
1)在执行了某一分支项内的语句后,跳出case语句结构,终止case语句执行。
2)case语句中的各个<分支语句>中表达式取值必须是互不相同的。
二. 系统函数
定义:Verilog语言中预先定义了一些任务和函数,用于完成一些特殊的功能,它们被称为系统任务和系统函数。这些函数大多数都是只能在Testbench仿真中使用的,使得用户可以更方便地进行验证。
1.timescale函数
格式: 'timescale 1ns/1ns #时间尺度预编辑指令 参数对应 时间单位/时间精度
其中时间单位和时间精度由值为1、10、100以及单位s, ms, us, ns, ps, fs 组成。
时间单位:定义仿真过程所有与时间相关量的单位。
仿真中使用"#数字"格式表示延时相应时间单位的时间,例如在前面设定的时间单位和精度前提下"#10"表示延时10个时间单位,也就是10ns
时间精度:决定时间相关量的精度以及仿真显示的最小刻度。
例如:
`timescale 1ns/10ps 表示的精度为0.01ns, #10.11表示延迟10110ps
很重要的一点:timescale前面的符号是键盘左上角的,千万不要写成单引号!
注意:在使用timescale函数时要保证时间单位大于时间精度
'timescale 100ps/1ns 这样的写法就是错误的
2.display函数
功能:用来输出、打印信息
格式:$display("%b+%b=%d",a,b,c); #"%b+%b=%d"用于输出格式控制,未指定时默认为十进制
%h或%H 以十六进制格式输出
%d或%D 以十进制格式输出
%o或%O 以八进制格式输出
%b或%B 以二进制格式输出
注意:每次输出结束后自动换行
3.write函数
功能:用于输出、打印信息
格式:$write("%b+%b=%d\n",a,b,c); #其中控制参数的用法同display函数
注意: $write函数每次输出结束后不会自动换行,换行需要搭配"\n"实现。
4.strobe函数
功能和用法与display和write相同,区别在于$strobe函数不论被置于程序中什么位置都是固定放在最后执行,其中变量参数也都是程序执行到最后的值。并且会自动换行。
5.monitor函数
功能和用法与display和write相同,区别在于每改变$monitor中参数的值时就会打印一次。自动换行。
6.stop和finish函数
$stop用于暂停仿真,$finish用于结束仿真
7. time和random函数
功能:$time为时间函数,返回64位当前仿真时间;$random用于产生随机函数,返回随机数
8.readmemb/readmemh函数
功能:$readmemb和$readmemh分别用于读取二进制和十六进制文件
格式: $readmemb("<数据文件名>",<存储器名>);
例如:$readmemb("test.txt",a);
三. 关键字
1. module(模块开始)---endmodule(模块结束)
格式: module exmaple
2. input(输入)/input(输出)
input wire sys_cik, #这里注意每一个参数定义要加‘,’而定义语句结尾没有
input wire sys_rst_n,
inout wire sda, #inout定义的信号可同时用于输入或输出
output wire po_flag
3. 变量类型
1)线网型变量: wire #在系统中会被直接认为具有一条物理连线
2)寄存器型变量: reg #对某一时间点的状态进行保存,在系统中会被认为是寄存器
格式: reg [7:0] a [20:0]; #表示定义一个名为a,位宽为8bit,深度为21的寄存器变量
两种变量类型的使用场景:
1. 当使用always语句给变量赋值时,要用reg型
2. 当使用assign语句给变量赋值时,用wire型。assign可以视为在电路中添加一条连线。
4.参数
1)parameter
格式: parameter CNT_MAX = 100;
特性:实例化时参数可修改
2)localparam
格式:localparam CNT_MAX = 100;
特性:只能在模块内部使用,不能实例化
5.常量(基数表示法)
格式:[换算为二进制后位宽的总长度]['][数值进制符号][与数值进制符号相对应的应的数值]
其中:[数值进制符号]:[h]表示十六进制,[o]表示八进制,[b]表示二进制
例如: 8'd171 : 位宽是8bit,十进制的171
8'hab : 8bit的十六进制数ab
8'o253 : 8bit的八进制数253
8‘b1010_1011 : 8bit的二进制数1010_1011 #二进制数可以用下划线分割增强可读性
[换算为二进制后位宽的总长度]这一项可有可无,verilog会自动为常量匹配合适的位宽。 当总位宽大于实际位宽,则自动在左边补0,总位宽小于实际位宽则自动截断左边超出的位数
例如:'d7与8'd7表示相同数值,8'd7换算为二进制是8'b0000_0111,前五位补0
2'd7换算为二进制是2'b11,超过2位宽的部分被截断 #这里的截断是从右往左进行的
注意:如果直接写参数,例如100,表示位宽为32bit的十进制数100
6.赋值
1)阻塞赋值(“=”)
内部语句同时并行执行
2)非阻塞赋值(“<=")
程序从上到下顺序执行赋值语句
注意一些问题:
1.阻塞赋值电路结构与时钟触发沿没有关系,只与输入电平的变化有关。阻塞赋值语句是顺序执行的,先计算赋值符号右边的语句并更新赋值号左边的语句,此时不允许有来自其他任何语句的干扰,直到现行的赋值完成,才允许下一条赋值语句的执行。
2.非阻塞赋值的电路结构与时钟触发沿有关系,只有触发沿时刻才进行相应的非阻塞赋值。非阻塞赋值语句是并行执行的,先计算所有非阻塞赋值号右边的语句再同时进行向左赋值,这期间允许其他语句进行操作。
3.非阻塞赋值只能用于对寄存器变量进行赋值,因此只能能用于‘inital’和‘always’块中(敏感列表要采用电平触发方式/敏感列表就是always@(这里)的语句),不允许用于连续赋值‘assign’中。
4.时序逻辑电路中推荐使用非阻塞赋值,方便实现时钟边沿触发。
5.一个语句块中不要同时用阻塞和非阻塞赋值。同时推荐一个always语句块只对一个变量进行赋值。
6.使用锁存器(latch)要采用非阻塞赋值。
实例:DDS信号发生器设计
图中:频率字输入F_WORD和相位字输入P_WORD共同调节输出信号的频率,有如下关系:
k=2^N*Fout/Fclk (k为频率字输入F_WORD)
Fout=k*Fclk/2^N
P=θ*(2*pi/2^M) (P位相位字输入P_WORD)
其中累加寄存器的作用是将频率字输入与相位累加器的输入隔离开,防止频率字输入的不规则性相位累加器造成影响,相位字输入的累加寄存器则同理。
注意到累加寄存器输入位宽N和相位调制器输入位宽M往往并不相同,因此这里需要考虑到位宽转换的问题,这里简单地取N的高M位作为M的输入,这样做导致的问题是计数周期会变成原来的2^(N-M)倍,也就是频率会变成原来的1/2^(N+M)倍,相当于降低了输出频率。
而频率字累加寄存器和相位字累加寄存器的值需要在相位调制器处求和并作为波形数据表寻址来在已经储存有波形幅值数据的波形数据表中取出需要的波形成分,完成DDS信号发生器的制作。
四. 一些Verilog开发中需要注意的问题汇总
1. 使用always@(*)进行组合逻辑设计时要注意敏感变量的情况,此时Verilog默认对于always语句块内所有变量敏感,也就是说当在同一个always语句块内对同一变量多次赋值时将导致always块被循环触发,因此会陷入死循环。
错误形式:在仿真时会出现collapse,如modelsim报错如下:Error:(vsim-3601) Iteration limit reached at time 55445 ns.
解决方法:在每个always@(*)语句块内只对同一变量进行一次赋值,如果单次赋值无法满足需求则需额外增加temp变量。
2. 在Verilog语法中,如果没有使用变量承接运算的中间变量,则中间变量的位宽默认为1,例如:
上面语句的预期功能是判断补码运算的溢出问题,在实际运行中发现参与异或运算的两个数据移位后结果恒为0,其原因时Verilog默认中间变量位宽为1。当为中间变量提前指定位宽后运行正常:
3.关于Modelsim仿真的一点提示:
如果Modelsim报错error loading design 则最常用的解决办法是返回检查tb文件中的文件名是否与仿真项目名一致。文章来源:https://www.toymoban.com/news/detail-775256.html
文章来源地址https://www.toymoban.com/news/detail-775256.html
到了这里,关于Verilog HDL笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!