Microsoft Word - 文档 1

Size: px
Start display at page:

Download "Microsoft Word - 文档 1"

Transcription

1 第 1 章集成电路设计中的基本概念 本章简要介绍了现代集成电路设计的基本概念, 包括集成电路设计方法分类 集成电路设计流程 集成电路设计的表示方法 传统与现代的集成电路设计的比较以及 VHDL 在电子系统硬件设计中的优点, 使读者对现代集成电路的设计有一个框架性的了解, 为以后章节的学习打下基础 1.1 集成电路设计方法分类 正向设计与反向设计 集成电路的设计方法从功能和实现的先后顺序上分, 可以分为正向 (Forward) 设计和反向 (Backward) 设计 所谓正向设计就是由设计者或用户提出一个功能要求, 然后通过综合得到最终的半导体实现 所谓反向设计就是对已有的一个半导体实现, 通过分析得到它的结构和功能, 在此基础上进行模仿或修改, 实现类似的电路功能 这两种设计方法如图 1-1 所示 随着集成电路的发展, 反向设计方法的应用领域越来越小 这里面主要有两个原因 : 第一, 现代电子产品的应用范围越来越广, 它们要求的 ASIC 的功能也越来越多样化 专门化 电子系统的开发者往往不能从已有的芯片产品中找到符合自己特殊功能及性能要求的专用集成电路, 所以必须从所开发电子系统的要求出发, 提出功能要求, 进行专用芯片的正向设计 第二, 随着集成电路制造工艺的发展, 芯片规模越来越大, 集成度越来越高, 一块芯片的规模往往达到几百万甚至上千万门, 对于这些大规模 高集成度的芯片进行版图分析非常困难, 需要耗费巨大的成本和时间, 这对产品开发极为不利 另外, 现在集成电路产品加强了保密措施, 这就使得反向设计几乎成为不可能 与此同时, 正向设计方法得到了越来越广泛的研究和应用 正向设计方法中的关键技术是综合技术, 正向设计的发展主要依赖于包括高层次综合 逻辑综合 版图综合在内的各个层次的综合方法和工具的发展, 而高层次综合是这些综合技术中的首要环节 自顶向下的设计和自底向上的设计 集成电路的设计方法从整体和局部的先后顺序上分, 可以分为自顶向下 (Top-down) 的设计和自底向上 (Bottom-up) 的设计 所谓自顶向下的设计, 就是设计者首先从整体上规划整个系统的功能和性能, 然后对系统进行划分, 分解为规模较小 功能较为简单的局部模块, 并确立它们之间的相互关系, 这种划分过程可以不断地进行下去, 直到划分得到的单元可以映射到物理实现 所谓自底向上的设计, 就是设计者首先选择具体的逻辑单元, 1

2 进行逻辑电路设计, 得到系统需要的独立的功能模块, 然后再把这些模块连接起来, 组装成整个系统 这两种设计方法如图 1-2 所示 正向设计 反向设计 行为设计 版图分析 算法设计 电路提取 结构设计 功能分析 逻辑设计 模仿修改 电路设计 逻辑设计 版图设计 电路设计 版图设计 图 1-1 正向设计和反向设计 自顶向下 (Top-down) 自底向上 (Bottom-up) 用系统级行为描述表达一个包含输入输出的顶层模块, 同时完成整个系统的模拟与性能分析 由基本门组成各个组合与时序逻辑单元 将系统划分为各个功能模块, 每个模块由更细化的行为描述表达 由逻辑单元组成各个独立的功能模块 由各个功能模块连成一个完整系统 由 EDA 综合工具完成到工艺的映射 进行整个系统的测试与性能分析 图 1-2 自顶向下与自底向上的设计方法自底向上的设计方法是从传统的手工设计发展而来的 在进行手工电路设计时, 一个硬件系统的实现过程是从选择具体的元器件开始的 当从手工设计转变为 CAD 设计时, 2

3 为电路设计开发的 CAD 软件也是按照这种设计流程建立起来的 CAD 设计与手工设计过程的区别只是在 CAD 设计中, 由底层逻辑库中调用逻辑门单元的过程是借助计算机完成的 这种设计过程的优点是符合硬件设计工程师的传统习惯, 缺点是在进行底层设计时, 缺乏对整个系统总体性能的把握 如果在整个系统完成后发现性能还需改进, 则修改起来就比较困难 随着系统规模与复杂度的提高, 这种设计方法的缺点就越来越突出, 因而逐渐被自顶向下的设计方法所取代 自顶向下的设计方法是随着硬件描述语言 (HDL) 和 EDA 工具同步发展起来的 硬件描述语言可以在各个抽象层次上对电子系统进行描述, 而且借助于 EDA 设计工具, 可以自动实现从高层次到低层次的转换, 这就使得自顶向下的设计过程得以实现 采用自顶向下的设计方法的优点是显而易见的 由于整个设计是从系统顶层开始的, 结合模拟手段, 可以从一开始就掌握所实现系统的性能状况, 结合应用领域的具体要求, 在此时就调整设计方案, 进行性能优化或折衷取舍 随着设计层次向下进行, 系统性能参数将得到进一步的细化与确认, 并随时可以根据需要加以调整, 从而保证了设计结果的正确性, 缩短了设计周期, 设计规模越大, 这种设计方法的优势越明显 自顶向下的设计方法的缺点就是需要先进的 EDA 设计工具和精确的工艺库的支持 1.2 集成电路设计流程 集成电路设计流程的概念和作用 现代集成电路设计多指正向设计 集成电路设计流程就是为实现集成电路从功能定义到半导体实现的整个过程所需要进行的所有工作及其先后次序 集成电路设计流程是集成电路设计方法学中一个重要方面, 它对于设计活动的作用表现在 : 1) 设计流程是规范设计活动的准则, 它使得设计活动在各个阶段有了交流 比较的可能 2) 设计流程规定了工具的选择和使用, 为各种工具之间的接口提供了可能 3) 设计流程规定了设计人员的工作次序与内容, 这使得在同一个设计项目中可以进行多人分工与协作, 从而缩短设计周期 4) 设计流程自身的科学性也保障了所进行的设计的正确性和可靠性 因此一个规范的 科学的集成电路设计流程, 对设计活动具有重要的指导意义, 可以提高设计活动的效率和可靠性, 有利于设计活动的管理和交流 集成电路设计流程是随着集成电路的发展而发展的 在集成电路制造工艺进入到深亚微米阶段后, 传统的集成电路设计流程就不再适应设计深亚微米集成电路, 而被现代的深亚微米集成电路设计流程取代 集成电路设计的一般流程 在通常的集成电路设计中, 其流程大致可以分为 9 步 如图 1-3 所示 3

4 图 1-3 集成电路设计的一般流程图 1-3 所示流程中各步的意义如下 : 1. 行为级描述在完成系统性能分析与功能划分的基础上, 对于各个电路功能模块, 用硬件描述语言完成行为级描述 2. 行为级优化 仿真以及向 RTL 级描述的转化对上一步中完成的描述进行算法优化和功能仿真 算法优化的目标是选择最优的算法实现, 功能仿真的目的是为了验证给定的行为描述是否能够实现所需的功能 在进行行为级优化的同时, 通常还要进行向 RTL 级描述的转化 进行这种转化的原因在于现有的 EDA 工具只能接受 RTL 级描述进行逻辑综合 同样, 对得到的 RTL 级描述, 也要进行功能仿真 3. 选定工艺库, 确定约束条件, 完成逻辑综合与逻辑优化逻辑综合和逻辑优化的目标是将前面得到的 RTL 描述映射到具体的工艺上加以实现 因而从这一步开始, 设计过程就和工艺相关了 自动逻辑综合的前提是要有逻辑综合库的支持, 逻辑综合库内部包含了相应的工艺参数, 如门延迟 单元面积 扇入扇出数等 对不同的工艺, 其综合库中的工艺参数就会不同 4

5 4. 门级仿真门级仿真的目标是为了验证逻辑综合出来的电路的正确性 完成逻辑综合后的门级仿真包含了门单元的延迟信息, 因而门级仿真需要相应工艺的仿真库的支持 5. 测试生成完成逻辑综合后, 可产生相应的网表文件, 但在将设计提交给下一步进行布局布线时, 应当提供相应的测试文件 测试分为功能测试和制造测试两部分 功能测试就是为了检验线路的逻辑 时序是否正确 前面的行为级仿真 RTL 级仿真 门级仿真都属于功能测试的范畴 制造测试是针对半导体工艺而设计的, 目的是检测制造过程中的物理故障, 通常称为测试向量 对复杂电路, 具有高故障覆盖率的测试向量一般要借助于测试综合工具, 通过 ATPG(Automatic Test Pattern Generation) 产生 6. 布局布线对于 IC 设计来说, 这一步是借助版图综合的工具, 在对应工艺的版图库支持下, 完成自动布局布线 对于 FPGA 设计来说, 只需借助 FPGA 提供商提供的专用工具实现 从这一步开始, 设计过程就和半导体物理实现 ( 版图 ) 有关, 通常成为后端设计 7. 参数提取在逻辑综合后的门级电路网表中, 只包含了门单元的工艺参数 当完成版图综合后, 由于各单元的布局布线已经确定, 所以可以进一步提取实际电路中连线电阻 连线电容等分布参数 8. 后仿真这一步的目标就是将上一步提取的分布参数包含于原来的门级网表中, 进行包含门延迟 连线延迟的门级仿真 这一步主要是考察在增加连线延迟后, 时序是否仍然满足设计要求 9. 制版, 流片对于 IC 设计来说, 在完成上面 8 个步骤的设计后, 可交付集成电路制造厂家进行投片生产 对于 FPGA 设计来说, 只需向 FPGA 器件加载代码即可 1.3 集成电路设计的表示方法 一个集成电路设计的表示或描述涉及两方面的问题 : 描述的层次和描述的领域 描述共有 5 个抽象层次 : 系统层 (System) 算法层(Algorithm) 寄存器传输层(Register-transfer) 逻辑层 (Logic) 和电路层 (Circuit) 对每一个层次, 分别有 3 种不同领域的描述 : 行为 (Behavior) 领域描述 结构 (Architecture) 领域描述和物理 (Physics) 领域描述 这些设计的表示方法的内容如表 1-1 所示 需要强调的是往往有这么一种概念, 认为某个描述层次和某个描述领域是等同的 例如, 有人认为系统层只涉及行为领域描述, 逻辑层只涉及结构领域描述 为了概念上更加清楚和全面, 建议使用表 1-1 中所表述的设计层次和描述领域的概念, 这已为大多数人所认可 5

6 描述层次逻辑层布尔方程 ( 组 ) 门 触发器 锁存器门级单元布图 表 1-1 设计的表示方法 描述领域 行为领域描述 结构领域描述 物理领域描述 行为 性能 输入输出的映射关 CPU 存储器 开关网络 控 芯片 模块 电路板以及子 系统层 系 制器以及总线之间的连接 系统的物理划分 算法层 实现行为的算法 硬件模块 数据结构 部件之间的物理连接 寄存器传输层 寄存器传输 状态表 ALU 多路选择器 寄存器 总线 存储器 控制器等 芯片 宏单元等 电路层电路微分方程晶体管 电阻 电容版图 设计的 5 个描述层次的主要含义如下 : 1. 系统层系统层描述主要是针对整个电子系统性能的描述, 是系统最高层次的抽象描述 在这一层次, 目前仍以高级程序语言为主要描述手段, 例如 C 语言 FORTRAN 语言等 2. 算法层算法层描述是在系统级性能分析和结构划分之后, 对每个模块功能行为的描述, 这一层次又称为行为层或功能层 这一层的描述手段多为硬件描述语言 (HDL) 3. 寄存器传输层算法层所描述的功能 行为, 最终要以数字电路来实现, 而数字电路从本质上可以看做是寄存器与组合逻辑两种类型的电路组成的, 寄存器负责信号的存储, 组合逻辑负责信号的传输 寄存器传输层描述就是从信号存储 传输的角度去描述整个系统的 这一层的描述手段多为硬件描述语言 (HDL) 4. 逻辑层寄存器传输层中的寄存器和组合逻辑都是通过各种基本的逻辑门实现的, 例如反相器 与非门 或非门等 逻辑层描述就是从各种逻辑门的组合 连接的角度去描述整个系统的 这一层仍可采用硬件描述语言 (HDL) 作为描述手段 5. 电路层逻辑层中的逻辑门是由晶体管电路构成的 例如, 在 CMOS 电路中, 一个反相器是由一个 PMOS 晶体管和一个 NMOS 晶体管构成的 ; 一个最基本的与非 / 或非门是由两个 PMOS 晶体管和两个 NMOS 晶体管构成的 电路层描述就是从晶体管的组合 连接的角度来描述整个系统的 这一层仍可采用硬件描述语言 (HDL) 作为描述手段, 但由于这一层处于设计描述的底层, 而且这一层的描述一般只需让 EDA 工具 看懂, 硬件描述语言并不能发挥其面向高层的优势 设计的 3 个描述领域的主要含义如下 : 1. 行为领域描述行为领域描述一个设计的基本功能, 或者说所设计的电路应该做什么 从概念上讲, 纯行为是输入和输出映射关系的描述 例如, 布尔方程组就是逻辑网络的行为描述, 它描述逻辑网络输入输出间的关系 6

7 2. 结构领域描述结构领域描述一个设计的逻辑结构, 或者说一个设计的抽象实现, 典型的表达方式就是一些抽象功能模块之间相互连接的网表 例如, 在寄存器传输层, 抽象功能模块是 ALU 多路选择器 寄存器等 3. 物理领域描述物理领域描述一个设计的物理实现, 或者说把结构描述中的抽象元件代之以真正的物理元件 例如, 在寄存器传输层, 物理描述方法描述了实现 ALU 多路选择器 寄存器等功能模块的平面布图 在每一个层次中, 物理描述通常都涉及速度 功耗 面积等方面的约束 无论是描述层次还是描述领域, 它们的边界是可以重叠的 一个设计的描述, 通常在描述层次和描述领域上可以混合表示 例如, 一个典型的寄存器传输层的描述, 通常既包含行为领域描述又包含结构领域描述, 既包含寄存器级元件又包含逻辑级元件 对任何设计, 设计者的目的都是获得其物理实现 ; 或者说, 一个设计最终要被转换成物理领域描述 另一个值得一提的问题是, 一个设计可以在不同的层次进行描述, 但并不是说每一个设计都必须从系统层开始进行 对于有经验的设计者, 在设计所熟悉的电子产品时, 可能更喜欢从寄存器传输层或更低的层次开始着手设计 大多数普通设计人员从更高层次入手, 设计的自由度更大, 更能发挥 EDA 工具的优化能力, 设计出性能更优的产品 以一个系统中的加法器为例, 如果在寄存器传输层进行描述, 就必须由设计者来决定是用并行实现还是用串行实现 假设这个系统要求时间最优, 那么设计者很自然地会选用并行的加法器 如果设计者从行为级开始设计, 只给出加法的行为描述, 而将时间最优作为综合优化的约束条件, 那么综合器首先对整个系统进行分析 如果这个加法器位于系统的关键路径上, 综合器会选用并行实现 ; 如果这个加法器不在系统的关键路径上, 综合器就会选用串行实现, 它们同样都能满足时间最优的条件 前面的寄存器传输层设计, 不管怎样都在设计描述时预先决定了采用并行加法器 ; 而后面这种行为级设计, 可以通过对整个系统进行分析并根据约束条件来选择使用并行或串行加法器, 这样有可能在同样满足时间最优的条件下比前面一种设计更节省面积或成本, 使产品有更好的性能价格比 1.4 传统与现代集成电路设计的比较 传统与现代集成电路设计方法的比较 传统与现代的集成电路在设计方法上的主要区别如表 1-2 所示 表 1-2 传统与现代集成电路设计方法的比较 传统集成电路设计 现代集成电路设计 设计方法类别 自底向上 自顶向下 设计手段 电路原理图 硬件描述语言 系统构成 通用元器件 ASIC 电路 仿真调试 在设计的后期进行 在设计的早期进行 7

8 传统的自底向上的设计方法的主要步骤是 : 根据系统对硬件的要求, 详细编制技术规范书, 并画出系统控制流图 ; 然后, 根据技术规范书和系统控制流图, 对系统的功能进行细化, 合理地划分功能模块, 并画出系统的功能框图 ; 接着进行各功能模块的细化和电路设计 ; 各功能模块电路设计 调试完成后, 将各个功能模块的硬件电路连接起来再进行系统的调试, 最后完成整个系统的硬件设计 现代的自顶向下的设计方法就是从系统的总体要求出发, 自顶向下地将设计内容细化, 最后完成系统硬件的整体设计 在用传统的硬件设计方法所形成的设计文件, 主要是由若干张电原理图构成的文件 在电原理图中详细标注了各逻辑元器件的名称和互相间的信号连接关系 该文件是用户使用和维护系统的依据 对于小系统, 这种电原理图只要几十张至几百张就行了 但是, 如果系统比较大, 硬件比较复杂, 那么这种电原理图可能要有几千张 几万张, 甚至几十万张 如此多的电原理图给归档 阅读 修改和使用都带来了极大的不便 现代集成电路设计中, 主要的设计文件是 HDL 语言编写的源程序 如果需要也可以转换成电原理图形式输出 用 HDL 语言的源程序作为归档文件有很多好处 其一是资料量小, 便于保存 其二是可继承性好 当设计其他硬件电路时, 可以使用文件中的某些库 进程和过程等描述某些局部硬件电路的程序 其三是阅读方便 阅读程序比阅读电原理图要更容易一些, 阅读者很容易在程序中看出某一硬件电路的工作原理和逻辑关系 在传统的硬件电路设计中, 设计者总是根据系统的具体需要, 选择市场上能买到的逻辑元器件, 来构成所要求的逻辑电路, 从而完成系统的硬件设计 随着微处理器的出现, 尽管在由微处理器及其相应硬件构成的系统中, 系统的许多硬件功能可以用软件系统来实现, 从而在较大程度上简化系统硬件电路的设计, 但是, 这种选择通用的元器件来构成系统硬件电路的方法并未改变 在现代的集成电路设计中, 由于目前众多的制造 ASIC 芯片的厂家的工具软件都支持 HDL 语言的编程, 因而, 硬件设计人员在设计硬件电路时, 无须受只能使用通用元器件的限制, 可以根据硬件电路设计需要, 设计自用的 ASIC 芯片或可编程逻辑器件 这样最终会使系统硬件电路设计更趋合理, 体积也可大为缩小 在传统的系统硬件设计中, 仿真和调试通常只能在系统硬件设计的后期才能进行 因为进行仿真和调试的仪器一般为系统仿真器 逻辑分析仪和示波器等, 而这些仪器只有在硬件系统已经构成以后才能使用 系统设计时存在的问题只有在后期才能较容易发现 这样, 传统的硬件设计方法对系统设计人员有较高的要求 一旦考虑不周, 系统设计存在较大缺陷, 那么就有可能要重新设计系统, 使得设计周期大大增加 在现代集成电路设计中, 由于采用自顶向下的设计方法, 在系统设计过程中较高层次进行仿真调试, 从而可以在系统设计早期发现设计中存在的问题 与传统设计的后期仿真相比, 可大大缩短系统的设计周期, 节省设计成本 传统与现代集成电路设计流程的比较 传统的集成电路设计流程指的是集成电路制造工艺处于微米 亚微米时期 (0.35 微米以上 ) 的设计流程, 现代集成电路设计流程指的是当前集成电路制造工艺已经达到深亚微米水平 (0.35 微米以下 ) 后与之相适应的设计流程 集成电路制造的特征尺寸从微米 亚微米水平减小到深亚微米水平后, 为集成电路设 8

9 计带来了两个需要注意的问题 : 一是芯片集成度的提高导致了其功率密度的提高, 不利于电路正常工作 ; 二是在深亚微米水平, 连线引入的延迟与门延迟相当, 不再是可以忽略的因素 以上这两个问题直接影响了现代集成电路设计流程, 使其呈现出与微米 亚微米时期不同的特点 图 1-4 是传统和现代的集成电路设计流程的比较 传统 现代 功能定义 功能定义 电路生成 划分与布局规划 功能验证 电路生成 布局布线 功能验证 后仿真 时序分析 芯片制造 布局布线 后仿真 芯片制造 图 1-4 传统和现代集成电路设计流程比较传统的微米 亚微米集成电路设计流程与现代的深亚微米集成电路设计流程有以下几点不同 : 1) 深亚微米集成电路设计流程中, 在功能定义之后增加了划分与布局规划的工作 在划分与布局规划上, 需要考虑芯片的功能模块划分 功率分布 全局信号的匹配等, 这些都是根据深亚微米集成电路规模大 功率密度高 连线影响大等特点进行的 2) 深亚微米集成电路设计流程中, 在布局布线之前就对电路进行时序分析, 而在微米 亚微米集成电路设计流程中, 时序是在布局布线后的后仿真阶段来进行验证的 在微米 亚微米集成电路中, 连线延迟可以忽略, 电路延迟主要体现为门延迟, 如果对电路的 9

10 功能验证 ( 带门延迟 ) 的结果正确, 那么在布局布线之后, 由于连线对电路影响很小, 电路功能不会有大的改变, 仍然正确 而在深亚微米集成电路中, 即使对电路的功能验证 ( 带门延迟 ) 的结果正确, 当布局布线引入连线后, 由于连线对电路的不可忽略的影响, 有可能导致电路功能的错误 为避免或降低这种情况的可能性, 在深亚微米集成电路设计流程中, 在生成电路后提前对电路时序进行分析, 及时对电路结构进行调整, 争取在布局布线后电路仍具有正确的时序, 保证功能的正确 3) 在两种集成电路设计流程中, 虽然有的部分名称相同, 但它们的内容是不同的 深亚微米集成电路的布局布线要比微米 亚微米集成电路的布局布线更侧重考虑功率和延迟 ; 两种集成电路设计流程中后仿真的互连线模型是不同的 在深亚微米互连线模型中, 互连线对电路参数的影响要比微米 亚微米模型大得多 1.5 VHDL 在电子系统硬件设计中的优点 所谓硬件描述语言, 就是可以描述硬件电路的功能 信号连接关系及定时关系的语言 它能比电原理图更有效地表达硬件电路的特征 目前各 ASIC 芯片制造商都相继开发了用于各自目的的 HDL 语言, 但是大多都未标准化和通用化 惟一已被公认的硬件描述语言之一是美国国防部开发的 VHDL 语言, 它已成为 IEEE STD_1076 标准 许多公司研制的硬件电路设计工具也逐渐向 VHDL 语言靠拢, 提供对 VHDL 的支持 VHDL 在电子系统硬件设计中具有下列优点 : 1. 设计技术齐全 方法灵活 支持广泛 VHDL 语言可以支持自顶至下 (Top-Down) 和基于库 (Library-Based) 的设计方法, 而且还支持同步电路 异步电路 FPGA 以及其他随机电路的设计 其范围之广是其他 HDL 语言所不能比拟的 例如,SFL 语言和 UDL/I 语言, 它们只能描述同步电路 另外,VHDL 语言早在 1987 年 12 月已作为 IEEE_STD_1076 标准公布开发, 目前大多数 EDA 工具几乎在不同程度上都支持 VHDL 语言 这给 VHDL 语言进一步推广和应用创造了良好的环境 2. 系统硬件描述能力强如前所述,VHDL 语言具有多层次描述系统硬件功能的能力, 可以从系统的数学模型直到门级电路 另外, 高层次的行为描述可以与低层次的 RTL 描述和结构描述混合使用 例如, 在 PC 扩展槽上要设计一块接口卡, 该接口卡的硬件设计应满足主机的接口要求 此时, 主机部分功能可以用行为方式描述, 在系统仿真时可以验证接口卡的工作是否正确 这样, 在接口卡设计出来以前就可以知道接口卡的工作是否满足系统要求 VHDL 语言能进行系统级的硬件描述, 这是它最突出的一个优点 其他 HDL 语言, 如 UDL/I Verilog 等只能进行 IC 级 PCB 级描述, 而不能对系统级的硬件很好地进行描述 3.VHDL 语言可以与工艺无关编程在用 VHDL 语言设计系统硬件时, 没有嵌入与工艺有关的信息 当然, 这样的信息是可以用 VHDL 语言来编写的 与大多数 HDL 语言不同, 采用 VHDL 语言设计时, 当门级或门级以上层次的描述通过仿真检验以后, 再用相应的工具将设计映射成不同的工艺 ( 如 10

11 MOS CMOS 等 ) 这样, 在工艺更新时, 无须修改原设计程序, 只要改变相应的映射工具就行了 由此可见, 修改电路和修改工艺相互之间不会产生影响 4.VHDL 语言标准 规范, 易于共享和复用由于 VHDL 语言已作为一种 IEEE 的工业标准, 这样, 设计成果便于复用和交流, 反过来也更进一步推动 VHDL 语言的推广及完善 另外,VHDL 语言的语法比较严格, 其风格类似于 Ada 语言, 给阅读和使用都带来了极大的方便 11

12 第 2 章 VHDL 语言程序基础 一个完整的 VHDL 语言程序通常包含实体 (entity) 构造体(architecture) 配置 (configuration) 包集合(package) 和库 (library)5 个部分 前 4 部分是可分别编译的源设计单元 实体用于描述所设计的系统的外部接口信号 ; 构造体用于描述系统内部的结构和行为 ; 包集合存放各设计模块都能共享的数据类型 常数和子程序等 ; 配置用于从库中选取所需单元来组成系统设计的不同版本 ; 库存放已经编译的实体 构造体 包集合和配置 库可由用户生成或由 ASIC 芯片制造商提供, 以便于在设计中为大家所共享 本章将对上述 VHDL 设计的主要构成做详细介绍 2.1 VHDL 语言程序的结构 VHDL 语言程序设计的基本单元及其构成 所谓 VHDL 语言程序设计的基本单元 (design entity), 就是 VHDL 语言的一个基本设计实体 一个基本设计单元, 简单点的可以是一个与门 (and gate), 复杂点的可以是一个微处理器或一个系统 但是, 不管是简单的还是复杂的数字电路, 其基本构成是一致的 它们都由实体说明 (entity declaration) 和构造体 (architecture body) 两部分构成 如前所述, 实体说明部分规定了设计单元的输入输出接口信号或引脚, 构造体部分定义了设计单元的具体构造和操作 ( 行为 ) 例 2-1 示出了作为一个设计单元的 2 选 1 电路的 VHDL 描述 由例 2-1 可以看出, 实体说明是 2 选 1 器件外部引脚的定义 ; 而构造体则描述了 2 选 1 器件的逻辑电路和逻辑关系 下面以 2 选 1 器件描述为例, 说明这两部分的具体书写规定 例 2-1 entity mux is generic (m:time:=1 ns); port(d0,d1,sel:in bit; q:out bit); end mux; architecture counnect of mux is signal tmp:bit; cale:process(d0,d1,sel) variable tmp1,tmp2,tmp3:bit; 12

13 tmp1:=d0 and sel; tmp2:=d1 and (not sel); tmp3:=tmp1 or tmp2; tmp<=tmp3; q<=tmp after m; end process; end connect; 实体任何一个基本设计单元的实体说明都具有如下的结构 : entity 实体名 is [ 类属参数说明 ]; [ 端口说明 ]; end 实体名 ; 一个基本设计单元的实体说明以 entity 实体名 is 开始, 至 end 实体名 结束 例如, 在例 2-1 中从 entity mux is 开始, 至 end mux 结束 对 VHDL 而言, 大写或小写都一视同仁, 不加区分 本书一律采用小写 1. 类属参数说明类属参数说明必须放在端口说明之前, 用于指定参数 例如, 例 2-1 中的语句 generic (m:time:=1ns) 该语句指定了构造体内 m 的值为 1ns, 而语句 tmp1:=d0 and sel after m; 表示 d0 和 sel 两个输入信号相与后, 经 1ns 延迟才送到 tmp1 在此例中,generic 利用类属参数为 tmp1 建立一个延迟时间值 2. 端口说明端口说明是对基本设计实体 ( 单元 ) 与外部接口的描述, 也可以说是对外部引脚信号的名称 数据类型和输入输出方向的描述 其一般书写格式如下 : port( 端口名 {, 端口名 }: 方向数据类型名 ; 端口名 {, 端口名 }: 方向数据类型名 ); (1) 端口名端口名是赋予每个外部引脚的名称, 通常用一个或多个英文字母, 或者用英文字母加数字命名之 如例 2-1 中的外部引脚为 d0 d1 sel 和 q (2) 端口方向端口方向用来定义外部引脚的信号方向是输入还是输出 例如, 例 2-1 中的 d0,d1, sel 为输入引脚, 故用方向说明符 in 说明之, 而 q 则为输出引脚, 用方向说明符 out 说明之 凡是用 in 进行方向说明的端口, 其信号自端口输入到构造体, 而构造体内部的信号不能从该端口输出 相反, 凡是用 out 进行方向说明的端口, 其信号将从构造体内经 13

14 端口输出, 而不能通过该端口向构造体输入信号 另外, inout 用以说明该端口是双向的, 可以输入也可以输出 ; buffer 用以说明该端口可以输出信号, 且在构造体内部也可以利用该端口输出信号 ; linkage 用以说明该端口无指定方向, 可以与任何方向的信号相连接 当一个构造体用 buffer 说明输出端口时, 与其连接的另一个构造体的端口也要用 buffer 说明 对于 out 则没有这样的要求 (3) 数据类型在 VHDL 语言中有 10 种数据类型, 但是在逻辑电路设计中只用到两种 :bit 和 bit_vector 当端口被说明为数据类型时, 该端口的信号取值只可能是 '1' 或 '0' 注意, 这里的 '1' 和 '0' 是指逻辑值 因此,bit 数据类型是位逻辑数据类型, 其取值只能是两个逻辑值 ('1' 和 '0') 中的一个 当端口被说明为 bit_vector 数据类型时, 该端口的取值可能是一组二进制位的值 例如, 某一数据总线输出端口, 具有 8 位的总线宽度, 那么这样的总线端口的数据类型可以被说明成 bit_vector, 总线端口上的值由 8 位二进制位的值所确定 较完整的端口说明如例 2-2 所示 例 2-2 library IEEE; use ieee.std_logic_1164.all; port(d0,d1,sel:in bit; q:out bit; bus:out bit_vector(7 downto 0); 例 2-2 中 d0,d1,sel,q 都是 bit 数据类型, 而 bus 是 bit_vector 数据类型,(7 downto 0) 表示该 bus 端口是一个 8 位端口, 由 b7~b0 8 位构成 位矢量长度为 8 位 在某些 VHDL 语言的程序中, 数据类型的说明符号有所不同 仍以例 2-2 为例进行说明 例 2-2 中 bit 类型可用 std_logic 说明, 而 bus 则可用 std_logic_vector(7 downto 0) 说明 上述两种描述实际上是完全等效的 在 VHDL 语言中存在一个库, 该库有一个包集合, 专门对数据类型进行说明, 其作用像 C 语言中的 include 文件一样 这样做主要为了标准和统一 因此, 在用 std_logic 和 std_logic_vector 说明时, 在实体说明以前必须增加例 2-2 中所示的前两个语句, 以便在对 VHDL 语言程序编译时, 从指定库的包集合中寻找数据类型的定义 构造体 构造体是一个基本设计单元的实体, 它具体地指明了该基本设计单元的行为 元件及内部的连接关系, 也就是说定义了设计单元具体的功能 构造体对基本设计单元具体的输入输出关系可以用 3 种方式进行描述, 即行为描述 ( 基本设计单元的数学模型描述 ) 寄存器传输描述 ( 数据流描述 ) 和结构描述 ( 逻辑元器件连接描述 ) 不同的描述方式, 只体现在描述语句上, 而构造体的结构是完全一样的 由于构造体是对实体功能的具体描述, 因此它一定要跟在实体的后面 通常, 编译实体之后才能对构造体进行编译 如果实体需要重新编译, 那么相应构造体也应重新进行编 14

15 译 一个构造体的具体结构描述如下 : architecture 构造体名 of 实体名 is [ 定义语句 ] 内部信号 常数 数据类型 函数等的定义 ; [ 并行处理语句 ]; end 构造体名 ; 一个构造体从 architecture 构造体名 of 实体名 is 开始, 至 end 构造体名 结束 下面对构造体的有关内容和书写方法做一说明 1. 构造体名称的命名构造体的名称是对本构造体的命名, 它是该构造体的惟一名称 of 后面紧跟的实体名表明了该构造体所对应的是哪一个实体 用 is 来结束构造体的命名 构造体的名称可以由设计者自由命名 但是在大多数的文献和资料中, 通常把构造体的名称命名为 behavioral( 行为 ),dataflow( 数据流 ) 或者 structural( 结构 ) 如前所述, 这 3 个名称实际上是 3 种构造体描述方式的名称 当设计者采用某一种描述方式来描述构造体时, 该构造体就以其描述方式来命名 这样, 使阅读 VHDL 语言程序的人能直接了解设计者所采用的描述方式 例如, 使用结构描述方式来描述 2 选 1 电路, 那么 2 选 1 电路的构造体就可以命名为 : architecture structural of mux is 2. 定义语句定义语句位于 architecture 和 之间, 用于对构造体内部所使用的信号 常数 数据类型和函数进行定义 例如 : architectuer behav of mux is signal nes1:bit; end behav; 信号定义和端口说明的语句一样, 应有信号名和数据类型的说明 因是内部连接用的信号, 故没有也不需要有方向说明 3. 并行处理语句并行处理语句位于语句 和 end 之间, 这些语句具体地描述了构造体的行为 例如,2 选 1 的数据流方式描述如例 2-3 所示 例 2-3 entity mux is port (d0,d1:in bit; sel:in bit; q:out bit); end mux; architecture dataflow of mux is 15

16 q<=(d0 and sel)or(not sel and d1); end dataflow; 在该程序的构造体中所使用的语句, 实际上是 2 选 1 电路的逻辑表达式的描述语句 它正确地反映了 2 选器件的行为 这种语句和其他高级语言是相当类似的, 读者只要具有一点基本的语言知识就可以读懂 在语句中, 符号 <= 表示传送 ( 或代入 ) 的意思, 即将逻辑运算结果送 q 输出 另外, 在构造体中的语句都是可以并行执行的, 也就是说, 语句的执行不以书写的语句顺序为准 配置 配置 (configuration) 语句描述了层与层之间的连接关系, 以及实体与构造体之间的连接关系 设计者可以利用配置语句来选择不同的构造体, 使其与要设计的实体相对应 在仿真某一个实体时, 可以利用配置选择不同的构造体进行性能对比试验, 以得到性能最佳的构造体 例如, 要设计一个 2 输入 4 输出的译码器 假设一种结构中的基本元件采用反相器和 3 输入与门, 另一种结构中的基本元件都采用与非门, 它们各自的构造体是不一样的, 并且放在各自不同的库中, 那么要设计的译码器, 就可以利用配置语句实现对两种不同的构造体的选择 配置语句的基本书写格式如下 : configuration 配置名 of 实体名 is [ 语句说明 ]; end 配置名 ; 配置语句根据不同情况, 其说明语句有简有繁, 下面举几个例子做一些说明 最简单的缺省配置格式为 : configuration 配置名 of 实体名 is for 选配构造体名 end for; end 配置名 ; 例 2-4 library std; use std.std_logic.all; entity counter is port(load,clear,clk:in std_logic; data_in:in integer; data_out:out integer); end counter; architecture count_255 of counter is 16

17 process(clk) variable count:integer:=0; if clear='1'then count:=0; elsif load='1'then count:=data_in; elsif (clk'event)and(clk='1')and(clk'last_value='0') then if (count=255)then count:=0; else count:=count+1; end if; end if; data_out<=count; end process; end count_255; architecture count_64k of counter is process(clk) variable count:integer:=0; if (clear='1')then count:=0; elsif load='1'then count:=data_in; elsif (clk'event)and(clk='1')and(clk'last_value='0') then if (count=65535) then count:=0; else count:=count+1; end if; end if; data_out<=count; end process; end count_64k; configuration small_count of counter is for count_255 17

18 end for; end small_count; configuration big_count of counter is for count_64k end for; end big_count; 在例 2-4 中, 一个计数器实体可以实现两个不同构造体的配置 需要注意的是, 为达到这个目的, 在计数器实体中, 对装入计数器和构成计数器的数据位宽度不应做具体说明, 只将输入和输出数据作为 integer( 整型 ) 数据来对待 这样可以支持多种形式的计数器 ( 如例 2-4 中的 8 位计数器和 16 位计数器 ), 以便在宿主机上方便地进行仿真 下面再讲一个构造体内含有元件的配置, 仍是设计一个 2 输入 4 输出的译码器, 译码器由反相器和 3 输入与门构成 例 2-5 反相器和 3 输入与门子电路描述 library std; use std.std_logic.all; use std.std_ttl.all; entity inv is port(a:in std_logic; b:out std_logic); end inv; architecture behave of inv is b<=not(a) after 5 ns; end behave; configuration invcon of inv is for behave end for; end invcon; use std.std_logic.all; use std.std_ttl.all; entity and3 is port(a1,a2,a3:in std_logic; ol:out std_logic); end and3; architecture behave of and3 is ol<=al and a2 and a3 after 5 ns; 18

19 end behave; configuration and3con of and3 is for behave end for; end and3con; 下面就是用反相器和 3 输入与门构成译码器的程序实例, 例中使用了 component 语句和 port map( ) 语句 ( 这两个语句的含义在后面章节中详述 ) 例 2-6 构成译码器的程序 library std; use std.std_logic.all; entity decode is port(a,b,en:in std_logic; q0,q1,q2,q3:out std_logic); end decode; architecture structural of decode is component inv port(a:in std_logic; b:out std_logic); end component; component and3 port(a1,a2,a3:in std_logic; ol:out std_logic); end component; signal nota,notb:std_logic; i1:inv port map(a,nota); i2:inv port map(b,notb); a1:and3 port map(a,en,notb,q0); a2:and3 port map(a,en,notb,q1); a3:and3 port map(nota,en,b,q2); a4:and3 port map(a,en,b,q3); end structural; 根据上面对反相器和 3 输入与门的结构描述, 以及译码器结构的描述, 可以利用不同 19

20 层次的连接关系实现不同的译码器的配置 例 2-7 选择低层次配置 configuration decode_llcon of decode is for structural for i1:inv use configuratior work.invcon end for; for all:and3 use configuration work.and3con end for; end for; end decode_llcon; 例 2-8 实体与构造体相对应的配置 configuration decode_eacon of decode is for structural for i1:inv use entity work.inv(behave) end for; for others:inv use entity work.inv(behave) end for; for a1:and3 use entity work.and3(behave) end for; for others:and3 use entity work.and3(behave) end for; end structural; end decode_eacon; 上面例 2-4 至例 2-8 是配置的几个实际应用例子, 内部的语句也有多种多样的书写格式, 但是不管怎样变, 基本格式和实现的功能是完全相同的 VHDL 语言构造体的基本子结构 在规模较大的电路设计中, 全部电路都用惟一的一个模块来描述是非常不方便的 为此, 电路设计者总希望将整个电路分成若干个相对比较独立的模块来进行电路的描述 这样, 一个构造体可以用几个子结构, 即相对比较独立的几个模块来构成 VHDL 语言可以有以下 3 种形式的子结构描述语句 : block 语句结构 ; process 语句结构 ; subprograms 结构 下面就上述 3 种子结构逐一说明 块 (block) 语句子结构 1.block 语句的结构 20

21 采用 block 语句描述局部电路的书写格式如下 : 块结构名 : block end block 块结构名 ; 采用 block 语句来描述 2 选 1 电路的程序如例 2-9 所示 : 例 2-9 entity mux is port(d0,d1,sel:in bit; q:out bit); end mux: architecture connect of mux is signal tmp1,tmp2,tmp3:bit; cale: block tmp1<=d0 and sel; tmp2<=d1 and(not sel); tmp3<=tmp1 or tmp2; q<=tmp3; end block cale; end connect; 上述程序的构造体中只有一个 block 块, 如果电路较复杂时可以由几个 block 块组成 2.block 块和子原理图的关系人们在用计算机电路辅助设计工具输入电原理图时, 往往将一个大规模的电原理图分割成多张子原理图, 进行输入和存档 同样, 在 VHDL 语言中也不例外, 电路的构造体对应整个电原理图, 而构造体可以由多个 block 块构成, 每一个 block 块对应一张子原理图 这样, 电原理图的分割关系和 VHDL 语言程序中用 block 分割构造体的关系是一一对应的 在用其他高级语言编程时, 总希望程序模块小一点, 以利于编程和查错, 也利于实现积木化结构 同理, 在 VHDL 语言中采用 block 语句, 对编程 查错 仿真及再利用都会带来莫大的好处 3.block 中语句的并发性在对程序进行仿真时,block 语句中所描述的各个语句是可以并行执行的, 它和书写顺序无关 在 VHDL 语言中将可以并行执行的语句称为并发语句 (concurrent statement) 当然, 在构造体内直接书写的语句也是并发的 另外, 在 VHDL 语言中也存在只能顺序执行的语句, 这一点将在后面介绍 4. 卫式 block(guarded block) 21

22 一般地, 使用 block 语句, 仅仅是将构造体划分成几个独立的程序模块, 和执行控制没有直接关系 如前所述, 在系统仿真时,block 语句将被无条件地执行 但是, 在实际电路设计中, 往往会碰到这样的情况 : 当某一条件得到满足时,block 语句才可以被执行 ; 条件不满足时, 该 block 语句将不能执行 这就是卫式 block, 它可以实现 block 的执行控制 例如, 用 block 语句来描述一个锁存器的结构 该锁存器是一个 D 触发器, 具有一个数据输入端 D, 时钟输入端 clk, 输出端 q 和反相输出端 qb 众所周知, 只有 clk 有效时 ( 即 clk= 1 ), 输出端 q 和 qb 才会随输入数据变化而变化 此时, 可用卫式 block 语句描述该锁存器结构, 如例 2-10 所示 : 例 2-10 entity latch is port(d,clk:in bit; q,qb:out bit); end latch; architecture latch_guard of latch is g1: block(clk='1') q<=guarded d after 5 ns; qb<=guarded not(d) after 7 ns; end block g1; end latch_guard; 如上述程序所示, 卫式 block 语句的格式为 : block[ 卫式布尔表达式 ] 当卫式布尔表达式为真时 ( 例中 clk ='1' 时为真 ), 该 block 语句被启动执行 ; 而当卫式布尔表达式为假时, 该 block 语句将不被执行 在 block 块中的两个信号传送语句都写有前卫关键词 guarded, 表明只有卫式布尔表达式为真时, 这两个语句才被执行 现在根据程序, 描述一下锁存器的工作过程 : 当端口 clk 的值为 '1' 时, 卫式布尔表达式为真,D 端的输入值经 5 ns 延迟以后从 q 端输出 ; 然后对 D 端的值取反, 经 7 ns 后从 qb 端输出 ; 当端口 clk 的值为 '0' 时,D 端到 q qb 端的信号传递通道被切断,q 端和 qb 端的输出保持原状, 不随 D 端值的变化而变化 进程 (process) 语句子结构 1.process 语句的结构采用 process 语句描述电路结构的书写格式为 : 22

23 [ 进程名 ]:process( 信号 1, 信号 2, ) end process; 其中, 进程名可以省略 process 语句从 process 开始, 至 end process 结束 执行 process 语句时, 通常带有若干个信号量, 它们将在 process 结构的语句中被使用 用 process 语句结构描述的程序如例 2-11 所示 : 例 2-11 entity mux is port(d0,d1,sel:in bit; q:out bit); end mux; architecture connect of mux is cale:process(d0,d1,sel) variable tmp1,tmp2,tmp3:bit; tmp1:=d0 and sel; tmp2:=d1 and (not sel); tmp3:=tmp1 or tmp2; q<=tmp3; end process; end connect; 程序中 tmp1 tmp2 tmp3 是变量, 变量是在进程中定义的, 详细说明后面再述 2. 进程 (process) 中语句的顺序性在 VHDL 语言中, 在系统仿真时,process 结构中的语句是按书写顺序一条一条向下执行的, 而不像 block 中的语句可以并行执行 这一点与单处理机上执行 C 语言和 Pascal 语言的语句是完全一样的 在后面还会讲到, 在 VHDL 语言中, 这种顺序执行的语句只在 process 和 subprograms 的结构中使用 3.process 的启动在 process 语句中总是带有一个或几个信号量 这些信号量是 process 的输入信号, 在书写时跟在 process 后面的括号中 例如 process(d0,d1,sel), 其中 d0 d1 sel 都是信号量, 在 VHDL 语言中也称敏感量 这些信号无论哪一个发生变化 ( 如由 '0' 变 '1' 或由 '1' 变 '0') 都将启动该 process 语句 一旦启动以后,process 中的语句将从上至下逐句执行一遍 当最后一个语句执行完毕以后, 就返回到开始的 process 语句, 等待下一次变化的出现 因此, 只要 process 中指定的信号变化一次, 该 process 语句就会执行一遍 4. 进程 (process) 的同步描述在上例用 process 结构描述的 2 选 1 电路的程序中, 构造体内部只有一个 process 但在实际的程序设计中, 同一个构造体中可以有多个进程存在, 而且各 process 之间还可 23

24 以一边通过接口信号进行通信, 一边并行地同步执行 子程序 (subprogram) 语句子结构 所谓子程序就是在主程序调用它以后能将处理结果返回主程序的程序模块, 其含义和其他高级语言中的子程序概念相当 它可以反复调用, 使用非常方便 在调用时, 子程序首先要进行初始化, 执行结束后子程序就终止 ; 再调用时要再进行初始化 因此, 子程序内部的值不能保持 子程序返回以后才能被再调用, 它是一个非重入的程序 在 VHDL 语言中子程序有两种类型 : 过程 (procedure) 函数 (function) 其中, 过程 与其他高级语言中的子程序相当 ; 而 函数 与其他高级语言中的函数相当 1. 过程语句 (1) 过程语句的结构在 VHDL 语言中, 过程语句的书写格式如下 : procedure 过程名 ( 参数 1; 参数 2; )is [ 定义语句 ]; ( 变量等的定义 ) [ 顺序处理语句 ]; ( 过程的语句 ) end 过程名 ; 在 procedure 结构中, 参数可以是输入也可以是输出 也就是说, 过程中的输入输出参数都应列在紧跟过程名的括号内 例如, 在 VHDL 语言中, 将位矢量转换为整数的程序可以由一个过程语句来实现 例 2-12 procedure vector_to_int (z:in std_logic_vector; x_flag:out boolean; q:inout integer)is q:=0; x_flag:=false; for i in z'range loop q:=q*2; if (z(i)=1) then q:=q+1; elsif (z(i) /= 0) then x_flag:=true; exit;

25 end if; end loop; end vector_to_int; 该过程调用后, 如果 x_flag=false, 则说明转换失败, 不能得到正确的转换整数值 在上例中,z 是输入,x_flag 是输出,q 为输入输出 在没有特别指定的情况下, in 作为常数, 而 out 和 inout 则看做 变量 进行拷贝 当过程的语句执行结束以后, 在过程内所传递的输出和输入输出参数值, 将拷贝到调用者的信号或变量中 此时, 输入输出作为信号使用, 因此在过程参数定义时要指明是信号 例 2-13 procedure shift(din:in std_logic_vector; signal dout:out std_logic_vector) is; end shift; (2) 过程结构中语句的顺序性前面已经提到,process 结构中的语句是顺序执行的, 在 procedure 结构中的语句也是顺序执行的 调用者在调用过程前应先将初始值传递给过程的输入参数, 然后过程语句启动, 按顺序自上至下执行过程结构中的语句 执行结束, 将输出值拷贝到调用者的 out 和 inout 所定义的变量或信号中 2. 函数语句 (1) 函数语句的结构在 VHDL 语言中, 函数语句的书写格式为 : function 函数名 ( 参数 1; 参数 2; ) return 数据类型 is [ 定义语句 ]; [ 顺序处理语句 ]; return[ 返回变量名 ]; end[ 函数名 ]; 在 VHDL 语言中,function 语句中括号内的所有参数都是输入参数或称输入信号 因此, 在括号内指定端口方向的 in 可以省略 function 的输入值由调用者拷贝到输入参数中, 如果没有特别指定, 在 function 语句中按常数处理 通常各种功能的 function 语句的程序都集中在包集合 (package) 中 例 2-14 library IEEE; use IEEE.std_logic_1164.all; package bpac is function max(a:std_logic_vector, b:std_logic_vector) 25

26 26 return std_logic_vector; end bpac; package body bpac is function max(a:std_logic_vector; b:std_logic_vector) return std_logic_vector is variable tmp:std_logic_vector(a'range); if (a>b) then tmp:=a; else tmp:=b; end if; return tmp; end max; end bpac; (2) 函数调用及结果的返回在 VHDL 语言中, 函数语句可以在构造体的语句中直接调用 例 2-15 是用 function 语句描述最大检出的程序 例 2-15 library IEEE.newlib; use IEEE.std_logic_1164.all; use newlib.bpac.all; entity peakdetect is port(data:in std_logic_vector(5 downto 0); clk,set:in std_logic; dataout:out std_logic_vector(5 downto 0)); end peakdetect; architecture rtl of peakdetect is signal peak:std_logic_vector(5 downto 0); dataout<=peak; process(clk) if (clk'event and clk='1') then if (set='1') then peak<=data; else peak<=max(data,peak); end if; end if;

27 end process; end rtl; 在上述程序中,peak<=max(data,peak) 就是调用 function 的语句 在包集合中的参数 a 和 b, 在这里用 data 和 peak 替代, 函数的返回值 tmp 被赋予 peak 在 max(a, b) 函数的定义中, 返回值 tmp 可以赋予信号或者变量, 在本例中被赋予信号 peak 上面详细叙述了子程序中过程 函数的结构和使用方法 为了能重复使用这些过程和函数, 这些程序通常组织在包集合 库中 它们与包集合和库具有这样的关系, 即多个过程和函数汇集在一起构成包集合 (package), 而几个包集合汇集在一起就形成一个库 (library) 有关包集合和库的详细内容将在下一节中介绍 但是, 需要指出的是, 不同公司发布的包集合和库的登记方法是各不相同的 VHDL 的设计资源 除了实体和构造体之外, 包集合 库和配置是 VHDL 语言中另外 3 个可以各自独立进行编译的源设计单元 设计库库 (library) 是经编译后的数据的集合, 用于存放包集合定义 实体定义 构造定义和配置定义 库的功能类似于 UNIX 和 MS-DOS 操作系统中的目录, 库中存放设计的数据 在 VHDL 语言中, 库的说明放在设计单元的最前面, 即 library 库名 ; 这样, 在设计单元内的语句就可以使用库中的数据 由此可见, 库的优点就在于可以使设计者共享已经编译过的设计结果 在 VHDL 语言中可以同时存在多个不同的库, 但是库和库之间是独立的, 不能互相嵌套 1. 库的种类当前, 在 VHDL 语言中存在的库大致可以归纳为 5 种 :IEEE 库 std 库 ASIC 矢量库 work 库和用户定义库 (1)IEEE 库在 IEEE 库中有一个 std_logic_1164 的包集合, 它是 IEEE 正式认可的标准包集合 现在有些公司, 如 synopspsys 公司也提供一些包集合 std_logic_arith std_logic_ unsigned, 尽管它们没有得到 IEEE 的承认, 但是仍汇集在 IEEE 库中 (2)std 库 std 库是 VHDL 的标准库, 在库中存放有称为 standard 中的数据可以不按标准格式说明 std 库中还包含有称为 textio 的包集合 在使用 textio 包集合中的数据时, 应先说明库和包集合名, 然后才可使用该包集合中的数据 例如 : library std; use std.textio.all; 27

28 (3)ASIC 矢量库在 VHDL 语言中, 为了进行门级仿真, 各公司可提供面向 ASIC 的逻辑门库 在该库中存放着与逻辑门一一对应的实体 为了使用面向 ASIC 的库, 对库进行说明是必要的 (4)work 库 work 库是现行作业库 设计者所描述的 VHDL 语句不需要任何说明, 将都存放在 work 库中 在使用该库时无需进行任何说明 (5) 用户定义库用户为自己设计需要所开发的公用包集合和实体等, 也可以汇集在一起定义成一个库, 称为用户定义库或用户库 在使用时同样要首先说明库名 2. 库的使用 (1) 库的说明前面提到的 5 类库除 work 库和 std 库之外, 其他 3 类库在使用前都首先要做说明, 第一条语句是 library 库名, 表明使用什么库 另外, 还要说明设计者要使用的是库中哪一个包集合以及包集合中的项目 ( 如过程名 函数名等 ), 这样第 2 条语句的格式为 : 28 use library name. package name. Item name; 所以, 一般在使用库时首先要用两条语句对库进行说明 例如 : library IEEE; use IEEE std_logic_1164.all; 上述表明, 在该 VHDL 语言程序中要使用 IEEE 库中 std_logic_1164 包集合的所有项目 这里, 项目名为 all, 表示包集合中的所有项目都要用 (2) 库说明作用范围库说明语句的作用范围是从一个实体说明开始到它所属的构造体 配置为止 当一个源程序中出现两个以上的实体时, 两条作为使用的库的说明语句应在每个实体说明语句前重复书写 例 2-16 library IEEE; use IEEE.std_logic_1164.all; entity andl is Μ end andl; archtecture rtl of andl is Μ end rtl; configuration sl of andl is Μ end s1; library IEEE; use IEEE.std_logic_1164.all;

29 entity orl is Μ Μ configuration s2 of orl is Μ end s2; 包集合 包集合 (package) 说明像 C 语言中的 include 语句一样, 用来单纯地罗列 VHDL 语言中所要用到的信号定义 常数定义 数据类型 元件语句 函数定义和过程定义等, 是一个可编译的设计单元, 也是库结构中的一个层次 要使用包集合时可以用 use 语句说明 例如 : use IEEE.std_logic_1164.all; 该语句表示在 VHDL 程序中要使用名为 std_logic_1164 的包集合中的所有定义或说明项 包集合的结构如下所示 : package 包集合名 is [ 说明语句 ]; 包集合标题 end 包集合名 ; package body 包集合名 is [ 说明语句 ]; 包集合体 end body; 一个包集合由两大部分组成 : 包集合标题 (header) 和包集合体 (package body) 包集合体是一个可选项, 也就是说, 包集合可以只由包集合标题构成 一般包集合标题列出所有项的名称, 而包集合体给出各项的细节 例 2-17 library std; use std.std_logic.all; package math is type tw16 is array(0 to 15) of std_logic; function add(a,b:in tw16) return tw16; function sub(a,b:in tw16) return tw16; end math; package body math is function vect_to_int (s:tw16) return integer is variable result:integer:=0; for i in 0 to 15 loop result:=result*2; 29

30 30 if s(i)= '1' then result:=result+1; end loop; return result; end vect_to_int; function int_to_tw16(s:integer) return tw16 is variable result:tw16; variable digit:integer:=2**15; variable local:integer; local:=s; for i in 15 downto 0 if local/digit>=1 then result(i):=1; local:=local-digit; else result(i):=0; end if; digit:=digit/2; end loop return result; end int_to_tw16; function add(a,b:in tw16) return tw16 is variable result:integer; result:=vect_to_int(a)+vect_to_int(b); return int_to_tw16(result); end add; function sub(a,b:in tw16) return tw16 is variable result:integer; result:=vect_to_in(a) - vect_to_int(b); return int_to_tw16(result); end sub; end math; 例 2-17 中的包集合由包集合标题和包集合体两部分组成 在包集合标题中, 定义了数据类型和函数的调用说明, 而在包集合体中才具体地描述了实现该函数功能的语句和数

31 据的赋值 这种分开描述的好处是, 当函数的功能需要做某些调整或数据赋值需要变化时, 只要改变包集合体的相关语句就行了, 而无须改变包集合标题的说明, 这样可以使重新编译的单元数目尽可能少 包集合也可以只有一个包集合标题说明, 因为在包集合标题中也允许使用数据赋值和有实质性的操作语句 例 2-18 library IEEE; use IEEE std_logic_1164.all; package upac is constant k:integer:=4; type instruction is(and,sub,adc,inc,srf,slf); subtype cpu_bus is std_logic_vector(k-1 downto 0); end upac; 例 2-18 中的包集合是用户自定义的 在该包集合中定义了 CPU 指令 (instruction) 这一数据类型和 cpu_bus 为一个 4 位的位矢量 由于它是用户自己定义的, 因此编译以后就会自动地加到 work 库中 如要使用该包集合, 则可用如下格式调用 : use work.upac.instruction; 2.2 VHDL 程序的描述方法 VHDL 语言像其他高级语言一样, 具有多种数据类型, 且对大多数数据类型的定义, 彼此是一致的 但也有某些区别, 如 VHDL 语言中可以由用户自己定义数据类型, 这一点在其他高级语言中是做不到的 读者在阅读时请多加注意 VHDL 的数据类型与运算符 VHDL 语言的数据类型如前所述, 在 VHDL 语言中信号 变量 常数都要指定数据类型 为此,VHDL 提供了多种标准的数据类型 另外, 为使用户设计方便, 还可以由用户自定义数据类型 这样使语言的描述能力及自由度更进一步提高, 从而为系统高层次的仿真提供了必要手段 然而,VHDL 语言的数据类型的定义相当严格, 不同类型之间的数据不能直接代入 ; 而且, 即使数据类型相同, 但位长不同时也不能直接代入 因此, 为了熟练地使用 VHDL 语言编写程序, 读者必须很好地理解各种数据类型的定义 1. 标准的数据类型标准的数据类型共有 10 种, 如表 2-1 所示 下面对各数据类型做简要说明 (1) 整数 (integer) 整数与数学中整数的定义相同 在 VHDL 语言中, 整数的表示范围为

32 ~ , 即从 -(2 31-1) 到 (2 31-1) 千万不要把一个实数( 含小数点的数 ) 赋予一个整数的变量, 因为 VHDL 是一个强类型语言, 它要求在赋值语句中的数据类型必须匹配 整数的例子如下 : 表 2-1 标准数据类型 数据类型含义整数整数 32 位, 实数 位 位矢量 浮点数,-1.0e e+38 逻辑 '0' 或 '1' 位矢量 布尔量逻辑 假 或 真 字符时间错误等级自然数, 正整数字符串 ASCII 字符时间单位 fs,ps,ns,µs,ms,sec,min,hr note,warning,error,failure 整数的子集字符矢量 +136, ,-457 尽管整数值在电子系统中可能使用一系列二进制位值来表示, 但是整数不能看做位矢量, 不能按位来进行访问, 也不能用逻辑操作符 当需要进行位操作时, 可以用转换函数, 将整数转换成位矢量 目前, 在有的 CAD 厂商所提供的工具中, 对此规定已有所突破, 允许对有符号和无符号的整型量进行算术逻辑运算 在电子系统的开发过程中, 整数也可以作为对信号总线状态的一种抽象手段, 用来准确地表示总线的某一种状态 (2) 实数 (real) 在进行算法研究或实验时, 作为对硬件方案的抽象手段, 常常采用实数四则运算 实数的定义值范围为 -1.0e+38~+1.0e+38 实数有正负数, 书写时一定要有小数点 例如 : -1.0,+2.5,-1.0e38 有些数可以用整数表示也可以用实数表示 例如, 数字 1 的整数表示为 1, 而实数表示则为 1.0 两个数的值是一样的, 但数据类型却不一样 (3) 位 (bit) 在数字系统中, 信号值通常用一个位来表示 位值的表示方法是, 用字符 '0' 或者 '1'( 将值放在引号中 ) 表示之 位与整数中的 1 和 0 不同,'1' 和 '0' 仅仅表示一个位的两种取值 例如 : bit('1') 位数据可以用来描述数字系统中总线的值 位数据不同于布尔数据, 但也可以用转换函数进行转换 (4) 位矢量 (bit_vector) 位矢量是用双引号括起来的一组数据 例如 : ,x 00bb 32

33 在这里, 位矢量最前面的 x 表示是十六进制 用位矢量数据表示总线状态最形象也最方便, 在以后的 VHDL 程序中将会经常遇到 (5) 布尔量 (boolean) 一个布尔量具有两种状态, 真 或者 假 虽然布尔量也是二值枚举量, 但它和位不同, 没有数值的含义, 也不能进行算术运算 它能进行关系运算 例如, 它可以在 if 语句中被测试, 测试结果产生一个布尔量 true 或者 false 一个布尔量常用来表示信号的状态或者总线上的情况 如果某个信号或者变量被定义为布尔量, 那么在仿真中将自动地对其赋值进行核查 一般这一类型的数据的初始值总为 false (6) 字符 (character) 字符也是一种数据类型, 所定义的字符量通常用单引号括起来, 如 'a' 一般情况下 VHDL 对大小写不敏感, 但是对字符量中的大 小写字符则认为是不一样的 例如,'B' 不同于 'b' 字符量中的字符可以是从 a 到 z 中的任一个字母, 从 0 到 9 中的任一个数以及空格或者特殊字符, 如 $,@,% 等等 包集合 standard 中给出了预定义的 128 个 ASCII 码字符, 不能打印的用标识符给出 字符 '1' 与整数 1 和实数 1.0 都是不相同的, 当要明确指出 1 的字符数据时, 则可写为 : character('1') (7) 字符串 (string) 字符串是由双引号括起来的一个字符序列, 也称字符矢量或字符串数组 字符串常用于程序的提示和说明 (8) 时间 (time) 时间是一个物理量数据 完整的时间量数据应包含整数和单位两部分, 而且整数和单位之间至少应留一个空格的位置 例如,55 sec,2 min 等 在包集合 standard 中给出了时间的预定义, 其单位为 fs,ps,ns,µs,ms,sec,min 和 hr 例如: 20 µs,100 ns,3 sec 在系统仿真时, 时间数据特别有用, 用它可以表示信号延时, 从而使模型系统能更逼近实际系统的运行环境 (9) 错误等级 (severity level) 错误等级类型数据用来表征系统的状态, 共有 4 种 :note( 注意 ),warning( 警告 ), error( 出错 ),failure( 失败 ) 在系统仿真过程中可以用这 4 种状态来提示系统当前的工作情况, 从而使设计人员随时了解当前系统工作的情况, 并根据系统的不同状态采取相应的对策 (10) 大于等于零的整数 (natural), 正整数 (positive) 这两种数据是整数的子类,natural 类数据为取值 0 和 0 以上的正整数 ; 而 positive 则只能为正整数 上述 10 种数据类型是 VHDL 语言中标准的数据类型, 在编程时可以直接引用 如果用户需使用这 10 种以外的数据类型, 则必须进行自定义 但是, 大多数的 CAD 厂商已在包集合中对标准数据类型进行了扩展 例如, 数组型数据等, 这一点请读者注意 由于 VHDL 语言属于强类型语言, 在仿真过程中, 首先要检查赋值语句中的数据类型 33

34 和区间, 任何一个信号和变量的赋值均须落入给定的约束区间中, 也就是说要落入有效数值的范围中 约束区间的说明通常跟在数据类型说明的后面 例如 : integer range 100 downto 1 bit_vector(3 downto 0) real range 2.0 to 30.0 其中,downto 表示下降,to 表示上升 一个 BCD 数的比较器, 利用约束区间的端口说明语句为 : entity bcd_compare is oirt(a,b:in integer range 0 to 9:=0; c: out boolean); end bcd_compare; 2. 用户定义的数据类型在 VHDL 语言中, 使用户最感兴趣的一个特点是, 可以由用户自己来定义数据类型 由用户定义的数据类型的定义书写格式为 : type 数据类型名 { 数据类型名 } 数据类型定义 ; 在 VHDL 语言中还存在不完整的用户定义的数据类型的书写格式为 : type 数据类型名 { 数据类型名 } 这种由用户做的数据类型定义是一种利用其他已定义的说明所进行的 假 定义, 因此它不能进行逻辑综合 可由用户定义的数据类型有 : 枚举 (enumerated) 类型 ; 整数 (integer) 类型 ; 实数 (real) 浮点数 (floating) 类型 ; 数组 (array) 类型 ; 存取 (access) 类型 ; 文件 (file) 类型 ; 记录 (recode) 类型 ; 时间 (time) 类型 ( 物理类型 ) 下面对常用的几种用户定义的数据类型做一举例说明 (1) 枚举 (enumerated) 类型在逻辑电路中, 所有的数据都是用 '1' 或 '0' 来表示的, 但是人们在考虑逻辑关系时, 只有数字往往是不方便的 在 VHDL 语言中, 可以用符号名来代替数字 例如, 在表示一周每一天状态的逻辑电路中, 可以假设 000 为星期天, 001 为星期一 这对联机阅读程序是颇不方便的 为此, 可以定义一个叫 week 的数据类型如下所示 : type week is(sun,mon,tue,wed,thu,fri,sat); 34

35 基于上述的定义, 凡是用于代表星期二的日子都可以用 tue 来代替, 这比用代码 010 表示星期二直观多了, 使用时也不易出错误 枚举类型数据的定义格式为 : type 数据类型名 is( 元素, 元素, ); 这种用户定义的数据类型应用相当广泛, 在包集合 std_logic 和 std_logic_1164 中都有此类数据的定义 例如 : type std_logic is ('u', 'x', '0', '1', 'z', 'w', 'l', 'h', '-'); (2) 整数类型, 实数类型 (integer, read) 整数类型在 VHDL 语言中已存在, 这里指的是用户所定义的整数类型, 实际上可以认为是整数的一个子类 例如, 在一个数码管上显示数字, 其值只能取 0~9 的整数 如果由用户定义一个用于数码显示的数据类型, 那么可以写为 : type digit is integer range 0 to 9; 同理, 实数类型也如此, 例如 : type current is real range -1e4 to 1e4; 据此, 可以总结出整数或实数用户定义数据类型的格式为 : type 数据类型名 is 数据类型定义约束范围 ; (3) 数组 (array) 类型数组是将相同类型的数据集合在一起所形成的一个新的数据类型 它可以是一维也可以是二维或多维的 数组定义的书写格式为 : type 数组类型名 is array 范围 of 原数据类型名 ; 在此, 如果 范围 这一项没有被指定, 则使用整数数据类型范围 例如 : type word is array (1 to 8)of std_logic; 若 范围 这一项需用整数类型以外的其他数据类型的范围时, 则在指定数据范围前应加数据类型名 例如 : type word is array(integer 1 to 8)of std_logic; type instruction is(add,sub,inc,srl,srf,lda,ldb,xfr); subtype digit is integer 0 to 9; type insflag is array (instruction add to srf )of digit; 数组在总线定义及 ROM,RAM 等的系统模型中使用 std_ logic_ vector 也属于数组数据类型, 它在包集合 std _logic_ 1164 中的定义为 : 35

36 36 type std_ logic_ vector is array (natural range<>)of std_logic; 其中, 范围由 range< > 指定, 这是一个没有范围限制的数组 在这种情况下, 具体范围由信号说明语句等确定 例如 : signal aaa:std_ logic_ vector (3 downto 0); 在函数和过程的语句中, 若使用无限制范围的数组时, 其范围一般由调用者所传递的参数来确定 多维数组需要用两个以上的范围来描述, 而且多维数组不能生成逻辑电路, 只能用于仿真图形及硬件的抽象模型 例如 : type memarray is array (0 to 5,7 downto 0) of std_logic; constant romdata:memarray: = (('0', '0', '0', '0', '0', '0', '0', '0'), ('0', '1', '1', '1', '0', '0', '0', '1'), ('0', '0', '0', '0', '0', '1', '0', '1'), ('1', '0', '1', '0', '1', '0', '1', '0'), ('1', '1', '0', '1', '1', '1', '1', '0'), ('1', '1', '1', '1', '1', '1', '1', '1')); signal data_bit: std_logic; data_bit<=romdata (3,7); 上述例子是二维的, 在三维情况下要用 3 个范围来描述 在代入初值时, 各范围最左边所说明的值为数组的初始位脚标 在上例中,(0,7) 是起始位, 接下去右侧范围向右移 1 位变为 (0,6), 以后顺序为 (0,5),(0,4) 直至 (0, 0); 然后, 左侧范围向右移 1 位变为 (1,7), 此后按此规律移动得到最后 1 位 (5,0) (4) 时间 (time) 类型 ( 物理类型 ) 表示时间的数据类型, 在仿真时是必不可少的, 其书写格式为 : type 数据类型名 is 范围 ; units 基本单位 ; 单位 ; end units; 例如 : type time is range -1e18 to 1e18; units fs; ps=1000fs; ns=1000ps; µs=1000ns;

37 ms=1000µs; sec=1000ms; min=60sec; hr=60min; end units; 这里基本单位是 fs, 其 1000 倍是 ps 等等 时间是物理类型的数据, 当然对容量 阻抗值等也可以做定义 (5) 记录 (record) 类型数组是同一类型数据集合起来形成的, 而记录则是将不同类型的数据和数据名组织在一起而形成的新客体 记录数据类型的定义格式为 : type 数据类型名 is record 元素名 : 数据类型名 ; 元素名 : 数据类型名 ; end record; 在从记录数据类型中提取元素数据类型时应使用. 例如 : type bank is record addr0: std_ logic_ vector (7 downto 0); addr1: std_ logic_ vector (7 downto 0); r0: integer; inst: instruction; end record; signal addbus1, addbus2: std_ logic_ vector (31 downto 0); signal result: integer; signal ALU _code: instruction; signal r _bank: bank: =( , ,0,add); addbus1<=r _bank.addr1; r_ bank.inst <=ALU_code; 用记录描述 SCSI 总线及通信协议是比较方便的, 它比较适用于系统仿真 记录数据类型在生成逻辑电路时应将它分解开来才行 3. 用户定义的子类型 用户定义的子类型是用户对已定义的数据类型, 做一些范围限制而形成的一种新的数据类型 子类型的名称通常采用用户较易理解的名字 子类型定义的一般格式为 : subtype iobus is std_logic_vector(7 downto 0); subtype digit is integer range 0 to 9; 子类型可以由原数据类型指定范围而形成, 也可以完全和原数据类型范围一致 例如 : 37

38 subtype abus is std_logic_vector(7 downto 0); signal aio: std_logic_vector(7 downto 0); signal bio: std_logic_vector(15 downto 0); signal cio: abus aio<=cio; 正确操作 bio<=cio; 错误操作 除上述应用外, 子类型还常用于存储器阵列等的数组描述的场合 新构造的数据类型及子类型通常在包集合中定义, 再由 use 语句装载到描述语句中 4. 数据类型的转换在 VHDL 语言中, 数据类型的定义是相当严格的, 不同类型的数据之间是不能进行运算和直接代入的 为了实现正确的代入操作, 必须将要代入的数据进行类型转换 这就是所谓类型变换 变换函数通常由 VHDL 语言的包集合提供 例如, 在 std_logic_1164, std_logic_arith, std_logic_unsigned 的包集合中都提供了数据类型变换函数 下面举一个数据类型转换的例子 例 2-19 由 std_logic_vector 变换成 integer 实例 library IEEE; use IEEE std_logic_1164.all; use IEEE std_logic_unsigned.all; entity add5 is port (num:in std_logic_vector(2 downto 0)); Μ end add5; architecture rtl of add5 is signal in_num:integer range 0 to 5; Μ in_num<=con_integer(num);( 变换式 ) Μ end rtl; 此外, 由 bit_vector 变换成 std_logic_vector 也非常方便 代入 std_logic_vector 的值只能是二进制数, 而代入 bit_vector 的值除二进制数以外, 还可以是十六进制及八进制数 并且, bit_vector 还可以用 - 来分隔数值位 下面的几个语句表示了 bit_vector 和 std_logic_vector 赋值语句 : signal a: bit_vector(11 downto 0); signal b: std_logic_vector(11 downto 0); a<=x"a8"; b<=x"a8"; b<=to_std_logic_vector(x"af7"); 十六进制值可赋予位矢量 语法错, 十六进制不能赋予位矢量 38

39 b<=to_std_logic_vector(o"5117"); 八进制变换 b<=to_std_logic_vector(b" "); 5. 数据类型的限定在 VHDL 语言中, 有时可以用所描述的文字的上下关系来判断某一数据的数据类型 例如 : signal a: std_logic_vector(7 downto 0); a<=" "; 联系上下文关系, 可以断定 不是字符串 (string), 也不是位矢量 (bit_vector), 而是 std_logic_vector 但是, 也有判断不出来的情况 例如 : case(a & b & c)is when "001"=>y<=" "; when "010"=>y<=" "; Μ end case; 在该例中,a&b&c 的数据类型如果不确定就会发生错误 在这种情况下, 要对数据进行类型限定 ( 这类似 C 语言中的强制方式 ) 数据类型限定的方式是在数据前加上 类型名 例如 : a=std_logic_vector(" "); subtype std3bit is std_logic_vector (0 to 2); case std3bit'(a&b&c)is when"000"=>y<=" "; when"001"=>y<=" "; Μ 类型限定方式与数据类型变换很相似, 这一点请读者注意 6.IEEE 标准 std_logic, std_logic_vector 在数据类型介绍中, 曾讲到 VHDL 语言的标准数据类型 bit 是一个逻辑型的数据类型 这类数据取值只有 '0' 和 '1' 由于该类型数据不存在不定状态'x', 故不便于仿真 另外, 它也不存在高阻状态, 也很难用它来描述双向数据总线 为此,IEEE 1993 制定出了新的标准 (IEEE std_l164), 使得 std_logic 型数据可以具有如下多种不同的值 : 'u' 初始值 ; 'x' 不定 ; '0' 0; '1' 1; 'z' 高阻 'w' 弱信号不定 ; 'l' 弱信号 0; 39

40 'h' 弱信号 1; '-' 不可能情况 std_logic 和 std_logic_vector 是 IEEE 新制定的标准化数据类型, 也是在 VHDL 语法外所添加的数据类型, 因此将它归属到用户定义的数据类型中 当使用该类型数据时, 在程序中必须写出库说明语句和使用包集合的说明语句 VHDL 语言的运算操作符在 VHDL 语言中共有 4 类操作符, 可以分别进行逻辑运算 (logical) 关系运算 (relational) 算术运算(arithmetic) 和并置运算 (concatenation) 需要注意的是, 操作符操作的对象是操作数, 且操作数的类型应该和操作符所要求的类型相一致 另外, 运算操作符是有优先级的, 例如, 逻辑运算符 not, 在所有操作符中优先级最高 1. 逻辑运算符在 VHDL 语言中逻辑运算符共有 6 种, 它们分别是 : not 取反 ; and 与 ; or 或 ; nand 与非 ; nor 或非 ; xor 异或 这 6 种逻辑运算符可以对 std_logic 和 bit 等逻辑型数据 std_logic_vector 逻辑型数组及布尔数据进行逻辑运算 必须注意, 运算符的左边和右边, 以及代入的信号的数据类型必须是相同的 当一个语句中存在两个或两个以上的逻辑表达式时, 在 C 语言中运算有 自左至右 的优先级顺序的规定, 而在 VHDL 语言中, 左右没有优先级差别 例如, 在下例中, 如去掉式中的括号, 那么从语法上来说是错误的 : x<=(a and b) or (not c and d); 当然也有例外, 如果一个逻辑表达式中只有 and or 和 xor 运算符, 那么改变运算顺序将不会导致逻辑值的改变 此时, 括号是可以省略的 例如 : a<=b and c and d and e; a<=b or c or d or e; a<=b xor c xor d xor e; a<=(b nand c) nand e; a<=(b and c)or (d and e); 必须要括号 必须要括号 在所有逻辑运算中 not 的优先级最高 2. 算术运算符 VHDL 语言有 10 种算术运算符, 它们分别是 : + 加 ; 40

41 - 减 ; * 乘 ; / 除 ; mod 求模 ; rem 取余 ; + 正 ;( 一元运算 ) - 负 ;( 一元运算 ) ** 指数 ; abs 取绝对值 在算术运算中, 一元运算的操作数 ( 正 负 ) 可以为任何数值类型 ( 整数 实数 物理量 ) 加法和减法的操作数也和一元运算一样, 具有相同的数值类型, 但参加加 减运算的操作数的数据类型也必须相同 乘除法的操作数可以同为整数和实数 物理量可以被整数或实数相乘或相除, 其结果仍为一个物理量 ; 物理量除以同一类型的物理量即可得到一个整数量 求模和取余的操作必须是同种整数类型数据 指数运算符的左操作数可以是任意整数或实数, 而右操作数应为一整数 ( 只有在左操作数是实数时, 右操作数才可以是负整数 ) 实际上, 能够真正综合逻辑电路的算术运算符只有 + - 和 * 在数据位较长的情况下, 使用算术运算符进行运算, 特别是使用乘法运算符 * 时, 应特别慎重 因为对于 16 位的乘法运算, 综合时逻辑门电路会超过 2000 个门 对于算术运算符 / mod 和 rem, 分母的操作数为 2 乘方的常数时, 逻辑电路综合是可能的 对 std_logic_vector 进行 + ( 加 ) - ( 减 ) 运算时, 两边的操作数和代入的变量位长如不同, 则会产生语法错误 另外, * 运算符的两边等位长相乘后的值和要代入的变量的位长不相同时, 同样也会出现语法错误 3. 关系运算符 VHDL 语言中有 6 种关系运算符, 它们分别是 : = 等于 ; /= 不等于 ; < 小于 ; <= 小于等于 ; > 大于 ; >= 大于等于 ; 在关系运算符的左右两边是操作数, 不同的关系运算符对两边的操作数的数据类型有不同的要求 其中, 等号 = 和不等号 /= 可以适用所有类型的数据 其他关系运算符则可适用于整数 (integer) 和实数 (real) 位 (std_logic) 等枚举类型以及位矢量 (std_logic_vector) 等数组类型的关系运算 在进行关系运算时, 左右两边的操作数的数据类型必须相同, 但是位长度不一定相同, 当然也有例外的情况 在利用关系运算符对位矢量数据进行比较时, 比较过程是从最左边的位开始, 自左至右按位进行比较的 在位长不同的情况下, 只能按自左至右的比较结果作为关系运算的结果 例如, 对 3 位和 4 位的位矢量进行比较 : signal a:std_logic_vector(3 downto 0); 41

42 signal b:std_logic_vector(2 downto 0); a<="1010" ;--10 b<="111" ;--7 if (a>b) then Μ else Μ 上例中,a 的值为 10, 而 b 的值为 7,a 应该比 b 大 但是, 由于位矢量是从左至右按位比较的, 当比较到次高位时,a 的次高位为 '0', 而 b 的次高位为 '1', 故比较结果 b 比 a 大 这样的比较结果显然是不符合实际情况的 为了能使位矢量进行关系运算, 在包集合 std_logic_unsigned 中对 std_logic_vector 的关系运算重新做了定义, 使其可以正确的进行关系运算 注意, 在使用时必须首先说明调用该包集合 当然, 此时位矢量还可以和整数进行关系运算 在关系运算符中小于等于符 <= 和代入符 <= 是相同的, 在读 VHDL 语言的语句时, 应按照上下文关系来判断此符号到底是关系符还是代入符 4. 并置运算符并置运算符 & 用于位的连接 例如, 将 4 个位用并置运算符 & 连接起来就可以构成一个具有 4 位长度的位矢量, 两个 4 位的位矢量用并置运算符 & 连接起来就可以构成 8 位长度的位矢量 例如 : tmp b<=b and (en&en&en&en) ; y<=a & tmp b; 第一个语句表示 b 的 4 位矢量由 en 进行选择得到一个 4 位位矢量的输出 ; 第 2 个语句表示 4 位位矢量 a 和 4 位位矢量 b 再次连接 ( 并置 ) 构成 8 位的位矢量 y 输出 位的连接也可以使用集合体的方法, 即将并置符换成逗号就可以了 例如 : tmp b<= (en,en,en,en ); 但是, 这种方法不适用于位矢量之间的连接 如下的描述方法是错误的 : a<= (a, tmp, -b); 集合体也能指定位的脚标, 例如上一个语句可表示为 : tmp b<= (3=>en,2=>en,1=en,0=>en); 或 tmp b<= (3 downto 0 => en); 在指定位的脚标时, 也可以用 others 来说明 : tmp b<= (others => en); 要注意, 在集全体中 others 只能放在最后 假若 b 位矢量的脚标 b(2) 的选择信号为 '0', 其他位的选择信号均为 en 那么此时表达式可写为: tmp b <= (2=> '0',others =>en); 42

43 2.2.2 VHDL 语言构造体的 3 种描述方式 在第 1 章中已经提到, 对硬件系统进行描述可以采用 3 种不同风格的描述方式, 即行为描述方式 寄存器传输 ( 或数据流 ) 描述方式和结构化的描述方式 这 3 种描述方式从不同的角度对硬件系统进行行为和功能的描述 目前, 采用后两种描述方式的 VHDL 语言程序可以进行逻辑综合, 而采用行为描述的 VHDL 语言程序, 大部分只用于系统仿真, 少数的也可以进行逻辑综合 本节针对这 3 种不同风格的描述方式做一介绍 构造体的行为级 (behavior) 描述方式 什么样的描述属于行为描述方式, 这一点目前还没有确切的定义 因此, 在不同的书刊中, 对于某些相同的或相似的用 VHDL 语言描述的逻辑电路的程序, 有不同的说明 有的说明为行为描述方式, 有的说明为寄存器传输描述方式 但是, 有一点是明确的, 行为描述方式是对系统数学模型的描述, 其抽象程度比寄存器传输描述方式和结构化描述方式的更高 在行为描述方式的程序中, 大量采用了算术运算 关系运算 惯性延时 传输延时等难于进行逻辑综合和不能进行逻辑综合的 VHDL 语句 一般来说, 采用行为描述方式的 VHDL 语言程序主要用于系统数学模型的仿真或者系统工作原理的仿真 在 VHDL 语言中存在一些专门用于描述系统行为的语句, 它们是 VHDL 语言为什么能在高层次上对系统硬件进行行为描述的原因所在 这些语句与一般的高级语言的语句有较大差别 1. 代入语句代入语句是 VHDL 语言中进行行为描述的最基本的语句 例如 : a <= b; 该语句的功能是 a 得到 b 的值 当该语句有效时, 现行信号 b 的值将代入到信号 a 只要 b 的值有一个新的变化, 那么该语句将被执行 所以,b 是该代入语句的一个敏感量 代入语句是普遍的格式为 : 信号量 <= 敏感信号量表达式 ; 例如 : z <= a nor (b nand c); 上式有 3 个敏感量 a b c 无论哪一个敏感量发生新的变化, 该代入语句都将被执行 具有延时时间的代入语句如下所示 : a<=b after 5 ns 该语句表示, 当 b 发生新的变化 5 ns 以后才被代入到信号 a 中 众所周知, 一个 2 输入的与门, 由于与门的固有延时, 当输入端发生变化以后, 与门的输出端的新的输出总要比输入端的变化延时若干时间 与门的这种输出特性就可以用具有延时时间的代入语句来描述 例 2-20 entity and2 is 43

44 port (a,b:in bit; c:out bit); end and2; architecture and2_behav of and2 is c<=a and b after 5 ns; end and2_behav; 下面再举一个如何用行为描述方式描述 4 选 1 电路的例子 4 选 1 电路的逻辑原理图如图 2-1 所示 描述 4 选 1 电路的 VHDL 语言程序如例 2-21 所示 i0 i1 i2 i3 b a 4 选 1 电路 q 44 例 2-21 library IEEE; use IEEE.std_logic_1164.all; entity mux4 is port (i0,i1,i2,i3,a,b:in std_logic; q:out std_logic); end mux4; architecture behav of mux4 is signal sel:integer; with sel select q<=i0 after 10 ns when 0, i1 after 10 ns when 1, i2 after 10 ns when 2, i3 after 10 ns when 3, 'x' after 10 ns when others; sel<=0 when a='0'and b='0'else 1 when a='1'and b='0'else 2 when a='0'and b='1'else 3 when a='1'and b='1'else 4; 图 选 1 电路

45 end behav; 在 4 选 1 电路的构造体中有 6 个输入端口和 1 个输出端口 a 和 b 是选择信号输入端口, 在正常情况下,a 和 b 共有 4 种取值 0~3 a 和 b 的取值将确定 i0~i3 中的哪一个输入端信号可以通过 4 选 1 电路从输出端 q 输出 在例 2-21 中, 用了两个条件代入类型的语句 也就是说, 只有 when 后面所指定的条件得到满足时, 指定的代入值才被代入信号量 sel 或输出量 q 当第一个语句执行时, 将使用选择信号 根据选择信号 sel 的当前值, 后跟的 5 种状态下的值 i0~i3 'x' 中的一种值将通过输出端口 q 输出 在正常情况下,q 端将选择 i0~i3 的之一输出, 在非正常情况下将输出 'x' 值 第 2 个语句执行时, 根据 a 和 b 的具体状态, 将 0~4 的值代入信号量 sel 正常情况下, 代入 sel 的值为 0~3, 非正常情况下代入 4 上述两个语句都存在敏感信号量 在第 2 个语句中,a 和 b 是敏感信号量, 当 a 和 b 任何一个值有变化, 该语句将被执行 第一个语句的信号敏感量为 sel, 只要 sel 值有新的变化, 第一个语句就会执行 在该构造体中, 上述这两上语句是可以并发执行的 有关并发执行的概念, 将在后面章节进一步介绍 2. 延时语句在 VHDL 语言中存在两种延时类型 : 惯性延时和传输延时 这两种延时常用于 VHDL 语言的行为描述方式 (1) 惯性延时在 VHDL 语言中, 惯性延时是缺省的, 即在语句中如果不做特别说明, 产生的延时一定是惯性延时 这是因为大多数器件在行为仿真时都会呈现惯性延时 在惯性模型中, 系统或器件的输出信号要发生变化必须有一段时间的延时, 这段延时时间常称为系统或器件的惯性或惯性延时 惯性延时有一个重要特点, 即当一个系统或器件, 它的输入信号变化周期小于系统或器件的惯性 ( 或惯性延时 ) 时, 其输出将保持不变 如图 2-3 所示, 有一个门电路, 其惯性延时时间为 20 ns, 当该门电路的输入端 a 输入一个 10 ns 的脉冲信号时, 其输出端 b 的输出仍维持低电平, 没有发生变化 对于惯性时间等于 20 ns 的门电路, 为使其实现正常的功能, 输入信号的变化周期一定要大于 20 ns a b<=transport a after 20 ns ; a b<=a after 20 ns ; b b ns ns 图 2-2 传输延时示例 图 2-3 惯性延时示例 几乎所有器件都存在惯性延时, 因此, 设计人员为了逼真地仿真硬件电路的实际工作情况, 在代入语句中总要加上惯性延时时间说明 例如 : b <= a after 10 ns; 45

46 惯性延时说明只在行为仿真时有意义, 逻辑综合时将被忽略, 或者在逻辑综合前必须去掉延时说明 (2) 传输延时在 VHDL 语言中, 传输延时不是缺省的, 必须在语句中明确说明 传输延时常用于描述总线延时 连接线的延时及 ASIC 芯片中的路径延时 如图 2-3 的门电路的惯性延时若用传输延时来替代, 那么就得到如图 2-2 的波形结果 从图 2-2 的波形可以看到, 同样的门电路, 当有 10 ns 的脉冲波形输入时, 经 20ns 传输延时以后, 在输出端就产生 10 ns 的脉冲波形 也就是说, 输出端的信号除延时规定时间外, 将完全复现输入端的输入波形, 而不管输入波形的形状和宽窄如何 具有传输延时的代入语句如下所示 : b<=transport a after 20 ns; 语句中 transport 是专门用于说明传输延时的前置词 3. 多驱动器描述语句在 VHDL 语言中, 创建一个驱动器可以由 1 条信号代入语句来实现 当有多个信号并行输出时, 在构造体内部必须利用代入语句, 对每个信号创建一个驱动器 因而, 在构造体内部会有多个代入语句 在设计逻辑电路时, 有时会碰到这样情况, 多个驱动器的输出将连接到同一条信号线上 考虑这种情况, 多驱动器的构造体应按如下方式描述 : 46 architecture sample of sample is a<=b after 5 ns; a<=d after 5 ns; end sample; 在上述的 sample 构造体中, 信号 a 由两个驱动源 b 和 d 驱动 每一个并发的信号代入语句都将创建一个驱动器, 它们的输出共同驱动信号 a 第 1 条语句创建一个驱动器, 其输出值为 b, 经 5 ns 延时驱动信号 a; 第 2 个语句创建一个驱动器, 其输出值为 d, 经 5 ns 延时驱动信号 a 在这种情况下, 信号 a 的值将取决于两个驱动器的输出 b 和 d 那么信号 a 到底应该取何值? 这一点在标准的数据类型中是没有定义的 为了解决多个驱动器同时驱动一个信号的信号行为描述, 在包集合 std_logic_1164 中专门定义了一种描述函数的数据类型, 称为决断函数子类型 所谓决断函数就是在多个驱动器同时驱动一个信号时, 判断输出哪一个值的函数 例 2-22 是包集合 std_logic_1164 中关于决断函数描述的部分源程序 例 2-22 package std_logic_1164 is Μ type std_ulogic is('u','x', '0', '1', 'z', 'w', 'l', 'h', '-'); type std_ulogic_vector is array(natural range<>) of std_ulogic; -- 决断函数说明 function resolved(s:std_ulogic_vector) return std_ulogic;

47 subtype std_logic is resolved std_ulogic ; -- 子类型数据说明 type std_logic_vector is array(natural range<>)of std_logic; Μ end std_logic_1164; package body std_logic_1164 is Μ constant resolution_table:std_logic_table:=( -- u x 0 1 z w l h - ('u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u'), -- u ('u', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'), -- x ('u', 'x', '0', 'x', '0', '0', '0', '0', 'x'), -- 0 ('u', 'x', 'x', '1', '1', '1', '1', '1', 'x'), -- 1 ('u', 'x', '0', '1', 'z', 'w', 'l', 'h', 'x'), -- z ('u', 'x', '0', '1', 'w', 'w', 'w', 'w', 'x'), -- w ('u', 'x', '0', '1', 'l', 'w', 'l', 'w', 'x'), -- l ('u', 'x', '0', '1', 'h', 'w', 'w', 'h', 'x'), -- h ('u', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'), -- - ); function resolved(s:std_ulogic_vector)return std_ulogic is -- 决断函数本体 variable result:std_ulogic:= 'z'; -- 高阻状态 if (s length=1) then return s(s'low); else for i in s range loop result:=resolution_table(result,s(i)); end loop; end if; return result; end resolved; end std_logic_1164; 在例 2-22 中定义了决断函数, 当系统要确定多驱动器输出的状态时, 可调用该函数, 例如 : function resolved (s:std_ulogic_vector)return std_ulogic; 语句中 s 是位矢量, 其位长度就是多驱动器输出的信号数 若 s 取值为 ('0', '1', 'x'), 则 : sl:=resolved(s); --sl 为 std_ulogic 上述两条语句表示, 有 3 个驱动器, 其输出值分别为 '0','1' 和 'x' sl 是这 3 个驱动器输出共同驱动的信号 那么在这种情况下,sl 应该处于什么状态呢? 调用决断函数 resolved(s) 得 47

48 到的返回值应为 'x', 那么此时 sl 的状态应为 'x' 若(s='0', 'z', 'z'), 调用 resolved(s) 得到返回值 '0', 那么此时 sl 的状态应为 '0' 这样, 使用决断函数就可以正确地描述多驱动器输出时的信号行为 4.generic 语句 generic 语句常用于不同层次之间的信息传递 例如, 在数据类型说明上用于位矢量长度 数组的位长以及器件的延时时间等参数的传递 该语句所涉及的数据除整数类型以外, 如涉及其他类型的数据则不能进行逻辑综合 因此, 该语句主要用于行为描述方式 使用 generic 语句易于使器件模块化和通用化 例如, 要描述 2 输入与门的行为 2 输入与门的逻辑关系是明确的, 但是由于在集成时材料不同和工艺不同, 不同类型的 2 输入与非门的上升沿 下降沿等参数是不一致的 为简化设计和供其他设计人员方便地调用, 需要开发一个通用的 2 输入与门的程序模块 在该模块中某些参数是待定的, 在仿真或逻辑综合时, 只要用 generic 语句将待定参数初始化后, 即可实现各种类型 2 输入与门的仿真或逻辑综合 例 2-23 就是采用 generic 语句的一个实例 例 2-23 entity and2 is generic(rise,fall:time); port(a,b:in bit; c:out bit); end and2; architecture behav of and2 is signal internal:bit; internal<=a and b; c<=internal after(rise) when internal='1'else internal after (fall); end behav; 例 2-23 是一个通用的 2 输入与门的实体 如果现在要构成一个如图 2-4 所示的电路, 那么尽管图 2-4 中的各 2 输入与门的上升和下降的时间不同, 如使用 generic 和 generic map 语句, 仍能调用通用的 2 输入与非门模块, 以简化电路的设计 利用例 2-23 中的通用 2 输入与门模块, 构成图 2-4 逻辑电路的 VHDL 语言程序如例 2-24 所示 图 个 2 输入与门构成的电路 48 例 2-24 entity sample is generic (rise,fall:time);

49 port(ina,inb,inc,ind:in bit; q:out bit); end sample; architecture behav of sample is component and2 generic(rise,fall:time); port(a,b:in bit; c:out bit); end component; signal u0_c,u1_c:bit u0:and2 generic map (5 ns,5 ns) port map(ina,inb,u0_c); u1:and2 generic map(8ns,10ns) port map (inc,ind,u1_c); u2:and2 generic map (9ns,11ns) port map (u0_c,u1_c,q); end behav; 由例 2-24 可以看到, 利用 generic map 语句的功能, 在使用同一个 and2 实体的情况下, 可使 u0 u1 u2 这 3 个与门的上升时间和下降时间具有不同的值 u0 的上升时间为 5 ns, u1 的上升时间为 8 ns,u2 的上升时间为 9 ns; 而 u0 的下降时间为 5 ns,u1 的下降时间为 10 ns,u2 的下降时间为 11 ns 如此灵活地改变参数就可以完全满足实际设计中的要求 还有其他许多语句也用于构造体的行为描述方式, 如 guarded block 等 VHDL 语言之所以优于目前已开发的各种硬件描述语言, 其主要的一个优点是, 它具有丰富的语句和语法, 能在高层次上对系统的行为进行描述和仿真 构造体的寄存器传输级 (rtl) 描述方式 如前一节所述, 采用行为描述方式的 VHDL 语言程序, 在一般情况下只能用于行为层次的仿真, 而不能进行逻辑综合 对于用行为描述方式的 VHDL 语言程序, 只有改写为 rtl 描述方式才能进行逻辑综合, 也就是说 rtl 描述方式才是真正可以进行逻辑综合的描述方式 在某些书刊中, 也把 rtl 描述方式称为数据流描述方式 1.rtl 描述方式的特点 rtl 描述方式, 是一种明确规定寄存器描述的方法 由于受逻辑综合的限制, 在采用 rtl 描述方式时, 所使用的 VHDL 语言的语句有一定限制, 其限制情况如附录 A 所示 在 rtl 描述方式中, 要么采用寄存器硬件一一对应的直接描述, 要么采用寄存器之间的功能描述 例如, 上节中的 4 选 1 电路, 如采用 rtl 描述方式, 其 VHDL 语言描述程序如例 2-25 所示 例 2-25 library IEEE; use IEEE.std_logic_1164.all; 49

50 use IEEE.std_logic_unsigned.all; entity mux4 is port (input:in std_logic_vector(3 downto 0); sel:in std_logic_vector(1 downto 0); y:out std_logic); end mux4: architecture rtl of mux4 is y<=input(0)when sel="00"else input(1)when sel= 01 else input(2)when sel="10"else input(3); end rtl; 例 2-25 其实是对 4 选 1 电路的功能进行描述而得到的 rtl 描述实体 input(0) input(1) sel 2 选 1 电路 2 选1 电y y 50 图 选 1 电路原理图 下面再举一个 2 选 1 电路的例子,2 选 1 电路的电原理图如图 2-5 所示 下面用两种不同的方法来描述该电路 例 2-26 是用功能描述的 rtl 描述方式, 例 2-27 是采用硬件一一对应的 rtl 描述方式 例 2-26 library IEEE; use IEEE.std_logic_1164.all; entity mux2 is port (input:in std_logic_vector (1 downto 0); sel:in std_logic; y:out std_logic); end mux2; architecture rtl of mux2 is y<=input(0)when sel='1 else

51 input(1); end rtl; 例 2-27 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all entity mux2 is port (in0,in1,sel:in std_logic; y:out std_logic); end mux2; architecture rtl of mux2 is signal tmp1,tmp2,tmp3:std_logic; tmp1<=in0 and sel; tmp2<=in1and (not sel); tmp3<=tmp1 or tmp2; y<=tmp3; end rtl; 对于例 2-26, 是将 2 选 1 电路看成一个黑框, 编程者无需了解 2 选 1 电路内部的细节, 只要知道外部特性和功能就可以进行正确的描述 而对例 2-27, 编程者就必须了解 2 选 1 电路是怎样构成的, 内部采用了哪些门电路 只有了解了这些细节, 才能用 VHDL 语言进行正确的描述 所以从编程效率及编程难度上来看, 应该选择例 2-26 的编程方法, 来编写 rtl 描述方式的程序 随着 CAD 技术的发展, 人们也正在探讨如何对使用行为描述方式的程序进行逻辑综合, 如能做到这一点, 将会大大提高 CAD 技术的水平 2. 使用 rtl 描述方式应注意的几个问题 (1)'x' 状态的传递在目前的 rtl 设计中, 要对所设计的程序进行仿真检验, 且在逻辑电路综合以后还有必要对综合的结果进行仿真 之所以要进行 2 次仿真, 是因为在仿真过程中存在 'x' 传递的影响, 它可以使 rtl 仿真和门级电路仿真产生不一致的结果 所谓 'x' 状态的传递, 实质上是不确定信号状态的传递, 它将使逻辑电路产生不确定的结果 不确定状态在 rtl 仿真时是允许出现的, 但是在逻辑综合后的门级电路仿真中是不允许出现的 例 2-28 process(sel) if (sel='1') then y<='0'; else 51

52 y<='1'; end if; end process; 例 2-28 是一个 2 值输入的器件的 rtl 描述 当 sel='1' 时, 其输出 y 为 '0'; 而当 sel=0 时, 其输出 y 为 '1' 如果在这里 sel 的状态为 'x', 那么, 因为 'x' 不是 '1', 故程序执行 else 项, 使输出为 '1' 这样'x' 状态就从前一段传递到后一段, 在仿真时认为电路是正确的 现在将例 2-28 的描述顺序改变一下, 如例 2-29 所示 例 2-29 process(sel) if (sel='0') then y<='1 ; else y<='0'; end if; end process; 例 2-29 中, 若 sel=x 时, 输出的 y 值将变为 '0' 为了防止这种不合理的结果, 在例 2-28 中增加一项 y<='x' 输出项, 如下所示 : process (sel) if (sel ='1') then y<='0'; elsif (sel='0') then y<='1'; else y<='x' end if; end process; 其中 else 项以前, 将 sel 所有的可能取值都做了明确的约束 当 sel='x' 时, 其输出 y 也将变为 'x', 就不会出现不合理的结果 在逻辑综合时,else 项是被忽略的, 这样 rtl 仿真结果就和逻辑综合的仿真结果是一样的 在使用双向总线 ( 如数据总线 ) 时, 其信号取值总是会出现高阻状态 'z' 当双向总线的信号驱动逻辑电路时, 就有可能出现 'x' 状态的传递 为了保证逻辑电路的正常工作, 高阻状态 'z' 是应该禁止的, 如图 2-6 所示 在图 2-6 中, 用与门来禁止, 不使 'z' 状态变为 'x' 状态而被传递 禁止信号 en 保证在双向总线出现 'z' 状态时其取值为 '0', 正常时取值为 '1' 双向数据总线 52 en & & & yy &

53 图 2-6 双向总线与逻辑电路的连接 (2) 寄存器 rtl 描述的限制由 rtl 描述所生成的逻辑电路中, 一般来说寄存器的个数和位置与 rtl 描述的情况是一致的 但是, 寄存器 rtl 描述不是任意的, 而是有一定限制的 禁止在一个进程中存在两个寄存器描述 rtl 描述规定, 在一个进程中只能描述一个寄存器 像例 2-30 那样对两个寄存器进行描述是不允许的 例 2-30 process(clk1,clk2) if (clk1'event and clk1='1') then y<=a; end if; if (clk2'event and clk2='1') then z<=b; end if; end process; 禁止使用 if 语句中的 else 项在用 if 语句描述寄存器功能时, 禁止采用 else 项 例 2-31 这样的描述是应该禁止使用的 例 2-31 process(clk) if (clk'event and clk='1') then y<=a; else y<=b; end if; end process; 寄存器描述中必须代入信号值 53

54 在寄存器描述中, 必须将值代入信号, 如例 2-32 所示 例 2-32 process(clk) variable tmp:std_logic; if (clk'event and clk='1') then tmp:=a; end if; y<=tmp; end process; (3) 关联性强的信号应放在一个进程中在设计 与 或 这样的部件时, 如果在原理图上是并行放置的, 那么通常进程和部件是一一对应的 但是, 在较复杂的电路中, 存在多个输入和输出信号, 有些信号互相的关联度很高, 而有些信号互相的关联度就很低 在这种情况下, 为了逻辑综合以后, 使其电路的面积和速度指标更高, 通常将关联度高的信号放入一个进程中, 将电路分成几个进程来描述 如图 2-7 所示的逻辑电路, 可以用一个进程来描述, 如例 2-33 所示, 也可以采用多个进程描述, 如例 2-34 所示 1 & 1 1 & 图 2-7 多进程描述的电路 例 2-33 library IEEE; use IEEE.std_logic_1164.all; entity ex1 is port (a,b,c,zin,yin:in std_logic; dout,eout,fout,gout:out std_logic); end ex1; 54 architecture rtl of ex1 is

55 process(a,b,c,zin,yin) if (a='1' and b='0') then dout<='1' eout<=zin; fout<='0'; gout<='0'; elsif (a='0' and b='0') then dout<='0'; eout<=yin; fout<='0'; gout<='0'; elsif (a='0' and b='1') then dout<='0' eout<=yin; fout<='1' gout<='0'; elsif (c='1') then dout<='1'; eout<=zin; fout<='1'; gout<='1'; else dout<='1'; eout<=zin; fout<='1'; gout<='0'; end if; end process; end rtl; 例 2-34 library IEEE; use IEEE.std_logic_1164.all; entity ex2 is port(a,b,c,zin,yin:in std_logic; dout,eout,fout,gout:out std_logic); end ex2; architecture rtl of ex2 is 55

56 56 process(a,zin,yin) if (a='1') then dout<='1'; eout<=zin; else dout<='0'; eout<=yin; end if; end process; process(b) if (b='1') then fout<='1'; else fout<='0'; end if; end process; process(a,b,c) if (a='1' and b='1' and c='1') then gout<='1'; else gout<='0'; end if; end process; end rtl; 由上面几个例子可以看出, 在用 rtl 描述时, 要想能正确地进行逻辑综合, 并使综合结果具有较佳的性能, 就必须注意 rtl 描述的一些具体规定和相应的技巧 构造体的结构级 (structure) 描述方式 所谓构造体的结构描述方式, 就是在多层次的设计中, 高层次的设计模块调用低层次的设计模块, 或者直接用门电路设计单元来构成一个复杂的逻辑电路的描述方法 结构描述方式最能提高设计效率, 它可以将已有的设计成果, 方便地用到新的设计中去 例如, 某一个逻辑电路是由 and 门 or 门和 xor 门构成的, 而 and 门 or 门和 xor 门的逻辑电路都已有现成的设计单元 那么, 用这些现成的设计单元 (and 的 entity or 的 entity 和 xor 的 entity) 经适当连接就可以构成新的设计电路的 entity 这种描述方式, 其结构非常清晰, 且能做到与电原理图中所画的器件一一对应 当然, 如要用结构描述方式, 则要求设计人员有较多的硬件设计知识 1. 构造体结构描述的基本框架

57 一个 2 选 1 电路用结构化描述方式描述的构造体如例 2-35 所示 2 选 1 电路的逻辑电路如图 2-8 所示 U3 u3 U4u4 u4 d0 & aa 1 1 U2 u2 ab q d1 U1 & sel 1 nsel nsel u1 图 选 1 逻辑电路 例 2-35 entity mux2 is port (d0,d1,sel:in bit; q:out bit); end mux2; architecture struct of mux2 is component and2 port (a,b:in bit; c:out bit); end component; component or2 port(a,b:in bit; c:out bit); end component; component inv port(a:in bit; c:out bit); end component; signal aa,ab,nsel:bit; u1:inv port map (sel,nsel); u2:and2 port map (nsel,d1,ab); u3:and2 port map(d0,sel,aa); u4:or2 port map(aa,ab,q); end struct; 从例 2-35 中可以看出, 在 2 选 1 电路的构造体中用 component 语句指明了在该电路中 57

58 所使用的已生成的模块 ( 在此是 and or not 门电路 ), 供本构造体调用 用 port map( ) 语句将生成模块的端口与所设计的各模块 ( 在此为 u1 u2 u3 u4) 的端口联系起来, 并定义相应的信号, 以表示所设计的各模块的连接关系 结构描述方式能较方便地进行多层次的结构设计 例如, 某系统由若干块插件板组成, 每个插件块又由若干块专用的 ASIC 电路组成, 各专用的 ASIC 电路又由若干个已生成的基本单元电路组成 这需要 3 个层次构成的系统可以用 3 个层次的结构来描述 (1)ASIC 级结构描述假设该系统中的 ASIC 电路的基本结构是由与门 或门和非门 3 种基本逻辑电路构成的 那么 ASIC 级的结构描述如例 2-36 所示 58 例 2-36 ASICl:block port( ); component and2 Μ end component; component or2 Μ end component; component inv Μ end component; signal ; for u1:and2 use entity work.and2; for u2:or2 use entity work.or2; u1:and2 port map( ); u2:or2 port map( ); Μ end block ASIC1; Μ ASICn:block Μ end block ASICn; 例 2-36 中, 对 n 种 ASIC 芯片的结构做了描述 不同的 ASIC 芯片是由不同数量和连接关系的与门 或门和非门构成的 对这些 ASIC 芯片进行逻辑综合就可以得到现成的 ASIC 芯片 如在其他的逻辑电路中要使用这些 ASIC 芯片, 在库中调用即可 (2) 插件板级结构描述每种插件板是由若干块不同的 ASIC 芯片构成的, 如要描述插件板的逻辑电路, 仍可采用结构描述方式来进行描述, 如例 2-37 所示 :

59 例 2-37 entity printed_board1 is port( ); end printed_board1; architecture board1 of printed_board1 is signal ; ASIC1:block Μ end block ASIC1; Μ ASIC5:block Μ end block ASIC5; Μ end board1; Μ entity printed_boardm is port( ); end printed_boardm; architecture boardm of printed_boardm is Μ end boardm; 在例 2-37 中描述了 m 块插件板的每一块是由哪些 ASIC 芯片组成的, 且其连接关系是什么 这样就得到了插件板级的逻辑电路的结构描述 (3) 系统级的结构描述若一个系统是由 m 块插件板连接而成的, 且通过插件板级描述, 则认为它们是可以供系统设计逻辑电路时能任意调用的已设计好的模块 此时, 系统级的结构描述如例 2-38 所示 例 2-38 entity system is port( ); Μ end system; architecture struct of system is component printed_board1 port( ); 59

60 end component; Μ component printed_boardm port( ); end component; for b_1:printed_board1 use entity work.printed_board1; Μ for b_m:printed_boardm use entity work.printed_boardm; signal ; b_1:printed_board1 -- 插件板 1 port map( ); Μ b_m:printed_boardm -- 插件板 m port map( ); end struct; 由此 3 级结构描述, 可以清晰地看到系统的结构和它们的连接关系, 而且这些现成的模块可以为以后的设计带来很大的方便 2.component 语句在构造体的结构描述中,component 语句是基本的描述语句 该语句指定了本构造体中所调用的是哪一个现成的逻辑描述模块 例如, 在例 2-35 的 2 选 1 电路的结构描述程序中, 使用了 3 个 component 语句, 分别引用了现成的 3 种门电路的描述 这 3 种门电路在库中已生成, 在任何设计中如用到这 3 种门电路, 只要用 component 语句调用就行了, 无须在构造体中再对这些门电路进行定义和描述 component 语句的基本书写格式为 : component 元件名 generic 说明 ; port 说明 ; end component; -- 参数说明 -- 端口说明 component 语句可以在 architecture, package 及 block 的说明部分中使用 component-instant 语句的书写格式为 : 标号名 : 元件名 port map ( 信号, ); 例如 : u2:and2 port map (nse1,d1,ab); 标号名放在元件名的前面, 在该构造体的说明中该标号名一定是惟一的 下一层元件端口信号与实际连接的信号用 port map 的映射关系联系起来 映射方法有两种 : 一种是位置映射 ; 一种是名称映射 (1) 位置映射方法所谓位置映射方法, 就是在下一层的元件端口说明中的信号书写顺序位置和 port map 60

61 () 中指定的实际信号书写顺序一一对应 例如, 在 2 输入与门中端口的输入输出定义为 : port(a,b:in bit; c:out bit); 在设计中引用的与门 u2 的信号对应关系描述为 : u2:and2 port map(nse1,d1,ab); 也就是说在图 2-8 中,u2 的 nsel 对应 a,d1 对应 b,ab 对应 c (2) 名称映射方法所谓名称映射就是将已经存于库中的现成模块的各端口名称, 赋予设计中模块的信号名 例如 : u2:and2 port map(a=>nse1,b=>d1,c=>ab); 在输出信号没有连接的情况下, 对应端口的描述可以省略 VHDL 语言的基本描述语句 在用 VHDL 语言描述系统硬件行为时, 按语句执行顺序对其进行分类, 可以分为顺序描述语句和并发描述语句 例如, 进程语句是一个并发语句 在一个构造体内可以有几个进程语句同时存在, 各进程语句是并发执行的 但是, 在进程内部的所有语句是顺序描述语句, 也就是说, 是按书写顺序自上至下 一个语句一个语句地执行的 例如,if 语句 loop 语句等都属于顺序描述语句 灵活运用这两类语句就可以正确地描述系统的并发行为和顺序行为 信号 变量与常量在 VHDL 语言中凡是可以赋予一个值的对象就称为客体 (object) 客体主要包括以下 3 种 : 信号 变量 常数 (signal,variable,constant) 在电子电路设计中, 这 3 类客体通常都具有一定的物理含义 例如, 信号对应地代表物理设计中的某一条硬件连接线 ; 常数对应地代表数字电路中的电源和地等 当然, 变量的对应关系不太直接, 通常只代表暂存某些信号的载体 3 类客体的含义和说明场合如表 2-2 所示 表 2-2 VHDL 语言 3 类客体含义和说明场合 客体类别 含 义 说明场合 信号 信号说明全局变量 architecture,package,entity 变量 变量说明局部量 process,function,procedure 常量 常量说明全局量 上面两种场合下, 都可以申明 1. 常数 (constant) 常数是一个固定的值, 常数说明就是对某一常数名赋予一个固定的值 通常赋值在程序开始前进行, 该值的数据类型则在说明语句中指明 常数说明的一般格式如下 : constant 常数名 : 数据类型 := 表达式 ; 61

62 例如 : constant vcc : real:=5.0; constant delay : time:=100 ns; constant fbus : bit_vector:="0101"; 常数一旦被赋值就不能再改变 上面 vcc 被赋值为 5.0, 那么在该 VHDL 语言程序中 vcc 的值就固定为 5.0, 它不像后面所提到的信号和变量那样, 可以任意代入不同的数值 另外, 常数所赋的值应和定义的数据类型一致 例如 : constant vcc:real:="0101"; 这样的常数说明显然是错误的 2. 变量 (variable) 变量只能在进程语句 函数语句和过程语句结构中使用, 它是一个局部量 在仿真过程中, 它不像信号那样, 到了规定的仿真时间才进行赋值, 变量的赋值是立即生效的 变量说明语句的格式为 : variable 变量名 : 数据类型约束条件 := 表达式 ; 例如 : variable x,y :integer; variable count:integer range 0 to 255 : =10; 变量在赋值时不能产生附加延时 假设 tmp1 tmp2 tmp3 都是变量, 那么下式产生延时的方式是不合法的 : tmp3:= tmp1+tmp2 after 10 ns; 3. 信号 (signal) 信号是电子电路内部硬件连接的抽象 它除了没有数据流动方向说明以外, 其他性质几乎和前面所述的 端口 概念一致 信号通常在构造体 包集合和实体中说明 信号说明语句格式为 : signal 信号名 : 数据类型约束条件 : = 表达式 ; 例如 : signal sys_clk: bit: ='0'; signal ground: bit: ='0'; 在程序中, 信号值的代入采用 <= 代入符, 而不是像变量赋值时用 := 符 而且信号代入时可以附加延时 例如,s1 和 s2 都是信号, 且 s2 的值经 10 ns 延时以后才被代入 s1 此时, 信号传送语句可书写为 : s1<=s2 after 10 ns; 信号是一个全局量, 它可以用来进行进程之间的通信 一般来说, 在 VHDL 语言中对信号赋值是按仿真时间来进行的 信号值的改变也需按仿真时间的计划表行事 4. 信号和变量值代入的区别 62

63 信号和变量值的代入不仅形式不同, 而且其操作过程也不相同 在变量的赋值语句中, 该语句一旦被执行, 其值立即被赋予变量 在执行下一条语句时, 该变量的值就为上一句新赋的值 变量的赋值符为 : = 信号代入语句采用 <= 代入符, 该语句即使被执行也不会使信号立即发生代入 下一条语句执行时, 仍使用原来的信号值 直到进程结束之后, 所有信号代入语句的实际代入才顺序进行处理 因此, 实际代入过程和代入语句的执行是分开进行的 如例 2-39 所示, 信号 c 和 d 的代入值 (a+b) 和 (c+b) 将从 process 外部通过进程的敏感信号 a b c 取得 进程执行时, 只从信号所对应的实体取值, 只要不碰到 wait 语句或进程执行结束, 进程执行过程中信号值是不进行代入的 如例 2-39 所示, 为了进行仿真, 需要让代入和处理交替地反复进行 例 2-39 process(a,b,c,d) d<=a; x<=b+d; d<=c; y<=b+d; end process; 结果 : x<=b+c; y<=b+c; process(a,b,c) variable d:std_logic_vector(3 downto 0); d:=a; x<=b+d; d:=c y<=b+d; end process; 结果 : x<=b+a; y<=b+c; 现在来看一下例 2-39 中两个进程描述的语句 首先, 由于信号 a 发生变化使进程语句开始启动执行 这样一来, 仿真器对进程中的各语句自上至下地进行处理 当进程所有语句执行完毕, 或者中途碰到 wait 语句时, 该进程执行结束, 信号代入过程被执行 代入同样应按顺序自上至下地进行 在例 2-39 的第 1 个进程中,d 中最初代入的值是 a, 接着又代入 c 值 尽管 d 中先代入 a 值, 后代入 c 值, 在时间上有一个 的延时, 但是, 在代入时由于不进行处理, 因此仿真时认为是时间 0 值延时 因此,d 的最终值应为 c, 这样 x 和 y 的内容都为 (b+c) 63

64 在例 2-39 的第 2 个进程中,d 是变量 在执行 d: =a; 语句以后,a 的值就被赋给 d, 所以 x 为 (b+a); 此后又执行 d: =c; 语句, 从而使 y 为 (b+c) 由此可以看出, 信号是将进程语句最后所代入的值作为最终代入值 ; 而变量的值一经赋值就变成新的值 这就是变量赋值和信号代入在操作上的区别 顺序描述语句顺序描述语句只能出现在进程或子程序中, 它将定义进程或子程序所执行的算法 语句中所涉及到的系统行为有时序流 控制 条件和迭代等 ; 语句的功能操作有算术 逻辑运算 信号和变量的赋值 子程序调用等 顺序描述语句像在一般高级语言中一样, 其语句是按出现的次序加以执行的 在 VHDL 语言中, 顺序描述语句有以下几种 : wait 语句 ; 断言语句 ; 信号代入语句 ; 变量赋值语句 ; if 语句 ; case 语句 ; loop 语句 ; next 语句 ; exit 语句 ; 过程调用语句 ; null 语句 空 (null) 语句表示只占位置的一种空处理操作, 但它可以用来为所对应信号赋一个空值, 表示该驱动器被关闭 该语句在下面不做介绍, 其余语句将通过具体实例做详细介绍 1.wait 语句进程在仿真运行中总是处于下述两种状态之一 : 执行或挂起 进程状态的变化受等待语句的控制 当进程执行到等待语句时, 就被挂起, 并设置好再次执行的条件 wait 语句可以设置 4 种不同的条件 : 无限等待 时间到 条件满足和敏感信号量变化 这几类条件可以混用, 其书写格式为 : wait wait on wait until wait for -- 无限等待 -- 敏感信号量变化 -- 条件满足 -- 时间到 (1) wait on wait on 语句的完整书写格式为 : wait on 信号 [, 信号, ]; wait on 语句后面跟着的是一个或多个信号量, 例如 : 64

65 wait on a,b; 该语句表明, 它等待信号量 a 或 b 发生变化 a 或者 b 中只要有一个信号量发生变化, 进程将结束挂起状态, 而继续执行 wait on 语句的后继语句 wait on 也可以再次启动进程的执行, 其条件是指定的信号量必须有一个新的变化, 这一点来看, 与进程指定的敏感信号量有新的变化时会启动进程的情况相类似, 如例 2-40 所示 : 例 2-40 process(a,b) process y<=a and b; y<=a and b; end process; wait on a,b; end process; 例 2-40 所示的两个进程的描述是完全等价的, 只是 wait on 和 process 中所使用的敏感信号量的书写方法有区别 在使用 wait on 语句的进程中, 敏感信号量应写在进程中的 wait on 语句后面 ; 在不使用 wait on 语句的进程中, 敏感信号量只应在进程开头的 process 后跟的括号中说明 需要注意的是, 如果在 process 语句中已有敏感信号量说明, 那么在进程中不能再使用 wait on 语句 例如, 例 2-41 的描述是非法的 例 2-41 process(a,b) y<=a and b; wait on a,b; (-- 错误语句 ) end process; (2)wait until wait until 语句的完整书写格式为 : wait until 表达式 ; wait until 语句后面跟的是布尔表达式, 当进程执行到该语句时将被挂起, 直到表达式返回一个 真 值, 进程才被再次启动 该语句的表达式将建立一个隐式的敏感信号量表, 当表中的任何一个信号量发生变化时, 就立即对表达式进行一次评估 如果评估结果使表达式返回一个 真 值, 则进程脱离等待状态, 继续执行下一个语句 例如 : wait until(x*10<=100); 当信号量 x 的值大于或等于 10 时, 进程执行到该语句时将被挂起 ; 当 x 的值小于 10 时进程再次被启动, 继续执行 wait 语句的后继语句 (3)wait for 语句 wait for 语句的完整书写格式为 : wait for 时间表达式 ; 65

66 wait for 语句后面跟的是时间表达式, 当进程执行到该语句时将被挂起, 直到指定的等待时间到时, 进程再开始执行 wait for 语句的后继语句 例如 : wait for 20ns wait for (a*(b+c)); 在上述的第 1 个语句中, 时间表达式是一个常数 20 ns, 当进程执行到该语句时将等待 20 ns 一旦 20 ns 时间到, 进程将执行 wait for 语句的后继语句 在上述的第 2 个语句中,for 后面是一个时间表达式,a*(b+c) 是时间量 wait for 语句在等待过程中, 要对表达式进行一次计算, 计算结果返回的值就作为该语句的等待时间 例如 :a=2,b=50 ns,c=70 ns 那么 wait for(a*(b+c)) 这个语句将等待 240 ns, 也就是说该语句和 wait for 240 ns 是等价的 (4) 多条件 wait 语句在前面已叙述的 3 个 wait 语句中, 等待的条件都是单一的, 要么是信号量, 要么是布尔量, 要么是时间量 实际上 wait 语句还可以同时使用多个等待条件 例如 : wait on nmi,interrupt until((nmi=true) or(interrupt=true))for 5 µs; 上述语句等待的是 3 个条件 : 信号量 nmi 和 interrupt 任何一个有一次新的变化 ; 信号量 nmi 或 interrupt 任何一个取值为 真 ; 该语句已等待 5 µs 只要上述 3 个条件中一个或多个条件满足, 进程将再次启动, 继续执行 wait 语句的后继语句 应该注意的是, 在多条件等待时, 表达式的值至少应包含一个信号量的值 例如 : wait until(interrupt = true)or (old_clk ='1') 如果该语句的 interrupt 和 old_clk 两个都是变量, 而不是信号量, 那么, 即使两个变量的值有新的改变, 该语句也不会对表达式进行评估和计算 ( 事实上, 在挂起的过程中变量的值是不可能改变的 ) 这样, 该等待语句将变成无限的等待语句, 包含等待语句的进程就不能再启动 在多种等待条件中, 只有信号量变化才能引起等待语句表达式的一次新的评价和计算 (5) 超时等待往往存在这样的一种情况, 在你所设计的程序模块中, 等待语句所等待的条件, 在实际执行时不能保证一定会碰到 在这种情况下, 等待语句通常要加一项超时等待项, 以防止该等待语句进入无限期的等待状态 但是, 如果采用这种方法, 应做适当的处理, 否则就会产生错误的行为, 如例 2-42 所示 66 例 2-42 architecture wait_example of wait_example is

67 signal sendb,senda:std_logic; senda<='0'; a:process wait until sendb='1'; senda<='1'after 10 ns; wait until sendb='0'; senda<='0'after 10 ns; end process a; b:process wait until senda='0'; sendb<='0'after 10 ns; wait until senda='1'; sendb<='1'after 10 ns; end process b; end wait_example; 在例 2-42 中, 一个构造体内包含有两个进程 这两个进程通过两个信号量 senda 和 sendb 进行通信 尽管该例实际上并不做任何事情, 但是它可以说明为什么等待语句会处于无限期的等待状态, 也就是通常所说的 死锁 状态 在仿真的最初阶段, 所有的进程都将会执行一次 进程通常在仿真启动的执行点得到启动 在例 2-42 中, 进程 a 在仿真启动点启动, 而在执行下述语句时被挂起 : wait until sendb='1'; 此时, 进程 b 同样在启动点被启动, 而在执行下述语句时被挂起 : wait until senda='1'; b 进程启动以后不会停留在第 1 条等待语句 wait until senda ='0' 因为该构造体中的第 1 条语句 senda< ='0', 它使 b 进程中的第 1 条等待语句的等待条件得到满足, 可以继续执行的后继语句 此后 b 进程向下执行将 '0' 代入 sendb, 而后停留在 b 进程的第 2 条等待语句上 这样, 两个进程就处于相互等待状态, 两个进程都不能继续执行 因为两个进程各自等待的条件都需要对方继续执行后才能得到满足 如果在每一个等待语句中插入一个超时等待项, 那么就可以允许进程继续执行, 而不至于进入死锁状态 为了检测出进程在没有满足等待条件就继续向下执行的情况, 在等待语句后面可加一条 assert( 断言 ) 语句 加有超时等待项的例程如例 2-43 所示 例 2-43 architecture wait_timeout of wait_example is signal senda,sendb:std_logic; a:process 67

68 wait until(sendb='1')for 1us; assert(sendb='1') report"sendb timed out at'1' " severity error; senda<='1'after 10 ns; wait until(sendb='0')for 1us; assert(sendb='0') report"sendb timed out at'0' " severity error; senda<='0'after 10 ns; end process a: 68 b:process wait until (senda='0')for 1us; assert(senda='0') report"seeda timed out at'0' " severity error; sendb<='0'after 10ns; wait until(senda='1')for 1us; assert(senda='1') report"senda timed out at'1' " severity error; sendb<='1'after 10ns; end process b; end wait_timeout; 在例 2-43 中每个等待语句的超时表达式用 1 µs 说明 如果等待语句的等待时间超过了 1 µs, 进程将执行下一条 assert 语句 assert 语句判断条件为 假, 就向操作人员提供错误信息输出, 从而有助于操作人员了解在进程中发生了超时等待 2. 断言 (assert) 语句 assert 语句主要用于程序仿真 调试中的人机会话, 它可以给出一个文字串作为警告和错误信息 assert 语句的书写格式为 : assert 条件 [report 输出信息 ][severity 级别 ]; 当执行 assert 语句时, 就会对条件进行判别 如果条件为 真, 则向下执行另一个语句 ; 如果条件为 假, 则输出错误信息和错误严重程度的级别 在 report 后面跟的是设计者所写的文字串, 通常是说明错误的原因, 文字串应用双引号括起来 severity 后面跟的是错误严重程度的级别 在 VHDL 语言中错误严重程度分为 4 个级别 :failure,error,warning 和 note

69 例如, 在例 2-43 中 a 进程的第 1 个等待语句后面跟的 assert 语句 : assert(sendb='1') report"sendb timed out at '1'" severity error; 该断言语句的条件是信号量 sendb ='1' 如果执行该语句时, 信号量 sendb ='0', 说明条件不满足, 则会输出 report 后跟的文字串 该文字串说明, 出现了超时等待错误 severity 后跟的错误级别告诉操作人员, 其错误级别为 error assert 语句为程序的仿真和调试带来了极大的方便 3. 信号代入语句信号代入语句的情况在前面已有详述, 这里只做归纳性的介绍 信号代入语句的书写格式为 : 目的信号量 <= 信号量表达式 ; 该语句表明, 将右边的信号量表达式的值赋予左边的目的信号量 例如 : a<=b 该语句表示将信号量 b 的当前值赋予目的信号量 a 需要再次指出的是, 代入语句的符号 <= 和关系操作的小于等于符 <= 非常相似, 要正确判别不同的操作关系, 应注意上下文的含义和说明 另外, 代入符号两边信号量的类型和位长度应该是一致的 4. 变量赋值语句变量赋值语句的书写格式为 : 目的变量 := 表达式 ; 该语句表明, 目的变量的值将由表达式所表达的新值替代, 但两者的类型必须相同 目的变量的类型 范围及初值在事先应已给出说明 右边的表达式可以是变量 信号或字符 该变量和一般高级语言中的变量是类似的 例如 : a:=2; b:=3.0; c:=d+e; 变量值只在进程或子程序中使用, 无法传递到进程之外 因此, 它类似于一般高级语言的局部变量, 只在局部范围内有效 5.if 语句 if 语句是根据所指定的条件来确定执行哪些语句的, 其书写格式通常可以分成 3 种类型 (1)if 语句的门闩控制用做门闩控制的 if 语句的书写格式为 : if 条件 then 顺序处理语句 69

70 end if; 当程序执行到该 if 语句时, 就要判断 if 语句所指定的条件是否成立 如果条件成立, 则 if 语句所包含的顺序处理语句将被执行 ; 如果条件不成立, 程序将跳过 if 语句所包含的顺序处理语句, 而向下执行 if 语句的后继语句 这里的条件起到门闩的控制作用, 如例 2-44 所示 例 2-44 if (a= '1') then c<=b; end if; 该 if 语句所描述的是一个门闩电路 a 是门闩控制信号量 ;b 是输入信号量 ;c 是输出信号量 当门闩控制信号量 a 为 '1' 时, 输入信号量 b 的任何值的变化都将被赋予输出信号量 c 也就是说,c 值与 b 是永远相等的 当 a '1' 时,c<=b 语句不被执行,c 将维持原始值, 而不管信号量 b 值发生什么变化 这种描述经逻辑综合, 实际上可以生成 D 触发器 例 2-45 就是 D 触发器的 VHDL 语言描述 例 2-45 library IEEE; use IEEE.std_logic_1164.all; entity dff is port(clk,d:in std_logic; q:out std_logic); end dff; architecture rtl of dff is process(clk) if (clk'event and clk='1') then q<=d; end if; end process; end rtl; 在例 2-45 中,if 语句的条件是时钟信号 clk 发生变化, 且时钟信号 clk ='1', 此时,q 端输出复现 D 端输入的信号值 当该条件不满足时,q 端维持原来的输出值 (2)if 语句的 2 选择控制 if 语句用作 2 选择控制时的书写格式为 : if 条件 then 70

71 顺序处理语句 ; else 顺序处理语句 ; end if; 在这种格式的 if 语句中, 当 if 语句所指定的条件满足时, 将执行 then 和 else 之间的顺序处理语句 ; 当条件不满足时, 将执行 else 和 end if 之间的顺序处理语句 也就是说, 用条件来选择两条不同的程序执行路径 这种描述的典型逻辑电路实例是 2 选 1 电路 假设 2 选 1 电路的输入为 a 和 b, 选择控制端为 sel, 输出端为 c 那么用 if 语句来描述该电路行为的程序如例 2-46 所示 例 2-46 architecture rtl of mux2 is process(a,b,sel) if (sel='1') then c<=a; else c<=b; end if; end process; end rtl; (3)if 语句的多选择控制 if 语句的多选择控制又称 if 语句的嵌套, 在这种情况下, 它的书写格式为 : if 条件 then 顺序处理语句 ; elsif 条件 then 顺序处理语句 ; Μ elsif 条件 then 顺序处理语句 ; else 顺序处理语句 ; end if; 在这种多选择控制的 if 语句中, 设置了多个条件 当满足所设置的多个条件之一时, 就执行该条件后紧跟的顺序处理语句 如果所有设置的条件都不满足, 则执行 else 和 end if 之间的顺序处理语句 这种描述的典型逻辑电路实例是多选一电路 例如,4 选 1 电路的描述如例 2-47 所示 71

72 例 2-47 library IEEE; use IEEE.std_logic_1164.all; entity mux4 is port(input:in std_logic_vector(3 downto 0); sel:in std_logic_vector(1 downto 0); y:out std_logic); end mux4; architecture rtl of mux4 is process(input,sel) if (sel="00") then y<=input(0); elsif (sel="01") then y<=input(1); elsif (sel="10") then y<=input(2) else y<=input(3); end if; end process; end rtl; if 语句不仅可以用于选择器的设计, 而且还可以用于比较器 译码器等凡是可以进行条件控制的逻辑电路设计 需要注意的是,if 语句的条件判断输出是布尔量, 即 真 (true) 或 假 (false) 因此, 在 if 语句的条件表达式中只能使用关系运算操作 (=,/=,<,>,<=,>=) 及逻辑运算操作的组合表达式 6.case 语句 case 语句用来描述总线或编码 译码的行为, 从含有许多不同语句的序列中选择其中之一执行 虽然 if 语句也有类似的功能, 但是 case 语句的可读性比 if 语句要强得多, 程序的阅读者很容易找出条件和动作的对应关系 case 语句的书写格式如下所示 : case 表达式 is when 条件表达式 => 顺序处理语句 ; end case; 上述 case 语句中的条件表达式可以有如下 4 种不同的表示形式 : 72 when 值 => 顺序处理语句 ;

73 when 值 值 值 值 => 顺序处理语句 ; when 值 to 值 => 顺序处理语句 ; when others=> 顺序处理语句 ; 当 case 和 is 之间的表达式的取值满足指定的条件表达式的值时, 程序将执行后跟的, 由符号 => 所指的顺序处理语句 条件表达式的值可以是一个值 ; 或者是多个值的 或 关系 ; 或者是一个取值范围 ; 或者表示其他所有的缺省值 当条件表达式取值为某一值时,case 语句的使用实例如例 2-48 所示 例 2-48 library IEEE; use IEEE.std_logic_1164.all; entity mux4 is port(a,b,i0,i1,i2,i3:in std_logic; q:out std_logic); end mux4; architecture mux4_behave of mux4 is signal sel:integer range 0 to 3; process(a,b,i0,i1,i2,i3) sel<=a; if (b='1') then sel<=sel+2; end if; case sel is when 0=>q<=i0; when 1=>q<=i1; when 2=>q<=i2; when 3=>q<=i3; end case; end process; end mux4_behave; 例 2-48 表明, 选择器的行为描述不仅可以用 if 语句, 而且也可以用 case 语句 但是 它们两者还是有区别的 在 if 语句中, 先处理最起始的条件 ; 如果不满足, 再处理下一个条件 而在 case 语句中, 没有值的顺序号, 所有值是并行处理的 因此, 在 when 项中已用过的值, 如果在后面 when 项中再次使用, 那么在语法上是错误的 也就是说, 值不能重复使用 另外, 应该将表达式的所有取值都一一列举出来, 如果不列举出表达式的所有取值, 在语法上也是错误的 带有 when others 项的 3-8 译码器的行为描述的例子, 如例 2-49 所示 73

74 例 2-49 library IEEE; use IEEE.std_logic_1164.all; entity decode_3to8 is port(a,b,c,g1,g2a,g2b:in std_logic; y:out std_logic_vector(7 downto 0)); end decode_3to8; 74 architecture rtl of decode_3to8 is signal indata:std_logic_vector(2 downto 0); indata<=c & b & a; process(indata,g1,g2a,g2b) if (g1='1' and g2a='0'and g2b='0') then case indata is when "000"=>y<=" "; when "001"=>y<=" "; when "010"=>y<=" "; when "011"=>y<=" "; when "100"=>y<=" "; when "101"=>y<=" "; when "110"=>y<=" "; when "111"=>y<=" "; when others=>y<="xxxxxxxx"; end case; else y<=" "; end if; end process; end rtl; 在例 2-49 中,indata 是矢量型数据, 除了取值为 '0' 和 '1' 之外, 还有可能取值为 'x','z' 和 'u' 尽管这些取值在逻辑电路综合时没有用, 但在 case 中却必须把所有可能取的值都要描述出来, 故在本例中应加一项 when others 项, 使得它包含了 y 输出的所有缺省值 当 when 后跟的值不同, 但是输出相同时, 则可以用 ' ' 符号来描述 例如, 本例中 when others 项也可以写成 :when uzx zxu uuz uuu => xxxxxxxxx ; 所有 'u','z','x' 3 种状态的排列, 表示了不同的取值 但是, 所有这些排列, 使其 3-8 译码器的输出值是一致的 因此,when 后面可以用 others 符号来列举所有可能的取值 同样, 当输入值在某一个连续范围内, 其对应的输出值是相同的, 此时在用 case 语句

75 时, 在 when 后面可以用 to 来表示一个取值的范围 例如, 若取值为自然数且取值范围为 1 9, 则可表示为 when 1 to 9=> 应该再次提醒的是 when 后跟的 => 符号不是关系运算操作符, 它在这里仅仅用来描述值和对应执行语句的对应关系 在进行组合逻辑电路设计时, 往往会碰到任意项, 即在实际正常工作时不可能出现的那些输入状态 在利用卡诺图对逻辑进行化简时, 可以把这些项看做 '1' 或者 '0', 从而可以使逻辑电路得到简化 现在来看一下, 怎样用 case 语句来描述这种逻辑设计时的任意项 例 2-49 是一个 3-8 译码电路 将 3-8 译码电路的输入变为输出, 输出变为输入, 它就变为二至十进制的编码电路, 该电路的功能描述如例 2-50 所示 例 2-50 library IEEE; use IEEE.std_logic_1164.all; entity encoder is port(input:in std_logic_vector(7 downto 0); y:out std_logic_vector (2 downto 0); end encoder; architecture rtl of encoder is process(input) case input is when" "=>y<="111"; when" "=>y<="110"; when" "=>y<="101"; when" "=>y<="100"; when" "=>y<="011"; when" "=>y<="010"; when" "=>y<="001"; when" "=>y<="000"; when others=>y<="xxx"; end case; end process; end rtl; 在例 2-49 中的 when others 语句和例 2-50 中的 when others 语句尽管都使 'x' 值代入 y, 但是其含义是不一样的 在例 2-49 中, 在正常情况下的所有的输入状态从 都在 case 语句中的 others 之前罗列出来了, 因此在逻辑综合时就不会有什么不利影响 而在例 2-50 中, 输入的所有状态并未在 case 语句中的 others 之前都罗列出来 例如, 当某一项输 75

76 入同时出现两个或两个以上的 '0' 时,y 输出值就将变为 'x'( 可能是 '0', 也可能是 '1') 逻辑综合时, 可以认为这些是不可能的输出项, 即不考虑这些无关态, 这样可以大大简化逻辑电路的设计 在仿真时如果出现了不正确的 'x' 值就可以检查是否出现了不正确的输入 如果要用 case 语句描述具有两个以上的 '0' 的情况, 并使它们针对某一特定的 y 输出, 例如将 others 改写为 : when others=>y<="111"; 那么, 在逻辑电路综合时, 就会使电路的规模和复杂性大大增加 目前,VHDL 语言的标准中还没有能对输入任意项进行处理的方法 例如优先级编码器的真值表如表 2-3 所示 其中, 标有 '-' 符号的输入项为任意项 也就是说标有 '-' 的位, 其值可以取 '1', 也可以取 '0' 如果想用 case 语句来描述优先级编码电路就必须用到下述这样的语句 : when "xxxxxxx0"=>y<="111"; when "xxxxxx01"=>y<="111"; Μ 表 2-3 优先级编码器的真值表 i7 i6 i5 i4 i3 i2 i1 i0 o2 o1 o 显然, 这样的描述语句在 VHDL 语言中还未制订出来, 因此不能使用这种非法的语句 此时, 利用 if 语句则能正确地描述优先级编码器的功能, 如例 2-51 所示 例 2-51 library IEEE; use IEEE.std_logic_1164.all; entity priorityencoder is port(input:in std_logic_vector(7 downto 0); y:out std_logic_vector(2 downto 0); end priorityencoder; 76 architecture rtl of priorityencoder is process(input)

77 if (input(0)='0') then y<="111"; elsif (input(1)='0') then y<="110"; elsif (input(2)= '0') then y<="101"; elsif (input(3)= '0') then y<="100"; elsif (input(4)= '0' then y<="011"; elsif (input(5)= '0') then y<="010"; elsif (input(6)= '0') then y<="001"; else y<="000"; end if; end process; end rtl; 在例 2-51 中,if 语句首先判别 input(0) 是否为 '0', 然后再次依顺序判别下去 如果该程序中首先判别 input(6) 是否为 '0', 然后再判 input(5) 是否为 0', 这样一直判别到 input(0) 是否为 '0' 尽管每种情况所使用的条件是一样的, 而且每种条件也只用到一次, 但是其结果却是不一样的 例 2-51 中所采用的判别顺序是正确的, 它正确地反映了优先级编码器的功能 ; 而后者由 input(6) 到 input(0) 进行判别的顺序是错误的, 它不能正确反映优先级编码器的功能, 其原因请读者自己思考 通常, 在 case 语句中,when 语句可以颠倒次序而不致于发生错误 ; 而在 if 语句中, 颠倒条件判别的次序往往会使综合逻辑功能发生变化 这一点希望读者切记 在大多数情况下, 能用 case 语句描述的逻辑电路, 同样也可以用 if 语句来描述 例如, 例 2-47 用 if 语句描述的 4 选 1 电路和例 2-48 用 case 语句描述的 4 选 1 电路是等价的 目前 IEEE 正在对以任意项描述的 VHDL 语言标准进行深入探讨, 相信在不久的将来, 像优先级编码器那样的逻辑电路也完全可以用 case 语句进行描述 7.loop 语句 loop 语句与其他高级语句中的循环语句一样, 使程序进行有规则的循环, 循环的次数受迭代算法控制 在 VHDL 语言中常用来描述位与逻辑, 用于迭代电路的行为 loop 语句的书写格式一般有两种 (1)for 循环变量这种 loop 语句的书写格式如下 : [ 标号 ]:for 循环变量 in 离散范围 loop 77

78 顺序处理语句 ; end loop [ 标号 ]; loop 语句中的循环变量的值在每次循环中都发生变化, 而 in 后跟的离散范围则表示循环变量在循环过程中依次取值的范围 例如 : asum:for i in 1 to 9 loop sum=i+sum; --sum 初始值为 0 end loop asum; 在该例子中 i 是循环变量, 它可取值 1 9 共 9 个值, 也就是说 sum=i+sum 的算式应循环计算 9 次 例 2-52 是 8 位的奇偶校验电路的 VHDL 语言描述的实例 例 2-52 library IEEE; use IEEE.std_logic_1164.all; entity parity_check is port(a:in std_logic_vector(7 downto 0); y:out std_logic); end parity_check ; architecture rtl of parity_check is process(a) variable tmp:std_logic; tmp:='0'; for i in 0 to 7 loop tmp:=tmp xor a(i); end loop; y<=tmp; end process; end rtl; 在例 2-52 中有几点需要说明 :tmp 是变量, 它只能在进程内部说明, 因为它是一个局部量 for-loop 语句中的 i 无论在信号说明和变量说明中都未涉及, 它是一个循环变量 如前例所述, 它是一个整数变量, 信号和变量都不能代入到此循环变量中 tmp 是变量, 如果变量值要从进程内部输出就必须将它代入信号量, 信号量是全局的, 可以将值带出进程 在例 2-52 中 tmp 的值通过信号 y 带出进程 (2)while 条件这种 loop 语句的书写格式如下 : 78

79 [ 标号 ]:while 条件 loop 顺序处理语句 ; end loop[ 标号 ]; 在该 loop 语句中, 如果条件为 真, 则进行循环 ; 如果条件为 假, 则结束循环 例如 : i:=1; sum:=0; sbcd:while(i<=10)loop sum:=i+sum; i:=i+1 end loop sbcd; 该例和 for-loop 语句的行为是一样的, 都是对 1 9 的数求累加和的运算 这里利用了 i<=10 的条件使程序结束循环, 而循环控制变量 i 的递增是通过算式 i:=i+1 来实现的 例 2-52 的 8 位奇偶校验电路的行为如果用 while 条件的 loop 语句来描述, 即可写为如例 2-53 所示的程序 例 2-53 library IEEE; use IEEE.std_logic_1164.all; entity parity_check is port(a:in std_logic_vector(7 downto 0); y:out std_logic); end parity_check; architecture behav of parity_check is process(a) variable tmp:std_logic; tmp:= '0'; i:=0; while(i<=8)loop tmp:=tmp xor a(i); i:=i+1; end loop; y<=tmp; end process; end behav; 虽然 for-loop 和 while-loop 语句都可以用来进行逻辑综合, 但是一般都不采用 79

80 while-loop 语句来进行 rtl 描述 8.next 语句在 loop 语句中,next 语句用于跳出本次循环, 其书写格式为 : 80 next [ 标号 ][when 条件 ]; next 语句执行时将停止本次迭代, 而转入下一次新的迭代 next 后跟的 标号 表明下一次迭代起始位置, 而 when 条件 则表明 next 语句执行的条件 如果 next 语句后面既无 标号 也无 when 条件 说明, 那么只要执行到该语句就立即无条件地跳出本次循环, 从 loop 语句的起始位置进入下一次循环, 即进行下一次迭代, 如例 2-54 所示 例 2-54 process(a,b) constant max_limit:integer:=255; for i in 0 to max_limit loop if (done(i)=true) then next; else done(i):=true; end if; q(i)<=a(i) and b(i); end loop; end process; 当 loop 语句嵌套时, 通常 next 语句应标有 标号 和 when 条件 例如, 有个 loop 嵌套的程序如下所示 : l1:while i<=10 loop l2: while j<=20 loop Μ next l1 when i=j; Μ end loop l1; 在上例中, 当 i=j 时,next 语句被执行, 程序将从内循环跳出, 而再从下一次外循环开始执行 由此可知,next 语句实际上是用于 loop 语句的内部循环控制 9.exit 语句 exit 语句也是 loop 语句中使用的循环控制语句, 与 next 语句不同的是, 执行 exit 语句将结束循环状态, 从 loop 语句中跳出结束 loop 语句的正常执行 exit 语句的书写格式为 : exit [ 标号 ][when 条件 ]; 如果 exit 后面没有跟 标号 和 when 条件, 则程序执行到该语句时就无条件地从 loop

81 语句中跳出, 结束循环状态, 继续执行 loop 语句的后继语句, 如例 2-55 所示 例 2-55 process(a) variable int_a:integer; int_a:=a; for i in 0 to max_limit loop if (int_a<=0) then exit; else int_a:=int_a-1; q(i)<=3.1416/real(a*i); end if; end loop; y<=q; end process; 在例 2-55 中,int_a 通常代入大于 0 的整数值 如果 int_a 的取值为负值或零将出现错误状态, 算式就不能计算 也就是说 int_a 小于或等于 0 时,if 语句将返回 真 值,exit 语句得到执行,loop 语句执行结束 程序将向下执行 loop 语句的后继语句 exit 语句具有 3 种基本的书写格式 第 1 种书写格式是 exit 语句没有 循环标号 或 when 条件 当条件为 真 而执行 exit 语句时, 程序将按如下顺序执行 : 执行 exit, 程序将仅仅从当前所属的 loop 语句中退出 如果 exit 语句位于一个内循环 loop 语句中, 即该 loop 语句嵌在任何其他的一个 loop 语句中, 那么执行 exit, 程序仅仅退出内循环, 而仍然留在外循环的 loop 语句中 第 2 种书写格式是 exit 语句后跟 loop 语句的标号 此时, 执行 exit 语句时, 程序将跳至所说明的标号 第 3 种书写格式是 exit 语句后跟 when 条件 语句 当程序执行到该语句时, 只有所说明的条件为 真 的情况下, 才跳出循环 loop 语句 此时, 不管 exit 语句是否有标号说明, 都将执行下一条语句 如果有标号说明, 下一条要执行的语句将是标号所说明的语句 ; 如果无标号说明, 下一条要执行的语句是循环外的下一条语句 exit 语句是一条很有用的控制语句 当程序需要处理保护 出错和警告状态时, 它能提供一个快捷 简便的方法 并发描述语句 在 VHDL 语言中, 能进行并发处理的语句有 : 进程 (process) 语句, 并发信号代入 (concurrent signal assignment) 语句, 条件信号代入 (conditional signal assignment) 语句, 选择信号代入 (selective signal assignment) 语句, 并发过程调用 (concurrent procedure call) 语句和块 (block) 语句 由于硬件描述的实际系统, 其许多操作是并发的, 所以在对系统进行仿真时, 这些系统中的元件在定义的仿真时刻应该是并发工作的 并发语句就是用来 81

82 表示这种并发行为的, 并发描述可以是结构性的也可以是行为性的 在并发语句中最关键的语句是进程 下面介绍一下各种并发语句的使用 1. 进程 (process) 语句 process 语句在前面已多次提到, 并在众多实例中得到了广泛的使用 进程语句是一种并发处理语句, 在一个构造体中多个 process 语句可以同时并发运行 因此,procee 语句是 VHDL 语言中描述硬件系统并发行为的最基本的语句 process 语句归纳起来有如下几个特点 : 它可以与其他进程并发运行, 并可存取构造体或实体所定义的信号 ; 进程结构中的所有语句都是按顺序执行的 ; 为启动进程, 在进程结构中必须包含一个显式的敏感信号量表或者包含一个 wait 语句 ; 进程之间的通信是通过信号量传递来实现的 下面要提到的一些并发语句, 实际上是一种进程的缩写形式, 它们仍可以归属于进程语句 2. 并发信号代入 (concurrent signal assignment) 语句在 2.2 节中已详述了代入语句的功能和相关的问题 在这里重提代入语句, 并且冠以 并发信号 的语句, 主要是强调该语句的并发性 代入语句 ( 信号代入语句 ) 可以在进程内部使用, 此时它作为顺序语句形式出现 ; 代入语句 ( 并发信号代入语句 ) 也可以在构造体的进程之外使用, 此时将它作为并发语句形式出现, 一个并发信号代入语句实际上是一个进程的缩写 例如 : architecture behav of a_var is output<=a(i); end behav; 可以等效于 architecture behav of a_var is process(a,i) output<=a(i); end process; end behav; 由信号代入语句的功能可知, 当代入符号 <= 右边的信号值发生任何变化时, 代入操作就会立即发生, 将新的值赋予代入符号 <= 左边的信号 从进程语句描述来看, 在 process 语句的括号中列出了敏感信号量表, 例中是 a 和 i 由 process 语句的功能可知, 在仿真时进程一直在监视敏感信号量 a 和 i 一旦任何一个敏感信号量发生新的变化, 进程将得到启动, 代入语句将执行, 新的值将从 output 信号量输出 由上面叙述可知, 并发信号代入语 82

83 句和进程语句在这种情况下确实是等效的 并发信号代入语句在仿真时刻同时运行, 它表征了各个独立器件各自的独立操作 例如 : a<=b+c; d<=e*f; 其中, 第 1 个语句描述了一个加法器的行为 ; 第 2 个语句描述了一个乘法器的行为 在实际硬件系统中, 加法器和乘法器是独立并行工作的 现在第 1 个语句和第 2 个语句都是并发信号代入语句, 在仿真时刻, 两个语句是并发处理的, 从而真实地模拟了实际硬件系统中的加法器和乘法器的工作 并发信号代入语句可以仿真加法器 乘法器 除法器 比较器及各种逻辑电路的输出 因此, 在代入符号 <= 的右边可以用算术运算表达式, 也可以用逻辑运算表达式, 还可以用关系操作表达式来表示 3. 条件信号代入 (conditional signal assignment) 语句条件信号代入语句也是并发描述语句, 它可以根据不同条件将不同的多个表达式之一的值代入信号量, 其书写格式为 : 目的信号量 <= 表达式 1 when 条件 1 else 表达式 2 when 条件 2 else 表达式 3 when 条件 3 else Μ 表达式 n; Μ else 在每个表达式后面都跟有用 when 指定的条件, 如果满足该条件, 则该表达式值代入目的信号量 ; 如果不满足条件, 则再判别下一个表达式所指定的条件 最后一个表达式可以不跟条件, 它表明在上述表达式所指明的条件都不满足时, 则将该表达式的值代入目标信号量 例 2-56 就是利用条件信号代入语句来描述的 4 选 1 逻辑电路 例 2-56 entity mux4 is port(i0,i1,i2,i3,a,b:in std_logic; q:out std_logic); end mux4; architecture rtl of mux4 is signal sel:std_logic_vector(1 downto 0); sel<=b & a; q<=i0 when sel="00" else 83

84 i1 when sel="01" else i2 when sel="10" else i3 when sel="11" else 'x'; end rtl; 条件信号代入语句与前述的 if 语句的不同之处就在于, 后者只能在进程内部使用 ( 因为它们是顺序语句 ), 而且与 if 语句相比, 条件信号代入语句中的 else 是一定要有的, 而 if 语句则可以有也可以没有 另外, 与 if 语句不同的是, 条件信号代入语句不能进行嵌套, 不能代入自身值, 因此, 不能生成锁存电路 由于用条件信号代入语句所描述的电路, 与逻辑电路的工作情况比较贴近, 这样, 往往要求设计者具有较多的硬件电路知识, 从而使一般设计者难于掌握 一般来说, 只有当用进程语句 if 语句和 case 语句难于描述时, 才使用条件信号代入语句 4. 选择信号 (selective signal assignment) 语句选择信号代入语句类似于 case 语句, 它对表达式进行测试, 当表达式取值不同时, 将使不同的值代入目的信号量 选择信号代入语句的书写格式如下 : with 表达式 select 目的信号量 <= 表达式 1 when 条件 1, 表达式 2 when 条件 2, Μ 表达式 n when 条件 n; 下面仍以 4 选 1 电路为例说明该语句的使用方法, 具体如例 2-57 所示 例 2-57 library IEEE; use IEEE.std_logic_1164.all; entity mux is port(i0,i1,i2,i3,a,b:in std_logic; q:out std_logic); end mux; 84 architecture behav of mux is signal sel:integer; with sel select q<=i0 when 0, i1 when 1, i2 when 2, i3 when 3, 'x' when others;

85 sel<=0 when a='0'and b='0'else 1 when a='1'and b='0'else 2 when a='0'and b='1'else 3 when a='1'and b='1'else 4; end behav; 上例中的选择信号代入语句, 根据 sel 的当前不同值来完成 i0 i1 i2 i3 及剩余情况的选择功能 选择信号代入语句在进程外使用, 当被选择的信号 ( 例如 sel) 发生变化时, 该语句就会启动执行 由此可见, 选择信号的并发代入, 可以在进程外实现 case 语句的功能 例 2-58 library IEEE; use IEEE.std_logic_1164.all; entity mux4 is port(input:in std_logic_vector(1 downto 0); i0,i1,i2,i3:in std_logic; q:std_logic); end mux4; architecture rtl of mux4 is process(input) case input is when"00"=>q<=i0; when"01"=>q<=i1; when"10"=>q<=i2; when"11"=>q<=i3; when others=>q<='x'; end case; end process; end rtl; 对照例 2-57 和例 2-58 可以看到, 两者功能是完全一样的, 仅仅是描述方法有区别而已 5. 并发过程调用 (concurrent procedure call) 语句并发过程调用语句可以出现在构造体中, 而且是一种可以在进程之外执行的过程调用语句 有关过程的结构及书写方法在 2.1 节中已详述, 这里就调用时应注意的几个问题做一简单说明 : 并发过程调用语句是一个完整的语句, 在它的前面可以加标号 ; 并发过程调用语句应带有 in out 或者 inout 类型的参数, 它们应列于过程名后跟 85

86 的括号内 ; 并发过程调用语句可以有多个返回值, 但这些返回值必须通过过程中所定义的输出参数带回 在构造体中采用并发过程调用语句的实例如下所示 : archiecture vector_to_int(z,x_flag,q); end; Μ 例中的并发过程调用语句 vector_to_int 是对位矢量 z 进行数制转换, 使之变成十进制的整数 q x_flag 是标志位, 当标志位为 真 表示转换失败, 为 假 表示转换成功 这种并发过程调用语句实际上是一个过程调用进程的简写 如 节所述, 过程调用语句可以出现在进程语句中, 如果该进程的作用就是进行过程调用, 完成该过程的操作功能, 那么两者是完全等效的 由此可知, 上例的并发过程调用语句和下面的过程调用进程是完全等效的, 两者都是为了完成矢量 - 整数的转换 architecture process (z,q) vector_to_int(z,x_flag,q); Μ end process; end ; 在构造体中的并发过程调用语句也由过程信号敏感量的变化而得到启动 例如, 上例中的位矢量 z 的变化将使 vector_to_int 语句得到启动, 并执行之 执行结果将拷贝到 x_flag 和 q 中, 构造体中的其他语句可以使用该结果 另外, 过程中还存在这样一个问题, 尽管某一个目标量并未编入过程的自变量表中, 但是过程中的目标量的值却发生着变化 例如, 过程中某一语句的代入操作可能使构造体中的一个信号量的值发生变化, 而这个信号量并未编入过程的自变量表中 再如, 有两个信号并未在过程的自变量表中说明, 但是在进程的过程调用中发生了代入操作 这样的信号量的代入操作都会带来问题, 因此在编写过程语句时应很好地注意这个问题, 不要使类似的问题发生 6. 块 (block) 语句在 节中已经介绍,block 语句是一个并发语句, 它所包含的一系列语句也是并发语句, 而且块语句中的并发语句的执行与次序无关 为便于 block 语句的使用, 这里再详细介绍一下 block 语句的书写格式 block 语句的书写格式一般为 : 86

87 标号 :block 块头 { 说明语句 }; { 并发处理语句 }; end block 标号名 ; 在这里, 块头主要用于信号的映射及参数的定义, 通常通过 generic 语句 genericmap 语句以及 port 语句和 portmap 语句来实现 块中的说明语句与构造体的说明语句相同, 主要是对该块所要用到的客体加以说明 可说明的项目有 : use 子句 ; 子程序说明及子程序体 ; 类型说明 ; 信号说明 ; 元件说明 block 语句常用于构造体的结构化描述 为了更好地了解 block 语句的使用方法, 这里再举一个使用实例 若要设计一个 CPU 芯片, 为简化起见, 假设这个 CPU 只由 ALU 模块和 reg8( 寄存器 ) 模块组成 ALU 模块和 reg8 模块的行为分别由两个 block 语句来描述, 每个模块相当于 CPU 电原理图中的子原理图 (reg8 模块又由 8 个 reg1,reg2,,reg8 子块构成 ) 在每个块内能够有局部信号 数据类型 常数等说明 任何一个客体既可以在构造体中说明, 也可以在块中说明, 如例 2-59 所示 例 2-59 library IEEE; use IEEE.std_logic_1164.all; package bit32 is type tw32 is array(31 downto 0) of std_logic; end bit32; use IEEE.std_logic_1164.all; use work.bit32.all; entity cpu is port(clk,interrupt:in std_logic; addr:out tw32;data: inout tw32); end cpu; architecture cpu_blk of cpu is signal ibus,dbus:tw32; 87

88 alu:block signal qbus:tw32; --alu 行为描述语句 end block alu; reg8:block signal zbus:tw32; reg1:block signal qbus:tw32; --reg1 行为描述语句 end block reg1; -- 其他 reg8 行为描述语句 end block reg8; end cpu_blk; 在例 2-59 中,CPU 模块有 4 个端口用做与外面的接口 其中 clk,interrupt 是输入端口,addr 是输出端口,data 是双向端口 这些信号对该实体的构造体中的所有 block 而言都是全局信号量, 因此, 都可以在 block 内使用 信号 ibus 和 dbus 是构造体 cpu_blk 中的局部信号量, 它只能在构造体 cpu_blk 中使用, 在构造体 cpu_blk 之外不能使用 只要是在 cpu_blk 构造体内, 无论在哪一个 block 块中这些信号量都是可以使用的 另外,block 块是可以嵌套的, 内层 block 块能够使用外层 block 块所说明的信号, 而外层 block 块却不能够使用内层 block 块中所说明的信号 例如, 例 2-59 中的 qbus 信号, 只在 alu 块中说明 因此, 它是 alu 块的局部信号量, 所有 alu 块中的语句都可以使用 qbus 信号, 但是在 alu 块之外则不能使用该信号, 如 reg8 块就不能使用 qbus 信号 再譬如,zbus 信号是 reg8 块说明的局部信号量,reg1 块是嵌套在 reg8 块中的内层块, 所以 reg1 块可以使用 zbus 信号 在 reg1 块的信号说明项中也有一个称为 qbus 的信号, 该信号与 ALU block 块中所说明的信号具有相同的名字 那么, 这样会不会引起问题呢? 事实上, 编译器将分别对这两个块区域中的同名信号加以说明, 同样也仅仅在这些范围中有效 因此, 可以这样认为, 它们是具有相同信号名的各自独立的信号 每个 qbus 只能在所说明的 block 块区域中使用 还有一种应该注意的情况如下所示 : 88 blk1:block signal qbus: tw32; blk2:block signal qbus: tw32;

89 --blk2 语句 end block blk2; --blk1 语句 end block blk1; 在上述的实例中, 信号 qbus 在两个块中都进行了说明 应注意的是, 这两个块是嵌套关系, 一个块包含另一个块 现在先来看一下 blk2 块对信号 qbus 的操作 第 1 种类型的操作是 blk2 中的语句对 blk2 中所说明的局部信号量 qbus 进行操作 ; 第 2 种类型的操作是 blk2 中的语句对 blk1 中所说明的局部信号量 qbus 进行操作 ( 由于 blk1 包含 blk2, 因此这种操作是允许的 ) 由此可见,blk1 块所说明的信号可以看做是 blk2 块内部说明的信号 如果名字相同, 可以在信号名字前面加块名字前缀 例如, 在本例中 blk1 块的 qbus 可以用 blk1_qbus 来表示 通常, 这种同名信号会使编程发生混乱 出现这个问题的起因是, 在有限时间内, 如果不对信号说明进行详细分析, 就不能保证正确地使用 qbus 信号 以上所提到的只是块本身内部所涉及的问题 但是, 块是一个独立的子结构, 它可以包含 port 和 generic 语句 因此, 设计者可以通过这两个语句将块内的信号变化传递给块的外部信号, 同样也可以将外部的信号变化传递给块的内部 port 和 generic 语句的这种性能, 将允许在一个新的设计中可重复使用 block 块 例如, 在例 2-59 中的 CPU 的设计中, 如果需要扩展 alu 部分的功能, 设计一个新的 alu 模块, 使其完成新的所需要的功能 在新的 CPU 模块中 port 名和 generic 名与原来的不一致, 此时, 如果在块中采用 port 和 generic 映射就可以顺利解决这个问题 也就是说, 在上例的基础上, 在设计中映射信号名和产生参数, 就可能建立一个新的 ALU 模块, 如例 2-60 所示 例 2-60 package math is type tw32 is array(31 downto 0) of std_logic; function tw_add(a,b:tw32) return tw32; function tw_sub(a,b:tw32) return tw32; end math; -- 加函数 -- 减函数 use work.math.all; use IEEE.std_logic_1164.all; entity cpu is port(clk,interrupt:in std_logic; add:out tw32; comt:in integer; data:inout tw32); end cpu; architecture cpu_blk of cpu is signal ibus,dbus:tw32; 89

90 alu:block port(abus,bbus:in tw32; d_out:out tw32; ctbus:in integer); port map(abus=>ibus,bbus=>dbus, d_out=>data,ctbus=>comt); signal qbus:tw32; d_out<=tw_add(abus,bbus)when ctbus=0 else tw_sub(abus,bbus)when ctbus=1 else abus; end block alu; end cpu_blk; 从例 2-60 可以看出, 除了端口和端口映射语句之外,alu 的说明部分和前面例子中所述的是一样的 端口语句说明了端口号和方向, 并且还说明了端口的数据类型 端口映射语句映射了带有信号的新的端口, 或者映射了 block 块外部的端口 例如, 本例中端口 abus 被映射到 cpu_blk 构造体中说明的局部信号 ibus; 端口 bbus 被映射到 dbus; 端口 d_out 和 ctbus 被映射到实体外部的端口 映射实现了端口和外部信号之间的连接, 使连接到端口的信号值发生变化, 由原来的值变成一个新的值 如果这种变化发生在 ibus 上, 则 ibus 上出现新的值, 将被传送到 alu 块内, 使得 abus 端口得到新的值 当然, 其他有映射关系的端口也应如此 90

91 第 3 章基本逻辑单元的 VHDL 模型 在前面几章中, 对 VHDL 语言的语句 语法及利用 VHDL 语言设计逻辑电路的基本方法做了详细介绍 为了使读者能深入理解使用 VHDL 语言设计逻辑电路的具体步骤和方法, 在本章以常用的基本逻辑电路设计为例, 再次对其进行详细介绍, 以使读者初步掌握用 VHDL 语言描述基本逻辑电路的方法 3.1 组合逻辑电路设计 在本节所要叙述的组合逻辑电路有简单门电路 选择器 译码器 三态门等 下面逐一地对它们进行介绍 基本逻辑门设计 简单门电路包括 2 输入 与非 门 集电极开路的 2 输入 与非 门 2 输入 或非 门 反相器 集电极开路的反相器 3 输入 与门 3 输入 与非 门 2 输入 或门 和 2 输入 异或 门等, 它们是构成所有逻辑电路的基本电路 1.2 输入 与非 门电路其逻辑电路图如图 3-1 所示 a b & & yy 图 输入 与非 门电路 利用 VHDL 语言描述 2 输入 与非 门有多种形式, 现举两个例子加以说明 例 3-1 library IEEE; use IEEE.std_logic_1164.all; entity nand2 is port(a,b:in std_logic; y:out std_logic); end nand2: architecture nand2_1 of nand2 is y<=a nand b; end nand2_1; 91

92 92 例 3-2 library IEEE; use IEEE.std_logic_1164.all; entity nand2 is port(a,b:in std_logic; y:out std_logic); end nand2; architecture nand2_2 of nand2 is t1: process(a,b) variable comb:std_logic_vector(1 downto 0); comb:=a&b; case comb is when"00"=>y<='1'; when"01"=>y<='1'; when"10"=>y<='1'; when"11"=>y<='0'; when others=>y<='x'; end case; end process t1; end nand2_2; 从上面两个例子可以看出, 例 3-1 的描述更简洁, 更接近于 2 输入 与非 门的行为描述, 因此也更易于阅读 例 3-2 的描述是以 2 输入 与非 门的真值表为依据来编写的, 它罗列了 2 输入 与非 门的每种输入状态及其对应的输出结果 集电极开路的 2 输入 与非 门和一般 2 输入 与非 门在 VHDL 语言的描述上没有什么差异, 所不同的只是从不同元件库中提取相应的电路而已 例如 : library std; use std.std_logic.all; use std.std_ttl.all; entity nand2 is Μ end nand2; library std; use std.std_logic.all; use std.std_ttl.all; entity nand1 is Μ

93 end nand1; 在第 1 个例子中要生成的是一般 TTL 的 2 输入 与非 门, 而在第 2 个例子中要生成的是 TTL 集电极开路的 2 输入 与非 门 这里所叙述的情况对其他门电路同样适用 因此, 在本节里对不同类型门电路的集电极开路输出门将不再赘述 2.2 输入 或非 门电路其逻辑电路如图 3-2 所示 a b 1 >=1 y 图 输入 或非 门电路 现举两个用 VHDL 语言描述 2 输入 或非 门电路的例子 例 3-3 library IEEE; use IEEE.std_logic_1164.all; entity nor2 is port(a,b:in std_logic; y:out std_logic); end nor2; architecture nor2_1 of nor2 is y<=a nor b; end nor2_1; 例 3-4 library IEEE; use IEEE.std_logic_1164.all entity nor2 is port(a,b:in std_logic; y:out std_logic); end nor2; architecture nor2_2 of nor2 is t2: process(a,b) variable comb:std_logic_vector (1 downto 0); comb:=a&b; case comb is when"00"=>y<='1'; when"01"=>y<='0'; 93

94 when"10"=>y<='0'; when"11"=>y<='0'; when others=>y<='x'; end case; end process t2; end nor2_2; 3. 反相器反相器电路的逻辑电路图如图 3-3 所示 a 1 y 图 3-3 反相器电路 VHDL 语言对反相器的描述如例 3-5 和例 3-6 所示 例 3-5 library IEEE; use IEEE.std_logic_1164.all; entity inverter is port (a:in std_logic); y:out std_logic); end inverter; architecture inverter_1 of inverter is y<=not a; end inverter_1; 例 3-6 library IEEE; use IEEE.std_logic_1164.all; entity inverter is port(a:in std_logic; y:out std_logic); end inverter; 94 architecture inverter_2 of inverter is t3: process(a) if (a='1') then

95 y<='0' else y<='1' end if end process t3; end inverter_2; 4.3 输入 与非 门电路 3 输入 与非 门和 2 输入 与非 门的差异仅在于多了一个输入引脚, 在用 VHDL 语言编程时, 在端口说明中应加一个输入端口 例如, 原来的输入端口为 a,b 两个, 现在应变为 a,b,c 3 个 当然, 根据逻辑表达式, 该输入端口的信号 c 应与 a 和 b 一样, 一起参与逻辑运算, 以得到最后的输出 y a b c & >=1 y 例 3-7 library IEEE; use IEEE.std_logic_1164.all; entity nand3 is port(a,b,c:in std_logic; y:out std_logic); end nand3; architecture nand3_1 of nand3 is y<=not (a and b and c); end nand3_1; 图 输入 与非 门电路 例 3-8 library IEEE; use IEEE.std_logic_1164.all; entity nand3 is port(a,b,c:in std_logic; y:out std_logic); end nand3; architecture nand3_2 of nand3 is t4: process(a,b,c) variable comb:std_logic_vector (2 downto 0); 95

96 comb:=a & b & c; case comb is when"000"=>y<='1'; when"001"=>y<='1'; when"010"=>y<='1'; when"011"=>y<='1'; when"100"=>y<='1'; when"101"=>y<='1'; when"110"=>y<='1'; when"111"=>y<='0'; when others=>y<='x'; end case; end process t4; end nand3_2; 5.2 输入 异或 门电路 2 输入 异或 门电路的逻辑表达式为 : y=a b 其逻辑电路如图 3-5 所示 a b =1 =1 y 96 图 输入 异或 门电路 用 VHDL 语言描述 2 输入 异或 门的程序如例 3-9 例 3-10 所示 例 3-9 library IEEE; use IEEE.std_logic_1164.all; entity xor2 is port(a,b:in std_logic; y:out std_logic); end xor2; architecture xor2_1 of xor2 is y<=a xor b; end xor2_1; 例 3-10 library IEEE; use IEEE.std_logic_1164.all; entity xor2 is

97 port(a,b:in std_logic; y:out std_logic); end xor2; architecture xor2_2 of xor2 is t5: process(a,b) variable comb:std_logic_vector(1 downto 0); comb:=a & b; case comb is when"00"=>y<='0'; when"01"=>y<='1'; when"10"=>y<='1'; when"11"=>y<='0'; when others=>y<='x'; end case; end process t5; end xor2_2; 上述简单的门电路大多用两种不同形式的 VHDL 语言程序来描述, 其行为和功能是完全一样的 事实上还可以运用 VHDL 语言中所给出的其他语句来描述这些门电路, 这就给编程人提供了较大的编程灵活性 但是, 一般来说, 无论是编程人员还是阅读这些程序的人员, 都希望能一目了然, 因此尽可能采用 VHDL 语言中所提供的语言和符号, 用简捷的语句描述其行为 编 译码器与选择器 编 译码器和选择器是组合电路中较简单的 3 种通用电路 它们可以由简单的门电路组合连接而构成 例如, 图 3-6 所示是一个 3-8 译码器电路 (74LS138) 由有关手册可知, 该译码器由 8 个 3 输入 与非 门 4 个反相器和 1 个 3 输入 或非 门构成 如果事先不做说明, 只给出电路, 让读者来判读该电路的功能, 那么毋庸置疑, 要读懂该电路就要花较多的时间 如果采用 VHDL 语言, 从行为 功能来对 3-8 译码器进行描述, 不仅逻辑设计变得非常容易, 而且阅读也会很方便 译码器 3-8 译码器是一种常用的小规模集成电路, 它有 3 个二进制输入端 a b c 和 8 个译码器输出端 y 0 ~y 7 对输入 a b c 的值进行译码, 就可以确定输出端 y 0 ~ y 7 的哪一个输出端变为有效 ( 低电平 ), 从而达到译码的目的 3-8 译码器还有 3 个选通输入端 g 1 g 2a 和 g 2b 只有在 g 1 =1,g 2a =0,g 2b =0 时,3-8 译码器才进行正常译码, 否则 y 0 ~y 7 输出将均为高电平 97

98 图 译码器电路 用 VHDL 语句描述的 3-8 译码器如例 3-11 所示 例 3-11 library IEEE; use IEEE.std_logic_1164.all; entity decoder_3_to_8 is port(a,b,c,g1,g2a,g2b:in std_logic; y: out std_logic_vector(7 downto 0)); end decoder_3_to_8; 98 architecture rtl of decoder_3_to_8 is signal indata:std_logic_vector (2 downto 0); indata<=c & b& a; process(indata,g1,g2a,g2b) if (g1='1' and g2a='0' and g2b='0') then case indata is when"000"=>y<=" "; when"001"=>y<=" "; when"010"=>y<=" "; when"011"=>y<=" "; when"100"=>y<=" "; when"101"=>y<=" "; when"110"=>y<=" "; when"111"=>y<=" "; when others=>y<="xxxxxxxx"; end case; else y<=" ";

99 end if; end process; end rtl; 2. 优先级编码器优先级编码器常用于中断的优先级控制 例如,74LS148 是 1 个 8 输入 3 位二进制码输出的优先级编码器 当其某一个输入有效时, 就可以输出一个对应的 3 位二进制编码 另外, 当同时有多个输入有效时, 将输出优先级最高的那个输入所对应的二进制编码 图 3-7 就是优先级编码器的引脚图, 它有 8 个输入 input(0)~input(7) 和 3 位二进制输出 y(0)~y(2) 用 VHDL 语言描述优先级编码器的程序如例 3-12 所示 : input(0) input(1) 优input(2) 优先input(3) 先级级 input(4) 编 input(5) 码 input(6) 器 input(7) y(0) y0 y(1) y1 y(2) y2 图 3-7 优先级编码器电路 例 3-12 library IEEE; use IEEE.std_logic_1164.all; entity priorityencoder is port (input:in std_logic_vector(7 downto 0); y:out std_logic_vector(2 downto 0); end priorityencoder; architecture rtl of priorityencoder is if (input(0)= '0') then y<="111"; elsif (input(1)= '0') then y<="110"; elsif (input(2)= '0') then y<="101" elsif (input(3)= '0') then y<="100"; elsif (input(4)= '0') then y<="011"; elsif (input (5)= '0') then 99

100 y<="010"; elsif (input(6)= '0') then y<="001"; elsif y<="000"; end if; end rtl; 由于 VHDL 语言中目前还不能描述任意项, 所以不能用前面一贯采用的 case 语句来描述, 而是采用 if 语句 3.4 选 1 选择器选择器常用于信号的切换,4 选 1 选择器可以用于 4 路信号的切换 4 选 1 选择器有 4 个信号输入端 input(0)~input(3) 两个选择信号 a 和 b 及一个信号输出端 y 当 a b 输入不同的选择信号时, 就使 input(0)~input(3) 中某个相应的输入信号与输出 y 端接通 例如, 当 a=b='0' 时,input(0) 就与 y 接通 其逻辑电路如图 3-8 所示 用 VHDL 语言对它进行描述, 就可以得到如例 3-13 所示的程序 input(0) input(1) input(2) input(3) a b 4 选 1 选择器 4 选1 电路y1 100 图 选 1 选择器 例 3-13 library IEEE; use IEEE.std_logic_1164.all; entity mux4 is port (input:in std_logic_vector (3 downto 0); a,b:in std _logic; y:out std_logic); end mux4: architecture rtl of mux4 is signal sel:std_logic_vector(1 downto 0); sel<=b & a; process(input,sel) if (sel="00") then

101 y<=input(0); elsif (sel="01") then y<=input(1); elsif (sel="10") then y<=input(2); else y<=input(3); end if; end process; end rtl; 例 3-13 的 4 选 1 选择器是用 if 语句描述的, 程序中的 else 项作为余下的条件, 将选择 input(3) 从 y 端输出, 这种描述比较安全 当然, 不用 else 项也可以, 这时必须列出 sel 的所有可能出现的情况, 加以一一确认 加法器和求补器 1. 加法器加法器有全加器和半加器之分, 全加器可以用两个半加器构成, 因此下面先以半加器为例加以说明 半加器有两个二进制 1 位的输入端 a 和 b 以及 1 位和的输出端 s 和 1 位进位的输出端 co 其电路符号如图 3-9 所示 用 VHDL 语言描述半加器的程序如例 3-14 所示 Σb 半加 co a 半s 器 例 3-14 library IEEE; use IEEE.std_logic_1164.all; entity half_adder is port (a,b:in std_logic;) s,co:out std_logic); end half_adder; architecture half1 of half_adder is signal c,d:std_logic; c<=a or b; 图 3-9 半加器电路 101

102 d<=a nand b; co<=not d; s<=c and d; end half1; 用两个半加器可以构成一个全加器, 全加器的电路如图 3-10 所示 U0 u0 Σa 半加半 b 器加器 cin U1 u1 Σu0_s 半半加器加 U2 u2 器 >=1 1 u0_co s co 图 3-10 用两个半加器构成的全加器 基于半加器的描述, 若采用 component 语句和 port map 语句就很容易编写出描述全加器的程序 例 3-15 library IEEE; use IEEE.std_logic_1164.all; entity full_adder is port (a,b,cin:in std_logic; s,co:out std_logic); end full_adder; archtecture full of full_adder is component half_adder port(a,b:in std_logic; s,co:out std_logic); end component; signal u0_co,u0_s,u1_co:std_logic; u0:half_adder port map(a,b,u0_s,u0_co); u1:half_adder port map(u0_s,cin,s,u1_co); co<=u0_co or u1_co; end full; 2. 求补器二进制运算经常要用到求补的操作,8 位二进制数的求补电路如图 3-11 所示 102

103 a(0) a(1) a(2) a(3) a(4) a(5) a(6) a(7) 8 位求补电路 8 位求补电路b(0) b(1) b(2) b(3) b(4) b(5) b(6) b(7) 图 位二进制数的求补电路 求补电路的输入为 a(0)~a(7), 补码输出为 b(0)~b(7), 其中 a(7) 和 b(7) 为符号位 该电路较复杂, 如果像半加器那样, 对每个门进行描述连接是可以做到的, 但是太烦琐了 这里采用 rtl 描述更加简洁 清楚 例 3-16 library IEEE; use IEEE.std_logic_1164.all; entity house is port (a: in std_logic_vector(7 downto 0); b:out std_logic_vector (7 downto 0)); end house; architecture rtl of house is b<=not a+'1'; end rtl; 三态门及总线缓冲器 三态门和双向缓冲器是接口电路和总线驱动电路经常用到的器件 它们虽然不属于组合电路, 为简化章节, 也列于此进行叙述 1. 三态门电路三态门的电原理图如图 3-12 所示 它具有一个数据输入端 din, 一个数据输出端 dout 和一个控制端 en 当 en='1' 时,dout=din; 当 en='0' 时,dout='z'( 高阻 ) din en Δ dout 图 3-12 三态门电路 103

104 用 VHDL 语言描述的三态门的程序实例如例 3-17 所示 例 3-17 library IEEE; use IEEE.std_logic_1164.all; entity tri_gate is port (din,en:in std_logic; dout:out std_logic); end tri_gate; architecture zas of tri_gate is tri_gate1:process(din,en) if (en='1') then dout<=din; else dout<='z'; end if; end process tri_gate1; end zas; 在第 2 章中读者已经知道, 一个实体可以对应多种构造体, 例 3-18 和例 3-19 就是用不同的 VHDL 语句描述的三态门结构 例 3-18 architecture blk of tri_gate is tri_gate2:block(en='1') dout<=guarded din; end block tri_gate2; end blk; 例 3-18 中采用卫式块语句结构来表示三态门 卫式块语句结构的特点是, 只有块语句的条件满足时, 块中所含的语句才会被执行 在这里只有 en='1' 的条件满足时,dout<= guarded din 语句才会被执行 例 3-19 architecture nas of tri_gate is tri_gate3:process(din,en) 104

105 case en is when'1'=>dout<=din; when others=>dout<='z'; end case; end process tri_gate3; end nas; 例 3-19 中, 当 en ='1' 时, 使 dout 和 din 的信号保持一致, 否则就将 'z' 波形赋予 dout, 也就是说 dout 不受 din 的影响 2. 单向总线缓冲器在微型计算机的总线驱动中经常要用单向总线缓冲器, 它通常由多个三态门组成, 用来驱动地址总线和控制总线 一个 8 位的单向总线缓冲器如图 3-13 所示 8 位单向总线缓冲器由 8 个三态门组成, 具有 8 个输入端和 8 个输出端 所有的三态门的控制端连在一起, 由一个控制输入端 en 控制 图 3-13 单向总线缓冲器 用 VHDL 语言描述的 8 位单向总线缓冲器的程序如例 3-20 例 3-21 和例 3-22 所示 例 3-20 library IEEE; use IEEE.std_logic_1164.all; entity tri_buf8 is port(din:in std_logic_vector(7 downto 0); dout:out std_logic_vector(7 downto 0); en:in std_logic); end tri_buf8; architecture zas of tri_buf8 is bti_buff:process(en,din) if (en='1') then 105

106 106 dout<=din; else dout<="zzzzzzzz"; end if end process tri_buff; end zas; 例 3-21 architecture blk of tri_buf8 is tri_buff:block(en='1') dout<=guarded din; end block tri_buff; end blk; 例 3-22 architecture nas of tri_buf8 is tri_buff: process(en,din) case en is when'1'=>dout<=din; when others=>dout<="zzzzzzzz"; end case; end process tri_buff; end nas; 在编写上述程序时应注意, 不能将 'z' 值赋予变量, 否则就不能进行逻辑综合 另外, 对信号赋值时,'z' 和 '0' 或 '1' 不能混合使用, 如 : dout<="z001zzzz"; 这样的语句是不允许出现的 但是变换赋值表达式, 分开赋值是可以的 例如 : dout(7)<='z'; dout(6 downto 4)<= "001"; dout(3 downto 0)<= "zzzz"; 3. 双向总线缓冲器双向总线缓冲器用于对数据总线的驱动和缓冲, 典型的双向总线缓冲器的电路图如图 3-14 所示 图中的双向缓冲器有两个数据输入输出端 a 和 b, 一个方向控制端 dr 和一个选通端 en 当 en=1 时, 双向总线缓冲器未被选通,a 和 b 都呈现高阻 当 en=0 时, 双向总线缓冲器被选通, 如果 dr=0, 那么 a<=b; 如果 dr=1, 那么 b<=a 用 VHDL 语言描述的双向总线缓冲器的程序如例 3-23 所示

107 a dr en Δ 双向总线缓冲器 Δ b 图 3-14 双向总线缓冲器 例 3-23 library IEEE; use IEEE.std_logic_1164.all; entity tri_bigate is port(a,b:inout std_logic_vector(7 downto 0); en:in std_logic; dr:in std_logic); end tri_bigate; architecture rtl of tri_bigate signal aout,bout:std_logic_vector(7 downto 0); process(a,dr,en) if (en='0')and(dr='1') then bout<=a; else bout<="zzzzzzzz"; end if; b<=bout; end process; process(b,dr,en) if (en='0') and (dr='0') then aout<=b; else aout<="zzzzzzzz"; end if a<=aout; end process; end rtl; 107

108 从例 3-23 可以看出, 双向总线缓冲器由两组三态门组成, 利用信号 aout 和 bout 将两组三态门连接起来 由于在实际工作过程中 a 和 b 都不可能同时出现 '0' '1', 故在这里不需要定义决断函数 3.2 时序电路设计 在本节的时序电路设计中主要介绍触发器 寄存器和计数器 在介绍这些电路以前, 先说明一下时钟信号和复位信号的描述 时钟信号和复位信号 1. 时钟信号描述众所周知, 任何时序电路都以时钟信号为驱动信号, 时序电路只是在时钟信号的边沿到来时, 其状态才发生改变 因此, 时钟信号通常是描述时序电路的程序的执行条件 另外, 时序电路也总是以时钟进程形式来进行描述的, 其描述方式一般有两种 (1) 进程的敏感信号是时钟信号在这种情况下, 时钟信号应作为敏感信号, 显式地出现在 process 语句后跟的括号中, 例如 process(clock_signal) 时钟信号边沿的到来, 将作为时序电路语句执行的条件, 如例 3-24 所示 : 例 3-24 process(clock_signal) if (clock_edge_condition) then signal_out<=signal_in; Μ 其他时序语句 Μ end if; end process; 例 3-24 程序说明, 该进程在时钟信号 clock_signal 发生变化时被启动, 而在时钟边沿的条件得到满足后才真正执行时序电路所对应的语句 (2) 用进程中的 wait on 语句等待时钟在这种情况下, 描述时序电路的进程将没有敏感信号, 而是用 wait on 语句来控制进程的执行 也就是说, 进程通常停留在 wait on 语句上, 只有在时钟信号到来 且满足边沿条件时, 其余的语句才能执行, 如例 3-25 所示 例 3-25 process 108

109 wait on(clock_signal)until(clock_edge_condition); signal_out<=signal_in; Μ 其他时序语句 Μ end process; 在编写上述两个程序时应注意 : 无论 if 语句还是 wait on 语句, 在对时钟边沿说明时, 一定要注明是上升沿还是下降沿 ( 前沿还是后沿 ), 只说明是边沿是不行的 当时钟信号作为进程的敏感信号时, 在敏感信号的表中不能出现一个以上的时钟信号, 除时钟信号以外, 像复位信号等是可以和时钟信号一起出现在敏感表中的 wait on 语句只能放在进程的最前面或者最后面 (3) 时钟边沿的描述为了描述时钟边沿, 一定要指定是上升沿还是下降沿, 这一点可以使用时钟信号的属性来达到 也就是说, 由时钟信号的值是从 '0' 到 '1' 的变化, 还是从 '1' 到 '0' 的变化, 来判断是时钟脉冲信号的上升沿还是下降沿 时钟脉冲的上升沿描述时钟脉冲上升沿波形与时钟信号属性的描述关系如图 3-15 所示 从图 3-15 中可以看到, 时钟信号起始值为 '0', 故其属性值 clk'last_value='0'; 上升沿的到来表示发生了一个事件, 故用 clk'event 表示 ; 上升沿以后, 时钟信号的值为 '1', 故其当前值为 clk='1' 这样, 表示上升沿到来的条件可写为 : if clk='1'and clk'last_vaule='0'and clk'event 图 3-15 时钟脉冲上升沿波形和 时钟信号属性描述关系 图 3-16 时钟脉冲下降沿波形和 时钟信号属性描述关系 时钟脉冲下降沿描述时钟脉冲下降沿波形与时钟信号属性的描述关系如图 3-16 所示 其关系与图 3-15 相似, 此时 clk'last_value='1'; 时钟信号当前值为 clk='0'; 下降沿到来的事件为 clk'event 这样, 表示下降沿到来的条件可写为 : if clk='0' and clk'last_value='1'and clk'event 根据上面的上升沿和下降沿的描述, 时钟信号边沿检出条件可以统一描述如下 : if clock_signal=current_value and 109

110 110 clock_signal'last_value and clock_signal'event 在某些书刊中边沿检出条件也可简写为 : if clock_signal=current_ualue and clock_signal'event 2. 触发器的同步和非同步复位触发器的初始状态应由复位信号来设置 根据复位信号对触发器复位的操作不同, 可以分为人为同步复位和非同步复位两种 所谓同步复位, 就是当复位信号有效且在给定的时钟边沿到来时, 触发器才被复位 ; 而非同步复位则是, 一旦复位信号有效, 触发器就被复位 (1) 同步复位在用 VHDL 语言描述时, 同步复位一定在以时钟为敏感信号的进程中定义, 且用 if 语句来描述必要的复位条件 下面两个例子就是同步复位方式的程序实例 例 3-26 process(clock_signal) if (clock_edge_condition) then if (reset_condition) then signal_out<=reset_value; else signal_out<=signal_in; Μ 其他时序语句 Μ end if; end if; end process; 例 3-27 process wait on(clock_signal)until(clock_edge_condition) if (reset_condition) then signal_out<=reset_value; else signal_out<=signal_in; Μ 其他时序语句 Μ end if; end process;

111 (2) 非同步复位非同步复位又称异步复位, 在描述时与同步方式不同 : 首先, 在进程的敏感信号中除时钟信号外, 还应加上复位信号 ; 其次, 用 if 语句描述复位条件 ; 最后, 在 elsif 段描述时钟信号边沿的条件, 并加上 event 属性 其描述方式如例 3-28 所示 例 3-28 process(reset_signal,clock_signal) if (reset_condition) then signal_out<=reset_value; elsif (clock_event and clock_edge_condition) then signal_out<=signal_in; Μ end if end process; 从例 3-28 可以看到, 非同步时的信号和变量的代入和赋值必须在时钟信号边沿有效的范围内进行, 如例 3-27 中的 elsif 后进行的那样 另外, 添加 clock_event 是为了防止没有时钟事件发生时的误操作 譬如, 现在时钟事件没有发生而只是发生了复位事件, 这样该进程就得到了相应的启动 在此情况下, 若复位条件没有满足, 而时钟边沿条件却是满足的, 那么与时钟信号有关的那一段程序 (elsif 段 ) 就会得到执行, 从而造成错误操作 触发器 触发器种类很多, 这里仅举常用的几种加以说明 1. 锁存器锁存器根据触发边沿 复位和预置的方式以及输出端多少的不同也可以有多种不同形式的锁存器 (1)D 锁存器 正沿 ( 上升沿 ) 触发的 D 锁存器的电路如图 3-17 所示 它是一个正沿触发的 D 触发器, 有一个数据输入端 D, 一个时钟输入端 clk 和一个数据输出端 q 用 VHDL 语言描述该 D 锁存器的程序如例 3-29 和例 3-30 所示 d D q clk 例 3-29 library IEEE; use IEEE.std_logic_1164.all; entity dff1 is port(clk,d:in std_logic; 图 3-17 D 锁存器 111

112 q:out std_logic); end dff1; architecture rtl of dff1 is process(clk) if (clk'event and clk='1') then q<=d; end if; end process; end rtl; 例 3-30 library IEEE; use IEEE.std_logic_1164.all; entity dff1 is port(clk,d:in std_logic; q:out std_logic); end dff1; architecture rtl of dff1 is process wait until clk'event and clk='1'; q<=d; end process; end rtl; 例 3-29 和例 3-30 是对时钟信号边沿利用前述 CLR D CLK Q 的不同方法描述时所得到的两个不同程序 程序中描述的是正沿触发, 如果要改成下降沿触发, 只要对条件进行如下改动即可 : if(clk' event and clk='0') (2) 非同步复位的 D 锁存器 图 3-18 非同步复位的 D 锁存器 非同步复位的 D 锁存器的电路符号如图 3-18 所示 它和一般的 D 锁存器的区别是多了一个复位输入端 clr 当 clr='0' 时, 其 q 端输出被 强迫置为 '0', 故 clr 又称清零输入端 用 VHDL 语言描述的非同步复位的 D 锁存器如例 3-31 所示 例 3-31 library IEEE; use IEEE.std_logic_1164.all; 112

113 entity dff2 is port (clk,d,clr:in std_logic; q:out std_logic); end dff2; architecture rtl of dff2 is process(clk,clr) if(clr='0')then q<='0'; elsif (clk'event and clk='1') then q<=d; end if; end process; end rtl; (3) 非同步复位 / 置位 D 锁存器非同步复位 / 置位 D 锁存器的电路符号如图 3-19 所示 除了前述的 D clk 和 q 端外, 还有 clr 和 pset 等复位 置位端 当 clr ='0' 时复位, 使 q ='0'; 当 pset ='0' 时置位, 使 q ='1' 用 VHDL 语言描述的非同步复位 / 置位锁存器的程序如例 3-32 所示 例 3-32 library IEEE; use IEEE.std_logic_1164.all; entity dff3 is port(clk,d,clr,pset:in std_logic; q:out std_logic); end dff3; architecture rtl of dff3 is process(clk,pset,clr) if (pset='0') then q<='1'; elsif (clr='0') then q<='0'; elsif (clk'event and clk='1') then q<=d; 图 3-19 非同步复位 / 置位 D 锁存器 113

114 end if; end process; end rtl; 从例 3-32 可以看出, 事件的优先级是置位最高, 复位次之, 而时钟最低 这样, 当 pset=0 时, 无论 clk 是什么状态,q 一定被置为 1 (4) 同步复位的 D 锁存器同步复位的 D 锁存器的电路如图 3-20 所示 与非同步方式不同的是, 当复位信号 clr 有效 (clr='1') 以后, 只有在有效时钟边沿到来时才能进行复位操作 图 3-20 中 clr='1' 以后, 在 clk 的上升沿到来时,q 输出才变为 0 另外, 从图中还可以看出复位信号的优先级比 D 端的数据输入高, 也就是说当 clr ='1' 时, 无论 D 端输入什么信号, 在 clk 的上升沿到来时,q 输出总是为 图 3-20 同步复位 / 置位 D 锁存器 用 VHDL 语言描述的同步复位 D 锁存器的程序如例 3-33 所示 例 3-33 library IEEE; use IEEE.std_logic_1164.all; entity dff4 is port(clk,clr,d:in std_logic; q:out std_logic); end dff4; architecture rtl of dff4 is process(clk) if (clk'event and clk='1') then if (clr='1') then q<='0'; else q<=d; end if; end if; end process;

115 end rtl; 2.JK 触发器带有复位 / 置位功能的 JK 触发器电路如图 3-21 所示 JK 触发器的输入端有置位输入 pset 复位输入 clr 控制输入 J 和 K 以及时钟信号输入 clk; 输出端有正向输出端 q 和反向输出端 qb 用 VHDL 语言描述 JK 触发器的程序如例 3-34 所示 图 3-21 JK 触发器 例 3-34 library IEEE; use IEEE.std_logic_1164.all; entity jkdff is port(pset,clr,clk,j,k:in std_logic; q,qb:out std_logic); end jkdff; architecture rtl of jkdff is signal q_s,qb_s:std_logic; process(pset,clr,clk,j,k) if (pset=' 0') then q_s<='1'; qb_s<='0'; elsif (clr='0') then q_s<='0'; qb_s<='1'; elsif (clk'event and clk='1') then if (j='0')and(k='1') then q_s<='0'; qb_s<='1'; elsif (j='1')and(k='0') then q_s<='1'; qb_s<='0'; elsif (j='1')and (k='1') then 115

116 q_s<=not q_s; qb_s<=not qb_s; end if; q<=q_s; qb<=qb_s; end if; end process; end rtl; 例 3-34 中的复位和置位显然也是非同步的, 且 pset 的优先级比 clr 高, 也就是说当 pset='0', 且 clr='0' 时,q 将输出 '1',qb 输出为 '0' 为了避免这种情况, 程序可以改写成例 3-35 所示 例 3-35 architecture rtl of jkdff is signal q_s,qb_s;std_logic; process(pset,clr,clk,j,k) if (pset='0')and(clr='1') then q_s<='1'; qb_s<='0'; elsif (pset='1')and(clr='0') then q_s<='0'; qb_s<='1'; elsif (clk'event and clk='1') then if (j='0')and(k='1') then q_s<='0'; qb_s<='1'; elsif (j='1')and(k='0') then q_s<='1'; qb_s<='0'; elsif (j='1')and(k='1') then q_s<=not q_s; qb_s<=not qb_s; end if; end if; q<=q_s; qb<=qb_s; end process; end rtl; 116

117 在例 3-35 中 pset='0',clr='0' 这种情况未加考虑, 那么在逻辑综合时, 其输出是未知的 寄存器 寄存器一般由多个触发器连接而成, 通常有锁存寄存器和移位寄存器等 下面主要介绍一些移位寄存器的实例 1. 串行输入 串行输出移位寄存器串行输入 串行输出移位寄存器的电原理图如图 3-22 所示 它具有 1 个数据输入端 a 1 个时钟输入端 clk 和 1 个数据输出端 b 图中所示的是 8 位的串行移位寄存器, 在时钟信号作用下, 前级的数据向后级移动 该 8 位移位寄存器由 8 个 D 触发器构成 正如第 5 章所述, 利用 generate 语句和 D 触发器的描述很容易写出 8 位移位寄存器的 VHDL 语言程序, 如例 3-36 所示 图 3-22 串行输入, 串行输出的 8 位移位寄存器 例 3-36 library IEEE; use IEEE.std_logic_1164.all; entity shift8 is port (a,clk:in std_logic; b:out std_logic); end shift8; architecture sample of shift8 is component dff port(d,clk:in std_logic; q:out std_logic); end component; signal z:std_logic_vector(0 to 8); z(0)<=a; g1:for i in 0 to 7 generate dffx:dff port map(z(i),clk,z(i+1)); end generate; b<=z(8); end sample; 117

118 在例 3-36 中, 把 dff 看做已经生成的元件, 然后利用 generate 来循环生成串行连接的 8 个 D 触发器 8 位移位寄存器也可以直接利用信号来连接, 其 VHDL 描述如例 3-37 所示 例 3-37 library IEEE; use IEEE.std_logic_1164.all; entity shift8 is port(a,clk:in std_logic; b:out std_logic); end shift8; architecture rtl of shift8 is signal dfo_1,dfo_2,dfo_3,dfo_4,dfo_5,dfo_6,dfo_7,dfo_8:std_logic; process(clk) if (clk'event and clk='1') then dfo_1<=a; dfo_2<=dfo_1; dfo_3<=dfo_2; dfo_4<=dfo_3; dfo_5<=dfo_4; dfo_6<=dfo_5; dfo_7<=dfo_6; dfo_8<=dfo_7; b<=dfo_8; end if; end process; end rtl; 在第 2 章里已经提到了变量赋值和信号代入的区别, 其中特别强调了 : 即使执行了信号代入语句, 被代入的信号量的值在当时并没有发生改变, 直到进程结束, 代入过程才同时发生 因此, 例 3-37 这样描述是正确的 如果将例 3-37 中的信号量改成变量, 代入符 <= 改成赋值符 :=, 那么该程序所描述的是否仍是一个 8 位移位寄存器? 这一点请读者根据已学知识进行思考 2. 循环移位寄存器在计算机的运算操作中经常用到循环移位, 它可以用硬件电路来实现 8 位循环左移的寄存器电路如图 3-23 所示 该电路有 8 个数据输入端 din(0)~din(7), 移位和数据输出控制端 end, 时钟信号输入端 clk, 移位位数控制输入端 s(0) ~s(2),8 位数据输出端 dout(0) ~dout(7) 118

119 图 位循环移位寄存器 当 end=1 时, 根据 s(0)~s(2) 输入的数值, 确定在时钟脉冲作用下, 循环左移几位 当 end=0 时,din 直接输出至 dout 为了生成 8 位循环左移位的寄存器, 在对其进行描述时要调用包集合 cpac 中的循环左移过程 在 cpac 中, 该过程的描述如例 3-38 所示 例 3-38 libary IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; use IEEE.std_logic_unsigned.all; package cpac is procedure shift(din,s:in std_logic_vector; signal dout:out std_logic_vector); end cpac; package body cpac is procedure shift(din,s:in std_logic_vector; signal dout:out std_logic_vector)is variable sc:integer; sc:=conv_integer(s); for i in din'range loop if (sc+i<=din'left) then dout(sc+i)<=din(i); else dout(sc+i-din'left)<=din(i); end if; end loop; end shift; end cpac; 利用该移位过程就可以描述 8 位的循环左移寄存器, 具体如例 3-39 所示 119

120 例 3-39 library IEEE; use IEEE.std_logic_1164.all; use work.cpac.all; entity bsr is port (din:in std_logic_vector(7 downto 0); s:in std_logic_vector(2 downto 0); clk, enb:in std_logic; dout:out std_logic_vector(7 downto 0)); end bsr; architecture rtl of bsr is process(clk) if (clk'event and clk='1') then if (enb='0') then dout<=din; else shift(din,s,dout); end if; end if; end process; end rtl; 该程序经逻辑综合所生成的电路较复杂 3. 带清零端的 8 位并行加载移位寄存器该移位寄存器就是 TTL 手册中的 74166, 其引脚图如图 3-24 所示 q clr s/l clk fe se a b c d e f g h 图 3-24 带清零端的 8 位并行加载移位寄存器图中各引脚名称及功能如下 : a,b,,h 8 位并行数据输入端 ; se 串行数据输入端 ; q 串行数据输出端 ; clk 时钟信号输入端 ; 120

121 fe 时钟信号禁止端 ; s/l 移位加载控制端 ; clr 清零端 当清零输入端 clr 为 '0' 时,8 个触发器的输出均为 '0', 从而使 q 输出也为 '0' fe 是时钟禁止端, 当它为 '1' 时将禁止时钟, 即不管时钟信号如何变化, 移位寄存器的状态不发生改变 另外, 时钟信号只在上升沿时才有效, 即 fe='0' 当控制端 s/l ='1' 时是移位状态, 在时钟脉冲上升沿的控制下, 向右移 1 位, 串行输入端 se 的信号将移入 qa 位, 而 q 的输出将是移位前的内部 qh 输出 当 s/l='0' 时是加载状态, 在时钟脉冲上升沿的作用下, 数据输入端 a,b 的信号就装载到移位寄存器的 qa~qh 根据上述描述, 就可以用 VHDL 语言编写出描述 功能的程序, 如例 3-40 所示 例 3-40 library IEEE; use IEEE.std_logic_1164.all; entity sreg8parlwclr is port (clr,sl,fe,clk,se,a,b,c,d,e,f, g,h:in std_logic; q:out std_logic); end sreg8parlwclr; architecture behav of sreg8parlwclr is signal tmpreg8:std_logic_vector (7 downto 0); process(clk,sl,fe,clr) if (clr='0') then tmpreg8<=" "; q<=tmpreg8(7); elsif (clk'event)and(clk='1')and(fe='0') then if (sl='0') then tmpreg8(0)<=a; tmpreg8(1)<=b; tmpreg8(2)<=c; tmpreg8(3)<=d; tmpreg8(4)<=e; tmpreg8(5)<=f; tmpreg8(6)<=g; tmpreg8(7)<=h; q<=tmpreg8(7); elsif (sl='1') then for i in tmpreg8'high downto tmpreg8'low+1 loop tmpreg8(i)<=tmpreg8(i-1); end loop; 121

122 tmpreg8(tmpreg8'low)<=se; q<=tmpreg8(7); end if; end if; end process; end behav; 计数器 计数器分同步计数器和异步计数器两种, 如果按工作原理和使用情况来分那就更多了 计数器是一个典型的时序电路, 分析计数器就能更好地了解时序电路的特性 1. 同步计数器所谓同步计数器, 就是在时钟脉冲 ( 计数脉冲 ) 的控制下, 构成计数器的各触发器的状态同时发生变化的那一类计数器 (1) 带允许端的十二进制计数器该计数器由 4 个触发器构成,clr 输入端用于清零,en 端用于控制计数器工作,clk 为时钟脉冲 ( 计数脉冲 ) 输入端,qa qb qc qd 为计数器的 4 位二进制计数值输出端 该十二进制计数器用 VHDL 语言描述的程序如例 3-41 所示 例 3-41 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity count12en is port(clk,clr,en:in std_logic; qa,qb,qc,qd:out std_logic); end count12en; architecture rtl of count12en is signal count_4:std_logic_vector(3 downto 0); qa<=count_4(0); qb<=count_4(1); qc<=count_4(2); qd<=count_4(3); process(clk,clr) if (clr='1') then count_4<="0000"; elsif (clk'event and clk='1') then if (en='1') then if (count_4="1011") then 122

123 count_4<="0000"; else count_4<=count_4+'1'; end if; end if; end if; end process; end rtl; 该程序对应的电路的引脚图如图 3-25 所示 qa qb qc qd clr clk en 图 3-25 带允许端的十二进制计数器电路 (2) 可逆计数器所谓可逆计数器, 就是根据计数控制信号的不同, 在时钟脉冲作用下, 计数器可以进行加 1 操作或者减 1 操作的一种计数器 可逆计数器有一个特殊的控制端, 这就是 updn 端 当 updn ='1' 时, 计数器进行加 1 操作 ; 当 updn ='0' 时, 计数器就进行减 1 操作 用 VHDL 语言所描述的 6 位二进制可逆计数器的程序如例 3-42 所示 例 3-42 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity updncount64 is port(clk,clr,updn:std_logic; qa,qb,qc,qd,qe,qf:std_logic); end updncount64; architecture rtl of updncount64 is signal count_6:std_logic_vector(5 downto 0); qa<=count_6(0); qb<=count_6(1); qc<=count_6(2); qd<=count_6(3); qe<=count_6(4); 123

124 qf<=count_6(5); process(clr,clk) if (clr='1') then count_6<=(others=>'0'); elsif (clk'event and clk='1') then if (updn='1') then count_6<=count_6+'1'; else count_6<=count_6-'1'; end if; end if; end process; end rtl; 程序中语句 count_6<=(others=>'0') 表示矢量 count_6 的各位均为 '0' 根据该程序所合成的电路引脚如图 3-26 所示 qa qb qc qd qe qf clr clk up/dn 图 位二进制可逆计数器电路 (3) 六十进制计数器众所周知, 用一个 4 位二进制计数器可以构成 1 个十进制计数器, 也就是说可以构成 1 个 bcd 计数器, 而 2 个十进制计数器连接起来可以构成一个六十进制的计数器 六十进制的计数器常用于时钟计数 一个六十进制计数器的电路引脚图如图 3-27 所示 bcd bcd clk din cin bcd1wr bcd10wr 图 3-27 六十进制计数器电路 124

125 六十进制计数器的输入和输出端的名称和功能说明如下 : clk 时钟输入端 ; bcd1wr 个位写控制端 ; bcd10wr 十位写控制端 ; cin 进位输入端 ; co 进位输出端 ( 注 : 在图中, 该端口没有标出 ); din 数据输入端, 共有 4 条输入线 din (0) ~din (3); bcd1 计数值个位输出, 共有 4 条输出线 bcd1 (0) ~ bcd1(3); bcd10 计数值十位输出, 共有 3 条输出线 bcd10 (0) ~ bcd10 (2) 在该六十进制计数器的电路中,bcd1wr 和 bcd10wr 与 din 配合, 以实现对六十进制计数器个位和十位的加载操作, 也就是说可以实现对个位和十位值的预置操作 应注意, 在对个位和十位进行预置操作时,din 输入端是公用的, 因而个位和十位的预置操作必定要串行进行 利用 VHDL 语言描述六十进制计数器的程序如例 3-43 所示 例 3-43 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity bcd60count is port(clk,bcd1wr,bcd10wr,cin:instd_logic; co:out std_logic; din:in std_logic_vector(3 downto 0); bcd1:out std_logic_vector (3 downto 0); bcd10:out std_logic_vector (2 downto 0)); end bcd60count; architecture rtl of bcd60count is signal bcd1n:std_logic_vector(3 downto 0); signal bcd10n:std_logic_vector(2 downto 0); bcd1<=bcd1n; bcd10<=bcd10n; process(clk,bcd1wr) if (bcd1wr='1') then bcd1n<=din; elsif (clk'event and clk='1') then if (cin='1') then if (bcd1n=9) then bcd1n<="0000"; else 125

126 126 bcd1n<=bcd1n+1; end if; end if; end if; end process; process(clk,bcd10wr) if (bcd10wr='1') then bcd10n<=din(2 downto 0); elsif (clk'event and clk='1') then if (cin='1'and bcd1n=9) then if (bcd10n=5) then bcd10n<="000"; else bcd10n<=bcd10n+1; end if; end if; end if; end process; process(bcd10n,bcd1n,cin) if (cin='1'and bcd1n=9 and bcd10n=5) then co<='1'; else co<='0'; end if; end process; end rtl; 在例 3-43 中, 第 1 个进程处理个位计数, 第 2 个进程处理十位计数, 第 3 个进程处理进位输出 co 的输出值 应注意, 个位和十位的计数条件是不一样的 2. 异步计数器异步计数器又称行波计数器, 它的低位计数器的输出作为高位计数器的时钟信号, 这一级一级串行连接起来构成了一个异步计数器 异步计数器与同步计数器不同之处就在于时钟脉冲的提供方式, 除此之外就没有什么不同了, 它同样可以构成各种各样的计数器 但是, 由于异步计数器采用行波计数, 从而使计数延迟增加, 在要求延迟小的应用领域受到了很大的限制 尽管如此, 由于它的电路简单, 仍有广泛的应用 用 VHDL 语言描述的异步计数器, 与上述同步计数器的不同之处主要表现在对各级时钟脉冲的描述上, 这一点请读者在阅读例程时多加注意 一个由 8 个触发器构成的行波计数器的程序如例 3-44 所示

127 例 3-44 library IEEE; use IEEE.std_logic_1164.all; entity dffr is port (clk,clr,d:in std_logic; q,qb:out std_logic); end dffr; architecture rtl of dffr is signal q_in:std_logic; qb<=not q_in; q<=q_in; process(clk,clr) if (clr='1') then q_in<='0'; elsif (clk'event and clk='1') then q_in<=d; end if; end process; end rtl; library IEEE; use IEEE.std_logic_1164.all; entity rplcont is port(clk,clr:in std_logic; count:out std_logic_vector(7 downto 0)); end rplcont; architecture rtl of rplcont is signal count_in_bar:std_logic_vector(8 downto 0); component dffr port (clk,clr,d:in std_logic; q,qb:out std_logic); end component; cunt_in_bar(0)<=clk; genl:for i in 0 to 7 generate u:dffr port map (clk=>count_in_bar(i), clr=>clr,d=>count_in_bar(i+1), q=>count(i),qb=>count_in_bar(i+1)); 127

128 end generate; end rtl; 3.3 存储器 存储器按其类型可分为只读存储器和随机存储器 它们的功能有较大的区别, 因此在描述上也有诸多不同 尽管如此, 它们也有许多相同之处, 在分别详述它们的各自特性以前, 先就一些共性的问题做一说明 存储器描述中的一些共性问题 1. 存储器的数据类型存储器是众多存储单元的一个集合体, 按单元号顺序排列 每个单元由若干个二进制位构成, 以表示单元中存放的数据值 这种结构和数组的结构是非常相似的 不妨认为某一个存储器可以用一个数组来代表, 每个单元代表数组中的一个元素, 数组中的元素序号和存储器中的单元序号一致 这样, 用一个数组就能很好地描述存储器存放数据的结构了 每个存储单元所存放的数可以用不同的 由 VHDL 语句所定义的数的类型来描述, 例如用整数或位矢量来描述 : type memory is array (integer range< > ) of integer; 这是一个元素用整数表示的数组, 可以用它来描述存储器存储数据的结构, 再如 : subtype word is std_logic_vector (k-1 downto 0); type memory is array (0 to 2* *w-1) of word; 这是一个元素用位矢量表示的数组, 用它来描述存储器存储数据的结构 其中,k 表示存储单元二进制位数 ;w 表示数组的元素个数 2. 存储器的初始化在用 VHDL 语言描述 ROM 时,ROM 的内容应该在仿真时事先读到 ROM 中, 这就是所谓存储器的初始化 存储器的初始化要依赖于外部文件的读取, 也就是说要依赖于 textio 下面是对 ROM 进行初始化的实例 128 变量说明 : variable startup: boolean:=true; variable l: line; variable j: integer; variable ROM:memory; file ROMin: text is in"rom24s10.in"; 初始化程序 : if startup then for j in ROM'range loop readline (ROMin,l);

129 read(l,rom(j)); end loop; end if; 一般,ROM 初始化在系统加电之后只执行 1 次 在仿真时, 如果 RAM 也要事先赋值, 那么也可以采用上述同样的方法 ROM( 只读存储器 ) 一种容量为 的 ROM 存储器的引脚图如图 3-28 所示 该 ROM 有 8 位地址线 adr(0) adr(7) 4 位数据输出线 dout(0) dout(3) 及两位选择控制输入端 g1 和 g2 当 g1='1', g2='1' 时, 由 adr(0)~adr(7) 选中某一 ROM 单元, 该单元中的 4 位数据就从 dout(0)~dout(3) 输出 ; 否则 dout(0)~dout(3) 将呈现高阻状态 据此就可以用 VHDL 语言写出对 ROM 的描述程序, 如例 3-45 所示 图 3-28 ROM 存储器电路图 例 3-45 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity ROM24s 10 is port(g1,g2:in std_logic; adr:in std_logic_vector(7 downto 0); dout:out std_logic_vector(3 downto 0)); end ROM24s10; architecture behav of ROM24s10 is subtype word is std_logic_vector(3 downto 0); type memory is array(0 to 255)of word; signal adr_in:integer range 0 to 255; varible ROM:memory; varible startup:boolean:=true; varible l:line; varible j:integer; file ROMin:text is in "ROM24s10.in"; 129

130 process(g1,g2,adr) if startup then for j in ROM'range loop readline(romin,l); read(l,rom(j)); end loop; startup:=false; end if; adr_in<=conv_integer(adr); if (g1='1'and g2='1') then dout<=rom(adr_in); else dout<="zzzz"; end if; end process; end behav; 例 3-45 中的 conv_integer( ) 是一个将位矢量转换成整数的函数, 在 IEEE 的标准包集合可以找到, 在这里直接引用了该函数 RAM( 随机存储器 ) RAM 和 ROM 的主要区别, 在于 RAM 的描述上有读和写两种操作, 而且在读 写时对时间有较严格的要求 一种容量为 8 8 的 SRAM 的引脚框图如图 3-29 所示 如图 3-29 所示, 它有 8 条地址线 adr(0)~adr(7),8 条数据输入线 din(0)~din(7),8 条数据输出线 dout(0)~dout(7) 另外,wr 为写控制线,rd 为读控制线,cs 为片选控制线 当 cs='1' wr 信号由低变高 ( 上升沿 ) 时,din 上的数据将写入 adr 所指定的单元 ; 当 cs='1' rd='0' 时, 由 adr 所指定单元的内容将从 dout 的数据线上输出 SRAM 图 SRAM 存储器引脚图由 VHDL 语言描述的 SRAM 的程序如例 3-46 所示 其中,now 表示系统仿真的当前时间 130

131 例 3-46 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; entity SRAM64 is generic(k:integer:=8; w:integer:=3); port(wr,rd,cs:in std_logic; adr:in std_logic_vector(k-1downto 0); din:in std_logic_vector(k-1 downto 0); dout:out std_logic_vector(k-1 downto 0)); end SRAM64; architecture behav of SRAM64 is subtype word is std_logic_vector (k-1 downto 0); type memory is array (0 to 2**w-1)of word; signal adr_in:integer range 0 to 2 **w-1; signal SRAM:memory; signal din_change,wr_rise:time:=0 ps; adr_in<=conv_integer(adr); && 位矢量变成整数 process(wr) if (wr'event and wr='1') then if (cs='1'and wr='1') then SRAM(adr_in)<=din after 2 ns; end if; end if; wr_rise<=now; &&wr 上升时间 assert (now-din_change>=800ps) report"setup error din(sram) " severity warning; &&din 建立时间检查 end process; process(rd,cs) if (rd='0'and cs='1') then dout<=sram(adr_in)after 3 ns; else 131

132 dout<="zzzzzzzz"after 3 ns; end if; end process; process(din) din_chang<=now; assert(now-wr_rise>=300 ps) perort"hold error din (SRAM)" severity warning; &&din 保持检查 end process; end behav; 在例 3-46 中加了一些信号传送的延迟时间和错误检查的语句 它们是在仿真时检查 RAM 定时关系所必须的程序, 将保证实际进行逻辑综合后的电路能满足 RAM 的定时要求 FIFO( 先进先出堆栈 ) FIFO 是先进先出堆栈, 作为数据缓冲器, 通常其数据存放结构是和 RAM 完全一致的, 只是存取方式有所不同 图 3-30 是容量为 8 4 位的 FIFO 的引脚框图 图 3-30 FIFO 电路引脚图图 3-30 中的 FIFO 有 4 条数据输入线 din,4 条数据输出线 dout,1 条读控制线 rd,1 条写控制线 wr,1 条时钟输入线 clk 及两条状态信号线, 即满信号和空信号线 (full,empty) FIFO 由 6 个功能块组成, 它们是存储体 写指示器 (wr) 读指示器(rp) 满逻辑 in-full, 空逻辑 in-empty 和选择逻辑 select 这是一个同步的 FIFO 在时钟脉冲的上升沿作用下, 当 wr=0 且 full=0 时,din 的数据将压入 FIFO 堆栈 在通常情况下,rp 指示器所指出的单元内容总是放于 dout 的输出数据线上, 只是在 rd=0 且 empty=0 时,rp 指示器内容才改变而指向 FIFO 的下一个单元, 下一个单元的内容替换当前内容并从 dout 输出 应注意, 在任何时候 dout 上总有一个数据输出, 而不像 RAM 那样, 只有在读有效时才有数据输出, 平时为三态输出 FIFO 的存储器实际上是一个环形数据结构, 由 wp 和 rp 分别指示数据写和读的对应单元 在此,wp 指示的是新数据待写入的单元地址, 只要发一个 wr 有效信号 (wr='0'), 就可将 din 上的数据写入该单元 ; 而 rp 指示的是已读了数据的单元地址, 要想读下一个数 132

133 据就要发一个 rd 有效信号 (rd='0') 使 rp=rp+1, 这时就可以读出下一个新的数据了 FIFO 在复位以后就处于初始状态,wp='0',rp=7, 此时 FIFO 处于空状态, 数据第 1 次写入的单元应是 0 号单元 rp 和 wp 之间应满足 rp=wp-1 rp=wp 状态是 FIFO 只要进行 1 次写操作就会变成满的状态 满状态和空状态的 rp 和 wp 的关系是一致的, 均为 rp=wp-1 但是, 稍加分析即可知道, 满或空状态出现之前的一个状态是各不相同的 在 rp=wp 时, 由于写一个数据而使其进入满状态 (rp=wp-1), 而在 rp=wp-2 时, 由于读一个数据而使其进入空状态 (rp=wp-1) 据此, 即可得到满或空信号产生的条件 FIFO 的 VHDL 描述如例 3-47 所示 例 3-47 library IEEE; use IEEE.std_logic_1164.all; entity FIFO is generic(w:integer:=8; k:integer:=4); port(clk,reset,wr,rd:in std_logic; din:in std_logic_vector(k-1 downto 0); dout:out std_logic_vector(k-1 downto 0); full,empty:out std_logic); end FIFO; architecture behav of FIFO is type memory is array (0 to w-1)of std_logic_vector(k-1 downto 0); signal RAM:memory; signal wp,rp:integer range 0 to w-1; signal in_full,in_empty:std_logic; full<=in_full; empty<=in_empty; dout<=ram(rp); process(clk) if (clk'event and clk='1') then if (wr='0'and in_full='0') then RAM(wp)<=din; end if; end if; end process; process(clk,reset) if (reset='1') then wp<=0; 133

134 134 elsif (clk'event and clk='1') then if (wp='0'and in_full='0') then if (wp=w-1) then wp<=0; else wp<=wp+1; end if; end if; end if; end process; process(clk,reset) if (reset='1') then rp<=w-1; elsif (clk'event and clk='1') then if (rd='0'and in_empty='0') then if (rp=w-1) then rp<=0; else rp<=rp+1; end if; end if; end if; end process; process(clk,reset) if (reset='1') then in_empty<='1'; elsif (clk'event and clk='1') then if (rp=wp-2 or (rp=w-1 and wp=1) or (rp=w-2 and wp=0))and(rd='0'and wr='1') then in_empty<='1'; elsif (in_empty='1'and wr='0') then in_empty<='0'; end if; end if; end process; process(clk,reset) if (reset='1') then in_full<='0';

135 elsif (clk'event and clk='1') then if (rp=wp and wr='0' and rd='1') then in_full<='1'; elsif (in_full='1'and rd='0') then in_full<='0'; end if; end if; end process; end behav; 例 3-47 由 3 条代入语句和 4 个进程语句描述了 FIFO 的工作原理 3 条代入语句反映了当前满或空的状态及当前 FIFO 所输出的数据 第 1 个进程描述 FIFO 的数据压入操作, 第 2 个进程描述写数据地址指示器 wp 的数值修改, 第 3 个进程描述读数据地址指示器 rp 的数值修改, 第 4 第 5 进程描述 FIFO 空 满 标志的产生 135

136 第 4 章数字系统的系统级设计 本章以及下一章将讨论结构化的集成电路设计过程, 图 4-1 示意地给出了本章讨论的问题 在系统级, 硬件的指标规范用自然语言描述, 还可能伴随有方框图或时序图 系统级设计是硬件设计过程的第一步, 是把硬件的自然语言描述转换为真值表 状态图或算法模型的过程 将自然语言描述转换为真值表或状态图, 要求设计者对硬件的功能有较深入的理解, 或对硬件结构做了一些限制 ( 例如选用组合逻辑或时序逻辑电路实现等 ), 而且一旦真值表或状态图确定后, 硬件结构也随之确定 将硬件的自然语言描述转换为算法模型, 是将自然语言描述直接映射为 VHDL 模型, 不对电路结构和形式预先做任何限制 设计开始时, 可以把硬件行为的自然语言描述转换为这 3 种形式中的任何一种 本章重点讨论如何将硬件系统的自然语言描述转换为算法模型 硬件的指标规范 真值表算法模型状态图 图 4-1 结构化集成电路设计的最高层次图 4-1 中在算法模型和真值表之间 算法模型和状态图之间画出了双向连线, 这意味着可以先设计出真值表或状态图, 然后将它们转换为算法模型 ; 也可以先设计出算法模型, 然后将它转换为真值表或状态图 虽然存在这种双向关系, 但通常是先建立算法模型 与真值表和状态图相比, 硬件描述语言构造的算法模型更加灵活, 有利于全面搜索设计空间, 从而有可能实现更高的设计质量 在系统级设计中, 需要解决的主要问题是系统算法的研究 系统的设计划分以及硬件系统通信协议的确定 本章着重说明如何从系统设计的角度建立算法模型, 如何把系统划分为寄存器传输级的子系统以及如何在寄存器传输级实现通信 4.1 构造系统的算法模型 用 VHDL 语言构造的数字系统的算法模型由一系列相互关联的进程组成 构造算法模型实际上就是把描述系统功能的自然语言翻译为一组进程, 每个进程完成不同功能 完成这一转换, 需要经过如下几步 : 把描述系统功能的文字分组, 每组映射为一个进程或块语句 这一步隐含着设计的划分 对每个进程确定激活进程的条件以及进程激活后的动作 写出 VHDL 源代码, 完成进程激活后的动作 136

137 系统的进程模型图是 VHDL 行为描述的图形表示, 它可以帮助设计者完成将自然语言行为描述转换为算法模型的第一步工作 在 VHDL 中, 构造体用来描述硬件的行为, 每个构造体内都包含一些并行运行的进程 这里所说的进程是广义的, 既包括用 VHDL 关键字 process 明确指出的进程, 也包括并行语句 按照 IEEE 标准, 并行语句相当于简化的进程, 对赋值号右边的所有信号敏感 进程模型图实际上是构造体行为的图形表示 图 4-2 是一个典型的进程模型图, 图中的支路是进程间的通信信号, 有些支路上不但标出了信号名而且标出了相应的传输延时 例如, 图 4-2 中的 s(del_s),s 是信号名,del_s 是进程间传输信号 s 时的传输延时 图中的节点表示进程, 例如第 2 个节点可以为例 4-1 的 VHDL 进程 例 4-1 proc2 : process(x, y, z) variable svl, ovl : std_logic ; -- 进程的行为描述 -- 计算变量 svl 和 ovl 的数值 s<=svl after del_s ; out1<=ovl after del_out1 ; end proc2 ; s(del_s) in1 proc1 x proc 4 out1(del_out1) y in2 s2 proc 2 proc 3 out2(del_out2) 图 4-2 典型的进程模型图 在这个进程模型图中, 进程对信号 x y z 敏感, 用来计算 s 和 out1 的数值 其中 del_s 是为信号 s 赋值时的延时,del_out1 是为信号 out1 赋值时的延时 时钟 运行 运行 状态 取指 图 4-3 微处理器的一种进程模型图 137

138 除了描述器件的行为之外, 进程模型图还表示了硬件模型的划分, 这里的划分可以是物理意义上的划分, 也可以是纯功能上的划分 如果表示的是物理意义上的划分, 图 4-2 中支路上的延时值表示信号经过逻辑门 触发器 寄存器等实际硬件时的信号传输延时 在某些情况下, 特别是在硬件设计的初级阶段, 带有这些延时值的仿真结果对于后续设计阶段是十分重要的 图 4-3 是一个处理器的进程模型图, 其中的取指 运行等节点的功能描述与硬件的模型划分没有任何关系 在实际实现芯片硬件时, 不同的功能模块可能会共享某些电路模块 例如在这个微处理器中, 取指和运行两个节点可能共享同一物理存储区, 而取指的地址计算和执行中的算术逻辑运算也可能共享运算电路 4.2 构造算法模型的简单举例 并串转换电路的算法模型 下面这个例子是一个并行数据到串行数据转换电路的算法模型, 图 4-4 是电路的方框图, 电路行为的自然语言描述如下 : 在时钟沿上, 如果控制信号 ld 为高电平 '1', 则把并行输入信号 par_in 加载到转换电路内部, 并将状态标志信号 busy 置位为高电平 '1' 此后, 并行输入的数据在时钟 clk 上升沿上逐位移出转换电路 当加载的并行数据全被移出电路之后, 状态标志 busy 复位为 '0' ld Par_in clk 并串转换电路 Ser_out busy 图 4-4 并串转换电路的方框图 按照 4.1 节说明的方法, 先将自然语言描述分组, 然后把每组描述映射为一个 VHDL 进程, 就可以把并串转换电路映射为加载进程 load 和移位进程 shift 进程 load:1 在时钟上升沿, 若信号 ld 为 '1',8 位并行输入信号 par_in 被加载到转换电路的内部寄存器, 并将状态标志 busy 置位为 '1';2 在数据被全部移出转换电路之前,busy 保持为 '1' 在标志 busy 为 '1' 期间, 禁止再加载数据 进程 shift:1 数据在时钟 clk 上升沿逐位移出电路 ;28 位数据全部移出之前, 移位终止信号 sh_comp 一直保持为 '0' 图 4-5 是并串转换电路的算法模型的进程模型图 例 4-2 的 VHDL 源代码是依照这个进程模型图书写的 VHDL 算法级模型 模型由两个进程组成 进程 load 在时钟沿上检查 ld 是否为 '1', 如果满足, 则开始转换过程 首先将并行数据载入内部寄存器, 并将 busy 位置为 '1'; 此后用一个 while 循环等待 8 位数据移出, 如果 sh_comp 为 '1', 表示 8 位数据串行输出已经结束, 则将 busy 复位为 '0' 进程 shift 在时钟沿上检查 busy 是否为 '1', 如果满足, 则进行必要的初始化, 即将 sh_comp 置为 '0', 把移位计数值置为 '7', 并用 9 位的内部移位寄存器锁存 8 位数据 ( 在低端补 '0') 在接下来的时钟上升沿, 依次右移 1 位, 并将最低位输出 ;8 次移位结束之后, 进程置 sh_comp 为 '1', 表示并串转换结束 两个进程之间 138

139 使用握手协议进行通信 busy clk par_in 8 加载进程 load rec 移位进程 shift ser_out ld sh_comp 图 4-5 并串转换电路的进程模型图 例 4-2 entity par_to_ser is port (clk, ld : in std_logic ; par_in : in std_logic_vector(7 downto 0); busy : buffer std_logic; ser_out : out std_logic); end par_to_ser; architecture alg of par_to_ser is signal sh_comp:std_logic; signal reg : std_logic_vector(7 downto 0); load:process wait until clk'event and clk='1' and ld='1'; reg<=par_in; busy<='1'; waiting:loop wait until clk'event and clk='1'; if sh_comp='1' then busy<='0'; exit waiting; end if; end loop waiting; end process load ; shift:process variable count : integer; variable oreg : std_logic_vector(8 downto 0); wait until clk'event and clk='1' and busy='1'; 139

140 count:=7; oreg:=reg&'0'; ser_out<=oreg(0); sh_comp<='0'; while (count>=0) loop wait until clk'event and clk='1'; oreg:= '0'&oreg(8 downto 1); ser_out<=oreg(0); count:=count-1; end loop; wait until clk'event and clk='1'; sh_comp<='1'; end process shift; end alg; 移位乘法器的算法模型 图 4-6 是一个移位乘法器的方框图, 该电路行为的自然语言描述如下 : 移位乘法器的输入为两个 4 位操作数 a 和 b, 闸门信号 stb 启动乘法操作, 时钟信号 clk 提供系统定时 乘法结果为 8 位信号 result, 乘法结束后置信号 done 为 '1' a b 4 stb 4 移位乘法电路 8 result done 图 4-6 移位乘法器的方框图 乘法算法采用原码移位乘法, 即对两操作数进行逐位的移位相加, 迭代 4 次后获得乘法结果 具体算法为 : 1) 在被乘数和乘数的高位补 '0' 后扩展成 8 位 2) 乘数依次向右移位, 并且检查其最低位, 如果该位为 '1', 则将被乘数与部分积的和相加, 然后被乘数向左移位 ; 如果最低位为 '0', 则仅仅对被乘数进行移位操作 移位时, 乘数的高端和被乘数的低端均移入 '0' 3) 当乘数变成全 '0' 后 ( 最坏情况下需要 4 次移位 ), 乘法结束 如前所述, 将硬件描述的自然语言描述形式转换为算法模型的第 1 步, 要求把自然语言描述分组, 然后把每组描述映射为一个 VHDL 进程 下面把乘法器电路映射为控制器进程 controller 锁存移位进程 sra 和 srb 加法进程 adder 以及锁存结果的进程 acc 进程的具体情况如下 : 1) 控制器进程 controller 接收到有效 stb 信号后, 产生初始化信号, 然后依次在各个有效时钟沿进行状态转移, 并在各个状态发出不同控制信号, 使其他部件执行相应操作, 140

141 在 sra 输出的 8 位数据均为 '0' 时, 结束乘法操作 2) 锁存移位进程 sra 初始化时锁存 a 路数据, 并在 4 位数据前补 '0' 至 8 位, 然后在移位信号 shift 有效时依次右移, 在左端移入 '0' 3) 锁存移位进程 srb 初始化时锁存 b 路数据, 并在 4 位数据前补 '0' 至 8 位, 然后在移位信号 shift 有效时依次左移, 在右端移入 '0' 4) 加法进程 adder 是一个组合逻辑进程, 计算 srb 进程的输出与 acc 进程锁存结果之和, 即部分积的和 锁存结果的进程 acc 在 controller 进程产生的 add 信号有效时锁存部分积的和 图 4-7 是移位乘法器算法模型的进程模型图 由于以上的移位和锁存操作都在时钟上升沿进行, 所以这是完全同步式的设计, 图中省略了 clk 信号 b a 4 4 锁存移位进程 srb init 锁存移位进程 sra accout 8 shift lsb 8 sraout 加法进程 adder 控制器进程 controler stop 或非 nor accout 8 8 锁存进程 init stb acc 图 4-7 移位乘法器的进程模型图 控制器是整个硬件的核心, 下面给出的是控制器的 VHDL 源代码 这是一个纯算法式的描述, 状态是隐含的 控制器在时钟上升沿检测 stb, 若 stb 为 '1', 就启动 1 次乘法操作 控制器首先发出一个 init 脉冲, 初始化输入和输出锁存器 然后进入 while 循环, 用一个状态检查 lsb 如果 lsb 为 '1', 则先执行加法操作 ( 置 add 为 '1'), 再执行移位操作 ( 置 shift 为 '1'); 否则, 只执行移位 当 sraout 的各位均为 '0' 时, 循环终止, 乘法结束, 控制器置 done 信号为 '1', 表示乘法结束, 然后进程挂起直到下一次 stb 有效 例 4-3 controller: process wait until clk'event and clk='1' and stb='1'; done<='0'; init<='1'; -- 初始化各个锁存器 shift<='0'; add<='0' wait until clk'event and clk='1'; init <='0' ; 141

142 run_loop: while (stop /='1') loop if lsb='1' then wait until clk'event and clk='1'; add<='1'; shift<='1'; wait until clk'evnet and clk='1'; else wait until clk'evnet and clk='1'; shift<='1'; wait until clk'event and clk='1'; end if ; shift<='0' ; end loop run_loop ; done<='1'; end process; --lsb 是 sraount 的最低位 锁存移位进程 sra 和 srb 的 VHDL 源代码如例 4-4 所示, 可以看出, 这是一个典型的移位锁存器 锁存移位进程 srb 与 sra 的不同之处只是移位方向的不同 例 4-4 sra : process wait until clk'event and clk='1'; if init='1' then sraout<="0000"&a ; elsif shift='1' then sraout<='0'&sraout(7 downto 1); end if; end process sra ; 142 srb : process wait until clk'event and clk='1' ; if init='1' then srbout<="0000"&b ; elsif shift='1' then srbout<=srbout(6 downto 0)& '0' ; end if ; end process srb ; 例 4-5 给出的加法进程 adder 的 VHDL 源代码, 这是一个组合逻辑进程, 用于计算部分和 这个加法进程使用了普通的 异或 生成和算法

143 例 4-5 adder : process(accout, srbout) variable sum, tmp1, tmp2 : std_logic_vector(7 downto 0); variable carry : std_logic ; tmp1:=accout ; tmp2:=srbout ; carry:= '0'; for i in 0 to 7 loop sum(i):=tmp1(i) xor tmp2(i) xor carry ; carry :=(tmp1(i) and tmp2(i)) or (tmp1(i) and carry) or (tmp2(i) and carry) ; end loop ; addout<=sum; end process adder ; 最后是锁存器进程 acc 的 VHDL 源代码 这个模型非常简单, 就是在初始化时清 '0', 在加法操作时同步锁存运算结果 例 4-6 acc : process wait until clk'event and clk='1' ; if init = '1' then accout <=(others=>'0') ; elsif add='1' then accout<=addout; end if ; end process acc; 在乘法器的设计中, 除了控制器和加法器之外的电路都比较简单, 因此 sra,srb,acc 这 3 个进程实际上是按照寄存器传输级的风格书写的 下面的例子由两个稍微复杂些的进程组成 考虑时序关系的算法模型 在前面的例子中, 并没有将硬件的时序关系考虑在算法模型中, 本节讨论如何考虑硬件的输入输出的时序关系 图 4-8 是一种带有输出缓冲单元的寄存器的方框图, 该寄存器行为的自然语言描述如下 : 在闸门信号 strb 的上升沿加载寄存器, 如果使能信号打开输出缓冲单元, 被加载的数据延时 t sd 后, 出现在输出端 打开输出缓冲单元的控制信号 ( 使能信号 ) 是 ds1 and(not ds2) 使能信号的变化延时 t ed 之后, 控制缓冲单元的输出 strb_del di1 di8 strb 8 位寄存器 1 en do1 143 ds1 ds2 & en_del 1 en do8

144 图 4-8 带有输出缓冲单元的寄存器分析硬件的方框图及指标规范, 不难发现从输入到输出有两条路径 : 第 1 条是从寄存器的数据输入到缓冲器的输出 ; 第 2 条是从使能信号输入到缓冲器的输出 建立硬件的算法模型时, 每条路径应该对应一条信号线 实际上, 建立硬件模型时要求遵循下述准则 : 如果要求算法模型包含硬件的时序关系, 则从输入到输出之间的每一条路径必需由一个单独的 VHDL 信号描述, 这些信号可以并行活动 遵循这一准则, 在每条路径上的信号同时活动时, 可以保证算法模型行为的正确性 为了构造带缓冲单元的寄存器的算法模型, 假定 t sd =strb_del+o_del, t ed =en_del+o_del 如图 4-8 所示,strb_del 是从闸门信号 strb 的上升沿到数据寄存器输出端的延时 ;en_del 是从使能信号输入到缓冲单元打开的延时 ;o_del 是信号经过缓冲单元的延时 上述带缓冲单元的寄存器可以由 3 个进程描述, 各进程的行为描述如下 : 进程 preg 在闸门信号 srb 的上升沿将 di 的输入数据加载到内部寄存器 进程 enable 产生缓冲单元的使能信号, 缓冲单元的使能条件是 ds1 and (not ds2) 进程 output 中, 如果使能信号将输出缓冲单元打开, 则输出在 tsd 之后变化 ; 如果使能条件发生变化, 则输出在 ted 之后变化 图 4-9 是该带缓冲单元的寄存器的进程模型图 进程 preg 接收闸门信号 strb 和数据输入信号 di, 数据延时后通过信号 reg 送到进程 output 进程 enable 的作用与进程 preg 类似, 把延时后的选通信号 enabld 送到进程 output 进程 output 则接收信号 reg 和 enabld, 产生输出信号 do 进程模型图中 3 个路径的延时分别用 strb_del,en_del 和 o_del 标在图中 这个进程模型图符合提出的算法模型构造准则, 即不同的数据路径分别用不同的信号描述 strb di 进程 preg reg(strb_del) 进程 do(o_del) ds1 进程 output ds2 enable Enabled(en_del) 144 图 4-9 带缓冲单元的寄存器的进程模型图 例 4-7 是这 3 个进程的 VHDL 源代码, 其中接口描述及 3 个延时都通过类属参数描述

145 如果将该模型连接到某系统中成为系统内部的一个具体寄存器, 需要指定这些类属参数的具体数值 例 4-7 entity buff_reg is generic(strb_del, en_del, o_del : time); port ( strb, ds1, ds2 : in std_logic ; di : in std_logic_vector(7 downto 0); do : out std_logic_vector(7 downto 0)); end buff_reg ; architecture three_proc of buff_reg is signal enabled : std_logic; signal reg : std_logic_vector(1 to 8); preg : process(strb) wait until strb='1' ; reg<=di after strb_del; end process preg ; enable : process(ds1,ds2) enabled<=ds1 and not ds2 after en_del ; end process enable ; output : process(reg, enabled) if enabled='1' then do<=reg after o_del ; else do<="zzzzzzzz" after o_del ; end if ; end process output ; end three_proc ; 当然, 还可以写出其他形式的算法模型, 例如可以将两个进程 preg 和 enable 合并为一个进程, 用一个进程完成这两个进程的工作, 这种方式如例 4-8 所示 例 4-8 front_end : process(strb,ds1,ds2) 145

146 146 if strb'event and strb='1' then reg<=di after strb_del ; end if ; if ds1'event or ds2'event then enabled<=ds1 and not ds2 after en_del ; end if ; end process front_end ; 这里用包含两个动作的一个进程替代了原来的两个只包含单个动作的进程, 对信号 reg 和 enabled 赋值的方式并没有变化 这就提出了一个问题 : 在设计硬件的算法模型时, 是多用几个进程好, 还是少用几个进程好? 回答这个问题需要考虑如下几个因素 : 信号的个数, 进程越多所要求的进程间的信号个数就越多 在仿真器中, 信号个数越多, 则要求事件队列的个数越多且队列长度越长, 这将影响仿真效率 进程的复杂程度 一个进程要完成的动作太多, 进程就会复杂, 进程的通用性 可再用性就会变差 映射的困难程度 进程个数多一些有利于将硬件行为的自然语言描述转换为进程描述 把算法模型分解为一系列进程就是系统划分 在对系统进行划分时, 子模块的粒度, 即把多大规模的逻辑电路组织在同一模块 ( 在算法级主要指进程 ) 中是很重要的问题 如果规模太大, 那么仿真 综合或改写成寄存器级代码的时间都比较长, 由于 VLSI 设计是一个迭代过程, 所以这样就会延长设计周期 ; 反之, 若规模太小, 则限制了设计能力, 同样会影响设计效率 合理的划分, 不仅可以改善设计的总体性能, 提高设计的可再用性, 而且可以节省仿真时间和综合时的编译时间 系统划分一般按照下面的原则来进行 : 把相关的组合逻辑划分到同一进程中 ; 把结构规则的逻辑模块 ( 如多位加法器 ) 和随机逻辑模块 ( 如指令译码的硬连线逻辑 ) 划分到不同的子模块中 ; 每一进程完成的任务尽量单一化 根据 VHDL 语言的规定, 单独出现的信号赋值语句与进程等效 因此, 可以把上面描述带缓冲单元的寄存器的构造体写得更紧凑一些, 如例 4-9 所示 例 4-9 architecture data_flow of buff_reg is b1 : block(strb='1' and strb'event) signal enabled : std_logic ; signal reg : std_logic_vector(1 to 8) ; reg<=guarded di after strb_del ; enabled<=ds1 and not ds2 after en_del ; do<=reg after o_del when enabled='1' else "zzzzzzzz" after o_del ;

147 end block b1 ; end data_flow ; 这里采用了保护模式赋值语句对信号 reg 赋值, 只有在保护条件为 true, 即在信号 strb 从 '0' 变为 '1' 的上升沿时, 赋值语句才会执行 这种方式得到的硬件模型很简洁, 但模型的结构不明显 这种简洁的算法模型通常只用于数据流式硬件描述 4.3 构造算法模型时需要注意的问题 时序检查 按照 VHDL 语言的规定, 信号赋值的缺省延时方式为惯性延时, 信号赋值语句执行时会把输入信号中窄于指定延时时间的脉冲滤除 这样, 在算法模型中, 输入信号的值必需保持足够长时间才能保证模型的行为正确 例如在 D 触发器中, 在 clk 的上升沿之前信号 D 的值至少要保持一定的建立时间, 在 clk 的上升沿之后至少要保持一定的保持时间等 如果输入信号不满足这些时序要求, 输出信号的值可能不会按设计者设想的方式变化 除此之外, 硬件模型并不能直接告诉设计者输入信号的一些变化被滤除了, 这不利于模型的仿真验证 实际中, 对于一个信号本身来讲, 其上升时间 下降时间 最小脉冲宽度也常常需要检查, 即所谓波形检查 图 4-10 以图形化的方式说明了这些概念 建立时间 保持时间 数据输入 D 最小脉宽 时钟输入 clk CLK 上升时间 下降时间 图 4-10 D 触发器的输入信号时序要求 VHDL 语言允许在模型中检查这些时序要求, 这要用到断言语句 (assert 语句 ) 断言语句的一般形式为 : assert 布尔表达式 ; report 错误信息 severity 错误级别 ; 断言语句执行时, 先检查关键字 assert 后面的布尔表达式的值 如果表达式的值为 true, 则认为是正常条件 ; 如果表达式的值为 false, 则认为是非正常工作条件, 并报告出指定的错误信息 实际上, 断言语句可以用来报告很多有用的信息, 本节只用它来进行时序检查 对于时序检查, 断言语句中的布尔表达式中通常要用到信号的 VHDL 预定义属性, 使用比较多的属性是 x'event,x'last_value,x'stable(t) 和 x'delayed(t) 等 检查时序的断言语句可以放在设计实体的接口描述中, 也可以放在构造体中 如果把 147

148 断言语句放在构造体内, 则时序检查只对设计实体的特定结构有效 ; 如果断言语句放在实体的接口描述中, 则它是对该设计实体检查时序, 即对该实体的所有可能的构造体全进行时序检查 如果时序检查代码放在构造体中, 可以出现在过程调用 并行断言语句或进程内的顺序断言语句 3 种结构中 通常认为, 过程调用形式的时序检查代码具有可再用性好 代码简洁 仿真效率高 ( 仿真效率包括内存的使用量和仿真时间两个方面 ) 等优点 例 4-10 的 VHDL 源代码是一种通用的波形检查过程, 代码利用了 VHDL 中预定义的数据 now, 即当前仿真时间, 通过追踪波形的上升沿和下降沿来计算波形的各种宽度 148 例 4-10 procedure waveform_check (signal sig : std_logic; constant t_min_hl, t_max_hl : time :=0 ns ; constant t_min_lh, t_max_lh : time :=0 ns ; constant pw_hi_max, pw_hi_min : time :=0 ns ; constant pw_lo_max, pw_lo_min : time :=0 ns ; constant severity_sel : severity_level : =warning ) is variable rising_at, t1 : time :=0 ns ; variable falling_at, t0 : time :=0 ns ; checking : loop wait until sig='1' and sig'event ; -- 上升沿时序检查 if now>0 ns then assert (now-rising_at>=t_min_lh) or (t_min_lh==0 ns) report " 上升沿过窄 " ; severity severity_sel ; assert (now-rising_at<=t_max_lh) or (t_max_lh==0 ns) report " 上升沿过宽 " ; severity severity_sel ; end if ; t1:=now ; wait until sig'event and sig'last_value='1' ; falling_at:=now ; -- 高电平宽度检查 assert (now-t1>=pw_hi_min) or (pw_hi_min==0) report " 高电平脉冲过窄 " ; severity severity_sel ; assert (now-t1<=pw_hi_max) or (pw_hi_max==0) report " 高电平脉冲过宽 " ; severity severity_sel ; wait until sig'event and sig='0' ; t0:=now ;

149 -- 下降沿时序检查 assert (now-rising_at>=t_min_lh) or (t_min_lh==0 ns) report " 上升沿过窄 " ; severity severity_sel ; assert (now-rising_at<=t_max_lh) or (t_max_lh==0 ns) report " 上升沿过宽 " ; severity severity_sel ; assert (now-falling_at>=t_min_hl) or (t_min_hl==0 ns) report " 下降沿过窄 " ; severity severity_sel ; assert (now-falling_at<=t_max_hl) or (t_max_hl==0 ns) report " 下降沿过宽 " ; severity severity_sel ; -- 低电平宽度检查 assert (now-t0>=pw_lo_min) or (pw_lo_min==0) report " 低电平脉冲过窄 " ; severity severity_sel ; assert (now-t0<=pw_lo_max) or (pw_lo_max==0) report " 低电平脉冲过宽 " ; severity severity_sel ; end loop checking ; end waveform_check ; 与此类似, 可以写出用于检查建立时间和保持时间的 VHDL 源代码如例 4-11 所示 检查建立时间时, 要求时钟上升沿出现时被采样信号必须稳定足够长时间 检查保持时间时, 要求时钟上升沿后被采样信号至少保持一定时间 例 4-11 建立时间检查的 VHDL 源代码 procedure setup_check (signal sig,clk : std_logic ; constant setup_del : time :=0 ns ; constant severity_sel : severity_level:=warning ) is assert not ((clk'event and clk='1') and (not sig'stable(setup_del))) report " 建立时间不足 " ; severity severity_sel ; end setup_check ; -- 保持时间检查的 VHDL 源代码 procedure hold_check (signal sig, clk : std_logic; constant hold_del : time :=0 ns ; constant severity_sel: severity_level:=warning ) is 149

150 assert not ((clk'del (hold_del)'event and clk'del (hold_del)= '1') and (not sig'stable (hold_del))) report " 保持时间不足 "; sverity sverity_sel; end hold_check ; 需要指出的是, 现在的综合工具一般也能够进行时序检查, 并且可以按照用户输入的建立 保持时间约束来综合电路 那么, 是否还有必要在代码中加入时序检查代码呢? 实际上, 时序检查代码不仅能够对时序进行检查, 还可以帮助设计者编写更加准确而有效的测试矢量 ; 并且通过 assert 语句的报告, 设计者还可以在仿真时获得电路的内部信息, 这对于 VHDL 这种并行语言程序的调试是十分重要的 因此, 通常建议在程序中加入时序检查代码 选取适于综合的模型构造风格 VHDL 语言提供了多种硬件造型方式, 而硬件的 VHDL 模型可能用于不同领域, 例如, 可以利用 VHDL 模型建立硬件的设计文档 硬件的仿真验证 综合 测试生成等 这就提出了一个问题 : 按某种方式建立的硬件模型是否可以用于所有的这些领域? 一般地讲, 答案是否定的, 即某应用领域只能采用特定的造型方式 在 VLSI 设计中, 书写 VHDL 源代码的主要目标之一就是进行综合, 那么怎样才能达到优化的综合结果呢? 一般说来,VHDL 源代码是作为 EDA 综合工具的输入 EDA 工业界普遍认为, 有效的 VHDL 建模风格是控制综合结果的最为有力的手段 为了建立有效的 VHDL 源代码, 设计者应了解 VHDL 语言结构与综合结果的关系 应该指出的是, 由于综合算法的不同, 对于同样的硬件描述, 不同的 EDA 综合工具可能会得到不同的综合结果 因此, 这里着重说明综合过程中具有通用性的处理方法 如果具体工具的处理方式有所不同, 请参照该 EDA 工具的使用手册 下面将介绍 VHDL 向硬件映射时应遵循的原则 VHDL 语言在创立时, 主要是为了满足仿真的需要, 因此包含了许多用于仿真调试的语法结构 自从 VHDL 被用于综合以来, 都是对 VHDL 的子集进行处理, 这就是所谓的可综合的 VHDL 子集 不同综合工具支持的可综合子集不尽相同, 通常有如下要求 : 延时描述 (after 语句 wait for 语句 ) 等被忽略 仿真时, 为了具备一定精度, 往往在源代码中含有延时语句, 这些延时语句情况比较复杂, 有时是最大延时约束, 有时是典型经验值, 还有些是人为加入的, 所以现在所有的综合工具都忽略源代码中的延时语句 有些工具干脆把这些语句处理为语法错误 大部分工具忽略延时语句后, 给出警告提示 而综合的时间约束, 则是在综合过程中通过综合命令输入的 支持有限类型 VHDL 具有丰富的类型定义, 但是有些类型不具备硬件对应物, 不可能被综合, 如文件类型 通常可综合类型包括枚举类型 ( 包括自定义状态 预定义 bit 类型和 IEEE9 值逻辑类型等 ) 整数 数组等 其余像浮点数类型 记录类型等只得到有限支持, 而时间类型等完全不能被综合 进程的书写要服从一定的限制 在仿真时,VHDL 进程可以任意书写 而在综合时, 150

151 通常要求一个进程内只能有一个有效时钟, 有的工具还有进一步的限制 可综合代码应该是同步式的设计 现在的 EDA 综合工具普遍推荐使用同步式设计风格, 即整个芯片电路的状态只能在时钟信号有效时发生改变, 也就是说, 电路不会在仅有数据信号变化而时钟处于无变化状态时改变, 并且电路中的时钟信号应该都是从某一主时钟经过分频或其他运算得到 当然设计者也可以尝试其他风格的设计, 如异步式电路, 但这时综合工具产生的结果往往还需要设计者的进一步优化或调整 下面通过几个例子说明这些问题 首先来看一个实现乘 2 电路的例子, 下面给出了 3 种不同风格的 VHDL 模型 第 1 种代码 ( 代码 (a) 是直接了当的,2 被赋给信号 two), 在时钟上升沿锁存乘法结果 在硬件设计的初始阶段, 不考虑如何将行为模型转换为硬件, 可以用这种模型进行仿真, 以验证系统的功能 如果对这个构造体进行综合, 可以得到图 4-11(a) 所示的硬件 显然, 综合工具把 two 和 a 映射为 4 位信号线, 把 result 映射为 5 位信号线, 用乘法器计算乘法, 用寄存器锁存 result 但是, 乘 2 电路通过移位就能够完成, 并不需要成本很高的乘法器电路 为此, 可以把下面代码 (a) 改写为第 2 种形式的代码 (b) 这样一来, 综合工具用移位寄存器实现乘法操作, 硬件代价明显降低, 综合结果如图 4-11(b) 但是, 这样做的缺点在于无法使用形如 integer 的抽象的数据类型, 而只能使用位矢量, 且乘法运算以隐含方式表示, 因此在改善综合质量的同时, 降低了程序的可读性 实际上, 现在先进的综合工具如 synopsys 和 Cadence 所采用的综合算法已经能够很好地处理与常数相乘的运算 因此, 可以写出下面代码 (c) 示出的第 3 种风格的代码 这段代码把 two 定义为常数, 从而引导综合进行常数乘法优化, 所以综合结果与 (b) 的代码是相同的 必须指的是,(c) 是优化代码书写风格, 但并不是所有的综合工具都能够进行必要的综合优化, 因此设计者还要根据具体综合工具确定合适的代码工具 two a 4 4 * clk 5 5 位 5 寄存器 result 0 a clk 4 5 位寄存器 5 result (a) (b) 图 4-11 乘 2 电路的综合结果 例 (a) 代码形式之一 signal two,a:integer range 0 to 15; singal result:integer range 0 to 32; two<=2; process_a:process 151

152 wait until clk='1'and clk'event; result<=a * two; end process process_a; --(b) 代码形式之 2 signal a:std_logic_vector(3 downto 0); signal result:std_logic_vector(4 downto 0); process_b:process wait until clk='1'and clk'event; result<=a & '0'; end process process_b; --(c) 代码形式之 3 constant two:integer range 0 to 15; signal a:integer range 0 to 15; signal result:integer range 0 to 32; process_c:process wait until clk='1'and clk'event; result<=a * two; end process process_c; 下面再来看一个延时器的例子 在有些数字电路中, 系统需要等待一个固定延时之后, 再开始处理下一事件, 延时通过延时电路完成 该电路在 15 个输入时钟周期之后, 输出 time_out 为 '1' 用 4 位寄存器存储计数值, 复位时 (reset 为 '1') 载入初值 例 4-13 中的 VHDL 源代码 (a) 和 (b) 是两种不同的 VHDL 描述, 图 4-12 中的 (a) 和 (b) 分别是其综合后的电路 由 (a) 标出的代码使用传统的十六进制计数器来实现, 复位时初值为 0000 然后在时钟沿上计数, 计满为 1111 后输出 time_out 有效 这个描述对于实现功能是没有问题的, 可读性也较好 但是存在着一些缺点, 首先是由于电路由 4 位锁存器和加 1 电路构成, 所以芯片面积比较大, 在综合工具优化能力较差的情况下, 甚至会用加法器来实现加 1 过程 其次, 这种计算器电路的固有延时相对较大 ( 因为进位产生电路的扇入大 ), 只能用于低速工作的场合 由 (b) 标出的是用线性反馈移位寄存器 (lfsr,linear feeback shift register) 实现的延时器, 根据线性反馈移位寄存器理论,n 位 lfsr 计数器可以产生最大长度 2 n -1 的计数序列, 也就是说,n 位 lfsr 将遍历 2 n -1 个状态, 然后返回初值 在电路结构上,lfsr 要简单得多, 由 4 位的锁存器和 1 个异或门反馈电路组成 用这种方式实现延时, 虽然牺牲了顺序的计数序列 ( 即计算序列不再是从 1~15 的自然顺序 ), 但是电路规模小, 而且运算速度要比十六进制计数器快得多 这个例子说明, 仿真上等效的 VHDL 模型的综合结果可能会很不相同, 因此设计者应寻找有效的算法 152

153 图 4-12 用不同算法综合出的延时器电路 例 (a) 用计数器实现延时器 delay_counter:process(reset,clk) variable count:std_logic_vector(3 downto 0); function inc (x:in std_logic_vector)return std_logic_vector is variable xv:std_logic_vector(x'length-1 downto 0); xv :=x; for i in 0 to xv'high loop if xv(i)= '0'then xv(i):= '1'; exit; else xv(i):= '0'; end if; end loop; return xv; end inc; if reset='1'then count:="0000"; elsif clk='1' and clk'event then count:=inc(count); end if; 153

154 if (count="1111") then time_out<='1' ; end if; end process delay_counter; --(b) 用线性反馈移位寄存器实现延时 delay_lfsr:process variable count:std_logic_vector(3 downto 0); if reset='1'then count:= "0001"; elsif clk='1' and clk'event then count:=(count(1)xor count(0))& count(3 downto 1); end if; if (count="0011") then time_out<='1'; end if; end process delay_lfsr; 例 4-14 中的实体 count_one 用来确定输入信号中 '1' 的个数, 它是电路的系统级行为模型 电路的输入为 3 位信号 a, 输出信号为 2 位信号 c, 其数值为输入信号中 '1' 的个数 例 4-14 entity count_one is port (a:in std_logic_vector(2 downto 0); c:out std_logic_vector(1 downto 0)); end count_one; archtecture alg of count_one is a:process(a) variable num:integer range 0 to 3; num:=0; for i in 0 to 2 loop if a (i)= '1'then num:=num+1; end if; end loop; case num is when 0=>c<="00"; when 1=>c<="01"; when 2=>c<="10"; 154

155 when 3=>c<="11"; end case; end process a; end alg; 在构造体 count_one 中, 先用 for 循环扫描输入信号 a 的各位, 如果发现 a 中有一个 '1', 则将 num 的数值增加 1 扫描结束后, 再用 case 语句将十进制数据转换为二进制输出 如果不考虑如何将行为模型转换为硬件, 可以用这种模型进行仿真, 以验证系统的功能 如果把这个构造体综合为硬件, 由于这个 for 循环内部有一个不完全的条件判断 ( 即没有 else 分支 ), 加之 for 循环的迭代特征, 某些综合工具会用时序逻辑电路实现硬件 如果使用 synopsys 的综合工具 dc(design compiler), 则尽管能够展开循环以组合逻辑实现电路, 但是电路中会形成很长的比较链, 也就是说, 关键路径上延时很大, 所以将这个描述作为综合输入是不可取的 事实上, 如果写出电路 count_one 的输入输出关系的真值表, 就会发现可以用组合逻辑实现该电路 如果人工设计电路, 很容易发现这个问题 但如果是编制自动综合代码, 这样的 for 循环最直观的实现形式是时序电路 采用这种综合工具进行电路自动设计, 有可能会得出很不经济的硬件实现方案, 也有可能需要很长的计算时间 如果把构造体 alg 进行改进, 用 case 语句直接检查输入信号的码型, 并产生相应的输出信号, 则很容易将该行为模型转换为如图 4-13 所示的硬件 这是一个数据宽度为 2 的 8 选 1 电路, 集成电路和自动综合工具能够以很直观的形式把这种 case 结构转换为多路选择器 a2 a1 a 数据宽度为 2 的多路选择器 2 c 图 4-13 构造体 modifid_alg 的硬件实现 例 4-15 architecture modified_alg of count_one is b:process(a) case a is when"000"=>c<="00"; 155

156 when "001" "010" "100"=>c<="01"; when "011" "110" "101"=>c<="10"; when "111"=>c<="11"; end case; end process b; end modified_alg; 一般说来, 如果期望将硬件的系统级模型用于综合, 建议采用下述准则书写 VHDL 模型 : 将硬件的行为指标以合理的方式映射为一些进程 ; 对于每个进程完成的操作, 尽量选择最有效的算法 ; 了解综合器的性能, 特别是了解综合工具支持的 VHDL 可综合子集, 并以合理的代码风格引导综合工具生成硬件 ; 在条件允许的情况下, 尽量用变量代替信号, 对于固定值的信号要用常数代替 ; 尽量共享复杂运算, 可以共享的数据处理用函数或过程定义 ; 明确指出电路的无关态 (don't care condition), 引导综合器进行优化 ; 使用能够满足需要的最小数据宽度 ; 用组合逻辑实现的电路和用时序逻辑实现的电路分配到不同的进程中 现在再给出一个例子, 其 VHDL 源代码按本章建议的风格写出, 可以用于自动综合 在例 4-16 中, 实体 alu 给出的是该电路的 VHDL 行为描述, 根据输入信号 con 的不同状态, 进程 functions 可能完成 4 种不同的功能 :f =a,f =not a,f =a+b,f =a and b 在时钟 clk 的上升沿, 进程 store 将 f 的内容复制到 fout 图 4-14 是该电路的方框图, 这个结构可以通过 VHDL 描述综合得出 VHDL 源代码中的 case 语句映射为多路选择器 ; 数据运算 非 加 与 等被映射为组合逻辑电路 ; 进程 store 被映射为触发器 ; 信号 f 映射为多种选择器的输出端与寄存器的输入端之间的连线 自动综合工具很容易识别出 case 语句, 并把它对应于一个多路选择器 因为进程 store 由时钟 clk 触发, 所以按照寄存器推断原则, 被映射为触发器 由于采用了适合综合的 VHDL 源代码设计风格, 将算法描述转换为硬件变得相对简单 8 a con clk 2 not add 8 8 多路选择器 mux 8 寄存器 8 reg F fout b 8 and 8 图 4-14 实体 ALU 的硬件实现 例 4-16 entity alu is 156

157 port(con:in std_logic_vector(1 downto 0); a,b:in std_logic_vector(7 downto 0); clk:in std_logic; fout:out std_logic_vector(7 downto 0)); end alu; architecture alg of ALU is procedure add(a,b:in std_logic_vector;sum:out std_logic_vector)is variable sumv,av,bv:std_logic_vector(a'length-1 downto 0); variable carry:std_logic; av:=a; bv :=b; carry :='0'; for i in 0 to sumv'high loop sumv(i):=av(i)xor bv(i)xor carry; carry:=(av(i)and bv(i))or (av(i) and carry)or(bv(i)and carry); end loop; sum :=sumv; end add; signal f : std_logic_vector(7 downto 0); functions:process(con,a,b) case con is when"00"=>f<=a; when"01"=>f<=not a; when "10"=>add(a,b,f); when"11"=>f<=a and b; end case; end process functions; store:process(clk) if clk='1'and clk'event then fout<=f; end if; end process store; end alg; 157

158 4.3.3 处理复位的方法 在例 4-17 中的实体 wait_steps 的构造体 one 中, 描述了一个控制单元, 这个控制单元可以在任何状态被复位 通过在循环体内插入控制序列可以实现这样的功能 在控制序列中每一步的时钟沿上, 都用 wait 和 next 语句检查信号 reset 上是否发生事件, 一旦信号 reset 上发生事件, 则程序将转移到循环体的标号处, 即重新开始这一循环体 程序将在循环体的顶部等待直到 reset 变为 '0' 这样, 在构造体 one 中, 仿真开始时模型等待 run='1', 一旦出现 run='1', 该控制单元按状态 0,1,2 的顺序循环执行直到出现 r='1' 这时程序不再执行这一循环而进入等待状态 最初一个 wait 语句用来防止 r='1' 时出现无限循环, 从而不能省略 例 4-17 use IEEE.std_logic_1164.all; entity wait_steps is port(clk,r,run:in std_logic; s:out integer); end wait_steps; architecture one of wait_steps is running:process wait until clk='1'and clk'event and r='0'and run='1'; loop1:while r='0'and run='1'loop s<=0; next loop1 when r='1';--step 0 wait until clk='1'and clk'event and r='0'; s<=1; next loop1 when r='1';--step 1 wait until clk='1'and clk'event and r='0'; s<=2; next loop1 when r='1'; --step 2 wait until clk='1'and clk'event and r='0'; end loop loop1; end process; end one; 158

159 4.3.4 时分复用 采用总线决断函数实现总线分时共享的方法称为时分复用 决断函数的说明与定义方法见第 2 章 这种时分复用通常是使所有不活动的总线驱动器维持在 'z' 态来实现的 事实上, 还可以用其他方式实现时分复用 下面的 VHDL 源代码通过两个保护赋值模块实现时分复用 例 4-18 use IEEE.std_logic_1164.all; entity time_mux is generic(del1,del2:time); port(phase_one,phase_two:in std_logic; zout:out std_logic); end time_mux; architecture guard_block0 of time_mux is ph_one:block(phase_one='1') zout<=guarded'0' after del1: end block ph_one; ph_two:block (phase_two='1') zout<=guarded '1' after del2; end block ph_two; end guard_block0; 模块 ph_one 在 phase_one='1' 时把信号 zout 赋值为 '0', 模块 ph_two 在 phase_two='1' 时把信号 zout 赋值为 '1' phase_one 和 phase_two 是两相互不交迭的时钟 ( 如图 4-15 所示 ), 它们不能同时为 '1' 信号 zout 是一个 std_logic 信号, 其默认值为 'u' 假定硬件设计者的目的是 : 当 phase_one 为 '1' 时, 由模块 ph_one 控制信号 zout 的取值 ; 当 phase_two 为 '1' 时, 由模块 ph_two 控制信号 zout 的取值 当 phase_one 和 phase_two 全为 '0' 时, 信号 zout 保持其原有值 假设模块 ph_one 中信号 zout 的驱动器的输出值为 '0', 而模块 ph_two 中信号错误 如图 4-15 所示 为了分析出现这种情况的原因, 假定在 phase_one 变为 '1' 之前, 信号 zout 的初始状态为 'x'; 当 phase_one 变为 '1' 时, 信号 zout 的值成为 '0' 此后, 当 phase_two 变为 '1' 时, 期望信号 zout 的值会变成 '1' 由于总线决断函数是其驱动器输出值的静态函数, 决断函数的取值与其驱动器上的值保持了多长时间无关, 因此, 这时信号 zout 的取值并不是期望值 '1', 而变成了 'x' 159

160 phase_one phase_two 信号 zout x 0 x 图 4-15 两相互不交迭时钟及不正确的时分复用 为了解决这种不正确的分时复用问题, 可以设想用驱动器最新变化后的值控制信号 zout 的取值 对构造体 guarded_block0 稍加改动, 使得当保护信号值 phase_one 变为 '0' 时, 信号 zout 的驱动器输出值为 'z', 以修正这一错误, 即 if phase_one'event and phase_one='0' then zout<='z'; 然而, 这种修正并不能完全解决问题 如果两个保护信号全为 '0', 则信号 zout 的两个驱动器的输出值全为 'z', 从而信号 zout 的取值也为 'z' 这与设计要求 信号 zout 应保持其原有值 也不相符 事实上, 决断函数只能是其输入值的组合逻辑函数, 不可能实现 保持其原有值 这种设计要求 而且, 以上的修正代码不能被综合, 这种设计通常不能被接受 为了完全解决上述错误的时分复用问题, 应该同时使用总线决断函数和保护模式信号赋值 VHDL 语言为用保护模式赋值的信号提供了特殊处理 如果设计者期望实现时分复用, 应该将信号说明为寄存器型 (register) 或总线型 (bus) 对于寄存器型或总线型信号的保护模式赋值, 如果信号驱动模块的保护条件为 false, 则驱动该信号的赋值语句的作用是将信号与驱动器分离, 从而使决断函数忽略该驱动器的作用 ; 如果信号驱动模块的保护条件为 true, 则信号驱动器的值进入总线决断函数 这样, 假定将信号 zint 的所有驱动器全与信号分离, 如果 zint 是一个寄存器, 它的值会保持其原来的数值 ; 如果 zint 是一个总线, 则它被赋值为总线决断函数的输出默认值 在 IEEE9 值逻辑中, 总线决断函数输出的缺省值为 'z' 如果 zint 为寄存器, 它应该为带时钟使能的寄存器 ; 如果 zint 为总线, 它也应该为带时钟使能的总线 图 4-16 示意给出了带使能时钟的寄存器和总线的结构模型 例 4-19 的 VHDL 源代码说明了如何同时使用保护模式赋值和寄存器型 总线型信号实现时分复用 例 4-19 architecture guard_block1 of time_mux is signal zint:std_logic register; ph_one:block(phase_one='1') zint<=guarded'0'after del1; end block ph_one; ph_two:block(phase_two='1') ϕ & 160 ϕ 1 1D Q zout ϕ & >C1 ϕ 1

161 (a) 带时钟的寄存器时分复用 ϕ & ϕ & 1 IEN zout ϕ 1 ϕ (b) 带时钟的总线时分复用 zint<=guarded'1'after del2; end block ph_two; zout<=zint; end guard_block1; 图 4-16 带使能时钟的寄存器和总线时分复用 architecture guard_block2 of time_mux is signal zint:std_logic bus; ph_one:block(phase_one='1') zint<=guarded'0'after del1; end block ph_one; ph_two:block(phase_two='1') zint<=guarded'1'after del2; end block ph_two; zout<=zint; end guard_block2; 如果对信号赋值的是进程而不是保护模式赋值模块, 也存在仿真所得到的信号波形出 161

162 错的问题 下面例 4-20 的 VHDL 源代码表明了这种问题存在的情况 这里有两个进程驱动信号 zout, 由于信号 zout 是其驱动器的静态函数, 其取值与该值保存在其驱动器上的时间长短无关, 这个模型得出的信号 zout 的波形仍然如图 4-15 所示 例 4-20 architecture proc of time_mux is ph_one: process (phase_one) if phase_one='1' then zout<='0'after del1; end if; end process ph_one; ph_two:process(phase_two) if phase_two='1' then zout<='1' after del2; end if; end process ph_two; end proc; 解决这一问题的方法与保护模式模块赋值类似, 可引入一个寄存器型信号 zint 在两个进程中, 如果条件 phase_one 或 phase_two 变为 '0', 则把值 null 赋给 zint, 这样做的效果是使信号驱动器与决断函数隔离开 如果 phase_one 和 phase_two 不同时为 '1', 则只有活动驱动器 ( 非 null 值 ) 可以进入决断函数 如果 zint 的两个驱动器全不活动, 因为 zint 为一个寄存器, 所以 zint 保持其旧值 假定 zint 为一个总线信号, 在其所有驱动器输出值全为 null 的情况下, 总线的取值为总线决断函数的缺省输出值 下面例 4-21 的 VHDL 源代码实现了这种时分复用 例 4-21 实现时分复用的另一种形式 architecture proc_null of time_mux is signal zint:std_logic register; ph_one:process(phase_one) if phase_one='1' then zint<='0' after del1; else zint<=null; end if; end process ph_one; ph_two:process(phase_two) 162

163 if phase_two='1' then zint<='1' after del2; else zint<=null; end if; end process ph_two; zout<=zint; end proc_null; 应该指出的是, 由于在系统级采用了一定的 VHDL 语法结构描述电路行为才出现了这里的时分复用问题 在硬件设计的较低层次, 只要设计出实际电路, 就自然可以实现时分复用, 可以按这个实际电路写出 VHDL 行为模型 尽管如此, 在系统级设计时, 在没有设计出电路之前, 写出具有时分复用能力的算法模型还是很有用的 另一方面, 如果要求采用自动综合工具综合出具有时分复用能力的硬件, 那么硬件的行为描述必须能描述时分复用的行为 4.4 系统级算法模型设计举例 简单的 4 模块系统 本节以一个具体设计为例, 讨论系统级设计常会遇到的一些问题, 如系统设计的划分 行为级描述向寄存器传输级代码的转换 多值逻辑 复用和模块之间的通信协议等 下例可以说明如何用多值逻辑系统构造硬件系统的模型 图 4-17 示意地给出了由 3 个模块组成的系统,3 个模块分别是带缓冲寄存器 (buff_reg) 随机存储器(RAM) 加法- 存储模块 (add_store) 该硬件将完成如下功能: reset en 带缓冲的寄存器 buff_reg di strb clk dav daddr 加法 - 存储模块 add_store data_bus RAM_addr memen read write rack wack 随机存取存储器 RAM 图 个模块组成的硬件系统 当闸门信号 (strb) 变高时, 数据通过端口 di 送入 buff_reg 中, 并将数据有效标 163

164 志 dav 置位为 '1'; 当 dav 变为 '1' 后, 模块 add_store 作出响应 : 首先设 en='1', 该信号使用 buff_reg 中的数据送上总线 data_bus, 并将信号 dav 复位为 '0'; add_store 从 data_bus 上得到数据并把数据存储在其内部寄存器中 ; add_store 按地址总线 RAM_addr 指明的地址, 从 RAM 中读取数据 ;RAM_addr 通过端口 daddr 预先设定数值 读操作结束后, 信号 rack 给出标志 ; add_store 通过数据总线 data_bus 得到 RAM 中的数据, 把它与内部寄存器中原先存储的数据相加后, 再放回到内部寄存器 ; add_store 开始 RAM 写操作, 把内部寄存器中的数据写回到 RAM RAM 地址由 RAM_addr 指定,wack='1' 是写操作结束的标志 ; addr_store 返回到初始状态, 等待从 buff_reg 来的下一次数据有效信号 (dav); 同步时钟为 clk 例 4-22 的 VHDL 源代码是 buff_reg 的算法模型, 模型本身很容易读懂, 在时钟上升沿上如果 strb 有效, 则锁存数据 ;en 有效时寄存器输出 reg 的值, 否则输出高阻态 例 4-22 带缓冲的寄存器的 VHDL 模型 use IEEE.std_logic_1164.all; entity buff_reg is generic(str_del,dav_del,odel:time); port(di:in std_logic_vector(7 downto 0); clk:in std_ligic; strb,en:in std_logic; dav:out std_logic; do:out std_logic_vector(7 downto 0):="zzzzzzzz"); end buff_reg; architecture two_proc of buff_reg is signal reg:std_logic_vector(7 downto 0); front_end:process(clk,strb,en) if en='1' then dav<='0' after dav_del; elsif clk='1'and clk'event and strb='1'; reg<=di after strb_del; dav<='1'after dav_del; end if; end process front_end; 164 output:process(reg,en)

165 if en='1'then do<=reg after odel; else do<=(others=>'z'); end if; end process output; end two_proc; 例 4-23 给出了随机存取存储器 RAM 的算法模型, 这个模型也很直观, 容易理解 它对信号 cs rd 或 wr 的变化作出响应, 可以对它进行读操作或写操作 读写操作结束的标志位 rack 和 wack 的持续时间为 ack_pw 例 4-23 随机存取存储器 RAM 的 VHDL 模型 use IEEE.std_logic_1164.all; entity RAM is generic(rdel,disdel,ack_del,ack_pw:time); port(data:inout std_logic_vector(7 downto 0):=(others=>'z'); addr:in std_logic_vector(4 downto 0); clk:in std_logic; rd,wr,cs:in std_logic; rack,wack:out std_logic); end RAM; architecture alg of RAM is function intval(val:in std_logic_vector)return integer is variable valv:std_logic_vector(val'length-1 downto 0); variable sum:integer :=0; valv : =val; for i in valv'low to valv'high loop if valv(i)= '1' then sum :=sum+(2**i); end if; end loop; return sum; end intval; mem:process type memory is array(0 to 31)of std_logic_vector(7 down to 0); variable mem:memory :=(others=>'0'); wait until clk='1' and clk'event; if cs ='1' then 165

166 if rd='1' then data<=mem(intval(addr))after rdel; rack<='1' after ack_del, '0' after ack_del+ack_pw; elsif wr='1'then mem(intval(addr)):=data; wack<='1' after ack_del, '0' after ack_del+ack_pw; end if; else data<=(other=>'z')after disdel; end if; end process mem; end alg; add_store 模块控制系统完成各种动作 例 4-24 给出了 add_store 的算法模型 模型中给出了一系列控制操作, 每步控制操作分别用注释 cs0~cs6 标出, 每步控制操作都包括了一些动作且以 wait 语句结束 例 4-24 use IEEE.std_logic_1164.all; entity add_store is generic(con_del,do_del,ma_del,dis_del:time); port:(reset,dav,rack,wack:in std_logic; en,memen,read,write:out std_logic; data:inout std_logic_vector(7 downto 0) :=(others=>'z'); daddr:in std_logic_vector(4 downto 0); maddr:out std_logic_vector(4 downto 0)); end add_store; 166 architecture alg of add_store is control:process variable data_reg:std_logic_vector(7 downto 0); reset_loop:loop data<=(others=>'z')after dis_del; --cs0 wait until clk'event and clk='1'; if reset='1' then exit reset_loop; end if; run_loop:loop

167 wait until clk'event and clk='1'; if reset='1' then exit reset_loop; end if; wait until clk'event and clk='1' and dav='1'; en<='1' after con_del; --cs1 wait until clk'event and clk='1'; en<='0' after con_del; --cs2 data_reg :=data; wait until clk' event and clk='1'; maddr<=daddr after ma_del; memen<='1' after con_del; --cs3 read<='1' after con_del; wait until clk'event and clk='1'and rack='1'; data_reg :=data+data_reg; read<='0 'after con_del; memen<='0' after con_del; --cs4 wait until clk'event and clk='1'; data<=data_reg after co_del; write<='1'after con_del; memen<='1'after con_del; --cs5 wait until clk'event and clk='1' and wack='1'; write<='0'after con_del; memen<='0' after con_del; --cs6 data<=(others=>'z')after dis_del; wait until clk'event and clk='1'; end loop run_loop; end loop reset_loop; end process control; end alg; wait 语句使进程挂起, 直到满足给定条件后再恢复执行 上述几种形式的 wait 语句中, 第 1 种形式规定在条件 condition 为 true 时恢复执行, 常用于检测时钟沿 由于本例是同步电路, 因此 wait until clk'event and clk ='1' 实际隐含表示 : 每来一个时钟, 对应一个控制器的内部状态 在每一个状态上, 控制器发出相应的控制信号 例 4-23 中的实体 add_store 的行为描述 alg 中使用了这一语句, 在时钟沿上检测输入控制信号 dav rack 和 wack, 这也体现了同步操作的概念 这种 wait 语句的好处是可以综合, 一般说来, 现在的 EDA 综合工具把每个进程中 wait until 后面以 signal'event and signal='1'( 或 '0') 形式出现的信号当做时钟处理 ; 同时现有综合工具只能处理一个进程内只有单一时钟的情况, 而且 ASIC 设计方法学也提倡单时钟 同步操作的工作方式 因此, 书写可综合代码时, 必须保证进程内只有一个信号以上述形式出现在 wait 语句中 167

168 wait 语句的第 2 种形式用来描述硬件中的固定延时, 如果时钟信号 clk 的周期为 clk_per, 那么从仿真的角度看,wait for clk_per 与 wait until clk'event and clk='1' 的效果是完全相同的, 但是前一种形式的 wait 语句不能综合 wait 语句的第 3 种形式在后面不带有条件的情况下, 在功能上相当于进程敏感信号列表, 但可以出现在进程中的任意部分, 这种形式常用于描述组合逻辑电路的进程 如果后面带有条件, 则可以描述更复杂的电路, 如异步电路, 但不一定能够被综合 对于这个模型, 另一个值得注意的问题是它们的输出 3 个模块的输出通过信号 data_bus 连接在一起,data_bus 被初始化为 zzzzzzzz 由于 std_logic 型信号的初始默认值为 'u', 所以除非特别指明, 被 data_bus 驱动的所有信号的初始缺省值全为 'u' 为了使总线决断函数正常工作, 所有不活动的信号驱动器的值应都为 'z' 此外, 当某个信号的输出与总线信号的连接断开之后, 它也应返回 'z' 态 这 3 个模块的模型全按这种方式设计 在模块 add_store 的算法模型中, 使用了控制操作序列描述硬件的行为, 每步控制操作中都使用了 wait 语句 我们不但要使用这样的语法结构描述硬件的行为, 而且要了解这样的语法结构对应着什么样的硬件实现 这就提出了一个问题 : 这样的控制操作序列的硬件背景是什么? 为此, 下面要把模块 add_store 进一步划分为控制器部分和数据路径部分 图 4-18 示意给出了这种划分 其中, 控制器部分由控制单元 cu 和时钟生成单元 clk_gen 组成 ; 数据路径部分则是简单的加法单元 adu 在设计划分确定之后, 就可以书写寄存器的传输级代码, 然后进行综合 一般情况, 在寄存器传输级代码中, 控制器的部分以有限状态机的形式书写, 而数据路径则由运算电路和存储电路组成 寄存器传输级的综合技术已经非常成熟, 除非在一些极端情况下, 对这个层次的代码进行综合完全可以达到理想的效果 clk clk_gen daddr en 5 reset memen read dav rack wack cs1 cs 2 cu cs 3 cs 4 cs 5 write adu 5 8 maddr do 图 4-18 模块 add_store 的进一步划分 168 例 4-25 的 VHDL 源代码是时钟生成单元 clk_gen 的行为模型, 该模型仅可用于仿真 例 4-25 use IEEE.std_logic_1164.all; entity clk_gen is

169 generic(per:time); port(clk:out std_loigc); end clk_gen; architecture sim of clk_gen is signal tmp_clk:std_logic; update:process(tmp_clk) tmp_clk<=not tmp_clk after per; end process; clk<=tmp_clk; end sim; 例 4-26 的实体 adu 和相应的构造体 behavior 给出的 VHDL 源代码是加法单元 adu 的算法模型 加法单元 adu 在 clk 的上升沿上检查控制信号 cs2 cs3 cs4 和 cs5, 在这 4 个信号有效时, 该单元分别完成下列操作 : 加载数据寄存器 ; 执行加法运算 ; 输出存储器地址 ; 把数据值送上数据总线 例 4-26 use IEEE.std_logic_1164.all; entity adu is generic(do_del,ma_del:time); port(clk:in std_logic; cs2,cs3,cs4,cs5:in std_logic; data:inout std_logic_vector(7 down to 0) :=(others=>'z'); daddr:in std_logic_vector(4 downto 0); maddr:out std_logic_vector(4 downto 0)); end adu; architecture behavior of adu is du:process(cs2,cs3,cs4,cs5); variable data_reg:std_logic_vector(7 downto 0); wait until clk='1' and clk'event; if cs2='1'then data_reg :=data; elsif cs3='1'then data_reg :=data+data_reg; 169

170 170 elsif cs4='1'then maddr<=daddr after ma_del; elsif cs5='1'then data<=data_reg after do_del; else data<=(other=>'z'); end if; end process du; end behavior; 例 4-27 的实体 cu 和相应的构造体 fsm 给出了控制单元的 cu 的 VHDL 模型 cu 的输入信号为 :reset clk dav rack 和 wack, 其输出信号为 5 个控制信号 cs1 cs5 和存储器使能信号 memen 其中 cs1 cs3 和 cs5 分别连接到 en read 和 write 应该指出的是, 这个模型是寄存器传输级设计, 但为了完整地说明系统设计的概念, 这里也列出了全部寄存器传输级代码 在综合过程中, 综合工具会对状态编码, 一般是把每个控制状态映射为一个 D 触发器, 所以 D 触发器全由同一时钟信号 clk 驱动 ; 另一方面,reset 信号有效时, 系统复位, 全部控制信号复位为 '0' 由于状态的更新完全在时钟沿上进行, 所以这是完全同步的控制单元 从行为上看, 控制单元很像把 '1' 从 cs1 逐次移位到 cs5 的移位寄存器 例 4-27 use IEEE.std_logic_1164.all; entity cu is generic(cs_del:time); port(clk,reset,dav,rack,wack:in std_logic; cs1,cs2,cs3,cs4,cs5, memen:out std_logic); end cu; architecture fsm of cu is type states is (cstep0,cstep1,cstep2,cstep3,cstep4,cstep5); signal state,next_state:states; compute:process(reset,dav,rack,wack,state) case state is when cstep0=> if dav='1'then next_state<=cstep1; else next_state<=cstep0; end if; cs1<='0';cs2<='0';cs3<='0'; cs4<='0';cs5<='0';

171 memen<='0'; when cstep1=> cs1<='1'; if reset='1'then next_state<=cstep0; else next_state<=cstep2; end if; when cstep2=> cs1<='0'; cs2<='1'; if reset='1'then next_state<=cstep0; else next_state<=cstep3; end if; when cstep3=> cs2<='0'; cs3<='1'; if reset='1'then next_state<=cstep0; elsif rack='1'then next_state<=cstep4; else next_state<=cstep3; end if; when cstep4=> cs3<='0' cs4<='1'; if reset='1'then next_state<=cstep0; else next_state<=cstep5; end if; when cstep5=> cs4<='0'; cs5<='1'; if reset='1'then next_state<=cstep0; elsif wack='1'then next_state<=cstep0; 171

172 else next_state<=cstep5; end if; memen<='1'; end case; end process compute; update:process wait until clk='1' and clk'event; state<=next_state; end process update; end fsm; 将实体 cu 的构造体 fsm 同例 4-24 中的实体 add_store 的构造体 alg 进行比较, 可以发现这样一些问题 : 首先, 实体 add_store 的构造体 alg 是行为级代码, 行为级代码的状态是隐含表示的 ; 而实体 cu 的构造体 fsm 的代码是寄存器级的有限状态模型, 其状态是直接表示的 其次, 行为级代码中, 复位处理用 exit 退出当前循环实现, 但是这实际上隐含着一个问题, 主动复位处理只能在循环入口处进行 ; 而寄存器级代码中, 每一个状态均有检查复位信号的代码, 即复位可以在任意状态下开始 最后, 行为模型中, 控制与运算处理结合在一起 ; 而寄存器级代码中, 这两部分是分离的, 控制器只负责输出控制信号, 运算部件由实体 adu 的构造体 behavior 给出, 它只负责在相应控制信号有效时执行操作 之所以做这样的划分, 是为了适应综合工具的要求, 因为综合工具在单独处理有限状态机和数据运算电路时, 能够取得理想的综合结果 172

173 第 5 章数字系统的寄存器传输级设计 本章讨论集成电路的寄存器传输级设计 在寄存器传输级, 硬件通常可以分为控制单元和数据路径两类 控制单元一般用有限状态机方式或微码 ROM 描述 ; 而数据路径则以 VHDL 数据流方式描述, 或者被分解为一系列的寄存器级单元, 如 ALU 矢量寄存器及其控制逻辑等, 然后以互联的形式描述 在当前的超大规模集成电路设计过程中, 主导的设计方法仍然是把寄存器传输级代码作为综合输入, 因此, 本章首先从设计的角度讨论寄存器级 VHDL 模型 这又可以分为两部分, 即如何将数据路径的算法模型变化为数据流模型, 以及如何将算法模型中的控制电路翻译为寄存器级的控制单元 然后, 从综合的角度讨论寄存器级代码与综合电路的关系 合理的代码风格是控制综合的最有效方式, 芯片设计者能够熟练地掌握有效的 VHDL 编程风格, 对于设计成功是至关重要的 5.1 寄存器传输级的电路模型 第 4 章讨论了 VHDL 算法模型的设计, 并对如何把算法模型映射为硬件电路进行了讨论 这里讨论两个基本问题, 第 1 个问题是寄存器传输级的数据流描述与系统级的算法描述的区别在哪里 ; 第 2 个问题是什么样的硬件行为适合于在寄存器级用数据流方式造型, 而不适合于在系统级用算法方式造型 首先考虑第 1 个问题 算法模型意味着将寄存器等电路模块变换为进程, 而数据流模型则是具体表示出寄存器等电路模块 具体地讲, 硬件的数据流模型有如下特点 : 数据流模型中的信号代表了硬件中数据的实际移动方向以及电路的互连关系 ; 数据流模型中的语句与实际寄存器的结构模型之间存在直接的映射关系 ; 数据流模型指定了寄存器级的电路元件之间的连接关系, 从而隐含了电路结构 ; 数据流模型指定了存储单元的复用结构及总线 ; 数据流模型中明确指定了各个寄存器的驱动时钟 ; 数据流模型中通常不采用抽象的数据类型, 如浮点数 记录 (record) 文件等, 而把它们变换成 bit,bit_vector,std_ulogic,std_ulogic_vector,td_logic 和 std_logic_vector 等数据, 从而指定了寄存器的长度 对于第 2 个问题, 由于数据流模型显式定义了寄存器间的互连关系, 下述内容适合于在寄存器级采用数据流模型进行研究, 而不适合在系统级采用算法模型研究 : 寄存器级元件间的时序关系 ; 硬件资源分配 ; 调度 ; 微代码控制单元设计 ; 总线设计 下面采用一个实例说明如何把算法模型变换为寄存器级数据流模型 某系统由两个 173

174 8bit 寄存器 r1 r2 和一个加法器组成 用一个 2 bit 信号 con 设定操作指令, 系统可完成的 4 种操作如下 : 174 con="00", 加载寄存器 r1; con="01", 加载寄存器 r2; con="10", 将 r1 中的数值与 r2 中的数值相加 ; con="11", 从 r1 中减去 r2 的值, 通过 r1 与 r2 的反码相加再加 1 实现 例 5-1 的 VHDL 源代码是该系统的算法模型 这段代码使用了 IEEE 颁布的 std_logic_arith 算术运算程序包, 所以能够直接用 + 实现两个位矢量的相加 ; 综合时也可以使用 std_logic_arith 程序包给出的实现加法的算法来实现加法运算, 当然这一定是通用的算法, 不能针对具体需要进行优化 同时, 在译码器 多路选择器和寄存器分配等方面, 尽管算法模型说明了该系统完成何种功能, 但不能说明硬件如何实现这些功能 因此, 还需要书写数据流式的代码引导综合器得到优化的电路 例 5-1 某系统的算法 VHDL 模型 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; entity reg_sys is port(clk:in std_logic; con:in std_logic_vector(0 to 1); inp:in std_logic_vector(7 downto 0)); end reg_sys; architecture alg of reg_sys is signal r1,r2:std_logic_vector(7 downto 0); function:process wait until clk='1'and clk'event; case con is when"00"=>r1<=inp; when"01"=>r2<=inp; when"10"=>r1<=r1+r2; when"11"=>r1<=r1+not(r2)+ " "; end case; end process; end alg; 例 5-2 的 VHDL 源代码给出了这个系统的寄存器级数据流模型 这个数据流模型具有如下特点 : 信号表示系统中的互连关系 ; 直接定义了寄存器 复用器 译码器 数据运算 ( 加法 求反 加 1 等 ) 等硬件单

175 元 ; 信号赋值表示了数据的实际移动 例 5-2 数据流模型 1 architecture df1 of reg_sys is signal mux_r1,r1,r2,r2c,r2tc,mux_add,sum: std_logic_vector(7 downto 0); signal d00,d01,d10,d11,r1e:std_logic; d00<=not con(0)and not con(1); && 译码部分 d01<=not con(0)and con(1); d10<=con(0)and not con(1); d11<=con(0)and con(1); mux_r1<=sum when d00='0'else inp; && 重复使用 r1 r1e<=d00 or d10 or d11; r1_reg:block(r1e='1'and clk='1'and clk'event) r1<=guarded mux_r1; && 值 r1 end block r1_reg; r2_reg:block(d01='1'and clk='1'and clk'event) r2<=guarded inp; && 值 r2 end block r2_reg; r2c<=not r2; && 求反 r2tc<=not r2+" "; && 加 1 mux_add<=r2tc when d11='1'else r2; sum<=r1+mux_add; && 加法 end df1; 事实上, 对于同一硬件, 还可以写出其他形式的数据流模型 下面例 5-3 的构造体 df2 是该寄存器系统的另一种数据流模型 构造体 df2 只用了两个保护模式赋值语句, 源代码看起来非常简洁 但它不能清楚地表示连接关系, 不适用于进行时序分析等项研究 例 5-3 数据流模型 2 architecture df2 of reg_sys is signal r1,r2:std_logic_vector(0 to 7); r1_reg:block((con(0) or not con (1))= '1'and clk='1'and clk'event) r1<=guarded r1+r2 when(con(0) and not con(1))= '1'else r1+not(r2)+ " "when (con(0)and con(1))= '1'else inp; end block r1_reg; 175

176 r2_reg:block ((not con(0) and con(1))= '1'and clk='1'and clk'event) r2<=guarded inp; end block r2_reg; end df2; 在寄存器系统的第一种数据流模型 df1 中, 采用了集中译码方案, 即先将输入指令 con 译码为控制指令 d00 d01 d10 和 d11, 再用译码后的信号控制系统的动作 可以对该模型作出改变, 不采用集中译码方案, 而采用分别译码的方案 一般地讲, 集中译码方案要比分别译码方案好 首先, 从功能划分角度来看, 集中译码使得电路复杂性降低, 电路及其模型直观易懂, 有利于电路自动综合 ; 其次, 当需要给电路增加新功能时, 若采用集中译码方案, 只需对硬件做较小的改动就可以实现 但是, 对于较简单的硬件, 采用分别译码方案好处很多 例 5-4 中的构造体 df3 是分别译码方案的数据流模型, 图 5-1 实际上是这一分别译码电路的结构模型 图 5-1 寄存器系统 reg_sys 的综合结果 例 5-4 分别译码的数据流模型 architecture df3 of reg_sys is signal mux_r1,r1,r2,r2c,r2tc,mux_add,sum,r1e,r2e: std_logic_vector(7 downto 0); mux_r1<=sum when con(0)='1'else inp; r1e<=con(0)or not con(1); r1_reg:block (r1e='1'and clk='1'and clk'event) -- 复用 r1 176

177 r1<=guarded mux_r1; -- 值 r1 end block r1_reg; r2e= not r1e; r2_reg:block(r2e='1'and clk='1'and clk'event) r2<=guarded inp; -- 值 r2 end block r2_reg; r2c<=not r2; -- 求反 r2tc<=r2c+" "; -- 增加 1 mux_add<=r2tc when con(1)= '1'else r2; -- 加法器复用 sum<=r1+mux_add; -- 加法 end df3; 5.2 数据路径设计 数据路径主要包括组合逻辑电路 存储器电路等 由于存储器往往由专门的厂家根据不同的工艺条件加以生产, 因此, 这里将主要讨论组合逻辑电路的设计问题 在使用本节讨论的电路设计方法之前, 电路设计者必须对设计指标要求进行仔细分析, 作出采用组合逻辑电路完成设计这一决策 例如对下例进行分析, 可以得出适宜用组合逻辑电路实现的结论 设计一个二进制比较器, 使它对两个二进制数进行比较, 以确定哪一个数大 输入是两个 2 位的二进制数, 分别用 n=n1n0 和 m=m1m0 表示 输出为二进制信号 ge,le,g,e 和 l 电路的方框图和功能定义如图 5-2 所示 根据方框图以及功能描述, 很容易得出描述电路接口的 VHDL 实体如例 5-5 所示 n1 n0 n 1 n 0 比较器 com ge le e g l l: 当 n 的数值比 m 小时该信号输出为 true e: 当 n 的数值与 m 相等时该信号输出为 true g: 当 n 的数值比 m 大时该信号输出为 true le: 当 n 的数值比 m 小或 n 的数值与 m 相等时该信号输出为 true ge: 当 n 的数值比 m 大或 n 的数值与 m 相等时该信号输出为 true 图 5-2 电路方框图 例 5-5 entity com is 177

178 generic(del:time); port(n1,n0,m1,m0:in std_logic; ge,le,e,g,l:out std_logic); end com; 在上述 VHDL 实体 com 中, 类属参数 del 用来描述从信号输入到信号输出的延时 电路有 4 个输入端口 (n0,n1,m0,m1), 分别用来表示两个二进制输入数据 ; 有 5 个输出信号, 分别表示对两个输入信号比较的结果 对这个电路的性能指标要求进行分析可知, 电路的输出完全由电路当前的输入信号确定, 与过去的输入无关 因此, 有可能用组合逻辑电路实现电路的功能 作出了可以用组合逻辑实现该电路的结论之后, 就可以利用本节讨论的方法进行电路设计 应该指出的是, 可以用组合逻辑实现电路, 并不意味着只能用组合逻辑实现 事实上, 对同一个电路有可能既可以用组合逻辑实现, 又可以用时序逻辑实现 系统级的组合逻辑电路设计 如果输入信号的个数不太多, 组合逻辑电路设计中最常用的方法是写出真值表 真值表将电路输入的所有可能组合列出, 并给出对应各种输入组合下的输出值 上例的真值表如表 5-1 所示 表 5-1 二进制比较器的真值表 输 入 输 出 n1 n0 m1 m0 ge le e g l 在本小节中, 我们讨论将真值表变换为行为域中的算法描述的两种方法 第 1 种方法是数组式模型, 它可以直接映射为 ROM 硬件实现 ; 第 2 种方法是 case 条件语句模型, 它 178

179 可以直接映射为多路选择器 通过优化过程, 还可以用其他多路选择器结构实现真值表 1. 第 1 种方法 : 数组模型下面例 5-6 的 VHDL 源代码实现了表 5-1 描述的硬件行为 在这个 VHDL 模型中, 为了方便定义数据类型使用了程序包 truth4x5 常数 num_outputs 和常数 num_inputs 分别为输出和输入端口的个数 在器件 com 中, 它们分别为 5 和 4 器件 com 的输出端口为 ge le e g 和 l, 输入端口为 n1 n0 m1 和 m0 常数 num_rows 是真值表的行数, 其数值可由输入信号的个数计算得到, 即 :num_rows=2 num_inputs, 在器件 com 中其值为 16 对应于真值表中的一行, 各个输出的数值可以表示为一个矢量, 这个矢量的数据类型定义为 word 例如, 真值表中第 0 行的一个 word 为 (1,1,1,0,0) 整个真值表用一个常数矢量 truth 表示, 该矢量的每一个元素是一个 word 矢量 truth 在定义时就被初始化为硬件的真值表, 矢量的下标范围为 0 15 在真值表中,truth 的下标值为整数, 程序包中给出了一个函数 intval, 用以把位矢量转换为相应的整数, 例如将 0101 转换为 5 例 5-6 package truth4x5 is constant num_outputs:integer :=5; constant num_inputs:integer :=4; constants num_rows:integer :=2 * * num_inputs; type word is array (num_outputs-1 downto 0)of std _logic; type addr is array (num_inputs-1 down to 0)of std_logic; type mem is array (0 to num_rows-1)of word; constant truth:mem( "11100", "01001", "01001", "01001", "10010", "11100", "01001", "10010", "10010", "11100", "01001", "10010", "10010", "10010", "10010", "11100 ); function intval(val:addr)return integer; end truth4x5; package body truth4x5 is 179

180 function intval(val:addr)return integer is variable sum:integer :=0; for n in val'low to val'high loop if val(n)= '1'then sum:=sum+(2 **n); end if; end loop; end intval; end truth4x5; 180 use work.truth4x5.all; architecture table of com is process(n1,n0,m1,m0) variable index:integer; variable wout:word; index:=intval(n1&n0&m1&m0); wout :=truth(index); ge<=wout(4)after del; le<=wout(3)after del; e<=wout(2)after del; g<=wout(1)after del; l<=wout(0)after del; end process; end table; 函数 intval 通过对一个位矢量从右向左扫描, 确定它的哪一位为 '1', 按每位所对应的权重因子把它转换为十进制整数 在函数 intval 中, 变量 sum 用来存储部分和, 它被初始化为 '0' 信号属性 val'low 等于位矢量下标范围的最小值 (0); 信号属性 var'high 等于位矢量下标范围的最大值 (4) 这里使用信号属性而不使用常数的作用在于: 写出的函数 intval 是一个通用转换函数, 它可以把任意长度的位矢量转换为整数 实体 com 的构造体为 table, 它由一个进程组成, 在任何一个输入信号发生变化时, 进程都会被激活 进程的行为只是简单查表 在进程中, 首先用函数 intval 把输入信号转换为相应的整数, 用此整数作为下标在数组 truth 中选择适当的输出值 wout, 经延时之后把 wout 的值赋给输出信号 在自动综合程序中, 很容易将这个 VHDL 数组模型映射为一个 ROM,ROM 中存储的内容是常数 truth 中的数值, 器件 com 的 4 个输入信号连接到 ROM 的地址输入, 器件 com 的输出加接到 ROM 的数据输出端, 这需要规模为 16 5 的存储区 这样, 由 ROM 实现的二进制比较器电路如图 5-3 所示

181 n1 a3 d4 ge n 0 a d3 lk m1 a1 ROM d2 d1 e g m 0 a0 d0 l 图 5-3 直接将真值表数组转换为 ROM 2. 第 2 种方法 :case 条件语句模型在这种 VHDL 行为模型中, 真值表中的每一行对应于 case 条件语句的一种选择条件 下面例 5-7 的 VHDL 源代码是二进制比较器电路的 case 条件的语句模型 例 5-7 architecture mux of com is process(n1,n0,m1,m0) case n1&n0&m1&m0 is when"0000"=>ge<='1'after del;le<='1'after del; e<='1'after del;g<='0'after del;l<='0'after del; when"0001"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when"0010"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when"0011"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when"0100"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"0101"=>ge<='1'after del;le<='1'after del; e<='1'after del;g<='0'after del;l<='0'after del; when"0110"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when"0111"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when 1000 =>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"1001"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"1010"=>ge<='1'after del;le<='1'after del; e<='1'after del;g<='0'after del;l<='0'after del; 181

182 when"1011"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when"1100"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"1101"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"1110"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when"1111"=>ge<='1'after del;le<='1'after del; e<='1'after del;g<='0'after del;l<='0'after del; end case; end process; end mux; 与例 5-6 中的构造体 table 类似, 这个构造体 mux 同样由一个进程组成, 当输入信号 n1,n0,m1,m0 之一发生变化时, 进程被激活 整个进程利用一条 case 条件语句, 对应于输入信号的 16 种不同组合, 为输出信号赋不同的值 这里 case 语句的条件以及为信号赋的值直接从硬件的指标推出, 即直接从真值表推导出 二进制比较器的这种 VHDL 模型可以直接转换为多路选择器 图 5-4 是用多路选择器实现的输出信号 ge 4 个输入信号连接在多路选择器的地址输入端, 把 n1 连接到地址输入的最高位,m0 连接到地址输入的最低位 连接到多路选择器的数据输入端的数据是 case 条件语句中为 ge 所赋的数值 例如, 在 case 条件语句中, 如果输入为 0101, 则为 ge 赋值 '1', 所以在多路选择器中数据位 d5='1' 再利用另外 4 个多路选择器, 可以类似地用硬件实现输出信号 le,e,g 和 l n1 n0 m1 m d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 a3 a2 a1 a mux o ge 图 5-4 把 case 条件语句式的 VHDL 模型直接转换为硬件多路选择器 182

183 3.VHDL 算法模型的优化前面用多路选择器实现的输出信号 ge 是直接将 case 条件语句转换为硬件, 事实上, 用多路选择器实现这样的输出, 也可能存在更有效的方法 通常, 可以先对硬件的 VHDL 模型进行一些变化, 再将变换后的算法模型转换为硬件, 这样的变换过程称为 VHDL 模型的优化 真正的优化要利用设计者的知识, 需要人工智能的方法才能自动完成 本节只考虑特殊情况, 即对这个用多路选择器实现 case 条件语句的 VHDL 模型进行优化 将某个输入信号从多路选择器的地址输入端消除, 是实现 VHDL 模型优化的最简单的方法 为了说明这种方法, 任意选择一个输入信号, 不失一般性, 可以选择 m0, 把它从多路选择器的地址输入端消去 优化后的 VHDL 算法模型如例 5-8 所示 例 5-8 architecture mux3 of com is process(n1,n0,m1,m0) case n1&n0&m1 is when"000"=>ge<=not m0 after del;le<='1'after del; e<=not m0 after del;g<='0'after del;l<=m0 after del; when"001"=>ge<='0' after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when "010"=>ge<='1'after del;le<=m0 after del; e<=m0 after del;g<=not m0 after del;l<='0'after del; when"011"=>ge<='0'after del;le<='1'after del; e<='0'after del;g<='0'after del;l<='1'after del; when 100 =>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when "101"=>ge<=not m0 after del;le<='1'after del; e<=not m0 after del;g<='0'after del;l<=m0 after del; when"110"=>ge<='1'after del;le<='0'after del; e<='0'after del;g<='1'after del;l<='0'after del; when "111"=>ge<='1'after del;le<=m0 after del; e<=m0 after del;g<=not m0 after del;l<='0'after del; end case; end process; end mux3; 为了说明如何得到这种优化后的 VHDL 模型, 先来看构造体 mux 中 case 语句的两个条件 0000 和 0001 现在的目的是把条件中的 m0 项消除, 用同一个条件 n1&n0&m1= 000 来代替这两个条件 对于每个输出变量 (ge,le,e,g 和 l), 在优化前的构造体 mux 中, 对应于信号 m0 的不同值, 对输出信号的赋值可能出现下面 4 种可能情况 : 对于 m0 的两种不同值, 输出信号的值总与 m0 相同 这时可以把输出信号的值赋为 183

184 m0 对于 m0 的两种不同值, 输出信号的值总与 not m0 相同 这时可以把输出信号的值赋为 not m0 对于 m0 的两种不同值, 输出信号的值总为 '1' 这时可以把输出信号的值赋为'1' 对于 m0 的两种不同值, 输出信号的值总为 '0' 这时可以把输出信号的值赋为'0' 在优化前的 VHDL 行为描述 mux 中, 在输入信号为 0000 时 ( 这时 m0 为 '0'), 为信号 ge 赋值 '1'; 在输入信号为 0001 时 ( 这时 m0 为 '1'), 为信号 ge 赋值 '0' 所以对于输出 ge, 可以把构造体 mux 中的 case 语句中的 0000 和 0001 两种条件简化为构造体 mux3 中的一种条件, 即输入为 n1&n0&m1= 000 时, 为 ge 赋值 not m0 对于输出信号 le, 在输入信号为 0000 和 0001 两种情况下都是对它赋值为 '1', 所以可以把优化前的两种条件简化为一种条件, 即输入信号为 n1&n0&m1= 000 时, 为信号 le 赋值 '1' 与此类似, 可以对其他输出信号 (e,g 和 l) 做同样的优化 对于优化前的 VHDL 模型中的其他各对条件 ( 0010 和 0011, 0100 和 0101 等等 ), 进行类似的优化, 就可以得到如构造体 mux3 所示的优化后的 VHDL 行为模型 优化后的 VHDL 模型同样可以转换为多路选择器, 图 5-5 是把构造体 mux3 中的输出信号 ge 的行为描述转换为多路选择器后的硬件实现 这里, 把输入信号 n1 n0 和 m1 映射到多路选择器的地址输入端, 同样根据 case 条件语句中的赋值内容确定多路选择器的数据输入 例如,case 语句中输入条件为 000 时为信号 ge 赋值 not m0, 所以应将 m0 反相后接到数据端 d0 再例如,case 语句中输入条件为 110 时为信号 ge 赋值 '1', 所以应该将常数 '1' 连接到数据输入端 d6 采用与此类似的硬件结构, 可以实现二进制比较器的其他 4 个输出信号 le e g 和 l m0 n1 n0 m1 a3 a2 a mux o ge 1 1 图 5-5 将优化后的 case 条件语句式 VHDL 模型转换为多路选择器这里介绍的器件模型是 VHDL 算法模型, 优化也是对硬件的算法模型进行的, 所以有可能写出通用的程序, 对 case 条件语句式的硬件模型进行优化, 以得到优化后的 VHDL 硬件模型 组合逻辑电路的行为域数据流模型 根据第 1 章中关于数字集成电路设计方法学的讨论, 设计者通常需要把硬件的算法式行为描述转换为数据流描述 在完成这一转换的过程中, 通常要对模型进行优化 在设计变量个数较少时, 设计者可以应用卡诺图对硬件模型进行优化, 一般认为设计变量个数小 184

185 于 7 时, 可以使用卡诺图 当设计变量个数较多时, 应该采用适宜于编程的算法 例如 quine-mcclusky 法, 它适宜于设计变量个数为 7 20 的情况 通常也要利用设计者的知识, 对各设计变量之间的关系进行分析, 以简化设计内容 1. 设计的分解对于上例讨论的二进制比较器, 可以看出电路的 5 个输出信号并不是完全独立的 这种优化工作主要靠设计者根据经验作出, 而一般自动设计工具还做不到这一点 通过对该电路的 5 个输出进行分析, 可知输出信号 e g 和 l 可以通过信号 ge 和 le 导出 它们之间的关系如下 : e=ge and le; g=ge and (not le); l=le and (not ge); 因此, 只需显式确定出信号 ge 和 le 与输入信号的关系即可 2. 硬件行为的数据流描述的综合理论上, 根据真值表就可以直接得到组合逻辑电路的数据流模型, 也可以直接得到组合逻辑电路的逻辑门级的结构描述 把真值表中所有乘积项相加, 就可以完成这种 VHDL 模型 但是, 这样得到的电路可能非常不经济 在变量个数不太大的情况下, 通常使用卡诺图进行优化 图 5-6 是优化后的用 乘积项之和 表示的输出信号 ge 和 le 的逻辑表达式 ; 图 5-7 是优化后的用 和项之积 表示的输出信号 ge 和 le 的逻辑表达式 由于 和项之积 表示的逻辑关系在此例中使用的门数较少, 且使用的逻辑的输入端个数也较少, 所以在后面的讨论中经常将使用 和项之积 表示的逻辑关系 Z = N N Z = M 1 0 M + N M M N + N M + M M M N + N N 图 5-6 优化后的两级 乘积项之和 的逻辑表达式根据输出信号的逻辑表达式, 很容易得到输出项的 VHDL 数据流模型 下面的 VHDL 源代码就是根据优化后的 和项之积 的逻辑表达式得出的二进制比较器的数据流式模型 按当前的技术水平, 逻辑优化还不能完全自动完成 对于变量个数较少的问题, 自动逻辑优化程序可能得出最优的结果 ; 但对于规模较大的问题, 只能通过设计者进行人工干预才能得出令人满意的结果 N 0 + M M 0 N

186 Z = N M Z = N 1 N 1 0 M M N M + N N M N M + N M M Z = (N + M + M )( N + M )( N + N + M ) 1 Z = (N + N + M )(N + M )(N + M + M ) 图 5-7 优化后的两级 和项之积 的逻辑表达式 例 5-9 entity com is generic(del:time) port(n1,n0,m1,m0:in std_logic;ge,le,e,g,l:out std_logic); end com; architecture posdf of com is process(n1,n0,m1,m0) signal z1,z0:std_logic; z1<=(not n0 or m1 or m0)and (not n1 or m1)and (not n1 or not n0 or m0); z0<=(n1 or n0 or not m0)and (n1 or not m1)and (n0 or not m1 or not m0); ge<=z0 after del; le<=z1 after del; e<=z1 and z0 after del; g<=z0 and not z1 after del; l<=z1 and not z0 after del; end process; end posdf; 根据 乘积项之和 或 和项之积 的逻辑表达式, 可以得出电路的其他形式的 VHDL 数据流模型 例如, 对逻辑表达式按如下规则进行变换, 也可以得到一种形式的 VHDL 数据流模型, 这个模型中不使用与门, 而使用或非门 它的规则如下 : 在每个 和项 前, 即每个括号前插入一个算子 not 186

187 把每个与算子 and 换成或算子 or, 并在整个逻辑表达式前增加一个算子 not 下面的 VHDL 源代码是把例 5-9 中的构造体 posdf 按上述规则变换后得到的电路 com 的数据流模型 读者可以对这两个模型进行仿真, 以验证两个模型行为的一致性 例 5-10 entity com is generic(del:time) port(n1,n0,m1,m0:in std_logic;ge,le,e,g,l:out std_logic); end com; architecture nordf of com is process(n1,n0,m1,m0) signal z1,z0:std_logic; z1=not((not(not n0 or m1 or m0)) or(not(not n1 or m1))or(not(not n1 or not n0 or m0))); z0<=not(not(n1 or n0 or not m0)or not (n1 or not m1)or not (n0 or not m1 or not m0)); ge<=z0 after del; le<=z1 after del; e<=z1 and z0 after del; g<=z0 and not z1 after del; l<=z1 and not z0 after del; end process; end nordf; 组合逻辑电路的门级结构域综合 本小节讨论如何把组合逻辑电路的数据流模型转换为逻辑门级结构描述 这一过程相对容易编制程序以实现自动综合 一般地讲, 给出一个硬件的数据流式模型, 可以把它转换为不同的几个逻辑门级结构模型 例如, 对于上面讨论的二进制比较电路的 乘积项之和 式的数据流模型, 可以被直接转换为两级 与或电路 两级 与非与非电路 两级 或与非电路 或其他多级电路实现 同样, 对于上述电路的 和项之积 式的数据流模型也可以直接转换为两级, 或与电路 两级 或非或非电路 两级 与或非电路 或其他多级电路实现 从前面讨论的二进制比较电路的数据流模型 posdf 出发, 简单地把每个算子 and 换做 2 输入与门 把每个算子 or 换作 2 输入或门, 就可以得到如图 5-8 所示的电路 为了验证这样的逻辑门级结构模型的行为正确, 可以写出例 5-11 所示 VHDL 源代码所示的结构式模型 在二进制比较器的这个结构式模型中, 电路用基本逻辑门互连构成, 基本逻辑门用行为描述 整个器件的延时转换为多个逻辑门的延时 对这一层次的硬件模 187

188 型进行仿真, 可以得到比算法模型精确的时序信息, 仿真结束后, 可以根据仿真结果把时序信息反向标注到硬件的算法式模型中 如果将此二进制比较器用于其他电子系统设计, 在高层次仿真时, 可使用该器件的算法模型 由于算法模型中的时序信息是根据逻辑门级仿真结果反向标注得到, 所以算法模型的精度与门级精度相同, 但算法模型的仿真效率会比门级模型高很多 图 5-8 数据流模型直接转换为逻辑门级模型 : 两级或与 电路实现 例 5-11 use work.all; entity com is port(n1,n0,m1,m0:in std_logic:ge,le,e,g,l:out std_logic); end com; 188

189 architecture two_level_or_and of com is signal z10,z11,z12,z00,z01,z02:std_logic; signal n0bar,n1bar,m0bar,m1bar:std_logic; singal z0,z1,z0not,z1not:std_logic; component not2g generic(del:time); port(i:in std_logic;o:out std_logic); end component; for all:not2g use entity not2(behavior); component and2g generic(del:time); port(i1,i2:in std_logic;o:out std_logic); end component; for all:and2g use entity and2(behavior); component and3g generic(del:time); port(i1,i2,i3:in std_logic;o:out std_logic); end component; for all:and3g use entity and3(behavior); component or2g generic(del:time); port(i1,i2:in std_logic;o:out std_logic); end component; for all:or2g use entity or2(behavior); component or3g generic(del:time); port(i1,i2,i3:in std_logic;o:out std_logic); end component; for all:or3g use entity or3(behavior); component wireg port(i:in std_logic;o:out std_logic); end component; for all:wireg use entity wire(behavior); c1:not2g generic map (2 ns) port map (n0,n0bar); c2:not2g generic map(2 ns) port map(n1,n1bar); 189

190 190 c3:not2g generic map(2 ns) port map(m0,m0bar); c4:not2g generic map(2 ns) port map(m1,m1bar); c5:or3g generic map(2 ns) port map(n0bar,m1,m0,z10); c6:or2g generic map(2 ns) port map (n1bar,m1,z11); c7:or3g generic map (2 ns) port map(n1bar,n0bar,m0,z12); c8:and3g generic map (2 ns) port map(z10,z11,z12,z1); c9:or3g generic map (2 ns) port map(n1,n0,m0bar,z00); c10:or2g generic map (2 ns) port map(n1,m1bar,,z01); c11:or3g generic map (2 ns) port map(n0,m1bar,m0bar,z02); c12:and3g generic map (2 ns) port map(z00,z01,z02,z0); c13:not2g generic map (2 ns) port map(z1,z1not); c14:not2g generic map (2 ns) port map(z0,z0not); c15:and2g generic map (2 ns) port map(z0,z1,e); c16:and2g generic map (2 ns)

191 port map(z0,z1not,g); c17:and2g generic map (2 ns) port map(z1,z0not,l); c18:wireg port map(z0,ge); c19:wireg port map (z1,le); end two_level_or_and; entity not2 is generic(del:time) port(i:in std_logic;o:out std_logic); end not2; architecture behavior of not2 is o<=not i after del; end behavior; entity and2 is generic(del:time) port(i1,i2:in std_logic;o:out std_logic); end and2; architecture behavior of and2 is o<=i1 and i2 after del; end behavior; entity and3 is generic(del:time) port(i1,i2,i3:in std_logic;o:out std_logic); end and3; architecture behavior of and3 is o<=i1 and i2 and i3 after del; end behavior; entity or2 is 191

192 generic(del:time) port(i1,i2:in std_logic;o:out std_logic); end or2; architecture behavior of or2 is o<=i1 or i2 after del; end behavior; entity or3 is generic(del:time) port(i1,i2,i3:in std_logic;o:out std_logic); end or3; architecture behavior of or3 is o<=i1 or i2 or i3 after del; end behavior; entity wire is port(i:in std_logic;o:out std_logic); end wire; architecture behavior of wire is o<=i; end behavior; 组合逻辑电路设计方法小结 图 5-9 总结了组合逻辑电路设计的一般过程, 并表明了 VHDL 模型在组合逻辑电路设计中的用途 从电路性能指标的文字描述开始, 首先构造出电路输入输出关系的真值表, 这是电路行为的图形描述 利用行为域的算法模型, 可以对真值表描述的电路行为进行行为仿真 根据真值表可以建立行为域的两种算法模型 : 一种是数组式算法模型, 另一种是 case 条件语句式算法模型 数组模型可以直接转换为 ROM 式硬件实现, 而 case 语句则可以用多路选择器实现 对 case 语句式算法模型进行简化, 可以对多路选择器进行优化 利用卡诺图, 或采用如 quine-mcclusky 方式的系统化方法, 可以把行为域的算法模型转换为行为域的数据流模型 在对数据流模型进行下一步处理之前, 可以用仿真的方法对模型进行验证 行为域的数据流模型可以直接转换为逻辑门级的结构式模型 对于结构式模型同样可 192

193 以进行仿真, 以便在逻辑门级验证电路行为的正确性 对于逻辑门级仿真得到的时序信息, 可以反向标注到硬件的高层次模型, 如行为域的算法模型或数据流模型 这使得高层次仿真可以得到精确的时序信息 电路性能指标自然语言描述 真值表图形描述 卡诺图 综合 算法模型行为描述 CASE 语句 算法模型行为描述数组模型 卡诺图 数据流模型行为描述和项之积 综合 数据流描述行为描述只用或非门 优化 综合 综合 综合 结构模型多路选择器图形描述 改进算法模型行为描述 CASE 语句 ROM 图形描述 结构模型或 - 与图形描述 结构模型或非 - 或非图形描述 综合 综合 改进结构模型多路选择器图形描述 结构模型或 - 与 VHD 综合 结构模型或非 - 或非 VHD 图 5-9 组合逻辑电路设计过程小结 5.3 控制单元设计 前面已经介绍过, 在集成电路设计时, 通常可以将整个系统划分为两部分 : 一部分是数据单元 ; 另一部分是控制单元 图 5-10 示意给出了这种划分 其中, 数据单元中包含有保存运算数据和运算结果的数据寄存器, 也包括用来完成数据运算的组合逻辑电路单元 ; 控制单元用来产生控制信号序列, 以决定何时进行何种数据运算 控制单元要从数据单元得到条件信号, 以决定继续进行哪些数据运算 ; 数据单元要产生输出信号 数据运算状态等有用信息 通常控制单元可以有两种电路形式 : 一种是微程序 ( 微代码 ) 控制单元 ; 另一种是标准硬连线实现的控制单元, 即硬连线式控制单元 硬件直接实现的控制单元一般用有限状态机实现, 通常具有较高的运算速度 ; 但是通用性差, 对于每个电路, 都必须专门设计控制单元, 因此种类很多 图 5-11 是由 huffman 有限状态机实现的控制单元 其中, 状态寄存器模块 MEM 中包括了所有控制触发器 ; 模块 cl 中包含了控制单元中的所有组合逻辑电路 一般情况下, 控制信号的当前输出值与从数据单元中来的状态信息有关, 也与控制触发器的当前状态有关, 即状态机为 mealy 状态机 ; 有些情况下, 控制信号的当前输出值只与控制触发器的当前状态有关, 即状态机为 moore 状态机 控制触发器当前状态与其过去状态以及由数据单元来的状态信号有关 一般地讲, 硬件直接实现的控制单元比微代码控 193

194 制器工作速度快, 但电路复杂, 设计成本高, 且对不同应用必须专门设计控制器, 设计再用性差 输入信号 数据单元 输出信号 时钟信号 条件信号 控制信号 复位信号 控制单元 图 5-10 集成电路系统的划分 输入条件信号 组合逻辑电路 CL ( 计算下一状态和输出控制信号 ) 控制信号 当前状态 下一状态 状态寄存器 MEM 控制单元 图 5-11 硬件控制器的 huffman 模型微代码控制器如图 5-12 所示, 它在每个单元时间从 ROM 中读取该时段所有控制信号的值, 并将其寄存在指令寄存器 mir 中, 指令寄存器的输出端保持其数值直到下一单位时间 微代码控制器通过产生当前地址来依次得到各个时间单位的控制信号值, 即通过产生地址序列得以控制信号序列 如果地址生成电路相对简单, 这种微代码控制器应该是理想的电路设计方案 通常, 地址生成电路是个计数器, 正常情况下地址序列生成通过简单地加 1 得到下一地址, 如果需要在控制序列中跳转到某点, 则可以通过对寄存器加载一个特定值来实现 用这种方案产生地址序列, 电路相当简单 下一地址信息 地址生成器 地址寄存器 指令寄存器 指令寄存器 状态信号 AG 下一地址 MAR 当前地址 ROM MIR 状态信号 194

195 图 5-12 微代码控制器示意图微代码控制器的主要优点如下 : 对于同一类器件可以采用标准设计 同类器件中的不同控制器的不同之处只是存储在指令存储器 ROM 中的数据不同 因此, 只要改变 ROM 中的数据, 就可以得到不同的控制器 可以批量生产出电路的主体, 对于不同的应用, 可对 ROM 中写入不同的数据 容易改变电路设计方案 实际上, 只要改变 ROM 中的数据, 而不必改变控制器本身, 就可以改变控制器的功能 从这个意义上讲, 微代码控制器的改变要比硬连线控制器改变容易 一旦微代码控制器的硬件电路设计 调试结束, 对控制器的设计就简化为设计 ROM 中的数据 因此, 对于一个具体应用, 控制器设计就变得很简单 还可以利用成熟的软件工具, 例如编译器 仿真器 调试工具等 另外,ROM 数据出错的可能性很小, 即使出现错误, 也很容易发现, 且改变 ROM 中的数据也比改变软件本身容易 在电路的控制结构极其复杂的情况下, 硬连线控制电路的规模会变得无法忍受, 这时只能使用微代码控制器, 例如 Intel 的微处理器系列都采用了微码控制单元 与硬件直接实现的控制器相比, 微代码控制器的主要缺点如下 : 由于执行速度受 ROM 读取速度的影响, 所以速度相对较慢 由于微代码控制器至少包括一定规模的 ROM 和两组寄存器, 对于简单应用, 成本相对较高 但是对于复杂设计, 如果用硬连线实现控制器, 可能需要很多触发器, 这时应用微代码控制器 一旦选用了标准的微代码控制器, 就限制了电路设计只能使用控制器中预先实现的功能 因而对于微代码控制器本身的设计, 需要功能很完善, 才能满足不同的需要 但功能越强, 设计及制造成本就会越高 对于简单的应用, 即使不使用这些功能, 也要花费这些额外的代价 通常对控制器的限制包括有 : 可产生控制信号的个数, 可处理状态信号的个数, 地址产生电路的字长等 电路设计者需要综合考虑控制器的通用性 灵活性和设计成本 制造成本等因素 下面通过对各种控制器的设计举例来详细说明它们各自的设计方法 有限状态机控制器设计 与组合逻辑电路设计类似, 仍从电路的自然语言描述开始讨论 例如, 设计一个串并转换电路 图 5-13 是这个电路的方框图, 输入信号 clk 是控制系统工作的时钟 ; 输入信号 r 是系统复位信号, 如果 r='1', 则在 r 为 '1' 后的时钟结束时, 系统进入复位态 ; 输入信号 a 是同步信号 ; 输入 d 是串行输入信号 同步信号 a 在数据输入端 d 出现数据前一个周期出现, 即在 a 置位为 '1' 后的下一个周期,d 上连续出现串行数据 该串并转换电路把相继 4 位串行输入数据转换为 4 位并行数据送到输出端口 z, 且当并行数据输出端口 z 上的数据输出时, 信号 done 保持为 '1' 器件输出数据的时间要持续两个完整的时钟周期, 即信号 done 也要保持一个完整时钟周期 在并行数据输出端 z 输出数据的时钟周期内, 器件的同 195

196 步输入端 a 可以接收下一个同步脉冲, 即器件输出并行数据的下一个时钟周期就可以继续接收新的串行输入数据 如果有数据输入, 则器件继续接收数据 ; 如果没有数据输入, 器件进入复位态等待新数据出现 z 4 r a d clk 并串转换 stop done 图 5-13 串并转换电路的方框图 选择 moore 状态机或 mealy 状态机时序电路的通用模型是有限状态机, 有限状态机设计的第 1 步是决定采用 moore 状态机还是采用 mealy 状态机 读者可能对 moore 状态机和 mealy 状态机的特性已经很熟悉, 这里只简单列出构造状态表时要用的内容 根据定义,moore 状态机的输出只与状态机的状态有关, 与输入信号的当前值无关 ;mealy 状态机的输出不单与状态有关, 而且与输入信号的当前值有关 从实现电路功能的角度来讲, 这两种状态机都可以实现同样的功能 但它们的输出时序不同, 在选择使用哪种状态机时要根据实际情况进行具体分析 moore 状态机和 mealy 状态机之间的主要区别如下 : moore 状态机在时钟脉冲的有效边沿后的有限个门延时之后, 输出达到其稳定值 输出会在一个完整的时钟周期内保持其稳定值, 即使在该时钟周期内输入信号有变化, 输出也不会发生变化 输入对输出的影响要到下一个时钟周期才能反映出来 把输入与输出隔离开来, 是 moore 状态机的一个重要特点 mealy 状态机的输出直接受输入影响, 由于输入变化可能出现在时钟周期内的任何时刻, 这就使得 mealy 状态机对输入的响应可比 moore 状态机对输入的响应早一个时钟周期, 且输入信号中的噪声可能出现在输出端 实现同样的功能,moore 状态机所需的状态个数可能比 mealy 状态机多 通常, 对于具体电路的一个指标规范, 可能适合于用 moore 状态机实现, 或者适合于用 mealy 状态机实现, 也有可能用两种状态机实现都很合适, 硬件设计者需要自行决定来采用哪种状态机 对于这里讨论的串并转换电路, 在串行信号的最后一位输入后就可以产生输出, 而当输出并行数值时输入信号不再存在, 所以输出不能直接与输入关联 同时, 由于输出要保持一个完整的时钟周期, 在此周期内输出不能受输入信号的影响, 所以不适合采用 mealy 状态机, 最好采用 moore 状态机 构造状态表确定了采用 moore 状态机实现硬件, 接下来的问题就是构造状态表 构造状态表的工作需要充分利用硬件设计者的设计经验 对于同一个设计问题, 不同的设计者可能构造出不同的状态表, 这些状态表都可以很好地完成硬件要实现的功能 虽然不可能找到一个系 196

197 统化构造状态表的成熟算法, 但如果设计者遵循结构化设计的思想, 则很容易构造出效果好的状态表 为了清楚地描述状态转换关系, 设计者可采用状态转换图, 也可以采用状态转换表 状态转换图直观地给出了状态之间的转换关系以及状态转换条件, 因而容易理解状态机的工作机理, 适合于状态个数不太多的情况 状态转换表则采用表格的方式列出了状态及转换条件, 适合于状态个数较多的情况 建立状态转换图对于这里要讨论的串并转换电路, 由于电路相对简单, 状态的个数不会太多, 因而选择采用状态转换图的方式列出状态及其转换条件 构造状态转换图时, 通常从一个比较容易描述的状态开始 如果硬件的指标描述中规定了复位状态, 这通常是构造状态图的很好的起始态 在建立每个状态时, 最好都清楚地写出关于这个状态的文字描述, 为硬件设计过程提供清晰的参考材料, 也为最后完成的设计提供完整的设计文档 通常, 设计过程中可能会有些反复, 在开始设计时写出的状态功能描述可能会随设计过程的深入做必要的修正, 某些状态的最后描述可能会与开始设计时写出的描述差别很大, 完整的文字描述有利于设计过程的进行 对于串并转换电路, 我们从复位态开始设计, 令复位态为 s0 该状态的文字描述如下: 状态 s0: 复位状态 如果输入 r='1', 不论输入数据为何数值, 在相应的时钟周期的下降沿, 电路都会进入 s0 态 在状态 s0 中, 输出信号 done='0', 这意味着并行输出信号 z 上的值是无效信号, 这时无论 z 为何值, 都应该忽略它 如果当前态是 s0, 且输入 r='1', 不论其他输入为何数值, 它会保持在 s0; 如果 r='0', 且 a='0', 它也会保持在 s0 信号保持在 s0 的条件如下 : r or ((not a) and (not r))=r or (not a); ra 状态 s1 0 输出 :done, z r 状态 s0 0 r + a 图 5-14 构造串并转换电路的状态转换图的第一步如果在某个时钟周期有 r='0' 且 a='1', 则器件在后面的 4 个时钟周期中接收外部串行输入数据, 这意味着硬件会进入一个新状态, 将这一状态记作 s1 后面我们会给出状态 s1 的描述, 并依次构造该硬件的其他状态 状态转换图是一种状态及其转换关系的直观表示方法 状态转换图是一个有向图, 其节点表示器件的一个状态, 节点符号的内部写出了该状态的名称 对于 moore 状态机, 节点符号内部还标出了器件的输出 有向图中的支路表示了状态之间的转换关系, 图中支路上标出了状态转换的条件 图 5-14 是根据上述描述得出的状态转换图, 图中状态符号 s0 197

198 中标出的输出值 0- 表示 done 为 '0',z 的值无意义 从状态 s0 指向 s1 的弧线上指明了从状态 s0 转换为 s1 的条件为 (a and not r), 其意义是在 r='0' 且 a='1' 的时钟周期, 硬件进入状态 s1 且在状态 s1 的输出值仍为 0-, 即 done='0',z 无意义 从状态 s1 指向 s0 的弧线上标明了条件, 其意义是在输入 r='1' 的条件下返回状态 s0 状态 s1 的文字描述如下 : 状态 s1: 接收第 1 位串行输入数据 如果当前状态为 s0, 则当 r='0' 且 a='1' 时, 器件进入状态 s1 在状态 s1 中, 器件可以逐个接收外部串行数据, 并在数据出现的时钟周期的下降沿把它存储起来, 留作以后输出 状态 s1 中输出 done='0', 输出 z 无意义 如果在状态 s1 中, 出现 r='1' 的情况, 则器件被复位至状态 s0; 否则, 器件会依次进入接收第 2 位串行输入数据的状态 s2 图 5-15 构造串并转换电路的状态转换图的第二步对于构造出的每一个新状态, 必须对所有可能的输入条件进行分析, 以确定在哪些条件下从哪些状态可以转换到新状态, 确定在哪些条件下从新状态可以转换到哪些状态 在状态图中增加相应的节点和支路, 在每个节点上标出状态名和输出值, 在每个支路上标出转换条件, 就可以得出新的状态转换图 图 5-14 是对状态 s0 进行分析后构造出的状态转换图 ; 图 5-15 是对状态 s1 进行分析后构造出的状态转换图 通常对于给定的电路指标要求, 可以在有限的状态内完成所有的操作, 因此, 把这样的硬件称做有限状态机 事实上, 有可能某些电路的数据操作无法在有限的状态内完成, 确定能否在有限个状态内完成规定的数据操作是一个非常困难的问题, 本书不讨论这个问题 对状态 s2 的情况进行分析, 并依次对产生的新状态进行分析, 可以得出对各个状态的文字描述如下 : 状态 s2: 接收第 2 位串位输入数据 在状态 s1, 如果输入 r='0', 则在下一个时钟周期时, 第 2 位串行数据应该出现在数据输入端, 器件进入状态 s2 在状态 s2, 输出信号 done='0', 数据输出 z 无意义 如果在状态 s2 中, 出现 r='1' 的情况, 则器件被复位至状态 s0; 否则, 器件会依次进入接收第 3 位串行输入数据的状态 s3 状态 s3: 接收第 3 位串行输入数据 198

199 在状态 s2, 如果输入 r='0', 则在下一个时钟周期, 第 3 位串行数据应该出现在数据输入端, 器件进入状态 s3 在状态 s3, 输出信号 done='0', 数据输出 z 无意义 如果在状态 s3 中, 出现 r='1' 的情况, 则器件被复位至状态 s0; 否则, 器件会依次进入接收第 4 位串行输入数据的状态 s4 状态 s4: 接收第 4 位串行输入数据 在状态 s3, 如果输入 r='0', 则在下一个时钟周期时, 第 4 位串行数据输入, 且进入状态 s4 状态 s5: 把 4 位串行输入数据并行输出 图 5-16 串并转换电路的状态转换图在状态 s4, 如果输入 r='0', 则在下一个时钟周期时, 器件进入状态 s5 在状态 s5, 输出信号 done='1', 数据输出 z 为 4 位串行输入数据 如果在状态 s5 中, 出现 r='1' 的情况, 则器件被复位至状态 s0; 若不出现信号 a, 即 a='0', 器件也进入状态 s0 在状态 s5, 如果复位信号 r='0' 且 a='1', 则器件会进入状态 s1, 即准备接收下一组数据 这时将重复使用状态 s1, 而不是产生一个新状态, 这样就完成了整个状态转换图, 如图 5-16 所示 由于重复使用了状态 s1, 所以应该修改状态 s1 的文字描述, 以反映从状态 s5 转换到 s1 的情况 修改后的状态 s1 的文字描述如下 : 状态 s1( 修改后 ): 接收第 1 位串行输入数据 如果输入条件为 r='0' 且 a='1', 器件可以从状态 s0 或 s5 进入状态 s1 在这个状态中, 器件可以接收外部第 1 位串行数据, 并在数据出现的时钟周期的下降沿把它存储起来, 留作以后输出 这个状态中 done='0', 输出 z 无意义 如果在状态 s1 中, 出现 r='1' 的情况, 则器件被复位至状态 s0; 否则, 器件会依次进入接收第 2 位串行输入数据的状态 s 状态转换表上面讨论的状态转换图清楚地表明了状态间的转换关系及转换条件, 给定了有关的输 199

200 入序列, 很容易根据状态转换图跟踪状态之间的转换关系 但是, 对于复杂电路, 状态的个数可能很多, 状态图变得很复杂, 不易画出, 也不易读懂 在这种情况下, 有效的状态描述方法是状态转换表 状态转换表的建立过程与状态转换图类似, 只是用表格的方式描述了状态间的转换关系及转换条件 表 5-2 是串并转换电路的状态转换表 表 5-2 串并转换电路的状态转换表 当前状态转换条件下一状态数据转换输出值 s0 s1 s2 s3 s4 s5 r or (not a) s0 无 (not r) and a s1 无 (not r) s2 数据移位, 存储数据 r s0 无 (not r) s3 数据移位, 存储数据 r s0 无 (not r) s4 数据移位, 存储数据 r s0 无 (not r) s5 数据移位, 存储数据 r s0 无 (not r) and a s1 无 r s0 无 done ='0', z 无意义 done ='0', z 无意义 done ='0', z 无意义 done ='0', z 无意义 done ='0', z 无意义 done ='1', z 为并行输出数据 在构造硬件的状态转换表或转换图时, 互补原则可以帮助设计者检查设计过程中是否出现了错误 所谓互补原则, 指的是状态转换图中离开某个节点的所有支路上所标出的条件必须互补, 即从一个给定状态转换到其他状态的所有输入条件必须互补 如果从一个状态转换到两个不同状态的条件相与为逻辑 1, 则意味着硬件可能同时进入两个不同状态, 显然这种情况不允许出现在硬件中 例如, 对于图 5-16 中的状态 s0, 状态转换图中离开节点 s0 的两个条件分别为 r or (not a) 和 (not r)and a, 这两个条件的与 或结果为 : (r or (not a)) and ((not r) and a)=0, (r or (not a)) or ((not r) and a)=1 这就验证了离开节点 s0 的两个条件的互补性 在构造状态转换图时, 可以应用这个互补原则检查状态图中是否存在错误 显然, 状态转换表也满足互补原则, 也可以用这一互补原则检查状态转换表中可能出现的错误 建立状态机的 VHDL 模型 状态转换图和状态转换表都可以用来建立 VHDL 模型,VHDL 模型可以用来验证硬件的功能, 以便于在进一步进行较低层次的设计之前检查可能出现的错误 建立 VHDL 模型时, 通常把硬件划分为如图 5-17 所示的结构, 即将硬件划分为数据单元 控制单元和一个输出单元 在 moore 状态机中, 输出值与控制单元的状态有关, 也与存储在数据单元中的数据值有关 在 mealy 状态机中, 输出还可能与输入信号有关 根据串并转换电路的状态转换表或状态转换图, 容易得出如例 5-12 所示的模型 实体 stop 定义了硬件接口,std_logic 型输入信号为复位信号 r 同步信号 a 数据输入 d 和时钟信号 clk, 输出信号为 done 和 z, 其中 z 是数据宽度为 4 的矢量

201 moore 状态机无此信号 输入 I 数据单元 输出单元 输出 z 控制单元 时钟 clk 图 5-17 状态机模型的方框图 例 5-12 entity stop is port(r,a,d,clk:in std_logic;done:out std_logic; z:out std_logic_vector(3 down to 0)); end stop; architecture fsm_rtl of stop is type state_type is (s0,s1,s2,s3,s4,s5); signal state:state_type; signal shift_reg:std_logic_vector(3 downto 0); state:process(clk) if clk ='1'then case state is when s0=> -- 数据单元 -- 控制单元 if r='1'or a ='0'then state<=s0; elsif r='0' and a='1'then state<=s1; end if; when s1=> -- 数据单元, 第 1 位数据输入移入 shift_reg<=d& shift_reg(3 downto 1); -- 控制单元 if r='1'then 201

202 202 state<=s0; elsif r='0'then state<=s2; end if; when s2=> -- 数据单元, 第 2 位数据输入移入 shift_reg<=d& shift_reg(3 downto 1); -- 控制单元 if r='1'then state<=s0; elsif r='0'then state<=s3; end if; when s3=> -- 数据单元, 第 3 位数据输入移入 shift_reg<=d&shift_reg(3 downto 1); -- 控制单元 if r='1'then state<=s0; elsif r='0'then state<=s4; end if; when s4=> -- 数据单元, 第 4 位数据输入移入 shift_reg<=d& shift_reg(3 downto 1); -- 控制单元 if r='1'then state<=s0; elsif r='0'then state<=s5; end if; when s5=> --- 数据单元 --- 控制单元 if r='1' or a ='0'then state<=s0; elsif r='0'and a='1'then state<=s1; end if; end case; end if;

203 end process state; output:process(state) case state is when s0 to s4=> done<='0'; when s5=> done<='1'; z<=shift_reg; end case; end process output; end fsm_rtl; 在构造体 fsm_rtl 中, 首先定义了一个枚举型数据类型 state_type, 其中的枚举元素名直接来自状态转换图或状态转换表 信号 state 保存了状态机的当前状态 写出硬件的模型前, 必须决定如何存储每一位串行输入数据 实现数据存储可以采用不同的方法, 这将导致不同的硬件结构, 从而影响硬件成本 在构造体 fsm_rtl 中, 采用移位寄存器实现数据的存储, 移位寄存器由一个 std_logic 矢量表示, 矢量的排序与输出信号 z 的排序相同 构造体中有两个进程 state 和 output 其中,state 在每一个时钟周期的末尾更新状态机的状态, 从而它应该对时钟信号 clk 敏感 输入信号 clk 出现在进程 state 的敏感信号表中, 用 if then 语句检查时钟 clk 的上升沿, 如果检测到时钟输入的上升沿, 则更新状态机的状态 根据状态机的当前状态, 用 case 语句决定如何得到新状态,case 语句中的每个选择条件相应于状态的一个当前值 例如,case 语句中的选择条件 s0 相应于状态图中的状态 s0 每个状态中都可能有两种操作 : 数据处理和控制处理 对于状态 s0, 只有控制操作, 没有数据操作 根据状态转换图, 对于状态 s0, 如果输入条件为 r='1' 或 a='0', 下一状态还应该是 s0,vhdl 模型中通过语句 if r='1'or a='0' then state<=s0 实现了这种控制操作 状态转换图中还规定 : 如果输入条件为 r='0' 且 a='1', 则下一状态为 s1,vhdl 模型中通过语句 else if r='0' and a='1'then state<=s1 实现了这一功能 case 语句中的其他选择条件定义了基本状态转换的情况 与状态 s0 惟一不同之处在是在某些选择条件下加了数据操作问题 例如对于状态 s1, 在状态操作的同时, 还有数据操作,VHDL 模型中通过语句 shift_reg<=d&shift_reg(3 downto 0) 实现了寄存器 shift_reg 内数据的移位和新数据的存储 显然, 状态转换表中不但提供了状态转换信息及转换条件, 而且提供了在每种状态下如何进行数据操作 读者可以对状态转换表和 VHDL 模型进行对比分析, 检查 VHDL 模 203

204 型是否实现了状态转换表规定的数据操作 检查 output 是否定义了器件的输出信号上的逻辑值 对于 moore 状态机, 输出信号只与状态机的状态有关, 与当前输入的输入值无关,VHDL 模型中的输出进程只对当前状态的变化敏感 对于 mealy 状态机, 输出信号可能与输入信号有关, 输出进程的敏感信号可能包括输入信号 由于所设计的器件是一个 moore 状态机, 输出值只与状态机的状态有关, 所以进程 output 的敏感信号只是 state VHDL 模型中使用 case state 语句实现了对输出信号的赋值 根据状态转换图, 在状态 s0 s4, 输出信号 done 的值是 '0', 只有在状态 s5, 输出 done 的值为 '1' 如果 state 为 s0 s4,vhdl 模型中通过信号赋值语句 done <='0' 使输出信号 done 变为 '0' 根据所设计的硬件的指标规范, 在状态 s0 s4, 对输出 z 取什么样的值并没有规定, 只有在状态 s5, 才规定输出 z 的值为串行输入的 4 位数据 在 VHDL 模型中, z 只在状态 s5 改变数值, 而在其他状态输出 z 的值保持不变, 这满足了硬件的指标规范 这里要指出一个问题, 在使用自动综合工具的前提下, 通常要求对硬件高层次模型的仿真结果与对较低层次模型的仿真结果一致 如要高层次模型中在状态 s0 s4 时输出值保持不变, 在硬件的较低层次则需要采用特殊措施保持输出值不变, 这可能增加较低层次的硬件设计复杂性 也就是说, 在硬件造型的较高层次, 对某些信号的值不做规定并不是一种最好的方法 采用多值逻辑方法, 可以帮助解决这种问题 读者可以在本章的例子对不指定数值的信号赋值语句进行改进 VHDL 状态机模型的综合根据有限状态机的 VHDL 模型, 可以直接得到硬件电路 在此假定状态机的基本结构如图 5-17 所示, 首先讨论控制部分的综合 对于控制部分, 把每个控制状态用一个触发器实现, 并搜索例 5-12 的 VHDL 源代码中的 if then 语句, 可以得到每个状态对应的触发器, 从而设计出整个控制单元 图 5-18 是由此得出的控制单元示意图 204

205 图 5-18 根据串并转换电路的 VHDL 模型综合出的控制单元对于硬件模型中的每个触发器的数据输入端, 可以通过检查 VHDL 模型中的 case 语句来完成 对于每一个状态, 首先建立一个表格, 列出从所有状态转换到这个状态的条件 ; 然后, 根据表中列出的条件, 构造对应该状态的触发器的数据输入端的电路 表 5-3 是对串并转换电路建立的这样的表格 图 5-18 是根据该表格构造出的电路 例如, 对于状态 s1 的触发器的数据输入端 D,3 输入与门 g01 在输入条件为 (not r)and a 时控制从状态 s0 转换到状态 s1, 这对应于表 5-3 中终止态为 s1 的第 1 项内容 与此类似,3 输入与门 g51 在输入条件为 (not r)and a 时控制从状态 s5 转换到 s1, 这相应于终止态为 s1 的第 2 项内容 用一个或门把 g01 和 g51 的输出送到状态 s1 对应的 D 触发器的数据输入端 D, 就完成了状态 s1 的设计 相应于状态 s1 的 D 触发器的输出应该被送到数据单元 输出单元, 还应该送到控制单元中需要使用状态 s1 的地方 表 5-3 串并转换电路的控制单元综合终止状态起始状态转换条件终止状态起始状态转换条件 s0 s0 r or (not a) s0 (not r) and a s1 s1 r s5 (not r) and a s2 r s2 s1 not r s3 r s3 s2 not r s4 r s4 s3 not r s5 r or (not a) s5 s4 not r 根据 VHDL 模型中的 case 语句, 可以综合出该串并转换电路的数据单元 首先, 对 VHDL 模型中的 case 语句进行检查, 列出每个状态下所要进行的数据操作以及数据操作条件的表格 ; 然后, 根据表格中列出的条件生成数据操作电路 对于串并转换电路, 可以列 205

206 出如表 5-4 所示的表格 图 5-19 是根据该表构造出的电路 从表 5-4 可以看出, 每个状态下数据操作的控制信号为状态信号与表格中相应状态下的数据操作条件的逻辑与 由于 4 个状态中都需要对数据进行移位操作, 这种条件相应于将 4 个状态下的数据操作控制代码进行或运算, 因而移位操作的控制信号的逻辑表达式为 : shift=(s1)(1)+(s2)(1)+(s3)(1)+(s4)(1)=s1+s2+s3+s4 shift_reg(3) shift_reg (2) shift_reg (1) d shift_reg (0) si d3 d2 d1 d0 s1 s2 1 sh 移位寄存器 s3 s4 图 5-19 串并转换电路的数据单元 表 5-4 串并转换电路的数据单元的综合 数据操作 状 态 条 件 shift_reg<=d&shift_reg(3 downto 1) s1 1 shift_reg<=d&shift_reg(3 downto 1) s2 1 shift_reg<=d&shift_reg(3 downto 1) s3 1 shift_reg<=d&shift_reg(3 downto 1) s4 1 例 5-12 给出的串并转换电路的 VHDL 模型中, 并没有告诉设计者任何关于如何设计移位寄存器的信息 设计移位寄存器是另外任务, 在这里设计者假定移位寄存器是可以从设计库中得到的设计单元 对例 5-12 中的进程 output 中 case 语句的每种条件进行检查, 也可以得出该串并转换电路的输出单元 由于只有在状态 s5 对输出信号 done 赋值, 所以信号 done 的逻辑表达式为 : done <=s5 由于串并转换电路的原始指标中对状态 s0 s4 中的输出 z 没有作出规定, 输出信号 z 的设计有些复杂 VHDL 模型中的输出进程描述了一个组合逻辑电路 如果利用没有规定状态 s0 s4 中输出 z 的数值这一条件, 可以令输出 z 永远与移位寄存器中的数值相同 这样, 得到的输出 z 的逻辑表达式为 : z<=shift_reg VHDL 语言规定 : 如果没有对信号 z 进行过操作, 则总保持信号线 z 上原来的数值 但是, 上述逻辑表达式得出的输出电路是一个组合逻辑电路,z 的值总与移位寄存器中保持的数据相同 除了在状态 s5 中之外, 对例 5-12 中的 VHDL 算法模型进行仿真得到的结 206

207 果可能与对逻辑门级模型进行仿真得到的结果不同 但这种电路确定满足了电路的原始指标 微代码控制器设计 本节讨论微代码控制单元的设计问题 在给出了一个通用的基本微代码控制单元的基础上, 把硬件描述语言和其他方法结合, 讨论如何利用该基本微代码控制单元设计一个具体的微代码控制器 基本微代码控制单元 bmcu 微代码控制器如图 5-20 所示 在微代码的控制器中, 控制信号的值预先存储在只读存储器 ROM 中, 选择适当的 ROM 地址, 可以得到需要的控制信号 ROM 中存储的内容称为控制字 在每个时钟周期, 微代码控制器从 ROM 的给定地址读出所需的控制信号 在图 5-20 的控制器中, 包括有 a 位地址寄存器 (MAR) w 位指令寄存器 (MIR) 微代码存储器 ROM 和地址生成逻辑电路 (AGL), 微代码存储器 ROM 的字长为 w,rom 深度为 2 a 在给定时刻, 地址生成电路计算出 ROM 地址, 在这个地址中读出下一个时刻的信号以及下一次计算 ROM 地址所需的信息 地址生成逻辑确定下一 ROM 地址时, 要用到数据单元产生的条件信号 地址生成电路可能很复杂, 不同的设计可能采用不同的地址生成电路, 通常地址生成电路是限制微代码控制器应用的主要因素 条件信号 ROM 规模 :2 a w a 地址生成逻辑 AGL A w-s 地址寄存器 MAR a 微代码存储器 ROM w 指令寄存器 MIR s 控制信号 图 5-20 微代码控制器本小节讨论一种最简单的微代码控制单元, 称为 bmcu 这个微代码控制单元中的地址生成逻辑为分支逻辑, 由图 5-21 所示的矢量多路选择器构成 地址生成逻辑中的地址选择信号 as 为 '0' 时, 下一指令地址为当前指令地址加 1; 当地址选择信号 as 为 '1' 时, 下一指令地址为 nad 中指定的地址 as 的取值由指令存储器中的控制字和来自数据单元的条件信号共同确定 指令存储器中每个控制字有 64 位, 其组织方式如图 5-22 所示 对应于图 5-20 的一般形式的方框图,w=64,s =32 在 64 位控制字中, 最低 32 位是供数据单元使用的控制信号 lcs; 最高 24 位为条件选择信号 cs, 供 地址生成逻辑 生成下一 ROM 地址 ; 中间 8 位数 207

208 据是分支地址 nad, 也用于生成下一地址 as mar nad 8 加 1 8 inc 8 2 选 1 电路 8 下一地址 图 5-21 基本微代码控制电路的地址生成部分 条件选择 cs 下一地址 nad 控制信号 lcs cs23 cs22 cs0 lcs31 lcs30 lcs0 图 5-22 基本微代码控制单元 bmcu 中控制字的构成控制字的条件选择信号 cs 部分 ( 第 位 ) 包含的信息用来把从数据单元来的条件信号进行分类选择, 确定哪些条件应该被用来产生下一条指令的地址 在 bmcu 中, 条件选择域 cs 中同时只可能有 1 位为 '1', 其他位都为 '0' 如果 cs 的某位 csi 为 '1', 则数据单元的条件信号 ci 被选择用来产生下一指令地址 在 bmcu 的设计中, 条件信号的最大个数为 24 如果条件信号 ci ='1', 同时该信号被选择 ( 即 csi ='1'), 则下一条指令的地址是控制字中分支地址 nad 中指明的地址 ; 如果条件信号 ci 被选择 ( 即 csi ='1'), 但条件信号 ci ='0', 则下一条指令的地址是当前地址 (mar 中的内容 ) 加 1 控制字中的控制信号 lcs 部分 ( 第 31 0 位 ) 包含了数据单元中使用的所有控制信号 在设计中,bmcu 的控制信号的最大个数是 32 由于 ROM 地址必须能够通过控制字中的 nad 部分直接指定, 而 nad 部分只有 8 位, 所以这个 bmcu 的最大 ROM 规模为 256 字节 (2 8 =256) 在图 5-21 所示的地址生成逻辑中, 控制信号 as 由下式计算 : as=(cs23)(c23)+(cs22)(c22)+ +(cs1)(c1)+(cs0)(c0) 即如果某个条件信号 ci 被选择 (csi ='1') 且该条件信号 ci ='1', 则地址选择信号 as ='1' 基本微代码控制单元 bmcu 的算法模型为了采用自动综合程序综合出基本微代码控制单元 (bmcu), 应该写出 bmcu 的 VHDL 算法模型 根据前面的描述, 很容易得出图 5-23 所示的进程模型图, 这里增加了信号 reset 对 bmcu 复位 cs c 地址生成逻辑 指令译码 lcs 208 AGL nad decodep nmar clk mir clk 地址寄存器 指令寄存器

209 图 5-23 基本微代码控制单元的进程模型图 在进程模型图 5-23 中, 进程 marp 表示地址寄存器 MAR 当出现复位信号 reset 时, 地址寄存器 MAR 被复位为 在系统时钟的下降沿, 新地址 nmar 被加载到地址寄存器 MAR 内, 根据 nmar 的值, 可以把新的控制字 cword 从 ROM 中读出 在系统时钟的上升沿,cword 被传递到指令寄存器 MIR, 进程模型图中指令寄存器 MIR 由进程 mirp 表示 在出现系统复位信号时, 指令寄存器被复位为零矢量 进程 decodep 的功能是把控制字 cword 分解为 cs lcs 和 nad 三部分 进程 agl 根据前面描述的规则, 计算下一个控制字的地址 如果地址选择条件 as 为 true, 则下一指令地址为 nad; 如果地址选择信号 as 为 false, 则下一地址为 mar+1 信号 nmar 用来表示进程 agl 计算得到的新地址 在系统时钟的下降沿, 这一新地址被加载到地址寄存器 MAR 这样就重新启动了一个指令周期 根据进程模型图 5-23 和第 4 章中的知识, 可以写出描述该 bmcu 功能的 VHDL 源代码 在下面的 VHDL 算法模型中, 每个器件中都引入了类属参数, 用以在高层次描述延时信息 例 5-13 entity bmcu is generic(alg_delay,mar_delay,rom_delay,mir_delay:time); port (c:in std_logic_vector(23 down to 0) := b"0000_0000_0000_0000_0000_0000"; clk,reset:in std_logic:='0'; lcs:out std_logic_vector(31 downto 0)); end bmcu; use work.bmcu_function.all; architecture algorithmic of bmcu is signal mar,nmar:std_logic_vector(7 downto 0); --MAR 是 ROM 的地址寄存器 --nmar 是 mar 的下一数值 signal cword:std_logic_vector (63 downto 0); 209

210 -- 从 ROM 来的控制信号 signal mir:std_logic_vector(63 downto 0); -- 保存从 ROM 来的控制信号 signal nad:std_logic_vector(7 downto 0); -- 分支地址, 是 cword 的一部分 signal cs:std_logic_vector(23 downto 0); -- 保存从 ROM 来的控制信号 marp:process(clk,reset) if reset='1'then mar<=b " "after mar_delay; elsif clk'event and clk='1'then mar<=nmar after mar_delay; end if; end process marp; mirp:process(clk,reset) if reset='1'then mir<=x"00_00_00_00_00_00_00_00"; elsif clk'event and clk='1'then mir<=cword after mir_delay; end if; end process mirp; ROMP:process(mar) type mem_type is array(0 to 255)of std_logic_vector(63 downto 0); constant mem:mem_type:= -- 下面省略了 ROM 中的数据 -- --cs-- --nad-- --lcs-- ; cword<=mem(std_logic_vector_to_int(mar)) after ROM_delay; end process ROMP; decodep:process(mir) nad<=mir(39 downto 32); lcs<=mir(31 downto 0) cs<=mir(63 downto 40); end process decodep; agl:process(mar,nad,c,cs) variable as:std_logic; 210

211 as:=(cs(23)and c(23))or (cs(22)and c(22))or(cs(21)and c(21)) or (cs(20)and c(20))or(cs(19)and c(19)) or (cs(18)and c(20))or(cs(17)and c(17)) or (cs(16)and c(16) )or(cs(15)and c(15)) or (cs(14)and c(14) )or(cs(13)and c(13)) or (cs(12)and c(12))or(cs(11)and c(11)) or (cs(10)and c(10) )or(cs(9)and c(9)) or (cs(8)and c(8))or(cs(7)and c(7))or (cs(6)and c(6)) or (cs(5)and c(5))or(cs(4)and c(4))or(cs(3)and c(3)) or (cs(2)and c(2))or(cs(1)and c(1))or (cs(0)and c(0)); case as is when'0'=>nmar<=inc(mar)after agl_delay; when'1'=>nmar<=nad after agl_delay; end case; end process agl; end algorithmic; 微代码控制单元 bmcu 的算法模型要用到程序包 bmcu_function, 其源代码如例 5-14 所示 该程序包中包含了函数 std_logic_vector_to_int 和 inc, 分别用来把位矢量转换为整数和计算位矢量加 1 这两个函数全是通用函数, 其输入矢量的长度可以为任意值 函数中对信号属性 vec'right 和 vec'left 进行比较, 以确定矢量为升序排列还是降序排列, 并对两种情况分别处理 例 5-14 package bmcu_function is function std_logic_vector_to_int(vec:std_logic_vector) return integer; function inc(a:std_logic_vector) return std_logic_vector; end bmcu_function; package body bmcu_function is function std_logic_vector_to_int(vec:std_logic_vector)return integer is variable sum,wt:integer:=0; if vec'right<=vec'left then for n in vec'right to vec'left loop if vec(n)= '1'then sum:=sum+(2 **wt); end if; wt:=wt+1; 211

212 end loop; else for n in vec'right downto vec'left loop if vec(n)= '1' then sum:=sum+(2**wt); end if; wt:=wt+1; end loop ; end if; return sum; end std_logic_vector_to_int; 212 function inc(a:std_logic_vector)return std_logic_vector is variable carry:std_logic := '1' variable result:std_logic_vector(a'range); if a'right<=a'left then for n in a'right to a'left loop if carry='1'then result(n):=not a(n); else result(n):=a(n); end if; carry :=carry and a(n); end loop; else for n in a'right downto a'left loop if carry='1'then result(n):=not a(n); else result(n):=a(n); end if; carry :=carry and a(n); end loop; end if; return result; end inc; end bmcu_function; 把位矢量转换为整数的函数很简单 把二进制位矢量的每一位乘以它们的加权值, 并把相乘后的数值相加, 就可以得到二进制位矢量对应的整数值 在硬件实现中,ROM 地

213 址选择信号是二进制矢量输入, 所以, 并不需要实现这个转换函数 在例 5-14 的 VHDL 模型中, 由于 ROM 由数组表示, 而数组的下标是整数值, 因此使用了这个转换函数 图 5-24 给出了二进制数加 1 运算的两个例子 当对一个二进制矢量进行加 1 运算时, 位矢量的最低位总是变为原来的反码, 即从 1 变为 0 或从 0 变为 1 除此之外, 如果二进制位矢量的低位是一串 1, 则所有这些 1 都应该变成 0 如果对位矢量从右(lsb) 向左 (msb) 逐位搜索, 第 1 次遇到的 0 应该变为 1, 而该位左边的各位保持不变 在例 5-14 的函数 inc 中, 变量 carry 用来确定何时停止逐位求反 carry 被初始化为 1, 用来强制对二进制位矢量的最低位求反, 随着对二进制位矢量从右向左搜索, 只要 carry 和 a(n) 的与运算得到 true, 说明搜索过程还是只发现 1, 应该对这一位求反 图 5-24 就是根据上述算法进行加 1 运算的实例 0101_ _ _ _1011 图 5-24 二进制数加 1 的例子 基本微代码控制单元的综合一般情况下, 根据微代码控制器的算法模型, 可以利用自动综合工具综合微代码控制器的硬件本身 下面介绍一种系统化设计微代码控制器的方法 以本节讨论的串并转换电路为例, 具体说明如何利用基本微代码控制器 bmcu 的基本结构, 设计串并转换电路所需的 ROM 中的内容 系统化设计微代码控制器的算法由如下几步组成 1) 根据 VHDL 算法模型写出硬件的所有状态, 并定义状态之间的转换条件 2) 列出第 1) 步中出现的所有条件 后面对每个状态指定 ROM 地址之后, 这些条件还可能要改变 3) 确定复位状态 通常硬件指标中会给出复位态, 如果指标中没有指定复位态, 则任意选定一个状态作为复位态 4) 对每个状态指定 ROM 地址 ROM 地址的确定会直接影响控制器的成本以及性能, 不存在有效算法对指定 ROM 地址过程进行优化 ROM 地址的最优指定要通过寻找状态图中的最长路径来实现, 这在图论中是一个复杂的理论问题 虽然按最优的要求指定 ROM 地址是一个很复杂的问题, 但下面的简单算法可以用来对每个状态指定 ROM 地址 这个算法类似于汇编语言编译器中的第一遍扫描, 即类似于汇编语言编译器的地址分配过程 事实上, 对微代码控制器的 ROM 编程通常使用一个类似于汇编语言编译器的工具完成 该算法如图 5-25 所示 图中有两点说明如下 : 符号 I 表示地址顺序增加 ; 符号 B 表示地址跳转 可以采用最优化算法选择状态 Q; 实现状态 P, 需要 NSP 个 ROM 地址 5) 确定必须从数据单元传递到控制单元的条件信号 把第 2) 步建立的条件中的冗余项消除, 把每个非冗余项对应于一个条件信号 这一步不影响控制器的成本及性能 显然, 如果在控制单元中多处使用了同一条件信息, 也只需从数据单元中产生一次这样的信号 213

214 6) 列出每个状态下的数据操作 输出内容以及进行数据操作和产生输出的条件 7) 根据算法第 6) 步列出的表格, 确定控制数据单元进行数据操作和产生输出的信号, 这些信号在控制单元中产生, 从控制单元传递到数据单元 一般地讲, 对应每对数据操作 / 条件或每对输出 / 条件都需要一个控制信号 但是通过查看控制信号之间的关系, 可以做一些简化 由于微代码控制单元 bmcu 最多允许 32 个控制信号, 可以使用 lcs0 lcs31 之间的任何一个作为控制信号 8) 画出电路方框图 图中应该包括条件信号 控制信号 时钟信号以及复位信号 9) 根据前面 8 步得到知识, 即根据对各状态的地址分配以及算法第 1) 4) 两步构造的表格, 确定 ROM 中的数据内容 这一步的工作类似于汇编程序编译器的第 2 遍扫描时所做的工作 下面以串并转换电路为例, 说明如何完成上述几步工作 第 (1) 步 : 根据电路的 VHDL 算法描述, 可以得到如表 5-5 所示的状态转换表 读者先不用考虑表中最右列, 该列的内容将在算法的第 (4) 步得出 变量 P 为当前状态, 初始化为复位态, LC 表示 ROM 地址计数器, 初始化为 0 状态 P 的地址指定为 LC 对应的地址 所有状态全分配了 ROM 地址吗? 是 停止 否 NSP 为状态 P 后续的状态个数 NSP=1 NSP=? NSP>1 LC=LC+1 P 的下一状态是否指定了 ROM 地址? 是 否 说明 将下一状态用符号 I 标出设 P 为下一状态 将下一状态用符号 B 标出, 任选一个未指定 ROM 地址的状态为 P P 后续的状态中有没有状态没指定 ROM 地址?

215 图 5-25 微代码控制器中的 ROM 分配算法 215

216 表 5-5 执行微代码控制器设计方法的第 (1) 步得到的状态转换表 当前状态转换条件下一状态输出值 s0 s1 s2 s3 s4 s5 r or (not a) s0 B (not r) and a s1 I (not r) s2 I r s0 B (not r) s3 I r s0 B (not r) s4 I r s0 B (not r) s5 I r s0 B (not r) and a s1 B r or (not a) s0 B 第 (2) 步 : 对第 (1) 步产生的状态表中的所有条件都列出如下 : (not r) and a r or (not a) not r r 第 (3) 步 : 设定状态 s0 为复位态 第 (4) 步 : 执行图 5-25 中给出的算法, 可以把每个状态分别用符号 B( 表示地址跳转 ) 和 I( 表示地址顺序增加 ) 标注, 标注的结果如表 5-5 中的最右边一列所示 对每个状态分配的 ROM 地址如表 5-6 所示 表 5-6 对每个状态分配的 ROM 地址 状 态 ROM 地址 状 态 ROM 地址 s0 0 s3 3 s1 1 s4 4 s2 2 s5 5 第 (5) 步 : 观察表 5-5 中的数据, 容易看出 : 如果表中最右一列用符号 I 标注, 则从前一状态转换到当前状态只需要把 ROM 地址加 1, 即把 mar 中的内容加 1, 这时不需要将对应的条件从数据单元传递到控制单元 如果表中最右一列用符号 B 标出, 则转移到该状态需要 ROM 地址的跳转, 因此需要把此信号从数据单元传递到控制单元 把表中所有用符号 B 标出的条件选出, 消去冗余条件后, 得到的 3 个条件为 (not r)and a,r or(not a), r 根据微代码控制单元 bmcu 的结构, 最多可以允许有 24 个条件信号 (c0 c23), 任意选择 3 个信号, 分配给这 3 个条件, 即可得到控制信号的分配, 如表 5-7 所示 216

217 表 5-7 算法第 (5) 步选定的条件信号 转换条件 r r or (not a) (not r) and a 控制信号 c23 c22 c21 第 (6) 步 : 根据 VHDL 源代码, 可以列出在每个状态要进行的数据操作 实现数据操作的条件 各状态下电路的输出以及产生输出的条件等项内容, 如表 5-8 所示 表中条件项为无条件进行数据操作或无条件产生输出 事实上, 这个表格可以在算法第 (1) 步中产生, 只是为了清楚地划分算法每步要完成的任务, 这里把两种不同的任务分成了两步 表 5-8 算法第 (6) 步产生的数据操作和输出内容表 数据操作 输 出 输 出 状态 条件 done 条件 z 条件 s s1 数据右移 s2 数据右移 s3 数据右移 s4 数据右移 s 数据 1 第 (7) 步 : 根据表 5-8 可以得出需要从控制单元传递到数据单元的控制信号 显然可以看出, 需要有一个信号控制数据的移位, 还需要有一个信号控制产生输出 本例中, 数据操作和输出全无条件 表 5-9 列出了需要的两个控制信号, 并把它们与 lcs31 和 lcs30 对应起来 表 5-9 算法第 (7) 步确定的控制信号 控制单元输出 信 号 数据操作 / 系统输出 lcs31 shift 数据移位 lcs30 done_control done=1,z= 数据 第 (8) 步 : 至上面的第 (7) 步, 就确定了需要在数据单元和控制单元之间传递的全部信号, 可以根据前面几步的结果构造电路的方框图 图 5-26 示意给出了用微代码控制器 bmcu 实现的串并转换电路的方框图 24 个条件信号 c0 c23 中只使用了 3 个, 其他 21 个信号全接到了信号 f, 而信号 f 接到了逻辑 '0' 217

218 r a d f f c20 c0 控制单元 c23 c22 c21 lcs31 lcs30 c23 c22 c21 shift 数据单元 done 4 z reset done_control clk 图 5-26 串并转换电路的方框图 状 态 地 址 表 5-10 用微代码控制器 bmcu 实现串并转换电路时的 ROM 内容 控制字 cword cs nad lcs s s s s s s s 第 (9) 步 : 根据表 5-5 中内容, 很容易构造出 ROM 中的数据 表 5-10 给出了用微代码控制器 bmcu 实现串并转换电路时 ROM 中的数据 根据表 5-6 中对各状态的地址分配, ROM 地址 0 对应于状态 s0 根据表 5-5 和表 5-7 可知, 状态 s0 情况下, 产生跳转的条件 ( 用符号 B 标出 ) 为 c22(r or(not a)), 所以 ROM 地址 0 中的控制字的 cs 域中只有对应 c22 的一列为 1, 其他全为 0 同样, 根据表 5-5 可知,c22 为 1 时跳转后的状态为 s0, 所以, 地址 0 的控制字的 nad 域的内容为 根据表 5-8 可知, 在状态 s0 不进行数据操作, 也不产生输出, 所以对应信号 done_control 和 shift 的两位数据 (lcs31 和 lcs30) 应该为逻辑 0, 而地址 0 的控制字中 lcs 域的其他位也应该全是 0 当 c22 为 0 时,ROM 地址应该增加 1, 器件进入状态 s1, 状态 s1 的地址为 1 ROM 地址 1 4 对应于状态 s1 s4, 确定 ROM 地址 1 4 的内容的方法与 ROM 地址 0 的方法完全相同 对于串并转换电路, 从状态 s5 可以转换到 s0, 也可以转换到 s1 由于微代码控制器 bmcu 中的地址生成逻辑中只允许有一个跳转地址和一个增量地址, 所以对应状态 s5 的 ROM 地址中的数据要考虑两个跳转地址的情况, 实现从一个状态向两个或两个以上的状 218

219 态跳转需要多个 ROM 地址 对于本例中的状态 s5, 在 ROM 地址 5 处对条件 c21((not r) and a) 进行检查, 因此该地址的控制字中的 cs 域中对应 c21 的位设为 1, 其他位为 0 如果 c21 为 true, 器件应该跳转到 ROM 地址 1( 状态 s1), 应该在 ROM 地址 5 的 nad 域中指定这一地址 如果 c21 为 false, 器件进入 ROM 地址 6, 在 ROM 地址 6 再对条件 c22(r or (not a)) 进行检查, 对应 ROM 地址 6 的控制字中的 cs 域中 c22 位应该设为 1, 其他位全为 0 如果 c22 为 true, 硬件跳转到地址 0( 状态 s0), 因此 ROM 地址 6 的控制字中 nad 域要设为 0 可以看出, 由于在状态 s5 可能跳转到两个不同状态, 所以实现状态 s5 需要两个 ROM 地址, 这就验证了图 5-25 的说明中给出的结论 由于实现状态 s5 用了两个 ROM 地址, 硬件的工作也同样需要两个时钟周期 对于控制单元产生的控制信号, 设计方法与状态 s0 相同, 在状态 s5 不需要对数据进行移位操作, 所以 ROM 地址 5 6 中的控制字的 lcs 域中对应 lcs31(shift) 位的数据应该为 0 由于在状态 s5 要产生输出 done 和 z, 所以 ROM 地址 5 6 中的控制字的 lcs 域中对应 lcs30(done_control) 位的数据应该为 1, 其他各位全应该为 0 定义了 ROM 中的数据, 就完成了微代码控制器的设计 由于微代码控制器 bmcu 的硬件设计已经完成, 对于具体的电路设计, 例如串并转换电路, 设计者要做的工作就是编制 ROM 中的数据, 并把编程后的 ROM 嵌入微代码控制器 bmcu 在对 ROM 编程之前, 可以利用 VHDL 语言对设计的 ROM 数据进行验证 把例 5-13 讨论过的 bmcu 的算法模型 algorithmic 中数组 mem 中的内容按表 5-10 的内容填充, 并把结构名换为 stop, 即可得到串并转换电路的控制部分的算法模型 例 5-15 use work.bmcu_function.all; architecture stop of bmcu is signal mar,nmar:std_logic_vector(7 downto 0) --mar 是 ROM 的地址寄存器 --nmar 是 mar 的下一数值 signal cword:std_logic_vector(63 downto 0); -- 从 ROM 来的控制信号 signal mir:std_logic_vector(63 downto 0); -- 保存从 ROM 来的控制信号 signal nad:std_logic_vector(7 downto 0); -- 分支地址, 是 cword 的一部分 signal cs:std_logic_vector(23 downto 0); -- 保存从 ROM 来的控制信号 marp:process(clk,reset) if reset='1'then mar<=b" "after mar_delay; elsif clk'event and clk='0'then 219

220 mar<=nmar after mar_delay: end if; end process marp; mirp:process(clk,reset) if reset='1'then mir<=x"00_00_00_00_00_00_00_00"; elsif clk'event and clk='1'then mir<=cword after mir_delay; end if; end process mirp; ROMp:process(mar) type mem_type is array(0 to 255)of std_logic_vector(63 downto 0); constant mem:mem_type := (0=>x"40_00_00_00_00_00_00_00", 1=>x"80_00_00_00_80_00_00_00", 2=>x"80_00_00_00_80_00_00_00", 3=>x"80_00_00_00_80_00_00_00", 4=>x"80_00_00_00_80_00_00_00", 5=>x"20_00_00_01_40_00_00_00", 6=>x"40_00_00_00_40_00_00_00", others=>(others=>'0')); cword<=mem(std_logic_vector_to_int(mar)) after ROM_delay; end process ROMp; decodep:process(mir) nad<=mir(39 downto 32); lcs<=mir(31 downto 0) cs<=mir(63 downto 40); end process decodep; agl:process (mar,nad,c,cs) variable as:std_logic; as:=(cs(23)and c(23))or(cs(22)and c(22))or(cs(21)and c(21)) or (cs(20)and c(20))or (cs(19)and c(19)) or (cs(18)and c(18))or (cs(17)and c(17)) or (cs(16)and c(16))or (cs(15)and c(15)) or (cs(14)and c(14))or (cs(13)and c(13)) or (cs(12)and c(12))or (cs(11)and c(11)) or (cs(10)and c(10))or (cs(9)and c(9)) 220

221 or (cs(8)and c(8))or (cs(7)and c(7))or(cs(6)and c(6)) or (cs(5)and c(5))or (cs(4)and c(4))or(cs(3)and c(3)) or (cs(2)and c(2))or (cs(1)and c(1))or(cs(0)and c(0)); case as is when'0'=>nmar<=inc(mar)after agl_delay; when'1'=>nmar<=nad after agl_delay: end case; end process agl; end stop; 前面讨论用微代码控制器实现串并转换电路的设计过程中, 并没有讨论数据单元的设计 无论电路的控制单元由硬连线方法实现, 还是由微代码控制实现, 数据单元的设计都是相同的 例 5-16 的 VHDL 源代码是数据单元的数据流模型, 根据数据流模型可以采用前面已讨论过的方法设计数据单元的硬件 对于具体的硬件设计, 数据单元总是要专门设计 例 5-16 entity datastop is generic(shift_delay,gate_delay:time) port r,d,a,clk,shift,done_control:in std_logic; z:out std_logic_vector(3 downto 0); done,c23,c22,c21:out std_logic); end datastop; architecture dataflow of datastop is signal shift_reg:std_logic_vector(3 downto 0); -- 移位寄存器 shift_reg<=d & shift_reg(3 downto 1)after shift_delay when clk'event and clk='1'and shift='1'else shift_reg; -- 控制单元需要的条件信号 c23<=r; c22<=r or not a after gate_delay; c21<=not r and a after gate_delay; -- 输出信号 done<=done_control; z<=shift_reg; end dataflow; 例 5-17 给出的 VHDL 源代码是串并转换电路的系统级模型, 可以对该模型进行仿真以确定系统是否能正常工作 在系统级模型中, 控制单元和数据单元都各用一个器件表示, 器件之间的连接关系如图 5-26 所示 由于模块的所有输入端都不应该开路, 这里把没用的 221

222 端口连接到信号 f, 信号 f 则接到逻辑 '0'; 对于各模块中不用的输出端口, 则任其开路 在实际硬件设计中, 这是一种很好的设计习惯 例 5-17 use work.all entity test_bench end test_bench; architecture bmcu_test of test_bench is signal r,a,d,clk,init,reset:std_logic; signal c23,c22,c21:std_logic; signal shift,done_control:std_logic; signal done:std_logic; signal z:std_logic_vector(3 downto 0); signal x:std_logic_vector(3 downto 1); signal f:std_logic -- 数据单元 component data_unit generic(shift_delay,gate_delay:time) port(r,a,d,clk,shift,done_control:in std_logic; z:out std_logic_vector(3 downto 0); done,c23,c22,c21:out std_logic); end component; -- 控制单元 component micro_control_unit generic (agl_delay,mar_delay,rom_delay,mir_delay, mir_setup,mar_setup:time); port(c:in std_logic_vector(23 downto 0); -- 数据单元来的条件信号 clk,reset:in sed_logic; -- 系统时钟和复位信号 lcs:out std_logic_vector(31 downto 0)); -- 送往数据单元的控制信号 end component; for l1:data_unit use entity datastop(dadaflow); for l2:micro_control_unit use entity bmcu(stop); l1:data_unit generic map (20 ns, 10 ns) port map(r,a,d,clk,shift,done_control,z,done, c23,c22,c21); 222

223 l2:micro_control_unit generic map (50 ns,20 ns,50 ns,20 ns,5 ns,5 ns) port map(c(23)=>c23,c(22)=>c22,c(21)=>c21, c(0)=>f,c(1)=>f,c(2)=>f,c(3)=>f,c(4)=>f, c(5)=>f,c(6)=>f,c(7)=>f,c(8)=>f,c(9)=>f, c(10)=>f,c(11)=>f,c(12)=>f,c(13)=>f, c(14)=>f, c(15)=>f,c(16)=>f,c(17)=>f,c(18)=>f,c(19)=>f, c(20)=>f,reset=>reset,clk=>clk, lcs(31)=>shift,lcs(30)=>done_control); -- 时钟进程 process clk<='0'; wait for 200 ns; clk<='1'; wait for 200 ns; end process; -- 提供输入信号值的进程 f<='0'; reset<='1', '0'after 30 ns; process(x) r<=x(3); a<=x(2); d<=x(1); end process; -- 对输入 x 赋值 x<="100"after 50 ns, "010"after 650 ns, "001"after 1050 ns, "000"after 1450 ns, "001"after 1850 ns, "001"after 2250 ns, "000"after 2650 ns, "000"after 3050 ns, "010"after 3450 ns, "000"after 5050 ns, "010"after 5450 ns, "001"after 5850 ns, "001"after 6250 ns, "011"after 6650 ns, "010"after 7050 ns, "010"after 7450 ns, "000"after 7850 ns, "101"after 8250 ns, "001"after 8650 ns, "001"after 9050 ns, "000"after 9450 ns, "001"after 9850 ns, "000"after ns, "001"after ns, "000"after ns, "000"after ns; end bmcu_test; 微代码控制单元的局限性 本章已经讨论过微代码控制单元的主要优点, 并与硬连线方式的控制单元进行过对 223

224 比 本小节以微代码控制器 bmcu 为例, 具体讨论利用微代码控制单元进行电路设计的优点以及局限性 微代码控制单元 bmcu 只能从数据单元得到不多于 24 个的条件信号, 即控制单元只能对不多于 24 种的条件进行检查 微代码控制器最多只能为数据单元提供 32 个控制信号, 所以它最多只能对控制单元进行 32 种独立的数据操作 由于控制信号中地址 nad 只有 8 位, 限制微代码控制器的最大编程量是 256 个控制字 这 3 种局限性都是由于 bmcu 中控制字的长度及其 3 个域的分配方式所造成的 一般地讲, 任何预先设计好的微代码控制器中控制字的长度都限制了控制器的应用 事实上, 控制字的长度限制并不是微代码控制器最严重的局限性, 而产生下一 ROM 地址的方法对微代码控制应用的限制最为严重 微代码控制器 bmcu 采用了最基本的 2 分支电路确定下一 ROM 地址 控制单元根据一个输入条件信号确定分支地址 例如, 图 5-21 中的选择信号 as 为 '1' 时, 后续地址可以跳转为 ROM 区内的任意地址 ; 若 as 为 '0', 则后续地址为 ROM 中相邻的下一地址 虽然这样的地址生成电路也可以实现 3 分支或 3 个以上的分支, 但利用这种 2 分支电路实现多分支状态转移需要多个微指令周期才能完成, 这会影响硬件的运算速度 例如, 对于前面讨论的串并转换电路的状态 s5, 硬件可能转移到两种不同状态 s0 或 s1, 实现该状态下的功能需要两个 ROM 地址, 而执行该步运算则需要两个微指令周期 一般地讲, 对于 m 分支的状态转换, 需要 m-1 个跳转地址和一个增量地址, 判断向哪个地址跳转需要 m-1 个跳转条件, 执行状态转移需要 m-1 个微指令周期 通常, 一个微指令周期就是一个系统时钟周期, 如果控制单元确定状态转移需要的时间多于一个微指令周期, 就有可能对原始的数据输入输出的时序产生影响 对于例 5-15 中的串并转换电路 stop, 如果当前状态为 s5, 则需要根据输入条件确定下一状态为 s0 还是 s1 如果用微代码控制器 bmcu 实现该串并转换电路, 在状态 s5 需要两个微指令周期 假定在状态 s5 的第 2 个微指令周期中有一个输入脉冲到来, 由于这时控制器不对输入端进行检查, 该串并转换电路会漏掉图 5-13 中标出的输入脉冲序列中的第一位数据 这就是说, 用微代码控制器 bmcu 实现的串并转换电路的时序特性, 与图 5-13 给出的指标要求并不严格一致 如果严格按图 5-13 中的时序要求设计串并转换电路, 即每位数据只维持一个时钟周期, 且在一组串行数据输入后立刻产生并行输出, 并可以立即输入下一组串行数据, 则不能采用 bmcu 这样的微代码控制器实现该电路 一般地讲, 微代码控制器中的地址生成逻辑限制了确定一个状态的后续状态的方法, 也就使得在不同的状态需要不同数目的指令周期实现 如果把这样的器件与其他器件互连, 通常需要采用工作速度较慢的握手协议实现器件之间的通信 除此之外, 如果规定了器件输入和输出的时序关系, 有时不能采用给定的微代码控制器实现特定的硬件 上面讨论了微代码控制器实现串并转换电路时对时序关系的限制, 而事实上, 任何微代码控制器都限制了所能实现的电路的时序关系 微代码控制器设计时的其他条件选择方法在微代码控制器 bmcu 中, 控制字的 cs 域中的每一位都对应于一个数据单元中产生的条件信号, 用条件信号直接控制选择跳转 ROM 地址或顺序 ROM 地址, 不需要对条件信号再进行译码, 这称为条件线性选法 条件线性选法的译码代价最小 当条件信号个数较 224

225 多时, 采用条件线选方法, 需要 cs 域的位数较多, 在利用它设计具体电路时, 编程工作量也较大 与此相反, 还可以对条件信号进行全译码选择 ROM 地址, 这种方法称为条件全译码选择 采用全译码法时, 如果控制字的 cs 域有 16 位数据, 对 16 位数据全译码可以译出 2 16 =65536 个输出, 可以用来对应数据单元中产生的 个条件信号 这种全译码方法, 充分利用了控制字中的 cs 域中的信息, 但译码代价较高 例如, 假定控制字的字长为 64 位, 控制器最大允许的条件信号个数为 16 如果选用条件线性选择方法, 则控制字的 cs 域需要 16 位 ; 如果采用全译码方法, 则控制字的 cs 域需要 4 位 (2 4 =16) 假定保持控制字的其他内容相同, 那么与条件线性选择译码实现的硬件相比, 全译码方法实现的硬件是以增加一个 4~16 线译码器为代价, 把微程序 ROM 的字长从 64 位降低到了 52 位 条件线性选择和条件全译码选择是两个极端, 在两个极端之间可以有多种折衷方案 设计者可以根据译码电路的复杂程度以及 ROM 字长等各种因素, 综合考虑应该采用什么方法 选择 ROM 地址的多分支方法根据前面的讨论可知, 地址生成逻辑是限制微代码控制器应用范围的重要因素 改进地址生成逻辑, 使得在一个地址可以根据不同情况跳转到不同地址, 这就增加了微代码控制器的适应范围, 当然, 同时也增加了地址生成电路的复杂性 图 5-27(a) 给出了一种控制字分配方法, 可以用于 4 种不同的地址生成方法 控制字的 64 位数据的最高 2 位构成了 at 域, 用来在 4 种地址生成方法中选择一种 6 位 cs 域被全译码为 64 位输出, 用来对应于数据单元产生的最大 64 个条件信号 3 个地址域 nad1 nad2 和 nad3 用来计算后续 ROM 地址 ROM 地址宽度为 16 位, 因此地址域 ROM 地址为 64kb 32 位的 lcs 域是为数据单元提供的控制信号, 最大可以提供 32 位控制信号 当 at=00 时, 微代码控制器无条件转移到 nad1 和 nad2 共同组成的 16 位 ROM 地址, 而 cs 域和 nad3 域中可以为任意值, 地址生成逻辑不考虑 cs 域和 nad3 域中是什么数值 这种情况下控制字的内容如图 5-27(b) 所示 当 at=01 时, 控制器采用两分支地址生成逻辑 这种情况与 bmcu 类似,cs 域被全译码为 64 个输出, 分别用来选择数据单元产生的 64 个条件信号 如果选择条件为 true, 则控制器跳转到 nad1 和 nad2 组成的 16 位 ROM 地址 ; 如果选择条件为 false, 则控制器顺序执行后面的 ROM 地址, 即新的 ROM 地址为 mar+1 图 5-27(c) 给出了这种情况下的控制字的内容 当 at=10 时, 控制器采用 3 分支地址生成逻辑 硬件设计者要自行选择两组输入条件, 为了简化地址生成逻辑, 要求两组条件表示为两个连续的二进制数, 且第一个数为偶数, 即 c0='0' 先将控制字的 cs 域全译码, 译码结果和第一组输入条件信号按位进行与运算, 如果得到非零结果, 则设 cdl 为逻辑 '1' 计算 cdl 和 cdh 的电路原理如图 5-28 所示 硬件设计者应该保证数据单元产生的条件信号不能产生 cdl 和 cdh 同时为逻辑 '1' 的情况 若 cdl 为 true, 则将 nad1 和 nad2 组合起来, 构成控制器的跳转地址, 即将图 5-27(d) 中的 nadh 和 nadl1 组合构成跳转地址 若 cdh 为 true, 则将 nad1 和 nad3 组合构成控制器的跳转地址, 即将图 5-27(d) 中的 nadh 和 nadl2 组合构成跳转地址 图 5-27(d) 中画出了 3 分支地址生成逻辑的示意图, 用于两种跳转情况下的跳转地址的高字节相同, 全为 nadh, 所以两个跳转地址之间的最大距离为 256 字节 当然, 也可以在控制字中保存两个完整的 16 位跳转 225

226 地址, 以减小对跳转地址的限制, 但这样会把 ROM 字长从 64 位增加到 72 位 设计者可以综合考虑增加 ROM 字长的代价与增加跳转地址灵活性哪种情况更适合设计要求 at cs nad1 nad 2 nad 3 lcs (a) 一般结构 nad lcs (b) at=00, 无条件分支 cs nad lcs (c) at=01, 两分支情况 cs nadh nadl1 nadl2 lcs (d) at=10,3 分支情况 cs nadl1 nadl 2 nadl 3 lcs (e) at=11,4 分支情况 图 5-27 程序分支更灵活的控制字组织当 at=11 时, 控制器采用 4 分支地址生成逻辑, 硬件设计者要自行选择 3 组输入条件 为了简化地址生成逻辑, 要求 3 组条件表示为 3 个连续的二进制数, 且第 1 个数为 4 的整数倍, 即第 1 个二进制数的最低 ( 右 ) 两位全为 '0' 例如, 用户可以选择 3 组条件信号输入的二进制值分别为 和 26, 其二进制表示分别为 11000,11001 和 将控制字的 cs 域全译码, 译码结果和第 1 组输入条件信号按位作做运算, 如果得到非零结果, 则设 cd1 为逻辑 '1'; 将译码结果与第 2 组输入条件信号按位做与运算, 如果得到非零结果, 则设 cd2 为逻辑 '1'; 将译码结果与第 3 组输入条件信号按位做与运算, 如果得到非零结果, 则设 cd3 为逻辑 '1' 硬件设计者应该保证数据单元产生的条件信号不能产生 cd1 cd2 和 cd3 同时为逻辑 '1' 的情况 若 cd1 为 true, 则将当前 ROM 地址, 即 mar 中内容的高 8 位和 nad1 组合起来, 构成控制器的跳转地址 若 cd2 为 true, 则将当前 ROM 地址的高 8 位和 nad2 组合起来, 构成控制器的跳转地址 若 cd3 为 true, 则将当前 ROM 地址的高 8 位和 nad3 组合起来, 构成控制器的跳转地址 若 cd1=cd2=cd3=false, 则将当前 ROM 地址的下一地址, 即 mar+1 作为后续地址 图 5-27(e) 示意给出了 4 分支地址生成的情况 采用这里 226

227 给出的地址生成方法,3 个跳转地址的高 8 位与当前地址的高 8 位相同, 即当前 ROM 地址与跳转地址总在 256 字节的 ROM 区内, 与 3 分支地址生成的情况相同 由于对地址跳转有最大跳转长度为 256 字节的限制, 才保持了 ROM 字长为 64 位的优势, 这通常是一种较好的设计方案 控制单元 控制单元中控制字的 cs 域 cs5 cs1 cs 译码器 dcs63 dcs1 dcs0 数据单元 C63 c63 c1 C1 c0 C0 & DCS0 dcs0 & DCS1 dcs1 & DCS63 dcs3 或运算电路 cdl CDL 1 & dcs0 DCS0 DCS1 dcs1 & 或运算 CDH cdh 电 & 路 DCS63 dcs3 图 分支地址生成逻辑中 cdl 和 cdh 的计算利用本节讨论的组合地址生成算法, 对于大部分应用, 可以在不增加每个状态下的微指令周期的前提下, 设计出相同功能的代码程序 当然, 不可能利用这里介绍的地址生成方法直接实现 1 分支或多于 5 分支的状态转换 同时应该知道, 即使对于 3 分支或 4 分支的地址跳转, 采用这里介绍的地址生成逻辑对地址的产生也有一些限制 227

228 5.4 超级精简指令集计算机 (URISC) RISC(Reduced Instruction Set Computer) 的含义是精简指令计算机, 这意味着计算机的指令集非常简单, 所谓指令集简单是与复杂指令计算机 (CISC) 相对比而言 RISC 很适合于用超大规模集成电路实现, 由于指令简单, 所以译码复杂度低, 同时可以针对各条指令对硬件进行优化, 因此指令的执行速度很高, 从而弥补了其指令个数少的欠缺 一旦决定了要设计一个小指令集的计算机, 首先可以考虑的设计方案是利用超级 RISC 结构 所谓超级 RISC 结构, 指的是 mavaddat 和 parham 提出的一种 RISC 结构, 由于它只有一条指令, 指令集不能再精简, 所以称为超级精简指令计算机 (URISC) 尽管 URISC 只有一条指令, 它也是一种通用的计算机, 所有复杂操作都可以用这一种指令完成 URISC 指令是指做减运算, 且在结果为负值时转移 由于只有一条指令, 所以不需要对指令定义操作码 指令中只需指出两个操作数和运算结果为负数时的转移地址 URISC 指令的形式如下 : 第 1 个操作数的地址第 2 个操作数的地址运算结果为负时的转移地址 URISC 指令的执行过程如下 : 第 2 个操作数减去第 1 个操作数, 并把运算结果存储在第 2 个操作数的地址中 ; 如果减法运算得到的结果为负数, 则转移到指定的地址继续执行 ; 否则顺序执行下一地址中的指令 ; 如果转移到地址 0, 则停止 URISC 的运行 按这种方式, 每一条指令都是 3 字节指令, 占用 3 个存储单元, 需要 3 个存储器读周期才可以读出指令, 因此只有在存储器的读取速度很高的前提下,URISC 才能有效工作 本节将详细讨论对存储器读取速度的要求 下面以汇编语言的表示方法讨论如何用 URISC 实现指令的运算 汇编语言的一般格式为 : l:f,s,t 其中,l 是语句标号, 用来表示存储该条指令的符号地址 ;f 是第 1 个操作数的符号地址 ; s 是第 2 个操作数的符号地址 ;t 是减运算得到负结果时的跳转地址 假定在汇编语言中, 伪指令 l:word c 的作用是把常数 c 存储在符号地址 l 中 采用上述伪指令, 对表 5-11 所示的 URISC 程序进行讨论 这段程序实现的运算为 : z=(x+y)/2 其中, 除法通过相继进行减法运算实现 运算结束后, 跳转到地址 0, 即停止程序的执行 表 5-11 完成 z=(x+y)/2 的 URISC 程序 地址符号指令说明 0 stop: word 0 停止执行 1 ready: y,temp1,next1 temp1<=-y 2 next1: temp1,x,next2 x<=y+x 续表 228

229 地址符号指令说明 3 next2: z,z,test z<=0 4 test: x,temp2,positive temp2<=-(x+y) 5 negative: two,temp2,next3 temp2<=- (x+y) -2 6 count_neg: one,z,next3 z<=z-1 7 next3: two,temp3,negative goto negative 8 positive: two,x,stop x<=(x+y)-2 9 count_pos: mone,z,next4 z<=z+1 10 next4: two,temp4,positive goto positive temp1: word 0 temp2: word 0 temp3: word 0 temp4: word 0 x: word 0 z: word 0 one: word 1 mone: word 1 two: word URISC 处理器结构 本节讨论 URISC 处理器的结构, 图 5-29 是 URISC 处理器中的数据单元 程序计数器 pc 保持下一单位时间要执行的指令所在的地址 数据寄存器 mdr 以及地址寄存器 mar 用来提供 URISC 处理器与存储器之间的接口 寄存器 r 存储减运算的第 1 个操作数, 减运算通过加一个数的补码来实现 加法器的第 1 个操作数总是从总线 bus_a 得到, 另一个操作数的来源有两种可能 : 当 comp 为 '1' 时, 加法器的第 2 个操作数由 r 中的数值求反得到 ; 当 comp 为 '0' 时, 加法器的第 2 个操作数是常数 0 加法器的输出总是连接到总线 bus_b 加法器可能完成 3 种运算 : 若 comp='0', 且 cin='1', 则加法器完成加 1 运算, 用来实现 pc+1; 若 comp='1', 且 cin='1', 则加法器把第 2 个操作数与第 1 个操作数的补码相加, 实现了减运算 ; 若 comp='0' 且 cin='0', 则把常数 0 加到第 2 个操作数, 其作用是把 bus_a 上的数值直接传递到 bus_b, 这一功能用来把程序计数器 pc 的值或数据寄存器 mdr 的值传递到地址寄存器 mar 加法器带有两个状态输出信号 z 和 n, 其作用分别是指明加运算得到了零结果或负结果 在设定了控制信号 zin 和 nin 的情况下, 这两个标志信号可以存入相应的状态触发器 图 5-29 中用符号 给出了必要的控制点, 并标明了每个控制点的控制信号 用 out 标出的控制信号 ( 例如 pcout) 的作用类似于寄存器的使能信号, 用来控制相应的寄存器的输出与总线 bus_a 的连通或切断 例如, 当 pcout 为 '1' 时, 程序计数器 pc 的数值送上总线 bus_a;pcout 为 '0' 时, 程序计数器 pc 的输出为高阻态 图 5-29 中用 in 标出的控制信号 ( 例如 marin) 用来控制是否向寄存器加载数据 例如, 如果 marin 为 '1', 则总线 bus_b 上的数据被加载到地址寄存器 mar; 如果 zin 为 '1', 则加法器运算产生的状态信号 z 被加载到 z 状态触发器 229

230 pcout pcin 程序计数器 PC rin 寄存器 r zin comp nin 加法器 ACC cin z n mdrout 数据寄存器 MDR mdrin bus_a 地址寄存器 MAR 存储器 RAM marin read write bus_b 图 5-29 URISC 处理器的数据单元 通过对 pcout cin 和 pcin 置不同的数据, 就可以实现 pc+1 首先, 设置控制信号 pcout, 把程序计数器 PC 中的值送上总线 bus_a 其次, 设置 comp='0' 且 cin='1', 这样加法器的第 1 个操作数是总线 bus_a 上的数据, 第 2 个操作数是常数 0 由于 cin='1', 加法器运算的结果是把 pc+1 的值送上总线 bus_b 随着对控制信号 pcin 置位, 总线 bus_b 上的数据被加载到程序计数器 PC 中 这样就完成了一个指令周期, 实现了 pc+1 从指令中的第 2 个操作数中减去第 1 个操作数的过程也很简单 首先, 把第 1 个操作数加载到寄存器 r 中, 把第 2 个操作数加载到数据寄存器 MDR 中 然后, 对 mdrout cin 和 comp 置位, 就可以把 MDR 中的数据 ( 第 2 个操作数 ) 送到加法器的第 1 个输入端, 把 r 中数据 ( 第 1 个操作数 ) 的反码送到加法器的第 2 个输入端 由于 cin='1', 加法器完成的是将第 2 个操作数与第 1 个操作数的补码相加, 即实现了从第 2 个操作数中减去第 1 个操作数的运算 最后, 对控制信号 mdrin 置位, 将加法器运算结果通过总线 bus_b 送回到数据寄存器 MDR 中 如果把控制信号 pcout 和 marin 置位, 则可以把程序计数器 PC 中的值通过路径 bus_a 加法器 bus_b 传递到地址寄存器 MAR URISC 处理器的控制 230 为了使 URISC 处理器正常工作, 数据单元中的每一个控制信号必须在一定时刻按所实

231 现运算的要求置位, 而在另一时刻复位 控制单元的作用就是提供这些控制信号 图 5-30 示意给出了 URISC 处理器的方框图 该图中包括了控制单元 cu 数据单元 du 存储器 RAM 以及它们之间的控制信号 控制单元的输出主要是图 5-29( 数据单元 ) 中的各个控制信号 除了图 5-29 中要求的信号之外, 控制单元还产生读 read 写 write 信号用来控制存储器的读写 数据单元 du 数据 data 地址 address 存储器 RAM pcout pcin nin z n clk 控制单元 cu read write 图 5-30 URISC 处理器的方框图 URISC 处理的状态序列和指令周期 一个指令周期内的每一个时钟周期, 构成了 URISC 的一个指令状态 在每个指令状态, 控制单元要给出各个控制信号的数值 表 5-12 列出了一个指令周期中的 8 个指令状态 这 8 个指令状态组成的状态序列把一个指令从存储器中取出, 并执行这个指令 每个状态中需要置位的控制信号全列在表 5-12 中, 不需要置位的控制信号则没有列出 这个状态序列可以正常工作的前提是程序计数器 PC 中保存有将要执行的指令的第 1 个字节的地址, 且假定每个指令占用 3 个连续的存储单元 第 1 个操作数占用第 1 个存储单元, 第 2 个操作数占用第 2 个存储单元, 第 2 个操作数减去第 1 个操作数得到负值时的跳转地址占用第 3 个存储单元 在这个控制序列结束时, 下一条指令的第 1 个字节的地址不驻留在程序计数器 PC 中 重复执行这个序列, 可以执行完整的程序 表 5-12 URISC 处理器的一个指令周期 状态需要置位的信号 pcout,zin,marin,read,zend mdrout,marin,read mdrout,rin pcout,cin,pcin,marin,read mdrout,marin,read mdrout,comp,cin,nin,mdrin,write pcout,cin,pcin,marin,read pcout,cin,pcin,nnend mdrout,pcin 下面详细解释上述指令周期中的各个状态 231

232 1. 状态 0 状态 2: 把第 1 个操作数放入寄存器 r 在状态 0, 先通过设置控制信号 pcout 和 marin 为 '1', 把指定地址加载到地址寄存器 mar 中 由于 comp 和 cin 全为 '0', 所以 pc 中的数值通过总线 bus_a 与操作数 0 相加后传递到总线 bus_b,marin 为 '1' 使得总线 bus_b 上的数值加载到 mar, 即把当前指令的第 1 字节的地址加载到地址寄存器 mar 中 由于设置了控制信号 zend, 如果 pc 的值为 0, 则控制序列会在状态 0 无限执行, 这时称为 URISC 处理器的状态挂起, 以下讨论全假定 pc 的值不为 0 由于 read 为 '1', 所以在这一状态把 mar 所指地址的内容读入数据寄存器 mdr, 即把第 1 个操作数的地址读到数据寄存器 mdr 中 在状态 1, 把控制信号 mdrout 和 marin 设置为 '1', 这时数据寄存器 mdr 中的数据 ( 第 1 个操作数的地址 ) 经过 bus_a 加法器和 bus_b 进入地址寄存器 mar 由于这一状态仍设定了读控制信号 read, 所以此时把 mar 所指明地址中的内容 ( 第 1 个操作数 ) 读入数据寄存器 mdr 在状态 2, 设置控制信号 mdrout 和 rin 为 '1', 使得数据寄存器 mdr 中的数据 ( 第 1 个操作数 ) 进入寄存器 r 2. 状态 3 状态 5: 读第 2 个操作数, 把第 2 个操作数减去第 1 个操作数的结果存入原第 2 个操作数的地址中在状态 3, 读第 2 个操作数的地址 首先, 设置控制信号 pcout 和 cin 为 '1', 加法器对 pc 的值加 1 因为控制信号 pcin 和 marin 为 '1', 加法器运算的结果被同时送入程序计数器 pc 和地址寄存器 mar 由于读控制信号 read 为 '1', 此时将 mar 所指地址的内容 ( 第 2 个操作数的地址 ) 读入数据寄存器 mdr 在状态 4, 由于控制信号 mdrout 和 marin 为 '1', 数据寄存器 mdr 中的内容 ( 第 2 个操作数的地址 ) 经过 bus_a 加法器和 bus_b 进入地址寄存器 mar 由于这一状态仍设置读控制信号 read 为 '1', 所以此时把 mar 所指地址的内容 ( 第 2 个操作数 ) 读入数据寄存器 mdr 在状态 5, 设置控制信号 mdrout comp 和 cin 为 '1', 使得加法器从第 2 操作数中减去寄存器 r 中的内容 ( 第 1 个操作数 ) 由于设定了控制信号 mdrin, 减法运算的结果被送回到数据寄存器 mdr 由于设定了控制信号 nin, 如果减法运算得到负结果, 标志寄存器 n 会被置位 由于在状态 4 中为地址寄存器 mar 设定的数值并没有被改变, 所以写控制信号 write 为 '1' 时, 数据寄存器中的内容 ( 减运算的结果 ) 重新被写入原第 2 个操作数的地址中 3. 状态 6: 把减法运算得到负值时的转移地址读入数据寄存器 mdr 设置控制信号 pcout cin 和 pcin 为 '1', 加法器将程序计数器 pc 的值加 1 后送回到 pc, 即 pc 指向了当前指令的第 3 个字节 同时由于设定了控制信号 marin, 所以 pc 的新值也被送入地址寄存器 mar 读控制信号 read 使当前指令的第 3 个字节读入数据寄存器 mdr 4. 状态 7: 增加 pc, 使它指向下一条指令的第 1 个字节设置控制信号 pcout cin 和 pcin 为 '1', 加法器将程序计数器 pc 的值加 1 后送回到 pc, 即 pc 指向了下一指令的第 1 个字节 这时还可能出现两种情况 : 如果在状态 5 中, 做减法运算时没有产生负结果, 即没有把标志寄存器 n 置为 '1', 则由于这时设定了控制信号 nnend, 控制序列将返回到状态 0, 重新开始执行一条新指令 也就是说, 减法运算得到非负结果时,URISC 处理器继续执行下一条指令 如果在状态 5 中设置了标志位 n, 即减法运算得到了负结果, 则转移到状态 8 232

233 5. 状态 8: 把数据寄存器 mdr 中的数据复制到程序计数器 PC 之后, 控制单元转到状态 0 重新执行一条新指令由于将控制信号 mdrout 和 pcin 设置为 '1', 数据寄存器 MDR 中的数据经 bus_a 加法器和 bus_b 被复制到 PC 然后, 控制单元返回到状态 0 通过研究上述控制序列所描述的 URISC 指令执行过程, 可以看到几乎每一个状态中都有两个相继的操作 例如, 在状态 0, 程序计数器 PC 的值必须先转移到地址寄存器 MAR, 然后再读外部存储器, 把存储器中的数据转移到数据寄存器 MDR 中 除此之外, 还可以看到在每一个状态中, 几乎都是先有一个 URISC 的数据单元内部的操作, 然后再有一个存储器读 / 写操作 用两相互不交迭的时钟可以对这两个相继的操作进行控制, 第 2 相时钟 ph2 控制数据在 URISC 数据单元内部的移动, 第 1 相时钟 ph1 控制数据在 URISC 数据单元和存储器之间移动 图 5-31 给出了 URISC 处理器的时序 在控制序列的开始处, 时钟 ph2 的下降沿更新控制数据在 URISC 数据单元内传递 在每个控制状态的结尾处,ph1 的下降沿控制产生存储器读 / 写操作, 并把控制信号更新为下一状态的控制信号 ph1 ph2 图 5-31 URISC 处理器的两相互不交迭时钟 URISC 系统 图 5-32 给出了一个采用 URISC 处理器的计算机系统, 它包括有一个随机存取存储器 csmr cs write write rdmr read 存储器 run URISC address address RAM 处理器 data data rdio data 输入 / 输出 data io_dav read dav 端口 port strobe 图 5-32 采用 URISC 处理器的计算机系统示意 RAM 和输入 / 输出端口模块 port 其中, 信号 run 控制处理器的启动和停止 ; 信号 data 和 address 分别是外部数据总线和地址总线 ; 信号 read 指定数据从 RAM 向 URISC 处理器传 233

234 送 信号 write 指定数据从 URISC 处理器向 RAM 传送 ; 信号 dav 由模块 port 为该信号置位 当 URISC 处理器准备好处理数据时, 会为信号 rdio 置位 模块 port 在接收到该信号后, 才能把数据送上总线 data 在寄存器级设计 URISC 处理器 下面给出的 VHDL 源代码是 URISC 处理器的框架, 这里仍使用了程序包 IEEE.std_logic_1164 例 5-18 URISC 处理器模型的框架 URISC 处理器的实体说明 use IEEE.std_logic_1164.all; use IEEE.std_logic_arith.all; entity URISC is generic(enable_del,disbl_del,reg_del,add_del, per,count_del,rom_del,or_del,and_del, inv_del,mux_del:time); port(data:inout std_logic_vector(7 downto 0) :="zzzzzzzz"; address:out std_logic_vector(7 downt 0) := " "; run,io_dav:in std_logic; rdio,write:out std_logic; rdmr,csmr:out std_logic); end URISC; URISC 处理器的构造体框架 architecture behavior of URISC is signal pc1,r,r_not, bus_a,bus_b,mdr1,mdr:std_logic_vector (7 downto 0); signal pc:std_logic_vector(7 downto 0): =" "; signal,zero,zin,n,ph1,ph2,r_in,mdr_in,n_in,mar_in,c_in,clk:std_logic; signal clear,comp,mdr_out,pc_in,pc_out,zend,nnend,read,z_out, n_out:std_logic; singal c:std_logic_vector(3 down to ); procedure add (a,b:in std_logic_vector;cin:in std_logic; sum:out std_logic_vector;z_out,n_out:out std_logic)is variable sumv,av,bv:std_logic_vector(a'length-1 downto 0); variable carry:std_logic; av :=a; bv :=b; carry :=cin; for i in 0 to sumv'high loop sumv(i):=av(i)xor bv(i)xor carry; 234

235 carry :=(av(i)and bv(i))or(av(i)and carry)or (bv(i)and carry); end loop; sum :=sumv; z_out<=not (sumv(0)or sumv(1)or sumv(2)or sumv(3)or sumv(4)or sumv(5)or sumv(6)or sumv(7)); n_out<=sumv(7); end add; 程序计数器 PC pc_reg:block(pc_in='1'and ph2='0'and ph2'event) pc1<=guarded bus_b after reg_del; bus_a<=pc1 after enable_del when pc_out='1' else "zzzzzzzz"after disbl_del; end block pc_reg; 寄存器 r r_reg:block(r_in='1'and ph2='0'and ph2'event) r<=guarded bus_a after reg_del; r_not<=not r when comp='1'after inv_del else "zzzzzzzz"after inv_del; end block r_reg; 加法器 add(bus_a,r_not,c_in,bus_b,z_out,n_out)after add_del; 寄存器 n n_reg:block(n_in='1'and ph2='0'and ph2'event) n<=guarded n_out after reg_del else 'z'after reg_del; end block n_reg; 寄存器 z z_reg:block(z_in='1'and ph2='0'and ph2'event) z<=guarded z_out after reg_del else 'z'after reg_del; end block z_reg; 数据寄存器 MDR mdr_reg:block(mdr_in='1'and ph2='0'and ph2'event)or (read and ph1='0'and ph1'event)

236 end block mdr_reg; 地址寄存器 MAR mar_reg:block(mar_in='1'and ph2='0'and ph2'event) end block mar_reg; 内部两相时钟 process end process; 控制单元 process end process; end behavior; 实体定义中大部分类属参数是 URISC 中要用到的延时参数 例如 enable_del 是 data_bus 的使能信号的延时,per 是时钟周期 信号 data 是连接 RAM 和输入端口的外部数据总线, 信号 address 是连接 RAM 的外部地址总线 构造体说明中包含有用于 URISC 处理器内部的信号, 有些内部信号已为读者所熟悉, 例如 comp,zend 等 ; 另外还有一些新定义的信号 构造体对每个寄存器都用一个模块 (block) 表示, 对于 URISC 中的每个组合逻辑单元也用一个模块表示, 控制单元单独是一个模块 加法器模块中使用了过程 add, 该函数计算两个二进制数的加法, 并带有进位输入端 除了生成运算结果之外, 过程 add 还判断和数是否为 0( 通过求 8 位的或非 ) 以及和数是否是负数 ( 最高位为 1) 每个寄存器都设计成负边沿触发的寄存器 例如在例 5-18 中的程序计数器 PC, 它由一个受保护模块描述其保护条件, 在时钟 ph2 由 '1' 变为 '0' 时, 保护条件由 pcin 激活 在保护条件为真时, 总线 bus_b 上的数值加载到程序计数器 PC 当 pcout 为 '1' 时, 程序计数器 PC 的值送上总线 bus_a 送上 bus_a 的值, 不受保护条件的影响 如果信号的值 pcout 不为 '1', 则程序计数器 PC 的输出为高阻态 其他寄存器的设计方法与 PC 类似 URISC 处理器中的微代码控制器 由于 URISC 处理器的指令执行过程只有两个分支点, 且两个分支点的转移入口全为控制状态 0, 所以微代码控制器的地址生成逻辑电路十分简单, 可以用一个九进制计数器实现地址生成 计数器应该具备同步复位功能, 以便在满足分支条件时返回状态 0 如果不满足分支条件, 则计数器会从 0 计到 8 且会一直循环下去 如果有一个分支条件满足, 则计数器复位为 0, 即控制返回状态 0 下面是这一控制器的 VHDL 模型 这一模型可以直 236

237 接转换成图 5-12 所示的硬件结构 例 5-19 微代码控制器的 VHDL 模型 计数器 counter the counter with synchronous clear counter:block (ph1='0'and ph1'event) c<=guarded "0000" after count_del when clear='1'else c+"0001"after count_del; end block counter; 微代码只读存储器 ROM ROM:process(c) type:sq_array is array(0 to 8,0 to 8)of std_logic; constant mem:sq_array:= --mdrout marin nin rin pcin zend cin write nnend (('1', '1', '0', '0', '0', '0', '0', '0', '0'), ('1', '1', '0', '0', '0', '0', '0', '0', '0'), ('1', '0', '0', '1', '0', '0', '0', '0', '0'), ('0', '1', '0', '0', '1', '0', '1', '0', '0'), ('1', '1', '0', '0', '0', '0', '0', '0', '0'), ('1', '0', '1', '0', '0', '0', '1', '1', '0'), ('0', '1', '0', '0', '1', '0', '1', '0', '0'), ('0', '0', '0', '0', '1', '0', '1', '0', '1'), ('1', '0', '0', '0', '1', '0', '0', '0', '0')); mdr_out<=mem(intval(c),0)after ROM_del; mar_in<=mem(intval(c),1)after ROM_del; n_in<=mem(intval(c),2)after ROM_del; r_in<=mem(intval(c),3)after ROM_del; pc_in<=mem(intval(c),4)after ROM_del; zend<=mem(intval(c),5)after ROM_del; c_in<=mem(intval(c),6)after ROM_del; write<=mem(intval(c),7)after ROM_del; nnend<=mem(intval(c),8)after ROM_del; end process ROM; logic:block z_in<=zend; zero<=nor_std_logics(bus_b)after or_del; 237

238 clear<=(z and zend)or (not n and nnend)or(c="1000") after and_del+or_del; pc_out<=not mdr_out after inv_del; read<=mar_in; comp<=n_in; mdr_in<=n_in; rdmr<=read and not (address(7))after and_del; rdio<=read and (address(7))after and_del; csmr<=not (address(7))after inv_del; end block logic; 其中,nor_std_logics(bus_b) 表示对 bus_b 上的各位信号取或非运算 返回状态 0 的条件,clear 为 '1' 的第 1 个条件是信号 z 和信号 zend 的与为 '1', 其意义是在状态 0 时检测到程序计数器 PC 的值为 '0', 这时计数器 counter 应该复位为 '0', 控制在状态 0 下无限循环, 以实现 URISC 处理器的动态挂起 clear 为 '1' 的第 2 个条件是 not n and nnend, 用来指明减法运算得到非负结果, 计数器应该在状态 7 时终止, 这时应该将计数器 counter 复位为 0, 返回状态 0 去执行程序存储器中的下一条指令 反之, 如果 n 为 '1', 则应该进入状态 8, 为程序计数器加载跳转地址, 经过状态 8 后, 计数器也会复位为 '0', 这是 clear 为 '1' 的第 3 个条件, 即 c= 1000, 转移到跳转地址后, 应该执行跳转地址的下一条指令 对于 clear 为 '0' 的其他所有条件,counter 全增加 1, 控制进入下一个状态 计数器 counter 的输出是状态信号, 是只读存储器 ROM 的地址输入 每当 counter 的值发生变化时, 各控制信号的数值从 ROM 中被读出 在这个设计方案中, 控制信号是从 ROM 中直接读出, 而不是像图 5-12 中那样, 把 ROM 中的数据加载到指令寄存器 MIR 只有当 ROM 的字长足够长时, 才可能使用这样的方案, 一般情况下, 应该使用指令寄存器 为了减小 ROM 的字长, 可以研究各控制信号的相关性 通过分析各控制信号出现的时刻可以看出, 各控制信号并不独立 例如, 控制信号 zin 和 zend 总是同时出现, 因此可以用 ROM 中的同一位数据表示这两个控制信号 例 5-19 的模块 logic 中还指出了其他控制信号的相关性 URISC 处理器的硬连线控制器 硬连线控制器设计远没有微代码控制器那么规则, 它要根据需要专门设计 例 5-20 给出了一种硬连线控制器的寄存器级模型 更规则的方法应该对每个控制状态使用单独的一个触发器 例 5-20 URISC 的硬连线式控制单元的 VHDL 模型 计数器 counter the counter has a synchronous clear counter:block(ph1='0'and ph1'event) c<=guarded"0000"after count_del when (clear='1'or c="1000") 238

239 else inc_counter(c)after count_del; end block counter; 硬件连接的控制单元 译码器 第 1 级译码 st0<=not c(2)and not c(1)and not c(0) after and_del; st1<=not c(2) and not c(1)and c(0) after and_del; st2<=not c(2) and c(1)and not c(0)after and_del; st3<=not c(2) and c(1)and c(0)after and_del; st4<= c(2) and not c(1)and not c(0)after and_del; st5<= c(2) and not c(1)and c(0)after and_del; st6<= c(2) and c(1)and not c(0)after and_del; st7<= c(2) and c(1)and c(0)after and_del; 第 2 级译码 st07<=st0 or st7 after or_del: st25<=st2 or st5 after or_del: st36<=st3 or st6 after or_del: st57<=st5 or st7 after or_del: st73<=st7 or c(3) after or_del: 各控制信号 pc_out<=(st07 or st36)and not c(3) after (or_del+and_del); c_in<=st36 or st57 after or_del; pc_in<=st36 or st73 after or_del; mar_in<=not (st25 or st73)after (or_del+inv_del); mdr_out<=not pc_out after inv_del; read<=mar_in; comp<=st5; n_in<=st5; mdr_in<=st5; write<=st5; r_in<=st2; zend<=st0; nnend<=st7; 计数器和各寄存器控制信号 zero<=nor_std_logics(bus_b)after or_del; clear<=(z and zend)or (not n and nnend)after and_del+or_del; rdmr<=read and not (address(7))after and_del; rdio<=read and (address(7))after and_del; csmr<=not (address(7))after inv_del; 分析上面的代码, 可以看到, 实际上是用计数器实现了有限状态机, 这是常用的设计 239

240 技巧 状态信号 stn 是对计数器输出译码得到的, 而控制信号则是通过对状态信号译码形成的 这种风格的代码几乎是直接描述电路, 因此在电路相对简单或者设计者具备该电路的特殊设计知识时具有较好的综合效果 否则, 就应该使用有限状态机式的描述风格 240

241 第 6 章数字系统的高层次综合 集成电路制造技术的快速发展, 一方面促进了相应设计技术的发展, 一方面也对设计技术提出了更高的要求 今天的集成电路设计, 面临着功能强 性能好 规模大 成本低 设计周期短等一系列要求和挑战, 这些要求和挑战引起了在集成电路设计方法上的全面革新 当今, 以行为设计为主要标志的新一代系统设计理论已经形成并得到发展 新一代的集成电路设计方法在系统描述 电路生成 功能验证等诸多方面与传统的设计方法有着相当大的不同 高层次综合理论和技术就是其中一个重要的 具有代表性的内容 高层次综合和 VHDL 有着密切的联系 高层次综合是面向系统的综合方法,VHDL 是描述电路的一种硬件描述语言 首先, 高层次综合的对象从抽象意义上说, 是一个系统的行为, 但具体地说, 这个系统行为往往是采用 VHDL 等硬件描述语言进行表达的 ; 其次, 对高层次综合得到的结果, 仍然需要用硬件描述语言 ( 如 VHDL) 来进行描述 所以说, 高层次综合接受的是硬件描述语言, 得到也是硬件描述语言 高层次综合在技术上是基于硬件描述语言的, 只不过前后两个硬件描述语言是在不同的层次上对系统进行的描述 本章将向读者系统地介绍高层次综合的基本理论和基本方法, 使读者对这一新的集成电路设计方法有一个全面的了解和初步的掌握, 为研究 应用高层次综合理论和方法打下基础 6.1 数字系统高层次综合概述 高层次综合的概念 综合技术的研究可以追溯到 20 世纪 60 年代,IBM 公司 T.J.Watson 研究中心开发的 ALERT 系统, 将寄存器传输级算法描述转化成逻辑级的结构实现 ;20 世纪 70 年代, 综合技术发展迅速, 但主要致力于较低层次的逻辑综合和版图综合 ;20 世纪 80 年代中期, 专用集成电路的广泛应用要求芯片设计大规模 高性能 短周期, 大大推动了从算法级设计描述向寄存器传输级设计描述转换的高层次综合技术 数字系统可以在多个层次上进行描述, 这些层次由高到低可以分为算法层 寄存器传输层 逻辑层 电路层和版图层, 在每个层次上又有不同领域的描述, 分别为行为领域描述 结构领域描述和物理领域描述 所以, 将较高层次的描述转化为较低层次的描述的综合可以在不同的层次上进行 通常, 综合分为 3 个层次 : 高层次综合 (High-Level Synthesis) 逻辑综合 (Logic Synthesis) 版图综合 (Layout Synthesis) 版图综合负责将系统电路层的结构描述转化为版图层的物理描述 ; 逻辑综合负责将系 240

242 统寄存器传输层的结构描述转化为逻辑层的结构描述以及将逻辑层的结构描述转化为电路层的结构描述 ; 高层次综合负责将系统算法层的行为描述转化为寄存器传输层的结构描述 数字系统各个层次的描述和综合的关系如图 6-1 所示 图 6-1 数字系统各个层次的描述和综合的关系具体地说, 所谓数字系统的高层次综合, 就是给定数字系统的算法级行为描述 约束条件和目标集合, 在目标集合中找出一个满足约束条件 实现系统的行为的结构 这里的 行为 是指系统和外界的作用和联系, 如从输入到输出的映射关系 ; 这里的 结构 是指组成系统的部件和它们之间的连接关系 高层次综合的意义 在了解了高层次综合的概念之后, 接着会有这样的问题 : 为什么要进行高层次综合? 高层次综合对芯片设计有何意义? 第一, 随着芯片规模的扩大, 设计者不可能给出电路的低层描述, 在以往的寄存器传输层上给出描述也是相当困难的 一般来说, 对一个大规模的设计, 设计者只能给出系统的算法级行为描述, 而算法级行为描述不能直接被用于逻辑综合, 必须先将它转化为寄存器传输层的结构描述, 这个转化过程就是高层次综合, 所以高层次综合是一个系统从行为设计到具体实现的综合过程中的首要环节 第二, 对于一个系统的行为描述, 有很多种实现它的方案 这些方案具有不同的面积 速度等性能指标 例如, 为了实现 4 个数相加 Y=A+B+C+D, 可以有以下 3 种方案 : 方案 (1): 用 3 个加法器, 一步完成 4 个数的相加 如图 6-2 所示 方案 (2): 用 2 个加法器和 2 个寄存器, 两步完成 4 个数的相加 如图 6-3 所示 241

243 图 6-2 实现 Y=A+B+C+D 的第 1 种方案 1 A B Adder1 REG1 C D Adder2 REG2 2 REG1 Adder1 Y REG2 图 6-3 实现 Y=A+B+C+D 的第 2 种方案 方案 (3): 用 1 个加法器和 1 个寄存器, 三步完成 4 个数的相加 如图 6-4 所示 1 A B Adder1 Adder1 REG1 2 REG1 REG1 Adder1 Adder1 REG1 C 3 REG1 Adder1 Adder1 REG1 D 图 6-4 实现 Y=A+B+C+D 的第 3 种方案这 3 种方案所用的资源 步骤是不同的, 因而这 3 种实现方案导致的芯片面积 速度等性能指标也是不同的 高层次综合就是要搜索各种可能的实现方案, 在满足一定约束条件 ( 对资源 速度的要求 ) 的情况下, 将一个算法行为在资源 速度等方面进行优化, 找出一个最佳或相对较佳的实现方案 所以说, 高层次综合有利于实现系统的最优化结构 第三, 高层次综合的意义还在于在对系统实现方案的规划过程中, 可对各种方案的资源 速度等方面的特性作出评估, 使得设计者在设计初期就可以了解所设计的芯片在面积 242

244 速度等方面的大致性能, 可以及时对设计进行方案 结构上的修改, 令其满足系统的预期设计要求, 减少和避免了在设计后期 ( 逻辑层 电路层 版图层 ) 的设计回溯 因为算法层的行为描述一般只是描述系统的行为功能, 至于实现这个行为功能需要多少资源, 有多快的速度, 设计者无从得知 而经过高层次综合后的寄存器传输层的结构描述, 隐含了系统用到了多少资源, 完成整个功能需要多少步等信息, 设计者能很容易地估计出该设计方案所花费的资源 时间等方面的代价 高层次综合的主要内容 高层次综合的内容包括编译与转换 算子调度 资源分配 寄存器分配 连线网络生成 控制器与控制码生成等 6 部分内容 这里先简单介绍这 6 部分内容, 在后面的各节再分别详细介绍 1. 编译与转换高层次综合的出发点是系统的算法层行为描述, 往往体现为硬件描述语言的形式 它不含任何的结构信息, 而仅包含代数或逻辑运算符号 ( 例如赋值 加 减 乘 除 逻辑与 逻辑或等 ) 和控制语句 ( 例如循环 条件选择等 ) 高层次综合的第 1 步就是对描述系统算法行为的硬件描述语言进行编译, 将其转换为一种有利于进行高层次综合的中间表示格式 这种中间表示格式表示了系统中各数据 操作以及控制之间的相关性 这些关系确定了系统的基本结构信息, 是以后各步工作的基础 表示数据与操作相关性的中间表示格式称为数据流图 (Data Flow Graph 或 DFG), 表示对操作的控制的相关性的中间表示格式称为控制流图 (Control Flow Graph 或 CFG) 通常可以由一张图体现数据和控制相关性, 称为控制 / 数据流图 (Control/Data Flow Graph 或 CDFG) 编译与转换的任务就是将系统算法行为描述由语言形式变为流图形式 2. 算子调度一个系统要对输入数据按一定的先后次序进行一系列的操作或运算, 这些操作或运算称为算子 例如对于系统中的一次加法运算, 称之为一个加法算子 系统中的算子在满足数据 控制相关性的条件下, 它们在整个算法的执行过程中的位置是可变的 也就是说, 如果整个算法的执行分为若干个控制步, 一些算子所属的控制步是可变的 在某个方案中, 一个算子可以放在某一个控制步中执行 ; 而在另一个方案中, 这个算子可放在另一个控制步中执行 虽然各个方案不会影响整个算法的实现, 但会影响到所需资源和所需控制步的多少 例如, 如果将 2 个加法算子放在同一个控制步中执行, 那么就需要 2 个加法器 ; 如果将这 2 个加法算子放在 2 个控制步中分别执行, 那么只需要 1 个加法器,2 个加法算子用同一个加法器分 2 步完成 算子调度的任务就是将完成整个算法所用的算子分配到各个控制步, 实现满足约束条件的最优或较优的算子调度方案 3. 资源分配广义的资源包括所有的硬件结构, 如运算单元 存储单元 连线网络等 在本书的论述中将上述 3 种硬件结构分立开来, 所谓资源特指运算单元 运算单元与某种算子类型对应 例如, 加法算子的执行采用的是加法运算单元 在整个算法的实现过程中, 一些在不 243

245 同控制步中的同类型的算子往往可以用同一个运算单元执行, 称为资源的复用 资源分配的任务就是为每个算子指定它的运算单元, 或者从另一个角度说, 就是将运算单元分配给一些不冲突的同类型算子, 以达到最优的资源代价 4. 寄存器分配算法实现过程中需要保留的数据存放在寄存器中 一个数据从被保留到最后一次被利用的这段时期称为数据的生命期 显然, 并不需要为每个要保留的数据分配一个寄存器, 因为几个生命期不重叠的数据可以用同一个寄存器来保存, 称为寄存器复用 寄存器分配的任务就是为每个在算法实现过程中需要保留的数据指定存放它的寄存器, 或者从另一个角度说, 就是将寄存器分配给一些不冲突的数据 5. 连线网络生成这里说的连线网络指的是对各个资源, 即运算单元输入输出的控制 被复用的运算单元的输入有多个来源, 这些信号均要连接到该运算单元上, 所以, 要在一定的控制步中从该运算单元的多个输入来源中选出当时那个操作的操作数 ; 被复用的寄存器将保存多个运算单元的运算结果, 多个运算单元的输出信号均要连接到该寄存器上, 所以, 要在一定的控制步中从多个运算单元的输出中选出当时那个要保存的结果 这些外部到资源输入端的连线和选择控制以及资源输出端到外部的连线和选择控制就称为连线网络 连线网络生成的任务就是按照资源分配方案和寄存器分配方案, 通过多路选择器将输入信号 输出信号 寄存器和资源连接起来 6. 控制器设计 控制码生成与优化系统如何按照一定的控制步, 执行各个操作, 完成整个算法, 需要由一个控制器电路来控制 控制器通过控制码来控制数据通道的运作, 包括为每个运算单元选择操作数, 为每个寄存器选择需保存的结果等 控制器自身根据外部输入 ( 如时钟 ) 和数据通道的反馈信号来实现自身的运行 为减少控制码的长度和数量, 往往要对控制码进行优化 这部分的任务就是按照算子调度方案 连线网络方案, 制定每个控制步的控制码并进行优化, 并给出一个控制电路, 实现各个控制步的转换, 以及在每个控制步里向数据通道输出相应的控制码 高层次综合的流程 高层次综合的任务是从一个行为描述出发生成 RTL 级描述 将上面介绍的高层次综合的主要内容有序地组织起来就得到了高层次综合的流程 图 6-5 是高层次综合流程的图示 流程的第 1 步就是将行为描述转化成易于高层次综合的表示形式 控制数据流图 CDFG(Control & Data Flow Graph) 将行为描述转变为 CDFG 的另一个原因是它更为直观, 更易解释相应的原理 算子调度 (scheduling) 将操作安排在特定的时间间隔内 由于数据间的依赖关系, 一个操作可能被安排的位置是有限的 但对于一个复杂的算法行为来说, 由于操作的数量很大, 所以可能的调度方案数目也是很大的 算子调度的原则是在时间约束条件下最大限度地提高资源的复用率, 或在资源约束条件下最大限度地优化时间特性 244

246 图 6-5 高层次综合的流程资源分配 (allocation) 是将调度安排好的操作中的运算符 ( 算子 ) 指定到实际物理存在的资源上, 实现算子到资源的映射 显然, 资源分配过程对系统的时间特性和资源代价并没有任何优化作用, 它的目标是使这一映射过程尽可能导致最简的连线结构 由于在深亚微米电路中, 互连延迟已经接近或超过单元延迟, 所以获得最简连线结构以减小连线延迟对提高系统性能是非常重要的 资源分配的另一个重要内容是将描述中的变量或信号用物理上的寄存器来实现, 所以有时这一部分常被独立出来, 称为寄存器分配, 同时将原来广义的资源分配特指为算子资源的分配 寄存器分配的目标就是要达到寄存器最大限度的复用 寄存器分配不仅可以减少寄存器的数量, 而且会影响到未来连线网络的生成 资源分配实际上决定了算子资源和寄存器之间的连线形式, 而连线网络生成就是将这种连线形式具体化 连线网络生成的原则是产生最简的控制码, 控制码的繁简直接影响到控制器的复杂性 连线网络的生成标志着数据通道设计的完成 控制码生成包括从数据通道中提取控制码表和控制码优化两部分 控制码的繁简直接影响到控制器的复杂性 控制器的设计实际上是一个典型的有限状态机的设计 根据前面的算子调度方案可以得到各状态间的转换关系, 根据前面生成并经过优化的控制码表可以得到各状态下的输出, 最终可以用一个有限状态机来实现控制器 高层次综合的流程既包括了前面介绍的高层次综合的主要内容, 更强调了各部分工作的次序 一般来说, 在进行高层次综合过程中应该遵循这个流程 本章的下面几节就将按照这一流程介绍每一个步骤, 阐述其中有关概念 理论和方法 245

247 6.2 高层次综合的准备工作 系统的算法级设计 从严格意义上来说, 系统的算法级设计并不属于高层次综合的范畴 它不是高层次综合研究的内容, 但却是进行高层次综合的对象 换句话说, 如果把高层次综合过程看做一个黑盒子的话, 在算法级层次上完成的设计就是这个黑盒子的输入 高层次综合只能对一个已经确定的算法进行各个方面的调度 分配 优化等工作, 只能在算法实现所花费的代价上 ( 包括资源代价 时间代价等 ) 达到最佳或相对较佳, 而对算法本身没有任何影响 因此, 高层次综合只是对某种已确定的算法进行的综合 优化过程, 而算法本身也存在一个优化的问题 虽然一个系统的算法级设计与高层次综合在内容上是独立的, 但算法作为高层次综合的起点, 对最后的综合结果往往有着至关重要的影响 一个算法的确立, 实际上已经隐含地确定了系统某些性能的大致范围, 对设计者来说, 也隐含地确定了进行后续设计的设计空间或优化改进的余地 因此, 要想获得一个好的系统实现方案, 首先要确定一个好的算法, 再以此为起点, 通过高层次综合得到一个优化的实现方案 这里简单地举一个例子来说明算法对电路的影响 例如要设计一个硬件电路求解一元二次方程 Ax + Bx + C = 0 根据一元二次方程的求 解公式, 当 B 2 4AC 0 时, 2 x = ( B± B 4AC )/ 2 A, 否则方程无解 1,2 首先设计电路的输入输出 电路的输入包括系数 A B C 以及电路时钟 CLOCK, 输出包括表示方程有解无解的标志 STATE, 以及在方程有解情况下解的输出 x 1 x 2 如图 6-6 所示 2 2 B 4AC 0 0 Xx 1,2 1,2 B ± = 2 B 4AC 2A A B C clock CLOCK 一元二次方程求解电路 X1 x 1 X2 x 2 STATE clock 图 6-6 一元二次方程求解电路的黑盒子示意图 根据求解公式, 可以得到如图 6-7 所示的硬件算法流图 246

248 图 6-7 求解一元二次方程的硬件算法流图请注意, 这里的算法不同于一般意义上的 纯软件 的算法 在 纯软件 的算法表示中, 变量数目是不受限制的, 其中的表达式只代表抽象的数学关系 而这里的算法称为 硬件 算法, 因为算法的表述具有硬件的意义 例如, 一个变量对应于一个硬件上信号线或寄存器, 一个算术运算符代表一个运算电路, 所以在图 6-7 的算法表述中除了输入输出信号外只用了 2 个变量 R 1 和 R 2, 在各步中复用了这 2 个变量, 这就意味着在硬件上只需要 2 个寄存器来存放中间结果 另外, 从算法中也可以看出, 求解过程包含加 减 乘 除等运算, 在电路上也要有相应的运算电路, 整个求解过程中乘法用了 3 次, 除法用了 4 次 乘除运算要比加减运算复杂得多, 乘除运算花费的时间也比加减运算要长 为了优化系统性能, 可以在算法上进行改进, 减少乘除运算的次数, 或者将乘除运算转变为加减运算, 以降低系统复杂度, 提高系统运行速度 在图 6-7 所示的算法基础上进行修改, 得到如图 6-8 所示的硬件算法流图 在图 6-8 所示的改进后的算法中, 只用到了 2 次乘法运算和 2 次除法运算, 比改进前减少 1 次乘法和 2 次除法, 同时增加了 3 次加法, 但加法的代价比乘除法要小得多, 所以系统的整体性能得到了改善 从上面的简单例子可以看出,2 个不同的算法表述, 虽然在功能上是等同的, 但它们蕴涵的硬件信息是不同的, 隐含的实现代价也是不同的 算法上的改进和优化能够直接导致电路的改进和优化, 而高层次综合是无法对算法进行优化的 因此, 在高层次综合之前, 应该力求确定一个较优的算法 在上述一元二次的求解过程中, 还涉及了一个开方运算 如果再更进一步研究, 这个开方运算也有多种算法, 例如查表法 牛顿叠代法等, 这些算法也会导致不同的电路代价 这里对此不再赘述, 有兴趣的读者可自行研究 247

249 图 6-8 求解一元二次方程的另一种硬件算法流图 内部表示转化 在完成系统的算法级设计以后, 就要对这个确定的算法进行高层次综合了 首先, 这个算法需要有一个具体的存在形式 设计者最初往往用具有行为描述功能的硬件描述语言 ( 如 VHDL) 来描述系统的算法行为 但这种表示方法对高层次综合过程来说并不方便 直观 所以, 在高层次综合之前, 要先将算法描述转化为易于高层次综合的内部表示形式 高层次综合面向的是一个算法, 而一个算法具有两个要素 : 数据和对数据的控制 因此最能表达一个算法实质的就是其中数据和控制所构成的关系 高层次综合的内部表示就采用了这种 数据 + 控制 的表示形式, 称为数据控制流图 CDFG(Control & Data Flow Graph) 所谓的数据控制流图可以表述为一个有向图 G=(V,E), 其中 V 为顶点集合,E 为有向边集合 顶点集合中的每一个顶点代表算法中的一个操作, 一个外部数据, 或一个寄存器变量 ; 连接顶点 v(i) 到顶点 v(j) 的有向边 e(i,j) 代表了顶点 v(j) 对顶点 v(i) 的依赖关系, 或者说顶点 v(i) 对顶点 v(j) 的控制关系 这种控制 ( 依赖 ) 关系包括数据和操作之间的关系, 如一个操作的需要的数据来自一个外部数据或一个寄存器 ; 这种控制 ( 依赖 ) 关系还包括操作和操作之间的关系, 如一个操作需要的输入来自另一个操作的输出, 而这种操作和操作之间的关系有时也隐含着数据与操作之间的关系, 因为一个操作的输入和另一个操作的输出是通过数据联结的 在上述规定下, 从描述电路算法的硬件描述语言中可以得到电路的控制数据流图, 这种表示形式完全 简练地表示了算法行为, 有利于高层次综合的处理过程 作为一个简单的例子, 图 6-9 表示了一段 VHDL 算法描述所对应的控制数据流图 248

250 图 6-9 从描述中得到的控制数据流图 确定约束条件 在确定了系统的算法行为, 并将这种算法行为转化为易于高层次综合过程的表示形式 控制数据流图后, 还要在进行高层次综合之前确定对高层次综合结果的约束条件 高层次综合的约束条件是对高层次综合提出的的目标和限制 在高层次综合过程中, 必须在约束条件的指导下, 在满足约束条件的范围里选择方案 约束条件实际上给出了对最终方案的要求, 缩小了设计空间的范围 约束条件是根据所设计系统的具体背景 情况 环境提出的, 主要是对一些电路参数指标给出上界或者下界 约束条件包括对时间的约束和对代价的约束 对时间的约束主要指电路完成操作所允许的最大延迟等 ; 对代价的约束主要是指对电路中资源种类 数量的限制, 以及所允许的电路的最大面积等 每一种约束条件可以是对局部提出的约束, 例如对电路中某个重要的功能模块, 也可以是对整个电路提出的约束 一般来说, 设计者在做系统设计前, 都应该了解将要设计的电路的指标要求 设计者应该根据这些指标要求确定高层次综合的约束条件, 使高层次综合在这些约束条件下进行 这样, 一方面可以缩小设计空间的搜索范围, 提高高层次综合的效率, 另一方面也可以保证高层次综合的结果满足事先提出的要求 249

251 6.3 算子调度 算子调度的基本概念 算子调度是数据通道设计中至关重要的一步, 其主要任务是将描述中的每一个操作都安排到一个时间间隔内完成 根据不同的设计要求, 算子安排有不同的准则 例如在给定资源限制的条件下以最优的时间特性为准则, 或者在给定时间限制的条件下以最小的资源特性为准则等等 在介绍算子调度的各种算法之前, 先引入一些算子调度中涉及的基本概念 1. 控制步从外部看一个算法行为, 通常认为从输入到输出的运算是一步完成的 在高层次综合中, 为了达到资源复用的目的, 在实际的实现方案中将这一过程再划分为若干个更小的步骤, 各个步骤在时间上是不重叠的, 这就使得算法中同类型的操作如果被安排在两个不重叠的步骤中就可以复用同一个资源 将整个算法过程划分成的若干个步骤称为控制步 (Control Step), 完成整个算法所需的控制步数记为 N cs 2. 关键路径从前面的讨论可知, 当给定一个描述之后, 可以获得一个控制数据流图 G 由于 G 是一个有向图, 所以从起点到终点一定存在一条最长路径, 所谓最长, 就是指这条路径上的操作节点最多, 这条路径就称为关键路径 (Critical Path) 显然, 关键路径决定了系统的时间特性 关键路径上的操作节点间存在严格的前后依赖关系, 所以不能放在同一控制步中, 每个关键路径上的操作必然占据不同的控制步 由此可知, 完成一个算法的控制步数 N cs 至少应该是关键路径上的操作节点数 这就给出了一个重要的提示 : 如果知道了关键路径的长度, 也就知道了整个系统所需要的最少的控制步数 当不考虑系统的资源约束时, 关键路径长度 ( 控制步数 ) 实际上给出了系统最优时间特性的一个度量 3. 依赖关系当控制步的数目确定后, 算子调度的任务就是将每个操作都安排到一个控制步中去 但是数据间的依赖关系限制了这种安排的任意性, 并非可以任意地将一个操作安排到任何一个控制步中去 例如, 操作 b 的操作对象是操作 a 的操作结果, 那么操作 b 所在的控制步一定要位于操作 a 所在的控制步之后 如果两个操作 a b 在执行时间上有相对先后的要求, 那么称操作 a 和操作 b 之间存在依赖关系 如果操作 a 必须先于操作 b( 或者说操作 b 必须后于操作 a), 那么称操作 a 对于操作 b 具有后向依赖关系, 操作 b 对于操作 a 具有前向依赖关系 ASAP 和 ALAP 调度与时间特性评估 ASAP(As Soon As Possible) 通常被称为 尽可能早 的调度算法,ALAP(As Late As Possible) 通常被称为 尽可能迟 的调度算法 这两种调度算法是最基本 最简单的调度 250

252 算法 但由这两种调度算法得到的算子调度方案并不用于最终实现, 它们主要是用来对系统的时间特性进行评估, 指导设计优化的方向, 为下面的综合处理做准备 尽可能早 (ASAP) 的调度算法不考虑任何资源的限制而试图寻找时间特性最好的方案 尽可能早 意味着尽量早地执行每一个操作 它可以表述为 : 从描述的第 1 个操作开始, 依次将每个操作安排到一定的控制步 ; 对于每个待安排的操作 i, 在已经安排好了的操作中, 考察所有使 i 对其有前向依赖关系的操作所在的控制步, 从中选择一个最为靠后的控制步, 将操作 i 安排在该控制步之后相临的控制步中 ; 如果不存在使 i 对其有前向依赖关系的操作, 则将操作 i 放入第 1 个控制步中 尽可能迟 (ALAP) 的调度算法也不考虑任何资源的限制而试图寻找时间特性最优的方案 与 尽可能早 的算法不同, 尽可能迟 意味着尽量晚地执行每一个操作 它可以表述为 : 从描述的最后一个操作开始, 依次将每个操作安排到一定的控制步 ; 对于每个待安排的操作 i, 在已经安排好了的操作中, 考察所有使 i 对其有后向依赖关系的操作所在的控制步, 从中选择一个最为靠前的控制步, 将操作 i 安排在该控制步之前相临的控制步中 ; 如果不存在使 i 对其有后向依赖关系的操作, 则将操作 i 放入最后一个控制步中 图 6-10 给出了对一个控制数据流图分别按照 ASAP 调度和 ALAP 调度得到的调度方案 算子上的数字表示对算子的编号, 也可以用算子编号直接代表算子 从图 6-10 不难看出 : ASAP ALAP 调度方案对一个描述来说是惟一的 ; 它给出了最优的时间特性 ; 它给出了关键路径 如前所述,ASAP 和 ALAP 给出的调度方案并不是真正实用化的方案 因为尽可能地将操作执行的时间提前或推迟必然会使资源的复用性变差 在 ASAP 和 ALAP 的算法中也看到, 这两个算法并没有考虑任何资源的限制, 而算子调度的目的是最大限度地复用资源, 所以 ASAP 和 ALAP 调度算法并不符合算子调度的初衷 但是, 从这 2 种调度方案中可以得到关于算子的一个重要特性 : 算子的机动性 因为 ASAP 和 ALAP 调度给出了算子所能在的控制步的最前和最后的极限位置, 算子在任意一种调度方案中所在的控制步只能在这两个位置之间变动, 这种变动的范围就体现为算子机动性的概念 关于算子机动性的准确定义如下 : 设 v 为一个操作,CS ASAP (v) 为操作 v 在 ASAP 调度中所在控制步的序号,CS ALAP (v) 为操作 v 在 ALAP 调度中所在控制步的序号, 则算子 v 的机动性 M(v)= CS ALAP (v)- CS ASAP (v)+1 按照上述的定义, 可以得到关于算子机动性的物理意义 : 算子 v 的机动性 M(v) 表示算子 v 可以在 M(v) 个控制步中变动 图 6-11 给出了图 6-10 的例子中各算子的机动性及变动范围 从图 6-11 中易知, 处在关键路径上的算子的机动性为 1, 也就是说, 处在关键路径上的算子实际上已经被安排好了, 需要安排的仅是那些处在非关键路径上的算子 由于关键路径的长度决定了系统所需的控制步数, 如果希望进一步提高系统的时间特性, 只能回到系统的算法级设计上, 重新设计系统的算法 251

253 1 控制步 (b) ASAP 调度 (c) ALAP 调度 ( ) 调度 ( ) 调度 图 6-10 控制数据流图和 ASAP ALAP 调度方案 控制步 ASAP 调度 ALAP 调度算子机动性 ASAP 调度 ALAP 调度算子机动性 图 6-11 算子机动性 252

254 按照算子机动性的物理意义, 由组合中的乘法原则可以得到 : 所有可能的调度方案数不超过所有算子机动性的乘积 在大多数情况下, 所有可能的调度方案数达不到所有算子机动性的乘积 因为在实际的调度方案中, 还要考虑数据依赖关系的限制 例如, 如果操作 b 必须在操作 a 之后执行, 那么在算子 a 位置确定以后, 算子 b 的变动范围还要考虑到一定要在算子 a 之后, 这就会使得算子 b 的可能位置数小于它的机动性 M(b) 所以算子机动性的乘积只是给出了可能的调度方案数的上界, 大致地指出了设计空间的大小 综上所述, 可以从 ASAP 调度和 ALAP 调度中得出关于系统时间特性和设计空间的评估 从 ASAP 和 ALAP 调度得到的关键路径可以得知最少所需要的控制步数, 从而估计系统的最优时间特性 ; 另外, 从 ASAP 和 ALAP 调度中引出算子机动性的概念和计算方法, 各算子机动性的乘积代表了所有可能调度方案的上界, 从而可以大致估计设计空间的大小 表格调度算法 算子调度的任务是将每一个算子安排到一个控制步中去 算子调度实际上包括了两个步骤 : 首先要选出一个算子, 然后选出一个控制步将这个算子放进去 所以算子调度的优化问题也包括两方面 : 一是如何选择待安排的算子 ; 二是如何选择安排这个算子的控制步 因为每一个算子的安排都有赖于前面算子的安排, 所以算子被安排的次序直接影响调度结果 表格调度算法是最基本的算子调度方法之一 这个算法主要侧重于对算子调度中第 1 个问题的解决, 即研究如何确定算子被安排的次序 一旦算子被安排的次序确定以后, 在按照这个次序为每个算子选择控制步时, 基本上是按 尽可能早 的原则 当然, 还要同时考虑系统的约束条件 表格调度算法的核心是为每个算子确定一个优先级数, 按照优先级数从大到小的顺序确定算子被安排的次序 确定算子优先级的原则和方法有很多种 例如, 可以用算子机动性作为该算子的优先级数, 还可以用控制数据流图中算子节点到出口的路径长度作为该算子的优先级数等等 所以表格调度算法的实质内容就是给出一个算子的优先级函数, 使得按照这一优先级函数确定的算子安排次序能够导致较优的调度方案 下面举一个简单的例子说明不同的优先级函数导致不同的算子安排次序, 不同的算子安排次序导致不同的调度结果 假设数据控制流图如图 6-12 所示, 要求调度结果只使用 1 个乘法器和 1 个加法器 + * + * 约束条件 : 约束条件只有一个乘法器和一个加法器 : 调度结果只使用一个乘法器和一个加法器 + * 图 6-12 表格调度算法举例 第 1 种算法以算子出现的先后顺序作为算子的优先级函数, 即先出现的算子优先级 253

255 高, 先被安排 ; 后出现的算子优先级低, 后被安排 图 6-13 给出了算子安排次序和最后的调度结果 图中算子旁的数字既代表算子被安排的次序, 在调度结果中也代表算子本身 即第 i 个被安排的算子记为算子 i 控制数据流图 : 控制步控制步算子算子 + * + * 调度结果 * 图 6-13 表格调度算法举例 算法 1 算子调度的过程如下 : (1) 选择算子 1, 放入控制步 1; (2) 选择算子 2, 因为算子 2 不与其他算子存在前向依赖关系, 同时算子 2 为乘法算子, 而已经放入控制步 1 的算子 1 为加法算子, 所以算子 2 仍可放入控制步 1; (3) 选择算子 3, 由于算子 3 为加法算子, 而控制步 1 中已有一个加法算子 1, 按照资源约束条件, 算子 3 不能放入控制步 1 中, 又按照 尽可能早 的原则, 将算子 3 放入控制步 2; (4) 选择算子 4, 由于算子 4 为乘法算子, 而控制步 1 中已有一个乘法算子 2, 按照资源约束条件, 算子 4 不能放入控制步 1 中, 又按照 尽可能早 的原则, 将算子 4 放入控制步 2; (5) 选择算子 5, 由于算子 5 对算子 2 3 有前向依赖关系, 按照 尽可能早 的原则, 应将算子 5 安排在算子 2 3 中最靠后的控制步之后相临的控制步中, 即将算子 5 放入控制步 3; (6) 选择算子 6, 由于算子 6 对算子 4 5 有前向依赖关系, 按照 尽可能早 的原则, 应将算子 6 安排在算子 4 5 中最靠后的控制步之后相临的控制步中, 即将算子 6 放入控制步 4 第 2 种算法以算子节点到控制数据流图底端出口的路径长度作为算子的优先级函数, 即离出口路径长的算子优先级高, 被先安排 ; 离出口路径短的算子优先级低, 被后安排 图 6-14 给出了算子离出口的路径长度 ( 即优先级 ) 安排次序和最后的调度结果 图中算子旁括号内的数字为算子的优先级, 括号外的数字既代表算子被安排的次序, 在调度结果中也代表算子本身 注意, 图 6-14 和图 6-13 中的算子编号和实际算子的对应关系是不同的 算子调度的过程如下 : (1) 选择算子 1, 放入控制步 1; (2) 选择算子 2, 因为算子 2 不与其他算子存在前向依赖关系, 同时算子 2 为加法算子, 而已经放入控制步 1 的算子 1 为乘法算子, 所以算子 2 仍可放入控制步 1; 254

256 5(0) 1(2) 2(2) 3(1) 5(0) 1(2) 2(2) 3(1) 控制数据流图 : + * + * 4(1) 4(1) + 调度结果 : 控制步 算子算子 (0) * 图 6-14 表格调度算法举例 算法 2 (3) 选择算子 3, 由于算子 3 为乘法算子, 而控制步 1 中已有一个乘法算子 1, 按照资源约束条件, 算子 3 不能放入控制步 1 中, 又按照 尽可能早 的原则, 将算子 3 放入控制步 2; (4) 选择算子 4, 由于算子 4 对算子 1 2 有前向依赖关系, 按照 尽可能早 的原则, 应将算子 4 安排在算子 1 2 中最靠后的控制步之后相临的控制步中, 即控制步 2, 同时控制步 2 中已有的算子 3 为乘法算子, 而算子 4 为加法算子, 所以可将算子 4 放入控制步 2; (5) 选择算子 5, 算子 5 对其他算子没有前向依赖关系, 但控制步 1 2 中已有加法算子, 所以只能将算子 5 放入控制步 3; (6) 选择算子 6, 由于算子 6 对算子 3 4 有前向依赖关系, 按照 尽可能早 的原则, 应将算子 6 安排在算子 3 4 中最靠后的控制步之后相临的控制步中, 即控制步 3, 同时控制步 3 中已有的算子 5 为加法算子, 而算子 6 为乘法算子, 所以可将算子 6 放入控制步 3 从上面的例子可以看出, 第 1 种算法和第 2 种算法安排算子的次序不同, 它们的调度结果都用了一个乘法器和一个加法器, 但是第 2 种算法比第 1 种算法少用一个控制步 这就说明了算子被安排的次序导致调度结果优化程度的不同 如何确定算子优先级函数以提高最终调度结果的优化程度是一个比较复杂的问题, 优先级函数有简有繁, 其效果也有好有差 除了上例中的以算子节点到控制数据流图底端出口的路径长度作为算子的优先级以外, 另一种算子的优先级函数是根据算子的机动性确定的, 机动性小的算子优先级大, 先被安排, 机动性大的算子优先级小, 后被安排 这一算法的根据是 : 机动性小的算子由于可供选择的控制步少, 导致优化的可能性较小, 所以应先安排它们 ; 机动性大的算子由于可供选择的控制步多, 导致优化的可能性较大, 所以应靠后安排 这样, 处在关键路径上的算子总是先被安排, 因此系统的时间特性不会有恶化的可能 然后, 在对非关键路径上的算子进行安排时, 再从资源复用的角度对调度方案进行优化 表格调度算法还有很多其他的算子优先级函数, 例如根据算子被安排在某一控制步的概率确定优先级函数, 这里不做过深的介绍 综上所述, 表格调度算法的最大优点就是比较简单, 易于实现, 在大多数情况下都能得到较优的算子调度方案 但表格调度算法只关心算子安排次序的问题, 对整个优化过程考虑不全面, 所以并不能保证所得的结果是最优的 另外, 确定一个有效的算子优先级函数也比较困难 255

257 6.3.4 分枝与边界调度算法 分枝与边界调度算法是另一种算子调度的典型算法 这种算法的基本思想很简单, 就是利用 穷举 的方法, 列出每一个可能的算子调度方案, 从中选择一个最优的方案 通常是按照控制步的先后顺序, 先列出第 1 个控制步可能安排的各种算子组合, 然后对在第 1 个控制步下的每一种情况考虑第 2 个控制步的可能安排 以此类推 在列举的过程中, 一般每一步都存在多种算子安排的可能性, 以树形结构的分枝来表示这些可能的算子安排, 在这一 调度树 中, 每一条从根到叶的路径就代表一个可能的调度方案 这就是分枝与边界调度算法名称的由来 需要指出的是, 虽然这个算法的原则是给出所有可能的调度方案, 但实际上在具体操作时并不是列出所有的情况, 而是在中间分枝的时候, 按某种原则, 先去掉了一些明显不好的情况 例如, 可以采取使算子尽可能集中在同一控制步的原则, 来减少控制步数目 举例来说, 如果当前控制步可以安排的算子有 2 个, 那么就面临 3 种可能的情况 : 安排第 1 个算子或安排第 2 个算子或同时安排这两个算子 按照前面所说的算子尽可能集中的原则, 只要这两个算子不冲突, 可以安排在一个控制步, 因此就把它们安排在一个控制步, 而不再考虑单独安排一个算子的情况了 由于这一算法的基本原理较为简单, 下面以一个例子来加以演示和说明 图 6-15 中给出了由一个描述得到的数据控制流图以及算子之间的约束条件 下面用分枝与边界调度算法来对其进行算子调度 约束条件约束条件 : 2,7 2,7 不能在同一控制步中 3,7 3,7 不能在同一控制步中 5, 6 不能在同一控制步中 5, 图 6-15 分枝与边界调度算法举例从第 1 个控制步开始考虑, 因为 没有任何前向依赖关系, 所以均可以被安排在第 1 个控制步 按照在一个控制步中尽可能多地安排算子的原则, 又要考虑到 2 7 不能被安排在同一控制步的约束条件, 得到第 1 个控制步的两种可能安排 : 将 12 安排在第 1 个控制步, 或者将 17 安排在第 1 个控制步 在第 1 个控制步的安排上, 就得到了两个分枝 然后考虑第 2 个控制步的安排, 即在第 1 个控制步安排的每一种情况下, 再列出各个可能的安排 ; 以此类推, 考虑第 3 个控制步 第 4 个控制步 直到安排完所有的算子 图 6-16 给出了上述列举过程结束后得到的 调度树 图 6-16 所示的调度树中从根节点到叶节点的每条路径都代表了可能的调度方案, 由此可以得到了如表 6-1 所示的 6 种调度方案 256

258 开始 1,2 1,7 第 1 步 3 7 2,8 第 2 步 4,7 3,8 3 第 3 步 5,8 6,8 4 4 第 4 步 第 5 步 第 6 步 图 6-16 分枝与边界调度算法举例 调度树 表 6-1 图 6-16 中的所有调度方案 控制步 调度方案 第一种 第二种 第三种 第四种 第五种 第六种 从这 6 种调度方案中, 可以按照某种评价标准选择一个最优的调度方案 例如要求控制步最少, 那么就选择第 1 种或第 2 种调度方案 从上面的介绍和例子中可以看出, 分枝与边界调度算法是一种 穷举型 的调度算法, 由于它穷举了所有可能的调度方案, 一般来说, 通过这种方法可以得到最优的调度结果, 但是也正因为它具有 穷举 的特点, 使得它的时间复杂度近似于指数函数, 且需要较大的存储空间, 不易于实现 力量引导调度算法 在前面的讨论中曾经指出, 算子调度包括两个方面 : 一是选择待安排的算子, 二是选择控制步来安排这个算子 由于这两方面的问题相互交织, 相互影响, 很多算子调度算法往往无法把这两个方面很好地统一起来 这一节将要介绍的力量引导调度算法兼顾了这两个方面的问题 在调度过程中, 该算法既指出应该选择哪一个算子进行安排, 又指出这个算子应该安排在哪一个控制步中 所以到现在为止, 力量引导调度算法被认为是最好的 惟一能保证调度结果最优性的算子调度方法 257

259 力量引导算法的基本原理来自这样一个共识 : 算子调度是为了提高资源复用率, 而提高资源复用率的方法就是使各个控制步中同类算子的数量尽可能平均 同时, 为了更清楚更形象地表述控制步中的算子与控制步之间的关系, 力量引导算法从物理学中引入了 力 的概念 把一个控制步想像成一个可以承载砝码的弹簧, 而这些砝码就是可能放入该控制步的算子 每个控制步都承受来自算子施加的 压力 按照 各个控制步中同类算子的数量尽可能平均 的原则, 要使得各个控制步上的 压力 尽可能的平均 而各算子在各控制步之间的调整是通过控制步对算子的 斥力 和 引力 来完成的 如果一个控制步中的算子相对较多, 控制步所受的 压力 就较大, 那么控制步对算子的反作用力也较大, 形成了控制步对算子的 斥力 ; 相反, 如果一个控制步中的算子相对较少, 控制步所受的 压力 就较小, 那么控制步对算子的反作用力也较小, 形成了控制步对算子的 引力 在 力 的作用下, 一个算子总是应该位于对它 斥力 最小或 引力 最大的控制步中 有了以上的概念, 接下来的问题就是如何来度量这种 斥力 或 引力 如果得到了每个控制步对每个算子的 力, 就可以选择 引力 最大或 斥力 最小的那一对控制步和算子, 将这个算子放入到这个控制步中 这样既选择了要安排的算子, 又选择了安排这个算子的控制步, 正如前面所说, 力量引导调度算法同时考虑了算子调度中两个方面的问题 需要指出的是, 事实上并不需要考虑每个控制步对每个算子的 力 由前面的讨论可知, 处于关键路径上的算子, 机动性为 1, 它们只能被安排在惟一的控制步中, 只需要考虑非关键路径上的算子 另外, 非关键路径上的算子也只能在某一些控制步内变动, 只需要考虑这一些控制步对它的 力 控制步对算子的作用力的计算源自力学中的虎克定理 :f = kx 其中,k 为弹簧的弹性系数 ;x 为弹簧的形变量 ;f 为弹簧产生的力,f 的符号代表了力的方向 在将该定理形式应用到计算控制步对算子的作用力的时候, 公式中各变量的意义发生了变化 :k 表示的是控制步上的初始压力 ( 初始压力的概念将在下面解释 );x 表示的是算子出现在控制步中的概率的变化量 假设算子 OP 可以位于控制步 CS, 计算控制步 CS 对算子 OP 的作用力的公式如下 : 258 j i f ( CS, OP ) = DG( k) x( OP,CS,CS ) i j k 其中, k 遍历所有 OP 机动范围内的控制步序号 ( 包括 i ), DG (k) 为 CS k 上的初始压力, j x ( OP j,csi,csk ) 为将算子 OP j 安排到控制步 CS i 后引起的在控制步 CS k 上的出现概率的变化 初始状态下, 算子 OP j 在其机动范围内的每个控制步上出现的概率是相同的, 均为其机动性的倒数, 当 OP j 被安排在控制步 CS i 后, OP j 在 CS i 上出现的概率变为 1, 在 CSk ( k i ) 上出现的概率变为 0 CS k 上的初始压力 DG (k) 的计算公式如下 : DG ( k) = G( OP ) P(, CS ) j j OP j 其中, j 遍历每一个可能在 CS k 上出现的算子编号 G ( OPj ) 为算子 OP j 的 重量, P ( OP j, CSk ) 为算子 OP j 在 CS k 上出现的概率 为方便起见, 一般规定每个算子的 重量 均为 1 j i i k k j

260 下面通过一个例子来演示和说明力量引导调度算法 图 6-17 表示了要调度的算子及其机动性 控制步 算子机动性 计算各控制步初始压力 图 6-17 力量引导调度算法举例 图 6-17 中, 算子 位于关键路径上, 已经被安排好了, 算子 位于非关键路径上, 是待调度的算子 在调度以前, 非关键路径上的算子是 自由 的, 在其机动范围内的各控制步出现的概率是相同的 按照这种初始状态, 首先计算各控制步上的初始压力 控制步 1 上有算子 1, 其出现概率为 1, 所以 DG ( 1) = 1 ; 控制步 2 上有算子 2, 其出现概率为 1, 所以 DG ( 2) = 1 ; 控制步 3 上有算子 , 其中算子 5 6 出现概率为 1, 算子 3 出现的概率为 1/2, 算子 9 10 出现的概率为 1/3 所以 DG(3)=1+1+1/2+1/3+1/3=19/6; 控制步 4 上有算子 , 其中算子 7 出现概率为 1, 算子 3 4 出现的概率为 1/2, 算子 9 10 出现的概率为 1/3 所以 DG(4)=1+1/2+1/2+1/3+1/3=8/3; 控制步 5 上有算子 , 其中算子 8 出现概率为 1, 算子 4 出现的概率为 1/2, 算子 9 10 出现的概率为 1/3 所以 DG(5)=1+1/2+1/3+1/3=13/6 另一种更形象地理解控制步初始压力的方法是将图 6-17 逆时针旋转 90 度, 这样就使得控制步在图的下方, 算子可以被看做是 压 在控制步上 如图 6-18 所示 各算子的 重量 均为 1, 机动性为 1 的算子将全部重量都 压 在一个控制步上, 机动性大于 1 的算子将重量平均地 压 在其机动范围内的各个控制步上 2. 计算各控制步对各算子的作用力 (1) 计算控制步 3 对各算子的作用力在控制步 3 中可能出现的待调度算子为算子 , 下面分别计算控制步 3 对算子 的作用力 259

261 控制步 图 6-18 控制步的初始压力 将算子 3 安排在控制步 3 中, 则算子 3 在控制步 3 中出现的概率由原来的 1/2 变为 1, x( OP 3,CS3,CS3) = 1/2 ; 算子 3 在控制步 4 中出现的概率由原来的 1/2 变为 0, x( OP 3,CS3,CS4) = 1/2 根据作用力计算公式, 控制步 3 对算子 3 的作用力为 : f ( CS3, OP3 ) = DG(3) x( OP3, CS3, CS3 ) + DG(4) x( OP3, CS3, CS4 ) 即 : f ( CS3, OP3 ) = + ( ) = 将算子 9 安排在控制步 3 中, 则算子 9 在控制步 3 中出现的概率由原来的 1/3 变为 1, x( OP 9,CS3,CS3) = 2/3 ; 算子 9 在控制步 4 中出现的概率由原来的 1/3 变为 0, x( OP 9,CS3,CS4) = 1/3; 算子 9 在控制步 5 中出现的概率由原来的 1/3 变为 0, x( OP 9,CS3,CS5) = 1/3 根据作用力计算公式, 控制步 3 对算子 9 的作用力为 : f ( CS3, OP9 ) = DG(3) x( OP9, CS3, CS3) + DG(4) x( OP9, CS3, CS4) + DG(5) x( OP9, CS3, CS5) 即 : f ( CS3, OP9 ) = + ( ) + ( ) = 将算子 10 安排在控制步 3 中, 则算子 10 在控制步 3 中出现的概率由原来的 1/3 变为 1, x,cs,cs ) = 2/3; 算子 10 在控制步 4 中出现的概率由原来的 1/3 变为 0, ( OP ( OP 10, CS3, CS4 ) = ( OP 10, CS3, CS5 ) = ( CS3, OP10) = DG(3) x( OP10, CS3, CS3) + DG(4) x( OP10, CS3, CS4) + DG(5) x( OP10, CS3, CS5 x 1/3; 算子 10 在控制步 5 中出现的概率由原来的 1/3 变为 0, x 1/3 根据作用力计算公式, 控制步 3 对算子 10 的作用力为 : f 即 : f ( CS, OP10 ) = + ( ) + ( ) = 计算控制步 3 对各算子作用力的数据可以简明地用表 6-2 表示 1 2 ) 260

262 表 6-2 计算控制步 3 对各算子作用力的数据 DG(k) x OP 3 OP 9 OP 10 CS 1 1 CS 2 1 CS 3 19/6 1/2 2/3 2/3 CS 4 8/3-1/2-1/3-1/3 CS 5 13/6-1/3-1/3 (2) 计算控制步 4 对各算子的作用力 在控制步 4 中可能出现的待调度算子为算子 , 下面分别计算控制步 4 对算子 的作用力 将算子 3 安排在控制步 4 中, 则算子 3 在控制步 3 中出现的概率由原来的 1/2 变为 0, x( OP 3,CS4,CS3) = 1/2; 算子 3 在控制步 4 中出现的概率由原来的 1/2 变为 1, x( OP 3, CS4, CS4 ) = 1/2 根据作用力计算公式, 控制步 4 对算子 3 的作用力为 : f ( CS4, OP3 ) = DG(3) x( OP3, CS4, CS3) + DG(4) x( OP3, CS4, CS4) 即 : f CS, OP ) = ( ) + = ( 4 3 将算子 4 安排在控制步 4 中, 则算子 4 在控制步 4 中出现的概率由原来的 1/2 变为 1, x( OP 4, CS4, CS4 ) = 1/2; 算子 4 在控制步 5 中出现的概率由原来的 1/2 变为 0, x( OP 4, CS4, CS5 ) = 1/2 根据作用力计算公式, 控制步 4 对算子 4 的作用力为 : f ( CS4, OP4 ) = DG(4) x( OP4,CS4,CS4) + DG(5) x( OP4,CS4,CS5) 即 : f CS, OP ) = + ( ) ( 4 4 = 将算子 9 安排在控制步 4 中, 则算子 9 在控制步 3 中出现的概率由原来的 1/3 变为 0, x( OP 9, CS4, CS3 ) = 1/3; 算子 9 在控制步 4 中出现的概率由原来的 1/3 变为 1, x( OP 9, CS4, CS4 ) = 2/3; 算子 9 在控制步 5 中出现的概率由原来的 1/3 变为 0, x( OP 9, CS4, CS5 ) = 1/3 根据作用力计算公式, 控制步 4 对算子 9 的作用力为 : f ( CS4, OP9 ) = DG(3) x( OP9, CS4, CS3) + DG(4) x( OP9, CS4, CS4) + DG(5) x( OP9, CS4, CS5) 即 : f CS, OP ) = ( ) + + ( ) ( 4 9 = 将算子 10 安排在控制步 4 中, 则算子 10 在控制步 3 中出现的概率由原来的 1/3 变为 0, x, CS, CS ) = 1/3; 算子 10 在控制步 4 中出现的概率由原来的 1/3 变为 1, ( OP ( OP 10, CS4, CS4 ) = ( OP 10, CS4, CS5 ) = ( CS4, OP10) = DG(3) x( OP10, CS4, CS3) + DG(4) x( OP10, CS4, CS4) + DG(5) x( OP10, CS4, CS5 x 2/3; 算子 10 在控制步 5 中出现的概率由原来的 1/3 变为 0, x 1/3 根据作用力计算公式, 控制步 4 对算子 10 的作用力为 : f 即 : ) 261

263 f ( CS, OP10 ) = ( ) + + ( ) = 计算控制步 4 对各算子作用力的数据可以简明地用表 6-3 表示 表 6-3 计算控制步 4 对各算子作用力的数据 x DG(k) OP 3 OP 4 OP 5 OP 10 CS 1 1 CS 2 1 CS 3 19/6-1/2-1/3-1/3 CS 4 8/3 1/2 1/2 2/3 2/3 CS 5 13/6-1/2-1/3-1/ (3) 计算控制步 5 对各算子的作用力 在控制步 5 中可能出现的待调度算子为算子 , 下面分别计算控制步 5 对算子 的作用力 将算子 4 安排在控制步 5 中, 则算子 4 在控制步 4 中出现的概率由原来的 1/2 变为 0, x( OP 4, CS5, CS4 ) = 1/2; 算子 4 在控制步 5 中出现的概率由原来的 1/2 变为 1; x( OP 4, CS5, CS5 ) = 1/2 根据作用力计算公式, 控制步 5 对算子 4 的作用力为 : f ( CS5, OP4 ) = DG(4) x( OP4, CS5, CS4) + DG(5) x( OP4, CS5, CS5) 即 : f CS, OP ) = ( ) + = ( 5 4 将算子 9 安排在控制步 5 中, 则算子 9 在控制步 3 中出现的概率由原来的 1/3 变为 0, x( OP 9, CS5, CS3 ) = 1/3; 算子 9 在控制步 4 中出现的概率由原来的 1/3 变为 0, x( OP 9, CS5, CS4 ) = 1/3; 算子 9 在控制步 5 中出现的概率由原来的 1/3 变为 1, x( OP 9, CS5, CS5 ) = 2/3 根据作用力计算公式, 控制步 5 对算子 9 的作用力为 : f ( CS5, OP9 ) = DG(3) x( OP9, CS5, CS3) + DG(4) x( OP9, CS5, CS4) + DG(5) x( OP9, CS5, CS5) 即 : f CS, OP ) = ( ) + ( ) + = ( 5 9 将算子 10 安排在控制步 5 中, 则算子 10 在控制步 3 中出现的概率由原来的 1/3 变为 0, x, CS, CS ) = 1/3; 算子 10 在控制步 4 中出现的概率由原来的 1/3 变为 0, ( OP ( OP 10, CS5, CS4 ) = ( OP 10, CS5, CS5 ) = f ( CS5, OP10) = DG(3) x( OP10, CS5, CS3) + DG(4) x( OP10, CS5, CS4) + DG(5) x( OP10, CS5, CS5 x 1/3; 算子 10 在控制步 5 中出现的概率由原来的 1/3 变为 1, x 2/3 根据作用力计算公式, 控制步 5 对算子 10 的作用力为 : 即 : f ( CS, OP10 ) = ( ) + ( ) + = 计算控制步 5 对各算子作用力的数据可以简明地用表 6-4 表示 )

264 第 7 章 VHDL 行为设计与高层次综合实例 上一章简单介绍了集成电路设计高层次综合领域内的基本概念 基础理论以及一些典型算法等 本章将讲解一个更为复杂的例子, 给出更为详细的行为设计和高层次综合过程, 并用 VHDL 实现这个设计 本章旨在通过这个例子, 加深读者对 VHDL 行为设计和高层次综合各个过程的理解, 以及高层次综合在设计过程中的应用 需要说明的是, 在这个例子中进行高层次综合的过程是手工完成的, 所应用的理论 算法都是很基本的, 只是为了说明高层次综合中的最基本内容 对于简单的 对综合优化结果要求不是太高的电路, 可以用这样的过程进行高层次综合 ; 而在实际工程的集成电路设计中, 对于复杂的 对综合优化结果要求较高的电路, 往往要用到高层次综合中较深的理论, 也难以用手工完成 所以, 在理想情况下, 高层次综合的过程应该可以借助于 EDA 工具自动完成, 设计者无须像本例那样介入高层次综合的各个过程 目前, 由于无论在高层次综合的理论研究方面还是在高层次综合的 EDA 工具开发方面都不够完善, 高层次综合对电路的优化综合能力还不能很好地体现出来 现在较为典型的高层次综合的 EDA 工具是 SYNOPSYS 公司的 Behavior Compiler 工具 7.1 设计任务说明 设计要求 图 7-1 TH-FIR6TD 端口图 本例所要设计的芯片称为 TH-FIR6TD, 它是集记时 FIR 滤波以及时间和滤波结果输出显示等功能于一体的专用集成电路 TH-FIR6TD 的端口要求 功能要求 性能要求如下 ( 请读者注意, 这里的设计要求就是前面所说的系统级设计 ) 1. 端口要求 TH-FIR6TD 的外部端口如图 7-1 所示 TH-FIR6TD 各端口的意义说明如表 7-1 所示 对各端口更多的说明请参见 TH-FIR6TD 的功能要求 表 7-1 TH-FIR6TD 端口说明 端口模式意义 cs in 片选信号 ( 可选 ) 286

265 reset in 全局异步复位信号 续表 端 口 模式 意 义 clk in 时钟信号 pe in 采样数据读入控制信号 ale in 地址锁存使能信号 wr in 外部写信号 din[7:0] in 数据输入 ( 来自采样 ) dbus[7:0] in 数据输入 ( 来自 CPU8051) addr[7:0] in 地址输入 dout[7:0] out 滤波后数据输出 disd100[6:0] out Dout 的百位数字的 7 段译码信号 disd10[6:0] out Dout 的十位数字的 7 段译码信号 disd1[6:0] out Dout 的个位数字的 7 段译码信号 disap[15:0] out Dout 开方结果的 16 段译码信号 dishour10[6:0] out 系统时间小时数的十位数字 7 段译码信号 dishour1[6:0] out 系统时间小时数的个位数字 7 段译码信号 dismin10[6:0] out 系统时间分钟数的十位数字 7 段译码信号 dismin1[6:0] out 系统时间分钟数的个位数字 7 段译码信号 2. 功能要求 TH-FIR6TD 在 INTEL8051 的控制下对输入信号进行数字滤波, 并根据输入数据的大小产生一组控制液晶板的显示 具体说明如下 : 1) 在 reset 信号的控制下, 实现高电平复位功能, 即所有的输出均置为零 2) 在微处理器 INTEL8051 给出的 wr 信号和 ale 信号的控制下,dbus 端口读入时间信号值以及数字滤波器的系数等数据 微处理器可随时修改这些数据 3)TH-FIR6TD 在 ALE 的下降沿将 addr 上的地址信息锁存, 在 wr 的上升沿将 dbus 上的数据读进来, 按照锁存的地址将数据存入 4)TH-FIR6TD 内部产生一组时间信号, 包括时和分, 在转换成 10 进制并作 7 段译码后, 从端口 dismin10 dismin1 dishour10 dishour1 送出 5) 在外部信号 pe 的上升沿触发下, 芯片从端口 din 读入一个 8 位数据 6) 从 din 读入的数据后, 对信号进行数字滤波, 所得的结果取低 8 位后, 同时以下面 3 种方式输出 : 一是从端口 dout 直接输出 ; 二是在转换成十进制后, 其百位 十位 个位作 7 段译码后分别经端口 disd100,disd10,disd1 送出 ; 三是经开方运算后所得结果做 16 段译码后由端口 disap 送出 因为 8 位二进制数开方结果的范围为 0~15, 所以用 16 段显示管代表 0~15 16 段译码的方式为 : 与输入数据对应的显示管亮, 其余显示管不亮 7) 数字滤波器的转移函数为 : HB FIR (z)=b 0 +B 1 z -1 +B 2 z -2 +B 3 z -3 +B 4 z -4 +B 5 z -5 +B 6 z -6 其中 B 0 ~B 6 均由微处理器给出 287

266 7.1.2 设计环境 本例中 VHDL 源程序的编辑 仿真 综合等过程, 均在 ULTRA SPARC 系列工作站上完成通过 本例中所使用的 VHDL 源文件编辑工具为 UNIX 系统提供的 vi, 所使用的 EDA 工具为 SYNOPSYS 公司的 SYNOPSYS 系列 EDA 软件 如表 7-2 所示 表 7-2 本例中使用的设计工具 工具 UNIX vi SYNOPSYS vhdlan SYNOPSYS vhdldbx SYNOPSYS design_analyzer 完成的任务 VHDL 源程序的编辑 VHDL 的编译 VHDL 的仿真 调试逻辑综合, 综合结果的 VHDL 反标注 7.2 行为级设计与仿真 功能模块划分 根据上面的设计要求, 将所要设计 TH-FIR6TD 电路分为以下 5 个功能模块 : 记时功能模块 地址锁存功能模块 数据写入功能模块 滤波运算功能模块 译码输出功能模块这些功能模块之间以及它们与外部端口之间的关系如图 7-2 所示 ale ale addr 地址锁存功能模块 记时功能模块 dishour dishour dismin dismin clk pc pe 译码输出模块 dout dout dbus wr wr 数据写入功能模块 滤波运算功能模块 disd disd disap disap din 288 图 7-2 TH-FIR6TD 的系统划分图

267 7.2.2 各功能模块的行为级设计及其 VHDL 描述 按照 节对电路功能模块的划分, 下面分别对这些功能模块进行行为级的设计, 并用 VHDL 对所设计的行为进行描述 1. 计时功能模块行为描述计时器记录当前时间, 并将时间信号送至外部显示端口 该时间信号有两种来源, 一是 TH-FIR6TD 对外部时钟 CLK 计数产生, 规定 seccount 个 (seccount 为根据时钟频率定义的常数 )CLK 上升沿为 1 秒, 分钟信号通过对秒计数产生, 小时信号通过对分计数产生 ; 二是由 CPU 写入分钟寄存器或小时寄存器 规定分钟寄存器的地址为 , 小时寄存器的地址为 在每个时钟上升沿检测地址锁存器 ADDR_REG 地址锁存使能信号 ALE 和外部写信号 WR, 如果当前地址为分钟寄存器或小时寄存器地址 ( 特征为 ADDR_REG(4)=1) 且外部写有效 ( 特征为 ALE=0 AND WR=0), 计时器处于 CPU 写入时间模式 ; 否则计时器处于 CLK 计数模式 计时功能模块的 VHDL 描述如例 7-1 所示 例 7-1 process(clk,reset) variable count:integer range 0 to 255; variable second:integer range 0 to 59; variable settime:std_logic; if cs='0' then if reset='1' then count:= '0'; second:= '0'; hour<='0'; minute<='0'; settime:='0'; else if clk'event and clk='1' then if wr='0' and ale='0' and addr_reg(4)='1' then settime:='1'; else settime:='0'; end if; if settime='0' then count:=count+1; if count=seccount then count:=0; 289

268 if second=59 then second:=0; if minute=59 then minute<=0; if hour=23 then hour<=0; else hour<=hour+1; end if; else minute<=minute+1; end if; else second:=second+1; end if; end if; elsif settime='1' then case addr_reg is when " "=> minute<=conv_integer(dbus); when " "=> hour<=conv_integer(dbus); when others=> null; end case; end if; end if; end if; end if; end process; 2. 地址锁存功能模块行为描述地址锁存的功能比较简单, 就是在每个 ALE 的下降沿将 ADDR 上的信号锁存至地址寄存器 ADDR_REG 地址锁存功能模块的 VHDL 描述如例 7-2 所示 例 7-2 process(reset,ale) if cs='0' then if reset='1' then addr_reg<=" "; else if ale'event and ale='0' then addr_reg<=addr; 290

269 end if; end if; end if; end process; 3. 数据写入功能模块行为描述在 ALE=0 时, 在每个 WR 的上升沿从端口 DBUS 读入数据, 根据当前地址寄存器中的地址将数据写入相应的寄存器 需要写入的数据包括时间和滤波器系数 写入时间的过程已在计时器行为中描述, 这里只需描述对滤波器系数的写入 各滤波器系数寄存器对应的地址如表 7-3 所示 表 7-3 各滤波器系数寄存器对应的地址 寄存器 B0 B1 B2 B3 B4 B5 B6 地址 数据写入功能模块的 VHDL 描述如例 7-3 所示 例 7-3 process(reset,wr) if cs='0' then if reset='1' then for i in 0 to 6 loop b(i)<= " "; end loop; else if ale='0' then if wr'event and wr='1' then case addr_reg is when " "=> b(0)<=dbus; when " "=> b(1)<=dbus; when " "=> b(2)<=dbus; when " "=> b(3)<=dbus; when " "=> b(4)<=dbus; when " "=> b(5)<=dbus; when " "=> b(6)<=dbus; when others=> null; end case; end if; end if; end if; end if; 291

270 end process; 4. 滤波运算及采样功能模块行为描述由于该滤波为 6 阶滤波, 故 TH-FIR6TD 中有 7 个采样数据寄存器存储连续 7 个采样点的采样数据 在 PE 的控制下,TH-FIR6TD 从 DIN 端口读入采样数据, 移动 更新采样数据寄存器的内容, 然后进行滤波计算 滤波运算及采样功能模块的 VHDL 描述如例 7-4 所示 例 7-4 process(reset,pe) variable result:std_logic_vector(15 downto 0); if cs='0' then if reset='1' then data_out_reg<=" "; for i in 0 to 6 loop z(i)<= " "; end loop; else if pe'event and pe='1' then for i in 5 downto 0 loop z(i+1)<=z(i); end loop; z(0)<=din; result:=b(0)*z(0)+b(1)*z(1)+b(2)*z(2)+b(3)*z(3)+b(4)*z(4)+b(5)*z(5)+b(6)*z(6); data_out_reg<=result(7 downto 0); end if; end if; end if; end process; 5. 译码输出功能模块行为描述 TH-FIR6TD 显示的数据包括 : 时间 ( 小时和分钟 ) 滤波运算结果 滤波运算结果的平方根 其中时间 (0~23h,0~59min) 和滤波运算结果 (0~255) 的显示方式是将其十进制形式的各位上的数字通过 7 段 LED 显示管显示, 滤波运算结果的平方根 (0~15) 通过 16 段显示管显示 译码输出功能模块的 VHDL 描述如例 7-5 所示 例 7-5 process(reset,hour,minute,data_out_reg) variable intdata:integer range 0 to 255; variable sqrdata:integer range 0 to 15; 292

271 if cs='0' then if reset='1' then dishour10<=" "; dishour1<=" "; dismin10<=" "; dismin1<=" "; disd100<=" "; disd10<=" "; disd1<=" "; disap<=" "; else dishour10<=getledcode(get10(hour)); dishour1<=getledcode(get1(hour)); dismin10<=getledcode(get10(minute)); dismin1<=getledcode(get1(minute)); intdata:=conv_integer(data_out_reg); disd100<=getledcode(get100(intdata)); disd10<=getledcode(get10(intdata)); disd1<=getledcode(get1(intdata)); sqrdata:=getsqrt(intdata); disap<=getapcode(sqrdata); end if; end if; end process; 在上面的描述中, 多次调用了一些用户自定义函数, 这些函数用于实现显示管译码 ( 如函数 getledcode getapcode 等 ), 数据的十进制形式的各位数字获取 ( 如函数 get100,get10, get1 等 ), 数据开方运算 ( 如函数 getsqrt) 等功能 下面对所用到的函数做一个简单介绍 1) 函数 get100 get10 get1 分别为取数据的十进制形式的百位 十位 个位上的数字, 算法中采用了除法和取模运算 例如 : digit100 = num/100 digit10= num/10 mod 10; digit1= num mod 10; 其中 num 的范围为 0 到 255 2) 函数 getledcode 为 7 段译码, 采用了列真值表的方式 输入为一位十进制数 (0~9), 输出为该十进制数在 7 段数码管上的显示信号 每一个信号控制数码管上某一段的亮灭, 如下图所示如果信号为 1, 则数码管上对应的发光段亮 输入输出的关系如表 7-4 所示 表 段译码器的输入与输出 y[1] 待译码的数 x 译码输出 y[6:0] 293 y[6] y[0] y[2] y[5] y[3] y[4]

272 (4FH) (FFH) (0EH) (7BH) (5BH) (4DH) (1FH) (37H) (0BH) (7EH) (3) 函数 getapcode 为 16 段译码, 当数据为 i 时 (i=0~15), 将 16 位输出向量 [15: 0] 中的第 i 位置 1, 其余各位置 0 (4) 函数 getsqrt 为对数据的开方, 算法为二分比较 运算数据与运算结果的对应如表 7-5 所示 表 7-5 开方运算表 x y = x ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~24 4 9~15 3 4~8 2 1~ 上述各函数在用户自定义的程序包 THFIR6TD_PKG 中定义和实现,THFIR6TD_PKG 的程序包声明及包体的 VHDL 源程序如例 7-6 所示 例 7-6 library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; use ieee.std_logic_arith.all; package thfir6td_pkg is 294

273 type std_logic_vector_array is array (natural range<>) of std_logic_vector(7 downto 0); function "/" (l:integer;r:integer) return integer; function mod (L:integer;R:integer) return integer; function get1(num:integer range 0 to 255) return integer; function get10(num:integer range 0 to 255) return integer; function get100(num:integer range 0 to 255) return integer; function getledcode(digit:integer range 0 to 9) return std_logic_vector; function getapcode(num:integer range 0 to 15) return std_logic_vector; function getsqrt(num:integer range 0 to 255) return integer; end thfir6td_pkg; package body thfir6td_pkg is function "/" (l:integer;r:integer) return integer is variable ll,rr:integer; variable quotient:integer; ll:=l; rr:=r; quotient:=0; for I in 1 to 10 loop if ll>=rr then ll:=ll-rr; quotient:=quotient+1; end if; end loop; return quotient; end; function "mod" (L:integer;r:integer) return integer is variable ll,rr:integer; variable residue:integer; ll:=l; rr:=r; residue:=0; for I in 1 to 10 loop if ll>=rr then ll:=ll-rr; 295

274 end if; end loop; residue:=ll; return residue; end; function get1(num:integer range 0 to 255) return integer is variable result:integer range 0 to 9; result:=num mod 10; return result; end; function get10(num:integer range 0 to 255) return integer is variable result:integer range 0 to 9; result:=(num mod 100)/10; return result; end; function get100(num:integer range 0 to 255) return integer is variable result:integer range 0 to 9; result:=num/100; return result; end; 296 function getledcode(digit:integer range 0 to 9) return std_logic_vector is variable result:std_logic_vector(6 downto 0); case digit is when 0 => result:= " "; when 1 => result:= " "; when 2 => result:= " "; when 3 => result:= " "; when 4 => result:= " "; when 5 => result:= " "; when 6 => result:= " "; when 7 => result:= " "; when 8 => result:= " "; when 9 => result:= " ";

275 when others=> result:= " "; end case; return result; end; function getapcode(num:integer range 0 to 15) return std_logic_vector is variable result:std_logic_vector(15 downto 0); result:= " "; result(num):= '1'; return result; end; function getsqrt(num:integer range 0 to 255) return integer is variable result:integer range 0 to 15; if num < 64 then --0~64 if num <16 then --0~16 if num < 4 then --0~4 if num < 1 then --0~1 result:=0; else --1~4 result:=1; end if; else --4~16 if num <9 then --4~9 result:=2; else --9~16 result:=3; end if; else end if; if num < 36 then if num <25 then result:=4; else result:=5; end if; --16~ ~ ~ ~36 else --36~64 297

276 if num < 49 then result:=6; else result:=7; end if; end if; --36~ ~64 else end if; if num < 144 then if num < 100 then if num < 81 then result:=8; else result:=9; end if; --64~ ~ ~ ~ ~100 else if num < 121 then result:=10; else result:=11; end if; end if; --100~ ~ ~144 else if num < 196 then if num < 169 then result:=12; else result:=13; end if; else --144~ ~ ~ ~ ~256 if num < 225 then --196~225 result:=14; else result:=15; end if; end if; --225~ end if;

277 end if; return result; end; end thfir6td_pkg; 到这里为止, 就完成了对 TH-FIR6TD 的功能模块划分, 以及对每个功能模块的 VHDL 行为描述 可以看到, 在以上行为描述中某些数据的类型上, 使用了代表十进制的整数类型, 而没有使用代表二进制的 STD_LOGIC 类型, 如计时功能模块中小时数 分钟数 ; 另外, 在数据处理上, 直接使用了 + * 等运算符, 还用到了自定义的译码函数 开方函数等, 而不需顾及它们的电路实现 这些方面都体现了行为描述面向系统行为 具有高层性的特征 对整个电路的行为级设计描述就是把以上这些功能模块的行为描述组合起来 在 VHDL 中, 就体现为将这些功能模块作为各个并行的进程 (Process), 写在实体 TH-FIR6TD 的构造体中 上面完整地给出了各功能模块涉及的行为描述的 VHDL 源程序, 为完成整个电路的 VHDL 源程序, 还需要说明所用库及程序包的库语句 (library 语句 ) 说明 TH-FIR6TD 外部端口的实体语句 (entity 语句 ), 以及将各功能模块组合在一起的构造体语句 (architecture 语句 ) 如例 7-7 所示 例 7-7 library IEEE; use IEEE.std_logic_1164.all; use IEEE.std_logic_unsigned.all; library work; use work.thfir6td_pkg.all; entity thfir6td is generic(seccount:integer:=1); port( cs :in std_logic; reset :in std_logic; clk :in std_logic; pe :in std_logic; din :in std_logic_vector(7 downto 0); dbus :in std_logic_vector(7 downto 0); addr :in std_logic_vector(7 downto 0); ale :in std_logic; wr :in std_logic; dout :out std_logic_vector(7 downto 0); dishour10 :out std_logic_vector(6 downto 0); 299

278 dishour1 :out std_logic_vector(6 downto 0); dismin10 :out std_logic_vector(6 downto 0); dismin1 :out std_logic_vector(6 downto 0); disd100 :out std_logic_vector(6 downto 0); disd10 :out std_logic_vector(6 downto 0); disd1 :out std_logic_vector(6 downto 0); disap :out std_logic_vector(15 downto 0) ); end thfir6td; architecture behav of thfir6td is signal hour:integer range 0 to 23; signal minute:integer range 0 to 59; signal addr_reg:std_logic_vector(7 downto 0); signal data_out_reg:std_logic_vector(7 downto 0); signal data_in_reg:std_logic_vector(7 downto 0); signal b:std_logic_vector_array(6 downto 0); signal z:std_logic_vector_array(6 downto 0); -- 在前面部分给出的代表各功能模块行为描述的过程语句 --timer process --get address process --get data process --filter process --display process end architecture behav; 至此, 对 TH-FIR6TD 的行为级设计及其 VHDL 源程序编写就完成了 VHDL 源程序可以放在一个文件里, 也可以分成几个文件存放 将 TH-FIR6TD 的实体和构造体放在文件 thfir6td_bhv.vhd 中, 将用户自定义程序包 THFIR6TD_PKG 放在文件 thfir6td_pkg.vhd 中 对这两个文件进行检查 编译后, 就可以对设计进行仿真, 来检测设计的系统是否符合预期的行为 300

279 7.2.3 行为级仿真 1. 制定仿真内容首先, 需要确定要仿真什么, 即仿真的内容是什么 因为这里是对行为级进行仿真, 所以主要是验证所描述的电路功能是否正确, 或者说, 描述是否正确地表达了预想的功能 在 节中, 将电路分为 5 个功能模块来描述, 所以在验证时也基本上按这 5 个功能来验证 一般来说, 需要对下列几种情况进行仿真 : 1)RESET 信号的全局复位 ; 2) 处于 CLK 计数模式的记时 ; 3) 处于 CPU 写入时间模式的记时 ; 4)CPU 将滤波系数写入相应的寄存器, 读入采样数据并进行滤波运算 ; 5) 上述各种情况下的输出译码 在仿真过程中, 可以单独仿真某个功能, 也可以将几个功能同时仿真 为使对电路的验证更可靠, 应尽量对电路所可能处于的各种情况进行仿真 在本章的例子中, 为节省篇幅, 仅对典型情况进行了简单仿真 在确定了仿真内容以后, 就要按照确定的仿真内容编辑相应的激励波形, 施加到电路上, 从输入输出的映射关系上分析电路的行为功能是否正确 2. 施加激励波形仿真过程中的输入信号波形需要由用户指定, 称为激励信号的施加 因为用的 VHDL 仿真工具 Synopsys vhdldbx 中没有图形化编辑波形的功能, 所以用 VHDL 语言编写的测试平台 (Testbench) 来进行仿真 在测试平台中, 将待仿真的电路作为其中的元件, 把它的端口引到对应的信号上, 对其中连接输入端口的信号采用 VHDL 信号赋值语句编辑其波形 在本例中, 待测电路的输入端口共有 9 个, 分别为 :CS reset CLK ALE WR ADDR dbus din 和 PE 根据前面制定的仿真方案, 要求仿真时的输入波形如图 7-3 所示 图 7-3 行为级仿真激励波形图 301

280 对激励波形作几点说明 : (1) 整个仿真过程为 600 ns (2) 片选信号 CS 为低电平有效, 在仿真过程中使其一直为 0, 保持电路一直处于被选中状态 (3)reset 初值为 1 并保持 10 ns, 电路复位, 以后 reset=0, 电路工作 (4)CLK 波形为周期时钟, 周期为 10 ns, 占空比为 50% (5)ALE WR 和 ADDR dbus 配合, 进行 CPU 写入操作 在 ALE 的下降沿,ADDR 上的地址信号被锁存, 在 WR 的上升沿,dbus 上的数据根据锁存的地址被写入到相应的寄存器中 在仿真过程中,CPU 共写入 9 个数据 如表 7-6 所示 表 7-6 仿真过程中 CPU 写入数据 ALE 下降沿序数 时刻 (ns) 地址 ( 十六进制 ) WR 上升沿序数 时刻 (ns) 数据 ( 十六进制 ) CPU 所写的寄存器 H BH Minute= H H Hour= H H B0= H H B1= H H B2= H H B3= H H B4= H H B5= H H B6=1 (6)PE 和 din 配合, 进行滤波运算 在每个 PE 的上升沿,din 上的数据被采样, 并将新得到的采样数据与前 6 个采样点数据进行 6 阶滤波运算 PE 在 320 ns 时刻给出第 1 个上升沿, 以后每隔 30 ns 给出一个上升沿, 共有 6 个上升沿, 所以共要 6 次滤波运算 为简单起见,din 上的数据在 310 ns 后均为 02H, 即每次采样滤波的数据均为 02H 3. 编写用于仿真的 VHDL 文件在制定了仿真方案和具体的激励波形后, 接下来就要用 VHDL 语言来描述以上的激励波形, 并把激励施加到待测电路上进行仿真 这个 VHDL 文件称为电路的测试平台 (Testbench) 本例中, 根据上面的激励波形, 编写的测试平台如例 7-8 所示 例 7-8 library ieee; use ieee.std_logic_1164.all; entity thfir6td_tb is end thfir6td_tb; 302 architecture t1 of thfir6td_tb is

281 component thfir6td port( cs:in std_logic; reset:in std_logic; clk:in std_logic; pe:in std_logic; din:in std_logic_vector(7 downto 0); dbus:in std_logic_vector(7 downto 0); addr:in std_logic_vector(7 downto 0); ale:in std_logic; wr:in std_logic; dout:out std_logic_vector(7 downto 0); dishour10:out std_logic_vector(6 downto 0); dishour1:out std_logic_vector(6 downto 0); dismin10:out std_logic_vector(6 downto 0); dismin1:out std_logic_vector(6 downto 0); disd100:out std_logic_vector(6 downto 0); disd10:out std_logic_vector(6 downto 0); disd1:out std_logic_vector(6 downto 0); disap:out std_logic_vector(15 downto 0) ); end component; signal cs:std_logic:='0'; signal reset:std_logic:='0'; signal clk:std_logic:='0'; signal din:std_logic_vector(7 downto 0):=" "; signal dbus :std_logic_vector(7 downto 0):=" "; signal addr:std_logic_vector(7 downto 0):=" "; signal ale:std_logic:='0'; signal wr:std_logic:='1'; signal pe:std_logic:='1'; signal dout:std_logic_vector(7 downto 0); signal dishour10:std_logic_vector(6 downto 0); signal dishour1:std_logic_vector(6 downto 0); signal dismin10:std_logic_vector(6 downto 0); signal dismin1:std_logic_vector(6 downto 0); signal disd100:std_logic_vector(6 downto 0); signal disd10:std_logic_vector(6 downto 0); signal disd1:std_logic_vector(6 downto 0); 303

282 signal disap:std_logic_vector(15 downto 0); UUT: thfir6td port map( cs=>cs, reset=>reset, clk=>clk, pe=>pe, din=>din, dbus=>dbus, addr=>addr, ale=>ale, wr=>wr, dout=>dout, dishour10=>dishour10, dishour1=>dishour1, dismin10=>dismin10, dismin1=>dismin1, disd100=>disd100, disd10=>disd10, disd1=>disd1, disap=>disap ); cs<='0'; reset<='1','0' after 10 ns; process wait for 5 ns; clk<=not clk; end process; ale<='1' after 30 ns, --enable minute addr '0' after 40 ns, '1' after 60 ns, --enable hour addr '0' after 70 ns, '1' after 90 ns, --enable b0 addr '0' after 100 ns, '1' after 120 ns, --enable b1 addr '0' after 130 ns, '1' after 150 ns, --enable b2 addr 304

283 '0' after 160 ns, '1' after 180 ns, --enable b3 addr '0' after 190 ns, '1' after 210 ns, --enable b4 addr '0' after 220 ns, '1' after 240 ns, --enable b5 addr '0' after 250 ns, '1' after 270 ns, --enable b6 addr '0' after 280 ns, '1' after 300 ns, '0' after 310 ns, '1' after 330 ns, '0' after 340 ns, '1' after 360 ns, '0' after 370 ns, '1' after 390 ns, '0' after 400 ns, '1' after 420 ns, '0' after 430 ns, '1' after 450 ns, '0' after 460 ns; addr<=" " after 30 ns, --minute addr " " after 60 ns, --hour addr " " after 90 ns, --b0 addr " " after 120 ns, --b1 addr " " after 150 ns, --b2 addr " " after 180 ns, --b3 addr " " after 210 ns, --b4 addr " " after 240 ns, --b5 addr " " after 270 ns, --b6 addr " " after 300 ns; --no action addr wr<='0' after 40 ns, --enable write minute '1' after 50 ns, '0' after 70 ns, --enable write hour '1' after 80 ns, '0' after 100 ns, --enable write b0 '1' after 110 ns, '0' after 130 ns, --enable write b1 '1' after 140 ns, 305

284 '0' after 160 ns, '1' after 170 ns, '0' after 190 ns, '1' after 200 ns, '0' after 220 ns, '1' after 230 ns, '0' after 250 ns, '1' after 260 ns, '0' after 280 ns, '1' after 290 ns; --enable write b2 --enable write b3 --enable write b4 --enable write b5 --enable write b6 pe<='0' after 310 ns, '1' after 320 ns, '0' after 340 ns, '1' after 350 ns, '0' after 370 ns, '1' after 380 ns, '0' after 400 ns, '1' after 410 ns, '0' after 430 ns, '1' after 440 ns, '0' after 460 ns, '1' after 470 ns; --enable input 1st z --enable input 2nd z --enable input 3rd z --enable input 4th z --enable input 5th z --enable input 6th z dbus<=" " after 40 ns, --minute data " " after 70 ns, --hour data " " after 100 ns; --b0~b6 data din<=" " after 310 ns; end t1; --z data 当然, 这里给的测试方案和激励波形比较粗略, 涉及的情况也比较简单, 只是为了作一个示例, 使读者能够了解如何用 VHDL 的测试平台 (Testbench) 来描述自己的测试方案, 即编辑激励波形 读者在掌握这种方法后, 就可根据具体情况和要求, 编写更为复杂的测试平台 编写完测试平台后, 最后要做的就是对测试平台中的待测元件进行配置 因为在测试平台中, 只给出了待测元件的实体名 thfir6td, 只有其外部端口而没有内在实现 所以要通过 VHDL 中的配置语句指定其中的待测元件使用了哪个构造体 进行配置的 VHDL 源代码如例 7-9 所示 306

285 例 7-9 configuration conf of thfir6td_tb is for t1 for uut: thfir6td use entity work.thfir6td(bhv); end for; end for; end conf; 通过上面的配置语句, 可以灵活地仿真不同的实现方式下的待测元件 例如, 现在测试的是该电路的行为级实现, 用的是行为级描述的构造体 ; 当综合以后得到电路的逻辑级描述构造体, 要对其进行仿真时, 只需在上述配置语句中改变其构造体的名称即可 因为测试平台 (Testbench) 本身就是 VHDL 语言, 所以它在同配置语句一起编译后, 可以在任何一个 VHDL 的仿真器中进行仿真, 得到输出波形 4. 仿真结果分析若使用 synopsys 的 VHDL 仿真工具 synopsys-vhdldbx 对上述测试平台进行仿真, 可得到如图 7-4 所示的波形 图 7-4 仿真结果波形图 307

286 下面对仿真结果波形进行分析, 以验证电路功能是否正确 (1)0 到 10 ns 之间,reset=1, 电路处于被复位状态, 从波形上看, 各输出端口均被置为 0, 表明复位功能正确 (2) 一个 ALE 的下降沿和紧接着的一个 WR 的上升沿构成外部 CPU 对电路中寄存器的一次写操作 根据激励波形, 前两次是对记时器的写操作 : 第 1 次锁存的地址是 11H, 数据是 3BH, 即向分钟寄存器写入 59; 第 2 次锁存的地址是 12H, 数据是 17H, 即向小时寄存器写入 23 从仿真得到的波形上看, 在第 1 次写入后, 输出端口 dismin10=5bh, dismin1=4fh, 它们对应的 7 段数码管的显示为 59 ; 在第 2 次写入后, 输出端口 dishour10=37h,dishour1=1fh, 它们对应的 7 段数码管的显示为 23 从而表明对记时器的写操作以及小时 分钟的译码显示正确 后 7 次是对 7 个滤波系数进行写操作, 数据均为 01H, 即 B0= B1= B2=B3=B4=B5=B6=01H 虽然无法直接观察系数寄存器, 但可以从以后的滤波结果进行间接地验证 (3) 在每个 PE 上升沿, 并行执行采样信号序列的滤波运算和将 din 上的新采样数据移入到采样信号序列中两项功能 因为在所给的激励中滤波系数均为 01H, 采样数据均为 02H, 所以从波形上看, 在第 1 个 PE 上升沿后, 对采样序列 Z6=Z5=Z4=Z3=Z2=Z1=Z0=0 做滤波运算,dout 输出运算结果为 00H,disd100 disd10 disd1 均为 7EH, 对应 7 段数码管输出为 000, 开方运算结果 16 段译码为 0001H, 同时移入新采样数据, 使得采样信号序列变为 Z6=Z5=Z4=Z3=Z2=Z1=0,Z0=02H; 在第 2 个 PE 上升沿后, 对采样序列 Z6=Z5= Z4=Z3=Z2=Z1=0,Z0=02H 做滤波运算,dout 输出运算结果为 02H,disd100 disd 10 disd 1 分别为 7EH 7EH 37H, 对应 7 段数码管输出为 002, 开方运算结果 16 段译码为 0002H, 同时移入新采样数据, 使得采样信号序列变为 Z6=Z5=Z4=Z3 =Z2=0,Z1=02H, Z0=02H 以后过程请读者自行分析 该结果波形表明, 电路的滤波运算部分的功能正确 上面的分析表明, 从施加的激励信号和得到的输出来看, 电路的功能正确 当然, 还可以对更多的 更复杂的情况进行验证, 在此不再赘述 到这里为止, 就完成了对 thfir6td 电路行为级描述的仿真, 仿真结果表明该行为描述正确, 达到了预想功能 由行为级描述得到的系统评估 从电路的行为描述中, 可以大致估计对电路所需的资源, 具体地说, 就是可以估计出这个行为描述经过逻辑综合后的电路可能含有的资源的种类和数量 对本例中的行为描述, 可简单地评估其所需资源 : 1. 乘法器滤波运算要用到乘法, 所以在电路中需要有乘法器 滤波运算的表达式为 : result:=b(0)*z(0)+b(1)* z (1)+b(2)* z (2)+b(3)* z (3)+b(4)* z (4)+b(5)* z (5)+b(6)* z (6); 式中有 7 个乘法运算, 综合工具对这个表达式进行综合的时候, 会认为这 7 个乘法运算是并行的, 从而在综合结果中会有 7 个乘法器 2. 加法器滤波运算要用到加法, 所以在电路中需要有加法器 滤波运算的表达式为 : 308

287 result:=b(0)* z (0)+b(1)* z (1)+b(2)* z (2)+b(3)* z (3)+b(4)* z (4)+b(5)* z (5)+b(6)* z (6); 式中有 6 个加法运算, 综合工具对这个表达式进行综合的时候, 会认为这 6 个加法运算是并行的, 从而综合结果中会有 6 个加法器 3. 取商运算单元这里的取商运算指的是取商的整数部分 在时间 滤波结果译码显示之前, 需要将数据按十进制形式提取它各位上的数字, 这里就要用到取商运算 在程序包 thfir6td_pkg 中的函数 get100( 提取百位上的数字 ) 和 get10( 提取十位上的数字 ) 中用到了取商运算 取商运算符为 /, 它也是在程序包 thfir6td_pkg 中定义的 从源程序看, 取商运算是通过累减运算实现的 因为要显示 2 位的小时数 (0~23h) 2 位的分钟数 (0~59min) 和 3 位的滤波结果 (0~ 255), 所以需要提取小时数十位, 分钟数十位和滤波结果百 十位 综合工具进行综合的时候, 会认为用到的取商运算是并行的, 故一共要用到 4 个取商运算单元 4. 取模运算单元在时间 滤波结果译码显示之前, 需要将数据按十进制形式提取各位上数字, 这里就要用到取模运算 在程序包 thfir6td_pkg 中的函数 get10( 提取十位上的数字 ) 和 get1( 提取个位上的数字 ) 中用到了取模运算 取模运算符为 mod, 它也是在程序包 thfir6td_pkg 中定义的, 从源程序看, 取模运算是通过累减运算实现的 因为要显示 2 位的小时数 (0~23h) 2 位的分钟数 (0~59min) 和 3 位的滤波结果 (0~ 255), 所以需要提取小时数个 十位, 分钟数个 十位和滤波结果个 十位 综合工具进行综合的时候, 会认为用到的取模运算是并行的, 故一共要用到 6 个取模运算单元 5. 开方运算单元对滤波结果的开方运算需要用到开方运算单元 开方运算的函数 getsprt 是在程序包 thfir6td_pkg 中定义的, 从源程序看, 开方运算是通过一系列比较实现的 本例的电路中要用到一个开方运算单元 6.7 段译码单元待显示的数据被提取出的各位上的数字需要经过 7 段译码输出显示 需要 7 段译码的数字包括小时数的个 十位, 分钟数的个 十位和滤波结果的个 十 百位, 综合工具进行综合的时候, 会认为这些译码运算是并行的, 所以要用到 7 个 7 段译码单元 7.16 段译码单元滤波结果经开方后经过 16 段译码输出显示, 所以电路中要用到 1 个 16 段译码单元 上面从 thfir6td 的行为描述中粗略地估计了电路中用到的资源 可以看出, 用行为描述直接进行综合, 各个运算之间都被认为是并行的, 所以资源之间没有复用, 电路实现所花费的资源代价较大 下面进行的高层次综合就是为了改善行为描述的这个缺点, 对系统行为中的各个算子进行调度分配, 达到资源的有效复用, 在满足约束条件的前提下, 进行速度和资源的折衷 309

288 7.3 高层次综合与综合结果仿真 数据控制流图 thfir6td 电路的主要数据流是滤波运算以及运算结果的译码, 为了更清楚地说明和演示对已有的算法行为进行高层次综合的过程, 在本例的整个高层次综合过程中对 thfir6td 电路进行了简化, 仅考虑了电路的主要运算部分 滤波及其运算结果译码, 而忽略电路中的计时器部分 这样更易于体现问题的主要矛盾 thfir6td 的滤波及译码部分的数据控制流图如图 7-5 所示 图 7-5 数据控制流图图中的 b 0 ~b 6 为滤波器系数,x 0 ~x 6 为连续 6 个时间采样点上的信号值 滤波运算的结果 (8 位二进制数 ) 分 3 路输出 : 一是直接输出 ; 二是开方并经 16 段译码后输出 ; 三是转化为 3 位十进制数, 其每位数字经 7 段译码后输出 算子调度 310 从上面的数据控制流图来看, 算法中的算子包括以下 6 类 : 乘法算子 ( * ) 加法算子 ( + )

289 二 - 十进制转化算子 (bin-dec) 开方算子 (sqrt) 7 段译码算子 (decode7) 16 段译码算子 (decode16) 每个算子在硬件实现上都对应一个电路单元, 称为资源 例如乘法算子对应一个乘法器单元, 开方算子对应一个开方运算单元 每个资源又对应芯片上的一块面积 为了尽可能地节省芯片面积, 减少使用资源的数目, 需要让多个算子使用同一个资源 为了让使用同一个资源的算子不发生冲突, 需要通过算子调度, 将这些算子放在不同的控制步中来执行 根据数据控制流图, 得到如表 7-7 所示的算子调度方案 表 7-7 算子调度方案 控制步 相应的运算 使用的资源 0 采样, 复位 ( 包括使 y 0 =0) 1 y 0 =x 0 * b 0+ y 0 ; 1 个乘法器和 1 个加法器 2 y 0 =x 1 * b 1+ y 0 ; 1 个乘法器和 1 个加法器 3 y 0 =x 2 * b 2+ y 0 ; 1 个乘法器和 1 个加法器 4 y 0 =x 3 * b 3+ y 0 ; 1 个乘法器和 1 个加法器 5 y 0 =x 4 * b 4+ y 0 ; 1 个乘法器和 1 个加法器 6 y 0 =x 5 * b 5+ y 0 ; 1 个乘法器和 1 个加法器 7 y 0 =x 6 * b 6+ y 0 ;y 1 = y 0 ; 1 个乘法器和 1 个加法器 8 9 (d100,d10,d1)<=bin-dec(y1); sqr<=sqrt(y1); disd100<=decode7(d100); disd10<=decode7(d10); disd1<=decode7(d1); disap<=decode16(sqr); 1 个二 - 十进制转化模块和 1 个开方模块 3 个 7 段译码器和 1 个 16 段译码器 上述算子调度方案将每一次滤波及输出的算法过程分为 10 个控制步, 需要的资源为 : 1 个乘法器 1 个加法器 1 个二 - 十进制转化模块 1 个开方模块 3 个 7 段译码器和 1 个 16 段译码器 可以看到, 将相同的算子安排在不同的控制步中, 达到了资源复用的目的, 大大节省了资源数目 但是由于运算被划分为多个控制步来进行, 运算的时间增加了 在本例中, 对电路延时的要求并不作为主要考虑因素, 所以以下进一步高层综合的各步骤都是按照这个算子调度方案进行的 资源分配 在上面算子调度的表中, 实际上已给出了每一步所要用到的资源 不同控制步中的同类资源在物理实现上对应同一块硬件电路 因而 thfir6td 的滤波及输出部分的硬件电路总共包括 1 个乘法器 1 个加法器 1 个二 - 十进制转化模块 1 个开方模块 3 个 7 段译码器 311

290 和 1 个 16 段译码器 这些资源及它们所承担的操作如表 7-8 所示 表 7-8 资源分配方案 资 源 资源对应的操作 乘法器 b i * x i (i=0,1,,6) 加法器 b i * x i +y 0 (i=0,1,,6) 二 - 十进制转化模块 (d100,d10,d1)<=bin-dec(y1) 开方模块 sqr<=sqrt(y1) 7 段译码器 1 disd100<=decode7(d100) 7 段译码器 2 disd10<=decode7(d10) 7 段译码器 3 disd1<=decode7(d1) 16 段译码器 disap<=decode16(sqr) 连线网络 本例中的连线网络比较简单, 输入端部分用了多路选择器结构, 为被复用的乘法器的两个输入端选择相应的当前操作数, 这两个多路选择器分别由控制码 ( c 0c1c2 ) 和 ( c 3c4c5 ) 进行数据选择 ; 用 1 个多路选择器控制寄存器 y 0 的输入, 初始状态时输入 0, 使 y 0 置 0, 在运算过程中,y 0 存储每一步的结果, 这个多路选择器由控制码 c 7 控制 ; 还有一个传输开关将寄存器 y 0 的数据传给寄存器 y 1, 这是为了在运算结束之前对保持在 y 1 中的上一次的运算结果进行译码显示, 而仅当运算结束时才将最终结果 y 0 传给 y 1, 并对新的结果进行译码显示, 该传输开关由控制码 c 6 控制 本例的连线网络如图 7-6 所示 控制器与控制码 前面介绍过, 一个处理器通常被划分为数据通道和控制器 数据通道负责数据的运算 传输 ; 控制器负责控制数据通道使其在每一个特定的控制步中能对正确的数据进行运算和传输, 控制数据通道一步一步地完成所需的运算 进行了算子调度以后, 整个运算过程就被分为了若干个控制步 每一个控制步实际上就是电路的一个状态, 因而控制器就是一个有限状态机 (Finite State Machine), 它根据有关信号, 例如时钟 数据通道的反馈等信号来进行各电路状态间的转换, 以及在每一个状态下向数据通道输出相应的控制码, 控制数据通道中数据的传输和运算关系 所以说, 控制器包括两个要素, 一个是控制器的状态转换关系, 一个是控制器在每个状态 ( 控制步 ) 下向数据通道输出的控制码 1. 状态转换关系一般来说, 一个控制步对应一个电路状态, 因而电路的状态数和控制步数是相同的 在本例中, 将滤波输出部分分为 10 个控制步, 所以电路的状态共有 10 个, 分别用 CS 0 至 CS 9 来代表第 0 个到第 9 个状态 ( 控制步 ), 其中 CS 为 Control Step 之意 本例中, 状态之间的转换仅依靠时钟来进行, 且使用的是单周期调度, 即在每一个时钟的上升沿或下降沿进行状态的转换 采用这种状态转换时, 要求每个控制步中的运算时间都应在一个时钟 312

291 周期之内, 或者说, 电路的时钟周期应大于各控制步运算时间的最大者 图 7-6 连线网络 图 7-7 表示了本例中控制器的状态转换关系 2. 控制码及其优化控制器对数据通道的控制是通过在每一个控制步中向数据通道提供相应的控制码来实现的 所谓控制码就是一组控制信号, 对数据通道中的数据选择 数据传输等数据关系进行控制, 从而使数据通道在当前控制步中对相应的数据进行操作, 实现所需的运算 实际上图 7-6 中的资源以及连线网络就代表了数据通道, 而其中的多路选择器 传输开关的控制信号就组成了数据通道的控制码 从图 7-6 中可以看到, 乘法器 2 个输入端各有 1 个 7 选 1 的多路选择器, 分别由控制信号 ( c 0c1c2 ) 和 ( c 3c4c5 ) 进行控制, 寄存器 y 0 的输入端有 1 个 2 选 1 的多路选择器, 由控制信号 ( c 7 ) 进行控制, 寄存器 y 0 和寄存器 y 1 之间有一个传输开关, 由控制信号 ( c 6 ) 控制 以上所有控制信号的组合 c c c c c c c ) 就称为控制码 ( c7 313

292 图 7-7 控制器状态转换关系 假设 c 0c1c2 =000~110 时, 多路选择器分别选中 x 0 到 x 6 ; c 3c4c5 =000~110 时, 多路选择器分别选中 b0 ~ b 6 ; c 6 =1 时传输开关导通, c 6 =0 时传输开关关闭 ; c 7 =1 时多路选择器选择加法器输出端, c 7 =0 时多路选择器选择 0 根据上述控制信号的控制功能, 以及在每一个控制步中所要求的数据运算关系, 可以得到如表 7-9 所示的控制码表 表中 CS 0 至 CS 9 代表第 0 到 9 个控制步,c 0 至 c 7 代表一个控制码中的各位控制信号, 控制信号取值 x 代表对应的控制信号可以取 0 和 1 中的任意值 表 7-9 控制码表 c 0 c 1 c 2 c 3 c 4 c 5 c 6 c 7 CS 0 x x x x x x 0 0 CS CS CS CS CS CS CS CS 8 x x x x x x 0 x CS 9 x x x x x x 0 x 上表实际上就是控制器在每个控制步中输出的控制码 控制码是用控制器中的逻辑电路产生的, 控制码优劣会影响该逻辑电路的繁简 所以 314

293 为了尽量简化控制器的控制码产生逻辑, 减小芯片面积, 需要对得到的控制码做一些优化 评估控制码的质量主要有两个方面 : 一是完成整个算法所需控制码的数量, 二是每个控制码的码长 在表 7-9 所示的控制码表中,10 个控制步分别有不同的控制码, 因而共有 10 个控制码, 每个控制码由 c 0 到 c 7 组成, 因而控制码码长为 8 所谓对控制码的优化, 就是从减少控制码数量和减小控制码码长这两个方面来进行优化 要减少控制码数量, 就意味着应尽量使不同的控制步使用同一个控制码, 或者某个控制步的控制码可以由其他某个控制步的控制码经过简单的逻辑关系得到 ; 要减小控制码码长, 就意味着应尽量找出某些控制信号间的关系, 由其中某个信号通过简单的逻辑关系衍生出其他的控制信号, 而不必使这些控制信号都独立产生 进行控制码的优化, 除了在初始控制码表中存在的逻辑关系外, 还可以人为地将表中的 x 项指定为 1 或 0, 以满足某种逻辑关系 下面对表 7-9 所示的控制码表进行优化 1) 可以发现,CS 8 和 CS 9 的控制码是相同的, 另外, 如果将 CS 8 CS 9 中的 c 7 的 x 指定为 0, 那么 CS 8 CS 9 和 CS 0 的控制码也是相同的 所以可以将这 3 个控制码合并为 1 个控制码, 从而减少了控制码的数量 2) 在每个控制码中,(c 0 c 1 c 2 ) 和 (c 3 c 4 c 5 ) 是相同的, 即 c 0 =c 3,c 1 =c 4,c 2 =c 5, 那么只需要 (c 0 c 1 c 2 ) 就可以了,(c 3 c 4 c 5 ) 可以由 (c 0 c 1 c 2 ) 得到 所以可以舍去 (c 3 c 4 c 5 )3 位, 把控制码由原来的 8 位变为 5 位 经过以上的优化, 得到表 7-10 所示的经优化过后的控制码表 其中, 新的控制码 (cc 0 cc 1 cc 2 cc 3 cc 4 ) 和原来 8 个控制信号 (c 0 ~c 7 ) 之间的关系为 : c 0 =cc 0,c 1 =cc 1,c 2 =cc 2,c 3 =cc 0,c 4 =cc 1,c 5 =cc 2,c 6 =cc 3,c 7 =cc 4 到这里为止, 就完成了控制器部分的设计 表 7-10 优化后的控制码表 cc 0 cc 1 cc 2 cc 3 cc 4 CS 0 CS 8 x x x 0 0 CS 9 CS CS CS CS CS CS CS 上面对 thfir6td 中的滤波运算即译码输出部分进行了高层次综合 首先根据数据运算关系得到了数据控制流图 ; 然后根据图中的算子和数据依赖关系进行了算子调度, 将整个运算过程划分为若干个控制步 ; 接着在资源复用的原则下进行资源的分配 ; 然后再根据每一个控制步中的数据运算关系在数据和资源之间建立连线网络, 组成数据通道 ; 最后设计 315

294 了控制器部分, 并对控制码进行了优化 高层次综合后, 得到了 thfir6td 中滤波运算和结果输出部分的实现方案 下面要做的就是将这个方案用 VHDL 语言描述出来, 以进行对方案的仿真验证, 进而进行逻辑综合 高层次综合结果的 VHDL 描述及仿真 在此不准备给出以上高层次综合得到的方案的 VHDL 语言描述, 因为在上面的高层次综合过程中, 对该方案已经给出了比较详细的说明, 读者很容易自己根据这个方案写出 VHDL 源代码 为了节省篇幅, 在这里只简要地介绍一下用 VHDL 语言描述高层次综合结果的思路 一般来说, 高层次综合结果的电路都是 数据通道 + 控制器 的结构形式, 所以进行 VHDL 描述一般分为以下几个部分 : 1) 设计者首先要给出电路中所用资源的 VHDL 描述 资源的 VHDL 描述可以是行为级的也可以是结构级的, 例如本例中就需要给出乘法器 加法器 译码器 二 - 十进制转换模块 开方模块 多路选择器以及传输开关等资源的 VHDL 描述 2) 设计者根据连线网络用 VHDL 语言描述各个资源与数据信号 寄存器 控制信号之间的连接关系, 即描述整个数据通道 3) 设计者根据算子调度方案和控制码对控制器进行 VHDL 描述, 控制器一般被描述为有限状态机形式 4) 设计者在一个最高层的实体描述中, 将上面描述的数据通道部分和控制器部分相互连接, 组成系统 在整个电路描述完成后, 仍采用测试平台 (Test Bench) 的方式对其进行模拟仿真 测试平台的编写请参见 节中的有关内容, 这里不再赘述 7.4 行为级设计与高层次综合结果比较 以上几节对同一电路 thfir6td 分别进行了行为级设计和高层次综合设计, 并用 VHDL 语言描述了这两种设计 在得到电路的 VHDL 描述之后, 可以对该 VHDL 描述进行逻辑综合, 从而得到电路的门级网表 作为比较, 分别对 thfir6td 电路的行为级 VHDL 描述和高层次综合的 VHDL 描述进行逻辑综合 因为在进行高层次综合的时候, 只考虑了电路中滤波和结果输出部分, 为了便于和行为级中相同部分的综合结果进行比较, 对原来给出的行为级 VHDL 描述进行了简化, 也只保留其中的滤波和结果输出部分, 去掉了计时器部分 作者在 SUN ULTRA 系列工作站上, 用 Synopsys 公司的逻辑综合工具 design-compiler 对上述行为级描述和高层次综合描述的 VHDL 源文件进行了逻辑综合, 得到门级电路, 并由该综合工具分别对两种描述综合所得的门级电路进行分析, 得到如表 7-11 所示的有关设计参数 316

295 表 7-11 行为设计和高层次综合设计的逻辑综合电路比较 高层次综合设计 行为设计 Number of nets Number of cells Number of references Total cell area 表 7-11 中所列的电路参数的意义如下 : 1)Number of nets: 电路中的信号线数目, 代表同一信号的所有连线算做一条信号线, 这里的信号仅指电路内部的中间信号, 不包括电路的端口 2)Number of cells: 电路中的单元数目 单元指的是电路中的逻辑功能模块, 如反相器 与非门 触发器等, 甚至包括加法器 乘法器等, 所有单元的个数即电路中的单元数目 3)Number of references: 电路中的被引用单元数目 被引用单元指的是电路中的单元原型 尽管同一类型的单元可能有多个, 但它们都对应同一单元原型, 在计算被引用单元时只算做一个 4)Total cell area: 电路的面积 这个面积是根据电路中各单元和连线的面积得到的大致估计的结果 需要注意, 被引用单元数和单元数是不同的, 它们的区别是 : 被引用单元数指的是单元的种数, 而单元数指的是单元的个数 例如, 一个电路由 2 个反相器和 3 个与非门组成, 则该电路的单元数为 5, 被引用单元数为 2 从表中可以看出, 由高层次综合设计得到的电路的各项参数都较行为设计得到的电路小, 这意味着高层次综合设计得到的电路具有较小的资源消耗, 较低的成本 这是因为在高层次综合设计时, 考虑到了在完成一个算法功能过程中分步进行, 复用资源 这也正是高层次综合设计的优势所在 7.5 总结 本章对同一电路 thfir6td 分别进行了行为级设计和高层次综合设计, 并对这两种设计用 VHDL 语言进行了描述和仿真, 最后对这两种设计进行了逻辑综合, 并对逻辑综合得到的电路进行了比较 在这个例子中, 首先从最直观的行为功能出发, 划分了电路的功能模块, 用 VHDL 语言进行了行为功能的描述和仿真验证 ; 然后对其中的主要算法行为 滤波及输出部分进行了高层次综合, 根据该算法的数据控制流图进行了算子调度, 将整个行为功能的实现过程划分为若干个控制步, 在控制步中进行了资源分配, 使得电路能够尽可能多地复用资源 ; 然后在数据和资源之间根据运算关系确定了连线网络 ; 最后设计了控制器的状态转换关系, 优化了控制码, 使得由资源和连线网络组成的数据通道在控制器的控制下, 按照预先划分的控制步完成算法功能 同样, 对得到的高层次综合的方案也用 VHDL 语言进行了描述和仿真验证 为了进行比较, 还对两种设计的 VHDL 描述进行了逻辑综合, 比较了所得到的 317

296 门级电路 从设计过程中可以看到, 行为设计和高层次综合设计是不同的 行为级的设计更侧重于考虑电路功能的实现, 而不过于仔细地考虑具体的实现方式, 所以在行为级描述中更多地直接采用了抽象符号 数学表达式等 ; 高层次综合的设计比行为级设计更进了一步, 它不但要考虑算法功能的实现, 还要考虑该算法过程如何更有效 更经济地实现, 在满足功能要求和相关约束的前提下, 通过算子调度 资源分配等过程, 得到一个最佳或相对较佳的实现方案 从两种设计的逻辑综合结果可以看到, 两种设计所对应的门级电路是不同的 行为级设计由于比较抽象, 在描述方式上仅给出数学表达式, 所以在逻辑综合时往往一个算子对应一个运算单元, 消耗的资源比较多, 电路面积比较大, 但各资源的运行是并行的, 所以电路速度较快 ; 而高层次综合设计通过考虑算子调度和资源复用, 有效地控制了电路中资源的数目, 所以在逻辑综合时往往较节省资源, 面积较小 但由于要达到资源的复用, 就要将利用同一资源进行的运算分在不同的控制步进行, 这就要比利用多个资源并行运算花费更多的时间, 导致速度的下降 所以高层次设计过程往往是一个面积和速度折衷的过程 本章举这个设计实例的目的有两个 : 一是为了说明如何运用 VHDL 对电路的行为功能进行描述, 二是为了演示高层次综合的整个过程 所以, 在行为级设计部分, 主要侧重于使用 VHDL 这种语言, 因而详细地给出了 VHDL 描述的源代码 ; 而在高层次综合设计部分, 主要侧重于阐述高层次综合的过程和方法, 让读者对高层次综合设计有一个感性认识, 因而仅对高层次综合中的各个步骤和所得的结果进行了比较详细的说明, 而没有给出最终方案的 VHDL 描述, 有兴趣的读者可自行用 VHDL 描述得到的实现方案 本章所举的例子相对比较简单, 作者认为这样已经足以并更有利于清晰地说明相关概念, 演示相关的过程和方法, 而使读者不被问题本身的复杂性所干扰 在理解概念和掌握方法的基础上, 读者完全可以将这些概念和方法应用到更复杂的场合中 需要指出的是, 这个例子涉及的内容比较简单, 而对于复杂的电路, 对其进行高层次综合就需要采用第 6 章中介绍的更复杂的理论和方法 在大多数实际应用中, 高层次综合过程往往很难用手工进行 在理想情况下, 高层次综合设计应该可以借助于 EDA 工具完成, 目前用于高层次综合的较成熟的 EDA 工具是 SYNOPSYS 公司的 Behavior Compiler 318

297 第 8 章 部分 VHDL 工具软件使用指南 前面几章讲解了 VHDL 的语言规范和如何用这种语言来描述想要设计的电路 设计者把自己对一个电路的功能设想用 VHDL 语言描述出来, 只是设计的第一步 设计者还需要对这个 VHDL 语言描述出来的电路进行验证 修改, 保证描述没有错误, 然后进行综合, 转换成逻辑实现 电路实现乃至版图实现 VHDL 设计的验证 综合等过程就需要借助 VHDL 的工具软件来完成 事实上, 学习 VHDL 的目的之一就是为了使用 VHDL 的工具软件来进行设计 VHDL 是设计者和 EDA 工具的 共同语言, 通过这种语言,EDA 工具就能了解设计者的意图, 帮助设计者验证 修改 实现一个设计 如果没有 VHDL 的工具软件,VHDL 无法发挥它在设计实现上的作用 为了使读者能真正地在设计实践中应用所学到的 VHDL 知识, 本章选择了目前国内比较常见的 应用较为普遍的 VHDL 工具软件介绍给读者, 包括 VHDL 的模拟仿真软件 Active-VHDL 和可将 VHDL 描述转换为 FPGA 实现的工具 MAXPLUS II 在每种工具的介绍中, 首先讲解了该工具的整体概貌和主要功能, 然后有一个实际操作的例子, 读者可按照例子中的步骤, 一步一步地学习 掌握使用该软件的基本过程 现在,VHDL 的工具软件越来越多, 功能越来越强大和复杂, 由于本书并不是专门介绍 VHDL 工具软件的, 所以本章只能起到一个抛砖引玉的作用, 仅仅选择介绍了部分 VHDL 工具的基本使用, 希望读者能够在本章基础上, 在实践中不断学习 提高 8.1 集成电路 EDA 工具概述 集成电路 EDA 工具的主要领域 EDA 是 Electronic Design Automation 的缩写 集成电路设计工作的日益复杂化要求有越来越强大的设计自动化工具来帮助设计者完成一个设计, 在这种市场需求的推动下, 加上电子计算机技术 计算机辅助设计技术的发展, 集成电路 EDA 工具种类越来越多, 功能越来越强大和全面, 几乎覆盖了集成电路设计中的每一个层次 在集成电路设计的每个层次上, 大致都有描述 模拟验证 综合等 3 种类型的工作, 所以这 3 个领域也成为集成电路 EDA 工具开发所主要针对的领域 1. 硬件描述语言硬件描述语言 (Hardware Description Language 或 HDL) 是设计者和 EDA 工具的界面, 设计者通过硬件描述语言描述自己的设计对象 EDA 工具通过硬件描述语言了解设计者的意图, 帮助设计者完成设计 所以对 EDA 工具来说, 首先要有一种它能识别 接受的描述语言标准, 然后要具有对这种描述语言的足够的处理能力, 这种处理包括对一个采用该语言的描述进行各种语法分析检查 将所描述的信息转化为工具内部的便于处理的数据格 319

298 式等, 这个过程通常称为 编译 EDA 工具所支持和采纳的硬件描述语言有图形化的, 也有文本化的 ; 有经过国际组织标准化的通用的硬件描述语言, 如 VHDL 和 Verilog HDL, 也有 EDA 工具开发商为其工具制定的专用的硬件描述语言, 如 ALTERA 公司的 AHDL 等 2. 模拟验证所谓模拟验证, 是指对实际数字系统加以抽象, 提取其模型, 然后将外部激励信号施加于此模型, 通过观察模型在外部激励信号作用下的反应, 判断该数字系统是否实现了预期的功能 模拟技术是当前数字系统验证的主要手段, 但它具有一定的局限性 它的局限性在于 : 模拟器的功能仅仅是表现该数字系统在某一组外部激励信号下的行为, 至于加什么样的外部激励信号, 以及在该外部激励信号下数字系统的反应正确与否, 需要设计者自己决定和判断 所以,EDA 工具的模拟器需要以下信息 : 数字系统基本元件的功能特性 ; 数字系统基本元件的互连关系或相互作用关系 ; 外部激励信号的名称和数据, 观察点信号的名称及其数据的输出形式 因为一个设计的表示方法有多种层次, 所以对一个设计进行验证的模拟过程也有不同的层次 模拟过程一般涉及下面 3 个层次 : 1) 寄存器传输级模拟寄存器传输级描述的基本元件是运算单元 ALU 寄存器 总线 多路选择器等 此时, 元件的延时特性 负载特性一般来说是未知的 寄存器传输级模拟主要用来验证电路的行为 功能 2) 逻辑级 ( 门级 ) 模拟门级描述的基本元件是逻辑门及触发器等 门级描述表达电路中基本元件的互连关系, 即电路的结构信息 当寄存器传输级验证完成之后, 应将寄存器传输级的元件具体化为门级元件 这些元件的延时特性 负载特性在门级模拟中应当有较为精确的描述 门级模拟用于验证门级电路的正确性, 除验证功能的正确性外, 还可估计电路的时序 负载是否达到设计要求 3) 电路级模拟电路级描述的基本元件是晶体管 电阻 电容等 电路级描述表达电路中基本元件的互连关系 当门级验证完成之后, 应将门级的元件具体化为电路级元件 电路级模拟可以模拟得到电路各点的电压电流等物理信号值 ( 而不是象门级模拟中 0,1 等逻辑信号值 ), 它主要用于对模拟电路或数字电路的模拟参数进行验证 另外, 电路级的模拟分为布图前电路模拟和布图后版图提取参数电路模拟 布图前的电路中的元件模型虽然也有自身的参数, 但并不包括在版图中所产生的寄生参数 例如布图前电路图上的一条互连线是一条理想的导线, 而硅片上的互连线则具有寄生电容 寄生电感等, 这些参数在布图前的电路模拟中并未考虑 当这些寄生参数影响较大时, 布图前的电路模拟结果已不足以证明实际在硅片上实现的电路的正确性 所以应根据版图及工艺技术, 提取电路的寄生电容 寄生电阻等参数, 在考虑这些参数的情况下对电路再次进行模拟验证 对于深亚微米工艺而言, 布图后版图提取参数电路模拟成为不可缺少的重要环节 320

299 3. 综合技术模拟或分析的方法是分析验证某一数字系统是否实现了预期的功能, 其前提是已有了一个现成的设计方案 而综合是分析的逆过程, 其前提是给定了电路应实现的功能和实现此电路的约束条件 ( 速度 面积 功耗 电路类型等 ), 目标是得到一个满足上述要求的设计方案 一个电路有各种层次上的描述, 一个电路的设计过程实际上就是电路描述从高层次 ( 行为 ) 向低层次 ( 物理实现 ) 转化的过程 这种转化称之为综合 综合技术主要包括 3 种类型 : 高层次综合 逻辑综合和版图综合 高层次综合负责将系统算法层的行为描述转化为寄存器传输层的结构描述 ; 逻辑综合负责将系统寄存器传输层的结构描述转化为逻辑层的结构描述, 以及将逻辑层的结构描述转化为电路层的结构描述 ; 版图综合负责将系统电路层的结构描述转化为版图层的物理描述 对实现综合功能的 EDA 工具来说, 它要求设计者提供对电路预期功能的行为描述, 一般是具有行为描述能力的硬件描述语言, 以及对电路的约束条件, 包括速度 面积 电路类型 ( 面向 ASIC 或 FPGA) 等 综合的结果是一个设计方案, 该方案必须满足预期功能和约束条件的要求, 这样的方案可能有多个, 综合工具可按一定的算法产生一个最优或较优的结果 集成电路 EDA 工具的构成 虽然用于集成电路设计的 EDA 软件工具很多, 功能也不尽相同, 但它们都是由一些大致功能相似的子系统组成的 了解 EDA 工具的一般构成, 对于新的 EDA 工具就能举一反三, 便于读者更快地学习 使用新的 EDA 工具 一个 EDA 软件系统可能包含下面一些子系统 : 设计输入子系统 (Design Entry) 该子系统接受用户的设计描述, 并进行语法 语义的检查 在检查通过后, 将用户描述的信息转化为 EDA 系统内部的数据格式, 存入设计数据库中 该子系统接受的设计描述一般包括文本的 图形的或图文混合的描述 因此, 该子系统一般包括一个文本的 图形的或图文混合的编辑器和一个对所输入的描述的分析器 设计数据库子系统 (Design Database) 该子系统存放系统及用户提供的库单元以及系统工作过程中的中间结果 分析验证子系统 (Analysis and Verification) 该子系统包括各个层次的模拟验证 设计规则检查 故障诊断等 综合子系统 (Synthesis) 该系统包括各个层次的综合工具 布局布线子系统 (Place and Route) 该子系统实现从逻辑设计到物理实现的映射, 它与物理实现的方式相关 例如, 最终的物理实现方式可以是门阵列 标准单元 可编程逻辑阵列等, 每种物理实现方式对应的布局布线工具会有很大差异 划分子系统 (Partition) 321

300 该子系统完成将一个大规模的电路划分为若干个较小规模的电路的功能 事实上, 很少有一个 EDA 工具软件集成了上述所有的子系统功能, 实际的 EDA 软件只是由其中的某个或某几个子系统组成的 另外, 各种 EDA 工具的侧重点和优势各不相同, 有的长于模拟, 有的长于综合, 设计者需要根据自己的要求选择适合的 EDA 工具 8.2 Active-VHDL 使用指南 Active-VHDL 概貌 Active-VHDL 是 ALDEC 公司所开发的用于 VHDL 的编写 调试 模拟的工具软件 它可以对一个 VHDL 设计进行行为级的模拟, 也可以对一个 VHDL 设计配上相应的标准延时格式 (SDF) 文件, 进行时序模拟 1.Active-VHDL 的功能 Active-VHDL 为 VHDL 设计者提供了一个高度集成的环境, 使设计者可以在其中完成从 VHDL 代码编写 编译到调试 模拟的整个过程 Active-VHDL 支持 VHDL-93 的语言标准 Active-VHDL 的核心是 VHDL 的模拟, 它可以进行基于 VHDL 代码语句的调试和信号波形模拟 Active-VHDL 是对 VHDL 设计进行模拟 验证的强大工具 另外, Active-VHDL 对每一个 VHDL 设计引入 项目 的概念进行管理, 在工具内部提供对所涉及的设计文件的管理功能 2.Active-VHDL 的界面及主要工具窗口一个应用 Active-VHDL 的典型的界面如图 8-1 所示 322 图 8-1 Active-VHDL 的典型界面 该界面和其他 WINDOWS 应用程序相似 标题栏中显示了当前打开的项目名称和文件

301 名称, 例如上图中, 当前项目名为 counter8, 当前文件名为 counter8.vhd 标题栏下面是该工具的菜单栏和快捷工具按钮 整个窗口部分分为 3 个子窗口, 左边为设计浏览器 (Design Browser), 用于对各种设计资源文件的浏览 管理 ; 右边上面为文档窗口 (Document Window), 它是多种工具的公用窗口, 包括 VHDL 编辑器 (VHDL Editor) 波形编辑器 (Waveform Editor) 库管理器(Library Manager) 等都共同占用这一窗口, 这些不同的工具窗口通过文档窗口下方的标签来切换, 当切换到不同工具窗口时, 主窗口中的菜单和快捷按钮会有相应的改变 ; 右边下面为命令行窗口 (Console), 用于输入和显示命令行, 并显示命令执行的有关输出信息 Active-VHDL 中包括下列一些主要工具窗口 (1) 命令行窗口 (Console) 命令行窗口是 Active-VHDL 的交互性的输入输出文本窗口, 它用于输入 Active-VHDL 命令行, 输出执行命令 ( 通过命令行或单击菜单等方式 ) 后的相关信息 如图 8-2 所示 图 8-2 Active-VHDL 的命令行窗口该窗口下方有 4 个标签, 分别为 Console Find Compilation Simulation, 切换不同的窗口内容, 分别显示不同种类的信息 命令行 (Console) 标签该部分显示所执行的命令以及命令执行后的主要信息 在这个窗口中可由用户输入 Active-VHDL 的命令行,Active-VHDL 中所有用菜单或工具按钮执行的命令都可以通过 Active-VHDL 的命令行来执行, 关于 Active-VHDL 的命令行的格式及语法, 请查阅相关资料 查找 (Find) 标签如果使用了菜单命令 Search >Find in Files 查找某一字符串, 则所有符合查找条件字符串条目会列出在这一窗口中 双击某一条目, 则文档窗口就会定位到该条目所指示的所查字符串的出现位置 编译 (Compilation) 标签该部分显示编译器所产生的信息, 例如编译源文件时的错误信息 双击错误信息条目, Active-VHDL 就会打开相应的源文件并定位错误位置 模拟 (Simulation) 标签该部分显示模拟器所产生的信息 (2) 设计探索器 (Design Explorer) 设计探索器用于对各个设计项目的管理 一个设计项目是指与一个电子设计相关的所有源文件和设计过程中软件工具产生的文件的有一定组织结构的集合 设计探索器显示了 323

302 所有 Active-VHDL 的设计项目, 每个设计项目被显示为一个图标 用户建立一个设计项目后, 该设计项目就自动成为设计探索器中的一个图标 设计探索器可以对这些图标进行管理 需要特别注意的是, 每个图标与一个实际存在的设计项目相关联, 但设计探索器并不涉及这些设计项目在计算机硬盘上的实际存在位置, 对图标的操作也不会影响它们所代表的设计项目在计算机硬盘上的内容和位置 所以一个设计项目可以有多个图标, 同目录下可以有同名的但代表不同设计项目的图标 如图 8-3 所示 图 8-3 Active-VHDL 的设计探索器窗口窗口的左边部分是设计项目图标的目录结构 ( 注意, 这不是设计项目的所在目录, 所显示的目录并不在计算机硬盘上实际存在 ); 窗口的右边部分是当前选中图标目录下的所有设计项目图标 ; 窗口底部的状态栏显示的是当前选中的设计项目在硬盘上的实际路径 菜单和工具按钮可以对图标和目录进行操作 可以看到, 一个设计项目图标与一个.adf 文件相关联,.adf 文件是 Active-VHDL 的项目管理文件, 记录一个设计项目的有关信息 对用户建立的每个设计项目, 设计探索器都在 Active VHDL Designs 目录下为其自动建立一个图标 用户可以在设计探索器中删除一个已有的设计项目图标, 或者新建一个设计项目图标并把它指向一个设计项目文件 ; 用户还可以建立或删除图标的目录, 以及将设计项目图标在各个目录间移动 (3) 设计浏览器 (Design Browser) 设计浏览器用于显示当前打开的设计项目中的内容和它们之间的层次结构 设计浏览器有 3 个标签, 分别为 Files Structure Resources, 每个标签窗口分别显示各自的内容 文件 (Files) 标签该窗口显示当前设计项目中的源文件及工作库 (VHDL 标准中的 WORK 库 ) 如图 8-4 所示 窗口的上面部分显示了设计项目中的源文件, 并显示了源文件中所包含的 VHDL 对象 VHDL 对象是指编译后的 VHDL 实体 构造体 配置等 不同类型的源文件和 VHDL 对象由不同的图标表示 窗口中的最后一项是设计项目的工作库, 并显示了工作库中包含的 VHDL 对象 这个工作库就相当于 VHDL 标准中的 WORK 库 324

303 结构 (Structure) 标签该窗口显示设计的 VHDL 结构 需要注意, 一个设计项目只有经过编译和模拟初始化, 其 VHDL 结构才能在此窗口中显示 如图 8-5 所示 窗口分为上下两个部分 上半部分显示了设计的 VHDL 层次结构, 下半部分显示了在上半部分选中的某一结构中包含的 VHDL 对象,VHDL 对象包括信号 (signal) 变量 (variable) 常量(constant) 端口(port) 类属 (generic) 等 ; 一个对象分为若干栏内容, 包括对象名 (Name) 对象的 VHDL 类型 (Type) 对象的当前值(Value) 前一次值 (Last Value) 最后一次事件发生时间(Last Event Time) 等 资源 (Resources) 标签该窗口可对设计项目中所有文件按照其类型 ( 体现为文件的扩展名 ) 进行显示 如图 8-6 所示 图 8-4 Active-VHDL 的设计浏览器文件标签窗口 图 8-5 Active-VHDL 的设计浏览器结构标签窗口 325

304 窗口中的目录图标并不是实际存在的目录, 只起一个对文件分类的作用, 每个目录都与一种文件扩展名相关联, 目录所关联的文件扩展名可以在目录的属性对话框中指定或修改 选中某一目录单击右键, 在弹出菜单中单击 Properties, 即出现目录属性对话框 每个目录下方显示的文件就是当前设计项目中具有该目录关联的扩展名的所有文件 这些文件实际存在的路径可以是不在同一目录下 (4)VHDL 编辑器 (VHDL Editor) VHDL 编辑器窗口用于编辑 VHDL 源代码 另外, 该窗口还与编译器和模拟器相关联, 可以指示编译过程中的错误语句, 还可以在该窗口对 VHDL 源文件进行逐句的模拟 调试 如图 8-7 所示 图 8-6 Active-VHDL 的设计浏览器资源标签窗口 图 8-7 Active-VHDL 的 VHDL 编辑器窗口窗口上部为编辑工具按钮, 其功能包括剪切 复制 粘贴 查找 替换 行定位 文本块的注释及其清除注释以及书签浏览等 ; 下部左边为行号区 (line number) 和空白区 (margin), 右边为编辑区 (editing workspace) 在空白区中可能出现红色的错误指示标志 (error mark) 和蓝色的书签标志 (bookmark) 326

305 (5) 库管理器 (Library Manager) 库管理器窗口用于管理 VHDL 的库 库管理器窗口左下方有 Libraries 和 Contents 2 个标签 库列表 (Libraries) 标签该窗口显示 Active-VHDL 中所有可用的 VHDL 库的列表, 列表上的每个库具有逻辑名 在硬盘上的路径 只读属性 备注等项内容 ( 如图 8-8 所示 ) 库内容 (Contents) 标签该窗口显示在 Libraries 标签窗口所选中的库包含的内容 包括 VHDL 的实体 (Entity) 构造体(Architecture) 配置(Configuration) 包(Package) 及包体 (Package body) 等 ( 如图 8-9 所示 ) 图 8-8 Active-VHDL 的库管理器库列表标签窗口 图 8-9 Active-VHDL 的库管理器库内容标签窗口 327

306 在库管理器窗口中, 用户可以为当前项目新建一个库, 引入一个库, 或者删除一个库 ( 这里的删除并不是从硬盘上删掉库文件, 而只是使得一个库在当前项目中成为不可见 ), 这些操作可通过库管理器窗口上方的 3 个工具按钮实现 (6) 波形编辑器 (Waveform Editor) 波形编辑器窗口用于模拟时编辑激励波形和观察模拟结果波形 用户可在该窗口中定义需观察的信号, 图形化地编辑模拟需要的激励信号, 以及在模拟调试过程中观察所得的结果波形 波形编辑器窗口中的波形还可以被保存为不同格式的波形文件,Active-VHDL 中的测试平台生成向导 (Test Bench Wizard) 工具可按照波形文件在产生的测试平台 (Test Bench) 中施加相应的激励波形 如图 8-10 所示 图 8-10 Active-VHDL 的波形编辑器窗口窗口上方有一排工具按钮, 其功能包括对波形剪切 复制 粘贴, 波形的缩放 测量, 波形的定位以及按书签浏览等 ; 下方是波形显示区 每个被显示的信号为一行, 并分为若干栏内容, 包括信号名 信号类型 信号在光标所在处的值 输入信号的激励源以及信号在时间坐标上的波形等 (7) 列表窗口 (List Window) 列表窗口也是用来编辑和观察信号的, 但与波形编辑窗口不同, 它是以表格的方式来列出要观察的信号及该信号在模拟过程中每个时刻的值 如图 8-11 所示 窗口的上方有 3 个工具按钮, 分别用来添加一个信号 切换是否显示每个模拟时刻的所经过的 o 周期数以及定位到某个时刻 (8) 监视窗口 (Watch) 监视窗口用来显示在模拟 调试过程中所选择的 VHDL 对象的当前状态 如图 8-12 所示 328

307 图 8-11 Active-VHDL 的列表窗口 图 8-12 Active-VHDL 的监视窗口在该窗口中, 一个被显示的 VHDL 对象分为若干栏 最左边没有标题的一栏为事件标志栏, 如果当前模拟周期中一个信号上有事件发生, 则在该信号的事件标志栏中就出现一个红色的惊叹号标志 另外的栏目还有 : 对象名称 (Name), 对象在 VHDL 中的类型 (Type), 对象当前值 (Value), 对象在最后一次事件发生前的取值 (Last Value), 对象最后一次事件发生的时间 (Last Event Time) 等 其中, 对象在最后一次事件发生前的取值 (Last Value) 和对象最后一次事件发生的时间 (Last Event Time) 两栏, 仅在对象为信号 ( 包括端口 ) 时才用到 (9) 进程窗口 (Processes Window) 进程窗口用于显示模拟过程中所有进程列表及它们的当前状态 该窗口仅在运行模拟期间才是有效的 如图 8-13 所示 每个被显示的进程分为 3 栏 : 进程标识符 (Label) 进程层次路径(Path) 和进程当前状态 (Status) 对于源文件中未给出进程标识符的用 line + 进程所在行的行号 代表 进程状态有两种, 分别为 Ready 状态和 Wait 状态 Ready 状态表示该进程将在这一模拟周期内被执行, Wait 状态表示该进程正等待激发它被执行的事件的发生 一个进 329

308 程被执行后, 其状态由 Ready 变为 Wait 各进程的执行顺序就是它们在该窗口列表中由上到下的顺序 图 8-13 Active-VHDL 的进程窗口 (10) 测试平台生成向导 (Test Bench Generator Wizard) 测试平台生成向导用于根据用户的指定, 自动生成一个测试平台 (Test Bench) 以及在该测试平台上进行模拟测试的有关宏命令文件 这里简单说一下测试平台 (Test Bench) 的概念 VHDL 的测试平台就是用 VHDL 写成的一个没有任何端口的实体, 它内部含有一个待测元件 ( 要求是经过编译的实体 ) 以及一系列在待测元件的输入端口上施加激励的进程 对整个测试平台进行 VHDL 的模拟并观察元件的各端口信号, 就起到对待测元件施加激励和观察响应的作用, 达到测试的目的 之所以要将待测实体作为测试平台下层的一个元件, 是因为在 VHDL 中对一个实体的输入端口是不能赋值的, 因而就无法施加激励信号, 但如果将一个实体作为另一个高层实体的元件, 则待测实体的输入端口就成了高层实体的内部信号, 可以在进程中赋值, 测试平台就起了这样一个高层实体的作用, 它本身不需要任何端口 用 VHDL 写成的测试平台具有通用性和灵活性的特点 因为它本身就是用 VHDL 语言写成的, 所以它可以在任何支持 VHDL 的软件工具上使用 可能有的 VHDL 软件工具无法进行图形化的波形编辑和模拟, 但都能接受测试平台的模拟 另外, 一个设计实体可以有不同的构造体来实现, 例如综合前的行为描述和综合后的结构描述 ( 可能带延时信息等 ), 那么在测试平台中只需对待测元件进行相应的配置, 就可对不同的构造体进行模拟 对测试平台不熟悉的读者, 可以用 Active-VHDL 中的测试平台生成向导生成一个 VHDL 的测试平台文件, 查看一下源代码, 就能更好的理解其中的概念 Active-VHDL 的测试平台生成向导共有 4 个步骤, 在每一步需要用户输入用于生成测试平台的有关信息 这些信息包括待测的实体 构造体 测试平台类型 所用的激励文件等 其中, 测试平台类型和所用的激励文件是相关的 测试平台类型有两种, 它们施加激励信号的方式不同 : 一种是通过 VHDL 的信号赋值进程施加激励信号值 ; 另一种是通过 VHDL 的文件读写过程从向量文件中取得激励信号值, 该向量文件采用 WAVES(Waveform and Vector Exchange to Support Design and Test Verification) 标准 前一种测试平台的生成所需的激励文件可以是 Active-VHDL 的波形文件 (.awf) 或 VHDL 进程描述文件 (.vhs); 后一种测试平台的生成所需的激励文件可以是 Active-VHDL 的波形文件 (.awf) 或 WAVES 向量文件 (.vec) 这些文件可以在波形编辑器窗口中通过保存或输出得到 在波形编辑器窗口中, 所编辑的波形可以保存为 Active-VHDL 的波形格式 (.awf), 也可以保存为 VHDL 330

309 进程格式 (.vhs) 或 WAVES 向量文件格式 (.vec) 图 8-14 是测试平台生成向导的第 1 步 对测试平台生成向导的具体使用会在后面的一个例子中进行详细说明 图 8-14 Active-VHDL 测试平台生成向导 Active-VHDL 的基本设计流程 尽管所设计的电路之间它们功能不同 要求不同 规模不同, 但在用 EDA 软件工具进行设计时, 一般都遵循一个基本的流程, 在这个基本的流程中, 根据具体情况, 各个步序可繁可简, 或者省略有的步序 了解一个软件工具的使用流程, 是掌握一个软件工具的重要内容 使用 Active-VHDL 的目的主要是用 VHDL 建立一个设计并进行模拟验证 图 8-15 是较为完整的使用 Active-VHDL 进行设计的基本流程 图 8-15 中左边为用 Active-VHDL 进行设计的主要流程, 右边为设计流程各步所对使用的 Active-VHDL 的工具 上面只是列举了流程中一些步骤所用到的特殊的工具窗口, 没有包括在整个设计中都要用到的工具窗口 例如, 始终要用到设计浏览器和命令执行窗口等 从图 8-15 中可以看到, 由于 Active-VHDL 的主要功能是 VHDL 的模拟 调试, 所以对 VHDL 进行模拟 调试的功能较强, 提供的工具也较多, 每种工具可在模拟过程中进行不同方面的观察 但在实际使用过程中, 并不一定需要用到所有的工具, 设计者只需根据自己的需要来选用 331

310 流程流程 流程中涉及使用的工具窗口 1. 建立设计项目 2. 建立设计项目中的设计文件 VHDL 编辑器窗口 3. 对设计项目进行编译 4. 编辑激励信号波形 波形编辑器窗口 5. 按照上一步指定的激励波形进行模拟 调试 VHDL 编辑窗口 波形编辑器窗口 监视窗口 列表窗口 进程窗口 6. 自动生成测试平台 ( 可选 ) 测试平台生成向导 图 8-15 Active-VHDL 基本设计流程 一个实际操作 Active-VHDL 的例子 本节将以具体 实用为目的, 根据一个电路设计的实际例子, 按照上节所给出的设计流程, 完成一个电路设计 模拟 调试的完整过程, 以便于读者迅速掌握 Active-VHDL 的一般应用流程 在这个例子中, 不可能涉及到 Active-VHDL 各方面的特性, 也不可能用到 Active-VHDL 中所有的命令, 但这个过程对于一般的设计是适用和足够的,Active-VHDL 中的一些更高级的特性和更复杂的命令, 有兴趣的读者可查阅联机帮助或专门的资料 本节所用的例子是作者编写的一个 8 位加法计数器, 这个例子很简单, 读者只需将它作为一个电路设计的代表, 主要目的是学习如何应用 Active-VHDL 这个软件 1. 建立设计项目启动 Active-VHDL, 会出现当前设计项目对话框, 询问用户打开已有的设计项目还是新建一个设计项目 如图 8-16 所示 因为还没有载入当前设计项目, 所以 Active-VHDL 主窗口的标题栏里显示 design not loaded 在当前项目的对话框里, 有两种选择 : 打开已有的设计项目 (Open existing design) 或创建新的设计项目 (Create new design) 332

311 图 8-16 启动 Active-VHDL 时建立当前设计项目如果选择 打开已有的设计项目, 在 打开已有的设计项目 选择项下面有最近打开过的设计项目列表, 例如上图中的 counter 和 findmax ; 单击选中某个设计项目, 在列表框下面显示该设计项目对应的目录 如果列表框里没有要打开的设计项目, 可单击列表框右边的 More designs 按钮, 启动设计探索器窗口, 用户可在设计探索器窗口中浏览所有的 Active-VHDL 设计项目并选择打开一个设计项目 在列表框中, 双击一个设计项目或选中一个设计项目后单击 OK 按钮便打开该项目 ; 在设计探索器中, 双击一个设计项目图标便打开该项目 如果选择 创建新的设计项目, 选中此项后单击 OK 按钮, 会出现新项目创建向导 在本例中, 要创建一个新的设计项目 选择 Create new design, 单击 OK 按钮, 出现新项目创建向导对话框 如图 8-17 所示 这一步要求用户填写设计项目名称 设计项目在计算机硬盘上的存放路径以及设计项目对应的缺省工作库 ( 即 VHDL 标准中的 WORK 库 ) 的库名 在一般情况下, 缺省工作库的库名自动和设计项目名相同, 用户也可给缺省工作库指定不同的名字 在本例中, 设计项目名称为 counter8, 存放路径为 d:\users\fangyl\actvhdldesigns, 缺省工作库名与项目名相同 填写完毕后单击 下一步 按钮, 进入下一步 如图 8-18 所示 这一步要求用户选择如何创建一个新项目 有如下 4 种选择 : 在新建的项目中创建一个源文件 把已有的源文件加入到新建项目中来 引入 Active-CAD 设计项目作为新项目 创建一个空的设计项目 333

312 图 8-17 新项目创建向导 ( 基本信息 ) 图 8-18 新项目创建向导 ( 创建方式 ) 用户在此选择不同的选项, 新项目创建向导会根据用户的选项产生不同的后续步骤 在本例中, 选择第 4 个选项 创建一个空的设计项目, 然后单击 下一步 按钮, 进入下一步 如图 8-19 所示 这一步是新项目创建向导的最后一步, 它显示所创建项目各种信息 ( 一般都是用户在前面各步中指定的 ), 用户如发现有不妥之处, 可单击 上一步 按钮返回到向导中相应的步序修改, 如果所有信息正确无误, 可单击 完成 按钮 单击 完成 按钮后, 窗口如图 8-20 所示 334

313 图 8-19 新项目创建向导 ( 完成信息 ) 图 8-20 新项目创建后的界面窗口的标题栏显示了刚创建的项目的名称 counter8 它是一个空项目, 在设计浏览器中没有显示任何的设计文件, 其中的 counter8 library 一项表示当前项目可见的库, 双击它便在右边的文档窗口中启动库管理器显示其内容 可以看到, 除了当前项目的缺省工作库 counter8 外, 新建项目还自动引入了 Active-VHDL 工具所提供的各种库, 用户可直接引用这些库中定义的内容 关于这些库中定义的内容用户可查看库的 VHDL 源代码文件 在 Active-VHDL 的安装目录下有一个 Vlib 目录, 存放有关 Active-VHDL 提供的库文件 在这个 Vlib 目录下每个库都对应一个子目录, 在这个子目录下又有一个 src 子目录, 335

314 含有这个库的 VHDL 源码文件 例如, 在作者的计算机上,IEEE 库的 VHDL 源码文件就在 c:\program files\aldec\active VHDL\Vlib\ieee\src 目录下 到这里, 一个新的设计项目的创建就完成了 这是通过启动 Active-VHDL 后出现的对话框创建这个项目的 如果在启动 Active-VHDL 后出现的对话框中单击 Cancel 按钮, 则没有打开任何当前项目, 如果在已有一个当前项目的情况下要打开或新建另一个设计项目, 可以通过单击 File>Open Design 或 File>New Design 菜单来实现 单击 File>Open Design 菜单后会启动设计探索器, 单击 File>New Design 菜单后会启动新项目创建向导, 在此不再赘述 最后来看一下 Active-VHDL 对设计项目的管理 在新建了一个名为 counter8 的设计项目后,Active-VHDL 自动在所指定的设计项目存放目录 d:\users\fangyl\actvhdldesigns\ 下建立了一个与设计项目同名的目录 counter8 这个目录内有一个与设计项目同名的项目管理文件 counter8.adf, 记录整个设计项目的有关管理信息 ;Active-VHDL 还在这个目录内自动建立了 3 个子目录, 分别为 generic, log 和 src generic 目录存放一些由 Active-VHDL 生成的关于项目信息的文件 ; log 目录存放一些由 Active-VHDL 生成的使用某些命令的日志文件, 包括 console.log 文件 compile.log 文件等 ; src 目录是给用户存放项目中的源文件的, 例如 VHDL 的源代码文件 波形文件等, 在保存这些文件的时候,Active-VHDL 会提示用户将这些文件保存在 src 目录下 当然, 用户也可以自由地选择保存文件的路径 尽管 Active-VHDL 为每个设计项目建立了一个目录, 但一个设计项目并不和一个目录对应, 一个设计项目中的源文件并不一定要在该项目对应的目录下, 它们可以在计算机硬盘上的任何位置, 这意味着 2 种情况 : 一是用户可以将创建的项目文件保存在任何路径下 ; 二是用户可以将任何路径下的文件引入一个设计项目成为这个设计项目的项目文件之一 一个设计项目中的所有文件通过设计项目目录下的设计项目管理文件 (.adf) 进行组织管理, 使得用户在处理一个项目时无须考虑文件的物理位置 尽管如此, 在此仍建议将一个设计项目内的文件组织在这个项目的目录结构下, 这样使得在 Active-VHDL 之外对设计文档的管理也井然有序 2. 建立设计项目中的设计源文件无论新建一个设计项目还是打开一个已有的设计项目, 一个设计项目代表一个用户的设计, 一个设计体现为用户的 VHDL 源文件, 所以在把一个设计项目交给 Active-VHDL 工具处理之前, 首先要在项目中准备好这些源文件 在项目中对项目文件的操作主要有 : 创建文件 加入文件 编辑文件 删除文件等 (1) 创建文件在本例中新建了一个空的设计项目, 还没有任何文件 双击设计浏览器 (Design Browser) 的 Files 标签窗口中的第 1 项 Add New File, 出现如 8-21 所示的对话框 该对话框询问用户如何创建一个新文件 因为可创建的文件类型有两种, 分别为 VHDL 文件和状态图文件 (State Diagram File); 创建的方式也有两种, 分别为使用向导和直接创建空文件, 所以有 4 种选择, 即对话框中选项的前 4 个 对话框中的最后一个选项是将已有的文件加入项目, 属于下面说明的 加入文件 操作 336

315 图 8-21 Add New File 对话框本书是关于 VHDL 设计的, 在讲解工具使用的时候也是针对 VHDL 的 关于如何在 Active-VHDL 中采用状态图进行设计, 请有兴趣的读者参阅联机帮助或专门的资料 如果选择 Create Empty VHDL Source File, 单击 OK 按钮, 那么 Active-VHDL 便在文档窗口中启动 VHDL 编辑器, 编辑器窗口具有空白的编辑区, 用户可在其中编辑 VHDL 源代码 如果选择 Create VHDL Source File Using Wizard, 单击 OK 按钮, 就出现 VHDL 源文件创建向导, 按用户指定的信息建立一个非空的 VHDL 源文件 这个用向导创建的源文件包括了用户设计中一些相对固定的格式化的 VHDL 语句结构, 例如 ENTITY 语句 ARCHITECTURE 语句 PORT 语句等 用户可在此基础上进行进一步的编辑, 添加具体的设计内容 在本例中, 选择 Create VHDL Source File Using Wizard, 单击 OK 按钮, 出现 VHDL 源文件创建向导 如图 8-22 所示 图 8-22 VHDL 源文件创建向导 ( 向导说明 ) 这一步是显示对 VHDL 源文件创建向导的一些说明, 以及询问是否将新建的文件加入设计项目 选择将新建的文件加入设计项目, 然后单击 下一步 按钮, 进入下一步 如 337

316 图 8-23 所示 图 8-23 VHDL 源文件创建向导 ( 名称 ) 这一步要求用户填写 3 项内容, 第 1 项是将要创建的源文件的存放位置, 单击右面的 Browse 按钮, 可选择路径 如果选择的路径是项目目录下的 src 子目录, 则文本框内只显示文件名 ; 如果选择的路径不是项目目录下的 src 子目录, 则文本框内显示完整的路径 用户也可直接在文本框内填入文件名 ( 自动存放在项目目录的 src 子目录下 ) 或者完整的路径 本例将文件存放于项目目录下的 src 子目录中, 文件名为 counter8.vhd 第 2 项是所设计的实体名称, 填入 counter8 作为实体名称 第 3 项是构造体名称, 填入 behavior_arch 作为构造体名称 填写完毕后单击 下一步 按钮, 进入下一步 如图 8-24 所示 图 8-24 VHDL 源文件创建向导 ( 端口 ) 这是 VHDL 源文件创建向导的最后一步, 这一步要求用户填写所设计实体的端口信息, 包括端口名称 端口方向 端口类型 ( 向量类型还需要有上下标 ) 等 用户可单击 New 按钮添加一个端口, 在 Name 文本输入框中填入端口名称 ; 单击 Type 按钮选择端口类型 取值范围 向量上下标等, 或在 Array 一栏选取上下标 ; 在 Port direction 一栏中选择端口方向, 单击 Delete 按钮可删除在端口列表框中被选中的端口 338

317 在本例中, 所设计的 8 位加法计数器有 3 个端口 : 输入端口 CLK 和 RESET, 类型为 STD_LOGIC; 输出端口 COUNT, 类型为 INTEGER, 取值范围为 0 到 255 定义好这些端口后, 单击 完成 按钮, 完成 VHDL 源文件的创建 此时, 设计浏览器窗口出现了文件图标及名称 counter8.vhd, 说明文件已被加入到项目中, 同时文档窗口出现 VHDL 编辑器, 显示了由 VHDL 源文件创建向导自动生成的 VHDL 源代码 在硬盘上, 该文件也被保存在用户在向导中指定的路径下 如图 8-25 所示 图 8-25 通过 VHDL 源文件创建向导创建的 VHDL 文件可以看到, 在向导自动生成的 VHDL 源文件中, 已具备了通常的库说明和使用语句 根据用户的设计写成的实体说明语句, 以及构造体的基本语法框架, 省去了用户的部分键入工作, 用户可在空的构造体中加入自己的设计 到这里, 就利用 VHDL 源文件创建向导创建了一个 VHDL 源文件, 在硬盘上保存了这个文件, 并将它加入到项目中 如果没有使用 VHDL 源文件创建向导而创建了一个文件, 还需要使用 File>Save 或 File>Save As 菜单来保存文件, 以及用设计浏览器窗口中的 Add New File 来把它加入到项目中 (2) 加入文件如果要使一个已经存在的, 但不属于当前设计项目的文件成为该项目中的一个文件, 那么就要在当前设计项目中加入这个文件 需要注意的是, 并不是一个文件被保存在一个项目目录下就意味着这个文件属于这个项目, 而是一定要将这个文件加入到项目中, 其外在表现就是这个文件出现在设计浏览器窗口的 Files 标签窗口中 另一方面, 即使一个文件不在当前项目目录下, 也可以通过加入到项目中, 成为该项目的文件之一 想要在当前项目中加入一个文件, 可双击设计浏览器窗口中的 Add New File 项, 在出现的对话框中选择最后一个选项 Add Existing File, 然后单击 OK 按钮, 即出现添加文件对话框, 用户可在其中定位所要的文件, 将它加入当前的设计项目 339

318 (3) 编辑文件双击设计浏览器窗口中的某个 VHDL 源文件图标, 就可以将这个文件在 VHDL 编辑器中打开, 显示在文档窗口中 因为 VHDL 是文本形式的语言, 所以 Active-VHDL 中的 VHDL 编辑器类似于通常的文本编辑工具 对于共有的文本编辑功能, 在此不再赘述, 只介绍一下 Active-VHDL 中 2 个专门针对 VHDL 语言设置的特有的编辑功能 第 1 个特有的功能是对 VHDL 语句块的注释及清除注释 用鼠标的拖曳选中编辑区的一块文字, 然后单击菜单 Edit>Comment Block, 则被选中区域内每一行的文字前均被加上 VHDL 的注释标志 --, 使得被选中的语句块都成为 VHDL 中的注释 ; 相反, 选中编辑区的一块文字, 然后单击菜单 Edit>Uncomment Block, 则被选中区域内每一行的文字前的 VHDL 的注释标志 -- 均被清除 由于 VHDL 只有行注释标志而没有块注释标志, 所以这项功能对于大量连续语句的注释及清除注释是非常方便的 第 2 个特有的功能是 VHDL 的模板功能, 在 Active-VHDL 中称之为 语言助手 它提供各类语句 模块的 VHDL 语言模板 所谓 VHDL 语言模板, 就是一系列写好了的 VHDL 语句, 它提供一种语法上 形式上的框架, 涉及用户设计具体内容的部分, 会提示用户自行编辑 这使得用户编辑 VHDL 源代码更加快捷 规范, 减少了键入的工作量及语法上的错误 单击菜单 Tools>Language Assistant, 出现模板浏览窗口 如图 8-26 所示 图 8-26 VHDL 语言助手窗口分为左右两部分, 左边是模板分类及名称, 选中某个模板, 右边的窗口便出现该模板的 VHDL 语句 例如图 8-26 中选择实体说明模板, 右边的窗口中就出现了实体说明的 VHDL 语句, 其中大写的 ENTITY_NAME 和加尖括号的 <port_declaration> 是提示用户自行编辑的部分 如果要使用模板的内容, 可以选中右边窗口中的所有语句, 将其复制 粘贴到编辑窗口 本例中, 继续对 8 位加法计数器的 VHDL 源文件完成剩余部分的编辑 整个源文件的代码如例 8-1 所示 340

319 例 8-1 library IEEE; use IEEE.std_logic_1164.all; entity counter8 is port ( clk: in std_logic; reset: in std_logic; count: out integer range 0 to 255 ); end counter8; architecture behavior_arch of counter8 is process (clk,reset) variable counter: integer range 0 to 255; if reset='1' then counter := 0; elsif clk='1' and clk'event then if counter=255 then counter := 0; else counter := counter + 1; end if; end if; count <=.counter; end process; end behavior_arch; 想要设计的计数器要求在 reset 信号为 '1' 时复位, 输出的计数值为 0; 当 RESET 信号为 0 时, 计数器工作, 在每个时钟上升沿, 输出计数值增 1 计数范围是从 0 到 255 在后面的对其进行模拟的过程中, 可以看到这个 VHDL 设计是否达到了要求的功能 (4) 删除文件这里所说的 删除文件 有 2 种含义 第 1 种含义是从项目中移走这个文件, 使其不再属于这个项目, 但并不将这个文件从计算机硬盘上删除 ; 第 2 种含义是不仅从项目中移走这个文件, 还将其从计算机硬盘上删除 所以, 当在设计浏览器窗口中用鼠标右键单击一个文件, 在弹出的菜单中选取 Remove, 或单击菜单 Edit>Delete, 会出现对话框来询问要求的是何种含义上的 删除 如图 8-27 所示 341

320 图 8-27 删除文件对话框其中, Detach 按钮表示第 1 种含义的 删除 ; Delete 按钮表示第 2 种含义的 删除 3. 对设计项目进行编译当准备好项目中的各个源文件之后, 就可以让 Active-VHDL 来对这个设计进行处理了 首先, 要对这个设计进行编译 编译时可以通过单击菜单 Design>Compile All 对整个项目的所有源文件进行编译, 也可以选择一个文件后单击菜单 Design>Compile 对这一个文件进行编译 编译完成后, 会在命令行窗口 (Console) 显示编译的有关信息 如编译过程发现错误, 会显示错误位置及提示, 并在编辑区中错误语句下出现红色波浪线 例如, 如果没有正确拼写 library, 编译后出现图 8-28 所示的情形 图 8-28 编译后发现错误的 VHDL 文件如编译过程发现错误, 可根据错误提示修改源文件, 直至编译正确无误为止 设计浏览器中 VHDL 文件图标的颜色可以反映该文件的编译状况 图标为蓝色, 说明该文件还未经过编译 ; 图标为绿色, 说明该文件正确通过编译 ; 图标为橙黄色, 说明该文件在编译过程中发现错误 342

321 4. 编辑激励信号波形在一个设计正确通过编译后, 就可以用 Active-VHDL 对它进行模拟了 但在模拟之前, 需要用户给出模拟用的激励信号 Active-VHDL 提供了图形化的波形编辑器 (Waveform Editor), 可以方便地编辑激励信号波形 整个编辑过程可分为以下几个步骤 (1) 启动波形编辑器单击菜单 File>New>Waveform, 启动波形编辑器建立一个新的波形图 此时, 波形图中无任何信号, 波形区各项内容均为空白 如图 8-29 所示 图 8-29 启动波形编辑器后的界面 (2) 加入信号在波形编辑器窗口为当前文档窗口的情况下, 单击菜单 Waveform>Add signals, 出现添加信号对话框 如图 8-30 所示 图 8-30 波形编辑器 Add Signals 对话框 343

322 在该窗口中选择要观察的输入输出信号, 单击 Add 按钮, 就将所选信号加入到波形窗口中 如图 8-31 所示 图 8-31 加入信号的波形编辑器窗口 (3) 编辑输入信号激励波形 Active-VHDL 对信号波形的编辑采用了激励源 (Stimulator) 的概念, 为编辑一个信号波形, 首先要给这个信号指定一个激励源, 再编辑这个激励源 Active-VHDL 中的信号激励源有 6 种, 在此只介绍其中最常用的 2 种, 也是本例中要用到的 2 种 一种是时钟 (Clock) 信号激励源, 产生周期信号, 因多用于时钟, 故称时钟信号激励源, 本例中的 CLK 信号就是采用的这种激励源 ; 另一种是公式 (Formula) 信号激励源, 它根据用户给出的 信号值 - 时刻 对产生任意的波形, 本例中的 RESET 信号就是采用的这种激励源 首先编辑 CLK 信号的波形 在波形编辑器窗口中选中 CLK 信号所在的行, 单击鼠标右键, 在弹出的菜单中单击 Stimulators, 得到激励源选择及编辑对话框 如图 8-32 所示 图 8-32 信号激励源对话框 ( 时钟型激励源 ) 在 Stimulator type 下拉列表中选择 Clock 一项, 窗口中便出现时钟波形的示意 344

323 图, 用户可以在这个示意图中对波形进行编辑, 可编辑的内容为 : 信号初值 信号开始时刻 时钟周期 时钟频率 时钟占空比 其中, 时钟周期和时钟频率是相关联的, 改变其中一个, 另一个也相应改变 本例中, 定义时钟信号初值为 0 从 0ps 时刻开始, 时钟周期为 100ns( 时钟频率 10MHz), 占空比为 50% 编辑完成后, 单击 Apply 按钮, 然后单击 Close 按钮关闭对话框 可以看到在波形编辑器窗口中的 clk 信号的 Stimulator 一栏中出现了 Clock 字样, 但并没有实际的波形出现, 波形区仍是空白 这是因为还没有开始模拟, 这个激励源要在模拟过程中才产生激励, 所以现在还看不到波形 编辑好 clk 信号的波形后, 来编辑 reset 信号的波形 在波形编辑器窗口中选中 reset 信号所在的行, 单击鼠标右键, 在弹出的菜单中单击 Stimulators, 得到激励源选择及编辑对话框 如图 8-33 所示 图 8-33 信号激励源对话框 ( 公式型激励源 ) 在 Stimulator type 下拉列表中选择 Formula 一项, 窗口中便出现波形公式输入框, 所谓波形公式, 是由一系列 信号值 - 时刻 对组成的字符串, 它的格式如下 : 信号值 1 时刻 1, 信号值 2 时刻 2, 它表示信号在时刻 1 时取值为信号值 1, 然后一直持续到时刻 2, 变为信号值 2 在公式激励源的编辑中, 需要注意以下几点 : 信号值与时刻之间用空格符间隔, 不同 信号值 - 时刻 对之间用逗号间隔 ; 为了一开始不出现不确定态, 一般要定义时刻 0 时的信号值 ; 各个 信号值 - 时刻 对的排列要按照时刻的先后顺序 ; 在信号值的表示上, 可以用 0 1 字符串形式, 如 0,1,1101 等, 也可用二 四 八 十六进制数, 如 2#1101,4#31,8#217,16#F8 等 ; 在时刻的表示上, 如果不标时间单位, 缺省为 ps, 如果标时间单位, 时间单位与时刻数值之间用空格符间隔, 如 200 ns 在本例中希望 reset 信号从时刻 0 到 200 ns 之间为 1, 电路复位 ; 在 200 ns 时变为 0, 电路开始工作, 直到 900ns 时 reset 信号再变为 1, 电路再次复位 所以编写的激励公式如下 : 345

324 1 0,0 200 ns,1 900 ns 编辑完成后, 单击 Apply 按钮, 然后单击 Close 按钮关闭对话框 可以看到在波形编辑器窗口中的 reset 信号的 Stimulator 一栏中出现了 Formula 字样, 因为没有开始模拟, 所以仍没有实际的波形出现 到这里, 就完成了模拟所需的激励波形的编辑 5. 按照上一步指定的激励波形进行模拟 调试在上一步中, 已编辑好了输入激励信号 clk 和 reset 的波形, 现在就可以进行模拟 调试了 如果是第一次进行模拟, 首先必须单击菜单 Simulation>Initialize Simulation 进行模拟初始化 ; 如果在模拟过程中想结束本次模拟, 可单击菜单 Simulation>End Simulation ; 如果想重新开始模拟, 可单击菜单 Simulation>Restart Simulation Active-VHDL 中的模拟有两种方式, 分别为基于时间的模拟和基于语句的模拟 所谓基于时间的模拟, 就是以时间为单位, 指定一段时间, 在这段时间中进行模拟 ; 所谓基于语句的模拟, 就是以 VHDL 语句为单位, 模拟 VHDL 中进程的触发, 单步或者连续地执行 VHDL 源代码中的语句 基于时间的模拟主要有 3 条命令, 它们对应的菜单及功能如下 (1)Simulation>Run 该命令用于在一个模拟器指定的时间段中进行模拟, 模拟器指定的时间段是指从当前时刻开始到没有被触发的事件队列的时刻为止 (2)Simulation>Run Until 该命令用于在从当前时刻开始到用户指定的某一时刻为止的时间段中进行模拟 单击该菜单, 出现对话框, 询问模拟过程进行到哪个时刻 如图 8-34 所示 图 8-34 Run Until 对话框 (3)Simulation>Run For 该命令用于在一个用户指定长度的时间段中进行模拟, 用户指定的时间段是指从当前时刻开始经过用户指定的时间长度 该时间长度可在主窗口中的工具按钮栏中模拟时间长度输入框内输入 如图 8-35 所示 图 8-35 单步模拟时间长度输入框基于语句的模拟主要有 3 条命令, 它们对应的菜单及功能如下 (1)Simulation>Trace Into 346

325 执行一条 VHDL 语句, 如果该语句为子程序调用, 则转入该子程序体中 (2)Simulation>Trace Over 执行一条 VHDL 语句, 如果该语句为子程序调用, 则执行整个子程序调用而不转入子程序体内 (3)Simulation>Trace Out 执行到当前所在的子程序结束, 跳出当前子程序, 返回至调用该子程序的上一级程序中 在基于语句的模拟中, 当前将要模拟执行的语句的底色为黄色, 便于用户追踪语句的执行 如图 8-36 所示 图 8-36 模拟时的 VHDL 编辑器窗口无论在基于时间的模拟中还是在基于语句的模拟中, 任何时刻的模拟波形都会实时地反映在与模拟器连接的波形窗口 所谓与模拟过程连接, 就是在该波形窗口对应的菜单 Waveform>Connect to Simulator 一项被选中 只有与模拟器连接的波形窗口才能实时地反映模拟结果 在本例中, 由于电路的功能比较简单, 就用 Simulation>Run Until 命令让模拟器从 0 时刻到 1200 ns 时刻进行模拟, 直接观察整个模拟波形 如图 8-37 所示 分析图 8-37 的波形可以发现, CLK 和 RESET 信号的激励波形符合编辑激励源时的设想, COUNT 信号输出响应也符合设计的功能要求 当然, 这样的激励只是一个简略的例子, 并不完备 当模拟 调试完成后, 可以将所得波形进行保存或输出为测试文件 单击菜单 File>Save 或 File>Save As 可将波形保存为 Active-VHDL 中的二进制波形文件 (.awf); 单击菜单 Waveform>Export Waveforms 可将波形输出为测试文件, 用于测试平台生成向导根据这个文件生成测试平台 输出测试文件的可选格式有两种, 一种是 347

326 VHDL 进程语句格式 (.vhs), 另一种是 WAVES 标准的测试向量格式 (.vec) 图 8-37 模拟后的波形在本例中, 将波形输出为 VHDL 进程语句格式的文件 testwav.vhs 有兴趣的读者可查看一下自己输出的.vhs 文件, 看一下如何用 VHDL 进程语句描述上面定义的激励波形 6. 自动生成测试平台 ( 可选 ) Active-VHDL 中提供了测试平台生成向导 (Test Bench Generator Wizard), 可根据用户要求自动产生有关测试平台的文件 关于测试平台 (Test Bench) 的概念, 前面已有过叙述, 这里将说明如何使用测试平台生成向导自动生成测试平台 单击菜单 Tools>Generate Test Bench, 启动测试平台生成向导 如图 8-38 所示 图 8-38 测试平台生成向导 ( 第 1 步 ) 这一步要求用户选择测试平台所测试的实体及其构造体以及测试平台的类型, 用户可在窗口中的下拉列表中选择实体及其构造体 Entity 列表中会显示在当前设计项目中所 348

327 有的实体 ; 对选中的实体, Architecture 列表中会显示在当前设计项目中该实体所有的构造体 在 Test Bench Type 一栏中, 用户可选择测试平台的类型 测试平台的类型有 2 种, 一种是 Single Process, 一种是 waves Based 这 2 种测试平台的类型在前面已有过说明, 这里以 Single Process 类型为例 选择完毕后单击 下一步 按钮, 进入下一步 如图 8-39 所示 图 8-39 测试平台生成向导 ( 第 2 步 ) 这一步要求用户指定测试激励 使用的激励来自先前从波形窗口中输出的 testwav.vhs 文件 首先选中 Test vectors from file 一项, 表示激励来自文件, 再在下面的文件输入框内输入或单击 Browse 按钮选择激励文件 选好激励文件后, 向导窗口下方左边的列表会显示所选激励文件中包含的激励信号, 右边的列表显示待测实体元件的输入端口, 让用户查看二者是否一致 选择激励文件并查看正确无误后, 单击 下一步 按钮, 进入下一步 如图 8-40 所示 图 8-40 测试平台生成向导 ( 第 3 步 ) 这一步要求用户填写 4 项信息, 分别是测试平台实体名 测试平台构造体名 测试平台文件名以及向导生成的文件存放的目录 ( 这个目录自动位于项目目录下 ) 在用户填写之 349

328 前, 每一项都给出了缺省情况下的输入 在本例中, 使用缺省的输入 测试平台实体名为 counter8_tb, 测试平台构造体名为 TB_ARCHITECTURE, 测试平台文件名为 counter8_tb.vhd, 向导生成的文件存放的目录为项目目录下的 TestBench 子目录 完成后单击 下一步 按钮, 进入下一步 如图 8-41 所示 图 8-41 测试平台生成向导 ( 第 4 步 ) 这一步是测试平台生成向导的最后一步, 它提示用户向导将要生成哪些文件, 以及询问用户是否生成时序模拟配置文件 counter8_tb_tim_cfg.vhd 选中 Generate, 表示需要生成该配置文件 完成后单击 完成 按钮, 结束测试平台生成向导 完成测试平台生成向导后可以看到, 项目中加入了一个 TestBench 目录, 这个目录包含了测试平台生成向导生成的 3 个文件 : counter8_tb.vhd counter8_tb_ tim_cfg.vhd 和 counter8_tb_runtest.do 如图 8-42 所示 counter8_tb.vhd 为测试平台的 VHDL 源文件 ; counter8_tb_tim_cfg.vhd 为测试平台用于时序模拟时的 VHDL 配置文件 ; counter8_tb_runtest.do 为对该测试平台进行模拟 测试的宏命令文件 有兴趣的读者可以进一步查看这些文件的内容 图 8-42 测试平台生成向导生成的文件 350

329 8.3 MaxplusII 使用指南 MAXPULS II 概貌 MAXPLUS II 是美国 ALTERA 公司自行开发的一种针对其公司生产的系列 FPGA 的设计, 仿真 编程的工具软件, 其全称为 Multiple Array Matrix and Programmable Logic User Systems MAXPLUS II 是 FPGA 应用软件中比较典型和常见的一种工具, 在我国应用较为普遍 1.MAXPLUS II 的功能 MAXPLUS II 接受对一个电路设计的图形描述 ( 电路图 ) 或文本描述 ( 硬件描述语言 ), 通过编辑 编译 仿真 综合 FPGA 编程等一系列过程, 将用户所设计的电路原理图或电路描述转变为 FPGA 内部的基本逻辑单元, 写入 FPGA 芯片中, 从而在硬件上实现用户所设计的电路 该 FPGA 可用于正式的产品, 也可作为对最终实现的 ASIC 芯片的硬件验证 如图 8-43 所示 电路设计 MAXPLUS II 电路的 FPGA 实现 图 8-43 MAXPLUS I 的功能图示 2.MAXPLUS II 的主要特点 (1) 开放的界面 MAXPLUS II 软件可与其他 EDA 厂家的设计输入 综合 验证工具相连接 设计人员可使用 ALTERA 或标准 EDA 设计输入工具建立电路设计, 使用 MAXPLUS II 编译器 (Compiler) 对 ALTERA 的器件进行编译, 然后使用 ALTERA 或其他标准 EDA 验证工具进行验证 目前,MAXPLUS II 支持与 Cadence,Exemplarlogic,Mentor Graphics,Synopsys, Synplicity,Viewlogic 等公司的 EDA 工具接口 (2) 与结构无关 MAXPLUS II 系统的核心 编译器 (Compiler) 支持 ALTERA 公司的 FLEX10K, FLEX8000, FLEX6000, MAX9000, MAX7000, MAX5000 和 Classic 等可编程逻辑器件系列, 提供了业界惟一真正与结构无关的可编程逻辑设计环境 MAXPLUS II 的编译器还提供了强大的逻辑综合与优化功能, 使设计人员能比较容易地将其设计集成到可编程逻辑器件中 (3) 多平台 351

330 MAXPLUS II 软件可在多种 PC 机和工作站的操作系统中运行 其中包括 Windows NT 3.51,Windows NT 4.0,Windows 95,Windows 98,Sun SPAC Stations,HP 9000 Series 700/800,IBM RISC System/6000 等 (4) 完全集成化 MAXPLUS II 的设计输入 处理 验证 器件编程等功能全部集成在统一的开发环境下, 可以使用户进行动态调试, 加快开发进程 (5) 丰富的设计库 MAXPLUS II 提供丰富的库单元供设计者使用, 其中包括 74 系列的全部器件和多种特殊的逻辑宏功能 (Macro-Function) 以及新型的参数化兆功能 (Mega-Function) (6) 接受高级描述语言 MAXPLUS II 接受多种硬件描述语言 (HDL), 包括 VHDL,VERILOG 和 ALTERA 自己的硬件描述语言 AHDL (7)Megacore 功能 Megacore 功能是为复杂的系统级电路提供的经过验证的 HDL 描述, 它能使 FLEX10K, FLEX8000,FLEX6000,MAX9000,MAX7000 等器件实现最优化设计 设计人员可直接调用这些 Megacore 功能, 把主要精力投入到系统级设计上 3.MAXPLUS II 的界面和主要工具运行 MAXPLUS II 后, 出现如图 8-44 所示的界面 图 8-44 MAXPLUS II 启动后的界面由于没有运行任何程序, 窗口内为空白 标题栏上显示的 G:\fangyl\work\tlc\tlc 是最近一次打开的项目, 作为运行 MAXPLUS II 后的当前项目 当前项目是 MAXPLUS II 中一个重要概念, 它指的是 MAXPLUS II 的各种处理针对的对象,MAXPLUS II 的各种操作, 如编译 仿真等, 都是对当前项目进行的 如何设置当前项目, 后面将在一个例子的实际操作中讲解 MAXPLUS II 的菜单和快捷按钮的形式与其他 windows 应用程序相仿, 其中常用的命 352

331 令将在后面一个例子的实际操作中再讲解 单击窗口菜单最左边的 MAXPLUS II, 出现下拉菜单 菜单中的各项是 MAXPLUS II 的各个功能模块 单击某项后运行相应的工具 菜单和快捷按钮随着运行的工具不同而改变 下面对这些工具及其界面作一个简单介绍 (1)Hiararchy Display 层次显示层次化地显示当前项目中的设计文件 如图 8-45 所示 图 8-45 MAXPLUS II 层次显示窗口 (2)Graphic Editor 电路图编辑器当设计输入为电路图输入时, 用于编辑电路原理图 如图 8-46 所示 图 8-46 MAXPLUS II 电路图编辑器窗口 (3)Symbol Editor 电路符号编辑器编辑电路的 黑盒子 符号, 用于电路原理图的层次化设计 如图 8-47 所示 (4)Text Editor 文本编辑器编辑文本, 用于设计输入为硬件描述语言时 如图 8-48 所示 (5)Waveform Editor 波形编辑器编辑激励波形, 用于产生仿真的激励波形 在仿真结束后, 观察结果波形如图 8-49 所示 353

332 图 8-47 MAXPLUS II 电路符号编辑器窗口 图 8-48 MAXPLUS II 文本编辑器窗口 354 图 8-49 MAXPLUS II 波形编辑器窗口

333 (6)Floorplan Editor 底层映射图编辑器观察一个电路设计经编译后在所选器件中的映射结果, 必要时也可对其进行编辑 如图 8-50 所示 图 8-50 MAXPLUS II 底层映射图编辑器窗口 (7)Compiler 编译器编译一个当前设计项目 它包括了对一个设计 ( 电路图或硬件描述 ) 的语法检查 信息数据库的建立 逻辑综合 向器件单元的映射 提取延时信息 编程文件的生成等各种处理, 是 MAXPLUS II 软件的核心 如图 8-51 所示 图 8-51 MAXPLUS II 编译器窗口 (8)Simulator 电路模拟器对编译后的电路进行模拟 因为编译后的电路已经映射到实际器件中, 所以该模拟过程是 后模拟 如图 8-52 所示 (9)Timing Analyzer 时序分析器对当前编译后的电路进行时序分析 它可做以下 3 种分析 : 延时矩阵 (Delay Matrix) 分析, 即分析各个源节点和目标节点之间的传播延时 ; 建立 / 保持矩阵 (Setup/Hold Matrix) 分析, 即分析信号所需的最小建立 / 保持时间 ; 时序电路性能 (Registered Performance) 分析, 即分析电路的最高工作频率等性能 如图 8-53 所示 355

334 图 8-52 MAXPLUS II 电路模拟器窗口 图 8-53 MAXPLUS II 时序分析器窗口 (10)Programmer 编程器将当前编译好的电路写入实际的可编程器件中 如图 8-54 所示 356 图 8-54 MAXPLUS II 编程器窗口

335 (11)Message Processor 信息处理器显示 定位以上各工具运行时产生的信息, 如编译过程中的出错 警告信息等 如图 8-55 所示 图 8-55 MAXPLUS II 信息处理器窗口 MAXPLUS II 基于 VHDL 语言的基本设计流程 MAXPLUS II 接受对电路设计的电路原理图描述和硬件描述语言描述 在硬件描述语言方面, 包括 VHDL VERILOG HDL 和 ALTERA 自己的硬件描述语言 AHDL 本书是针对 VHDL 语言的, 在这里只介绍 MAXPLUS II 基于 VHDL 语言的使用 1.MAXPLUS II 对 VHDL 的支持 (1) 支持 VHDL 的 87 版本和 93 版本 MAXPLUS II 支持 VHDL 的 87 版本和 93 版本, 在编译之前需要对 VHDL 的版本进行指定 应注意所编写的 VHDL 源程序的格式和语句应和所指定的版本一致 (2) 支持 VHDL 的库 MAXPLUS II 系统中有 3 个 VHDL 的库 :IEEE 库 ALTERA 库和 LPM 库 IEEE 库提供基本数据类型 运算函数等 ;ALTERA 和 LPM 库提供常用的函数 一定功能的电路模块等 具体内容可查阅 MAXPLUS II 中的联机帮助 (3) 支持 VHDL 语句的一个子集需要特别注意的是, 由于 MAXPLUS II 是主要针对可编程逻辑器件的软件, 所以它并不支持所有的 VHDL 语句 它只支持 RTL 级描述, 不支持行为级描述 ; 只支持 硬件性 的语句, 而不支持 软件性 的语句 因而 AFTER 语句 进程中的 WAIT 语句等将得不到支持 2.MAXPLUS II 基于 VHDL 的基本设计流程一个完整的设计流程就是按照一定的次序使用 MAXPLUS II 中的各个工具 图 8-56 仅给出在 MAXPLUS II 中用 VHDL 进行 FPGA 电路设计的主要流程 图 8-56 中, 左边为 MAXPLUS II 中用 VHDL 进行设计的主要流程, 右边为设计流程各步所对应使用的 MAXPLUS II 的工具 在整个流程中, 如果某一个环节出错, 需要进行回溯修改, 然后再继续往下进行 357

336 流程流程 流程中涉及使用的工具流程中涉及使用的工具 1.VHDL 源文件的编辑 文本编辑器 2.VHDL 源文件的编译 编译器 3. 观察或修改底层逻辑单元映射 底层映射图编辑器 4. 编辑要观察的节点, 并在输入节点上编辑激励波形 波形编辑器 5. 按照上一步指定的激励波形进行电路模拟 电路模拟器 6. 观察电路模拟结果波形 波形编辑器 7. 分析电路中的时序关系 时序分析器 8. 器件编程 编程器 图 8-56 MAXPLUS II 中用 VHDL 进行设计的主要流程 一个实际操作 MAXPLUS II 的例子 本节将以具体 实用为目的, 根据一个电路设计的实例, 按照上节所给出的设计流程, 完成一个电路设计到 FPGA 实现的完整过程, 便于读者迅速掌握 MAXPLUS II 的一般应用流程 在这个例子中, 不可能涉及到 MAXPLUS II 各方面的特性, 也不可能用到 MAXPLUS II 中所有的命令, 但这个过程对于一般的设计是适用和足够的.MAXPLUS II 中的一些更高级的特性和更复杂的命令, 有兴趣的读者可查阅专门的资料 本节所用的例子是 MAXPLUS II 自身所带的一个 8 位加法器的例子, 在 max2work\vhdl\ 目录中,VHDL 文件名为 adder.vhd 这个加法器的描述很简单, 读者只需将它作为一个电路设计的代表, 学习如何应用 MAXPLUS II 这个软件, 将一个电路设计转变为 FPGA 实现 358

337 1.VHDL 源文件的编辑 (1) 创建新文件使用 MAXPLUS II 新建一个 VHDL 文件, 单击菜单 File>New, 在出现的询问窗口中, 选择 Text Editor File 如图 8-57 所示 图 8-57 创建新文件对话框 单击 OK 按钮, 启动 MAXPLUS II 的文本编辑器, 当前编辑的文档为空白文档, 用户可键入自己的 VHDL 程序 如图 8-58 所示 图 8-58 创建的空白的新文件 359

338 (2) 打开已有的文件打开已有的 VHDL 文件, 单击菜单 File>Open, 出现对话框, 如图 8-59 所示 图 8-59 打开文件对话框在对话框中指定文件所在路径 显示文件类型, 找到并选中所要打开的文件, 单击 OK 按钮, 启动文本编辑器, 当前编辑的文档为所打开的文档, 用户可进行修改 如图 8-60 所示 360 图 8-60 打开的文件

339 (3) 保存正在编辑文件保存正在编辑的文件, 单击 File>Save 或 File>Save As, 这和其他 Windows 应用程序中的文件保存功能没有区别, 在此不再赘述 (4)MAXPLUS II 编辑器中的常用命令尽管 VHDL 文件可以由任何一个文本编辑器编辑, 但 MAXPLUS II 文本编辑器提供的一些特有命令使得对硬件描述语言的编辑更为方便 快捷 主要有下面两种 : a. 增加 / 减少缩进单击菜单 Edit>Increase Indent 或 Edit>Decrease Indent, 可使所选取的文字块共同增加或减少一个缩进距离 这对于调整硬件描述语言中的层次格式, 提高易读性具有重要作用 b. 语言模板调用 MAXPLUS II 的文本编辑器提供了 VHDL VERILOG AHDL 的语言模板, 每种语言的模板内有该语言各类语句的基本形式和需要用户自行添加内容的提示 应用语言模板减少了用户的键入, 避免用户记忆各类语句的语法形式 单击菜单 Templates>VHDL Template, 出现 VHDL 的模板对话框, 如图 8-61 所示 图 8-61 VHDL 语言模板选中所要的语句, 如上图中的 Entity Declaration, 单击 OK 按钮, 则该语句的基本形式和有关提示就插入到文本编辑器中光标所在位置之后 如图 8-62 所示 除了上述 2 项功能外,MAXPLUS II 文本编辑器还提供一般文本编辑的命令 其功能和对应的菜单如下 : 361

340 图 8-62 插入到编辑器中的 VHDL 语言模板 文字定位 : Utilities>Go To 搜索文字 : Utilities>Find Text 寻找上一个 / 下一个被搜索的文字 : Utilities>Find Previous 或 Utilities>Find Next 搜索并替换文字 : Utilities>Search & Replace 本例中, 直接打开 max2work\vhdl\ 下的文件 adder.vhd, 不需要对它做任何编辑 2.VHDL 源文件的编译编辑好一个 VHDL 源文件后就可以对其进行编译了 但在编译之前, 还需要做下列一些准备工作 (1) 当前项目指定由于 MAXPLUS II 的编译器是对当前项目 ( 而不是当前编辑的文件!) 进行编译的, 所以一定要指定当前项目 一个简便而常用的方法就是用菜单命令 File>Project>Set Project To Current File 将当前编辑的文件指定为当前项目, 以后的编译即是对该文件进行的 当前项目路径和名称会出现在 MAXPLUS II 窗口的标题栏中 例如打开加法器的 VHDL 文件后, 将该文件指定为当前项目, 如图 8-63 所示 指定完成后, 标题栏显示的路径和项目即为当前项目 (2) 器件指定器件指定是指选择用于实现该电路的可编程逻辑器件的系列及型号 编译器可根据所选器件将电路映射到该器件内部的逻辑单元中 单击菜单 Assign>Device, 出现器件指定对话框, 如图 8-64 所示 本例中, 指定器件为 FLEX10K 系列, 型号为 EPF10K20RC240-3 (3) 器件管脚指定选定器件后, 用户可指定所设计的电路中的某个端口对应器件的某一管脚, 以便于器件编程后对该器件的测试和应用 如不指定, 则编译器自动将端口随机地映射到器件的管脚上 各器件的管脚排列和编号可查阅相应器件的产品说明 单击菜单 Assign>Pin/ Location/Chip, 出现器件管脚指定对话框, 如图 8-65 所示 362

341 图 8-63 设定当前文件为当前项目 图 8-64 器件指定对话框在该对话框中, 还可以指定器件中的某个位置 ( 行 列 逻辑单元等 ), 但一般不常用 本例中, 省略了器件管脚指定 ( 从编译完成后生成的报告文件中可查看端口到管脚的映射关系 ) 完成上述工作后, 就可以启动编译器, 对编译器进行一些设置 单击菜单 Maxplus II>Compiler, 启动编译器, 菜单和快捷按钮也变为编译器所具有的菜单和快捷按钮 如图 8-66 所示 363

342 图 8-65 器件管脚指定对话框 图 8-66 启动编译器可对编译器进行如下设置 : (1) 语言版本设置 MAXPLUS II 支持 VHDL 的 87 版本和 93 版本, 在编译之前需要对 VHDL 的版本进行设置, 以使 MAXPLUS II 编译器按照指定版本的格式和语法对文件进行编译 单击菜单 Interfaces>VHDL Netlist Reader Settings, 出现对话框, 如图 8-67 所示 在本例中, 指定语言版本为 93 版本 364

343 (2) 输出文件设置 图 8-67 编译器 VHDL 读入设置对话框 MAXPLUS II 编译器可生成各种标准格式的输出文件, 与其他 EDA 工具接口 包括 EDIF VERILOG VHDL 等格式 用户可设置产生某种格式的输出文件 在 Interfaces 菜单中含有 EDIF Netlist Writer VHDL Netlist Writer Verilog Netlist Writer 等项 选中某项, 即可使编译器产生指定格式的输出文件 如图 8-68 所示 本例中, 不考虑与其他 EDA 工具的接口, 故不选上述任何一种格式 图 8-68 编译器输出文件设置菜单完成上述的指定和设置后, 就可以单击编译器窗口中的 Start 按钮, 开始编译 编译过程中如有信息需要向用户显示, 则程序启动信息处理器, 显示信息, 用户可根据该信 365

344 息对源文件进行修改 如无错误信息, 则编译完成 3. 观察或修改底层逻辑单元映射完成编译后, 编译器已将电路映射到器件中 可以用底层单元编辑器查看其映射情况 单击菜单 MAXPLUS II>Floorplan Editor, 启动底层单元编辑器, 如图 8-69 所示 图 8-69 编译后的底层逻辑单元映射图窗口左边有 8 个按钮, 可以进行放大 缩小 显示端口或单元的扇入扇出等 4. 编辑要观察的节点, 并在输入节点上编辑激励波形 MAXPLUS II 可对设计的电路进行模拟, 所以要编辑观察节点, 并在输入节点上编辑激励波形 单击菜单 MAXPLUS II>Waveform Editor, 启动波形编辑器, 如图 8-70 所示 366 图 8-70 启动波形编辑器 在波形编辑器窗口中, 按如下步骤进行 :

345 (1) 添加所有要观察的信号节点单击菜单 Node>Insert Node, 出现对话框, 如图 8-71 所示 在对话框中填入节点名 (Node Name), 输入输出类型 (I/O Type), 然后单击 OK 按钮 重复上述过程, 添加所有观察节点信号 注意信号名和输入输出类型要和 VHDL 源文件中的一致 添加完成后, 波形编辑器窗口如图 8-72 所示 图 8-71 添加信号节点对话框 图 8-72 添加信号后的波形编辑器窗口 (2) 设置模拟时间单击菜单 File>End Time, 出现对话框, 填入模拟时间长度, 单击 OK 按钮即可 如图 8-73 所示 367

346 图 8-73 设置模拟时间对话框本例中, 设置模拟时间长度为 100ns (3) 编辑激励波形窗口左边有一竖排按钮, 最下面的 9 个按钮是用来编辑波形的 首先用鼠标的拖曳选中某个信号的全部或部分时间区域, 再单击波形编辑按钮, 编辑选定区域的波形 波形编辑按钮包括信号值的置 1 置 0 置 X 置 Z 取反 时钟信号编辑 ( 总线 ) 信号数值定时递增 / 递减 总线信号赋值等 本例中, 让加法器在前 50ns 计算 FFH+FFH, 后 50ns 计算 55H+AAH 用倒数第 2 个按钮 总线信号赋值按钮为各段信号赋值 完成编辑后的波形如图 8-74 图 8-74 编辑后的波形 368

347 (4) 保存波形文件在波形编辑器窗口下, 可单击菜单 File>Save 或 File>Save As 保存波形文件 在电路模拟之前, 一定要保存波形文件 5. 电路模拟单击菜单 Maxplus II>Simulator, 启动电路模拟器, 如图 8-75 所示 图 8-75 启动电路模拟器单击电路模拟器窗口的 Start, 进行电路模拟 6. 观察电路模拟结果波形完成电路模拟后, 单击电路模拟器窗口的 Open SCF, 重新打开波形编辑器, 观察输出波形 模拟后的波形如图 8-76 所示 图 8-76 模拟后的结果波形 本例中, 输出波形 result 结果正确, 但有一定的延时 369

348 7. 分析电路中的时序关系单击菜单 MAXPLUS II>Timing Analyzer, 启动时序分析器 时序分析器有 3 种, 分别是延时矩阵 (Delay Matrix) 分析 建立 / 保持矩阵 (Setup/Hold Matrix) 分析和时序电路性能 (Registered Performance) 分析 启动时序分析器后, 可单击 Analysis 菜单选择其中的一种 本例中, 选择延时矩阵分析 如图 8-77 所示 图 8-77 启动时序分析器单击菜单 Node>Timing Analysis Source Node/Timing Analysis Destination, 定义待分析的源节点和目的节点 出现对话框如图 8-78 所示 在 Node Name 栏中填入信号名或匹配符, 单击 List, 则在 Available Nodes 框内列出了所有与填入的信号名匹配的源节点 选择其中的某个或某一些节点, 单击 =>, 将其加入右边的 Selected Nodes 框内 Selected Nodes 框内的节点即是待分析的节点 370 图 8-78 指定时序分析源节点对话框

349 本例中, 分析从输入端口 op1[0] 到输出端口 result[8]( 进位 ) 的延时 设置好源节点和目的节点后, 单击时序分析器窗口中的 Start 按钮, 进行时序分析, 分析完成后, 如图 8-79 所示 图 8-79 完成时序分析后的结果结果显示, 从输入端口 op1[0] 到输出端口 result[8]( 进位 ) 的延时为 24 ns 8. 器件编程如果在以上的电路模拟和时序分析中发现电路已经达到预期的功能要求和时序要求, 就可以将这个电路写入实际器件中去了 单击菜单 MAXPLLUS II>Programmer, 启动编程器, 如图 8-80 所示 图 8-80 启动编程器 371

350 在编程之前, 先单击菜单 Options>Hardware Setup, 设置编程硬件 用户可按照自己的编程硬件进行设置 设置好之后点击 Confignre 按钮, 就可以将电路配置一 FPGA 中了 由于器件编程与用户所用的具体器件和设备有关, 在此不再详述, 请查阅有关产品资料 372

4.1 VHDL VHDL 4-1 a b & c 4-1 2

4.1 VHDL VHDL 4-1 a b & c 4-1 2 4.1 VHDL 4.2 VHDL 4.3 VHDL 4.4 VHDL 4.5 1 4.1 VHDL 4.1.1 VHDL 4-1 a b & c 4-1 2 ( 4-1 ) (1) a b c ( 1 ) (2) c=a b CPU VHDL 3 VHDL 4-2 a b & c a c b c a b 4-2 VHDL 4 1 ENTITY IS d0 & 1 q END d1 & sel 1

More information

a b c d e f g C2 C1 2

a b c d e f g C2 C1 2 a b c d e f g C2 C1 2 IN1 IN2 0 2 to 1 Mux 1 IN1 IN2 0 2 to 1 Mux 1 Sel= 0 M0 High C2 C1 Sel= 1 M0 Low C2 C1 1 to 2 decoder M1 Low 1 to 2 decoder M1 High 3 BCD 1Hz clk 64Hz BCD 4 4 0 1 2 to 1 Mux sel 4

More information

第3节 VHDL语言的常用语法

第3节 VHDL语言的常用语法 第 3 节 VHDL 语言的常用语法 [ 学习要求 ] 掌握 VHDL 硬件描述语言的基本描述语句 并可以利用这些语句进行简单 电路的设计 [ 重点与难点 ] 重点 : 常用的并行语句与顺序语句的语法 难点 : 部件 (Component 的定义与应用 [ 理论内容 ] 一 并行语句所谓的并行语句指采用这些语法生成的硬件电路在时间上可以并行 ( 或并发 ) 的执行 ( 运行 ) 这是 VHDL 语法必须具备的能力,

More information

Microsoft PowerPoint - EDA-理论3 [兼容模式]

Microsoft PowerPoint - EDA-理论3 [兼容模式] 3 更复杂电路的 VHDL 描述 3.1 计数器的 VHDL 描述 时序电路中, 一般计数器的输入 / 输出信号包括 : n Q CLK Entity 电路设计? Architecture -1- 西安电子科技大学国家级精品课程数字电路与系统设计 例 1 : 4 位二进制加法计数器 ENTITY CNT4 IS PORT ( CLK : IN BIT ; Q : BUFFER INTEGER range

More information

B 6 A A N A S A +V B B B +V 2

B 6 A A N A S A +V B B B +V 2 B 6 A A N A S A +V B B B +V 2 V A A B B 3 C Vcc FT7 B B 1 C 1 V cc C 2 B 2 G G B 3 C 3V cc C B ND ND GND V A A B B C 1 C 3 C 2 C V cc V cc V 220Ωx B 1 B 2 B 3 B GND GND A B A B 1 1 0 0 0 2 0 1 0 0 3 0

More information

D-Type entity D_FF is D :in std_logic; CLK :in std_logic; Q :out std_logic); end D_FF; architecture a of D_FF is process(clk,d) if CLK'EVENT and CLK =

D-Type entity D_FF is D :in std_logic; CLK :in std_logic; Q :out std_logic); end D_FF; architecture a of D_FF is process(clk,d) if CLK'EVENT and CLK = VHDL (Sequential Logic) D-Type entity D_FF is D :in std_logic; CLK :in std_logic; Q :out std_logic); end D_FF; architecture a of D_FF is process(clk,d) if CLK'EVENT and CLK = '1' then Q

More information

PowerPoint Presentation

PowerPoint Presentation Verilog HDL 的基本知识 周立功 Actel 产品线 作者简介 20 世纪 60 年代毕业于清华大学自控系计算与技术专业 北京航空航天大学教授, 主要的研究领域为嵌入式数字系统的设计 夏宇闻教授 1995 年开始筹建我国首个 EDA 实验室, 在其后十几年间为航天部设计多个复杂数字电路 2006 年至今受聘于神州龙芯集成电路设计公司担任技术顾问 概述 数字通信和自动化控制等领域的高速度发展和世界范围的高技术竞争对数字系统提出了越来越高的要求,

More information

(Microsoft Word - \245\274\244\300\246\250\301Z\260\252\247C13.doc)

(Microsoft Word - \245\274\244\300\246\250\301Z\260\252\247C13.doc) VHDL 實 習 報 告 四 資 工 二 指 導 教 授 : 徐 演 政 學 生 : 廖 雅 竹 B9515010 陳 緯 琪 B9515044 敗 LED 史 上 無 敵 超 級 賭 骰 子 模 擬 機 以 廖 雅 竹 陳 緯 琪 Project Title: 骰 硬 件 啟 動 後, 可 以 明 顯 的 觀 察 到 實 驗 板 上 方 的 兩 個 骰 子 器 高 速 地 跳 動 Participants:

More information

器之 间 向一致时为正 相反时则为负 ③大量电荷的定向移动形成电 流 单个电荷的定向移动同样形成电流 3 电势与电势差 1 陈述概念 电场中某点处 电荷的电势能 E p 与电荷量 q Ep 的比值叫做该点处的电势 表达式为 V 电场中两点之间的 q 电势之差叫做电势差 表达式为 UAB V A VB 2 理解概念 电势差是电场中任意两点之间的电势之差 与参考点的选择无关 电势是反映电场能的性质的物理量

More information

长 安 大 学 硕 士 学 位 论 文 基 于 数 据 仓 库 和 数 据 挖 掘 的 行 为 分 析 研 究 姓 名 : 杨 雅 薇 申 请 学 位 级 别 : 硕 士 专 业 : 计 算 机 软 件 与 理 论 指 导 教 师 : 张 卫 钢 20100530 长安大学硕士学位论文 3 1 3系统架构设计 行为分析数据仓库的应用模型由四部分组成 如图3 3所示

More information

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1 C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 月 3 日 1 1 INPUTOUTPUT 1 InputOutput 题目描述 用 cin 输入你的姓名 ( 没有空格 ) 和年龄 ( 整数 ), 并用 cout 输出 输入输出符合以下范例 输入 master 999 输出 I am master, 999 years old. 注意 "," 后面有一个空格,"." 结束,

More information

吉林大学学报 工学版 244 第 4 卷 复杂 鉴于本文篇幅所限 具体公式可详见参考文 献 7 每帧的动力学方程建立及其解算方法如图 3 所示 图4 滚转角速度与输入量 η 随时间的变化波形 Fig 4 Waveform of roll rate and input η with time changing 图5 Fig 5 滚转角随时间的变化波形 Waveform of roll angle with

More information

FPGAs in Next Generation Wireless Networks WPChinese

FPGAs in Next Generation Wireless Networks WPChinese FPGA 2010 3 Lattice Semiconductor 5555 Northeast Moore Ct. Hillsboro, Oregon 97124 USA Telephone: (503) 268-8000 www.latticesemi.com 1 FPGAs in Next Generation Wireless Networks GSM GSM-EDGE 384kbps CDMA2000

More information

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double x) { d

More information

电子技术基础 ( 第 版 ) 3. 图解单相桥式整流电路 ( 图 4-1-3) 电路名称电路原理图波形图 整流电路的工作原理 1. 单相半波整流电路 u 1 u u sin t a t 1 u 0 A B VD I A VD R B

电子技术基础 ( 第 版 ) 3. 图解单相桥式整流电路 ( 图 4-1-3) 电路名称电路原理图波形图 整流电路的工作原理 1. 单相半波整流电路 u 1 u u sin t a t 1 u 0 A B VD I A VD R B 直流稳压电源 第 4 章 4.1 整流电路及其应用 学习目标 1. 熟悉单相整流电路的组成, 了解整流电路的工作原理. 掌握单相整流电路的输出电压和电流的计算方法, 并能通过示波器观察整流电路输出电压的波形 3. 能从实际电路中识读整流电路, 通过估算, 能合理选用整流元器件 4.1.1 认识整流电路 1. 图解单相半波整流电路 ( 图 4-1-1) 电路名称电路原理图波形图 4-1-1. 图解单相全波整流电路

More information

工程项目进度管理 西北工业大学管理学院 黄柯鑫博士 甘特图 A B C D E F G 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 甘特图的优点 : 直观明了 ( 图形化概要 ); 简单易懂 ( 易于理解 ); 应用广泛 ( 技术通用 ) 甘特图的缺点 : 不能清晰表示活动间的逻辑关系 WBS 责任分配矩阵 ( 负责〇审批

More information

⊙内容:常用逻辑电路设计

⊙内容:常用逻辑电路设计 内容 : 常用逻辑电路设计一般组合逻辑电路设计 例 2: 全加器设计 一般时序逻辑电路设计 一 一般组合逻辑电路设计 1 概念 : 组合逻辑电路输出只与当前的输入有关, 而与历史状态无关 即组合逻辑电路是无记忆功能电路 2 常见电路 : (1) 基本门电路 ( 与 非 或等 ) (2) 选择电路 (N 选 1 电路等 ) (3) 编码与解码电路 (3-8 电路 7 段显示 ) (4) 加法电路 (

More information

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例 帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例 这篇文章主要介绍了帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例, 本文还详细介绍了帝国 CMS 数据库类中的一些常用方法, 需要的朋友可以参考下 例 1: 连接 MYSQL 数据库例子 (a.php)

More information

9 什 么 是 竞 争 与 冒 险 现 象? 怎 样 判 断? 如 何 消 除?( 汉 王 笔 试 ) 在 组 合 逻 辑 中, 由 于 门 的 输 入 信 号 通 路 中 经 过 了 不 同 的 延 时, 导 致 到 达 该 门 的 时 间 不 一 致 叫 竞 争 产 生 毛 刺 叫 冒 险 如

9 什 么 是 竞 争 与 冒 险 现 象? 怎 样 判 断? 如 何 消 除?( 汉 王 笔 试 ) 在 组 合 逻 辑 中, 由 于 门 的 输 入 信 号 通 路 中 经 过 了 不 同 的 延 时, 导 致 到 达 该 门 的 时 间 不 一 致 叫 竞 争 产 生 毛 刺 叫 冒 险 如 FPGA 工 程 师 面 试 试 题 一 1 同 步 电 路 和 异 步 电 路 的 区 别 是 什 么?( 仕 兰 微 电 子 ) 2 什 么 是 同 步 逻 辑 和 异 步 逻 辑?( 汉 王 笔 试 ) 同 步 逻 辑 是 时 钟 之 间 有 固 定 的 因 果 关 系 异 步 逻 辑 是 各 时 钟 之 间 没 有 固 定 的 因 果 关 系 3 什 么 是 " 线 与 " 逻 辑, 要 实

More information

TD

TD *TD-000212-05* 20- 应用实例 4 本例显示的是使用两个亚低 音扬声器和多个顶箱的双声 道 立体声 设置 除了各声道都增加了一个顶 箱外 也可以增加更多的顶 箱 本例和例 3 的情况一 致 声道 2 或 右声道 声道 1 或 左声道 要接到更多的顶箱 将最后 一个顶箱的全幅线路输出接 头处的线缆接到下一个顶箱 的全幅线路输入接头 在不 降低信号质量的情况下 最

More information

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 odps-sdk 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基 开放数据处理服务 ODPS SDK SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基础功能的主体接口, 搜索关键词 "odpssdk-core" 一些

More information

序言.PDF

序言.PDF EDA VHDL VHDL VHDL EDA VHDL 1 7 9 10 FPGA 11 VHDL EDA 12 VHDL 13 VHDL 14 VHDL 12 VHDL 13 EDA / VHDL EDA 028 6636481 6241146 3201496 VHDL : ( 610054) : : : : 787 1092 1/16 14.875 343 : 1999 12 : 1999 12

More information

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas 目录 1 IPv6 快速转发 1-1 1.1 IPv6 快速转发配置命令 1-1 1.1.1 display ipv6 fast-forwarding aging-time 1-1 1.1.2 display ipv6 fast-forwarding cache 1-1 1.1.3 ipv6 fast-forwarding aging-time 1-3 1.1.4 ipv6 fast-forwarding

More information

第四章 102 图 4唱16 基于图像渲染的理论基础 三张拍摄图像以及它们投影到球面上生成的球面图像 拼图的圆心是相同的 而拼图是由球面图像上的弧线图像组成的 因此我 们称之为同心球拼图 如图 4唱18 所示 这些拼图中半径最大的是圆 Ck 最小的是圆 C0 设圆 Ck 的半径为 r 虚拟相机水平视域为 θ 有 r R sin θ 2 4畅11 由此可见 构造同心球拼图的过程实际上就是对投影图像中的弧线图像

More information

一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页

一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页 第 1 页共 32 页 crm Mobile V1.0 for IOS 用户手册 一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页 二 crm Mobile 界面介绍 : 第 3 页共 32 页 三 新建 (New) 功能使用说明 1 选择产品 第 4 页共 32 页 2 填写问题的简要描述和详细描述 第 5 页共

More information

水晶分析师

水晶分析师 大数据时代的挑战 产品定位 体系架构 功能特点 大数据处理平台 行业大数据应用 IT 基础设施 数据源 Hadoop Yarn 终端 统一管理和监控中心(Deploy,Configure,monitor,Manage) Master Servers TRS CRYSTAL MPP Flat Files Applications&DBs ETL&DI Products 技术指标 1 TRS

More information

z x / +/- < >< >< >< >< > 3 b10x b10x 0~9,a~f,A~F, 0~9,a~f,A~F, x,x,z,z,?,_ x,x,z,z,?,_ h H 0~9,_ 0~9,_ d D 0~7,x,X,z,Z

z x / +/- < >< >< >< >< > 3 b10x b10x 0~9,a~f,A~F, 0~9,a~f,A~F, x,x,z,z,?,_ x,x,z,z,?,_ h H 0~9,_ 0~9,_ d D 0~7,x,X,z,Z Verilog Verilog HDL HDL Verilog Verilog 1. 1. 1.1 1.1 TAB TAB VerilogHDL VerilogHDL C 1.2 1.2 C // // /* /* /* /* SYNOPSY SYNOPSY Design Compiler Design Compiler // //synopsys synopsys /* /*synopsys synopsys

More information

1 1

1 1 1 1 2 Idea Architecture Design IC Fabrication Wafer (hundreds of dies) Sawing & Packaging Block diagram Final chips Circuit & Layout Design Testing Layout Bad chips Good chips customers 3 2 4 IC Fabless

More information

untitled

untitled Verilog HDL Verilog HDL 邏 令 列邏 路 例 練 數 度 (top-down design) 行 (concurrency) 2.1 Verilog HDL (module) 邏 HDL 理 HDL 邏 料 數 邏 邏 路 module module_name (port_list) // 列 //

More information

Microsoft PowerPoint - 07 派生数据类型

Microsoft PowerPoint - 07 派生数据类型 能源与动力工程学院 目录 派生类型 陈 斌 固有数据类型 数值型 (numerical) 整型 INTEGER 实型 REAL 复数型 COMPLEX 非数值型 字符型 CHARACTER 逻辑型 ( 布尔型 )LOGICAL 自定义数据类型 ( 派生类型, derived type) 派生类型是指用户利用 Fortran 系统内部类型, 如整型 实型 复数型 逻辑型 字符型等的组合自行创建出一个新的数据类型,

More information

Microsoft Word - 最新正文.doc

Microsoft Word - 最新正文.doc 9 21 1.1.1 1.1.2 1 2 2 Windows 7+Office 2010 3 4 5 6 4 7 1.1.3 5 1.1.4 1 3 2 NII 1993 3 CNNIC 2014 1 16 33 1 2013 12 6.18 5358 45.8% 2012 3.7 2 2013 12 5 19.1% 2012 74.5% 81.0% 2013 3G 2013 12 2.47 2012

More information

邏輯分析儀的概念與原理-展示版

邏輯分析儀的概念與原理-展示版 PC Base Standalone LA-100 Q&A - - - - - - - SCOPE - - LA - - ( Embedded ) ( Skew ) - Data In External CLK Internal CLK Display Buffer ASIC CPU Memory Trigger Level - - Clock BUS Timing State - ( Timing

More information

<4D F736F F D20B5DAC8FDCBC4D5C2D7F7D2B5B4F0B0B82E646F63>

<4D F736F F D20B5DAC8FDCBC4D5C2D7F7D2B5B4F0B0B82E646F63> 第三章 Q3 1 1. 省略了 I/O 操作的复杂逻辑, 易实现, 耗费低 ; 2. 可以利用丰富的内存寻址模式实现灵活的 I/O 操作 Q3 2 假设存储单元 ds1 处寄存器地址为 0x2000, 代码如下 #define ds1 0x2000 while ( *ds1 == 0 ) ; Q3 3 假设设备 (dev1) 中有两个寄存器 ds1 和 dd1,dev1 的地址为 0x1000,ds1

More information

东南大学硕士学位论文 LCD 显示中灰度控制机理的研究及电路实现姓名 : 曹志香申请学位级别 : 硕士专业 : 微电子学与固体电子学指导教师 : 孙大有 20040327 LCD 显示中灰度控制机理的研究及电路实现 作者 : 曹志香 学位授予单位 : 东南大学 相似文献 (1 条 ) 1.

More information

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas 目录 1 IPv6 快速转发 1-1 1.1 IPv6 快速转发配置命令 1-1 1.1.1 display ipv6 fast-forwarding aging-time 1-1 1.1.2 display ipv6 fast-forwarding cache 1-1 1.1.3 ipv6 fast-forwarding aging-time 1-3 1.1.4 ipv6 fast-forwarding

More information

ebook105-1

ebook105-1 C D 1.1 0 1 0 1 2 ( 0 1 ) ( b i t s ) 0 1 1. 2. 0 1 3. ( ) 1-1 1-1 2 A B C A B C X Y 1.2 1.2.1 ( C D ) ( H D L ) H D L H D L J a v a C + + 1.2.2 C P U ( ) 1 3 1-2 C RT ( ) 1-2 ( C P U ) C P U C P U C P

More information

ChinaBI企业会员服务- BI企业

ChinaBI企业会员服务- BI企业 商业智能 (BI) 开源工具 Pentaho BisDemo 介绍及操作说明 联系人 : 杜号权苏州百咨信息技术有限公司电话 : 0512-62861389 手机 :18616571230 QQ:37971343 E-mail:[email protected] 权限控制管理 : 权限控制管理包括 : 浏览权限和数据权限 ( 权限部分两个角色 :ceo,usa; 两个用户

More information

101

101 Lecture 04 Modeling, Anlysis nd Simultion in Logic Design 逻辑设计中的建模 分析与仿真 Dr. Engineering Design Process 工程设计过程 定义问题研究勾画可能的解答 Identify nd define prolem reserch sketch possile solutions 建模 Modeling 分析 Anlysis

More information

Chapter #

Chapter # 第三章 TCP/IP 协议栈 本章目标 通过本章的学习, 您应该掌握以下内容 : 掌握 TCP/IP 分层模型 掌握 IP 协议原理 理解 OSI 和 TCP/IP 模型的区别和联系 TCP/IP 介绍 主机 主机 Internet TCP/IP 早期的协议族 全球范围 TCP/IP 协议栈 7 6 5 4 3 应用层表示层会话层传输层网络层 应用层 主机到主机层 Internet 层 2 1 数据链路层

More information

1 CPU

1 CPU 2000 Tel 82316285 82317634 Mail [email protected] 1 CPU 2 CPU 7 72 A B 85 15 3 1/2 M301 2~17 : 3/4 1/2 323 IBM PC 1. 2. 3. 1. 2. 3. 1.1 Hardware Software 1.2 M3 M2 M1 1.2 M3 M1 M2 M2 M1 M1 M1 1.2 M3 M1

More information

untitled

untitled 2004-2-16 (3-21) To Luo 207 Xilinx FPGA/CPLD ISE Xilinx Integrated Software Environment 6.1i FPGA VHDL VerilogHDL EDIF ModelSim FPGA FPGA ISE HDL FPGA ISE 7.1 7.1.1 ISE6.1i ISE6.1i ISE ModelSim ISE ModelSim

More information

chap07.key

chap07.key #include void two(); void three(); int main() printf("i'm in main.\n"); two(); return 0; void two() printf("i'm in two.\n"); three(); void three() printf("i'm in three.\n"); void, int 标识符逗号分隔,

More information

CH01.indd

CH01.indd 3D ios Android Windows 10 App Apple icloud Google Wi-Fi 4G 1 ( 3D ) 2 3 4 5 CPU / / 2 6 App UNIX OS X Windows Linux (ios Android Windows 8/8.1/10 BlackBerry OS) 7 ( ZigBee UWB) (IEEE 802.11/a/b/g/n/ad/ac

More information

Converting image (bmp/jpg) file into binary format

Converting image (bmp/jpg) file into binary format RAiO Image Tool 操作说明 Version 1.0 July 26, 2016 RAiO Technology Inc. Copyright RAiO Technology Inc. 2013 RAiO TECHNOLOGY INC. www.raio.com.tw Revise History Version Date Description 0.1 September 01, 2014

More information

数学分析(I)短课程 [Part 2] 4mm 自然数、整数和有理数

数学分析(I)短课程 [Part 2]   4mm 自然数、整数和有理数 .. 数学分析 (I) 短课程 [Part 2] 自然数 整数和有理数 孙伟 华东师范大学数学系算子代数中心 Week 2 to 18. Fall 2014 孙伟 ( 数学系算子代数中心 ) 数学分析 (I) 短课程 Week 2 to 18. Fall 2014 1 / 78 3. 自然数理论初步 孙伟 ( 数学系算子代数中心 ) 数学分析 (I) 短课程 Week 2 to 18. Fall 2014

More information

论文,,, ( &, ), 1 ( -, : - ), ; (, ), ; ;, ( &, ),,,,,, (, ),,,, (, ) (, ),,, :. : ( ), ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ), ( ),,,, 1 原译作 修补者, 但在英译版本中, 被译作

论文,,, ( &, ), 1 ( -, : - ), ; (, ), ; ;, ( &, ),,,,,, (, ),,,, (, ) (, ),,, :. : ( ), ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ), ( ),,,, 1 原译作 修补者, 但在英译版本中, 被译作 * 夏传玲 : 本文简要回顾了国内外定性研究在最近 多年的发展概况, 总结 了定性研究的六个发展趋势和分析策略上的三种流派 在上述两种背景下, 本文探讨了计算机辅助的定性分析给定性研究带来的机遇和挑战, 特别是它和手工操作对比时的优势和劣势, 以及应用这种定性分析技术所可能面临的困难 : 定性研究定性分析 文化差异,, (, ),,,, ( - ) ( - ) ( - ) ( - ) ( - ) (

More information

科学计算的语言-FORTRAN95

科学计算的语言-FORTRAN95 科 学 计 算 的 语 言 -FORTRAN95 目 录 第 一 篇 闲 话 第 1 章 目 的 是 计 算 第 2 章 FORTRAN95 如 何 描 述 计 算 第 3 章 FORTRAN 的 编 译 系 统 第 二 篇 计 算 的 叙 述 第 4 章 FORTRAN95 语 言 的 形 貌 第 5 章 准 备 数 据 第 6 章 构 造 数 据 第 7 章 声 明 数 据 第 8 章 构 造

More information

第一章三角函数 1.3 三角函数的诱导公式 A 组 ( ) 一 选择题 : 共 6 小题 1 ( 易诱导公式 ) 若 A B C 分别为 ABC 的内角, 则下列关系中正确的是 A. sin( A B) sin C C. tan( A B) tan C 2 ( 中诱导公式 ) ( ) B. cos(

第一章三角函数 1.3 三角函数的诱导公式 A 组 ( ) 一 选择题 : 共 6 小题 1 ( 易诱导公式 ) 若 A B C 分别为 ABC 的内角, 则下列关系中正确的是 A. sin( A B) sin C C. tan( A B) tan C 2 ( 中诱导公式 ) ( ) B. cos( 第一章三角函数 1. 三角函数的诱导公式 A 组 一 选择题 : 共 6 小题 1 ( 易诱导公式 ) 若 A B C 分别为 ABC 的内角 则下列关系中正确的是 A. sin( A B) sin C C. tan( A B) tan C ( 中诱导公式 ) B. cos( B C) cos A D. sin( B C) sin A sin60 cos( ) sin( 0 )cos( 70 ) 的值等于

More information

第5章:汇编语言程序设计

第5章:汇编语言程序设计 第 5 章 : 汇编语言程序设计 程 汇编语言指令格式 系统伪指令 存储器选择方式 常用子程序 1 汇编语言程序设计 PIC 指令系统 语言系统 指 CPU 编 器语言 器语言 器语言 设计 用 语言 设计 语言 汇编语言 2 汇编语言指令格式 汇编语言指令格式 ( 指令 ) label opcode operand comment 指令 用 存 指令 指令语 3 汇编语言指令格式 1 指令 用 指令

More information

untitled

untitled 01 1-1 Altera Installer 1-2 1-3 FBBCar 1-4 FPGA 1. 2. 3. 4. FBBCar Altera FPGA FBBCar Quartus II ModelSim-Altera 1-1 1-1 FBBCar 1 220 2 10k 2 1k 2 2k 2 470k 2 1 950nm 2 2 38kHz 2 2 3PIN 2 2 1 1 2 01 Altera

More information

Microsoft PowerPoint - string_kruse [兼容模式]

Microsoft PowerPoint - string_kruse [兼容模式] Strings Strings in C not encapsulated Every C-string has type char *. Hence, a C-string references an address in memory, the first of a contiguous set of bytes that store the characters making up the string.

More information

6.3 正定二次型

6.3 正定二次型 6.3 正定二次型 一个实二次型, 既可以通过正交变换化为标准形, 也可以通过拉格朗日配方法化为标准形, 显然, 其标准形一般来说是不惟一的, 但标准形中所含有的项数是确定的, 项数等于二次型的秩 当变换为实变换时, 标准形中正系数和负系数的个数均是不变的 定理 ( 惯性定理 ) 设有二次型 f =x T Ax, 它的秩为 r, 如果有两个实的可逆变换 x=c y 及 x=c z 分别使 f =k

More information

控制器 EtherCAT EtherCAT EtherCAT 接下一个电机驱动模块 (X4) 接下一个电机驱动模块 (X5) X11 IN X4 IN X3 OUT X5 IN X6 OUT X2 X1 X4 IN X3 OUT X5 IN X6 OUT X2 X1 SYS STA DC BUS D

控制器 EtherCAT EtherCAT EtherCAT 接下一个电机驱动模块 (X4) 接下一个电机驱动模块 (X5) X11 IN X4 IN X3 OUT X5 IN X6 OUT X2 X1 X4 IN X3 OUT X5 IN X6 OUT X2 X1 SYS STA DC BUS D 控制器 thert thert thert 接下一个电机驱动模块 () 接下一个电机驱动模块 () 电机驱动模块 电机驱动模块 电源模块 接下一个电机驱动模块 () 接下一个电机驱动模块 () 接下一个电机驱动模块 () 接下一个电机驱动模块 () X 0 X 0 4 /RK /RK 注 注 制动电阻阻值 Ω Φ 80: 适用电机驱动模块型号 8-M-XXXX--XX Φ : 适用电机驱动模块型号

More information

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1 21 , 7, Windows,,,, : 010-62782989 13501256678 13801310933,,,, ;,, ( CIP) /,,. : ;, 2005. 11 ( 21 ) ISBN 7-81082 - 634-4... - : -. TP316-44 CIP ( 2005) 123583 : : : : 100084 : 010-62776969 : 100044 : 010-51686414

More information

Guava学习之Resources

Guava学习之Resources Resources 提供提供操作 classpath 路径下所有资源的方法 除非另有说明, 否则类中所有方法的参数都不能为 null 虽然有些方法的参数是 URL 类型的, 但是这些方法实现通常不是以 HTTP 完成的 ; 同时这些资源也非 classpath 路径下的 下面两个函数都是根据资源的名称得到其绝对路径, 从函数里面可以看出,Resources 类中的 getresource 函数都是基于

More information

Microsoft Word - 新3.doc

Microsoft Word - 新3.doc 第三篇 VHDL 的应用 本篇内容 3.1 带你认识 VHDL 3.2 单项训练项目 3.3 综合实训项目 16-4 编码器的设计 3.1 带你认识 VHDL VHDL 的英文全称是 Very-High-Speed Integrated Circuit Hardware Description Language, 翻译成中文意思是超高速集成电路硬件描述语言 VHDL 语言是 20 世纪 80 年代出现的,

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: [email protected] 49 [P.51] C/C++ [P.52] [P.53] [P.55] (int) [P.57] (float/double) [P.58] printf scanf [P.59] [P.61] ( / ) [P.62] (char) [P.65] : +-*/% [P.67] : = [P.68] : ,

More information

Ps22Pdf

Ps22Pdf ( ) ( 150 ) 25 15 20 40 ( 25, 1, 25 ), 1. A. B. C. D. 2. A. B. C. D. 3., J = 1 H = 1 ( A B, J', J, H ) A. A = B = 1, J' =0 B. A = B = J' =1 C. A = J' =1, B =0 D. B = J' = 1, A = 0 4. AB + AB A. AB B. AB

More information

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式] Arrays and Strings 存储同类型的多个元素 Store multi elements of the same type 数组 (array) 存储固定数目的同类型元素 如整型数组存储的是一组整数, 字符数组存储的是一组字符 数组的大小称为数组的尺度 (dimension). 定义格式 : type arrayname[dimension]; 如声明 4 个元素的整型数组 :intarr[4];

More information

enews174_2

enews174_2 103 CMOS Seal-Ring 104 e-learning 104 104 / http://www.cic.org.tw/login/login.jsp CIC Introduction to Conversational French - Syllabus Summer 2004 1 4 21 CMOS MorSensor MorFPGA DUO 2 MorSensor 3 103 (

More information

Application Note Transient Voltage Suppressors (TVS) for 表 1 VISHAY 的 SM6T 系列的电特性 25 C 型号 击穿电压 器件标识码 V BR AT I T I T 测试电流 (ma) 关态电压 V RM 漏电流 I RM AT V

Application Note Transient Voltage Suppressors (TVS) for 表 1 VISHAY 的 SM6T 系列的电特性 25 C 型号 击穿电压 器件标识码 V BR AT I T I T 测试电流 (ma) 关态电压 V RM 漏电流 I RM AT V VISHAY GE NERAL SEMICONDUCTOR 瞬态电压抑制器 应用笔记 用于汽车电子保护的瞬态电压抑制器 (TVS) Soo Man (Sweetman) Kim, Vishay I) TVS 的重要参数 TVS 功率等级 TVS Vishay TVS 10 μs/1000 μs (Bellcore 1089) 1 TVS ESD 8 μs/20 μs 2 1 10 µs 10 µs/1000

More information

C/C++语言 - C/C++数据

C/C++语言 - C/C++数据 C/C++ C/C++ Table of contents 1. 2. 3. 4. char 5. 1 C = 5 (F 32). 9 F C 2 1 // fal2cel. c: Convert Fah temperature to Cel temperature 2 # include < stdio.h> 3 int main ( void ) 4 { 5 float fah, cel ;

More information

12 Differential Low-Power 6x6 12 bit multiply 1

12 Differential Low-Power 6x6 12 bit multiply 1 12 Differential Low-Power 6x6 12 bit multiply 1 2 07 1.1 07 1.2 07 1.2.1 (Sequential Structure Multiplier )07 1.2.2 (Array Structure Multiplier) 09 1.2.3 (Parallel Multiplier) 10 1.2.3.1 10 1.2.3.2 10

More information

Microsoft Word - CX1000-HMI_程序开发_PLC通讯

Microsoft Word - CX1000-HMI_程序开发_PLC通讯 用 VB.Net 开发 CX1000 的 HMI 第二部分和 TwinCAT PLC 通讯 一 TwinCAT 动态库 TwinCAT.Ads.dll The TwinCAT.Ads.dll 是一个.NET 类库, 它提供和 ADS 设备通讯的类 如果 TwinCAT PLC 运行在 IPC 上, 则需要添加的类库是路径 \TwinCAT\ADS Api\.NET\v1.1.4322 下的 TwinCAT.Ads.dll

More information

PowerPoint Presentation

PowerPoint Presentation 课程代码 :04830100 EDA 和 Verilog HDL 专题 佟冬 Microprocessor R&D Center [email protected] http://mprc.pku.edu.cn/courses/digital/2011fall 1 电子设计自动化软件 CAD, Computer-aid Design EDA, Electronic Design Automatic

More information

前言

前言 FPGA/CPLD FPGA/CPLD FPGA/CPLD FPGA/CPLD FPGA/CPLD 1.1 FPGA/CPLD CPLD Complex Programable Logic Device FPGA Field Programable Gate Array 1.3 CPLD/FPGA PLD PLD ASIC PLD PLD PLD FPGA PLD 7032LC 3 PLD 70 1

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 第 1 章程序设计和 C 语言 1.1 什么是计算机程序 1.2 什么是计算机语言 1.3 C 语言的发展及其特点 1.4 最简单的 C 语言程序 1.5 运行 C 程序的步骤与方法 1.6 程序设计的任务 1.1 什么是计算机程序 程序 : 一组计算机能识别和执行的指令 只要让计算机执行这个程序, 计算机就会自动地 有条不紊地进行工作 计算机的一切操作都是由程序控制的, 离开程序, 计算机将一事无成

More information