之前的章节,一直把重点放在用SystemC来描述硬件电路上,即如何编写SystemC 的RTL。本章的注意力集中在验证和编写测试平台上。
重点包括:
测试平台是检测并验证被测试设计正确定的模型。SystemC 不但可以描述设计,也可以编写测试平台。测试平台可以实现如下三个主要目标:
测试平台可以用多种不同的方式进行编写。如下是其中一种方式:
有三个主要模块,激励生成模块(stimulus.h, stimulus.cpp), 输出的检测和比较模块(monitor.h和monitor.cpp), 被测设计模块(dut.h和dut.cpp)。主程序main.cpp链接了这三个不同模块并将它们互相连接起来,构成一个测试平台。
另一种方式是:
将激励的生成和对输出的检测功能都嵌入到主程序中;或使用一个模块生成激励,而将检测比较和结果都嵌入到主程序中。
具体的方式完全根据用户、风格、测试和设计的复杂程度来决定。在规模比较大的设计中,上面第一种分成三个模块的风格是非常有用的,因为这些模块可能比较大。
下面的文件main.cpp展示了一个测试平台的结构
- //这里包含其它文件(#include 编译指令)
-
- int sc_main(int argc, char* argv[])
- {
- //这里声明sc_signal, 用于连接所引用的不同实例模型
-
- //声明时钟 sc_clock
-
- //被测设计、激励模块和监测模块的实例引用
-
- //创建记录仿真波形的(trace)文件,并进行监测
-
- //仿真的开始和控制
- }
必须为每一个准备测试的、使用SystemC描述的设计编写函数sc_main()。函数sc_main()是一个主程序,该主程序不但实例引用了被测设计模块,还引用了其它模块,他们在一起被编译和链接,最后生成一个可执行文件。当运行该可执行文件时,该主程序就会开始运行仿真。
- sc_signal <bool> clk, resetn;
- sc_signal
4> > din; - sc_signal
4> > dout; -
- tristate_reg tr1("tristate_reg_tr1"); //将模块声明为对象
-
-
- //通过端口名进行信号连接
- tr1.clock(clk);
- tr1.reset(resetn);
- tr1.data_in(din);
- tr1.data_out(dout);
-
-
- tristate_reg tr2 = new tristate_reg ("tristate_reg_tr1"); //通过new操作符实现模块实例的引用
- //通过端口位置进行信号连接
- tr2 << clk << resetn << din << dout;
SystemC提供一下结构来协助仿真。
如下示例,时钟波形rclk的开关周期是10ns, 默认占空比是50%,默认初始值为真(1);
时钟波形mclk周期是10ns, 占空比是20%, 第一个跳变沿出现在5ns之后,且在第一个跳变沿之后值为假(0);
第一个参数是时钟名称,必须指定时钟名称,如clka, 如果没有指定周期,默认为1个默认时间单位。在SystemC中默认时间单位是1ns。所以clka周期是1ns,占空比50%,第一个跳变沿发生在0时刻,且在第一个跳变沿后的初始值为真。
- sc_clock rclk("rclk", 10, SC_NS);
- sc_clock mclk("mclk", 10, SC_NS, 0.2, 5, SC_NS, false);
- sc_clock clka("clka");
SystemC可以通过三种不同格式来保存仿真结果,它们是是:
1)VCD(值变记录)格式;调用sc_create_vcd_trace_file()来打开文件,会自动加扩展名.vcd。
2)WIF(波形交换)格式; 调用sc_create_wif_trace_file()来打开文件,会自动加扩展名.awif。
3)ISDB(统一信号数据库)格式。调用sc_create_isdb_trace_file()来打开文件,会自动加扩展名.isdb。
调用sc_trace()可以指定一些信号,并将它们的值保存在仿真波形记录文件中。通常最好将字符串参数名和信号名保持一致。
sc_trace(tfile, signal_name, "signal_name");
在退出测试平台sc_main()函数之前,必须选择相应的函数来关闭仿真波形记录文件。
sc_close_vcd_trace_file(tfile);
必须先打开仿真波形记录文件,然后在创建需要记录的信号后才能调用sc_trace()函数将波形记录到文件中。
VCD文件中的时间单位可以调用sc_set_vcd_time_uint()方法来设置。如下示例,该方法的参变量是一个时间单位,该时间单位是以10为底的指数。所以参变量-6对应的时间单位是μs。参变量默认值是-9(ns)。
- sc_trace_file *trace_file = sc_create_vcd_trace_file("ahb_trans.vcd");
- ((vcd_trace_file *)trace_file)->sc_set_vcd_time_uint(-6);
可以将以∆周期(delta cycle)为节拍的仿真波形存入VCD文件中,波形观测器需要VCD文件才能显示仿真波形。使用函数delta_cycles()可以使能或禁止对以∆周期为节拍的仿真波形进行记录。参变量为真,则开始记录波形,为假,则停止记录仿真波形。
- trace_file->delta_cycles(true);
- ...
- trace_file->delta_cycles(false);
- ...
- trace_file->delta_cycles(true);
- ...
sc_start()方法告诉仿真内核启动仿真。该方法的调用必须出现在实例引用所有模块之后,也必须出现在调用所有仿真波形记录函数之后。
下面是调用的方法示例,在仿真过程中可以多次调用函数sc_start(), 若需要重新启动仿真,则只需要启动仿真的执行文件即可。
- sc_start(100, SC_MS); //启动仿真并运行100ns
-
- sc_start(-1); //启动仿真并一直运行
任何进程都可以调用sc_stop()方法来停止仿真,调用格式如下,不需要任何参数,在调用sc_stop()之后,不能在调用sc_start()方法。
sc_stop();
sc_time_stamp()方法用于返回当前的仿真时间(带有时间单位)。例如:
cout << "Current time is "<< sc_time_stamp() <
将打印如下信息:
Current time is 25ns
sc_simultion_time在最近的SystemC版本已经被IEEE_Std_1666/deprecated,下面是之前老版本的定义。
sc_simulation_time()方法根据默认的时间单位,以double(不带时间单位的双精度)类型的整数值格式返回当前仿真时间,例如:
- double current_time = sc_simulation_time();
- cout << "Time now is %f" <
将打印如下信息:
Tiem now is 96
sc_cycle/sc_initialize
这两个方法用于执行按周期节拍的仿真。例如想每过10个时间单位就计算一次设计的输出结果,就须使用周期节拍的仿真。这种情况下,不要使用方法sc_start(),而要使用sc_initialize()和sc_cycle()这两个方法。
sc_initialize()方法用于对仿真内核进行初始化。sc_cycle()方法执行所有准备运行的进程,直到不再需要有进程执行,但这将需要许多个∆周期节拍。在仿真时间再次前进指定的时间长度之前,sc_cycle()方法会将一些必要信号的仿真信息记录下来。
下面语句将仿真所有进程,并将仿真时间向前推进10μs。
sc_cycle(10, SC_US);
sc_time
sc_time用于声明由参量表示的时间值,如10ns, 20ps等。由sc_time声明的变量可以在需要指定时间值的地方使用,如sc_clock()和sc_start()中。
- sc_time t1(100, SC_NS);
- sc_time t2(20, SC_PS); //指定t2的值为20ps
-
- //这两种方式等价
- sc_start(t1);
- sc_start(100, SC_NS);
-
-
- sc_cycle(t2);
-
- sc_time period(10, SC_NS);
- sc_time start_time(2, SC_NS);
- sc_clock fclk("fclk", period, 0.2, start_time, true);
时间单位有如下几种: SC_FS, SC_PS, SC_NS, SC_US, SC_MS, SC_SEC。
默认的时间分辨率是1ps。可以通过sc_set_time_resolution()来修改。例如:
sc_set_time_resolution(100, SC_PS);
指定时间分辨率只能是10的幂,并且经常是在主程序sc_main()的最开始处指定一次时间分辨率。
打印不同时间单位的转换函数有:
- sc_time curr_time = sc_time_stamp();
-
- cout <<"As double: " << curr_time.to_double() <
- cout <<"In seconds: " << curr_time.to_seconds() <
- cout <<"In default time units: " << curr_time.to_default_time_units() <
- cout <<"In string form:" << curr_time.to_string() <
-
- cout <<"sc_time_stamp():" << sc_time_stamp() <
打印如下:
- As double: 100000
- In seconds: 1e-07
- In default time units: 100
- In string form:100 ns
- sc_time_stamp():100 ns
8.3 波形
普通波形,如时钟波形,可以通过调用sc_clock声明语句来生成。实际上,也可以通过在模块中使用SC_METHOD类型进程来生成任意类型的波形。
示例1,建模任意波形
SC_THREAD类型的进程prc_wave何时执行呢?在初始化时刻。所有进程都在初始化时刻(即在仿真开始之前的瞬间)执行一次。根据进程的执行,输出端口sig_out被赋值为0。接下来等待语句使得进程prc_wave被挂起并等待5ns。在5ns之后,sig_out被赋值1,然后该进程被挂起2ns,后面过程类似。
- #include "systemc.h"
-
- SC_MODULE(wave){
- sc_out <bool> sig_out;
-
- void prc_wave();
-
- SC_CTOR(wave){
- SC_THREAD(prc_wave);
- }
-
- };
-
- void wave::prc_wave(){
- sig_out = 0;
- wait(5, SC_NS);
- sig_out = 1;
- wait(2,SC_NS);
- sig_out = 0;
- wait(5, SC_NS);
- sig_out = 1;
- wait(8,SC_NS);
- sig_out = 0;
- }
示例2,建模复杂的重复波形
如果想每100ns重复一次上面的波形,那怎么办呢?可以在SC_THREAD类型进程使用一个永不停止的while循环语句来实现。如下:
- void wave::prc_wave(){
- sig_out = 0;
- wait(5, SC_NS);
- sig_out = 1;
- wait(2,SC_NS);
- sig_out = 0;
- wait(5, SC_NS);
- sig_out = 1;
- wait(8,SC_NS);
- sig_out = 0;
- wait(80, SC_NS);
- }
示例3,自定义时钟发生器
- #include "systemc.h"
-
- const int START_VALUE = 0;
- const int INITIAL_DELAY = 5;
- const int FIRST_DELAY = 2;
- const int SECOND_DELAY = 3;
-
-
- SC_MODULE(myclock){
- sc_out <bool> clk_out;
-
- void prc_myclock();
-
- SC_CTOR(myclock){
- SC_THREAD(prc_myclock);
- }
-
- };
-
- void myclock::prc_myclock(){
- clk_out = START_VALUE;
- wait(INITIAL_DELAY, SC_NS);
-
- while(1){
- clk_out = !(clk_out);
- wait(FIRST_DELAY, SC_NS);
- clk_out = !clk_out;
- wait(SECOND_DELAY, SC_NS);
- }
- }
示例4,完整测试平台搭建的生成一个衍生时钟
pulse.h文件
- #include "systemc.h"
-
- #define DELAY 2, SC_NS
- #define ON_DURATION 1, SC_NS
-
- SC_MODULE(pulse){
- sc_in <bool> clk;
- sc_out <bool> pulse_out;
-
- void prc_pulse();
-
- SC_CTOR(pulse){
- SC_THREAD(prc_pulse);
- sensitive_pos << clk;
- }
-
- };
-
- void pulse::prc_pulse(){
- pulse_out = 0;
-
- while(true){
- wait();
- wait(DELAY);
- pulse_out = 1;
- wait(ON_DURATION);
- pulse_out = 0;
- }
- }
SC_THREAD进程可以带敏感列表,也可以不带敏感列表;prc_pulse中while的第一个wait()等待语句 是等待敏感列表中的某个信号发生变化。
pulse_main.cpp
- #include "pulse.h"
-
- int sc_main(int argc, char * argv[]){
- sc_signal <bool> pout;
- sc_trace_file *tf;
- sc_clock clock("master_clk", 5, SC_NS);
-
- //pulse模块的实例引用
- pulse p1("pulse_p1");
- p1.clk(clock);
- p1.pulse_out(pout);
-
- //创建仿真波形记录文件pulse.vcd, 并设置需要记录波形的信号
- tf = sc_create_vcd_trace_file("pulse");
- sc_trace(tf, clock, "clock");
- sc_trace(tf, pout, "pulse_out");
-
- sc_start(100, SC_NS);
- sc_close_vcd_trace_file(tf);
- cout<<"Finished at time " << sc_time_stamp() <
- return 0;
-
- }
另外:
- 激励数据可以从文件中读取,然后加载到被测设计中。
- 还有一种激励是响应性激励,这种类型的激励源所生成的下一个激励取决于被测平台的当前状态。换言之,测试平台会根据被测设计的状态产生相应的激励。如进行数的阶乘计算的阶乘设计。
8.4 监视行为
监视行为包括:
1)判断仿真结果是否正确
将一个向量施加到被测设计的输入端口,在指定时间后,在输出端口进行采样,然后验证输出响应与期望值是否一致。
2)把结果保存到文本文件中
通过使用C++语言中文件写入(输出流)功能,可将被测设计的输出值保存到文本文件中,需要注意的是:当打印数值时,打印的是当前时刻的信号值,而不是按计划在下一个∆时刻被赋的值。
8.5 更多示例
测试平台的示例demo将包括如下。将用单独的博文分别介绍。
- 触发器
- 带同步输出的多路选择器
- 全加器
- 周期节拍级仿真
8.6 在sc_main内的语句顺序
SystemC对函数sc_main()中语句出现的先后顺序是敏感的。sc_main()函数基本上是一段有序程序,即按顺序执行的程序,因此期望程序的语句是有一定顺序的:
- 所有模块引用和互连必须出现在调用任何仿真波形记录函数之前。
- 在仿真启动之前,必须首先打开仿真波形的记录文件,然后再调用记录仿真波形的函数。
- 若使用sc_set_time_resolution()方法来改变默认的时间分辨率(1ps),则该方法必须出现在任何sc_time对象被创建之前。
- 在调用sc_start()之后不可以再出现模块的实例调用
8.7 跟踪记录集合类型
sc_trace()方法被预定义用于SystemC类型和其他的C++标量类型。为了记录数组或者结构类型,必须编写自己的sc_trace()重载方法来记录单个部件的值。
请考虑下面这个数组的声明,若想使用sc_trace()函数来记录reg_file的变化,则必须定义如下重载函数类型。
bool reg_file[NUM_BITS];
- const int MAXLEN = 8;
- void sc_trace(sc_trace_file *tfile, bool *v, const sc_string &name, int arg_length){
- char mybuf[MAXLEN];
-
- for(int j = 0; j
- sprintf(mybuf, "[%d]", j);
- sc_trace(tfile, v[j], name+mybuf);
- }
- }
上述声明之后,就可以用以下语句调用sc_trace函数来记录仿真的波形了。
sc_trace(tf, reg_file, "reg_file", NUM_BITS);
如果不想编写独立的sc_trace()重载函数,也可将上面的功能简单的按行写到(inline)函数sc_main()中,而不须再调用sc_trace()函数来记录reg_file的变化。
借助于模板(template)可以使sc_trace()函数适用于任何类型的数组。下面这段代码假设这两个数组的元素类型允许通过调用sc_trace()函数来记录它们的变化。
- template <class T>
- void sc_trace (sc_trace_file *tfile, const T array_var[], const sc_string &name, int arg_length){
- for (int j = 0; j < arg_length; j++){
- sc_trace(tfile, array_var[j], name + "." + sc_string:: to_string("%d", j));
- }
- }
-
- //调用的示例
- sc_signal <float> qms[4];
- int mlef[8];
-
- sc_trace(tfile, qms, "qms", 4);
- sc_trace(tfile, mlef, "mlef", 8);
对结构也可以进行类型的跟踪记录。可以编写一个sc_trace()重载方法,也可以只对个别成员编写sc_trace()调用来实现对结构的跟踪记录。若信号是结构类型的,则必须提供带其它重载操作符的sc_trace()重载方法。
- struct packet{
- sc_uint<2> packet_id;
- bool packet_state;
- };
-
- //为packet类型编写的sc_trace()重载方法
- void sc_trace(sc_trace_file *tfile, const packet&v, const sc_string &name){
- sc_trace(tfile, v.packet_id, name + ".packet_id");
- sc_trace(tfile, v.packet_state, name +".packet_state");
- }
有了上述函数声明语句后,就能以如下形式调用sc_trace()方法了。
- //集合类型信号
- sc_signal
saved; - sc_trace(tf, saved, "saved");
8.8 跟踪记录枚举类型
SystemC提供了记录枚举类型值的功能。不过没有什么特别的地方,使用C++编码风格即可。
-
相关阅读:
代码整洁之道-读书笔记之对象和数据结构
leetcode_2652倍数求和
10张流程图+部署图,讲透单点登录原理与简单实现
js如何区分数组和对象
功能上线回滚考虑
LeetCode题目笔记——2486. 追加字符以获得子序列
2022-09-20 mysql列存储引擎-POC-调用自定义函数-参数赋值
SpringBoot数据库管理 - 用flyway对数据库管理和迁移
记录一次rust浮点数计算没有java速度快的例子
在docker中安装MQTT教程
-
原文地址:https://blog.csdn.net/u010420283/article/details/133832000