PSS
PSS(Portable Stimulus Standard)是一种标准化语言和方法,用于描述复杂硬件/软件系统中的测试和验证场景。它由Accellera组织开发,旨在提供一种可移植、可重用的验证解决方案,适用于从单个IP模块到复杂SoC的设计。
PSS的核心理念
- 描述层次(上层):
PSS 的重点是更高抽象层级的验证描述。它允许设计者定义:- 系统中需要验证的功能行为。
- 不同组件之间的交互。
- 各种场景的组合和约束。
- 与下层的关系:
PSS 上层可以独立于底层平台。通过将上层的验证意图映射到具体平台或工具,PSS 提供了一种“编写一次,运行多次”的机制。例如:- 一个PSS场景可以生成UVM验证环境、SystemC仿真场景,甚至自动生成固件代码用于FPGA原型。
PSS 上层特点
- 平台无关性:PSS上层是硬件和软件无关的,可以描述在不同平台上复用的场景。
- 约束驱动:允许用户指定输入变量、约束条件,以及通过随机化生成不同测试场景。
- 可组合性:支持将小的验证场景模块化组合为更大的复杂场景。
PSS 上层与下层的典型架构
- 上层(Abstract Layer):
- 编写的是抽象验证意图,例如:事务、组件间的交互、输入输出关系。
- 与工具或底层实现无关。
- 下层(Platform-Specific Layer):
- 将上层的描述映射到具体工具或平台,例如:
- 转化为UVM代码。
- 调度到SoC验证平台(FPGA/模拟器)。
- 转换为嵌入式软件测试代码。
- 将上层的描述映射到具体工具或平台,例如:
UVM
UVM (Universal Verification Methodology) 是一种基于 SystemVerilog 的硬件验证框架,用于开发可复用、模块化和可扩展的验证环境。它由 Accellera Systems Initiative 开发,并被广泛用于 ASIC 和 FPGA 的设计验证领域。UVM 提供了一组标准的类库和方法学,用于提高验证效率和降低开发成本。
UVM 的主要特点
- 可重用性 (Reusability):
- UVM 提供了可复用的组件,如驱动器 (driver)、监视器 (monitor)、激励生成器 (sequence generator)、和检查器 (checker)。
- 验证环境可以在不同的项目中复用,减少开发时间。
- 面向对象编程:
- 基于 SystemVerilog 的面向对象特性,UVM 支持继承和多态,便于验证组件的扩展和维护。
- 标准化:
- 通过 Accellera 标准化,UVM 统一了硬件验证的框架,大大降低了学习曲线和跨团队的沟通成本。
- 高级功能:
- 包括随机激励生成 (Constrained Random Verification, CRV)、功能覆盖率 (Functional Coverage)、和事务级建模 (Transaction-Level Modeling, TLM)。
UVM 验证环境的主要组件
- UVM 环境 (uvm_env):
- 是验证环境的顶层封装,包含多个验证组件。
- 激励生成器 (Sequence and Sequencer):
- Sequence:定义具体的激励。
- Sequencer:管理激励的生成过程。
- 驱动器 (Driver):
- 将激励从事务级 (transaction-level) 转换为信号级 (signal-level),并驱动设计。
- 监视器 (Monitor):
- 采集 DUT(Design Under Test)的信号,并将其转化为事务。
- 检查器 (Scoreboard):
- 验证 DUT 的输出是否与期望的行为一致。
- 代理 (Agent):
- 封装驱动器、监视器、和 sequencer,为 DUT 提供完整的接口交互。
- TLM 通信机制:
- 用于在组件之间传递事务,提高验证环境的灵活性。
UVM 的工作流程
- 环境搭建:
- 构建 UVM 验证环境,定义组件和其交互。
- 激励生成:
- 使用随机激励和约束生成复杂的测试场景。
- 仿真和监控:
- 仿真 DUT 的行为,监控输入输出信号。
- 功能覆盖率收集:
- 确保所有设计功能都被验证到。
- 结果检查:
- 使用检查器或断言验证 DUT 输出的正确性。
UVM 的典型应用场景
- 芯片设计验证:
- 用于 ASIC 和 FPGA 的功能验证,特别是在复杂数字逻辑的开发中。
- 协议验证:
- 验证诸如 PCIe、Ethernet 等通信协议的实现。
- IP 模块验证:
- 用于验证独立的 IP 模块,并确保其在系统级的正确性。
UVM 的优势和挑战
- 优势:
- 提高验证效率和代码复用。
- 提供了一套统一的验证框架,便于团队协作。
- 支持复杂的激励生成和覆盖率收集。
- 挑战:
- 学习曲线较高,初学者需要熟悉 SystemVerilog 和 UVM 类库。
- 验证环境的搭建可能较为复杂,需注意模块化设计。
模型抽象级别
标准的C/C++可以对系统的算法进行描述,但是无法模拟硬件的并发性行为,即无法评估硬件系统架构。
SystemC其实就是C++的一个类库,在标准C++的基础上建立了一个Simulation Kernel,来对各种process的执行顺序进行调度。这个Kernel的算法思想是把连续的仿真时间划分为多个离散的仿真时刻,再把一个仿真时刻划分为多个delta-cycle(增量循环)。这样就可以在这些delta-cycle中用顺序执行的编程语言来模拟硬件的并行性行为。
用SystemC进行模型开发,表面上是在玩C++语法。但随着抽象层次不断地向下refine达到cycle-accurate,就需要对硬件的行为(尤其是RTL级)有深刻的理解。所以RTL背景的人可以很容易开发出周期精确的模型,当他们把抽象层次继续向上就比较困难;而要让纯软件背景的人把抽象层次不断向下,他们又对硬件的并发性理解不够深刻。
描述抽象层次可以分为算法级(ALM)、系统结构级(SAM)、事务级(TLM)、RTL。模型抽象级别:
– 不计时 (UT)
– 时间宽松 (LT)
– 大约定时 (AT)
– 寄存器传输逻辑 (RTL)
– 引脚和周期精确 (PCA)
– 总线周期精确 (BCA)
该图的 y 轴是模型功能的抽象级别。 x 轴是模型或逻辑块接口或通信的抽象级别。
TLM(事务级建模)方法
松散定时
快速的松散定时 (Loosely Timed) 模型通常使用阻塞传送接口、直接存储器接口和时间解耦。
松散定时模型主要用于软件开发和软件性能评估,也可以做体系结构分析。
近似定时
近似定时 (Approximately Timed) 模型通常使用非阻塞传送接口和净核事件队列。主要用于体系结构探索和性能分析。
近似定时建模通过非阻塞传送接口支待,主要用于体系结构探索和性能分析。在一个非阻塞传送中存在多个定时点,不同传送相位可以分别用时间标注。在近似定时建模中,一个事务被划分为多个相位,由不同的时间点进行分割。在基础协议中基本的时间点包括请求的开始和结束、应答的开始和结束。特定的协议可以进一步包括更多的时间点,但可能导致与通用净核失去兼容。
在近似定时建模时,一般不使用时间解耦,这是因为考虑到定时精度的需求。在近似定时建模时,每一个进程根据 SystemC 调度器的时间进度执行。在近似定时建模时,通过使用两种延迟:目标延迟和发起延迟 发起延迟也可以看作目标接收延迟。延迟一般使用 wait(delay)和notify(delay) 实现。
TLM2.0 核心接口包括四种,即阻塞、非阻塞传送接口、DMI、调试传送接口
阻塞、非阻塞传送接口都支待定时标注 (Timing annotation) 和时间解耦 (Termoral decoupling) 。阻塞传送接口没有事务处理相位参数,阻塞传送接口与非阻塞传送接口的事务处理相位的任何对应都是名义上的。非阻塞传送接口返回一个值指示返回路径是否已使用。
阻塞传送接口主要为支持松散时间建模。
b_transport(TRANS& trans,sc_core: :sc_time& t) 是阻塞事务处理接口的唯一方法。
非阻塞传输 使用 nb_transport_fw(前向调用)和 nb_transport_bw(后向调用)
- 阻塞传输接口(Blocking Transport Interface,
b_transport)- 适用于简单、同步的通信方式。
- 发送方在调用
b_transport()后会被阻塞,直到目标端完成处理。 - 常用于时序精度不高或对性能要求不高的场景,如软件模拟或功能验证。
- 非阻塞传输接口(Non-blocking Transport Interface,
nb_transport_fw/nb_transport_bw)- 适用于并发处理和高效的仿真。
- 传输请求可以是立即完成,也可以被延迟。
- 采用 握手协议,通常使用
tlm::tlm_phase进行三相握手(请求、响应、完成)。 - 适用于高性能仿真,如 CPU 总线、缓存一致性协议等。
DMI 缺省的事务对象的类型为 tlm_generic_payload ,
算法建模
算法建模是关于特定应用算法的定义,例如手机接收器、加密、视频和多种形式的数字信号处理的算法。与 RTL 调试相比,软件环境中的优化提供了更加友好的调试环境。此外,ESL 方法背景下的软件模型为架构师提供了一个询问和回答一系列问题的平台,例如:
- 我们的算术可以有多马虎,但算法仍然有效?
- 我可以使用纯软件解决方案吗?
- 我可以为我的处理器使用较低的时钟频率(从而节省电量)
- 如果我使用硬件协处理器来实现该算法?
在完成实际算法的设计(使其工作)后,算法架构师通常会将算法从浮点实现细化为定点(由 SystemC 支持)实现(用于硬件实现)。该架构师还将算法划分为硬件和软件。具有丰富经验的架构师将了解软件和硬件之间的不同权衡,并将使算法实现在所选空间中非常可实现(例如最小化硬件实现的内存使用)。
此活动通常在 SystemC、C/C++ 或 MATLAB 或其他商业工具中执行。这项工作通常会通过针对特定应用空间的库来增强,例如定点库、数字信号处理库等。
架构建模
架构建模是关于硬件和软件分区、子系统分区。它还涉及总线性能分析以及基于电源管理概念的其他初步改进。该建模还包括现有的知识产权和设计以及其他基于产品的技术和管理关键参数和权衡。在此活动中提出的一些问题是:
- 在硬件上实现性能提升?
- 总线性能是否足够?我们需要多个bus或bus分层?
- 预计的规模和成本是否在我们的产品目标之内?
- 不同的处理器会提高性能吗?较低的时钟频率或处理器能力较差会影响性能吗?
此活动可以使用 SystemC 或支持建模或模拟并发的其他语言来执行。过去,一些团队使用C或C++。这些团队被迫开发自定义模拟内核来支持并发。
对于此活动,系统的非关键部分多次使用“流量生成器”进行建模。这些生成器基于对非关键元件生成的总线流量的估计。随着模型的完善,模块也会得到完善,以添加开始运行软件所需的细节。有多家 EDA 工具供应商提供的产品可以帮助加速这一活动。
虚拟软件开发平台
虚拟软件开发平台允许系统软件的早期开发。在此活动期间,有时会提出并回答以下问题:
- 提供给软件的控制和状态寄存器是否足够?
- 软件是否具有必要的控制并明显满足客户的要求?
- 软件能否满足当前架构的软件时序预算?
SystemC 是这组活动的首选语言。 SystemC提供了必要的仿真功能(仿真并发)、与C和C++轻松集成的能力以及仿真性能。
与 C 和 C++ 轻松集成的能力使工程师能够将指令集模拟器 (ISS) 封装在模型中。此外,在开发过程的早期,可以在主机建模处理器上包装并执行 C 或 C++ 代码(相对于 ISS)。我们称这种技术为直接执行。即使软件经过改进并且需要操作系统调用,也可以使用其他技术来实现直接执行。
硬件完善
根据系统的性质,硬件细化活动可以集中于创建可执行规范以明确指定硬件。该规范加速了硬件开发。或者,活动可以集中于行为综合的细化,从而进一步提高进度和生产力。在这组活动中提出的一些问题是:
- 预期的延迟和时钟速度是多少?
- 哪些额外的控制和状态可以轻松且廉价地提供给软件?
- 该块的估计功耗是多少?
为硬件设计人员完善规范可以消除潜在的过度设计。在开发过程中,RTL 设计人员多次会向架构师提出特定问题,而这些问题的答案将显着(甚至轻微)影响性能。当架构师被问及选项 A 或选项 B 时,由于缺乏信息,他或她会回答是或两者都回答,从而增加了复杂性。通过可改进的模型,架构师可以对 A 和 B 的影响进行建模,并可能得到两个选项都无济于事的答案。改进的模型节省了设计时间、验证时间和硅面积。
功能和架构验证
传统的硬件功能验证涉及验证硬件是否符合功能规范。不幸的是,随着系统变得越来越复杂,功能规格可以用英尺(而不是英寸)纸来衡量。显然,任何需要英语(或德语、中文、法语等)等自然语言的过程都非常容易出错(只需查看本书第一版的勘误表即可)。
我们添加了一个新术语(至少对我们来说):架构验证。这项活动的目标是回答几个大问题:
- 此系统架构是否满足客户要求?
- 此系统架构是否适用于所有客户用例?
我们特意使用了系统架构这个术语,因为这涉及至少集成最低级别的软件和硬件模型。作者拥有计算机系统设计背景,之前一直恪守“软件运行之前无法验证”的格言。由于当今几乎所有复杂的电子系统都有“计算机系统”的重要组成部分,因此我们通过强调架构验证,恢复了“年轻时”的这句格言。
通过测试平台开发目标的早期可用性以及新功能验证方法(例如开放验证方法(OVM)和 SystemVerilog 验证方法手册(VMM)中最初定义的方法)的“参考模型”,功能验证也得到了增强。
验证小组总是抱怨他们不被允许开始,直到太晚了。造成这种趋势的部分原因是管理层不愿意将资源分配给项目活动,因为这些活动要到项目后期才会显示结果或进度。 TLM 建模活动为早期开发提供了目标。 TLM 的另一个好处是可以重用系统级 TLM 模型的“测试平台”的某些部分,并用于以后使用 RTL 进行功能验证。
许多较新的验证方法(例如 OVM 和 VMM)大量使用约束随机测试激励生成。这些方法需要开发“参考模型”来检查“随机输入”的结果。考虑到经过深思熟虑的方法,功能测试平台可以使用 TLM 模型或 TLM 模型的一部分来检查 RTL 结果。
SYSTEMC
语言比较和抽象级别
严格来说,SystemC 并不是一种语言。 SystemC 是成熟语言 C++ 中的类库。 SystemC 并不是解决所有设计生产力问题的灵丹妙药。然而,当 SystemC 与 SystemC 验证库结合使用时,它确实以一种语言提供了许多与系统设计和建模任务相关的特性,而这些特性在其他语言中缺失或分散。
此外,SystemC还提供了软件和硬件的通用语言C++。
已经出现了几种语言来解决系统设计的各个方面。
尽管 Java 已经证明了它的价值,但 C/C++ 目前主要用于嵌入式系统软件(至少是较低的软件级别)。硬件描述语言VHDL和Verilog用于模拟和综合数字电路。 Vera、e 和最近的 SystemVerilog 是复杂专用集成电路 (ASIC) 功能验证的首选语言。 SystemVerilog是从Verilog语言演变而来的一门新语言,旨在解决许多面向硬件的系统设计问题。 MATLAB 以及信号处理工作台 (SPW) 和 CoCentric® System Studio 等多种其他工具和语言广泛用于捕获系统需求和开发信号处理算法。
SystemC概述
SystemC 是 C++ 类库和硬件描述与系统级建模语言,主要用于 硬件建模、系统仿真、SoC设计验证。它的核心思想是:
- 把硬件模块抽象为 C++ 对象,通过事件驱动进行通信和调度。
- 可以在 更高抽象层次(Transaction Level Modeling,TLM) 或 RTL 层次 进行建模。
SystemC vs Verilog vs 真实硬件对比
| 特性类别 | SystemC | Verilog | 真实硬件 |
|---|---|---|---|
| 模块化 | SC_MODULE,C++对象封装端口和进程 | module,RTL模块 | Gate/RTL block,逻辑单元 |
| 端口/接口 | sc_in, sc_out, sc_port | input, output, inout | 实际 pin/信号线 |
| 信号 | sc_signal,事件驱动抽象 | wire, reg | Wire/Bus,实际连线、电阻、电容等物理属性 |
| 行为建模 | SC_METHOD, SC_THREAD, SC_CTHREAD | always, initial, assign | 组合逻辑/时序逻辑,连续执行 |
| 时序控制 | wait(sc_time), event | #delay, @(posedge clk) | 时钟周期、propagation delay、setup/hold |
| 并行性 | 事件驱动仿真,可模拟并行 | RTL并行描述 | 硬件真正并行,所有逻辑同时运行 |
| 事务/抽象层次 | TLM(Transaction Level Modeling) | RTL / Gate level | Gate/Physical level |
| 阻塞/非阻塞 | wait() 可阻塞 SC_THREAD | Verilog 非阻塞赋值 <= / 阻塞赋值 = | 无阻塞概念,连续逻辑受时钟驱动 |
| 仿真精度 | 可抽象,可快速模拟大规模系统 | RTL精确,Gate级可精确时序 | 实际硬件电气时序 |
| 物理/电气特性 | 无法建模电压、电流、串扰 | Gate level 可以在 FPGA/ASIC 时考虑 parasitic | 电压、电流、功耗、信号完整性真实存在 |
| 功耗/热效应 | 需额外建模 | 需额外工具分析 | 真实功耗和温度分布 |
| 适用场景 | 系统级仿真、SoC架构验证、大规模事务建模 | RTL设计、综合、FPGA/ASIC实现 | 实际芯片制造和运行 |
| 优势 | 高抽象、快速仿真、易与C++集成 | 精确RTL建模、可综合到硬件 | 真正运行、实际性能、功耗可测 |
| 限制 | 无法精确模拟电气/功耗 | 大规模系统仿真慢,周期级精度限制 | 不便于快速架构验证和实验 |
总结
- SystemC
- 高抽象,适合系统级和事务级仿真。
- 可模拟模块、端口、事件、延迟,但电气和功耗抽象掉。
- Verilog
- RTL 层次精确建模,可综合为 FPGA/ASIC。
- 信号、端口、时钟行为和非阻塞/阻塞赋值真实对应硬件,但仿真大规模系统慢。
- 真实硬件
- 物理存在的逻辑单元、wire、电压、电流、功耗和延迟。
- 不存在 SC 或 Verilog 的“阻塞 wait”,所有逻辑真正并行执行。
核心概念
| 概念 | 含义 | 类/用法 |
|---|---|---|
| 模块(Module) | 基本的硬件单元,封装端口、信号、进程 | SC_MODULE(name) |
| 端口(Port) | 模块与外界通信接口 | sc_in<T> / sc_out<T> |
| 信号(Signal) | 模块间通信的线路 | sc_signal<T> |
| 进程(Process) | 模块内部执行的行为单元 | SC_METHOD, SC_THREAD, SC_CTHREAD |
| 事件(Event) | 信号或进程的触发机制 | sc_event |
| 时间(Time) | SystemC 的仿真时间单位 | sc_time(10, SC_NS) |
| 仿真内核(Kernel) | 调度所有进程和事件的核心 | 自动管理,启动 sc_start() |
SystemC语言体系结构
SystemC 中实现的主要面向硬件的功能包括:
- 时间模型
- 硬件数据类型
- 用于管理结构和连接的模块层次结构
- 并发执行单元之间的通信管理
- 并发模型
时间模型
SystemC 使用名为 sc_time 的类以 64 位分辨率跟踪时间。全局时间在内核中是提前的。 SystemC提供了获取当前时间和实现特定时间延迟的机制。为了支持轻松全局时间在内核使用中是先进的,枚举类型定义了从秒到飞秒的几个自然时间单位。
对于那些需要时钟的模型,提供了一个名为 sc_clock 的类。
硬件数据类型
数字硬件所需的各种数据类型并未在 C++ 本机数据类型的自然边界内提供,这些数据类型通常为 8 位、16 位、32 位和 64 位实体。
SystemC 提供硬件兼容的数据类型,支持整数和定点量的显式位宽度。此外,数字硬件需要非二进制表示,例如三态和未知数,这些由 SystemC 提供。
最后,硬件并不总是数字化的。 SystemC目前不直接支持模拟硬件;然而,已经成立了一个工作组来研究与 SystemC 中的模拟硬件建模相关的问题。对于那些面临直接模拟问题的人来说,使用浮点表示对模拟值进行建模并提供适当的行为是合理的。
层次结构
大型设计几乎总是按层次结构进行分解以管理复杂性,从而简化工程团队对设计的理解。 SystemC 提供了几种用于实现硬件层次结构的结构。为此,硬件设计传统上使用通过电线或信号互连的块。为了对硬件层次结构进行建模,SystemC 使用通过通道与其他模块互连的模块实体。层次结构来自其他模块中模块类的实例化。
通信管理
SystemC 通道提供了强大的通信建模机制。从概念上讲,通道不仅仅是简单的信号或电线。通道可以代表复杂的通信方案,最终映射到重要的硬件概念上,例如 AMBA 总线。同时,通道也可以代表非常简单的通信,例如线路或 FIFO(先进先出队列)。
能够交替使用几个完全不同的通道实现来连接模块是一个非常强大的功能。此功能使简单总线的实现可以替换为更详细的硬件实现,最终通过门来实现。
SystemC 提供了几个软件和硬件设计通用的内置通道。这些内置通道包括互斥锁和信号量等锁定机制,以及 FIFO、信号等硬件概念。
最后,模块通过端口类连接到通道和其他模块。
并发性
模拟器中的并发始终是一种幻想。模拟器在单个物理处理器上执行代码。即使您确实有多个处理器执行模拟,实际硬件设计中的并发单元数量也始终会比用于执行模拟的处理器数量多出几个数量级。
考虑模拟运行模拟器的处理器的问题。
并发执行的模拟是通过模拟每个并发单元来完成的。每个单元都被允许执行,直到需要模拟其他单元以保持行为及时一致。事实上,模拟代码本身决定模拟器何时通过使用事件进行这些切换。对于 SystemC、Verilog、VHDL 或任何其他硬件描述语言 (HDL),这种并发模拟是相同的。换句话说,模拟器使用协作多任务模型。模拟器仅提供一个内核来协调各种并发元素的交换,称为模拟进程。
SystemC 组件概述
sc_core是systemc基本的内核空间,sc_dt定义了systemc的基本数据类型
-
SC_CTOR:- 是最简单的宏,适用于大多数 SystemC 模块。
- 自动生成构造函数并注册进程。
-
SC_HAS_PROCESS:- 用于需要自定义构造函数的模块。如果构造函数采用传统的 C++方法来定义,必须在源代码中用SC_HAS_PROCESS显式声明
- 显式声明模块具有进程,但需要手动注册进程。
-
SC_AS_PROCESS:- 用于将成员函数标记为 SystemC 进程。
- 通常与
SC_HAS_PROCESS一起使用,提供更灵活的进程注册方式。
模块和层次结构
硬件设计通常包含层次结构以降低复杂性。层次结构的每个级别代表一个块。 VHDL 将块称为实体/架构对,它将接口规范与每个块的代码主体分开。在 Verilog 中,块称为模块,并在同一代码中包含接口和实现。
SystemC 与 VHDL 类似,将接口和实现分开。头文件(.h 文件)的 C++ 概念用于实体,实现概念(.cpp 文件)用于架构。
设计组件被封装为“模块”。模块是从 sc_module 基类继承的类。作为简化,提供了 SC_MODULE 宏。
模块可能包含其他模块、进程以及用于连接的通道和端口。
SystemC 线程和方法
SystemC 中,进程是一个基本执行单位,它被调用来仿真目标系统的行为。
SystemC 的基本进程有三种:
- SC_METHOD
- SC_THREAD
- SC_CTHREAD
进程的行为是多样的。有些进程的行为像函数一样被调用后立刻返回,有些进程在仿真开始以后只运行一次一直运行到仿真结束,有些进程在运行过程中可能被挂起 到某一个条件满足。引起进程运行或者解除挂起的条件可能是时钟的边沿或者信号上值的变化。 SystemC 引入了上述三个基本进程类型来描述不同进程的行为。
进程通常会有一个敏感表。当在敏感表中的信号上有事件发生时,进程就会被激活。
SC_METHOD
方法进程的特点是当敏感表上有事件发生,它就会被调用,调用后应该立刻返回。只有该类进程返回后仿真系统的事件才有可能前进,因此该类进程中不能使用 wait( )这样的语句。如果该类进程内部有一个死循环,仿真时间将会停止。
SC_THREAD
线程进程 (Threaded Proecess) 能够被挂起和重新激活。线程进程使用 wait()挂起,当敏感表中有事件发生,线程进程被重新激活运行到遇到新的 wait( )语句再重新挂起。在一次仿真中,线程进程一且退出,将不能再次进入。
SC_CTHREAD
钟控线程进程 (Clocked Thread Process) 是一种特殊的线程进程,它继承于线程进程,但只能在时钟的上升沿或者下降沿被触发或者激活,这种行为更加接近实际硬件的行为。引入钟控线程进程的目的是产生更好的行为综合(行为综合是从高层次描述综合为寄存器传输级描述的过程,请读者参考有关文献)的结果。
敏感表与 wait() 的区别
- 敏感表(sensitive) 只适用于 SC_METHOD 进程,在仿真开始时设置,不能在运行时动态更改。
-
wait()适用于 SC_THREAD 和 SC_CTHREAD 进程,它可以在进程内部 动态等待事件,不像敏感表那样是静态的。
示例:wait() 等待事件
SC_THREAD(my_thread);
sensitive << my_event; // 线程第一次启动时由 my_event 触发
void my_thread() {
while (true) {
wait(my_event); // 运行后动态等待事件
cout << "Event triggered!" << endl;
}
} 总结
| 机制 | 适用进程 | 是否动态可修改 | 作用 |
sensitive | SC_METHOD, SC_THREAD, SC_CTHREAD | ❌ 不能动态修改 | 进程在仿真开始时注册的触发条件 |
wait() | SC_THREAD, SC_CTHREAD | ✅ 可动态修改 | 进程运行后,在代码中等待某个事件发生 |
如果你是 SC_METHOD 进程,应该使用 sensitive,如果是 SC_THREAD 进程,使用 wait() 会更灵活。
为了仿真硬件行为,钟控线程进程被约束只能采用 wait( )和 wait(int N) 两种等待形式,这里是等待的时钟周期数。同时,该进程还引入了专门的复位信号说明函数:
void reset_signal_is(const sc_in<bool>&, bool) ;
void reset_signal_is(cons sc_signal<bool>&, bool) ; 只能在钟控线程进程中使用
钟控线程进程最适合来描述隐式有限状态机。所谓隐式有限状态机是指编程中并不显式的定义状态机的状态,而是通过程序中的 wait( )语句和 wait( )语句中间的赋值语句来完成对状态机的描述。显式的有限状态机通常要明确定义系统的状态,要使用 case 语句来实现状态转移。
wait( )只能用于线程进程和钟控线程进程,其作用是将进程挂起等待下一个事件发生重新激活被挂起的进程 wait( )函数有以下几种参数形式:
(1) wait( ) — 等待敏感表中有事件发生
( 2 ) wait(const c_event ) 一 等待事件发生,
(3) wait(sc_event or_list&) — 等待事件之一发生
(4) wait(sc_event_and_list&) 一 等待事件全部发生。
(5) wait(double v, sc_time_unit tu) — 等待由v和 tu 决定的一段时间
(6) wait(double v, sc_time_unit tu,const sc_event& e) 一 等待事件e的发生,但如果在v和tu 决定的一段时间内事件没有发生将不再等待。
(7) 前面所有的 (double v, sc_time_unit tu) — 两个参数可以用一个 sc_time参数替代。
(8) wait(O, SC_NS) 或者 wait(SC_ZERO_TIME) — 等待一个时间。
Sensitive为SC_METHOD和SC_THREAD程设置敏感表
有些时候希望进程在仿真的0时刻不被执行,此时可以使用 dont_initialize( )
sc_start( )函数控制所有时钟的产生并在适当的时刻激活SystemC 调度器。 SystemC 调度器控制整个仿真过程中的调度工作,包括激活进程、产生延迟、计算和更新变量和信号的值等。一旦遇到 sc_stop(), SystemC 调度器就会停止运行。
动态进程创建允许在同 个函数方法中自动创建和分配多个进程,用于临时断言检查、处理临时性的并发事件等
sc_spawn( …)
sc_spawn_options
SC_FORK
SC_JOIN
sc_process_handle: :wait()
sc_bind( …)
sc_ref( …)
sc_cref(…)
事件、敏感度和通知
事件、敏感性和通知是理解 SystemC 模拟器并发实现的非常重要的概念。
事件是通过 SystemC sc_event 和 sc_event_queue 类实现的。事件是通过事件类成员函数notify引起或激发的。通知可以在模拟过程中发生,也可以作为通道中活动的结果。当模拟内核对某个事件敏感并且该事件发生时,就会调用SC_METHOD和SC_THREAD。
SystemC 有两种类型的灵敏度:静态和动态。
静态敏感性是通过在细化时(在构造函数内)将 SystemC 敏感命令应用于 SC_METHOD 或 SC_THREAD 来实现的。动态灵敏度允许模拟过程动态改变其灵敏度。
SC_METHOD 使用 next_trigger(arg) 命令实现动态灵敏度。 SC_THREAD 通过 wait(arg) 命令实现动态敏感性。 SC_METHOD和SC_THREAD都可以在仿真过程中在动态和静态灵敏度之间切换。
例子:
SC_MODULE(MyModule) {
sc_event my_event; // 定义事件
void my_process() {
cout << "Triggered by my_event!" << endl;
}
SC_CTOR(MyModule) {
SC_METHOD(my_process);
sensitive << my_event; // 使 my_process 对 my_event 敏感
}
}; 解释:
SC_METHOD(my_process); 定义一个 SC_METHOD 进程。
sensitive << my_event; 使 my_process 进程在 my_event.notify() 发生时执行。
SystemC 数据类型
SystemC 中提供了多种硬件数据类型。由于 SystemC 语言是基于 C++ 构建的,因此所有 C++ 数据类型都可用。此外,SystemC 还允许您为新硬件技术(即多值逻辑)或电子系统设计以外的应用程序定义新数据类型。
这些数据类型是使用模板化类和大量运算符重载来实现的,因此可以像本机 C++ 数据类型一样轻松地操作和使用它们。用于数学计算的硬件数据类型(例如 sc_fixed 和 sc_int)允许对 DSP 函数等复杂计算进行建模。这些数据类型评估在自定义硬件或不具有完整浮点功能的处理器中实现的算法的性能。 SystemC提供了使用硬件数据类型所需的所有方法,包括硬件数据类型之间的转换以及从硬件到软件数据类型的转换。
四状态逻辑 (0,1,X,Z) 数据类型(例如 sc_logic)支持非二进制硬件类型。为需要数据类型来表示基本逻辑值或逻辑值向量的 RTL 硬件设计人员提供了熟悉的数据类型(如 sc_logic 和 sc_lv)。
端口、接口和通道
芯片通过管脚与电路板上其他芯片或者元件通信,管脚有输入、输出和双向管脚。在 SystemC 中,模块通过端口 (Port) 与其他模块通信。端口也分为输入 (lnput) 输出 (Output) 和双向端口 (Inout) 。一个模块的端口或者通过信号 (Signal)与其他模块的端口相连或者与子/父模块的端口直接相连。端口与信号的连接,以及端口和端口的直接连接在 SystemC 中称作绑定。信号到信号的直接连接在实际建模中没有实际意义,信号之间直接进行赋值。
端口的数据类型可以是以下类型:
- 常见 C+ +数据类型,如 long、int、char、short、float、double
- SystemC 专有数据类型,如 sc_int<n>、sc_uint<n>、sc_bigint<n>、sc_biguint<n>、sc_bit、sc_logic、sc_bv<n>、sc_lv<n>、sc_fixed、sc_ufixed、sc_fix、sc_ufix
- 用户自定义结构。如:
typedef struct_frame{ unsigned short frame_control; unsigned short duration_id; char source_addr[6]; char destina ion_addr[6]; unsigned short sequence_control; char* body; unsigned fcs; } frame;
systemc端口定义方法如下:
输入端口:sc_in<端口数据类型> 端口名
输出端口:sc_out<端口数据类型> 端口名
双向端口:sc_inout<端口数据类型> 端口名
sc_in<bool> 与 sc_in_clk 在语法上是等价的
延迟是硬件描述语言中 个非常重要的概念,它是模块的端口(和信号)与模块内数据的重要区别之一
SystemC 中引入了解析逻辑向量信号 (Resolved Logic Vector signal) 来解决多驱动的问题。可以使用下面的方法定义解析型端口:
- sc_in_rv<n> x; // x被定义为n比特宽的解析逻辑向量型输入端口
- sc_out_rv<n> y; // y被定义为n比特宽的解析逻辑向量型输出端口
- sc_inout_rv<n> z; // z被定义为n比特宽的解析逻辑向量型双向端口
SystemC信息和差错报告
SystemC 中定义 个宏用于打印和报告事件信息以及事件的严重程度,它们是:
SC_REPORT_INFO(msg_type, msg)
SC_REPORT_WARNING(msg_type, msg)
SC_REPORT_ERROR(msg_type, msg)
SC_REPORT_FATAL(msg_type, msg)
sc_assert(expr) sc_assert 的严重程度为 FATAL
SystemC 中,信息所对应事件的严重程度分为5级,如下面的代码所定义:
enum sc_severity {
SC_INFO = 0,
SC_WARNING,
SC_ERROR,
SC_FATAL,
SC MAX_SEVERITY
}; 对千不同严重程度的信息多对应的事件,仿真器采取的行动不同,共分为9个级别,如下面的代码所定义:
enum {
SC UNSPECIFIED = OxOOOO,
SC_DO_NOTHING = OxOOOl,
SC_THROW = Ox0002,
SC_LOG = Ox0004,
SC_DISPLAY = Ox0008,
SC_CACHE_REPORT = Ox0010,
SC_ INTERRUPT = Ox0020,
SC_STOP = Ox0040,
SC ABORT = Ox0080
}; sc_report_handler和sc_report是SystemC 信息和差错报告机制中最重要的两个类。 SC_REPORT_INFO、SC_REPORT_WARNING、SC_REPORT_ERROR、SC_REPORT_FATAL、sc_assert (expr) 的定义依赖于 sc_report_handler
SystemC 组件总结
现在,是时候将我们刚刚讨论的所有基本概念整合到一个插图中了,如图 2.5 所示,在涉及不同的 SystemC 组件时,该插图在整本书中多次使用。它可能看起来相当令人生畏,因为它在一张图中显示了几乎所有概念。实际上,SystemC 模块通常不会包含所有所示组件。
该图显示了可以包含另一个 sc_module 实例的 sc_module 的概念。 SC_METHOD 或 SC_THREAD 也可以在 sc_module 中定义。
模块和仿真进程(SC_METHOD 和 SC_THREAD)之间的通信是通过端口、接口和通道的各种组合来完成的。模拟过程之间的协调也是通过事件来完成的。
现在我们将简要介绍 SystemC 模拟内核,该内核协调和调度图 2.5 中所示的所有组件之间的通信
SystemC仿真内核
SystemC 模拟器有两个主要的操作阶段:细化和执行。第三个阶段(通常是次要阶段)发生在执行结束时;这个阶段可以被描述为后处理或清理。
在 sc_start() 函数调用之前执行语句称为细化阶段。该阶段的特点是数据结构的初始化、连接的建立以及第二阶段执行的准备。
执行阶段将控制权交给 SystemC 模拟内核,该内核协调进程的执行以创建并发的假象。
对于那些研究过 Verilog 和 VHDL 仿真内核的人来说,图 2-6 中的插图应该看起来非常熟悉。简而言之,在 sc_start() 之后,所有模拟进程(除了一些例外)都会在初始化期间以未指定的确定性顺序调用。
初始化后,模拟进程在发生敏感事件时运行。 SystemC 模拟器实现了协作式多任务环境。一旦启动,正在运行的进程就会继续运行,直到获得控制权。多个模拟过程可以在模拟器时间的同一时刻开始。在这种情况下,将评估所有模拟过程,然后更新其输出。更新后的评估称为增量周期。
如果此时不需要评估其他模拟过程(作为更新的结果),则模拟时间会提前。当不需要运行额外的模拟过程时,模拟结束。
这个模拟内核的简要概述旨在为您提供本书其余部分的概述。稍后将再次使用此图来解释重要的复杂性。了解内核如何运作对于完全理解 SystemC 语言非常重要。
我们在我们的网站 www.scftgu.com 上提供了该图的动画版本,演示了一个小代码示例。 IEEE 标准 1666-2005 SystemC LRM(语言参考手册)指定了 SystemC 模拟内核的行为。本手册是有关 SystemC 的权威资料。我们鼓励读者在学习 SystemC 期间使用任何或所有这些资源,以充分理解模拟内核。
数据类型
SystemC 仿真模型的用户可用的数据类型。 SystemC 语言参考手册 (LRM) IEEE 标准 1666 使用 190 多页来指定 SystemC 数据类型。我们已尽量简短一些。
因此,读者需要参考 SystemC LRM 以获取 SystemC 数据类型的完整定义以及 C++ 数据类型和其他库的其他资源。 SystemC 库提供了专为硬件建模而设计的整数、逻辑和定点数据类型。除了 SystemC 数据类型之外,SystemC 模拟还可以使用本机 C++ 数据类型、其他库数据类型和用户定义的数据类型。
SystemC 数据类型的使用不限于使用仿真内核的模型;它们可以像使用其他数据类型一样用于非模拟应用程序。尽管可以使用任何可用的数据类型创建仿真模型,但数据类型的选择会影响仿真速度、可综合性和综合结果。使用本机 C++ 数据类型可以最大限度地提高仿真性能,但会牺牲硬件保真度和可综合性。
本机 C++ 数据类型
SystemC 库提供了专为数字逻辑和定点算术建模而设计的数据类型。 SystemC 逻辑向量有两种类型:2 值逻辑和 4 值逻辑;以及两种 SystemC 数字类型:整数和定点。 SystemC 数据类型旨在与本机 C++ 数据类型、其他 SystemC 数据类型和 C++ 字符串自由混合。 SystemC 数据类型可以像任何其他 C++ 库一样在 C++ 应用程序中使用。
除单位 sc_logic 类型外,所有 SystemC 数据类型的长度可配置范围比本机 C++ 数据类型宽得多。 SystemC 提供带有类型转换的赋值和初始化操作,允许使用 C++ 数据类型、SystemC 数据类型和 C++ 字符串进行初始化或对 SystemC 数据类型进行赋值操作。所有 SystemC 数据类型都实现相等和按位运算。
SystemC 算术数据类型(整数和定点)实现算术和关系运算。这些操作的 SystemC 实现在语义上与 C++ 兼容。 SystemC还支持SystemC数据类型和C++数据类型之间的转换。
SystemC 数据类型允许单位和多位选择操作。这些选择操作的结果可以用作源(右侧)或赋值操作的目标(左侧)或与其他位选择或与 SystemC 整数或向量数据类型连接。
所有 SystemC 数据类型都是 sc_dt 命名空间的一部分,可以与作用域运算符 (::) 一起使用,以避免与其他 C++ 库发生命名冲突。
SystemC 逻辑向量数据类型
SystemC 提供两种逻辑向量类型:sc_bv(位向量)和 sc_lv(逻辑向量),以及单位逻辑类型 sc_logic。 SystemC 的早期版本定义了 sc_bit,即 sc_bv 的单位版本,已被 IEEE SystemC 标准弃用。使用 sc_bit 数据类型的旧应用程序可能会将 sc_bit 的实例替换为 C++ bool 数据类型。
SystemC 逻辑向量类型旨在在非常低的级别(接近 RTL)进行建模,并且不实现算术运算。它们确实实现了全方位的赋值和逻辑运算,但有一些明显的限制。例如,具有高 z 或未知位值的 sc_lv 无法在不丢失某些信息的情况下分配给 sc_bv。
sc_bv<W>
SystemC 位向量数据类型 sc_bv 具有与 sc_lv 相同的功能,但位值仅限于逻辑 0 或逻辑 1。 sc_bv 是一个模板类,其中 T 指定位宽度。
sc_bv<BITWIDTH> NAME…;
SystemC 位向量运算包括所有常见的按位与、按位或和按位异或运算符(即 &、|、^)。除了位选择和位范围(即 [] 和 range())之外,sc_bv 还支持 and_reduce()、or_reduce()、nand_reduce()、nor_reduce()、xor_reduce() 和 xnor_reduce() 操作。归约运算将运算符放置在所有相邻位之间。
sc_logic 和 sc_lv<W>
比布尔数据类型更有趣的是用于表示未知和高阻抗(三态)条件的四值数据类型。 SystemC 使用 sc_logic 和 sc_lv 来表示这些数据类型(图 3.4)。这些数据类型的逻辑状态表示为:
- logic 0 - SC_LOGIC_0, Log_0, or ‘0’
- logic 1 - SC_LOGIC_1, Log_1, or ‘1’
- high-impedance - SC_LOGIC_Z, Log_Z, ‘Z’ or ‘z’
- unknown - SC_LOGIC_X, Log_X, ‘X’ or ‘x’
由于其开销,这些数据类型比 bool 和 sc_bv 慢得多。 sc_logic 数据类型是模板化 sc_lv 类的单位版本,其中单个模板参数是位宽度。
SystemC 没有其他多级数据类型或驱动强度的表示,例如 Verilog 的 12 级逻辑值或 VHDL 的 9 级 std_logic 值。但是,如果确实需要,您可以创建自定义数据类型,并且可以通过 C++ 中的运算符重载来操作它们。
sc_logic NAME[,NAME]…;
sc_lv<BITWIDTH> NAME[,NAME]…;
SystemC 逻辑向量运算(图 3.5)包括所有常见的按位与、按位或和按位异或运算符(即 &、|、^)。除了位选择和位范围(即 [] 和 range())之外,sc_lv 还支持 and_reduce()、or_reduce()、nand_reduce()、nor_reduce()、xor_reduce() 和 xnor_reduce() 操作。归约运算将运算符放置在所有相邻位之间。
SystemC 整数类型
SystemC 提供两种基本整数数据类型的有符号和无符号二进制补码版本。这两种数据类型是有限精度整数类型(最大长度为 64 位)和有限精度整数类型(可以更长)。这些整数数据类型提供了本机 C++ 整数类型中不可用的功能。本机 C++ 数据类型的宽度取决于主机处理器和编译器。本机数据类型针对主机处理器指令集进行了优化,并且非常高效。这些数据类型的长度通常为 8、16、32 或 64 位。 SystemC 整数数据类型是模板化的,数据宽度可以从 1 位到数百位。除了可配置的宽度之外,SystemC 整数数据类型还允许位选择、位范围选择和串联操作。
sc_int<W> 和 sc_uint<W>
大多数硬件需要在一定程度上指定实际的存储宽度。在处理算术时,内置的 sc_int 和 sc_uint(无符号)数值数据类型(图 3.6)提供了一种有效的方法来对具有从 1 到 64 位宽的特定宽度的数据进行建模。当对数据宽度不是模拟处理器数据路径的整数倍的数字进行建模时,必须执行一些位屏蔽和移位以使内部计算结果适合声明的数据格式。
sc_int<LENGTH> NAME…;
sc_uint<LENGTH> NAME…;
sc_bigint<W> 和 sc_biguint<W>
某些硬件可能大于本机 C++ 数据类型支持的数量。为此,SystemC 提供了 sc_bigint 和 sc_biguint 数据类型(图 3.7)。这些数据类型以速度为代价提供大量支持。
sc_bigint<BITWIDTH> NAME…;
sc_biguint<BITWIDTH> NAME…;
SystemC 定点类型
SystemC 定点数据类型解决了对无法证明使用浮点硬件合理的 DSP 应用进行建模时对非整数数据类型的需求。由于仿真速度要高得多,早期的 DSP 模型应使用本机 C++ 浮点数据类型进行开发。随着设计的发展,定点数据类型可以提供更高保真度的信号处理逻辑建模,并可用于提供通向可综合设计的路径。
SystemC 提供多种定点数据类型:有符号和无符号、编译时(模板化)和运行时可配置以及固定精度和有限精度 (_fast) 版本。
SystemC 定点数据类型(图 3.9)的特征在于字长(总位数)及其整数部分长度。可选参数提供溢出和量化模式的控制。
与其他 SystemC 数据类型不同,定点数据类型可以在编译时使用模板参数进行配置,也可以在运行时使用构造函数参数进行配置。无论如何创建定点数据对象,它都无法在程序执行过程中稍后更改。
sc_fixed<WL,IWL[,QUANT[,OVFLW[,NBITS]> NAME;
sc_ufixed<WL,IWL[,QUANT[,OVFLW[,NBITS]> NAME;
sc_fixed_fast<WL,IWL[,QUANT[,OVFLW[,NBITS]> NAME;
sc_ufixed_fast<WL,IWL[,QUANT[,OVFLW[,NBITS]> NAME;
sc_fix NAME(WL,IWL[,QUANT[,OVFLW[,NBITS]);
sc_ufix NAME(WL,IWL[,QUANT[,OVFLW[,NBITS]);
sc_fix_fast NAME(WL,IWL[,QUANT[,OVFLW[,NBITS]);
sc_ufix_fast NAME(WL,IWL[,QUANT[,OVFLW[,NBITS]); // to enable fixed-point data types
#define SC_INCLUDE_FX
#include <systemc>
// fixed-point data types are now enabled
sc_fixed<5,3> compass // 5-bit fixed-point word 由于其编译时开销,定点数据类型在默认的 SystemC 包含文件中被省略。要启用定点数据类型,必须在包含 SystemC 头文件之前定义 SC_INCLUDE_FX(图 3.10)。
定点数据类型有几个容易记住的区别。首先,那些以fast结尾的数据类型比其他数据类型更快,因为它们的精度内部限制为53位;快速类型是使用 C++ double 实现的。
其次,前缀 sc_ufix 表示无符号,就像 uint 表示无符号整数一样。第三,过去时态 ed 后缀来 fix 表示模板化数据类型必须具有使用编译时常量定义的静态参数。
请记住,fixed 是过去时(即已经一成不变),并且在编译后无法更改。在运行时,您可以创建和使用非模板化(修复版本)数据类型的新对象,根据需要改变配置。
尽管 sc_fix、sc_ufix、sc_fix_fast 和 sc_ufix_fast运行时可配置,一旦创建这些类型的对象,其配置就无法修改。
定点数据类型所需的参数包括字长 (WL)、整数字长 (IWL)、量化模式 (QUANT)、溢出模式 (OVFLW) 和饱和位数 (NBITS)。字长 (WL) 和整数字长 (IWL) 没有默认值,必须设置。
字长确定了表示数据类型的总位数。
整数字长指示放置二进制小数点的位置,可以是正数或负数。下面的图 3.11 显示了其工作原理。
SystemC 文字和字符串
文字数据的表示是所有语言的基础。 C++ 允许使用整数、浮点数、布尔值、字符和字符串。 SystemC 为其数据类型提供相同的功能,并使用 C++ 表示形式作为基础。
SystemC 字符串文字表示
SystemC 字符串文字可用于将值分配给任何 SystemC 数据类型。 SystemC 字符串文字由前缀、大小和可选的符号字符“+”或“-”组成。可选符号可以位于十进制以及符号和数值形式的前缀之前,但不允许用于无符号、二进制、八进制和十六进制。二进制、八进制和十六进制的负值可以使用二进制补码表示法来表示。表 3.4 列出了支持的前缀。
SystemC 数据类型的实例可以使用数据类型的 to_string 方法转换为标准 C++ 字符串。结果字符串的格式使用表 3.4 左栏所示的 sc_numrep 枚举来指定。上表最右列显示了这种格式的示例;使用的值为 13 和 -13。 to_string 方法有两个参数:表第一列的数字表示;和 bool 将表示前缀添加到结果字符串中。
字符串输入和输出
SystemC 字符串文字表示和流式 IO 使用相同的格式表示数据。 SystemC 支持使用输入提取器(运算符>>)的输入流和使用输出插入器(运算符<<)的输出流。
输入流使用表 3.4 第二列中所示的文字前缀来定义从输入流读取的数据的格式。这与将文字分配给 SystemC 变量时使用的格式相同。
输出流可以使用 C++ 输出流操纵符 dec、oct 和 hex 来控制 SystemC 数据类型的显示格式。可以通过使用数据类型的 to_string 方法来获得附加显示控制。
SystemC LRM 的早期版本定义了唯一的字符串类型 sc_string,现已弃用。新的 SystemC 应用程序应使用标准 C++ 字符串和本节中描述的 to_string 方法。
下面是图 3.14 中的几个示例,演示了 SystemC 文字、to_string 方法和输出流的使用:
SystemC 数据类型的运算符
SystemC 数据类型支持所有带有运算符重载的常见操作,如表 3.5 所示。
此外,SystemC 还提供特殊方法来访问位、位范围并执行显式转换,如表 3.6 所示。
位和部分选择以及串联操作的支持与硬件描述语言非常相似。
这些数据类型(和 C++ 数据类型)经常被忽视的一个方面是算术运算中的混合类型。混合不同长度的相似数据类型是可以的,但交叉类型是危险的。例如,将涉及两个 sc_int 变量的运算结果分配给 sc_bigint 不会自动将操作数提升为 sc_bigint 以进行中间计算。要实现这一点,必须使参数之一为 sc_bigint 或对至少一个操作数参数之一执行显式转换。下面是一个添加的例子。
更高层次的抽象和 STL
为了支持 TLM 建模, SystemC 定义了接口 (Interface ) 端口 (Port) 和通道(Channel)
接口是一个 C++抽象类。抽象类的特点是它定义了一组抽象方法,这些方法都以纯虚函数(纯虚函数指给出函数的声明,不给出函数体,它们必须在子类中重载)的方式出现。
通道实现一个或者多个接口。也就是说,通道继承一个或者多个接口,这些接口中定义的抽象方法在通道中实现。通道分为基本通道 (primitive channel) 和分层通道(hierarchical channel) 。基本通道不包括任何可见的内部结构、进程,基本通道包括 SC_fifo sc_signal sc_signal_rv sc_buffer sc_sephmore sc_mutex ;分层通道本身是一个模块,它实现了某一个或者多个接口,当然也可以调用包括基本通道在内的其他通道。
模块中的进程可以通过端口使用通道提供的方法。端口总是与一定的接口类型相关联的,端口只能连接到实现了该类接口的通道上。模块的进程只能通过端口访问通道,却不能直接访问通道。一个进程通过端口调用通道提供的方法,但它不必知道具体的方法如何实现。通道实现了具体的方法,但自己却不能调用。这种接口的定义和实现分开的方法是面向对象中的一个重要概念。
接口
SystemC 中, sc_interface 是所有接口的基类,任何一个接口必须是直接或者间接继承 sc_interface
方法 register_port(sc_port_base& port_, canst char* if_typename_) 完成通道绑定 (Bind) 时的静态规则检查。 port_是接口所连接的端口名, if_typename_是接口名。缺省的情况下,这个函数不做任何事情。
通道可以使用 default_event( )来返回静态敏感表的缺省事件。 default_ event()中调用的SC_REPORT_WARNING(SC_ID_NO_DEFAULT_EVENT_, 0)用于产生警告信息。
端口
对于基本的端口类型,可以调用的接口方法有write( )和 read( )
sc_port<InterfaceType, ChannelNumber= 1>Interface Type 是端口所要连接的通道的接口类型, ChannelN umber 代表端口
所要连接的最大通道数,默认值是1
类sc_port<IF, N> 是所有端口的基类,它是一个模板类。 IF 是接口类型,N是所连接的同一类型的通道数目,也就是接口数,它的缺省值是1
重载操作符 operator (IF& interface) 用千将端口与接口捆绑起来,实现端口与通道接口的互通,这是端口的最基本操作。操作符 operator () (sc_port_b<IF>& port) 用千将端口与父模块的端口捆绑起来,使端口支持分层的模块化设计。
通道
在同一模块内,各个进程之间也需要通信,它们可以通过共享变量、握手信号、模块内通道等方式通信 如果它们直接的通信是通过模块内通道,则此时不需要通过端口,即所谓直接通道调用
SystemC 约定:当一个端口与一个通道相连接,这时相应通道的 register_port()函数就会被调用。
sc_signal<T> 是最基本的通道,它用于连接模块的基本端口
sc_signal_rv<T> 是所谓解析的信号通道,它允许同时有多个端口连接到其上并进行写操作。
sc_buffer 不管 write() 写的数据是否与原数据相同,都要求进行数据更新,而 sc_signal 首先要检查新数据是否与原数据相同,如果不同才进行更新。这是 sc_signal和sc_buff 的唯一区别。
sc_mutex<T>
sc_fifo<T> SystemC 库中已经实现好的 FIFO 通道
其中 read(T& )和 read( )是阻塞型读方法,如果读的时候 FIFO 为空,则它们等到 FIFO 有数据写入时才返回数据,它们的读操作永远是成功的 nb_read(T&) 是非阻塞型读操作,它总是立刻返回。如果 FIFO 非空,则读 FIFO 成功,否则读失败
sc_semaphore SystemC2.2 定义的又一个重要的基本通道同。信号量是操作系统提供的管理公有资源的有效手段 信号量代表可用资源实体的数量,所以可以认为信号鼠就是一个资源计数器,它限制的是同时使用某共享资源(也称为临界资源)的进程的数量。
sc_event 可以用来定义一个事件,
sc_event_queue sc_event 的共同点是都有 notify( …)方法,而不同点是 SC_event_queue 实际上是一个分层通道,它可以有多个等待触发的通知,这些通知不互相覆盖
sc_export 允许一个通道将接口导出到其父模块
基本的 C++ 和 SystemC 数据类型缺乏结构和层次结构。对于这些,标准 C++ 结构体和数组是很好的起点。然而,C++ 库中可以免费提供许多非常有用的数据类型类,这提供了基于 C++ 的建模语言的另一个好处。
STL 是这些库中最受欢迎的,它随所有现代 C++ 编译器一起提供。 STL 包含许多有用的数据类型和结构,包括改进的字符数组(称为字符串)和通用容器,例如向量 、map、set、list 和 deque< T>。这些容器可以通过 STL 算法进行操作,例如 for_each()、count()、min_element()、max_element()、search()、transform()、reverse() 和 sort()。这些只是一些可用的算法。本书不会尝试详细介绍 STL,但一个简短的示例可能会激发您进一步搜索。
STL 容器 vector 与常见的 C++ 数组非常相似,但有一些有用的改进。与数组不同,向量可以进行赋值和比较。此外,向量可以动态调整大小。也许更重要的是——访问向量 的元素可以进行边界检查以确保安全。
下面的示例演示了 STL 矢量 的使用。
选择正确的数据类型
一个常见的问题是,“此设计应使用哪些数据类型?”最好的答案是,“根据当前的建模需求,选择尽可能接近本机 C++ 的数据类型。”选择本机数据类型始终会产生最快的模拟速度。表 3.7 给出了性能的概念。
不要使用 sc_int 或 sc_bigint,除非您需要比本机 C++ 数据类型提供的更多详细信息。为了获得更高的性能,最好使用 sc_int 来表示 64 位或更少的位,而不是使用 sc_bigint。一般来说,SystemC 数据类型比本机 C++ 数据类型慢,并且更复杂的 SystemC 类型比更简单的较小类型慢。
RTL综合工具通常要求所有数据都是SystemC数据类型。一些行为综合工具允许本机 C++ 数据类型。 SystemC 数据类型可用于指导行为综合工具。
SystemC LRM笔记
置于五个命名空间 sc_core、sc_dt、sc_unnamed、tlm 和 tlm_utils 之一中。
核心语言和预定义频道应放置在命名空间 sc_core 中。SystemC 数据类型应放置在命名空间sc_dt中。
细化和模拟阶段应按以下顺序运行:
- 细化 — 构建模块层次结构
- 细化 - 函数的回调 before_end_of_elaboration
- 细化 - 函数的回调 end_of_elaboration
- Simulation—函数的回调 start_of_simulation
- 仿真 - 初始化阶段
- 仿真 — 评估、更新、增量通知和定时通知阶段(重复)
- 模拟 - 函数end_of_simulation的回调
- 仿真 - 销毁模块层次结构
主要目的是根据需要在 kernel 中创建内部数据结构,以支持仿真的语义。在细化过程中,将创建模块层次结构的各个部分(模块、端口、原始通道和进程),并将端口和导出绑定到通道。
以下类 (或从这些类派生的类) 的实例可以在 e细化期间创建,并且只能在细化期间创建。在模拟结束时销毁模块层次结构之前,不得删除此类实例。
sc_module
sc_port
sc_export
sc_prim_channel
未生成的进程实例是通过调用以下三个进程宏之一创建的进程:
SC_METHOD
SC_THREAD
SC_CTHREAD