4.7 电子系统级(ESL)设计
4.7.1 ESL发展的背景
电子系统级(ESL,Electronic System Level)设计方法和ESL工具相对来说是一种较新的方法学和工具。虽然这种方法学的提出和工具的开发在20世纪90年代已经开始,由于相关工具无法配合及市场需求较少,过去几年在EDA产业一直居于不太起眼的位置。随着90nm技术的出现,上亿门规模电路的开发及系统的复杂度的剧增,ESL设计逐渐受到重视。但真正能够执行设计流程所需的ESL工具,直到最近几年才开始陆续上市。
在传统的设计过程中,SoC设计侧重于硬件,嵌入式系统设计侧重软件,而板上系统则需要更多地要兼顾软件和硬件。随着SoC设计发展,硬件设计规模越来越复杂。与此同时,软件复杂度的增长却大大超过了硬件复杂度的增加。图4-15总结了市场调研公司国际商业策略(International Business Strategies)对SoC设计工程师所做的调查数据。设计嵌入式软件和硬件结构的相关工作量随工艺的缩小而急剧增加。
图4-15 设计工作发展趋势
嵌入式软件开发工作量的增加,主要是由于能够实现消费产品之间兼容性和互操作性的无线及多媒体标准(或其中之一)越来越多。JPEG、MPEG、3G、GSM/EDGE、IEEE 802.11/a/b/g WLAN、Bluetooth和UWB等标准,都是现代电子产业获得商业成功所必需的。而硬件实现(RTL设计、综合、物理设计等)工作量的增加则要少得多。
系统结构开发工作量的增加主要是由于SoC需要集成和优化越来越复杂的处理、存储资源及通信协议。因而,在功能和系统结构级进行的系统结构探索来帮助设计师找到设计的平衡点就变得更加重要。
这些工作量的增加必然导致产品开发周期的增长,包括软件开发、硬件设计和验证时间的增加。要满足复杂度增加带来的改变,需要设计方法和EDA工具能够在设计的早期提供软硬件协同的设计环境。这就是ESL设计方法和ESL工具重新受到重视和开始成为主流的原因。目前,在全球顶级的SoC设计公司中,ESL设计已经被越来越多地采用。
4.7.2 ESL设计基本概念
ESL设计指系统级的设计方法,是从算法建模演变而来。ESL设计已经演变为嵌入式系统软硬件设计、验证、调试的一种补充方法学。这些嵌入式系统包括SoC、FPGA系统、板上系统、多板级系统。
ESL设计以抽象方式来描述SoC系统,给软硬件工程师提供一个虚拟的硬件原型平台,用以进行硬件系统结构的探索和软件程序的开发。在ESL设计中,系统的描述和仿真速度较高,让设计工程师有充裕的时间分析设计内容。ESL设计不仅能应用在设计初期与系统架构规划阶段,亦能支持整个硬件与软件协同设计的流程。
目前大多数的ESL工具包含以下功能:系统级设计、软硬件协同设计、体系结构定义、功能建模、协同验证。
4.7.3 ESL设计的流程
图4-16所示为ESL设计流程的示意图。
图4-16 ESL设计流程
首先,设计工程师接收一个设计定义的输入,这个定义可以是文本、图表、算法或某种描述语言(如UML、SLD、MATLAB等的描述)。设计工程师根据这个输入的定义,完成算法的开发,提出一种系统的结构,用ESL语言来描述这种系统结构,即系统级描述,并在此基础上完成软硬件的初步划分。
在完成基本的软硬件划分后,可以开始软件和系统级硬件的设计。这里的系统级硬件设计是指对功能单元在高抽象层次上进行建模,并完成功能设计,如用SystemC进行事务级的建模。软件设计是指根据系统结构中指定的处理器和软件任务的定义,用如C/C++或汇编语言完成应用软件的设计。
在这个阶段,开始软硬件的协同验证,即软件运行在系统的虚拟平台上。根据协同验证的结果反馈给系统结构和软硬件划分。后者根据性能、成本等因素重新做出调整。
软硬件的设计和验证,包括软硬件的协同验证,是一个迭代的过程。在整个设计过程中都要根据验证的结果对系统做出调整。
完成验证的硬件和软件设计就可以组成一个完整的系统级设计,并传递给下一级的设计作为输入。例如,ESL设计为软件应用提供C或C++语言描述的程序;为定制电路提供Verilog或VHDL语言描述的硬件设计;为硬件平台提供PCB板的功能部件或抽象层IP,如基于SystemC的IP。
需要指出的是,关于软硬件的划分有许多方法。最初是设计工程师根据经验手工划分,20世纪90年代开始出现了自动划分的方法。由于SoC系统复杂,算法的求解难度较大,只在一些EDA工具中得到了初步的应用。
图4-17所示为从可编程系统结构设计的角度来看ESL设计流程。ESL工具可以帮助进行快速的系统级建模,进行定量的系统性能分析。
在实现ESL设计流程的具体过程中,有不同的实现方法可以采用。图4-18所示的设计方法是直接由软件参考代码开始,从已有事务级(TLM)模型库中选取硬件结构(如通用处理器等),或者重新设计IP库中没有的模块的事务级模型,创建系统虚拟平台,在此基础上将任务并行化,映射到硬件结构上,然后进行验证和性能的分析。根据分析得到结果,再调整系统结构和软件,再分析,直到性能满意。通常,软件参考代码已实现了基本功能,特别是保证了算法及数据流等的正确性。例如,软件参考代码可以是某一标准协议用C语言写的参考代码。在软件参考代码和主要的事务级模型的基础上分别进行软件和硬件的设计。在软件设计中,会把建立完成的虚拟平台和构架作为集成开发环境的一部分。集成开发环境还包括编译器和调试工具的开发。在设计过程中要通过软硬件的协同验证调整设计的内容。
图4-17 ESL结构设计流程
图4-18 ESL设计实现方法实例
4.7.4 ESL设计的特点
ESL设计之所以会受欢迎,主要源于以下3方面的特性:功能正确和时钟精确型的执行环境使提前开发软件成为可能,缩短了软硬件集成的时间;系统设计更早地与验证流程相结合,能确定工程开发产品的正确性;在抽象层设置的约束和参数可以被传递到各种用于设计实现的工具中。
1.更早地进行软件开发
有了虚拟的原型平台意味着可以更早地开始软件开发。对于目前基于SystemC语言的ESL设计方法学来说,ESL设计工程师可用SystemC生成一个用来仿真SoC行为的事务级模型。由于事务级模型比RTL模型的开发速度要快得多。在RTL实现以前,完成TLM建模后的系统就可以开始软件的开发。那么,软件的开发可以与RTL实现同时展开,而不是传统的在RTL实现完成以后才开始软件开发。虽然部分与硬件实现细节有关的软件要在RTL完成以后才能开始,但还是可以节省大量开发时间。对于一个大型软件开发任务,尽可能早地开始软件开发很有必要。这样不但节省了大量软件开发的时间,还使软硬件的集成和验证变得更加容易。如此独一无二的特性吸引了很多公司将其作为设计流程中特定的一环。
2.更高层次上的硬件设计
为了适应不断变化的市场要求,需要不断推出新产品或经过改进的产品。在SoC设计中可以通过改进一些模块的性能、增加功能模块或存储器、甚至在系统结构上做出重大的调整。因此,设计工程师必须拥有可实现的快速硬件设计方法。
为了实现快速的硬件设计,ESL设计须建立在较高的抽象层次之上,如事务级建模(TLM)。事务级建模可实现较早开始软件开发、ESL设计及验证任务的虚拟集成平台。
以前,RTL平台曾被用来减少设计修改的问题,它通过为未来设计提供一个经过预验证的系统结构来实现这一点。然而,为满足新的市场需求而优化RTL系统结构和集成RTL级的IP所带来的困难越来越大,这会显著减缓设计过程。而一个未经优化的系统结构可能对性能和功耗产生负面影响。最终,设计团队可能会因为性能目标和成本目标的冲突而被迫放弃。
事务级模型被应用于函数调用和数据包传输层。这是一个抽象层,设计意图在该层被捕获,而且该层给设计工程师提供了一个直接而清晰的系统行为视图。而在ESL设计中的事务模型更容易集成到SoC架构的事务模型中,使SoC结构设计师能快速研究并分析多个备选硬件架构和硬件/软件分割方案以确定最佳架构。这种方法明显加快了初始设计,但它的最大好处是在快速转变的设计中采用最初的SoC TLM作为易于更改的平台。
传输级模型可以分为事件触发型和时钟精确型。事件触发机制在硬件设计仿真中经常使用,它能维持大量并发性事件的顺序。然而,面对如此众多的执行事件,如果想要在更高的抽象层执行得更快,就必须将它们归结到相应的时钟周期或指令周期,这就是时钟精确型模型的原理。所以,将大量有严格顺序的事件抽象到每个时钟节拍内建模,进而搭建出的虚拟原型平台可以达到比事件触发型的仿真快10倍以上的仿真速度,而并不影响功能和性能的评估结果。
这些模型能够提供比RTL级模型快好几个数量级的仿真速度。在保证功能正确的前提下,只损失一些时序精度,所以对ESL工具的挑战就是既要保持足够精度的时序信息来帮助设计决策,又要提供足够的仿真速度以满足大型的系统软件(如OS启动)在可接受的时间内完整运行。只要掌握了这种平衡,就可以在高级设计中验证时序和设置约束条件,再将这些优化的设计分割、分配到各个不同的软硬件设计工作组去加以实现。RTL或带有时序信息的RTL仿真通常只能提供10 MIPS到数百MIPS左右的系统仿真性能,然而,时钟精确型的ESL仿真却能达到100 kMIPS到1 MMIPS的仿真速度。一般来说,ESL上的仿真只会同RTL仿真的时序有些细微差别,这足以满足在时钟级别上验证系统行为的目的。
3.设计的可配置性和自动生成
越来越多的系统强调自己的可配置性,如不同的处理器、不同的总线带宽、不同的存储器容量、无数的外设,所以在仿真中模拟各种不同的配置对设计者来说非常有价值。设计的风险在于需要综合验证环境,配置和生成出来的设计必须与验证环境得到的结果完全一致,并延续到整个设计流程中。而且系统互连已不再是固定不变的IP总线和桥,通常系统互连是由可配置的结构生成器产生的。配置这些部件的本质就是要在存储器和处理器间提供足够优化和高性能的带宽。通过ESL模型,结构设计师能够找到最好的配置方案,但是,这样产生出来的结果需要与一套骨架的验证环境同步到设计实现中去。
ESL设计领域有3种不同的层次,最高的层次是算法开发,在设计和实现层次是系统架构开发,第3种就是设计的自动生成过程。ESL设计更进一步的目标是能够自动生成IP。一旦系统设计经过各种权衡确定方案之后,各种可配置的模块也可以将参数最终确定下来,就可以利用工具将确定的方案自动装配产生出来了。例如,ARM已经实现了从RealView SoC Designer ESL环境中自动导入Synopsys DesignWare CoreAssembler SoC的集成和综合流程,并且可以从CoreAssembler或Mentor Graphics公司的Platform Express中启动ARM PL300 AXI可配置互连生成器,来生成AXI总线系统。
4.方便的结构设计
ESL结构设计能完成功能到运算引擎的映射,这里的引擎指的是那些可编程的目标,如处理器、可配置的DSP协处理器,或者特殊的硬件模块如UART外设、互连系统和存储器结构。这是系统设计的开始环节,从行为上划分系统,验证各种配置选择的可行性及优化程度。在这个领域中的工具很多,如CoWare公司的ConvergenSC、Synopsys公司的CoCencentric System Studio、ARM公司的RealView SoC Designer,这些工具都可用于开始时的结构设计。
ESL工具对于开发可配置结构体系是非常关键的,它使系统结构从抽象的行为级很容易地映射到具体的硬件设计,从而方便决定哪些模块可以被复用,哪些新模块需要设计。它还能提供必要信息指导最优化的通信、调度和仲裁机制。
为了在结构设计中方便复用、模拟IP模块,就必须对它们进行建模,比如用SystemC语言对处理器和总线这样主要的IP进行事务级建模。在事务级模型完成后,在RTL建模和软件开发的同时可以对体系结构进行验证和评估。系统体系结构的开发需要使用带有时间信息的事务级模型。周期精确的RTL模型对系统体系结构的分析能够提供精确的分析。但RTL模型的开发远比事务级模型的开发需要更多的时间,而且当模型需要做出改变以适应软硬件划分调整的时候,事务级模型更加灵活。虽然事务级模型的精确性比RTL模型要低,但实验表明包含时间信息的事务级模型能够提供足够的精确度。
5.快速测试和验证
由于ESL设计中的抽象级别明显高于RTL设计抽象级别,ESL设计中可以做到描述模块内的电路状态、精确到纳秒的转换及精确到位的总线行为。相比使用RTL,使用周期精确的事务级模型将使硬件验证和硬件/软件协同验证速度快1000倍或者更多。这种方法不仅可以产生用于验证系统的行为,还支持与较低抽象级别的RTL模型的协同仿真。如果这样的ESL设计抽象级别被当作一个测试台的话,当下游的RTL实现模块可用时,它们便可以在这个测试台上进行验证。
系统级的软硬件协同验证要优于C/RTL实现级的HW/SW软硬件协同验证。因为系统级的验证可以较早的展开,而不必等到底层的实现完成后才开始。在底层实现没有开始前的协同验证可以及时修改体系结构或软硬件划分中的不合理因素。越高层次上的验证,可以在越大程度上减少修改设计带来的损失。
ESL工具提供商现在已经能够提供完善的方法,将过去需要数周或数月才能完成的设计和验证工作,在几分钟之内就完成,ESL提供可以信赖的方法学,快速地完成数百万门电路和数千行嵌入式软件代码的设计和验证。
4.7.5 ESL设计的核心——事务级建模
1.事务级建模介绍
以上关于ESL的描述更多侧重于它在方法学上的抽象描述,实现ESL设计的核心是事务级建模(TLM,Transaction Level Modeling)。要实现ESL的设计流程,包括系统级描述、体系结构设计、软硬件划分、软硬件协同设计和验证,都离不开事务级建模。
在系统级的设计中,首先要解决的问题是如何描述系统也就是所谓系统建模。在当前的集成电路设计中,算法层次上建立的功能模型(ALF,Algorithm Function)没有时序的概念,而且它与体系结构及具体实现关系并不大,没有办法进行进一步的性能分析。而RTL层次上的模型则关注电路在寄存器、连线层次上的细节,所以模型的建立和仿真都需要很长的时间。如何在这两个抽象层次之间再引入一个抽象层次一直是众多设计师努力解决的问题,而这个引入的抽象层次就是事务级模型。
通过在算法抽象层(ALF)和RTL抽象层之间增加一事务层(TLM)(如图4-19所示),设计师可以更加有效地开展功能仿真。事务级建模可以为算法选择,软硬件划分,协同仿真的接口建模提供折中的评价方法。同时可以实现较早地开始软件开发,验证体系结构,减小产品的开发周期,提高设计成功率。可以说事务级建模是ESL设计方法学的核心。
图4-19 设计抽象层次
这里讲的事务是指模块之间的数据和事件的交互。数据交换可以是一个或多个字,或者是一种数据结构,而同步或者中断等则属于事件的交互。事务级建模的核心概念是在一个系统建模的过程中将运算功能和通信功能分开,模块之间的通信透过函数调用来实现。与寄存器传输级模型相比较,事务级建模可以减少事件和信息的处理,而且事务级模型所需的程序代码更少,执行速度更快,并且能够根据实际需要提供足够的设计精度。根据描述系统精度的不同,事务级模型可以分为3种,即没有时序信息的模型、周期近似的模型和精确到每个周期的模型。没有时序信息的事务级模型的建模和仿真速度最快,而精确到周期的事务级最慢。根据一些公司的经验,没有时序信息的事务级模型的仿真速度要比RTL模型快1000~10000倍,带有时序信息的模型比RTL快100~1000倍,而精确到周期的模型比RTL快10~100倍。
2.事务级建模的一般理论
在更详细地描述事务级建模之前,首先介绍在事务级建模中用到的一些术语。
首先事务表示数据和事件的交换过程。各个连续的事务可以是不同大小的数据传输,也可以是在系统同步时用来调整或管理模块之间行为的事件。这种事件对保证模块间的行为传递是至关重要的。例如,DMA传输结束后的中断信息就是一个系统同步行为。在电子系统中,每个元件由一组状态和并发的行为组成。在事务级建模中,电子系统中的元件可以建模为一个功能模块。一个电子系统可以由若干个事务级模块组成。元件的状态在事务级模型中用变量表示,而不同的行为在模型中用一组可以并发进行的进程表示。模块之间通过一种特殊的事务级通信结构实现相互通信,这种结构称为通道。根据仿真精度的不同,通道可以是简单的路由器、抽象总线模型或者是片上网络,也可以是其他结构。通道是事务级模型中将通信从运算单元独立出来所必需的一种抽象。模块和通道通过端口连接在一起。事务级模型中的接口包含通信协议,而这种通信协议的具体实现则在通道中完成。在系统中,主设备是系统中发起事务的模块,而从设备是接收或响应事务请求的模块。在建立通信过程中,主设备的进程通过模块的端口访问接口,接口区分事务级模型系统中的通信。
如图4-20所示,事务级模型(模块M、模块S和通道)构成一个简单的系统。模块M是系统中发起事务的主设备,而模块S是系统的从设备。模块M和模块S分别有两个独立的进程,描述不同的行为。模块M和模块S之间通过各自的端口绑定到通道提供的接口实现通信。可以看出事务级模型实现了运算功能和通信功能的分开,它们在不同的事务级模型中完成。接口A和接口B分别是通信协议A和通信协议B的接口,它们是一组函数的集合,通过端口提供给进程调用,而接口函数集合中的函数体即通信协议的具体实现在通道模型中完成。在图4-20中通道实现了两种通信协议,通信协议A和通信协议B。而模块M和模块S通过模块的端口绑定到通道后,以通信协议A规定的方式进行通信。
图4-20 事务级模型构成的一个简单的系统
具体而言,在基于SystemC或C++的事务级模型中,接口通常被表示为C++抽象类。该抽象类定义了一组抽象的方法,但不定义这些方法的具体实现,即纯虚函数。通道则继承一个或多个接口,实现在接口中定义的所有虚函数。模块中的进程可以通过端口使用时用通道提供的方法。端口总是与一定的接口类型相关联,端口只能连接到实现了该类接口的通道上。关于在SystemC中接口、通道、端口的具体语法请参照4.6.7节所述。
前面提到事务级模型可以分为3种,即没有时序信息的模型、周期近似的模型和精确到每个周期的模型。没有时间信息的计算或通信模型表示系统设计的功能描述,这些模型没有具体的实现细节。周期近似的计算或通信模型则包含系统级的实现细节如系统体系结构的选择、系统定义的功能和体系结构中模块的映射关系等。周期近似的运算或通信模型中的执行时间通常在系统级通过估计得到,因为在这时还没有周期精确的RTL级或者指令级的仿真平台。而周期精确的运算和通信模型包含了系统级的实现细节如RTL级或指令级描述,因此,可以得到周期精确的仿真结果。
根据系统模型中运算和通信所包含时序信息的不同,系统建模可以通过如图4-21所示的Cajski & Cai等用过的系统建模图的例子来说明。
在图4-21中,横坐标表示运算目标,纵坐标表示通信目标。横坐标和纵坐标都有3个时间精度,它们分别是无时序信息、周期近似和周期精确。在图4-21中,定义了6种抽象模型,A称为算法模型,B称为周期近似的元件组装模型,C称为周期近似的总线仲裁模型,D称为总线功能模型,E称为周期精确的运算模型,F称为硬件实现模型。其中,元件组装模型、总线仲裁模型、总线功能模型、运算模型这4个模型属于事务级模型。从A到F可以有不同的路径,也就是说,对一个设计不是上述所有6种抽象模型都需要建立,设计者应根据具体情况建立所需要的抽象模型。
算法模型描述系统的功能与具体的实现无关。算法模型不用通道的概念而是通过变量访问的形式建模数据在进程间的传输。算法模型是没有时间信息的模型。
如图4-22所示,在元件组装模型中,并发进行的进程单元通过通道进行通信。所谓进程单元是指剥离了通信机制的定制硬件、通用处理器、DSP或其他IP的事务级模型的模块。通道是消息的输出通路,表示进程单元之间的数据传输和进程同步。在元件组装模型中,通道没有时间信息,也没有总线或协议的具体实现。进程单元的运算部分是周期近似的,通过估计特定的进程单元的执行时间得到。与算法模型相比较,元件组装模型明确定义了进程在系统结构中的位置,并规定了进程到进程单元的映射。
图4-21 Cajski及Cai的系统建模图
图4-22 由元件组装模型构成的系统
如图4-23所示,在总线仲裁模型中,进程单元间的通道表示总线,包含了总线或者协议的实现,称为抽象的总线通道。通道同样通过消息的传送实现数据传输。在总线仲裁模型中,总线协议没有实现周期精确或者引脚精确,抽象的总线通道只是包含近似的时间信息,这个时间信息通常在每个事务中的等待声明中给出。在一些情况下,几个总线通道抽象成一个总线通道,需要在总线通道的接口函数中增加逻辑地址和总线优先级参数,其中,逻辑地址区分不同的进程单元或进程调用接口函数,而总线优先级规定了总线冲突发生时总线的访问顺序,而且总线仲裁器作为一个新的进程单元增加到系统的体系结构中去,由它来处理总线冲突。主进程单元、从进程单元和总线仲裁器调用同一抽象总线通道中不同接口的函数。
图4-23 由总线仲裁模型构成的系统
如图4-24所示,总线功能模型包括时间精确或周期精确的通信及周期近似的运算。有两种总线功能模型,一种是时间精确,另一种是周期精确。时间精确的总线功能模型规定通信的时间约束,这种约束由模块之间通信协议的时序图决定,而周期精确模型以主进程单元时钟周期的方式给出时间约束。在总线功能模型中,消息传送通道被协议通道取代。在一个协议通道中,总线的互连被例化成相应的变量和信号,实现周期精确的通信。
图4-24 总线功能模型
周期精确的运算模型包括周期精确的运算模型和周期近期的通信,这个模型可以从总线仲裁模型得到。在这个模型中,运算单元是引脚精确和周期精确的。定制硬件电路可以在RTL级上建模周期精确的运算模型,而通用处理器或者DSP则一般在周期精确的指令集结构上建模周期精确的运算模型。为了使周期精确的进程单元和抽象总线通道的抽象接口进行通信,需要包裹一个转换层,实现高层抽象到低层抽象的数据传输,即实现进程单元和总线接口的通信。在周期精确的运算模型中没有必要使所有的运算都做到周期精确。
实现模型既是周期精确的运算,也是周期精确的通行,就是通常所指的RTL模型或者指令集模型。这个模型可以从总线功能模型或者周期精确的运算模型得到。
3.事务级建模标准
需要特别指出的是,在系统级设计中事务级建模过程可能存在差异,这是设计中对于建模的效率和建模的准确性折中的结果。如图4-24所示,从算法模型到RTL模型的实现过程中有不同的实现路径,也就是说并不是每种TLM模型都必须包括在设计过程中。系统设计师可以根据实际情况构建合适的抽象模型。因此,一个标准的建模方案可能更加有利于事务级建模方法学在SoC设计中的应用。而近年来,OCP-IP和OSCI一直在推动事务级建模的标准化工作,这两个组织也分别提出了各自的TLM抽象模型。
(1)OCP-IP TLM抽象模型
OCP-IP(Open Core Protocol International Partnership)是推动IP核接口标准化的国际组织。OCP-IP提出了一个4层的通信模型,如图4-25所示,除RTL层之外的3层属于事务层。
图4-25 OCP-IP的通信抽象层次
消息层处于最高抽象层次,被SoC系统结构工程师用来做功能划分和系统级的结构分析。消息层没有时间信息,是事件驱动的。主设备和从设备之间的一次事务包含了多个数据的传输,这些数据可能处于非常抽象的层次。
事务层在RTL层之上,被SoC系统结构工程师用来进行体系结构分析、周期性能估计,以及详细的硬件性能分析、软硬件划分和软硬件协同开发。事务模型有时间信息,但不是精确到周期,系统是时间驱动执行的。主设备和从设备之间的一个事务可能包含几个数据的传输。执行这个事务所需要的时间由主设备和从设备来估计。通常的事务级系统模型与总线协议无关,因为总线协议总是与精确到时钟周期的系统有关。事务模型和硬件仿真模型一起用来做为下层接口的驱动。
传输层模型用于详细建模任务,建立精确到周期的测试平台,精确到周期的性能仿真。传输层是周期精确的,但接口的引脚被抽象隐藏,接口是字节精确的。表4-2所示为传输层和RTL层的比较。将精确到周期的协议映射到给定的硬件接口和总线结构上。寄存器传输层模型是引脚和比特精确。RTL模型可以使用VHDL或Verilog或SystemC描述。
(2)OSCI TLM 1.0抽象模型
OSCI(The Open SystemC Initiative)是由集成电路行业的公司和一些大学组成的非营利组织,致力于推动SystemC成为系统级设计的开源标准。OSCI提出的事务级抽象层次如图4-26所示。
表4-2 OCP-IP的通信抽象层次中传输层和RTL层的比较
图4-26 OSCI提出的事务级抽象层次
图4-26中,在算法模型和RTL模型之间的三层抽象属于事务级抽象。下面分别介绍这三层抽象。
① 程序员观点层(Programmer’s View):该层模型包含的接口只有函数调用而没有通信事件。这一层只带有很少的时序信息,通常与没有时间信息的功能行为描述相关。
② 带有时间信息的程序员观点层(PVT,Programmer’s View plus Timing):这是一个周期近似的事务级模型,用于建模功能行为和模块之间的通信协议。可以分析通信的时延和吞吐量,并可在软件开发和体系结构的验证中提供足够的仿真精度。
③ 周期精确层:这是一个周期精确的模型,模块的内部行为以及模块和外部的通信可以是周期总数精确或者周期完全精确的。模块内部的行为建模可以不对寄存器建模以提高仿真速度。这是一个事务级模型,在RTL抽象层之上。在通信建模中同一时钟边沿触发的多个信号可以包装在一起作为一个传输,因此仿真性能要比RTL抽象好。
2005年6月,OSCI公布了事务级建模标准1.0,把芯片设计带到比RTL更高的抽象水准。SystemC事务级建模标准1.0定义了应用编程接口(API)和一个用于建置基础层的库,设计师可以在该基础层上制作具有互操作性的事务级模型。而事务级建模标准1.0对所处理的内容没做规定,而是把这一任务留给了TLM2.0标准。
OSCI事务级模型工作小组定义了一组普通、可复用的事务级模型接口,以及目标通过性语法。API包括一个用户层、一个协议层和一个传输层。值得注意的是,该标准只提供了一个基础层。IP供货商将需要提供符合特定总线标准的API及相应IP的事务级模型。因此,IP供货商就制作了所谓的“第二适配层”,在此之上用户可以建构系统和半导体IP。该标准将促进IP的共享和复用,鼓励EDA工具的开发,并使电子OEM厂商更容易使用事务级模型。OSCI发布的新标准可以使事务级建模从高阶用户走向主流用户群,该标准将为IP供货商进入事务级模型市场铺路,而且这些模型都可以被整合进系统级芯片(SoC)和EDA工具中。这在以前是不可能实现的,因为每个公司都使用自己制作的事务级模型。利用新的OSCI事务级模型库编写出来的模型可以透过标准接口而互相连接,这将进一步增强高层IP模型的复用性和可用性,如图4-27所示。OSCI和OCP-IP提出的模型都支持三层的TLM抽象。OSCI的带有时间信息的程序员抽象层与OCP-IP的第二层事务层接近,而周期精确层则与OCP-IP的第一层传输层抽象类似。
图4-27 OSCI定义的基于SystemC的层次标准(资料来源:OCSI)
基于SystemC的事务级建模的主要任务是利用SystemC进行相应的通信抽象,实现通信机制,其具有两大特点。
① 功能与通信分离:实现具体算法的功能部分与实现数据和事件传输的部分分离。功能由SystemC的模块(sc_module)来实现,而通信由通道(Channel)来实现。
② 接口方法调用:一组给定的通信方法被称为接口,包括数据接口和控制接口。而通道由一个或者多个接口来实现。模块能够使用它们的端口来与实现相应接口的通道进行互连。
(3)OSCI TLM 2.0抽象模型
当前,OSCI TLM 1.0抽象模型在工业界的SoC电子系统级设计中已得到广泛应用,如早期的软硬件集成、系统性能分析、结构设计、功能验证等。但随着抽象层次的提高,采用TLM 1.0标准进行建模的事务级模型缺乏实用性,并且仿真速度无法达到要求。另外,基于TLM 1.0的事务级模型间的互操作性和兼容性差,不同厂商提供的事务级模型由于接口等问题加大了系统集成的难度。为了解决上述问题,OSCI于2008年6月和2009年7月分别推出了TLM 2.0和TLM 2.0.1事务级模型标准。其目标是规范化TLM事务级模型建模标准,提出统一的API及数据结构,提高不同用户编写的TLM模型之间的互操作性。TLM 2.0标准的体系结构如图4-28所示。
图4-28 TLM 2.0标准的体系结构
TLM 2.0支持在发起者(主设备)和接收者(从设备)之间传递事务。主设备能够发起事务,即创建一个新的事务对象,并通过调用TLM 2.0核心接口定义的方法进行传递。主设备与从设备之间可以直接相连,也可以通过互连单元(总线、片上网络等)进行相连。互连单元是一个模块,它可以访问事务,但不是事务的发起者或接收者,而只是为发起者和接收者之间提供互连。事务对象所经由的互连单元、事务对象的发起者和接收者之间构成了事务对象所经过的路径。如图4-29所示,前向路径是主设备到从设备,或者互连单元的链路,或者链路级联。事务通过主设备调用的方法直接返回,或者从设备通过另外的方法调用返回事务称为反向路径。为了支持前向、反向路径,模块间的每一个链接都需要一个端口称为主设备套接字或从设备套接字。
图4-29 TLM 2.0通信中的前向和反向路径
为了增强互操作性,TLM 2.0中发起事务的主设备和接收事务并作出响应的从设备必须遵循一定的接口标准,称为事务处理核心接口,包括阻塞传输接口、非阻塞传输接口、直接存储接口(DMI,Memory Interface)和调试传输接口(Debug Transport Interface)。其中,阻塞和非阻塞传输接口是主设备、从设备之间进行事务处理的基本接口。两者均支持时序标注(Timing annotation)和时间解耦(Temporal decoupling)。但阻塞传输接口没有阶段(Phase)参数,事务仅有开始和结束两个定时点,一个调用请求发出后,直到请求被处理完成该调用才返回。而非阻塞传输接口在事务的整个生命周期内支持多阶段,即允许模型描述事务处理过程中的多个阶段细节(包括TLM 2.0已经与定义的阶段UNINITIALIZED_PHASE、BEGIN_REQ、END_REQ、BEGIN_RESP、END_RESP和用户可扩展阶段DECLARE_EXTENDED_PHASE),每一个阶段都有一个明确的定时点,调用请求可以不必等待事务处理完成即可返回。直接存储接口(DMI)允许主设备使用指针直接访问从设备所在的存储区域,而不需要使用如前所述的阻塞或非阻塞传输接口进行访问。DMI避免了对阻塞和非阻塞传输接口函数的多次调用,能够显著提高模型仿真速度。调试传输接口为主设备提供了一个对从设备进行读写的方法,可以使主设备在仿真过程中对存储器、寄存器的内容进行扫描,可用于实现指令集仿真器ISS的单步、断点等调试功能。
总线是当前电子系统不同模块间进行交互的基本通道接口,对于总线的事务级建模至关重要。通常,事务级建模采用C++的类对事务进行描述,然而对于不同总线,总线读写事务类定义不同。对于同一总线,不同设计者所编写的总线事务类也可能不同。这就导致了不同的TLM模型之间互操作性差,不利于系统集成。TLM 2.0标准针对总线建模提出了通用Payload类,用于提高总线模型的互操作性。通用Payload类提供了定义总线模型所需的基本属性(如地址、数据、字节使能、单字传输、突发传输、响应等),可方便、快捷地对当前已有的多数总线进行建模。同时,通用Payload还提供了扩展机制,以满足某些总线标准的特定需求。
为了进一步增强不同事务级模型之间的互操作性,TLM 2.0标准对采用SystemC/C++进行高抽象层次建模的编码风格进行了规范,提出了松散时序(Loosely-timed)和近似时序(Approximately-timed)的编码风格(注意:编码风格只是规定了编程语言的规范形式,并不是指某一抽象层次或API)。松散时序的编码风格主要使用阻塞传送接口,使用此种编码风格的模型对应了两个时间点,即事务处理的开始点和结束点。松散时序的编码风格支持如上提到的“时间解耦”,即模型的某部分功能可以在当前仿真时间之前运行,直到一个需要与其他部分进行同步的同步点。时间解耦能够有效地提高仿真速度,但降低了仿真精度。松散时序编码风格非常适用于构建对硬件细节要求不多的嵌入式软件验证虚拟平台,如嵌入式操作系统等。近似时序编码风格主要通过非阻塞传送接口进行支持,主要用于体系结构的设计空间探测和性能分析。在非阻塞传输接口中,一个事务往往被精确地划分为多个执行阶段,由不同的定时点进行分割,这些定时点包括请求的开始与结束、应答的开始与结束。为了提高仿真的精度,近似时序编码风格一般不使用时间解耦。在实际应用中,松散时序编码风格和近似时序编码风格可以根据需要同时使用。此外,TLM 2.0标准不提供对于无时序(Untimed)编码风格的支持,但无时序模型可以由TLM 1.0标准提供支持(注意:无时序模型并不是指没有任何时序信息,而是仅包含有限时序信息的模型,在TLM 2.0标准中通常被归为松散时序的编码风格)。同样,TLM 2.0也不提供对于周期精确(Cycle-accurate)编码风格的支持,设计者可借助SystemC和TLM 1.0完成周期精确模型的建模。但OSCI将在未来的工作中对近似时序编码风格进行扩展提出规范化的周期精确的编码风格。各种编码风格的应用场合如表4-3所示。
表4-3 OSCI TLM标准不同编码风格的应用
与TLM 1.0相比,TLM 2.0的改进如下。
① TLM 1.0没有标准的事务处理类,每一个应用根据需要创建专门的事务处理类,因此不同的模型之间的互操作性很差。TLM 2.0中针对这一问题,增加了通用Payload类。
② TLM 1.0不支持时序标注,因此不同的模型之间缺乏标准的交换时序信息的方法。TLM 1.0中一般通过调用wait函数实现延迟,仿真速度较慢。TLM 2.0中通过在阻塞和非阻塞传输接口中增加了时序标注,提高了仿真速度。
③ TLM 1.0要求通过值或常量引用来交换所有的事务处理对象和数据,影响了仿真速度。TLM 2.0针对这一问题,在传输接口中允许扩展事务处理对象的生命周期到不同的传输调用中。
4.7.6 事务级建模语言简介及设计实例
任何系统级建模语言,都需要具备在较高层次的抽象能力和对不同来源的IP的集成能力。建模方法的选择通常基于语言熟悉程度、建模支持、模型可用性和简单性。
在各种软硬件描述语言中,Verilog和VHDL是RTL级建模的最佳语言,有足够的精度如比特精确和周期精确,但缺乏高层次抽象的能力,而且对软件部分的描述无能为力。而C/C++、Java等都是软件的优秀描述语言,也具有高层次的抽象能力,一些设计师就是用它们来进行系统建模的。但他们没有精确到比特的能力,也没有并发描述能力和时钟的概念。
通过对上述语言进行扩展可以提高描述系统的能力。在这些语言中,有些是在C语言基础上进行扩展的HardwareC和SpecC,它们分别由斯坦福大学和加州大学研究小组开发。还有在Java上扩展的语言如JHDL,在Verilog上扩展的SystemVerilog,以及在C++上扩展的SystemC。这些语言通过增加系统描述的关键词,可以实现硬件和软件的描述,具有描述系统的能力。业界获得较多支持和应用较为广泛的语言是SystemVerilog和SystemC。
SystemVerilog是在Verilog上的扩展,吸收了C/C++语言中的一些变量,可以实现和C语言一起仿真。由于是在Verilog上的扩展,SystemVerilog的优势在于基于时钟的建模能力和验证能力,但在TLM的抽象上还存在一些不足,如缺少抽象的数据类型等。
更为成功的系统建模语言无疑是SystemC。SystemC是OSCI推出的基于C++语言扩展的描述语言,是一种可以完成电子系统从软件到硬件的全部建模过程的语言。在目前的设计中,软件开发占到设计任务的60%~90%,基于C++语言扩展的语言SystemC比从HDL扩展的语言有更加有利的发展趋势。SystemC完全支持TLM建模,而且OSCI已经提出了TLM建模的标准,而其他语言目前还没有完全支持TLM建模。
1.SystemC简介
SytemC最初的开发是在Synopsys、Coware和UC Irvine等公司和研究机构中进行的,在1999年SystemC被正式推出。SytemC由Open SystemC Initiative (OSCI) 负责支持、维护和发展。OSCI是一个旨在以促进普及和开发SystemC为目的的非营利团体。这个组织包含了范围广泛的公司、研究机构、大学和个人成员。这些公司包括ARM、ST、Cadence、CoWare、Mentor Graphics、SYNOPSYS等涵盖了系统厂商、半导体厂商、EDA工具厂商、IP供应商等。可以说SystemC在微电子行业有着比较广泛的支持。目前,许多EDA厂商都在开发SystemC的工具或者通过在原来的设计工具中增加支持SystemC的功能。现在已经推出的商用的SystemC软件包括Synopsys的CCSS及Cadence的NC-SystemC和SPW等。一些著名的IC公司,如ST和ARM等开始使用SystemC作为其系统级的开发语言。
SystemC的版本一直在更新,不断增强其事务级建模能力。2003年4月发展到SystemC 2.1版本。SystemC 2.1将硬件和软件建模功能拓宽至更高的抽象级层次上。在此基础上,2005年12月IEEE批准了IEEE 1666(tm)—2005作为SystemC的国际标准。这是SystemC发展中的一个里程碑,必将极大地推动SystemC语言的发展和应用。IEEE 1666(tm) SystemC语言参考手册是SystemC语言的权威性描述。为了鼓励使用这个标准,OSCI通过与IEEE标准组织协商,该标准的文档目前可以免费下载。
SystemC是一种基于C++的系统设计语言。在SystemC以前,C和C++是芯片结构设计的主要语言。而在SoC设计中需要集成各种IP和嵌入式软件,C++具有面向对象的设计观念,很适合作为SoC系统的开发语言。SystemC在C++语言的基础上增加了信号、事件等概念用来描述硬件,而且还可以支持定时、并发等概念。SystemC可以让系统、硬件和软件工程师使用相同的语言完成整个电子系统的全部建模过程,这样做能够为软硬件划分和缩短产品的上市时间带来帮助。SystemC通过定义新的C++类库和仿真核,用以支持硬件的建模和仿真。
下面介绍SystemC中的基本概念。在本节中主要介绍SystemC中作为C++扩展的内容如信号、事件、定时和并发等概念。详细的SystemC语言介绍需要参考其他教材或者是SystemC的用户手册。
(1)SystemC中的模块
模块(SC_MODULE)是SystemC系统建模的一个基本单位。一个系统由许多个模块构成,各个模块实现系统中不同的功能。在设计中,设计者需要根据功能把复杂的系统划分成若干个更小、更易于管理的模块。模块内部包含若干个进程(Process)来描述模块的功能。
一个模块可以包含端口、内部信号、内部数据、子模块、进程、构造函数和析构函数等,这些元素实现了模块要求的功能。SC_MODULE是SystemC库中定义的一个宏,使用它定义一个模块实际上是定义了一个新对象。下面是一个半加器的例子:
//File:myand.h #include <systemc.h> //该文件中存有SystemC类库的定义,所有SystemC模块都 //必须包含这个文件 SC_MODULE (myand) //这个模块的名字为myand { sc_in<bool> a,b; //输入端口 sc_out<bool> c; //输出端口 void proc_and ( ); //声明进程proc_and SC_CTOR (myand) { //构造函数 SC_METHOD (proc_and); //proc_and是一个SC_METHOD型进程 sensitive << a << b ; //敏感变量 } } //myand.cpp //这个文件存放进程的定义 #include "myand.h″ void myand::proc_and( ) { c=a.read( ) & b.read( ); }
模块与模块之间通过通道实现通信,而进程之间则利用通道或事件互相通信。这是最基本的系统建模概念。模块、端口、接口和通道这些概念在4.7.6节事务级建模中已经有过介绍。信号是SystemC中新增加的一个概念,模块之间的通信需要通过端口连接到通道。信号(以sc_signal为例)实现模块之间端口的连接。信号也是一种通道。信号与Verilog中的wire类似,可以实现模块之间的互连,这是一种硬件方式的互连,因此是可综合的设计。信号一般通过read( )和write( )函数来读/写,由于信号总是连接到端口上,如sc_in、sc_out及sc_inout 等,也可以通过端口读/写信号。其中,sc_in端口支持read( )、event( )、posedge( )及negedge( )4种方法,而sc_out和sc_inout还支持write( )方法。
(2)SystemC中的进程
进程是SystemC中基本的运行单元,调用进程可以仿真目标设备或系统的行为。进程是一个独立的处理单元,可以实现SystemC中并发的行为动作。并发是SystemC在C++基础上新增加的概念。对于每个独立的进程来说,只有在它的敏感信号或敏感事件发生时进程才会被调用,而在其他情况下处于保持状态。当多个独立的进程有同一个敏感变量时,这个敏感变量可以触发这些进程的调用,实现并发的行为。SystemC的进程有3种:SC_METHOD、SC_THREAD、C_CTHREAD。
方法进程(SC_METHOD)是目前唯一可综合的寄存器传输级进程,它可以描述同步或非同步系统。方法进程由sensitive( )、sensitive_pos( )、sensitive_neg( )等函数中的敏感信号触发。当敏感列表上有事件发生时,方法进程就会被调用。方法进程调用后需要立即返回,因此在该进程中不允许使用wait( )之类的语句。方法进程由于面向可综合的设计,而且方法进程的调用特性使它更倾向于在较低的抽象层次上使用,如在寄存器传输层次上。
线程进程(SC_THREAD)在执行过程中能够被挂起和重新激活,可以使用wait( )语句执行挂起动作,当敏感事件发生时被重新激活执行。由于线程进程的这个特点,它更加适合于较高抽象层次上的行为和系统的描述。线程进程的另外一个用途就是可以用来描述验证平台(testbench)。
钟控线程进程(SC_CTHREAD)是一种继承于线程进程的特殊进程,可以认为是线程进程和方法进程在描述行为上的一个折中。它只能在时钟的上升沿或下降沿被触发,这种行为其实更接近于实际硬件的行为。
(3)SystemC中的事件
在SystemC中,事件提供了一个底层的处理程序间同步及重新启动的方式,它能用来实现通道的功能,定义事件的语法如下:
sc_event event_name;
另外,要触发或引起一个事件,要使用通知函数notify( ),语法如下:
event_name.notify(参数); 或 notify(参数,event_name);
(4)SystemC中的数据类型
作为C++语言的扩展,SystemC支持所有C++的数据类型,包括long、int、short、char、unsigned long、unsigned int、unsigned short、unsigned char、float、double、long double和bool。另外,也能根据需求自行定义数据类型。此外,SystemC还新增描述硬件的数据类型,这些类型都以sc_开头,这些数据类型包括sc_bit、sc_logic、sc_int、sc_uint、sc_bigint、sc_biuint、sc_bv、sc_lv、sc_fixed、sc_ufixed、sc_fix、sc_ufix。各个数据类型的说明如表4-4所示。
表4-4 各个数据类型的说明
(5)SystemC中的定时
为了能够实现描述电路的行为,SystemC增加了对时钟的支持。在SystemC的库中,定义了两个特殊的对象:sc_time类和sc_clock类。这两个对象在SystemC库中定义的时候分别都有不同的构造函数,因此用户可以根据不同的需要定义自己的时钟模型。在SystemC中,支持的时间单位包括SC_FS、SC_PS、SC_NS、SC_US、SC_MS和SC_SEC,它们分别表示不同的时间精度。下面是定义一个时钟的例子,用来简单说明SystemC中定时的概念。
如果要产生一个周期为10ns的时钟信号,可以这样实现:
sc_time time(10,SC_NS); sc_clock clk("clk",time);
也可以这样定义一个完整的时钟(包含占空比、开始时间、初始逻辑等参数):
sc_clock clk("clk",10,SC_NS,0.5,5,SC_NS,false);
这个时钟周期为10ns,占空比(高电平持续时间与时钟周期的比)为0.5(0.5∶1),时钟初始第一逻辑值的保持时间是5ns,第一个逻辑值是高电平(false)。
另一个和时间概念相关的是wait( )函数。wait( )函数可以用来描述系统的等待时间。例如,在一个进程中wait(10,SC_NS)表示进程在等待10ns后被激活。
(6)SystemC TLM中的接口
在SystemC TLM中,接口是一个C++的抽象类。抽象类中的所有方法都是用“=0”标识表示的纯虚函数。C++不允许创建抽象类的对象,因为抽象类对象是没有意义的。在SystemC中,sc_interface是所有接口的基类,任何一个接口必须直接或间接继承sc_interface。此外,接口不包含任何数据成员。下面是一个接口实例,首先定义一个存储器读接口mem_read_if,然后定义一个存储器写接口mem_write_if,接着定义一个存储器的复位接口reset_if,最后利用这3个接口定义随机存取存储器的接口ram_if:
# ifndef _MEM_IF_H # define _MEM_IF_H # include “systemc.h” enum transfer_status {TRANSFER_OK=0, TRANSFER_ERROR}; template <class T> class mem_read_if : public sc_interface { public: virtual transfer_status read (unsigned int address, T& data)=0; // 定义存储器读的方法 }; template <class T> class mem_write_if : public sc_interface { public: virtual transfer_status write (unsigned int address, T& data)=0; // 定义存储器写的方法 }; class reset_if : public sc_interface { public: virtual bool reset ( )=0; // 定义存储器复位的方法 }; template <class T> class ram_if : public mem_write_if <T>, mem_read_if <T>, reset_if { public: virtual unsigned int start_address ( ) const=0; //定义获取存储器首地址的方法 virtual unsigned int end_address ( ) const=0; //定义获取存储器终止地址的方法 }; #endif
从上面的存储器接口的定义可以看出,接口是可以分层的,复杂的接口可以由多个简单的接口继承而得到。
(7)SystemC TLM中的通道
从接口的定义可以看出,在SystemC中的接口仅仅定义了一组通信方法,并不包含这些方法的具体实现。为了实现这些方法,使建模的模块具有实用的功能,SystemC中针对事务级建模引入了通道的概念。通道是接口方法的具体实现,通过继承一个或多个接口实现模型的具体功能。针对上述存储器接口ram_if,其通道的定义如下:
#ifndef _RAM_H #define _RAM_H #include "systemc.h" #include "mem_if.h" template <class T> class ram : public sc_module, ram <T> { public: ram (sc_module_name name, unsigned int start_address, unsigned int end_address) : sc_module (name) , m_start_address (start_address) , m_end_address (end_address){ sc_assert (end_address >=start_address); mem=new T[end_address -start_address]; } //构造函数 ~ ram ( ) { if (mem) {delete mem; mem=0} } //析构函数 transfer_status read (unsigned address, T& data) { if (address < m_start_address || address > m_end_address) { data=0; return TRANSFER_ERROR; } data=mem [address -start_address]; return TRANSFER_OK; } transfer_status write (unsigned address, T& data) { if (address < m_start_address || address > m_end_address) { return TRANSFER_ERROR; } mem [address -m_start_address]=data; return TRANSFER_OK; } bool reset( ) {...} inline unsigned int start_address ( ) const {...} inline unsigned int end_address ( ) const {...} private: T* mem; unsigned int m_start_address, m_end_address }; #endif
(8)SystemC TLM中的端口
在SystemC中,端口与特定的通道接口相连。进程通过特定的端口调用通道的接口提供的方法。对于(1)中提出的基本端口类型sc_in、sc_out以及sc_inout,可以调用的接口方法仅有write ( )和read ( ),但对于事务级建模,这些端口已经不能够满足需求。比如,当端口与总线接口或存储器接口相连时,需要同时提供地址和数据,事务级模型需要执行与数据读写无关的一些操作如复位操作等。
在SystemC中,一个端口可以同时连接到一个或多个实现了同一接口的通道之上。端口的定义如下:
sc_port <Interface Type, ChannelNumber=1>
Interface Type是端口所要连接的通道的接口类型。ChannelNumber代表端口所要连接的最大通道数,默认值是1。对于上面定义的的接口ram_if,下面的端口定义都是合法的:
sc_port <ram_if> ram_port1; //连接到一个RAM上 sc_port <ram_if, N> ram_portN; //连接到N个RAM上
根据上述存储器接口ram_if及相应通道的定义,可以定义一个模块Component通过端口对其进行访问,如下:
SC_MODULE (Component) { sc_in_clk clk; sc_port <ram_if <int> > ram_port; // 实例化端口 void action ( ); int data; unsigned int address; SC_CTOR ( ) { SC_METHOD (action, clk.pos( )); } }; void Component :: action ( ){ wait ( ); int i=0; while (i++< 100) { address=0; if (transfer_status status=ram_port -> write (address, data)) { // 通过端口ram_port调用存储器写方法 cout << "Write RAM successfully" << endl; } else cout << "Write RAM fail" << endl; if (transfer_status status=ram_port -> read (address, data)) { // 通过端口ram_port调用存储器读方法 cout << "Read RAM successfully" << endl; } else cout << "Read RAM fail" <<endl; wait( ); address++; } sc_stop( ); }
2.TLM设计实例——简易DMA设计
在一个SoC芯片中,一般由处理器、DMA控制器、中断控制器、存储器、各种接口(如UART、USB等)组成。DMA往往是总线的主设备,它可以发起存储器之间的数据传输。同时DMA也是总线的从设备,DMA中有若干个控制寄存器,当配置这些寄存器的时候DMA作为总线的从设备。因此,DMA与总线之间有两种接口,一种是主设备接口master_if,另一种则是从设备接口slave_if。作为TLM设计实例的简易DMA(以下简称DMA)中有5个寄存器——DMA使能寄存器用来使能DMA模块,突发传输(burst)的传输长度控制器控制每个突发传输的长度,传输控制器控制总的传输长度,源地址寄存器和目的地址寄存器分别设置数据传输的源地址和目的地址。在DMA中以突发传输的方式先从源地址读取数据并存储到DMA内部的缓存中,然后将数据写到目的地址中去。
在下面的内容中将给出两种DMA事务级建模的方法。在第1种模型中没有时钟,而在第2种模型是周期精确的。在这两种事务级模型中,都有3种端口分别是初始化端口、总线主设备接口和总线从设备接口。
(1)实例一:没有时钟的DMA模型
在没有时钟的事务级模型中有3个进程分别是main_action进程、dma_fsm进程和dma_rst进程。其中,main_action进程和dma_fsm进程定义为SC_THREAD类型,在SC_THREAD进程中没有时钟。在进程之间选择信号通道进行通信,因此定义了两个信号用于进程间的通信。信号是通道的一种。
DMA.h文件如下:
#ifndef _dma_h #define _dma_h #include <systemc.h> #include "slave_if.h" #include "master_if.h" enum state_type{idle, read_state, write_state}; SC_MODULE(dma) { //端口public: sc_in<bool> rst; //初始化信号 sc_port<master_if> master_port; //总线主设备接口 sc_port<slave_if> slave_port; //总线从设备接口 //signal sc_signal<unsigned int> my_state; //用来做进程间通信 sc_signal<unsigned int> dma_enable; //用来做进程间通信 SC_HAS_PROCESS(dma); //macro definition //DMA构造函数,指定模块名称和模块所属的地址空间 dma(sc_module_name name_, unsigned int start_address, unsigned int end_address) : sc_module(name_) ,reg_start_address(start_address) ,reg_end_address(end_address) { // process declaration SC_THREAD(main_action); sensitive << my_state; SC_THREAD(dma_fsm); sensitive << my_state << dma_enable; SC_METHOD(dma_rst); sensitive_neg << rst; } // process void main_action(); void dma_fsm(); void dma_rst(); //从设备接口函数 bool slave_read(int *data, unsigned int address); bool slave_write(int *data, unsigned int address); bool slave_select(unsigned address); private: unsigned int dma_en; //dma_en寄存器; unsigned int transfer_size; //transfer size寄存器; unsigned int burst_length; //burst length寄存器; unsigned int source_address; //source address寄存器; unsigned int destin_address; //destination address寄存器; state_type state; //DMA状态:初始化状态、读状态、写状态 int dma_buffer[16]; //DMA 缓存 int * buffer; //DMA 缓存指针 unsigned int reg_start_address; //DMA寄存器地址空间首地址 unsigned int reg_end_address; //DMA寄存器地址空间末地址 }; #endif
在SystemC中,接口是一种抽象的类。接口类中只定义一些纯虚函数,这些函数在不同的模块或通道中重载实现。DMA总线主设备接口中定义了两个读/写函数master_read和master_write。master_if.h文件如下:
ifndef __master_if_h #define __master_if_h #include <systemc.h> class master_if : public virtual sc_interface { public: //master interface virtual bool master_read(int *data, unsigned int address )=0; virtual bool master_write(int *data, unsigned int address)=0; }; // end class master_if #endif
DMA总线从设备接口除了定义读/写函数外,还定义了一个slave_select函数。该函数用来作为当从设备被选中时对总线的一个回应。slave_if.h文件如下:
#ifndef _slave_if_h #define _slave_if_h #include <systemc.h> class slave_if : public virtual sc_interface { public: // Slave interface virtual bool slave_read(int *data, unsigned int address)=0; virtual bool slave_write(int *data, unsigned int address)=0; virtual bool slave_select(unsigned int address)=0; }; // end class slave_if #endif
在dma.cpp文件中实现了从设备接口中定义的方法和dma模块中的进程方法。
#include "dma.h" void dma::main_action() //dma数据读/写函数 { unsigned int read_cnt=burst_length; unsigned int write_cnt=burst_length; while(dma_en && (~rst)) { wait(); if(state==idle) { buffer=dma_buffer; } if (state==read_state) { //以突发传输的形式从数据源读取数据 while(read_cnt) { master_port->master_read(buffer, source_address); read_cnt--; source_address++; buffer++; } read_cnt=burst_length; } if (state==write_state) { //以突发传输的形式向目的地址写数据 while(write_cnt) { master_port->master_write(buffer, destin_address); write_cnt--; destin_address++; buffer++; } write_cnt=burst_length; } } } void dma::dma_fsm() //dma状态机 { dma_enable=dma_en; while(dma_en && (~rst) && (transfer_size>0))
{
wait( ); if(state==idle) { wait(10,SC_NS); state=read_state; my_state=state; buffer=dma_buffer; } if(state==read_state) { wait(80,SC_NS); state=write_state; my_state=state; buffer=dma_buffer; } if (state==write_state) { wait(80,SC_NS); transfer_size=transfer_size-burst_length; state=idle; my_state=state; } }//end_while }//end_dma_fsm void dma::dma_rst() //初始化函数 { state=idle; //状态初始化 dma_en=0; //寄存器清零操作 transfer_size=0; burst_length=0; source_address=0; destin_address=0; } bool dma::slave_read(int *data, unsigned int address) //从设备接口的读方法 { switch(address) { case 4: *data=source_address; //source address寄存器地址为4 break; case 8: *data=destin_address; break; case 12: *data=burst_length; break; case 16: *data=transfer_size; break case 20: *data=dma_en; break; } return 1; } bool dma::slave_write(int *data, unsigned int address) //从设备接口的写方法 { switch(address) { case 4:source_address=*data; //写寄存器 break; case 8: destin_address=*data; break; case 12: burst_length=*data; break; case 16: transfer_size=*data; break; case 20: dma_en=*data; break; } return 1; } bool dma::slave_select(unsigned address) //判断DMA是否被选中 { if((address>=reg_start_address)&&(address<=reg_end_address)) return 1; else return 0; }
(2)实例二:周期精确的DMA模型
在周期精确的DMA模型中定义了时钟,所有的进程都是SC_METHOD进程。在SC_METHOD进程之间没有通信机制,都通过时钟作为敏感变量触发并发的进程行为。
dma.h文件如下:
#ifndef _dma_h #define _dma_h #include <systemc.h> #include "slave_if.h" #include "master_if.h" enum state_type{idle, read_state, write_state}; SC_MODULE(dma) { public://ports sc_in_clock clk; //输入时钟 sc_in<bool> rst; //初始化信号 sc_port<master_if> master_port; //总线主设备接口 sc_port<slave_if> slave_port; //总线从设备接口 SC_HAS_PROCESS(dma); //macro definition //DMA构造函数 dma(sc_module_name name_, unsigned int start_address, unsigned int end_address) : sc_module(name_) ,reg_start_address(start_address) ,reg_end_address(end_address) { // process declaration SC_METHOD(main_action); sensitive_pos << clk; SC_METHOD(dma_fsm); sensitive_pos << clk; SC_METHOD(dma_rst); sensitive_neg << rst; } private: int counter; …… //以下与无时钟的事务级模型相同 }; #endif
在周期精确的事务级模型中,dma.cpp文件如下:
#include "dma.h" void dma::main_action() { if (dma_en && (~rst) && (state==idle) ) { buffer=dma_buffer; } if (dma_en && (~rst) && (state==read_state)) { master_port->master_read(buffer, source_address); source_address++; buffer++; } if (dma_en && (~rst) && (state==write_state)) { master_port-> master _write(buffer, destin_address); destin_address++; buffer++; } } void dma::dma_fsm() { if(dma_en && (~rst) && (transfer_size>0)) { switch(state){ case idle: state=read_state; counter=burst_length; buffer=dma_buffer; break; case read_state: if(counter>1) { state=read_state; counter=counter-1; } else if(counter==1) { state=write_state; counter=burst_length; buffer=dma_buffer; } break; case write_state: if(counter>1) { state=write_state; counter=counter-1; } else if(counter=1) { state=idle; counter=burst_length; transfer_size=transfer_size-burst_length; } break; } } } void dma::dma_rst() {……} //与无时钟的事务级模型相同 bool dma::slave_read(int *data, unsigned int address) //从设备接口的读方法 {……} bool dma::slave_write(int *data, unsigned int address) //从设备接口的写方法 {……} bool dma::slave_select(unsigned address) //判断DMA是否被选中 {……}
本节介绍了简易DMA的两种事务级建模方法,有兴趣的读者可以自己参考这两种模型建立简易总线的事务级模型和简易存储器的事务级模型,并在一个简易系统的建模过程中增加对事务级建模的理解。
4.7.7 ESL设计的挑战
虽然计算机强大的计算功能使得EDA成为可能,极大地提高了人类对集成电路的设计能力,但这种设计能力提升的速度却远远赶不上集成电路制造工艺的增长,这就使得制造水平和设计能力之间的差距变得越来越大。提高抽象层次使之更接近于设计者思维进行设计,已经成为业界公认的设计技术革新之路。以TLM技术为基础的ESL设计方法依靠抽象层次提高与事务封装的手段,降低了对复杂系统建模与分析的复杂度,提高了模拟与验证的效率。电子系统级设计解决手段TLM与IEEE标准系统级设计语言SystemC之间有着紧密的联系,使得ESL能够更好地应用于实际设计与验证中。
然而,任何技术的革新都会面临一系列挑战,对ESL来说也不例外,当前的ESL设计领域还有如下问题亟待解决。
① 如何设计电子系统级IP核。在ESL设计中,IP核无疑起到很重要的作用。虽然在ESL设计中,功能模块的设计在较高的抽象层次上完成,这相对于RTL模块的设计速度要快得多。但要完成设计和验证众多的功能模块,没有专业的IP提供商也是难以想象的。而处理器等重要的虚拟模型则需要由ESL工具或专业的IP提供商来提供。而IP的可配置性决定了它们能否工作在不同平台上。
② 如何定义ESL的抽象层次。目前,虽然有一些官方和非官方的声音提出了ESL抽象层次的定义方案,但还不能达成共识,这必然会引起不同标准之间的竞争与分歧。
③ 如何提出相应的设计方法学。电子系统级设计毕竟是一个新的设计方法,需要在设计流程和设计方法学上有进一步的探索。
④ 如何转变设计人员的观念。传统的软件工程师、硬件工程师及体系结构工程师是各自分立。而ESL设计需要他们结合到一起而不是相互分离。这种传统的设计观念束缚着ESL在实际应用中的发展。
但是随着IC设计向90nm、65nm、45nm工艺上的不断发展,相信会有越来越多的人认识到ESL设计的重要性。IP核供应商、EDA工具提供商及芯片设计厂商也都会致力于这一领域的扩展,使ESL设计早日得到更加广泛的应用。