概述
设计(也就是设计描述)存放在设计文件中。所有的设计文件名字不能重复,如果设计是层次化的,也就是每一个子设计都会引用个设计文件,那么这个设计文件的名字也必须是唯一的。注意,不同的设计文件可以包含相同名字的子设计。
本章包含以下部分:
管理设计数据
为综合建立分区
适合综合的HDL代码编写
管理设计数据
使用系统的组织方法来管理设计数据。设计数据控制和数据组织是管理设计数据的两条个基本组成部分。
控制设计数据
当你的设计版本更新时,你必须维护一些存档和存续的记录方法,这种记录方法提供了设计更新的历史记录并且可在数据丢失的时候可以让你重启设计过程。建立数据的创建,维护,重写和删除的控制是设计管理的基本问题。建立文件名的约定是数据创建的最重要规则之一。表3-1列出了建议每种设计数据的文件拓展名。
表3-1 文件拓展名
管理设计数据
建立和坚持一种组织数据的方法比你对方法的选择更重要。在你将必要的设计数据按照一致的控制规则存放后,你就可以制定一套有意义的数据管理方式。为了简化数据交换和数据搜索,设计者应该坚持一种数据组织的方法。
你可以用一套层次化的目录结构来解决数据组织问题。你的编译策略会影响你的目录结构。图3-1展示了基于自顶向下的编译策略的目录结构,图3-2展示了由下而上的编译策略的目录结构。更多关于编译策略的细节,请参考“选择和使用编译策略”一节的内容。
图3-1 自顶向下的编译策略目录结构
图3-1 由下而上的编译策略目录结构
分块综合
对设计进行有效的分块可以增强综合结果,减少编译时间,并且可以简化约束和脚本文件。
分块会影响块尺寸,虽然Design Compiler没有固定的块尺寸但是你应该小心控制块的尺寸。如果你的块太小,可能会阻碍对边界的优化。如果你的块尺寸太大,那么编译时间可能会增加。
使用以下的的策略来对你的设计进行分块并且改善优化结果和运行时间:
以设计复用为目标的分块
将相关的组合逻辑放在一个块中
寄存块的输出
按照设计目标分块
按照编译技巧分块
将共享资源放置在一起
保持用户自定义的资源和它们所驱动的逻辑不变
将特殊功能隔离,譬如pad,时钟,边界扫描和异步逻辑
下面的章节描述了上面的每种策略
以设计复用为目标的分块
设计复用通过减少设计,集成和测试工作来缩短芯片的上市时间。
当设计中有复用时。对设计进行分块这样可以通过例化设计来实现复用。
为了使设计能够最大程度复用,请遵守以下的分块和块设计准则:
仔细定义并记录设计接口
尽可能对接口标准化
HDL代码参数化
将相关组合逻辑放置在一起
在默认情况下,Design Compiler无法跨越层次边界移动逻辑。将相关的组合逻辑划分到不同的块中会引入人为的障碍从而阻碍逻辑的优化。
为了获得最好的结果,请采用以下策略:
将相关的组合逻辑和其目标寄存器放置在一起
当处理完整的组合逻辑时,Design Compiler可以合并逻辑,这样可以获得更小,更快的设计。将组合逻辑和其目标寄存器放在一起也可以简化时序约束也可以启用时序逻辑优化。
消除胶合逻辑
胶合逻辑是连接块之间的组合逻辑。将这些逻辑移动到一个块中会优化综合结果,因为这样会为Design Compiler提供更多的灵活性。消除胶合逻辑也会减少编译时间,因为Design需要优化的逻辑层数会减少。
举个例子,假设你的设计包含了在关键路径上或者很靠近关键路径的三个组合逻辑云。图3-3显示了较差的设计分块。每一朵组合逻辑云都在不同的块中,所以Design Compiler无法尽全力的使用它的组合逻辑优化技巧。
图3-3 较差的相关逻辑分区
图3-4 显示了同样的设计,但是没有了人为的边界。在这个设计中,Design Compiler有很大的灵活性来将组合逻辑云中相关的功能合在一起。
图3-4 相关逻辑在一个块中
块输出寄存
确保寄存器驱动块输出(如3-5所示),可以简化约束。
图3-5 寄存所有输出
这种方法可以让你对每个块的约束更加容易,因为
一个块输入端口的驱动强度一般等于平均输入的驱动强度
来自上一个模块的input delay一般等于通过寄存器的路径延时
因为所有输出都寄存时,是没有纯组合路径的,所以时序预算和使用set_output_delay命令会更容易。假设每个模块的时钟相同,这种约束就比较简单而且每个模块的约束都是一样的。这种分块方法可以提高仿真性能。随着所有的输出被寄存,一个模块就可以被看成一个边沿触发的过程。敏感列表包含了时钟,有时候可能也会包含复位引脚。一个有限的敏感列表因为只会在每次的时钟周期才触发一次进程所以可以加速仿真。
通过设计目标来分块
以不同的设计目标来将逻辑划分到不同的块。当确定的某部分设计比他部分的时间和面积都更关键就可以使用这种方法。
将非关键速度约束的逻辑和关键约束速度的逻辑隔离开可以获得最好的综合结果。通过隔离非关键逻辑,你可以在块上应用譬如最大面积约束之类的不同约束。
图3-6显示了如何隔离不同设计目标的逻辑
图3-6 不同约束的块
通过编译技术来分块
将需要不同的编译技术的逻辑划分为不同的块。当设计约束包含高度结构化的逻辑和随机逻辑时,使用这种方法。
如错误检查电路(通常包含了大量的异或树)一般高度结构化的逻辑会更加适合结构化。
随机逻辑更适合扁平化。
更多关于结构化和扁平化的信息,请参考“逻辑级优化”。
将共享资源放置到一起
Design Compiler可以共享大的资源,譬如加法器或者乘法器,但是资源的共享只有在资源属于同一个VHDL 进程或者Verilog always块是才会发生。
举个例子,两个独立的加法器有相同的目标路径而且输出是通过多路复用到该路径,将这两个加法器放在同一个VHDL过程或者同一个verilog always块中。如果约束中允许共享,那么这种方法就可以使Design Compiler共享资源(使用一个加法器而不是两个)。图3-7显示了示例逻辑的可能实现方法。
图3-7 将共享资源放置在同一个进程
更多关于资源共享的信息,请参考HDL Compiler的文档。
保持自定义的资源和所驱动的逻辑不变
用户自定义的资源是指用户自定义的功能,过程或者宏单元或者用户创建的DesignWare部件。Design Compiler无法自动共享或创建多个用户自定义资源的实例。在时序目标无法以单个实例达到的情况下,将这些资源和它们驱动的逻辑保持住(不对其进行优化和改变)会给你通过手动插入多个实例来分割负载的灵活性。
图3-8描述了在信号PARITY_ERR的负载太大导致无法满足约束的时候通过多个实例分割负载的情况。
图3-8 复制用户自定义资源
隔离特殊的功能
将核心逻辑和特殊功能(譬如I/O pad,时钟生成电路)隔离。图3-9显示了推荐的顶层分块方法。
图3-9 推荐的顶层分块
设计的顶层包含了I/O pad环和处于中间层的边界扫描逻辑子模块,时钟生成电路,异步逻辑和核心逻辑。中间层的存在给I/O的例化提供了灵活性。时钟生成电路的隔离使得这个模块能够被实例化和谨慎的仿真。异步逻辑的隔离有助于将可测试性问题和静态时序分析问题限制在一个小范围内。
HDL编码
HDL编码是综合的基础,因为它指定了设计的初始结构。当编写HDL源代码是,需要考虑代码的硬件含义。好的代码风格可以让设计更小更快。本章节提供了帮助你编写有效代码的信息,可以帮助你在最短的可能时间内达到你的设计目标。
本章包含了如下的话题
编写工艺无关的HDL
使用HDL结构
编写高效的代码
编写工艺无关的HDL
使用全自动综合的高阶设计的目标是没有实例化门和触发器。如果你能满足这个目标,那么你就可以编写出可读的,简洁的和方便的高阶HDL代码,这些代码可以流转到其他供应商或者接下来的流程。
在某些情况,HDL Compiler工具需要提供具体实现信息的编译指令同时保持工艺无关性。在Verilog中的编译命令以//或者/*为开头。在VHDL中,编译命令以两个连字符—开头,后跟pragma或者synopsys。更多信息,请参考HDL Compiler文档。
后面的部分讨论了大量保持HDL代码工艺独立性的方法。
部件的推断
HDL Compiler有推断下列部件的能力:
多路复用器
寄存器
三态驱动
多bit部件
这种推导功能会在接下来的章节讨论。其他的信息和例子,请参考HDL Compiler文档。
推断多路复用器
HDL Compiler可以通过HDL代码中的case语句推断通用多路复用单元(MUX_OP)。如果你的目标工艺库包含至少一个2到1的多路复用单元,Design Compiler会将推断出的MUX_OP映射到多路复用单元上。Design Compiler设计约束的基础上决定了MUX_OP在编译过程中的实现方式。更多关于Design Compiler如何将MUX_OP映射到多路复用器上的信息,请参考Design Compiler Optimization Reference Manual。
使用编译指令infer_mux来控制多路复用器的推测。当该命令作用于一个块时,它会使块中所有的case语句都被推测为多路复用器。
推断寄存器
寄存器推测允许你在设计中指定与工艺无关的时序逻辑。寄存器是一个简单的单bit存储器件,它是一个锁存器或者一个触发器。所存器是电平敏感的存储设备,触发器是边沿触发的存储设备。
HDL编译器会在未给所有条件下的输出指定值时推断出D锁存器,譬如不完整的if或者case语句都是这种情况。HDL编译器也推断SR锁存器和主从锁存器。
HDL编译器会在verilog always块或者VHDL过程的敏感列表中包含边沿表达式(检测一个信号的上升或者下降沿)时推断出D触发器。HDL编译器也会推断出JK触发器和翻转触发器。
混合寄存器类型
为了能得到最好的结果,需要严格限制每一个Verilog always块或者VHDL过程限制为单一类型的寄存器推断:锁存器,带异步置位或者复位的锁存器,触发器,带异步置位或者复位的触发器或者带同步置位或者复位的触发器。
当在你的设计总出现了混合上升沿和下降沿触发的触发器,一定要小心。如果一个模块推测出同时含有上升沿河下降沿触发的触发器但是工艺库中没有下降沿触发的触发器,Design compiler会在时钟树中为下降沿时钟生成一个反相器。
推测无控制信号的寄存器
为了推测出无控制条件的寄存器,让数据和时钟pin可被输入port或通过组合逻辑控制。如果一个门级仿真器无法通过输入port或者经过组合逻辑控制数据或者时钟pin,该仿真器就无法初始化电路,并且仿真会失败。
推测带控制信号的寄存器
你可以通过使用同步或异步控制信号来初始化或者控制触发器的状态。
为了推测出带异步控制信号的锁存器,使用async_set_reset编译命令(VHDL的属性)来找出异步控制信号。当推测触发器时,HDL编译器会自动是被异步控制信号。
为了推断出同步复位,使用sync_set_reset编译命令(VHDL的属性)来找出同步控制信号。
推测三态驱动
为输出端口赋高阻态值(verilog中是1’bz,在VHDL中是’Z’)会让Design Compiler推断出三态门。三态门会降低设计的可靠性并且会让debug变得难。如果有可能,用多路复用器代替三态buffer。
不要在条件表达式中使用高阻态值。HDL 编译器总是将和高阻态值相关的表达式判断为false,这会导致门级实现和RTL描述不一致。
更多关于三态推测的相关信息,请参考HDL编译器文档。
推测多bit部件
多bit推测允许你将多路复用器,寄存器和三态驱动映射到规则结构的逻辑或者多bit库单元。使用多bit部件可以得到以下结果:
由于共享晶体管和优化的晶体管级layout可以得到更小的面积和延时。
减少时序门的clock skew
在时序部件中的时钟下获得更低的功耗
数据路径的改进的规则layout
多bit部件在下面的实例中可能效率不会高:
作为状态机寄存器
得益于单bit设计的小总线逻辑
在决定是否映射到多bit或单bit部件时,你必须衡量多bit部件的好处和优化灵活性损失。
infer_multibit编译命令附加到总线信号来推测多bit部件。你可以通过使用creat_multibit和remove_multibit命令来在优化后在单bit和多bit的实现中进行更改。
更多关于Design Compiler如何处理多bit部件的信息,请参考Design Compiler Optimization Reference Manual。
设计状态机
你可以通过不同的方式来指定一个状态机:
Verilog
VHDL
状态表
PLA
如果你在设计中使用state_vector和enum编译命令,Design Compiler可以从网网表中提取状态表。在状态表的格式下,Design Compiler不会保持casex,casez和并行case的信息。Design Compiler不会优化无效的输入组合和互斥的输入。
图3-10显示了一个有限状态机的结构。
图3-10 有限状态机的结构
使用提取的状态表提供一下好处:
可以进行状态最小化。
可以在不同的编码格式中权衡。
可以在不打平设计的前提下使用不关心的条件。
不关心的状态码可以被自动推导。
关于提取状态机和改变编码风格的信息,请参考Design Compiler Optimization Reference Manual。
使用HDL结构
下面的章节提供了以下特定HDL结构的信息和准则:
生成HDL结构
Verilog宏定义
VHDL端口定义
生成HDL结构
本章的内容同时适用于verilog和VHDL。
敏感列表
你应该为每个verilog always块和VHDL进程提供完备的敏感列表。不完整的敏感列表(如下面的例子)可能导致HDL和门级设计的仿真结果不匹配。
例3-1 不完整的敏感列表(verilog)
例3-1 不完整的敏感列表(verilog)
赋值
Verilog和VHDL都支持在RTL 代码适用立即赋值和延时赋值。由立即赋值产生的硬件—通过verilog的阻塞赋值(=)和VHDL的变量(:=)实现----和赋值表达式的顺序相关的。由延时赋值产生的硬件—通过verilog的非阻塞赋值(<=)和VHDL 信号(<=)实现—和赋值表达式的顺序无关。
为了得到正确的仿真结果,
在时序verilog always块或者VHDL的过程中使用延时赋值(非阻塞)
在组合verilog always块或者VHDL过程中使用立即赋值(阻塞)
If语句
如果在verilog的always块或者VHDL过程中使用if语句作为连续赋值的一部分却没有包含else语句,那么Design Compiler会创建一个锁存器。下面的例子显示了会在综合过程中产生锁存器的if语句。
例3-3 不正确的if语句(verilog)
例3-3 不正确的if语句(VHDL)
case语句
如果if语句包含超过三个条件,那么可以考虑使用case语句来提升你设计的并行性和代码的清晰度。下面的例子使用case语句来实现3bit的解码器。
例3-5 使用case语句(verilo g)
例3-6 使用case语句(VHDL)
不完整的case语句会导致锁存器的产生。VHDL不支持不完整的case语句。在verilog中你可以通过使用defalut语句或者全case编译指令来避免推测出锁存器。
虽然全case指令和default语句会组织锁存器推测,但是他们有不同的意义。全case指令断言了所有有效的输入值都已被指定并且default语句不是必须的。Default语句为未定义的输入值指定了输出。
为了达到最好的结果,使用default语句而不是full_case指令。如果为指定的输入值时不关心的条件,使用输出x的default语句可以产生更小的实现面积。
使用全case指令,如果case表达式计算遇到未指定的输入值,门级仿真和RTL级仿真可能不匹配。如果使用default语句,仿真的不匹配只会发生在你指定了不关心条件并且case表达式计算遇到为指定的输入值。
常数定义
使用verilog define语句或者VHDL常数语句来定义全局常数。在一个单独的文件中保存全局常数定义。使用parameter(verilog)或者generics(VHDL)来定义局部常数。 例3-7显示了包含全局
define语句和本地参数的verilog代码段。例3-8显示了包含全局常数和本地generic的VHDL代码段。
例3-7 使用宏和参数(verilog)
使用宏的时候应该是name,所以例子中的第四行应该是 reg regfile[
WIDHT-1:0]
例3-8 使用全局常数和generics(VHDL)
使用verilog宏定义
在verilog中,宏是通过使用define语句来实现的。使用
define语句时请遵守以下准则:
只用define来声明常量。 在一个独立的文件中保存
define语句。
不要使用嵌套的define语句 读取一个嵌套超过两层的宏是非常困难的。为了让你的代码可读性较高,不要使用嵌套的
define语句。
不要在模块定义内使用define语句。 当你在模块定义内使用
define语句时,局部宏和全局宏可能会有同样的引用名但是他们的值却不相同。定义局部常量请使用参数。
使用VHDL端口定义
当在VHDL源代码内定义端口时,请注意一下准则:
使用STD_LOGIC和STD_LOGIC_VECTOR包。
通过使用STD_LOGIV,你可以在综合过程中避免使用类转换功能。
不要使用buffer端口模式。
当你将一个端口定义成buffer时,这个端口在整个层次中都必须作为buffer。为了简化综合,将port声明为output,然后定义一个内部信号来驱动这个输出端口。
编写高效率的代码
本章为编写高效率的可读性强的用于综合的HDL源代码提供了指南。这些指南包括
标志符
表达式
功能
模块
标志符指南
一个号的标志符名字会传达出信号的意义,变量的值或者模块的功能;没有这些信息,硬件描述是很难读懂的。
遵守以下命名规则可以提高你的HDL源代码的可读性:
保证信号名能简明扼要的传达出信号的意义或者变量的值。
举个例子,假设你有一个表示rs1浮点操作码的变量。一个简单的譬如rs1的名字,不会像读者传达出任何意义。一个长名字,譬如floating_pt_opcode_rs1,传达了一定的意思,但是它太长可能导致源代码难以阅读。使用例如fpop_rs1这样的名字,就可以满足不太长又能达意的目的。
使用一致的大写命名规则来区分名字中独立的单词。
常用的风格包括C,Pascal和Modula。
C风格使用小写的名字,通过下划线来区分单词,例如,packet_addr,data_in和first_grant_enable。
Pascal风格将名字的第一个字母大写而且会将后面的单词开头的字母也大写,例如PacketAddr,DataIn和FirstGrantEnable。
Modulation风格将名字的首字母小写,但是会将接下的单词的第一个字母大写,例如packetAddr,dataIn和firstGrantEnable。
避免使用易混淆的字符。
一些字符(字母和数字)看起来比较像,所以比较容易混淆。例如,O和0(zero);l和1(one)。
避免使用保留字
使用名词或者名词后接动词的命名方式,例如AddrDecode,DataGrant,PCI_inerrupt.
增加后缀以使名字含义更加清晰
表3-2显示了常用后缀及其含义
表3-2 信号名后缀及其含义
表达式指南
遵守以下表达式指南:
使用圆括号来指明优先级。
表达式操作符的优先级规则让人很容易混乱,所以你应该使用括号来使你的表达式更容易被理解。除非你使用DesignWare资源,否则括号对生成的逻辑几乎没有影响。下面是以一个没有括号的表达式,这样的表达式难以阅读
将重复的表达式替换成功能调用或者连续赋值
如果你使用一个特定的表达式超过两次或者三次,那么可以考虑将这样的表达式替换为函数或者一个连续赋值。
函数指南
遵守以下的函数指南:
不要在函数中使用全局引用。
在程序代码中,函数在被调用时开始运行。在连续赋值中,只要函数的任何输入改变,该函数就会运行。
避免在函数中使用对非本地名的引用,因为在非本地值改变时,函数可能不会重新运行。这可能导致HDL描述和门级网表在仿真上的不匹配。
例如,下面的verilog 函数引用了非本地名字 byte_sel:
注意,任务和函数的本地存储是静态的。
形式参数,输出和局部变量会在函数返回后保持他们当前的值。每次调用函数时都会重用本地存储。在debug时,这些存储或许会有用,但是存储重用也就意味着函数和任务无法递归的调用。
使用组件含义时要小心。
你可以将函数通过map_to_module和return_port_name这两个编译器命令映射到特定的部件上。仿真会使用函数的这些内容。综合使用门级模块替代函数。当你使用部件含义时,RTL模块和门级模块可能会有差异。因此,设计无法被完全的验证除非在门级设计上进行仿真。
以下的功能可能需要部件实例或者函数含义:
用于节省功耗的时钟门控电路
含有潜在冒险的异步逻辑
该功能包括了在特定状态下有效的异步逻辑和异步信号
数据路径电路
该功能包含了大型多路复用器;实例化宽组的多路复用器;譬如RAM或者ROM的存储单元;和黑盒子宏单元。
更多关于部件含义的更多信息,请参考HDL编译器文档。
模块指南
遵守以下模块指南
当通过端口传值时避免使用逻辑表达式。
端口列表可以包含表达式,但是表达式会使得debug变得困难。此外,隔离与位字段相关的问题是比较困难的,特别是如果该位字段导致内部端口数量与外部端口数量不同。
将局部引用定义为generics(VHDL)或者参数(verilog)。不要将generics或参数传递到模块。
最后
以上就是爱听歌大神为你收集整理的DC学习-第三章的全部内容,希望文章能够帮你解决DC学习-第三章所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复