打印
[Verilog HDL]

语法与注释

[复制链接]
54|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gaochy1126|  楼主 | 2025-4-30 23:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

1.1 基本语法说明 1.1.1 语法与注释 Verilog语言与C语言类似,是区分大小写的语言,代码可多行书写也可以单行书写,一条语句结束后要以英文分号;结尾。 注释语言分为单行注释和多行注释,单行注释使用双斜线//后跟代码,多行注释使用/ /,中间写入注释。 示例:

wire A, B, C; // 这是一行代码 / 这是一行代码 / assign Count = A & B; 1.1.2 数值系统: 数值 说明 0 低电平,逻辑上为False 1 高电平,逻辑上为True x 未知电平,有可能为低电平,有可能为高电平,也有可能都不是,也可以写作X z 高阻态,简单而言是信号没有驱动,没有输出,也可以写作Z Verilog中基数格式:<bits>'<radix><value> 其中<bits>为二进制位宽,即位的个数,如果空缺不填则会由编译器根据后面的数值自动分配。

需要声明的一点:若二进制位宽小于实际位宽,则会进行截断, 比如:3'hfff实际会截断为3'b111。

<radix>为进制,共有4种进制:二进制(b,B),八进制(o,O),十进制(d,D),十六进制(h,H)。

在十六进制中,a~f这6个字母不区分大小写。

<value>为实际数值,在其中为了可读性可以加上下划线_,比如十进制数中三位一分或其他进制中四位一分等。

1.1.3 标识符与变量 标识符 说明 wire 线网型数据。表示以 assign 语句内赋值的组合逻辑信号,默认初始值为 z (高阻态),且 wire 是默认数据类型 reg always 语句内部进行赋值操作的信号(凡是 always 语句内部赋值的信号都应定义为 reg 类型),对应一种存储单元,默认初始值为 x (未知电平) 需要说明的一点是, reg 类型变量不一定对应一个寄存器,仅声明一个 always 语句中进行赋值的信号,受 always 描述的影响。

声明变量格式:wire/reg [width - 1 : 0] (<var_name>,...)其中width为位宽,若省略width,则默认为1,即[0 : 0];将width=1的变量称为标量(Scalar),width>1的变量称为向量(Vector)。

表达式中,可任意选择向量的一位或相邻几位,分别称为位选择(bit-select)和域选择(part-select)。

此外,Verilog中也有数组这一概念:wire/reg [width - 1 : 0] <var_name> [0 : width - 1]; 附:integer类型为有符号reg类型,一般用于描述循环变量或计算,一般32位。

在Verilog语言中,类似于i++、i--的运算是不允许的。

1.1.4 运算符 算数运算符:+、-、*、/、%。其中前两个也可以作为单目操作符,表示操作数的正负性,优先级最高。

关系运算符:>、<、>=、<=、==、!=、===、!==。前六位在别的编程语言中也很常见,其中最后两个表示“全等”和“非全等”。关系运算符的结果为0(False)或1(True),是1bit的值。对前六个运算符,如果某一操作数中有一位x或z,则运算结果全为x。而对于最后两个运算符,则对是x或z的位也进行比较,只有操作数完全一致,才为相应的1(对于===),或者0(对于!==)。

逻辑运算符:&&(逻辑与)、||(逻辑或)、!(逻辑非)。其运算结果也是0(False)或1(True),是1bit的值。与刚才说的六种关系运算符相同,如果任意一位为x或z,则运算结果为x。

按位运算符:~、&、|、^、~^或^~,分别表示按位非、按位与、按位或、按位异或和按位同或(后两个按位运算符都是按位同或)。对 2 个操作数的每 bit 数据进行按位操作。如果 2 个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。按位非是单目运算符,它对操作数的每 bit 数据进行取反操作。

归约运算符:&、~&、|、~|、^、~^。是单目运算符,对多位操作数进行逐位操作,最终产生一个1bit的结果。其中&可用来判断是否为全1;~|可用来判断是否为全0;^和~^可用来做奇偶校验:对于^,若1的个数为奇数,结果为1,对于~^,若1的个数为偶数,则结果为1。需要说明的一点是:带~的归约运算符,先归约后面的运算符,后对结果取反。

条件运算符:条件表达式 ? 真分支 : 假分支,与其他编程语言应用类似。

移位运算符:<<、>>、<<<、>>>,分别为:逻辑左/右移和算数左/右移。对于算数左移和逻辑左移:右边低位补0;对于逻辑右移:左边高位补0;对于算数右移:左边高位补充符号位。比如说:

A = 8'b 1100_1101; A >> 1; // 8'b 0110_0110 A >>> 2; // 8'b 1111_0011 A << 2; // 8'b 0011_0100 拼接运算符:{表达式1, 表达式2, ..., 表达式N}。实现将多个操作数拼接成新的操作数,既可以是常数,也可以是变量,但位宽必须确定且不可变。而且还有另一种用法,对于某个表达式要重复拼接许多次:{重复次数{表达式}},在将重复操作嵌入拼接操作时,需要用大括号把重复操作整体括起来,就如同我刚才写的那样。

1.2 Verilog语句 1.2.1 连续赋值 assign(并行,同时赋值) 对wire型变量进行赋值。语法为:

assign LHS = RHS; 其中LHS必须为wire型,不能是reg型;RHS类型无要求。下面举个实例说明:

wire Count, A, B; assign Count = A & B; 或者写作:

wire A, B; wire Count = A & B; 只要RHS表达式的操作数有事件发生(值的变化)时,RHS就会立刻重新计算,同时复制给LHS。

1.2.2 过程赋值 always、initial 对reg型变量进行赋值。这两种关键字不可嵌套使用,彼此间并行执行(执行顺序与其在模块中的前后顺序无关)。若语句内包含多个语句,则需要begin和end组成一个块语句。每个initial语句或always语句都会产生一个独立的控制流,执行时间都是从0时刻开始。

initial语句仅在0时刻开始执行一次内部的语句(单次);always语句从0时刻开始执行,执行完最后一条语句后,便再次执行语句块中第一条语句,如此循环反复(循环)。

always语句常用格式如下:

always @ (敏感变量列表) 过程语句 其中敏感变量列表:

触发always语句执行的条件; 仅在列表中的变量发生变化时,才执行内部的过程语句; 若敏感变量过多,Verilog 2001允许使用*表示缺省,根据always块内部内容自动识别敏感变量; 支持posedge:上升沿和negedge:下降沿,如always @(posedge clk)begin ... end; 不允许混合信号(边沿敏感信号与普通变量混合)。 1.2.3 阻塞赋值和非阻塞赋值 阻塞赋值:顺序执行(同C语言),使用=。

非阻塞赋值:并行同时执行,使用<=。

注意:在实际使用中,不推荐混合使用阻塞赋值与非阻塞赋值,否则时序不容易控制。

在设计电路时,always时序逻辑块使用非阻塞赋值,组合逻辑块使用阻塞赋值;在仿真电路时,initial块使用阻塞赋值。

举例:clk上升沿交换a、b的值:

reg temp; always @(posedge clk) begin temp = a; a = b; b = temp; end 这是我们在学习C语言等时常用的写法,但是在Verilog中,可以使用非阻塞赋值使代码更加简单:

always @(posedge clk) begin a <= b; b <= a; end 如果是对同一变量的多次非阻塞赋值,只有最后一次赋值是有效的,比如:

a <= a + 1; a <= a + 2; 这里实际上只有第二个语句生效。

1.2.4 条件语句 if else / case endcase 一般在always语句块中使用,不单独在模块中直接使用。

其语句格式为:

if (条件) 过程语句 [else 过程语句] 这里else 过程语句可以省略,但是不推荐,因为可能产生其他的问题。

case(case 表达式) case 条目表达式 1:过程语句 case 条目表达式 2:过程语句 ... default:过程语句 endcase 这里default语句同样可忽略,但是至多有一条default语句。

1.3 模块 1.3.1 模块声明 以关键字module开始,endmodule结束。从module开始到第一个分号之间的部分是模块声明,包括模块名称与输入输出端口列表。

模块内部由内部变量声明、数据流赋值语句(assign),过程赋值语句(always)以及底层模块例化组成,它们顺序不固定。

端口是模块与外界交互的接口,外界只能看到模块的端口。共有三种端口类型:输入端口(input)、输出端口(output)、双向端口(inout)。其中输入端口和双向端口不能声明为reg类型,输出端口可以声明为wire类型或reg类型。

在数据类型声明中,wire关键字可省略,reg关键字不可省略。

综上,模块基本结构可总结为:

module 模块名( 输入端口定义, // 端口定义之间用英文逗号,分开,输入端口只能是wire型 输出端口定义 // 输出端口可以是wire或reg型 ); 内部信号定义语句 // 可以按需设置wire和reg型变量 模块实例化语句 // 其他模块接入电路 assign 语句 always 语句 endmodule 1.3.2 模块例化 格式一般为:

<模块名> <例化标识符> (端口连接); 类似于其他编程语言中的函数实例化。

模块例化有2种方式:

基于位置的端口关联:端口需与声明时顺序一致,如:

F f(n1, n2, cin, sum, cout); 基于名字的端口关联:顺序可以不一致,如:

F f(.a(n1), .b(n2), .cin(cin), .s(sum), .cout(cout)); 一般input端口例化时不能删除,output端口可以删除。而对于这两种方式,方式也不一样(这里假设cout为输出端口):

基于位置的端口关联:

F f(n1, n2, cin,, cout); // 省略例化时,逗号不能省 基于名字的端口关联:

F f(.a(n1), .b(n2), .cin(cin), .s(sum), .cout()); // 省略时,括号不能省 下面再说一下端口连接规则:

端口 连接规则 input 输入端口 模块例化时,可wire或reg;模块声明时,必须wire output 输出端口 模块例化时,必须wire;模块声明时,可wire或reg inout 双向端口 模块例化和声明时,都必须wire 例化时,output悬空时,可省略;input悬空时,逻辑值为z(高阻态),而且不建议将input端口悬空,且例化时不能将悬空的input端口删除。不能将模块例化放入过程赋值语句(always)中。

1.3.3 参数传递 模块声明时使用parameter关键字指定参数。如:

module M

(parameter a = 8)

(...) // 模块声明 ... // 其他代码 例化时,M#(4) m(...);;或module ();,先不定义参数,例化后,defparam m.a = 8;。

注意:

只能将参数值传递给比顶层模块低一级的底层模块中; localparam,局部参数,不能通过顶层模块传参。

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:这个社会混好的两种人:一是有权有势,二是没脸没皮的。

1082

主题

11377

帖子

26

粉丝