Verilog®黄金参考指南
V1.0 1996 年 8 月
©版权 1996 Doulos 保留所有权力
本刊物的任何部分在得到 DOULOS 的书面许可前都不能通过包括电子 机械 影印 录像或其他任
何形式或任何方式复制 保存在搜索系统或发布 在英国和北爱尔兰印刷
Verilog-XLTM是商标 Verilog®是 Cadence Design System Inc 的注册商标
DOULOS Church Hatch, 22 Market Place, Ringwood.
Hampshire.
BH24 1AW England.
Tel (+44) (0)1425 471223 Fax (+44) (0)1425 471573 Email [email protected] URL http://www.doulos.co.uk
序言... 4
指南的使用... 4
索引... 4
用于定义Verilog 语法的记号说明... 4
Verilog 的简单介绍 ... 5
背景... 5
语言... 5
编译... 6
模块结构... 6
语句... 7
按字母顺序参考的部分... 9
Always... 9
Begin... 10
Case... 12
编码标准... 13
注释... 14
连续赋值... 15
Defparam ... 16
Delay... 17
设计流程... 18
Disable ... 18
错误... 19
事件... 20
表达式... 21
For... 22
Force... 23
Forever... 24
Fork... 25
函数... 26
函数调用... 27
门... 27
IEEE1364... 30
If... 31
Initial... 32
实例化... 33
模块... 35
名字... 37
线网... 38
数字... 41
运算符... 43
参数... 44
PATHPULSE$ ... 46
端口... 47
过程赋值... 48
过程连续赋值... 50
编程语言接口... 51
寄存器... 51
Repeat ... 53
保留字... 53
Specify ... 54
Specparam... 57
语句... 57
强度... 58
字符串... 60
任务... 61
任务使能... 64
定时控制... 65
用户定义的原语... 67
While... 70
编译器伪指令... 71
编译器伪指令... 71
`define ... 72
`ifdef... 74
`timescale... 74
系统任务和函数... 76
系统任务和函数... 76
$display 和$write... 81
$fopen 和$fclose ... 82
$monitor 等... 83
$readmemb 和$readmemh ... 84
$strobe ... 85
$timeformat ... 86
随机建模... 86
定时检查... 89
值改变转储... 91
命令行选项... 93
命令行选项... 93
序言
Verilog 黄金参考指南是 Verilog 硬件描述语言及其语法 语义 合并以及将它应用到硬件设计的一个 简明的快速参考指南
Verilog 黄金参考指南并不是要代替 IEEE 的标准 Verilog 语言参考手册 它不像 IEEE 的标准手册提供 了Verilog 完整 正式的描述 相反 黄金参考指南以一种方便的参考格式解答了在 Verilog 的实践应用过 程中经常遇到的问题
Verilog 黄金参考指南也不想成为介绍性的指南 这里所提出的信息是一种扼要的参考格式 而不是学 习像Verilog 这种复杂的主题所必要的渐进和共鸣方式 但必须承认的是已经熟悉计算机语言的人希望将这 个参考指南作为Verilog 的课本 因此在开始本指南就会对这个主题进行一个简单的非正式介绍
Verilog 黄金参考指南的主要特征是它包含了从许多 Verilog 项目中积聚起来的大量实践知识 指南不
仅提供方便的语法参考 很多类似的书也有 还对最常见的语言错误提出警告 在代码不能编译的时候给
出线索指出要看什么地方 警告你注意合并问题并对改善你的编码形式提出建议
Verilog 黄金参考指南是为了给 Doulos 的系列 Verilog 训练课程增值 也是 HDL PaceMaker Doulos Verilog 计算机基础训练包的补充
指南的使用
本指南的主体被分成3 个主要部分 每个部分都按字母顺序组织 每一部分都以每页顶部的关键字词
条作为索引 通常 你只要快速翻阅指南查找相应的关键字词条的就可以找到想要的信息 如果找不到
也可以用最后的完整索引查找
本指南的很多信息都围绕Verilog 的句法组织 但也有另外一些有关编码标准 设计流程 错误 保留
字以及在正文按字母顺序参考部分后面的编译器伪指令 系统任务和函数以及命令行选项等特殊的部分
如果你是Verilog 的新手 请先阅读次页的 Verilog 简单介绍
索引
粗体的索引条目在指南的主体有对应的页 剩下的索引条目按重要性的顺序在字符参考部分的页参考
后面
用于定义Verilog 语法的记号说明
在任何可能的时候 语法定义要写得和例子相似 但有需要引入一些额外的记号 简单地来说 方括
号[ ]包含可选的项目 三个点号…表示重复 花括号{ }包含的是注释 斜体表示语法该部分在别处定义 记号的完整描述如下
包含注释的花括号{ }所不是被定义的 Verilog 语法的一部分 但它向你提供了有关语法定义的补充信 息 粗体的花括号{ }是 Verilog 句法的一部分 连接运算符
包含在方括号[ ]中的语法是可选的 粗体的方括号是[ ]Verilog 语法的一部分 矢量范围 位和部分选
择 存储器元素
…表示前面的项目或行的重复 0 次或者多次 或者表示一个列表 例如 Item…表示 Item 重复 0 次或多次
,…表示在用逗号分隔的列表重复 例如 A,B,C 列表中至少有一个项目 列表的结尾没有,号
小写的字是保留字 是Verilog 语言的组成部分 例如 module
以大写字母开头的字 不是斜体 是Verilog 的标识符 即用户定义的名字 它们都不是保留的标识符 例如 InstanceName
斜体的字是句法范畴 即在别处完整给出句法名字的定义 句法范畴可以在相同的页里定义 也可以
在独立的页定义 还可以在下面定义的其中一个特殊范畴中定义
斜体=表示在同一页定义和使用的句法范畴 特殊的语法范畴
MinTypMaxExpression 以 Expression 定义 UnsignedNumber 以 Number 定义
SomethingExpression=Expression 其 中 Something 给 出 有 关 表 达 式 的 含 意 信 息 例 如 ConstantExpression ConstantMinTypMaxExpression
Verilog 的简单介绍
下面向不懂Verilog 语言的读者在技术上简要介绍一下 Verilog
背景
Verilog 硬件描述语言 HDL 是描述电子电路行为和结构的一种语言 是一种 IEEE 标准 IEEE Std.1364-1995
Verilog 用于模拟从随机和纯行为到门级和开关级的抽象范围等层次的数字电子电路功能 也用于从许 多抽象 寄存器传输级 描述合并 即自动产生 门级描述 Verilog 一般用于支持高层次的设计 或基于
语言的设计 其中电子设计在用自动合并工具进行详细设计前要通过高层次的完全抽象仿真来检验
Verilog 也广泛应用于 IC 的门级检验 包括仿真 故障仿真和定时检验
Verilog 最初是在 1984 年由 Gateway Design Automation 公司开发 Verilog-XL 仿真器的时候一起开发 出来 1989 年 Cadence Design Systems 公司并购 Gateway 公司 同时拥有对 Verilog 语言和 Verilog-XL 仿真器的权力 1990 年 Cadence 将 Verilog 语言 不是 Verilog-XL 放到公共领域 为了使 Verilog 语 言通过IEEE 标准化过程 一个非赢利性组织 Open Verilog International OVI 将它不断推进 结果在 1995 年Verilog 成为一个 IEEE 标准 此后 OVI 仍继续不断维护和开发这种语言
语言
在本部分和指南剩下的部分 以大写字母开头的斜体字都是技术术语 都可以在本指南的主体中找到 硬件设计的层次部分在Verilog 中用模块 Module 描述 模块定义了硬件单元的接口 即输入和输
出 及其内部结构或行为
大量的原语或者门 Gates 都内置在 Verilog 语言内 它们表示基本的逻辑门 例如 and or 另 外 还可以定义用户定义的原语 User Defined Primitives UDPs
电子电路的结构通过在高层模块内对模块和原语 UDPs 和门 举实例 Instances 来描述 而且实 例之间通过线网 Nets 连接 线网表示一个电气连接 一条线路或总线 端口 Port 连接列表用于将 线网连接到模块的端口或者连接到原语的实例 其中一个端口表示一个管脚 寄存器 Registers 见下面
也可以连接到实例的输入端口 只能连接到输入端口
线网 和寄存器 的值由逻辑值0 1 X 未知或未初始化的 和 Z 高阻或悬空 组成 除了逻辑 值外 线网还有强度 Strength 值 强度广泛地用于开关级模型以及解释网络有超过一个驱动器的情况
电子电路的行为是用Initial 和 Aways 结构以及连续赋值 Continuous Assignments 描述 这些结构
以及UDPs 和门表示设计的层次树的叶子 每个 Initial Always 连续赋值 UDP 和门实例相对于所有其
他结构是同时执行的 Initial 或 Always 内的语句 Statements 在很多方面上都和软件编程语言的语句相 似 它们在用定时控制 Timing Controls 规定的时间例如延迟以及用 仿真 事件控制触发执行 语句 在Begin-End 块顺序地执行 在 Fork-Join 块并行地执行 连续赋值语句修改线网的值 Initial 和 Aways 修改寄存器的值 Initial 或 Always 可以分解成有给定变量的指定的任务 Tasks 和函数 Functions Verilog 语言还有大量内置的系统任务 System Tasks 和函数 Functions 编程语言接口 Programming Language Interface PLI 是 Verilog 语言的一个组成部分 它提供了一种和调用系统任务和函数相同的 方法调用以C 写的函数
编译
Verilog 源代码通常输入到计算机的一个或多个文本文件中 然后 这些文本文件被提交到 Verilog 编 译器或解释器 编译用于仿真或合并的数据文件 有时候仿真在编译后立即进行 不创建中间的数据文件
模块结构
module M (P1, P2, P3, P4);
input P1, P2;
output [7:0] P3;
inout P4;
reg [7:0] R1, M1[1:1024];
wire W1, W2, W3, W4;
parameter C1 = "This is a string";
initial
begin : BlockName // 语句
end
always begin
// 语句 end
// 连续赋值...
assign W1 = Expression;
wire (Strong1, Weak0) [3:0] #(2,3) W2 = Expression;
// 模块实例...
COMP U1 (W3, W4);
COMP U2 (.P1(W3), .P2(W4));
task T1;
input A1;
inout A2;
output A3;
begin // 语句 end endtask
function [7:0] F1;
input A1;
begin
// 语句
F1 = Expression;
end endfunction
endmodule
语句
#delay
wait (Expression)
@(A or B or C)
@(posedge Clk)
Reg = Expression;
Reg <= Expression;
VectorReg[Bit] = Expression;
VectorReg[MSB:LSB] = Expression;
Memory[Address] = Expression;
assign Reg = Expression deassign Reg;
TaskEnable(...);
disable TaskOrBlock;
-> EventName;
if (Condition) ...
else if (Condition) ...
else ...
case (Selection) Choice1 : ...
Choice2, Choice3 : ...
default : ...
endcase
for (I=0; I<MAX; I=I+1) ...
repeat (8) ...
while (Condition) ...
forever ...
这个快速参考语法摘要不遵从本指南剩余部分所使用的符号惯例
Verilog 黄金参考指南
按字母顺序参考的部分
Always
包含一条或多条语句 过程赋值 任务使能 if case 和循环语句 这些语句在仿真运行中重复执行 由定时控制管理
语法 always
Statement
在何处使用
module-<HERE>-endmodule
规则
• always 只能赋值寄存器 reg integer real time realtime 类型
• 启动仿真时 所有always 都开始执行 而且在仿真过程中持续执行 当到达 always 的最后一条 语句时 程序返回到always 的第一条语句继续执行
注意
• 如果Always 包含超过一条语句 语句要包含在 begin-end 或 fork-join 块中
• 没有定时控制的always 将永远循环
合并
always 是其中一条很有用的 Verilog 合并语句 但 always 通常是不合并的 为了使结果最好 代码应 受到下面其中一种模板的限制
always @(Inputs) // 所有输入 begin
... // 组合逻辑
end
always @(Inputs) // 所有输入 if (Enable)
begin
... // 锁存器的动作
end
always @(posedge Clock) // 只是时钟 begin
... // 同步的动作
end
always @(posedge Clock or negedge Reset)
// 只是时钟和复位 begin
if (!Reset) // 测试异步复位的有效激活电平
... // 异步行动
else
... // 同步行动
end // 给出触发器+逻辑
举例
下面的例子是一个寄存器传送级always always @(posedge Clock or negedge Reset) begin
if (!Reset) // 异步复位 Count <= 0;
else
if (!Load) // 同步载入 Count <= Data;
else
Count <= Count + 1;
end
接下来的例子是描述组合逻辑的always always @(A or B or C or D)
begin
R = {A, B, C, D}
F = 0;
begin : Loop integer I;
for (I = 0; I < 4; I = I + 1) if (R[I])
begin F = I;
disable Loop;
end end // 循环 end
Begin
用于组合语句 使它们按顺序执行 Verilog 的语法通常要求例如在 always 中只有一条语句 如果需 要超过一条语句 语句就要被包含在一个begin-end 块内
语法
begin [: Label [ Declarations...]]
Statements...
end
Declaration = {either} Register Parameter Event
在何处使用 见语句
规则
• begin-end 块必须包含至少一条语句
• Begin-end 块内的语句按顺序执行 定时控制与前一语句有关 执行完 begin-end 块的最后一条 语句后 整个begin-end 块的执行也完成
• Begin-end 和 fork-join 块可在自己内部或相互之间嵌套
• 如果begin-end 块要包含局部声明 必须先命名这个 begin-end 块 即必须有标记
• 如果要禁能一个begin-end 块 必须先命名这个块
注意
仿真中 Verilog LRM 允许 begin-end 块被交叉执行 这就是说 即使 begin-end 块包含的两个相邻语 句之间没有定时控制 仿真器也可以选择执行另一个过程的部分 例如 另一个always 的语句 这就是 Verilog 语言有不确定性的原因
提示
• 在没有局部声明和块不会被禁能的情况下也可以给begin-end 块加上标记 增加其可读性
• 不会在别处使用的寄存器可以用局部声明来声明 这使声明更明确
举例 initial
begin : GenerateInputs integer I;
for (I = 0; I < 8; I = I + 1)
#Period {A, B, C} = I;
end
initial begin
Load = 0; // 时刻 0 Enable = 0;
Reset = 0;
#10 Reset = 1; // 时刻 10
#25 Enable = 1; // 时刻 35
#100 Load = 1; // 时刻 135 end
Case
一个语句基于case 表达式的值条件地执行最多一个分支 语法
CaseKeyword ( Expression)
Expression,... : Statement {表达式可以是变量}
Expression,... : Statement
... {任意数量的情况}
[default [:] Statement] {不一定在结尾}
endcase
CaseKeyword = {either} case casex casez
在何处使用 见语句
规则
• casex 语句的 X 和 Z 以及 casez 语句的 Z 表示 无关
• 最多可以有一条default 语句 当没有标记表达式符合 case 表达式时执行 default 语句 标记 是在冒号左面的一个表达式或是用逗号分隔的表达式列表 也可以是保留字default default 后面 可以有或者没有冒号
• 如果标记是用逗号分隔的两个或多个表达式列表 只要case 表达式符合其中一个标记表达式就 认为标记就符合case 表达式
• 如果没有标记表达式符合case 表达式而且没有 default 语句 case 语句不起作用
注意
• 如果特殊的标记要执行超过一条语句 语句必须包含在begin-end 或 fork-join 块内
• 如果相应的标记是第一个符合case 表达式的标记 case 只会执行这个分支 Case 的标记不需 要互斥 因此当相同的标记被错误地重复使用时Verilog 编译器不会报告出错
• Casex 和 casez 语句的要用保留字 endcase 而不是 endcasex 或 endcasez 结束
• Casex 表达式中的 X 或 Z 或者 casez 表达式的 Z 符合所有 case 的标记 这会使仿真结果混淆
合并
• case 语句内的赋值一般合并到多路复用器 如果 case 标记使用变量 例如寄存器或线网 那 么可以合成优先权译码器
• 无时钟的 always 中的不完全赋值 即没有指定某些输入条件的输出 被合并到透明锁存器 有 时钟always 中的不完全赋值合并到循环寄存器
提示
• 仿真时 通常用default 作为最后一条 case 语句 以捕捉非法的情况
• casez 通常比 casex 好 因为在仿真中出现 X 会导致产生误导或混乱的结果
• casex 和 casez 标记的 Z 可用字符?代替 这可以清楚地表示值 无关 而不是高阻 举例
case (Address)
0 : A <= 1; // 选择一个单独的地址值 1 : begin // 执行超过一条语句
A <= 1;
B <= 1;
end
2, 3, 4 : C <= 1; // 选出几个地址值 default : // 其他剩余的地址
$display("Illegal Address value %h in %m at %t", Address, $realtime);
endcase
casex (Instruction)
8'b000xxxxx : Valid <= 1;
8'b1xxxxxxx : Neg <= 1;
default begin
Valid <= 0;
Neg <= 0;
end endcase
casez ({A, B, C, D, E[3:0]}) 8'b1??????? : Op <= 2'b00;
8'b010????? : Op <= 2'b01;
8'b001???00 : Op <= 2'b10;
default : Op <= 2'bxx;
endcase
编码标准
编码标准共有两类 词汇编码标准负责控制文本格式 命名惯例和注释 这种标准增加程序的可读性
并简化维护 合并编码标准控制Verilog 的结构样式 避免普通的合并缺陷并在设计流程的早期发现合并错 误
下面列出的编码标准需要根据所选的工具和个人喜好修改
词汇编码标准
• 将每个Verilog 源文件的内容限制到一个模块 不从文件分割模块
• 源文件的名字应与文件内容有关 例如 ModuleName.v
• 每行只写一条声明或语句
• 使用和例子一样的缩进
• 用户定义的名字要注意大小写一致 例如 第一个字符是大写
• 尽管局部名字 例如 循环变量 可能很简洁 但用户定义的名字应当是有意义并包含信息的
• 写注释来解释 不是复制 Verilog 代码 其中 注释接口特别重要 例如 模块参数 端口 任 务和函数变量
• 在任何可能的时候用参数或`定义宏 避免在声明和语句中直接嵌入文本数字和字符串
合并编码标准
• 将设计分割成小的功能块 并为每个块使用一个行为样式 除了设计的主要部分外 其他部分要
避免门级描述
• 有定义好的时钟策略 而且在Verilog 明确执行该策略 例如 单时钟 多相时钟 门时钟 多 时钟域 确保Verilog 的时钟和复位信号是清楚的 即 不从组合逻辑或无意识的门产生
• 有定义好 制造好 的测试策略 并适当地编码Verilog 例如 所有触发器可复位 从外部管脚
进行测试 无功能冗余
• 所有Verilog 的 always 应遵守标准合并处理模板的其中一个 见 Always
• 描述组合和锁存逻辑的always 必须在 always 顶部的事件控制列表列出所有输入
• 组合的always 必须不能包含不完全赋值 即所有输入值的组合必须赋值给所有输出
• 描述组合和锁存逻辑的always 必须不能包含反馈 即 always 中被赋值为输出的寄存器不能作为 always 的输入
• 带时钟的always 在事件控制列表中只能有时钟和异步控制输入 一般是复位或置位
• 避免不必要的锁存器 这些多余的锁存器是由于无时钟always 的不完全赋值产生
• 避免不必要的触发器 当用非阻塞赋值在带时钟的 always 内给寄存器赋值或者当寄存器在连续 重复的带时钟always 之间和时钟周期之间保持值不变时 触发器被合并
• 所有内部状态寄存器必须可复位 这样寄存器传输极和门级描述在检验的时候可以复位到相同的
已知状态 这不能应用到流水线寄存器或同步寄存器
• 对于有不能到达的状态的有限状态机器和其他时序电路 例如 一个4 位的十进制计数器有 6 个 不可到达的状态 如果硬件在这些状态下可以被控制 那么所有2N个可能的状态的行为必须在 Verilog 中明确描述 包括不能到达的状态的行为 这就允许保留状态机器进行合并
• 避免在赋值中使用延时 除非要求在寄存器传输级解决0 延时时钟的时滞问题
• 不要使用integer 或 time 类型的寄存器 否则它们会分别合并到 32 位总线和 64 位总线
• 请仔细检查使用动态索引的Verilog 代码 即用可变的索引或地址作为位选择或存储器元素 循
环语句或算术运算符 因为这些代码要被合并成大量难以优化的门
注释
可以 应当 在Verilog 源代码中包含说明性的注释
语法 {单行注释}
//
{多行注释}
/* ... */
在何处使用
几乎可以在任何地方使用 但不能分割运算符 数字 字符串 名字和关键字
规则
• 单行注释用两个斜杠字符开始 在行的末端结束
• 多行注释用/*开始 注释可能跨过多行 直到*/处结束
• 多行注释不能嵌套 但多行注释中可以有单行注释 它们没有特别的含意
注意
/* ... /* ... */ ... */ 注释在第一个*/处结束 第二个*/将被忽略 这个例子无疑会出现句法错误
提示
全部使用单行注释 只在例如在开发和调试代码的过程中需要注释大段代码时使用多行注释
举例
// 这是一个注释 /*
共有三行的多行注释
*/
module ALU /* 8 位 ALU */ (A, B, Opcode, F);
连续赋值
当表达式中的线网或寄存器的值改变时 连续赋值在一个或多个线网创建事件
语法 {either}
assign [ Strength] [ Delay] NetLValue = Expression, NetLValue = Expression, ...;
NetType [ Expansion] [ Strength] [ Range] [ Delay]
NetName = Expression, NetName = Expression,
…; {见线网}
NetLValue = {either}
NetName
NetName[ ConstantExpression ]
NetName[ ConstantExpression: ConstantExpression ] { NetLValue,...}
在何处使用
module-<HERE>-endmodule
规则
两种形式的连续赋值效果相同
Assign 左边的线网必须在连续赋值语句的前面的源代码中明确声明
注意
尽管连续赋值和过程连续赋值很相似 但它们是不一样的 请确保将assign 放在正确的地方 连续赋 值在initial 或 always 外 过程连续赋值在允许使用语句的地方都可以使用 initial always 任务 函数
等的内部
合并
• 合并工具忽略延时和强度 使用指定定时限制的工具代替
• 连续赋值作为组合逻辑合并
提示
• 用连续赋值描述组合逻辑可以容易地用一个简单表达式描述 函数可以用于组成表达式 always
一般能较好地描述许多复杂的组合逻辑 而且其仿真速度比用大量独立的连续赋值语句快得多
• 当Verilog 要求使用线网时 连续赋值对传输寄存器的值到线网非常有用 例如 将在 initial 中描 述的测试激励应用到模块实例的输入输出口
举例
wire cout, cin;
wire [31:0] sum, a, b;
assign {cout, sum} = a + b + cin;
wire enable;
reg [7:0] data;
wire [7:0] #(3,4) f = enable ? data : 8'bz;
Defparam
编译时覆盖参数值 通过使用层次名字 参数值可以在设计层次内部或外部的任何地方被覆盖
语法
defparam ParameterName = ConstantExpression, ParameterName = ConstantExpression, ... ;
在何处使用
module-<HERE>-endmodule
合并 一般不合并
提示
不要使用defparam 它提供了一种有用的方法逆向注释布局延时 但现在通常通过使用特殊的程序块 和编程语言接口实现 对要覆盖的参数 请在模块实例化时使用#语法
举例
`timescale 1ns / 1ps module LayoutDelays;
defparam Design.U1.T_f = 2.7;
defparam Design.U2.T_f = 3.1;
...
endmodule
module Design (...);
...
and_gate U1 (f, a, b);
and_gate U2 (f, a, b);
...
endmodule
module and_gate (f, a, b);
output f;
input a, b;
parameter T_f = 2;
and #(T_f) (f,a,b);
endmodule
Delay
延时可以在UDP 和门的实例 连续赋值和线网中使用 这些延时能为线网元件和连接的传播延时建模
语法 {either}
# DelayValue
#( DelayValue[, DelayValue[, DelayValue]]) {上升 下降 关断}
DelayValue = {either}
UnsignedNumber ParameterName
ConstantMinTypMaxExpression
在何处使用
见连续赋值 实例化 线网
规则
• 如果只给出一个延时的值 这个值既表示上升和下降传播延时 即分别从0 或 1 跳变 又表示关 断延时 如果可用
• 如果给出两个延时的值 第一个值是上升延时 第二个值是下降延时 但tranif0 tranif1 rtranif0 和rtranif1 除外 它们的第一个值表示开启延时 第二个值是关断延时
• 如果给出三个延时的值 第三个延时是关断延时 转换到Z 但trireg 线网除外 它的第三个延 时是电荷衰减时间
• X 的延时是最小的指定延时
• 对于矢量来说 从非0 到 0 的转换被认为是 下降 转换到Z 是 关断 所有其他转换都被认
为是 上升
注意
很多工具强调 延时的MintypMax 表达式必须包含在括号中 例如 #(1:2:3)是允许的 但#1:2:3 是 不允许的
合并
合并工具忽略延时 合并线网列表的延时受到合并工具命令例如设置最大的时钟周期约束
提示
指定的块延时 路径延时 通常是建模延时的一种很精确的方式 而且它还提供了计算延时的机制以
及逆向注释布局信息
设计流程
使用Verilog 和合并设计 ASIC 或复杂 FPGA 的基本流程如下 设计流程需要进行迭代 但这里不作介
绍 而且 设计流程必须根据设计的器件种类和特殊的应用进行修改
1 系统分析和说明 2 系统划分
2.1 顶级块的捕捉 2.2 块的大小估算 2.3 最初的平面布置 3 块级设计 对于每个块
3.1 写寄存器传输级 Verilog 3.2 合并编码检查
3.3 写 Verilog 测试程序 3.4 Verilog 仿真
3.5 写合并脚本 约束 边界条件 层次 3.6 初始的合并 分析门计数和定时 4 芯片集成 对于完整的芯片
4.1 写 Verilog 测试程序 4.2 Verilog 仿真 4.3 合并 4.4 门级仿真 5 测试阶段
5.1 为测试修改门级线网列表 5.2 产生测试矢量
5.3 仿真可测试的线网 6 芯片的放置和布线 或装配
7 接线柱布局仿真 故障仿真和定时 时序 分析
Disable
使激活的任务或有名字的块在执行完所有语句前终止其执行
语法
disable BlockOrTaskName;
在何处使用 见语句
规则
• 禁能一个有名字的模块 begin-end 或 fork-join 或者一个任务就禁能从该模块或任务使能的所
有任务 而且向下禁能到使能的任务层次 然后继续执行被禁能的任务后面使能的语句或有名字
的块
• 有名字的模块或任务可以在自己内部用disable 语句自我禁能
• 当任务被禁能时 下面的事件不能确定 任何输出或输入输出的值 仍未生效的非阻塞赋值预定
的事件 assign 和 force 语句
• 函数不能被禁能
注意
任务自我禁能与任务返回不一样 因为任务自我禁能的输出没有定义
合并
disable 只在有名字的模块或任务自我禁能时合并
提示
用disable 可以从任务早期退出 退出循环或继续下一个迭代的循环
举例
begin : Break forever
begin : Continue ...
disable Continue; // 继续下一个迭代 ...
disable Break; // 退出 forever 循环 ...
end // 继续 end // 终止
错误
下面列出的是最常见的Verilog 错误 前五位占了所有错误的 50%
前5 位 Verilog 错误
• 过程赋值的左边不声明为寄存器类型
• 缺少begin-end 语句 或 begin-end 语句不匹配
• 二进制数缺少基 ’b 也就是说编译器将它们看作是十进制数
• 在编译器伪指令中错误使用撇号 应当是后撇号或重音符号` 和数字基 应当是一般的单引号或 倒转的逗号’
• 语句的结尾缺少分号
其他常见错误
• 尝试在任务或函数名字后面的方括号中定义任务和函数参数
• 在测试程序中测试时 忘了要将模块实例化
• 用过程连续赋值代替连续赋值 即 assign 在错误的地方使用
• 尝试用保留字作为标识符 例如 xor
• Always 内没有定时控制 导致无限循环
• 在事件控制 例如 @(a or b) 中用逻辑或运算符 || 代替保留字 or
• 用隐式线网连接矢量端口
• 模块实例中端口连接的顺序错误
• 嵌套的if-else 语句中有包含错误 begin-end 的位置不正确
• 等号 的形式错误 = 用于赋值 == 用于比较数字值 === 用于匹配 0 1 X 和 Z 的精确序列
事件
事件用于在行为模型中描述通讯和同步
语法
event Name ,...; {声明事件}
-> EventName; {触发事件}
在何处使用 见->的语句
在以下的地方允许有事件声明 module-<HERE>-endmodule begin : Label-<HERE>-end fork : Label-<HERE>-join task-<HERE>-endtask function-<HERE>-endfunction
规则
事件没有值或延时 它们被事件触发语句触发 在跳变沿敏感的定时控制中测试
合并 一般不合并
提示
有名字的事件通常用于在测试程序和系统级模型中与相同模块或不同模块 使用层次名字 的always 之间通讯
举例
event StartClock, StopClock;
always fork
begin : ClockGenerator Clock = 0;
@StartClock forever
#HalfPeriod Clock = !Clock;
end
@StopClock disable ClockGenerator;
join
initial
begin : stimulus ...
-> StartClock;
...
-> StopClock;
...
-> StartClock;
...
-> StopClock;
end
表达式
表达式从一组运算符 名字 文本值和子表达式计算值 常数表达式是在编译时可以计算值的表达式 标量表达式只计算1 位的值 延时可以用 MinTypMax 表达式表示
语法
Expression = {either}
Primary
Operator Primary {一元运算符}
Expression Operator Expression {二元运算符}
Expression ? Expression : Expression String
Primary = {either}
Number
Name {参数 线网或寄存器的名字}
Name[ Expression] {位选择}
Name[ Expression: Expression] {部分选择}
MemoryName[ Expression]
{ Expression,...} {串联}
{ Expression{ Expression,...}} {复制}
FunctionCall
( MinTypMaxExpression) {延时使用 MinTypMax 表达式}
MinTypMaxExpression = {either}
Expression
Expression: Expression: Expression
规则
• 只有矢量的线网和reg 以及 integer 和 time 允许位和部分选择
• 部分选择必须在分号的左边寻址一个比分号右边更高的位 最高位是线网或寄存器声明中左边
的范围表达式的值
• 有X 或 Z 或超出范围的位和部分选择可能会也可能不会被捕捉为编译器错误 它们给出的表达式 结果是X
• 存储器没有位或部分选择机制
• 当表达式用整数常数作为操作数 无基但带符号的整数 例如 -5 的处理和有基且带符号的整 数 例如 -‘d5 不一样 前者作为带符号数处理 后者作为无符号数处理
注意
很多工具要求常数MinTypMax 表达式的最小 典型和最大延时值有序 例如 min<=typ<=max
举例 A + B
!A
(A && B) || C A[7:0]
B[1]
-4'd12/3 // 一个很大的正数
"Hello" != "Goodbye" // 表达式为真 1
$realtobits(r); // 系统函数调用
{A, B, C[1:6]} // 并置 8 位
1:2:3 // MinTypMax
For
通用循环语句 允许一条或多条语句重复执行
语法
for ( RegAssignment; {初始值}
Expression; {循环条件}
RegAssignment) {循环值}
Statement
RegAssignment = RegisterLValue = Expression RegisterLValue = {either}
RegisterName
RegisterName[ Expression]
RegisterName[ ConstantExpression: ConstantExpression]
Memory[ Expression]
{ RegisterLValue,...}
在何处使用 见语句
规则
执行for 循环的时候 一定要建立初始值 在每次重复前 包括第一次 都要检验表达式 如果是 false 例如 0 X 或 Z 循环终止 每次循环后 都要进行循环赋值
注意
请小心使用小型宽度的reg 作为循环变量 测试负值的 reg 也要非常小心 如果重复的加 减运算和 reg 的值都作为无符号数处理 循环表达式就永远不会出错
reg [2:0] i; // i 的值永远在 0 到 7 之间
...
for ( i=0; i<8; i=i+1 ) // 循环不会停止 ...
for ( i=-4; i<0; i=i+1 ) // 不会执行 ...
在像上面的情况中 循环变量i 应该用 integer 类型
合并
如果循环的边界固定 For 循环被合并到重复的硬件结构
举例 V = 0;
for ( I = 0; I < 4; I = I + 1 ) begin
F[I] = A[I] & B[3-I]; // 4 个独立的与门
V = V ^ A[I]; // 4 个级联的异或门
end
Force
Force 和过程连续赋值相似 覆盖线网和寄存器的行为 它主要用于帮助调试
语法 {either}
force NetLValue = Expression ;
force RegisterLValue = Expression ;
{either}
release NetLValue;
release RegisterLValue;
NetLValue = {either}
NetName {NetName,...}
RegisterLValue = {either}
RegisterName {RegisterName,...}
在何处使用 见语句
规则
• 线网或寄存器的位或部分选择不能被force 或 release
• Force 优于过程连续赋值 assign 是过程语句
• Force 保持有效 直到在相同的线网或寄存器执行另一个 force 或者线网或寄存器被释放
• 当寄存器的force 释放后 它不立即修改寄存器的值 Force 的值会维持直到发生下一个过程赋
值 除非寄存器的过程连续赋值有效
• 当线网的force 释放后 线网的值由线网的驱动器决定 而且值可以立即更新
合并 不合并
提示
在测试程序使用 为了调试的目的对行为进行覆盖 请不要用于建模行为 用连续赋值代替
举例
force f = a && b;
...
release f;
Forever
使一条或多条语句执行无限循环
语法 forever
Statement 在何处使用
见语句
注意
无限循环应包含定时控制或可以自我禁能 否则它将无限循环
合并
一般不合并 如果连续重复被形式为@(posedge Clock)的定时控制终止 可以合并
提示
• 用于描述测试程序的时钟
• 用disable 跳出循环
举例 initial
begin : Clocking Clock = 0;
forever
#10 Clock = !Clock;
end
initial
begin : Stimulus ...
disable Clocking; // 停止时钟 end
Fork
将语句组合成一个并行的块 使它们能并发执行
语法 fork [ : Label
[ Declarations...]]
Statements...
join
Declaration = {either} Register Parameter Event
在何处使用 见语句
规则
• fork-join 块必须包含至少一个语句
• Fork-join 块的语句是并发执行的 所以 fork-join 块内语句的顺序无关紧要 定时控制与进入模块
的时间有关 当Fork-join 块的所有语句都执行完后 fork-join 块也执行完
• Begin-end 和 fork-join 块可以在自己内部或者互相嵌套
• 如果fork-join 块要包含局部声明 它必须被命名 即它必须有一个标记
• 如果要禁能fork-join 块 它必须被命名
合并 不合并
提示
Fork-join 语句用于描述并发的激励
举例 initial
fork : stimulus
#20 Data = 8'hae;
#40 Data = 8'hxx; // 最后执行的语句 Reset = 0; // 首先执行的语句
#10 Reset = 1;
join // 在时刻 40 完成
函数
用于组合语句来定义新的算术或逻辑函数 函数通常在模块内声明而且只从该模块调用 但它也可以
用层次名字从别处调用
语法
function [ RangeOrType] FunctionName;
Declarations...
Statement endfunction
RangeOrType = {either} Range integer time real realtime Range = [ ConstantExpression: ConstantExpression]
Declaration = {either}
input [ Range] Name,...;
Register Parameter Event
在何处使用
module-<HERE>-endmodule
规则
• 函数至少有一个输入变量 可以没有任何输出或输入输出
• 函数不包含定时控制 延时 事件控制或等待
• 通过赋值函数名 函数可以返回一个值 就象是寄存器一样
• 函数不能使能任务
• 函数不能被禁能
注意
• 函数的输入不像模块的端口一样列在函数名后面的方括号中 它们只在输入声明中声明
• 如果函数包含超过一个语句 语句必须包含在begin-end 或 fork-join 块内
合并
每次调用函数都被合并为独立的组合逻辑块
举例
function [7:0] ReverseBits;
input [7:0] Byte;
integer i;
begin
for (i = 0; i < 8; i = i + 1) ReverseBits[7-i] = Byte[i];
end endfunction
函数调用
调用一个函数 返回一个可以在表达式中使用的值
语法
FunctionName ( Expression,... );
在何处使用 见表达式
规则
函数必须至少有一个输入变量 因此函数调用通常至少有一个表达式
合并
每次函数调用都作为组合逻辑一个独立块合并
举例
Byte = ReverseBits(Byte);
门
Verilog 有大量的内置逻辑门和开关模型 这些门和开关可以在模块里实例化 创建模块行为的结构化
描述 逻辑门
and (Output, Input,...) nand (Output, Input,...) or (Output, Input,...) nor (Output, Input,...) xor (Output, Input,...) xnor (Output, Input,...)
缓冲门和非门 buf (Output,..., Input) not (Output,..., Input)
三态逻辑门
bufif0 (Output, Input, Enable) bufif1 (Output, Input, Enable) notif0 (Output, Input, Enable) notif1 (Output, Input, Enable)
MOS 开关
nmos (Output, Input, Enable) pmos (Output, Input, Enable) rnmos (Output, Input, Enable) rpmos (Output, Input, Enable)
CMOS 开关
cmos (Output, Input, NEnable, PEnable) rcmos (Output, Input, NEnable, PEnable)
双向开关
tran (Inout1, Inout2) rtran (Inout1, Inout2)
带控制的双向开关
tranif0 (Inout1, Inout2, Control) tranif1 (Inout1, Inout2, Control) rtarnif0 (Inout1, Inout2, Control) rtranif1 (Inout1, Inout2, Control)
上拉和下拉源 pullup (Output) pulldown (Output)
真值表
表中的逻辑值L 和 H 表示结果的值部分未知 L 表示 0 或 Z H 表示 1 或 Z
and 0 1 X Z nand 0 1 X Z 0 0 0 0 0 0 1 1 1 1 1 0 1 X X 1 1 0 X X X 0 X X X X 1 X X X Z 0 X X X Z 1 X X X
or 0 1 X Z nor 0 1 X Z 0 0 1 X X 0 1 0 X X 1 1 1 1 1 1 0 0 0 0 X X 1 X X X X 0 X X Z X 1 X X Z X 0 X X
xor 0 1 X Z xnor 0 1 X Z
0 0 1 X X 0 1 0 X X 1 1 0 X X 1 0 1 X X X X X X X X X X X X Z X X X X Z X X X X
buf not
输入 输出 输入 输出
0 0 0 1 1 0 1 1 X 0 X 1 Z 0 Z 1
缓冲门和非门有多个输出 这些输出的值都相等
使能端 EN 使能端 EN
bufif0
0 1 X Z bufif1
0 1 X Z
0 0 Z L L 0 Z 0 L L
1 1 Z H H 1 Z 1 H H
X X Z X X X Z X X X
数据
Z X Z X X 数据
Z Z X X X
使能端 EN 使能端 EN
notif0
0 1 X Z notfif1
0 1 X Z
0 1 Z H H 0 Z 1 H H
1 0 Z L L 1 Z 0 L L
X X Z X X X Z X X X
数据
Z X Z X X 数据
Z Z X X X
控制 控制 pmos
rpmos 0 1 X Z
nmos
rnmos 0 1 X Z
0 0 Z L L 0 Z 0 L L
1 1 Z H H 1 Z 1 H H
X X Z X X X Z X X X
数据
Z Z Z Z Z 数据
Z Z Z Z Z
cmos (W, Datain, NControl, PControl);
相当于
nmos (W, Datain, NControl);
pmos (W, Datain, PControl);
规则
当开关nmos pmos coms tran tranif0 和 tranif1 开路时 输入到输出所传播的信号强度不会改变 电阻性开关rnmos rpmos rcoms rtran rtranif0 和 rtranif1 会减弱传播的信号强度
强度 减弱为
电源 拉
强 拉
拉 弱
大 中等
弱 中等
中等 小
小 小
高阻 高阻
IEEE1364
Verilog HDL 是由 IEEE 标准 Verilog 硬件描述语言参考手册 1364-1995 定义 这个文档是从基于 Cadence Verilog LRM 版本 1.6 的 OVI Verilog 参考手册 1.0 和 2.0 衍生出来 在标准化进程以前 Cadence Verilog-XL 仿真器已经有一个事实语言标准 很多第三方的仿真器都试图遵从这个事实标准
尽管标准化进程的目的是标准化Verilog-XL 使用的已有的 Verilog 语言 但 IEEE 标准语言和事实标准
仍有一些差别 结果仿真器可能支持也可能不支持以下的特性
• 原语和模块实例数组 见实例化
• 带变量的宏定义 见`define
• `undef
• IEEE 标准不支持的数字强度值 见编译器伪指令
• 许多Verilog-XL 支持但 IEEE 标准不支持的系统任务 函数以及编译器伪指令
• 如果线网或寄存器在模块内只有一个驱动器 那么IEEE 标准允许特殊模块的通路延时目的地是 寄存器或者线网 事实标准对目的地有许多限制 见Specify
• 指定的通路延时表达式可以由高达12 个用逗号分隔的独立延时表达式组成 事实标准允许最多 6 个表达式 见Specify
• 线网声明中保留字scalared 和 vectored 的位置有变化 在事实标准中 保留字的后面紧跟着矢 量范围 IEEE 标准中 保留字紧跟在线网类型后面 见线网
• 对常数MinTypMax 表达式的最小值 典型值和最大值的相对大小没有限制 事实标准规定最小
延时的值要小于或等于典型延时 典型延时的值也要小于或等于最大延时的值
• IEEE 标准指出当用 MinTypMax 表达式表示延时的时候 该表达式不需要包含在方括号中 事实 标准要求有方括号
If
If 根据条件表达式执行两条语句或两个语句块的其中一条 个
语法
if ( Expression) Statement [else
Statement]
在何处使用 见语句
规则
如果表达式的值非0 表达式为真 如果值是 0 X 或 Z 表达式为假
注意
• 如果if 或 else 分支要执行超过一条语句 这些语句必须包含在 begin-end 或 fork-join 块内
• 注意省略 else 部分的嵌套 if-else 语句 else 与前面最近的 if 相关联 除非已经出现对应的 begin-end Verilog 编译器会省略源代码中的缩进
合并
• if 语句中的赋值通常合并到多路复用器
• 某些输入不改变输出的不完全赋值合并到无时钟always 的透明锁存器而且在带时钟 always 中再 循环
• 在一些情况下 嵌套的if 语句合并到多个逻辑级 用 case 语句代替 if 就不会出现这种情况
提示
一组嵌套的if-else 语句可以用于给出条件测试的优先级 如果想不给出优先级而译码一个值 可以使 用case 语句
举例
if (C1 && C2) begin
V = !V;
W = 0;
if (!C3) X = A;
else if (!C4) X = B;
else
X = C;
end
Initial
包含只执行一次的语句或语句块 initial 在仿真启动时开始执行
语法 initial
Statement
在何处使用
module-<HERE>-endmodule
合并 无合并
注意
如果initial 包含超过一条语句 这些语句要包含在 begin-end 或 fork-join 块内
提示
在测试程序中用initials 来描述激励
举例
下面的例子展示了在测试程序中用initial 产生矢量 reg Clock, Enable, Load, Reset;
reg [7:0] Data;
parameter HalfPeriod = 5;
initial
begin : ClockGenerator Clock = 0;
forever
#(HalfPeriod) Clock = !Clock;
end
initial begin
Load = 0;
Enable = 0;
Reset = 0;
#20 Reset = 1;
#100 Enable = 1;
#100 Data = 8'haa;
Load = 1;
#10 Load = 0;
#500 disable ClockGenerator; // 停止时钟发生器 end
实例化
实例是模块 UDP 或门的唯一副本 设计中的层次是通过实例化模块来创建 设计的行为在结构上可 以通过建立连接到线网的UDP 门和其他模块的实例来描述
语法 {either}
ModuleName [#( Expression,...)] ModuleInstance,...;
UDPOrGateName [ Strength] [ Delay] PrimitiveInstance,...;
ModuleInstance =
InstanceName [ Range] ([ PortConnections]) PrimitiveInstance =
[InstanceName [ Range]] ( Expression,...)
Range = [ ConstantExpression: ConstantExpression]
PortConnections = {either}
[ Expression] ,... {有序的连接}
.PortName([ Expression]) ,... {有名字的连接}
在何处使用
module-<HERE>-endmodule
规则
• 有名字的端口连接只允许用于模块实例
• 给出的有序端口连接表中 第一个元素连接到模块或门的第一个端口 第二个元素连接到第二个
端口 如此类推
• 如果给出有名字的端口连接表 名字必须和模块的端口对应 它与连接的顺序无关
• 在有序端口连接表中 用两个相邻的逗号省略表达式表示该模块端口不连接 有名字的端口连接
表中 省略所有名字或使方括号内的表达式为空表示端口不连接
• 输入端口可以连接任意的表达式 但输出端口只能连接到线网 线网的位或部分选择或者是它们
的级连 输入表达式创建隐性的连续赋值
• 如果给出的范围是实例名的部分 它表示一个实例数组 当端口表达式的位长度和模块 被实例
化的 UDP 或门相应端口的位长度相等时 整个表达式被连接到每个实例的这个端口 如果位长
度不同 每个实例都得到在从指定范围的右边开始的表达式部分选择 连接到所有实例的位太多
或太少都会出现错误
• #符号有两种不同的用法 它可以用于覆盖模块实例中一个或多个参数的值 也可以指定 UDP 或
门实例的延时 对于模块实例 第一个表达式替代在模块中声明的第一个参数的值 第二个表达
式替代第二个参数的值 如此类推
• 门pullup pulldown tran 和 rtran 的实例不允许有延时
• 以下的开关不能指定强度 nmos pmos coms rnmos rpmos rcmos tran rtran tranif0 tranif1 rtranif0 以及 rtranif1
注意
• 在有序表中很容易意外地交换两个端口 如果端口的宽度和方向相同 第一次提示出错是在仿真
中得到错误结果的时候 这些错误很难调试 在模块实例中用有名字的端口连接表可以避免这个
问题
• 模块 UDP 或门实例的阵列最近才添加到 Verilog 语言中 并不是所有工具都支持
合并
UDP 和开关的实例一般不合并
提示
• 模块端口使用有名字的连接增加可读性并减少错误的可能性 见上面
• 除了位或部分选择以及级连外 不要使用端口表达式 用独立的连续赋值代替
举例 UDP 实例
Nand2 (weak1,pull0) #(3,4) (F, A, B);
模块实例
Counter U123 (.Clock(Clk), .Reset(Rst), .Count(Q));
在下面的两个例子中 端口QB 不连接 DFF Ff1 (.Clk(Clk), .D(D), .Q(Q), .QB());
DFF Ff2 (Q,, Clk, D);
下面是一个与或非 显示了端口连接表的表达式
nor (F, A&&B, C) // 不推荐使用 下面的例子是一个实例数组
module Tristate8 (out, in, ena);
output [7:0] out;
input [7:0] in;
input ena;
bufif1 U1[7:0] (out, in, ena);
/* 相当于(除了实例名字)...
bufif1 U1_7 (out[7], in[7], ena);
bufif1 U1_6 (out[6], in[6], ena);
bufif1 U1_5 (out[5], in[5], ena);
bufif1 U1_4 (out[4], in[4], ena);
bufif1 U1_3 (out[3], in[3], ena);
bufif1 U1_2 (out[2], in[2], ena);
bufif1 U1_1 (out[1], in[1], ena);
bufif1 U1_0 (out[0], in[0], ena);
*/
endmodule
模块
模块是Verilog 的层次基本单元 模块包含声明和功能描述而且并代表硬件元件
模块也用于声明可以在各处使用的参数 任务和函数 这样的模块不代表实际的硬件元件 因为它们
不需要包含initial always 连续赋值或实例
语法 {either}
module ModuleName [( Port,...)];
ModuleItems...
endmodule
macromodule ModuleName [( Port,...)];
ModuleItems...
endmodule
ModuleItem = {either}
Declaration Defparam
ContinuousAssignment Instance
Specify Initial Always
Declaration = {either}
Port Net Register Parameter Event Task Function
在何处使用
模块要在任何其他模块或UDP 的外部声明
规则
• 几个模块或UDP 或者都有 可以在一个文件内描述 实际上 一个单独的模块可被分割成两
个或多个文件 但不提倡这样做
• 模块可以用关键字macromodule 定义 语法和模块一样 Verilog 对宏模块的编译和模块不一样
例如它不为宏模块实例创建层次结构的层 这可能会使仿真在速度或存储器方面更有效 为此
宏模块会受到某些执行指定约束的限制 如果没有碰到这些问题 宏模块和普通模块的处理一样
注意
module 和 macromodule 都用关键字 endmodule 结束
合并
• 每个模块作为一个独立的层次块合并 允许你控制合并线网的层次结构 但一些工具默认地拉平
层次结构
• 不是所有工具都支持宏模块
提示
每个文件只有一个模块 这简化了大型设计的源代码维护
举例
macromodule nand2 (f, a, b);
output f;
input a, b;
nand (f, a, b);
endmodule
module PYTHAGORAS (X, Y, Z);
input [63:0] X, Y;
output [63:0] Z;
parameter Epsilon = 1.0E-6;
real RX, RY, X2Y2, A, B;
always @(X or Y) begin
RX = $bitstoreal(X);
RY = $bitstoreal(Y);
X2Y2 = (RX * RX) + (RY * RY);
B = X2Y2;
A = 0.0;
while ((A - B) > Epsilon || (A - B) < -Epsilon) begin
A = B;
B = (A + X2Y2 / A) / 2.0;
end end
assign Z = $realtobits(A);
endmodule
名字
任何Verilog 的 东西 都用它的名字来识别
语法 Identifier
\EscapedIdentifier {用空白结束}
规则
• 标识符可以由字母 数字 下划线和美元符号组成 第一个字符必须是字母或下划线 不能是数
字或美元符号
• 用反斜杠引入的转义标识符以空白 空格 制表符 换页符或换行符 结束 它由任意可打印的
字符 除了空白字符 组成 反斜杠和空白不是标识符的组成部分 因此 例如标识符 Fred 和 转义标识符\Fred 是一样的
• Verilog 中的名字是大小写敏感的
• Verilog 正文中的名字在任何特殊地方都不能有超过一种含意 名字的内部声明 例如 在有名字 的begin-end 块中的名字 在声明外是隐藏的 例如 模块中的名字 其中有名字的 begin-end 块是模块的一部分
层次名字
• 在Verilog HDL 描述中的每个标识符有一个唯一的层次名字 这就意味着通过这个层次名字所有
线网 寄存器 事件 参数 任务和函数都能从声明它们的模块外访问
• 在层次名字的顶层是没有实例化的模块名字 尽管在一次独立的仿真运行中可能有超过一个顶级
模块 但顶层的测试程序是一个实例
• 层次名字的新层次由每个模块实例 有名字的块 任务或函数定义来定义
• Verilog 对象的唯一的层次名字是由层次根部的顶级模块名字和包含用点号分隔的对象的模式实
例 有名字的块 任务或函数的名字组成
向上的名字引用
• 层次名字形式的名字由两个用点号分隔的标识符组成 它涉及到以下的其中一种
当前模块内模块实例的一项 这是一个向下引用
顶级模块内的一项 这是一个层次名字
在当前模块的父模块内模块实例的一项 这是一个向上的名字引用
• 向上的名字引用中的第一个标识符可以是模块名或模块实例的名字
合并
合并工具一般不支持层次名字和向上的名字引用
提示
• 一般选择对读者来说有意义的名字 而且这对全局名比对局部名更重要 例如 G0123 对于全局 复位来说是一个坏名字 而对于循环变量来说I 是一个可接受的名字
• 不要使用转义名 这些名字是被 EDA 工具 例如线网制表器或合并工具 使用 这些工具的命 名规则和Verilog 不一样
• 只在测试工具或者在没有合适选择情况的高层系统模型中使用层次名
• 避免向上的名字引用 因为这会使代码很难懂 也对调试和维护不利
举例
下面例子是合法的名字 A_99_Z
Reset
_54MHz_Clock$
Module // 与'module'不一样
\$%^&*() // 转义标识符
下面的名字是非法的 原因如下
123a // 第一个字符是数字
$data // 第一个字符是美元符号
module // 是保留字
下面的例子是层次名字和向上的名字引用 module Separate;
parameter P = 5; // Separate.P endmodule
module Top;
reg R; // Top.R Bottom U1();
endmodule
module Bottom;
reg R; // Top.U1.R
task T; // Top.U1.T reg R; // Top.U1.T.R;
...
endtask
initial
begin : InitialBlock
reg R; // Top.U1.InitialBlock.R;
$display(Bottom.R); // 名字向上引用到 Top.U1.R
$display(U1.R); // 名字向上引用到 Top.U1.R ...
end endmodule
线网
线网用于建模结构化描述中的连接 线路和总线 线网的值由线网的驱动器决定 驱动器可以是门
UDP 或模块的实例或者连续赋值的输出
语法 {either}
NetType [ Expansion] [ Range] [ Delay] NetName,...;
trireg [ Expansion] [ Strength] [ Range] [ Delay]
NetName,...;
{用连续赋值进行线网声明}
NetType [ Expansion] [ Strength] [ Range] [ Delay]
NetAssign,...;
NetAssign = NetName = Expression NetType = {either}
wire tri {equivalent}
wor trior {equivalent}
wand triand {equivalent}
tri0 tri1 supply0 supply1
Expansion = {either} vectored scalared
Range = [ ConstantExpression: ConstantExpression]
在何处使用
module-<HERE>-endmodule
规则
• 线网supply0 和 supply1 的值分别是 0 和 1 强度都属于电源
• 当线网tri0 和 tri1 不被驱动时 它们的值分别是 0 和 1 强度属于上拉
• 如果使用关键字vectored 那么位和部分选择以及强度说明都不允许出现 PLI 认为线网是 不 可扩展的 如果使用关键字scalared 允许出现位和部分选择 PLI 认为线网是 可扩展的 建议使用这两个关键字
• 除了结构化声明中的端口和标量导线 线网必须在使用前声明
真值表
下面的真值表显示了当线网有两个或多个驱动器时 假设每个驱动器的强度值相等 如何解决其冲突
否则 驱动器用最大的驱动强度值驱动线网
wire
tri 0 1 X Z
0 0 X X 0 1 X 1 X 1 X X X X X Z 0 1 X Z
wand
triand 0 1 X Z wor
trior 0 1 X Z
0 0 0 0 0 0 1 1 X 0 1 0 1 X 1 1 1 1 1 1 X 0 X X X X X 1 X X Z 0 X X Z Z 0 1 X Z
tri0 0 1 X Z tri1 0 1 X Z 0 0 X X 0 0 0 X X 0 1 X 1 X 1 1 X 1 X 1 X X X X X X X X X X Z 0 1 X 0 Z 0 1 X 1
注意
• 当tri0 和 tri1 线网不被驱动时 向它们连续赋值强度到不会影响线网的值和强度 此时 强度一 般是上拉 逻辑值是0 tri0 或 1 tri1
• IEEE 标准和 Cadence 事实标准的可选扩展保留字 scalared 或 vectored 的位置不一样 Cadence 事实标准的保留字后面紧跟着范围
合并
• 线网被合并到连接 但这些可以被优化掉
• 除了wire 外 合并工具不支持其他线网类型
提示
• 甚至在建立隐式声明时 也要在每个模块的顶部明确声明所有线网 这样通过明确意图 增加了
Verilog 代码的可读性和可维护性
• 只用supply0 和 supply1 声明地和电源线网
举例 wire Clock;
wire [7:0] Address;
tri1 [31:0] Data, Bus;
trireg (large) C1, C2;
wire f = a && b,
g = a || b; // 连续赋值
数字
一个整数或实数数字 整数在Verilog 中用位表示 其中有些位可能是未知的 X 或高阻 Z
语法
{either} BinaryNumber OctalNumber DecimalNumber HexNumber RealNumber
BinaryNumber = [ Size] BinaryBase BinaryDigit...
OctalNumber = [ Size] OctalBase OctalDigit...
DecimalNumber = {either}
[ Sign] Digit... {有符号数}
[ Size] DecimalBase Digit...
HexNumber = [ Size] HexBase HexDigit...
RealNumber = {either}
[ Sign] Digit... .Digit...
[ Sign] Digit...[. Digit...]e[ Sign] Digit...
[ Sign] Digit...[. Digit...]E[ Sign] Digit...
BinaryBase = {either} 'b 'B OctalBase = {either} 'o 'O DecimalBase = {either} 'd 'D HexBase = {either} 'h 'H Size = Digit...
Sign = {either} + -
Digit = {either} _ 0 1 2 3 4 5 6 7 8 9 BinaryDigit = {either} _ x X z Z ? 0 1
OctalDigit = {either} _ x X z Z ? 0 1 2 3 4 5 6 7 HexDigit = {either} _ x X z Z ? 0 1 2 3 4 5 6 7 8 9 a A
b B c C d D e E f F UnsignedNumber = Digit...
在何处使用 见表达式
规则
• 基字符 十六进制数字 X 和 Z 在数字中是大小写不敏感的
• 数字中字符Z 和?意义相同
• 数字不包含空格 但基的两边允许有空格
• 负数用2 的补码表示
• 数字的第一个字符不允许是下划线 这是一个有效的标识符 下划线可以提高可读性 但在读
出的时候被忽略
• Size 表示实际的位数
• 没有说明size 的数字默认是 32 位或更多位 由器件决定
• 如果size 比数字的位数大 数字将在左边填补 0 如果最左边的一位是 X 或 Z 就在左边填补 X
或Z
• 如果size 比数字的位数小 数字将被从左边删节
注意
• 一个有大小的负数在赋值到寄存器时不带符号扩展 reg [7:0] byte;
reg [3:0] nibble;
initial begin
nibble = -1; // 即 4'b1111
byte = nibble; // 字节变为 8'b0000_1111 end
• 在表达式中使用寄存器或有大小的数字 它的值一般被解释为无符号数 integer i;
initial
i = -8'd12 / 3; // i 变为 81 (即 8'b11110100 / 3)
合并
• 0 和 1 分别被合并作为到地和电源的连接
• 赋值X 被解释为 无关 与X 比较的结果是假 FALSE 除了casex 语句 全等运算符===
和!==一般不合并
• 除了在casex 和 casez 语句中 Z 表示 无关 外 Z 还用于表示三态驱动器
提示
• 在case 语句的标记中 优先使用?而不是 Z 请不要在其他地方使用? 因为可能会引起混淆
• 在长的数字中加入下划线使其更有可读性
举例
-253 // 带符号的十进制数
'Haf // 无大小的十进制数
6'o67 // 一个 6 位的 8 进值数
8'bx // 一个 8 位的未知数字(8'bxxxx_xxxx) 4'bz1 // 除了最低位外所有位都是 Z (4'bzzz1)
下面的数字是非法的 原因如下
_23 // 第一个字符是_
8' HF F // 有两个非法的空格
0ae // 带十六进制数字符的十进制数
x // 是一个名字 不是一个数字(应用 1'bx)
.17 // 应当是 0.17
运算符
运算符用于从表达式的数字 参数和其他子表达式等操作数产生值 Verilog 中的运算符和 C 编程语言 的相似
一元运算符
+ - 符号
! 逻辑非
~ 位取反
& ~& | ~| ^ ~^ ^~ 归约 (~^ 和 ^~ 相等) 二进制运算符
+ - * / 算术
% 取模
> >= < <= 比较
&& || 逻辑
== != 逻辑等
=== !== 全等
& | ^ ^~ ~^ 按位操作(^~ 和 ~^相等)
<< >> 移位 其他运算符
A ? B : C 条件
{ A, B, C } 级连
{ N{A} } 复制
在何处使用 见表达式
规则
• 逻辑运算符将它们的操作数看作是布尔量 非零的操作数被解释为真 1’b1 零被解释为假
1’b0 不明确的值 即应是真或者假 例如4’bXX00 是未知的 1’bx
• 按位运算符 ~ & | ^ ^~ ~^ 和全等运算符 === !== 将操作数的各个位独立处理
• 如果操作数的某一位是X 或 Z ==或!=的逻辑比较值是未知 1’bX 见注意
• 如果用 < > <= >= 运算符的比较不明确 比较的结果是未知的 1’bX 例如 2'b10 > 1'b0X // 真(1'b1)
2'b11 > 1'b1X // 未知(1'bX) 见注意
• 归约运算符 & ~& | ~| ^ ~^ ^~ 将矢量简化为标量值
• 有大小的表达式的算术运算是四舍五入的 因此 例如4'b1111 + 4'b0001 的结果是 4'b0000
• 整数除法将删除结果的小数部分
• 模 % 是第一个操作数被第二个操作数除所得到的余数 结果取第一个操作数的符号
• 实数表达式只允许出现某些运算符 一元的+和-以及算术 关系 逻辑 等号和条件运算符 在 实数中使用逻辑或关系运算符的结果得到一个位值
运算符的优先级
• + - ! ~ (一元) – 最高优先级
• * / %
• + - (二元)
• << >>
• < <= > >=
• == != === !==
• & ~&
• ^ ^~
• | ~|
• &&
• ||
• ?: - 最低优先级
注意
• 并不是所有仿真器都遵从用== != < > <= >=进行未知和不明确比较的规则 请注意
• 请注意一元归约运算符和按位逻辑运算符的差别 其意义由上下文和包含特殊解释的方括号给
出
合并
• 逻辑 按位和移位运算符被作为逻辑运算符合并
• 条件运算符被作为多路复用器或三态使能端来合并
• 运算符+ - * < <= > >= == !=被分别作为加法器 减法器 乘法器和比较器合并
• 运算符/和%只作为移位或在常数表达式中合并 例如 /2 表示右移
• 所有工具都不合并其他运算符
提示
用括号而不是运算符优先级来组成表达式 这样能防止错误而且使不是很懂Verilog 语言的人可以理解 你的表达式
举例
-16'd10 // 一个表达式 不是一个带符号数!
a + b x % y
Reset && !Enable // 和 Reset && (!Enable)一样 a && b || c && d // 和(a && b) || (c && d)一样
~4'b1101 // 结果是 4'b0010
&8'hff // 结果是 1'b1 (所有位都是 1)
参数
参数是向常数值给定名字的一种方法 当设计被编译时 不是仿真时 参数的值可以被覆盖 因此可
以将总线宽度等参数化
语法
parameter Name = ConstantExpression,
Name = ConstantExpression, ... ;
{一些工具支持下面的非标准语法}
parameter [ Range] Name = ConstantExpression, Name = ConstantExpression, ... ;
Range = [ ConstantExpression: ConstantExpression ]
在何处使用
module-<HERE>-endmodule begin : Label-<HERE>-end fork : Label-<HERE>-join task-<HERE>-endtask function-<HERE>-endfunction
规则
参数是常量 在仿真时修改它们的值是非法的 但在编译时用 defparam 命令或者当模块包含的参数 被实例化时 参数值可以改变
合并
一些合并工具可以像 模板 一样处理参数化的模块 也就是说模块一旦被读入后可以用不同的参数
值合并几次 所有合并工具都能合并包含不被覆盖的参数的模块实例
提示
请给参数指定在字面上有意义的名字
举例
这是一个N 位的参数化移位寄存器例子 不同的移位器实例有不同的宽度 module Shifter (Clock, In, Out, Load, Data);
parameter NBits = 8;
input Clock, In, Load;
input [NBits-1:0] Data;
output Out;
always @(posedge Clock) if (Load)
ShiftReg <= Data;
else
ShiftReg <= {ShiftReg[NBits-2:0], In}
assign Out = ShiftReg[NBits-1];
endmodule
module TestShifter;
...
defparam U2.NBits = 10;
Shifter #(16) U1 (...); // 16 位的移位寄存器 Shifter U2 (...) // 10 位的移位寄存器 endmodule
PATHPULSE$
这是一个在特殊模块中控制脉冲传播的specparam 脉冲 pluse 是模块输出的两个预定的转换 在 比从模块到输出的延时还要短的时间内产生
默认地 仿真器拒绝接受脉冲 这意味着只有比通过模块的延时长的转换才被传播 这个作用被称为
惯性延时 PATHPULSE$ specparam 允许修改这个默认的特性
语法 {either}
PATHPULSE$ = ( Limit[, Limit]); {(拒绝,错误)}
PATHPULSE$Input$Output = ( Limit[, Limit]);
Limit = ConstantMinTypMaxExpression
在何处使用
specify-<HERE>-endspecify
规则
• 如果没有给定错误界限 它将被设置为和拒绝界限一样
• 比拒绝界限短的脉冲不能传播到输出
• 比拒绝界限长但比错误界限短的脉冲作为1’bX 传播
• 比错误界限长的脉冲将被如常传播
• 对于从 输入 到 输出 的延时 PATHPULSE$input$output specparam 覆盖了相同模块中的 普通PATHPULSE$ specparam
合并
合并工具忽略延时结构 包括指定的块
举例 specify
(clk => q) = 1.2;
(rst => q) = 0.8;
specparam PATHPULSE$clk$q = (0.5,1), PATHPULSE = (0.5);
endspecify