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
AutoConnectin 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,nameis defined as 1.-o<output file>: Specify the output file name. When omitted,<input file>is renamed to<input file>.bakand 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> ...
// $ScppEndDirectives 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_MODULEby ignoring#include <...>. - Similarly, system include headers (
stdio.hetc.) should be written with<...>. - If the source code is switched by
#ifdefetc., 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.
// $ScppAutoMemberOutput 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 );
// $ScppEndOutput 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 );
// $ScppEndOutput result ($ScppAutoMember)
// $ScppAutoMember Begin
void RegWriteThread( void );
// $ScppEndGenerate 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
);
// $ScppEndInstantiate 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$ScppInstanceis 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 regexps, 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
clkaandclkb, 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/":clkaport is connected toCLKasignal.
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_inO: Force connected signal tosc_outIO: Force connected signal tosc_inoutW: Force connected signal tosc_signalNC: 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_inalready exists in the module: no signal is generated - If
I,O,IOorWopiton is specified: The signal is generated according to that option - When connected to both
sc_inandsc_out(for example, connected to sc_in of submodule_a and sc_out of submodule_b):sc_signalis generated - If only connected to
sc_in:sc_inis generated - If only connected to
sc_out:sc_outis 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] );
}
// $ScppEndOutput result ($ScppAutoMember)
// $ScppAutoMember Begin
SimpleDmaReg *u_SimpleDmaReg[CH_NUM];
// $ScppEndSC_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;
// $ScppEndOutput result ($ScppAutoMember)
// $ScppAutoMember Begin
void AddrDecorder( void );
void ArbiterSelector( void );
// $ScppEndOutput 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$ScppSensitiveis 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];
// $ScppEndSC_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();
// $ScppEndOutput result ($ScppAutoMember)
// $ScppAutoMember Begin
void ArbiterSelector( void );
// $ScppEndGenerate 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
clkaandclkb, 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