杨园建设社区网站,北京海淀区有哪些企业,枣阳网站建设等服务,做内衣的网站之前的章节#xff0c;一直把重点放在用SystemC来描述硬件电路上#xff0c;即如何编写SystemC 的RTL。本章的注意力集中在验证和编写测试平台上。
重点包括#xff1a;
如何生成时钟信号和激励波形如何编写有响应能力的测试平台如何记录仿真结果
8.1 编写测试平台 测试平… 之前的章节一直把重点放在用SystemC来描述硬件电路上即如何编写SystemC 的RTL。本章的注意力集中在验证和编写测试平台上。
重点包括
如何生成时钟信号和激励波形如何编写有响应能力的测试平台如何记录仿真结果
8.1 编写测试平台 测试平台是检测并验证被测试设计正确定的模型。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_main()中模块的实例引用可通过两种途径来实现1将模块声明为对象2使用new操作符。使用sc_signal和sc_clock声明的信号可用于连接实例的端口。可采用两种不同方式进行端口连接1按端口名称进行连接2按端口位置进行连接。
sc_signal bool clk, resetn;
sc_signal sc_uint4 din;
sc_signal sc_lv4 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;8.2 仿真控制
SystemC提供一下结构来协助仿真。
sc_clock 生成时钟信号sc_trace 将仿真波形信息记录到一个指定格式的文件中sc_start 启动并运行仿真到指定时间sc_stop 停止仿真sc_time_stamp 获取当前带时间单位的仿真时间sc_simulation_time 获取当前不带时间单位的仿真时间sc_cycle, sc_initialize 用于执行周期循环级仿真sc_time 指定之间值
sc_clock 如下示例时钟波形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); sc_trace SystemC可以通过三种不同格式来保存仿真结果它们是是
1VCD值变记录格式调用sc_create_vcd_trace_file()来打开文件会自动加扩展名.vcd。
2WIF(波形交换格式 调用sc_create_wif_trace_file()来打开文件会自动加扩展名.awif。
3ISDB(统一信号数据库)格式。调用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。参变量默认值是-9ns。
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_stop sc_start()方法告诉仿真内核启动仿真。该方法的调用必须出现在实例引用所有模块之后也必须出现在调用所有仿真波形记录函数之后。 下面是调用的方法示例在仿真过程中可以多次调用函数sc_start() 若需要重新启动仿真则只需要启动仿真的执行文件即可。
sc_start(100, SC_MS); //启动仿真并运行100nssc_start(-1); //启动仿真并一直运行任何进程都可以调用sc_stop()方法来停止仿真调用格式如下不需要任何参数在调用sc_stop()之后不能在调用sc_start()方法。
sc_stop(); sc_time_stamp/sc_simultion_time sc_time_stamp()方法用于返回当前的仿真时间带有时间单位。例如
cout Current time is sc_time_stamp() endl;将打印如下信息
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 current_time endl;将打印如下信息
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() endl;
cout In seconds: curr_time.to_seconds() endl;
cout In default time units: curr_time.to_default_time_units() endl;
cout In string form: curr_time.to_string() endl;cout sc_time_stamp(): sc_time_stamp() endl; 打印如下
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.hSC_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.hconst 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_NSSC_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.hint 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);coutFinished at time sc_time_stamp() endl;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; jarg_length; j){sprintf(mybuf, [%d], j);sc_trace(tfile, v[j], namemybuf);}
} 上述声明之后就可以用以下语句调用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_uint2 packet_id;bool packet_state;
};//为packet类型编写的sc_trace()重载方法
void sc_trace(sc_trace_file *tfile, const packetv, 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 packet saved;
sc_trace(tf, saved, saved); 8.8 跟踪记录枚举类型 SystemC提供了记录枚举类型值的功能。不过没有什么特别的地方使用C编码风格即可。