1、简介
Verilog的语法和C语言非常类似,相对来说还是非常好学的。和C语言一样,Verilog语句也是由一连串的令牌(Token)组成。1个令牌必须由1个或1个以上的字符(character)组成,令牌可以是:
- 注释(Comment)
- 空白符(White space)
- 运算符(Operator)
- 数字(Number)
- 字符串(String)
- 标识符(Identifier)
- 关键字(Keyword)
Verilog是区分大小写的,所以下面两个变量是不相同的:
var_a; //小写
var_A; //大写
Verilog的书写格式很自由,既可以把代码写在同一行,也可以写在多行。不过一般来讲,我都建议一条代码写一行,这样的代码格式美观,阅读起来也没那么累。每一条Verilog语句的末尾都应使用 ; (即分号)作为结束。下面两种写法是等价的:
//换行写法(推荐写法)
reg a;
wire b;
//不换行写法(眼睛看得累写法)
reg a;wire b;
除了转义字符(escaped identifiers)外的空白符和换行符都没有实际意义,在编译阶段会被编译工具忽略。
2、注释(Comment)
子曾经曰过:我平生最讨厌两件事----写代码的时候写注释和看没注释的代码。
写注释是一个很好的开发习惯,虽然有些麻烦,但确实可以帮助其他开发者阅读代码,也可以在自己更改代码的时候提供一些思路上的帮助。因为编译器不会对注释进行编译,所以注释可以看做是给开发者阅读的设计说明----也就是说注释可以是英文,也可以是中文。
Verilog 有 2 种注释方式:
- 用 // 进行单行注释,在 // 后面的单行内容均会被编译器视为注释。。例如:
reg cnt;//定义一个寄存器cnt
- 用 /* 与 */ 进行跨行注释, /* 与 */ 内的内容均会被编译器视为注释(/* 与 */ 可以是多行,也可以是一行)。例如:
reg cnt;/* 定义一个寄存器cnt */。
reg cnt;
/*
定义一个
寄存器
cnt
*/
该种注释方式不可嵌套(注释一般也没必要嵌套)。
3、空白符(White space)
空白符是指 空格符(spaces), 制表符(tabs), 换行符(newlines), 和换页符(formfeeds)。当空白符用于独立的令牌时,编译器通常会忽略它;但当空格符和制表符在字符串中则不能被忽略,它们会起到作用。
空白符通常被用来对齐以调整代码格式,从而使代码更具可读性。比如下面的代码看起来有点拥挤,不够美观的同时也不方便阅读:
reg[3:0]cnt;//定义一个4位宽的寄存器cnt
wire[15:0]blk;//定义一个15位宽的线网blk
由于空白符是对编译不起作用的,所以上面的代码可以通过空白符来等价地修改为:
reg [3 :0] cnt; //定义一个4位宽的寄存器cnt
wire [15:0] blk; //定义一个15位宽的线网blk
这样的代码看起来就舒服多了。
4、运算符(Operator)
Verilog中有3种类型的运算符:
- 一元运算符(unary):一元运算符必须放在运算数的左边。例如:
x = ~y; // ~是一元运算符,y是运算数,该句表示对y值取反后赋给x
- 二元运算符(binary):二元运算符必须放在两个运算数的中间。例如:
z = x & y; // &是二元运算符,x、y是运算数,该句表示将x、y值相与后赋给z
- 三元运算符(ternary) 或者叫 条件运算符(conditional):条件运算符由两个独立的运算符来分开三个运算数。例如:
x = (y > 5) ? 1 : 0; // ?和:是条件运算符,表达式(y > 5)、1和0是运算数。该句表示当 y>5 成立时将1赋给x;否则将0赋给x。
5、数字(Number)
Verilog中的数字有两种形式:整数(integer constants)和实数(real constants)。
整数
整数除了可以使用我们最熟悉,同时也是默认使用的10进制(decimal)外,还可以使用2进制(binary)、8进制(octal)和16进制(hexadecimal)。例如:
16 //10进制的16
0x10 //16进制的16
10000 //2进制的16
20 //8进制的16
整数的写法可以指明位宽,也可以不指明位宽。
指明位宽
这种写法的一般格式为:
[size]'[base_format][number]
- size用来指定数字的位宽,这个数只能用10进制表示,即10一定表示位宽为10进制的10,而不是位宽为16进制的16
- 撇号(apostrophe character) ' 介于size和base_format之间
- base_format用来指定数字的进制,可以是2进制('b or 'B)、8进制 ('o or 'O)、10进制 ('d or 'D)、和16进制('h or 'H)
- number即为数字本身,2进制的0和1、8进制的0~7、10进制的0~9和16进制的0~9/a/b/c/d/e/f(不区分大小写)
例如:
4'b1010 //位宽为4的2进制数字1010
5'd3 //位宽为5的10进制数字3
不指明位宽
不指名位宽直接写数字时,默认为10进制,位宽取决于所用的编译器(位宽一般为32),写法有2种:
'd10 //位宽根据编译器而定,一般为32,即32'd10
10 //位宽根据编译器而定,一般为32,即32'd10
4af //非法写法,f为16进制数字,但是这里没有表明进制
负数
在表示位宽的数字前面加一个负号 - 来表示负数,例如:
-6'd3 //位宽为6的数字-3
如果把负号 - 放在进制和数字之间则是非法的,例如:
6'd-3 //这是非法写法。一定要注意,因为与日常写法很相似。
可以在base_format前添加一个s(或S)来指明这是一个有符号数,当然也可以不添加。有符号数的最高位将被视为符号位----0表示正数,1表示负数。例如:
4'b1111 //无符号数,等于10进制的15
4'sb1111 //有符号数,最高位1表示负数,剩余三位111转化等于10进制的7
负数应以二进制的补码形式表示,例如:
-8'd6 // 这定义了6的补码
4'shf // 等价于有符号数4'b1111,最高位1表示为负数,剩余三位111的补码(取反+1)是1,所以等价于 -4'h1
-4'sd15 // 等价于有符号数-4'b1111,最高位1表示为负数,剩余三位111的补码(取反+1)是1,所以等价于 -(-4'd1),即4'd1
下划线
当数字过多的时候,可以用下划线(underscore character) _ 来拆分数字,提高阅读效率,但是下划线不可以放在数字的最开始。下划线无实际意义,仅用于辅助阅读。例如:
16‘b1010111101010111 //数字太多,阅读起来不太方便
16‘b1010_1111_0101_0111 //用下划线拆分数字后,阅读起来方便多了
16‘b_1010_1111_0101_0111 //非法写法,下划线不能放在数字的最前面
实数
实数的表示方法有2种:10进制表示法(decimal notation)和 科学计数法(scientific notation)。
10进制表示法
该方法有小数点,且小数点两边都必须存在至少一位数字。例如:
1.2 //合法写法
0.1 //合法写法
2394.26331 //合法写法
.12 //非法写法,小数点左侧不存在至少一位数字
9. //非法写法,小数点右侧不存在至少一位数字
科学计数法
把一个数表示成 a(1≤|a|)与10的n(n为整数)次幂相乘的形式,其中10的整数次幂用e(或E)+ 数字来表示,例如10的8次幂即为e8或E8。例如:
1.2E2 //大小为120
0.1e-0 //大小为0.1
29E-2 //大小为0.29
.2e-7 //非法写法,小数点左侧不存在至少一位数字
4.E3 //非法写法,小数点右侧不存在至少一位数字
转换
实数到整数的转换采用四舍五入(round)的方法,而非直接截取整数部分。例如:
35.7 >> 36
1.5 >> 2
-1.5 >> -2
6、字符串(String)
字符串是由包含在双引号(double quotes)" " 内的多个字符组成的有限序列。字符串存储在reg变量中,每个字符都需要8 bits的空间。例如:
reg [8*12:1] stringvar; //位宽96 bits
initial begin
stringvar = "Hello world!"; //12个字符,共需要12*8=96 bits
end
字符串不能跨行书写,即字符串中不能包含回车符。下面是跨行的非法写法:
reg [8*12:1] stringvar;
initial begin
stringvar = "Hello
world!"; //非法写法
end
如果字符串的内容大于存储空间,则最高测(最左侧)的多余内容会被截掉;如果字符串的内容小于存储空间,则最高测(最左侧)的多余空间会填充0。例如:
module tb_test;
reg [3*8 : 1] str1;
reg [7*8 : 1] str2;
initial begin
str1 = "Hello"; //5字符放入3个byte,左侧多余的 "He" 会被截掉
$display("%s is stored as %h", str1,str1); //打印str1
str2 = "Hello"; //5字符放入7个byte,左侧多余空间会用0填充
$display("%s is stored as %h", str2,str2); //打印str2
end
endmodule
字符串是以字符的ASCII码形式存储的(H e l l o的ASCII码分别为 0x48 0x65 0x6c 0x6c 0x6f),所以上面代码的打印内容是这样的:
llo is stored as 6c6c6f //多余的左侧两位 He 被截断
Hello is stored as 000048656c6c6f //多出的左侧两位空间被填充 0
还有一类字符被称为转义字符----以反斜杠 \ 为开头,后面跟一个或几个字符,其意思是将反斜杠 \ 后面的字符转变成为另外的意义。例如 \n 不代表字母n而作为换行字符。类似的转义字符还有:
转义字符 | 说明 |
\n | 换行字符 |
\t | 制表字符 |
\\ | \ |
\" | " |
7、标识符(Identifier)
标识符是指用来标识某个实体如变量、信号、模块等(object)的独特符号,它可以是任意一组字母、数字、$ (美元符号)和 _(下划线)符号的组合,但标识符的第1个字符必须是字母或者下划线,且不能以数字或者美元符号开始。此外,标识符是区分大小写的。
shiftreg_a //合法标识符
busa_index //合法标识符
error_condition //合法标识符
merge_ab //合法标识符
_bus3 //合法标识符
n$657 //合法标识符
23 //非法标识符,不能以数字开始
2var //非法标识符,不能以数字开始
$var //非法标识符,不能以美元符号开始
标识符的长度限制取决于具体使用的编译器,但是这个限制长度至少要大于1024个字符。如果标识符超出长度,那么编译器需要发出错误警告。
为了能够使用标识符集合以外的字符或标号,除了普通的标识符外, Verilog还定义了转义标识符(Escaped identifiers)。采用转义标识符可以在一条标识符中包含任何可打印的字符。转义标识符以反斜杠 \ 开头,以空白字符(空格字符、制表字符或换行字符)结尾。例如:
\busa+index
\-clock
\***error-condition***
\net1/\net2
\{a,b}
\a*(b+c)
开头的反斜杠和结尾的空白字符都不会被视为转义标识符的一部分,例如 \cpu3 就等价于 cpu3 。
8、关键字(Keyword)
关键字是 Verilog 中预留的用于定义语言结构的特殊标识符。关键字不能转义,且关键字全部为小写。下图是Verilog-2005中的所有关键字:
9、其他
除了上述的7类语法外,Verilog中还有一些特殊的语法。
系统任务和函数
系统任务和函数(System tasks and functions)是一类预定义好的系统子函数,用于实现特定的功能。它们不是Verilog的功能语法,一般只能用于仿真。所有的系统任务都以美元符号$作为开始,例如:
$display ("display a message"); //打印系统任务,用于仿真时打印信息
$finish; //结束仿真系统任务,用于结束仿真
编译指令
以反引号 ` 开始的编译指令(Compiler directives )为Verilog代码的编写、编译、调试等提供了极大的便利。例如:
`define wordsize 8 //定义wordsize等价于8
属性
属性(Attributes)通俗来讲,就是Verilog开放的某些特定操作的接口。例如:
(* fsm_state *) reg [7:0] state1; //指定这是状态机的状态编码
如果不添加属性(* fsm_state *),那么后面定义的reg state1有可能不会被综合工具指定为状态机的状态编码。
- 📣您有任何问题,都可以在评论区和我交流📃!
- 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵,博客主页:wuzhikai.blog.csdn.net
- 📣您的支持是我持续创作的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!