SystemC调试记录(三)

1. 用异常取代sc_stop()

C++也是支持异常的,用异常取代sc_stop()来在极端情况下终止程序也许有好处。比如说,代码一个模块收到一个报文,检查报文的头结果发现根本不可能收到该报文,说明有错误发生。在这种时候,程序应当立即终止,并报出错误信息,便于调试。但是,简单的在报错之后使用sc_stop()的结果是程序正常退出。而往往更关键的信息并不是报错的函数本身,错误信息也不足以反映所有可能性,如果能列出出错时函数的调用关系,即堆栈,往往更有利于调试。

一般我们会使用gdb进行调试。为了显示堆栈,我们不得不寻找该错误的语句并设置断点。既然如此,在sc_stop()之前直接抛出一个异常,gdb会立即终止,并且堆栈没有销毁,则省去了设置断点的过程。当调试完毕,只需要将该句注掉,则以后还是以sc_stop()终止。当然,加上一个DEBUG宏会更好。

其实在C++代码中抛出一个异常极其简单。引入stdexcept头文件(没有.h),然后在任何需要抛出异常的位置写上throw; 就可以了。当然throw还可以带上很多参数,甚至带上一句话,连错误信息都一起完成了。

按照lex的意见,无论多么弱的技术内容,都翻译成英文。

1. Use exception instead of sc_top()

C++ programmes support exceptions and it is better to use it than sc_stop() under some occasions. For example, when a module receives a message and the message checking programme reports that it is impossible to happen, the module is required to pause immediately and report this error to programmers. However, the sc_stop() method simply stops the programme. Compared with the function that reports the error and the error itself, the function stack is more useful in most of the circumstances.

Normally gdb is used for debugging. To show the stack, programmers are required to search the code line that reports the error and then set a breakpoint. Now considering using the exception instead of sc_stop(), an exception immediately stops the programme and the function stack is reserved in gdb. We do not need to set any breakpoints anymore. Of course, using a macro, such as DEBUG, to notify the compiler to use exceptions only in debugging is an even better way.

It is extremely easy to throw an exception in C++ code. To throw an exception, you just need to include the stdexcept header file (without .h), and throw at any place you want. Check the usage of throw. It even can throw a string which including the error message.

2. VCD支持

有点弱了,今天才发现SystemC支持VCD格式,而且很好用。毕竟SystemC还是描述硬件的,有的时候用波形的方式会更直接,查错也更方便。简单说一下方法:

sc_trace_file *fp = sc_create_vcd_trace_file("wave_file");
sc_trace(fp, signal,"sig_name");
sc_start(1000, SC_NS);
sc_close_vcd_trace_file(fp);

sc_create_vcd_trace_file建立了一个名为wave_file的vcd文件,sc_trace将名为signal的信号加到波形的观测列表中,并且定义该信号在波形文件中的名字叫sig_name,sc_start将仿真1000ns,然后sc_close_vcd_trace_file将在仿真结束后关闭波形文件。这里的signal几乎可以为任何东西,比如sc_signal, sc_in, sc_out, int, double, ….

 

2. The support of VCD in SystemC

It is possible to dump signal changes in VCD files automatically in SystemC. Just do it as follow:

sc_trace_file *fp = sc_create_vcd_trace_file("wave_file");
sc_trace(fp, signal,"sig_name");
sc_start(1000, SC_NS);
sc_close_vcd_trace_file(fp);

sc_create_vcd_trace_file creates a vcd file named wave_file. sc_trace function adds the signal signal into wave window and names it sig_name in the VCD file. sc_start starts the simulation and stops after 1000 ns. Finally sc_close_vcd_trace_file closes and stores the VCD file. Note that the signal here can be anything, such as sc_signal, sc_in, sc_out, int, double, …

SystemC调试记录(二)

其实这一篇和SystemC没什么关系,只是一个刚刚意识到的关于C++的认知漏洞,以免变成英文贻笑大方,这篇就不翻成英文了。

今天在调试过程中出现了一个让我接近绝望的错误。具体是这样的。

我自己定义了一个报文类pdu_flit。当然由该类生成的flit对象会被各个模块传来传去。很自然地,我用了一个sc_fifo<pdu_flit>来模拟通道。然而,这里问题就出现了。由于使用fifo,我使用mflit=pfifo->read()来获取一个报文。程序运行没多久,就产生segment fault。用gdb调试,crash的位置指向了~pdu_flit()函数中的delete[] data;语句。

我定义的pdu_flit中报文长度是可变的,所以数据保存在一个运行时分配的数组中,在析构函数中使用delete[]来释放空间是再自然不过的事情,所以该错误让我冥思苦想了很久。

最后,我终于在几个小时之后,突然意识到问题。

解释一下其实也很简单。fifo的read()函数会生成一个新的pdu_flit对象,所以mflit=pfifo->read()会调用pdu_flit对象的默认=操作符将read()生成的临时对象赋值给mflit对象。在赋值之后,read()生成的临时变量生命周期完结,自然被释放,这里会调用我写的~pdu_flit()函数。

然而,最致命的问题,我忘记了重载pdu_flit类的operator=(),也就是说,赋值采用的是默认赋值。在默认赋值当中,data指针被简单的复制,而不是拷贝其中的内容。这样,在read()函数的临时对象销毁之前,mflit和临时对象的data都指向了临时对象的数据域,而临时对象销毁时,数据域已经销毁了。这样,当mflit的生命周期结束而被销毁时,底层代码发现data已经被销毁,重新再销毁则产生了段错误。

过程有些复杂,也没什么迹象可供跟踪,不过解决办法很简单。所有在构造函数中使用了new或者malloc必须重载析构函数,拷贝函数和赋值函数以防止该问题的出现。三个函数的原型:

析构 ~class_name();

拷贝 class_name(const class_name& rhs);

赋值 class_name& operator=(const class_name& rhs);

SystemC调试记录(一)

随着我用SystemC写的工程越来越大,遇到的问题也是接踵而至。很多东西可能在其他地方也遇不到,所以记下来。

1. sensitive列表。

在SystemC中SC_METHOD和SC_THREAD可以使用敏感列表,但是他们的意义完全不一样。对SC_METHOD使用敏感列表和普通RTL代码里面的敏感列表意义是一样的。然而,对于SC_THREAD,敏感列表仅仅触发该方法执行一次,以后所有的执行由方法自行触发(即使用wait和next_trigger的方式)。但是SC_METHOD当中不允许使用wait,甚至任何可能会导致wait的语句都是危险的。比如对一个sc_fifo执行write操作(fifo可能已经满了)。这样会造成一个问题,用SystemC写的standard cell将很难模拟门延时。

另外一个关键问题是上升沿和下降沿。2.0版本还可以使用sensitive_pos()或者sensitive_neg(),但是2.2版本不建议使用,改成在端口上使用.pos()和.neg()。如果看sensitive的定义,只要是event都可以加入敏感列表,但实际上并不是。注意到这样一个问题,在构造函数中将一个端口加入敏感列表时,其对应的信号并没有绑定。所以使用posedge_event()和negedge_event()对其取事件会导致运行时错误。我认为这也是使用.pos()和.neg()的原因。只是一个绕过错误的办法。

1. Sensitive List

It is possible to use sensitive list to SC_METHOD and SC_THREAD functions in SystemC but they have totally different meanings. SC_METHOD is triggerred by sensitive signals during the simulation process but SC_THREAD is only triggerred once. The following triggers for SC_THREAD are required to settled by the function itself, using the wait() and next_trigger() functions. It is not allowed to use wait() in SC_METHOD functions and any actions that may incur a wait is dengerous to SC_METHOD function, which will lead to a run-time error. Therefore, it is dengerous to write a value to a sc_fifo in SC_METHOD. In case the fifo is already full, this write action will stall the SC_METHOD. As a result, I believe, it is hard to simulate gate latency by writing a SystemC simulation cell library.

About the posedge and negedge of sensitive signals, in SystemC 2.0, it is possible to use sensitive_pos and sensitive_neg but in SystemC 2.2, it is recommended to use sig.pos() and sig.neg(). In data type analysis, it is only required to give sensitive an event but in fact it is not correct in all circumstances. For example, when we add ports to sensitive list in construction function for a certain class, the signals are not bonded yet. Therefore, using the posedge_event() or negedge_event() functions to extract events will cause run-time error. This is another explaination why the pos() and neg() functions are necessary: they are walkaround methods.

2. 模块的命名

这是SystemC很令人讨厌的一个特性。所有的模块都必须有自己独一无二的名字。对于RTL建模,也许不是问题,但是对于更广泛的模型,却成了制约。比如,我要建立一个模块组,这个名字规则导致我必须建立指针数组,并一个一个的初始化。

为了能够之间建立数组,我必须构造能够自动命名的构造函数。我想到了默认参数列表。这样,我可以把构造函数写成如下形式:

SC_HAS_PROCESS(EXAMPLE);

EXAMPLE(sc_module_name m_name = sc_module_name("example"))

: sc_module(m_name)

{}

这样,当需要名字的时候,可以给出名字。不需要的时候,特别是建立数组的时候,这个构造函数可以当作默认构造函数使用,模块的名字会自动命名为example。但是,建立数组意味着多个对象被命名为同一个名字,SystemC会报出运行时警告:

Warning: (W505) object already exists: example Latter declaration will be renamed to example_1

不过,这只是一个警告,对实际仿真没有任何影响。我的办法是屏蔽它。可以在sc_start()之前改变对于该警告的默认操作。具体的语句如下:

sc_report_handler::set_actions(SC_ID_OBJECT_EXISTS_, SC_DO_NOTHING);

sc_report_handler是SystemC管理报告的全局静态类,set_actions将改变操作。SC_ID_OBJECT_EXISTS_是这个警告的msg_type,在systemc-2.2.0/include/sysc/kernel/sc_kernel_ids.h文件中定义。同样的方法,可以屏蔽任何不想显示的警告。同时,sc_report_handler::set_actions(SC_WARNING, SC_DO_NOTHING);将屏蔽所有的警告。

2. Naming the Modules

One of the disgusting features of SystemC is its unique naming systems. Every single object in simulation must has a unique name. It is not a problem for RTL modulation but for some general modulations, it is a severe problem: it blocks us to difine a group of objects by a vector. The naming rule requires us to initialize them one by one and name them differently.

To ease the object group definition, we can use the default parameter list. So a construction method could be written as:

SC_HAS_PROCESS(EXAMPLE);

EXAMPLE(sc_module_name m_name = sc_module_name("example"))

: sc_module(m_name)

{}

Consequently, we can explicitly name the object or build up vector directly, thanks to the default construction. However, when use this method in realistic vectors, multiple objects are renamed automatically by SystemC simulator and WARNING 505 are reported, such as:

Warning: (W505) object already exists: example Latter declaration will be renamed to example_1

This warning has no impact to simulation results but will flash your screen. Fortunately, we can block its display by use the following sentence before calling the sc_start() function,

sc_report_handler::set_actions(SC_ID_OBJECT_EXISTS_, SC_DO_NOTHING);

sc_report_handler is the global static class for reporting. set_actions can modify the default actions of sepcific warnings even errors. SC_ID_OBJECT_EXISTS_ is the msg_type of warning 505, defined in systemc-2.2.0/include/sysc/kernel/sc_kernel_ids.h. SC_DO_NOTHING will tell the SystemC simulator to block the display of warning 505.

By the same means, all other warning can be blocked. You even can use the sc_report_handler::set_actions(SC_WARNING, SC_DO_NOTHING); to block all warnings.