SystemC is not a hardware-specific language, but it is implemented with C++ and libraries, so there are various writing problems.
The scpp is a preprocessor aimed at improving writing of SystemC. The scpp has following functions.
-
Module connection automation: Signal connections between modules are automatically connected according to rules. This is the same function as
AutoConnect
in Emacs Verilog mode. It also automatically generates necessary intermediate signals. -
Suppression of description separation of
SC_METHOD()
,SC_THREAD()
,SC_CTHREAD()
: In SystemC, these sentences must be written inSC_CTOR()
and usually separated from the function body. In the scpp, these sentences can be written immediately before the function body. -
Automatic generation of
sc_trace()
: When dumping signals in SystemC, you need to specify signals manually with hierarchy name. The scpp automates that. -
Automatic generation of signal name setting by member initializer: When display exact signal names in a waveform dump, you need to specify signal names with string in C++ member initializer. The scpp automates that.
-
Automatic generation of sensitivity list for
SC_METHOD()
: In Verilog-HDL, sensitivity list can be omitted withalways@( * )
sentence. The scpp can do this. -
Labor saving of writing test bench: Labor saving of stub description creation that instantiates DUT.
The above is achieved by replacing directives ($ScppInstance
, etc.) embedded in C++ source code. No other description is modified.
git clone https://github.com/yoshinrt/scpp.git
cd sample
make SYSTEMC=path_to_systemc_root
Now you can simulation a sample SystemC module. *.cpp /*.h
is generated from *.scpp.cpp /*.scpp.h
.
You can see what description was automatically generated by comparing the files before and after the scpp changes (ex: diff sample/SimpleDma.scpp.h sample/SimpleDma.h
) or by referring to commit e2de8eb.
scpp.pl [-I<include path>] [-D<name>[=<definition>]] [-o <output file>]
[-v] [--clean] <input file>
-I<include path>
: Add<include path>
to the end of the include file search path.-D<name>[=<definition>]
: Define a macro<name>
as<definition>
. If<definition>
is omitted,name
is defined as 1.-o<output file>
: Specify the output file name. When omitted,<input file>
is renamed to<input file>.bak
and then overwritten with<input file>
.-v
: Outputs automatically recognized port and signal information to<src_file>.list
.--clean
: Delete description automatically generated by the scpp.<input file>
: Specify the input file name.
To write directives in C++ source code, use one of the following formats: $ScppSomeDirective
specifically describes $ScppInstance
etc. See scpp directive list for available directives.
- 1 line, without Begin - End
// $ScppSomeDirective(<directive argument> ...)
- 1 line, with Begin - End
// $ScppSomeDirective(<directive argument> ...) Begin
    <C++ code> ...
// $ScppEnd
- Multiple lines, without Begin - End
/* $ScppSomeDirective(
    <Directive argument> ...
) */
- Multiple lines, with Begin - End
/* $ScppSomeDirective(
    <Directive argument> ...
) Begin */
    <C++ code> ...
// $ScppEnd
Directives are written as C++ comments.
If there is no directive argument or it is omitted, you can omit ()
.
If you want to write directive arguments on multiple lines, you must use /* ... */
style comments. It cannot be split into multiple lines with //
style comments.
The scpp outputs C++ code automatically generated by directives between Begin ... $ScppEnd
.
The code written between Begin ... $ScppEnd
before the scpp processing are deleted.
If Begin ... $ScppEnd
is not specified, it is automatically added by the scpp.
The scpp has a built-in C preprocessor (cpp). When the scpp analyzes C++ source code (for example, module port analysis), the scpp processes source code through the built-in cpp. Therefore, there are the following restrictions.
- When including
systemc.h
, use<...>
and write#include <systemc.h>
. The scpp suppresses expansion of macros such asSC_MODULE
by ignoring#include <...>
. - Similarly, system include headers (
stdio.h
etc.) should be written with<...>
. - If the source code is switched by
#ifdef
etc., it will be processed in the define status when the scpp is executed. If the define state is changed after the scpp processing, it may not match the code automatically generated by the scpp. - Due to the limitations described above, it is recommended that parameterization design be performed using a method that does not expand cpp macros, such as C++ templates, instead of defining with
#define
.
Please refer to here for known issues.
A decent C++ parser is not implemented, so the scpp may not work for just a little elaborate C++ source code.
- $ScppAutoMember
- $ScppAutoMemberSim
- $ScppCthread
- $ScppFunction
- $ScppInitializer
- $ScppInstance
- $ScppMethod
- $ScppSensitive
- $ScppSigTrace
- $ScppThread
Output necessary signals, pointer variables to modules, and prototype declarations.
$ScppAutoMember
There are no arguments.
This directive outputs follwing:
- Signals automatically generated by $ScppInstance
- Pointer variables to
SC_MODULE
- Prototype declarations of the member function in which is written $ScppMethod, $ScppThread, $ScppCthread, $ScppFunction.
Please refer to each explanation for details.
// $ScppAutoMember
Output result
// $ScppAutoMember Begin
sc_signal<sc_uint<32> > SrcAddr;
sc_signal<sc_uint<32> > DstAddr;
sc_signal<sc_uint<32> > XferCnt;
sc_signal<bool> Run;
sc_signal<bool> Done;
sc_signal<bool> NceCh[CH_NUM];
sc_signal<sc_uint<32> > RegRDataCh[CH_NUM];
sc_signal<sc_uint<32> > SrcAddrCh[CH_NUM];
sc_signal<sc_uint<32> > DstAddrCh[CH_NUM];
sc_signal<sc_uint<32> > XferCntCh[CH_NUM];
sc_signal<bool> RunCh[CH_NUM];
sc_signal<bool> DoneCh[CH_NUM];
SimpleDmaCore *u_SimpleDmaCore;
SimpleDmaReg *u_SimpleDmaReg[CH_NUM];
void AddrDecorder( void );
void ArbiterSelector( void );
void RDataSelector( void );
// $ScppEnd
Output a member initializer description that sets a name of a signal.
$ScppInitializer[( <delimiter> )]
delimiter
: Specify output delimiters as a string.- If the string contains
:
, output:
at the beginning of the initialization list. However, nothing is output when there is no initialization list. - If the string contains
,
, output,
at the end of the initialization list. However, nothing is output when there is no initialization list. - Both can be specified like
":,"
.
- If the string contains
Generate a member initializer description for signal name initialization specified in the constructor.
To dump a signal (sc_trace()
) in SystemC and display the signal name correctly in a dump file, you need to specify the signal name in the member initializer. $ScppInitializer
automates that.
$ScppInitializer
generates member initializer descriptions of all recognized sc_in_clk / sc_in / sc_out / sc_inout / sc_signal
.
However, member initializer descriptions for array signals are not generated (this is restriction of C++ language specification).
SC_CTOR( SimpleDma )
// $ScppInitializer( ":" )
{
Output result
SC_CTOR( SimpleDma )
// $ScppInitializer Begin
: clk( "clk" ),
nrst( "nrst" ),
RegAddr( "RegAddr" ),
RegWData( "RegWData" ),
RegNce( "RegNce" ),
RegWrite( "RegWrite" ),
RegRData( "RegRData" ),
SramAddr( "SramAddr" ),
SramWData( "SramWData" ),
SramNce( "SramNce" ),
SramWrite( "SramWrite" ),
SramRData( "SramRData" ),
SrcAddr( "SrcAddr" ),
DstAddr( "DstAddr" ),
XferCnt( "XferCnt" ),
Run( "Run" ),
Done( "Done" )
// $ScppEnd
{
SC_CTHREAD()
description can be written just before the function body.
$ScppCthread( <clock>[, <reset>, <attr>])
clock
: Specify a clock eventreset
: Specify a reset signalattr
: Specify reset attributes as a character string in the following combinations:"a"
: generatesasync_reset_signal_is()
"s"
: generatesreset_signal_is()
"p"
: Set reset polarity to true"n"
: Set reset polarity to false
If reset
and attr
are omitted, async_reset_signal_is()
and reset_signal_is()
descriptions are not generated.
SC_CTHREAD()
description can be written just before the function body.
SC_CTHREAD()
must be described in SC_CTOR()
and is usually separated from the function body describing the behavior, but this has a problem in readability.
By writing $ScppCthread
immediately before the function body, SC_CTHREAD()
description is generated at the position of $ScppSensitive
.
Also, a prototype declaration description of the function is generated at the position of $ScppAutoMember
.
// $ScppCthread( clk.pos(), nrst, "an" )
void SimpleDmaReg::RegWriteThread( void ){
Output result ($ScppSensitive
)
// $ScppSensitive( "SimpleDmaReg.cpp" ) Begin
SC_CTHREAD( RegWriteThread, clk.pos() );
async_reset_signal_is( nrst, false );
// $ScppEnd
Output result ($ScppAutoMember
)
// $ScppAutoMember Begin
void RegWriteThread( void );
// $ScppEnd
Generate prototype declaration of member function in header file.
$ScppFunction
There are no arguments.
By describing $ScppFunction
immediately before the function body, a prototype declaration description of the function is generated at the position of $ScppAutoMember
.
This directive is not directly related to SystemC, but you can generate a prototype declaration description in the header file in the same way as $ScppMethod or $ScppCthread.
// $ScppFunction
sc_uint<32> Decoder::GaloaLog( sc_uint<32> idx ){
...
}
// $ScppFunction
void Decoder::Compute(
sc_uint<32> *DataBuf,
sc_uint<32>& DataSize
){
...
}
Output result ($ScppAutoMember
)
// $ScppAutoMember Begin
sc_uint<32> GaloaLog( sc_uint<32> idx );
void Compute(
sc_uint<32> *DataBuf,
sc_uint<32>& DataSize
);
// $ScppEnd
Instantiate SystemC module and connect automatically. Necessary intermediate signals are automatically generated.
$ScppInstance( <submodule>, <instance>, <file> [, <regexp> ...])
submodule
: Specify the submodule name to instantiate.instance
: Specify the instance name of the submodule to instantiate.file
: Specify the file where the submodule is described. If "." is specified, the file itself in which$ScppInstance
is written is specified.regexp
: Specify the port ⇔ signal connection rule.
Instantiate SystemC module and connect automatically. Necessary intermediate signals are automatically generated.
Currently, the only ports that can be automatically connected are sc_(in|out|inout)
sc_(in|out|inout)_clk
sc_fifo_(in|out|inout)
.
Submodule ports are converted to module signal names according to the rules specified in regexp
. The format of regexp
is as follows.
"/port/signal/option"
You can use perl regular expressions for port
. If there are multiple regexp
s, the rule is checked to see if it matches port
in order from the first, and the first matching rule is adopted. For ease of description, port
is automatically prepended with '^' and ending with '$'.
- Example: To match both
clka
andclkb
, you need to specifyclk.*
.
signal
specifies the name of the signal connected to the port. If you use grouping (...)
on port
, you can use backreferences such as $1
.
- Example:
"/clk(.*)/CLK$1/"
:clka
port is connected toCLKa
signal.
If none of the rules match, the signal with the same name as the port name is connected.
Specify some of the following strings in option
. option
is optional.
I
: Force connected signal tosc_in
O
: Force connected signal tosc_out
IO
: Force connected signal tosc_inout
W
: Force connected signal tosc_signal
NC
: Declares that this output port is a floating port. In fact, a dummy sc_signal is created and connected to it.d
: Ports matching this rule are excluded from automatic connection.
port
is optional. If omitted, ^(.*)$
is assumed.
signal
is optional. If omitted, the signal with the same name as port
is connected.
The regular expression delimiter /
can be written with other character, and the first character of regexp
is recognized as a delimiter. For example, if you write "/foo.*/bar/", it will be recognized as the end of a multi-line comment at the part of */
, so it cannot be parsed correctly. In this case, you need to use another character, such as " @foo.*@bar@"
.
After processing all $ScppInstance
in the module where $ScppInstance
is described, signals are generated according to the following rules and output to the place of $ScppAutoMember
.
- If a signal declaration such as
sc_in
already exists in the module: no signal is generated - If
I
,O
,IO
orW
opiton is specified: The signal is generated according to that option - When connected to both
sc_in
andsc_out
(for example, connected to sc_in of submodule_a and sc_out of submodule_b):sc_signal
is generated - If only connected to
sc_in
:sc_in
is generated - If only connected to
sc_out
:sc_out
is generated
If the submodule port is a multidimensional array, it is connected to a signal of the same dimension and size. In other words, it is connected like u_Inst.port[ n ] -> signal[ n ]
.
instance
can contain an array index. In this case, an instance array is generated. In the example below, 8 modules of Module
are instantiated.
Example: $ScppInstance( Module, u_Inst[8], ... )
In this case, []
can be specified at the end of signal
in the connection rule. In this case, an array of signals is generated and connected to u_Inst[ n ].port -> signal[ n ]
. Furthermore, if port is a multidimensional array, it is connected as u_Inst[ n ].port[ m ] -> signal[ n ][ m ]
.
/* $ScppInstance(
SimpleDmaReg, u_SimpleDmaReg[ CH_NUM ], "SimpleDmaReg.h",
"/clk|nrst//",
"/(Addr|WData|Write)/Reg$1/",
"/(RData)/Reg$1Ch[]/W",
"//$1Ch[]/W",
) */
Output result ($ScppInstance
)
/* $ScppInstance(
SimpleDmaReg, u_SimpleDmaReg[ CH_NUM ], "SimpleDmaReg.h",
"/clk|nrst//",
"/(Addr|WData|Write)/Reg$1/",
"/(RData)/Reg$1Ch[]/W",
"//$1Ch[]/W",
) Begin */
for( int _i_0 = 0; _i_0 < CH_NUM; ++_i_0 ){
u_SimpleDmaReg[_i_0] = new SimpleDmaReg(( std::string( "u_SimpleDmaReg(" ) + std::to_string(_i_0) + ")" ).c_str());
u_SimpleDmaReg[_i_0]->clk( clk );
u_SimpleDmaReg[_i_0]->nrst( nrst );
u_SimpleDmaReg[_i_0]->Addr( RegAddr );
u_SimpleDmaReg[_i_0]->WData( RegWData );
u_SimpleDmaReg[_i_0]->Nce( NceCh[_i_0] );
u_SimpleDmaReg[_i_0]->Write( RegWrite );
u_SimpleDmaReg[_i_0]->RData( RegRDataCh[_i_0] );
u_SimpleDmaReg[_i_0]->SrcAddr( SrcAddrCh[_i_0] );
u_SimpleDmaReg[_i_0]->DstAddr( DstAddrCh[_i_0] );
u_SimpleDmaReg[_i_0]->XferCnt( XferCntCh[_i_0] );
u_SimpleDmaReg[_i_0]->Run( RunCh[_i_0] );
u_SimpleDmaReg[_i_0]->Done( DoneCh[_i_0] );
}
// $ScppEnd
Output result ($ScppAutoMember
)
// $ScppAutoMember Begin
SimpleDmaReg *u_SimpleDmaReg[CH_NUM];
// $ScppEnd
SC_METHOD()
description can be written just before the function body.
$ScppMethod[( code )]
code
: Write C++ code that represents the sensitivity list
SC_METHOD()
description can be written just before the function body.
SC_METHOD()
must be described in SC_CTOR()
, and is usually separated from the function body describing the behavior, but this has a problem in readability.
By writing $ScppMethod
immediately before the function body, SC_METHOD()
description is generated at the position of $ScppSensitive
.
Also, a prototype declaration description of the function is generated at the position of $ScppAutoMember
.
When the argument is omitted, a sensitivity list is automatically generated using all signals that are .read ()
in the function.
// $ScppMethod
void SimpleDma::AddrDecorder( void ){
// (...omitted...)
/* $ScppMethod(
for( int i = 0; i < CH_NUM; ++i ){
sensitive << DstAddrCh[i] << RunCh[i] << SrcAddrCh[i] << XferCntCh[i];
}
sensitive << Done;
)*/
void SimpleDma::ArbiterSelector( void ){
Output result ($ScppSensitive
)
// $ScppSensitive( "." ) Begin
SC_METHOD( AddrDecorder );
sensitive << RegAddr << RegNce;
SC_METHOD( ArbiterSelector );
for( int i = 0; i < CH_NUM; ++i ){
sensitive << DstAddrCh[i] << RunCh[i] << SrcAddrCh[i] << XferCntCh[i];
}
sensitive << Done;
// $ScppEnd
Output result ($ScppAutoMember
)
// $ScppAutoMember Begin
void AddrDecorder( void );
void ArbiterSelector( void );
// $ScppEnd
Output the sensitivity list described by $ScppMethod, $ScppThread, and $ScppCthread.
$ScppSensitive( <file> [, <file> ...])
file
: Specify a file containing$ScppMethod, $ScppThread, $ScppCthread
. If "." Is specified for the file name, the file itself with$ScppSensitive
is specified.
Output the sensitivity list described by $ScppMethod, $ScppThread, and $ScppCthread.
SC_METHOD() SC_TREAD() SC_CTHREAD()
must be described in SC_CTOR()
and is usually separated from the function body describing the operation, but this has a problem in readability.
By describing $ScppSensitive
in SC_CTOR()
, Search for $ScppMethod, $ScppThread, $ScppCthread from the specified file, then the sensitivity list description is generated at the position of $ScppSensitive
.
// $ScppSensitive( "." )
Output result
// $ScppSensitive( "." ) Begin
SC_METHOD( AddrDecorder );
sensitive << RegAddr << RegNce;
SC_METHOD( ArbiterSelector );
for( int i = 0; i < CH_NUM; ++i ){
sensitive << DstAddrCh[i] << RunCh[i] << SrcAddrCh[i] << XferCntCh[i];
}
sensitive << Done;
SC_METHOD( RDataSelector );
for( int i = 0; i < CH_NUM; ++i ) sensitive << RegRDataCh[i];
// $ScppEnd
SC_THREAD()
description can be written just before the function body.
$ScppThread( code )
code
: Write C++ code that represents the sensitivity list
SC_THREAD()
description can be written just before the function body.
SC_THREAD()
must be described in SC_CTOR()
and is usually separated from the function body describing the behavior, but this has a problem in readability.
By writing $ScppThread
immediately before the function body, SC_THREAD()
description is generated at the position of $ScppSensitive
.
Also, a prototype declaration description of the function is generated at the position of $ScppAutoMember
.
// $ScppThread( sensitive << clk.pos())
void SimpleDma::ArbiterSelector( void ){
Output result ($ScppSensitive
)
// $ScppSensitive( "." ) Begin
SC_THREAD( ArbiterSelector );
sensitive << clk.pos();
// $ScppEnd
Output result ($ScppAutoMember
)
// $ScppAutoMember Begin
void ArbiterSelector( void );
// $ScppEnd
Generate sc_trace()
descriptions of the signals in the module.
$ScppSigTrace[( <regexp> ... )]
regexp
: Specify the rule (regular expression) of signals to be traced or excluded. If the argument is omitted, all signals in the module are traced.
Generate sc_trace()
descriptions of the signals in the module. However, sc_fifo
cannot be traced due to SystemC limitation.
It is assumed that sc_trace_file *ScppTraceFile
is declared as a global variable. Also, ScppTraceFile
must be opened using sc_create_vcd_trace_file()
before this module is instantiated.
Since the generated code is delimited with #ifdef VCD_WAVE
, VCD_WAVE
macro must be defined in order to output VCD.
The signals to be traced or excluded are determined according to the rules specified in regexp
. The format of regexp
is as follows.
"/signal/option"
You can use perl regular expressions for signal
. If more than one regexp
is described, the rule is checked to see if it matches signal
, starting with the first rule. For ease of description, signal
is automatically prepended with '^' and ending with '$'.
- Example: To match both
clka
andclkb
, you need to writeclk.*
.
Signals that do not match all the rules are judged to be traced.
Specify some of the following strings in option
. option
is optional.
S
: This rule is applied only to scalar signals.A
: This rule is applied only to array type signals.N
: Signals that match this rule are excluded from tracing. Signals that match a rule for which this option is not specified will be traced.
signal
is optional. If omitted, .*
Is assumed.
The regular expression delimiter /
can be described with other character, and the first character of regexp
is recognized as a delimiter. For example, if you write "/foo.*/", it will be recognized as the end of a multi-line comment in the */
part, so it cannot be parsed correctly. In this case, you need to use another character, such as "@foo.*@"
.
sc_main()
description
#ifdef VCD_WAVE
sc_trace_file *ScppTraceFile;
#endif
int sc_main( int argc, char **argv ){
#ifdef VCD_WAVE
ScppTraceFile = sc_create_vcd_trace_file( "simple_dma" );
ScppTraceFile->set_time_unit( 1.0, SC_NS );
#endif
SimpleDma *u_SimpleDma = new SimpleDma( "u_SimpleDma" );
Each module description
#ifdef VCD_WAVE
extern sc_trace_file *ScppTraceFile;
#endif
SC_MODULE( SimpleDma ){
// ...(omitted)...
/* $ScppSigTrace(
"/DataBuf/N"
) */
Output result
// $ScppSigTrace Begin
#ifdef VCD_WAVE
sc_trace( ScppTraceFile, clk, std::string( this->name()) + ".clk" );
sc_trace( ScppTraceFile, nrst, std::string( this->name()) + ".nrst" );
sc_trace( ScppTraceFile, RegAddr, std::string( this->name()) + ".RegAddr" );
sc_trace( ScppTraceFile, RegWData, std::string( this->name()) + ".RegWData" );
sc_trace( ScppTraceFile, RegNce, std::string( this->name()) + ".RegNce" );
sc_trace( ScppTraceFile, RegWrite, std::string( this->name()) + ".RegWrite" );
sc_trace( ScppTraceFile, RegRData, std::string( this->name()) + ".RegRData" );
sc_trace( ScppTraceFile, SramAddr, std::string( this->name()) + ".SramAddr" );
sc_trace( ScppTraceFile, SramWData, std::string( this->name()) + ".SramWData" );
sc_trace( ScppTraceFile, SramNce, std::string( this->name()) + ".SramNce" );
sc_trace( ScppTraceFile, SramWrite, std::string( this->name()) + ".SramWrite" );
sc_trace( ScppTraceFile, SramRData, std::string( this->name()) + ".SramRData" );
sc_trace( ScppTraceFile, SrcAddr, std::string( this->name()) + ".SrcAddr" );
sc_trace( ScppTraceFile, DstAddr, std::string( this->name()) + ".DstAddr" );
sc_trace( ScppTraceFile, XferCnt, std::string( this->name()) + ".XferCnt" );
sc_trace( ScppTraceFile, Run, std::string( this->name()) + ".Run" );
sc_trace( ScppTraceFile, Done, std::string( this->name()) + ".Done" );
#endif // VCD_WAVE
// $ScppEnd