2018年5月23日水曜日

SystemC で全信号の sc_trace() をなるべく楽にする方法(2)

以前「インスタンス階層ツリーを出してくれる機能はなさげ」と書いたけど,SystemC の全信号を階層付きでリストアップする方法はあることがわかった.
Why sc_object menber function trace() deprecated?
ただしリンク先にもあるように,一発で全信号を波形ダンプする方法があるわけではない.
# 信号名の一覧が取得できるだけ
しかし階層付きの信号一覧取得できれば,ほぼ目的は達成したと言って良い.というわけでコード.
void all_trace( sc_trace_file *tf, std::vector<sc_object*>& children ){
for( std::vector<sc_object*>::iterator i = children.begin(); i != children.end(); ++i ){
if ( std::string(( *i )->kind()) == "sc_module" ){
sc_module* mptr = dynamic_cast<sc_module*>(*i);
std::vector<sc_object*> r_children = mptr->get_child_objects();
all_trace(tf, r_children);
}else if(
std::string(( *i )->kind()) == "sc_in"
|| std::string(( *i )->kind()) == "sc_out"
|| std::string(( *i )->kind()) == "sc_inout"
|| std::string(( *i )->kind()) == "sc_signal"
){
cout << ( *i )->name() << '\n';
}
}
}
int sc_main( int argc, char **argv ){
sc_trace_file *ScppTraceFile = sc_create_vcd_trace_file( "simple_dma" );
ScppTraceFile->set_time_unit( 1.0, SC_NS );
// ...
sim_top sim_top0( "sim_top0" );
#ifdef LIST_ALL_SIGNAL
std::vector<sc_object*> top = sc_get_top_level_objects();
all_trace( ScppTraceFile, top );
exit( 0 );
#else
#include "trace_all_signal.h"
#endif
// ...
}

最初に -DLIST_ALL_SIGNAL 付きでコンパイルして実行すると信号一覧が取得できる.それをスクリプトで適当に sc_trace() のコードに変換して trace_all_signal.h を生成する.あとは -DLIST_ALL_SIGNAL なしでコンパイル・実行すれば全信号のトレース付きで sim 実行される.
↓取得できる信号一覧
sim_top0.port_0
sim_top0.signal_0
sim_top0.signal_1
...
sim_top0.SimpleDma0.clk
sim_top0.SimpleDma0.nrst
sim_top0.SimpleDma0.RegAddr
sim_top0.SimpleDma0.RegWData
...
sim_top0.SimpleDma0.u_SimpleDmaReg(0).clk
sim_top0.SimpleDma0.u_SimpleDmaReg(0).nrst
sim_top0.SimpleDma0.u_SimpleDmaReg(0).Addr
view raw exec.log hosted with ❤ by GitHub
sc_trace( ScppTraceFile, sim_top0.port_0, "sim_top0.port_0" );
sc_trace( ScppTraceFile, sim_top0.signal_0, "sim_top0.signal_0" );
sc_trace( ScppTraceFile, sim_top0.signal_1, "sim_top0.signal_1" );
...
sc_trace( ScppTraceFile, sim_top0->SimpleDma0.clk, "sim_top0.SimpleDma0.clk" );
sc_trace( ScppTraceFile, sim_top0->SimpleDma0.nrst, "sim_top0.SimpleDma0.nrst" );
sc_trace( ScppTraceFile, sim_top0->SimpleDma0.RegAddr, "sim_top0.SimpleDma0.RegAddr" );
sc_trace( ScppTraceFile, sim_top0->SimpleDma0.RegWData, "sim_top0.SimpleDma0.RegWData" );
...
sc_trace( ScppTraceFile, sim_top0->SimpleDma0->u_SimpleDmaReg[0].clk, "sim_top0.SimpleDma0.u_SimpleDmaReg(0).clk" );
sc_trace( ScppTraceFile, sim_top0->SimpleDma0->u_SimpleDmaReg[0].nrst, "sim_top0.SimpleDma0.u_SimpleDmaReg(0).nrst" );
sc_trace( ScppTraceFile, sim_top0->SimpleDma0->u_SimpleDmaReg[0].Addr, "sim_top0.SimpleDma0.u_SimpleDmaReg(0).Addr" );
↑生成した trace_all_signal.h

前回の方法は,全モジュールに sc_trace() を埋め込むので記述が分散してそれなりに手間だが,今回の方法は 1箇所の記述で対応できるので楽といえば楽.

ただし,信号の配列の場合やメンバイニシャライザで信号名していない場合,取得した信号名はテンポラリな名前になってしまうため C++ 上の変数名と不一致を起こしコンパイルエラーになってしまう (上の例でいうと,sim_top0.signal_0 は存在せず,そのような変数名は trace_all_sighal.h から省かなければならない).

●各モジュールに sc_trace() 埋め込み
◎全信号をもれなくトレース可能
◎配列信号でも,正確な信号名を設定できる
×記述が分散する
×信号の増減は所詮手動で対応しなければならない

●全信号自動リストアップ
◎信号の増減に自動的に対応できる
◎1箇所にコードがまとまるのでメンテが楽
×2回コンパイルが必要
×name 設定していない (出来ない) 信号のトレース不可

楽さを取るか,もれなく信号をダンプするか.悩ましいところだなぁ.

2018年5月20日日曜日

ここがヘンだよ SystemC

SystemC は新規に設計された言語ではなく,C++ にライブラリを追加する形で実現されているため,いろいろと記述的にヘンであったり,煩雑なところがある.

(1) センシティビティリスト記述を,動作を記述している場所の近くに書けない (普通に書くと別ファイルになる)
(2) 信号を定義したら,その信号名を「文字列で」教えてやる必要がある.
(3) 前回の blog に書いた sc_trace() の問題.(2) と合わせると,信号の宣言,信号名の設定,トレースの設定,と同じような情報を 3箇所に書く必要がある.
(4) Verilog-2001 の always@(*) のようなセンシティビティリスト省略が出来ない
(5) Verilog-200x (いつの規格かわすれた) のインスタンス接続の省略表記「.*」が出来ない

こういうのって設計の頭使うところ以外で時間を食うことになるので,上記を改善するための SystemC preprocessor を作ってみた.

scpp.pl - SystemC preprocessor

我ながら SystemC のイライラポイントが大分改善されて満足ヽ(´ー`)ノ
下記の処理前後の diff を見てもられば,処理前の記述が大分削減されていることがわかってもらえると思う.
#include "common.h" #include "common.h"
#include "SimpleDmaReg.h" #include "SimpleDmaReg.h"
#include "SimpleDmaCore.h" #include "SimpleDmaCore.h"
template<int CH_NUM = 1> template<int CH_NUM = 1>
SC_MODULE( SimpleDma ){ SC_MODULE( SimpleDma ){
sc_in_clk clk; sc_in_clk clk;
sc_in<bool> nrst; sc_in<bool> nrst;
// register bus // register bus
sc_in<sc_uint<32>> RegAddr; sc_in<sc_uint<32>> RegAddr;
sc_in<sc_uint<32>> RegWData; sc_in<sc_uint<32>> RegWData;
sc_in<bool> RegNce; sc_in<bool> RegNce;
sc_in<bool> RegWrite; sc_in<bool> RegWrite;
sc_out<sc_uint<32>> RegRData; sc_out<sc_uint<32>> RegRData;
// SRAM bus // SRAM bus
sc_out<sc_uint<32>> SramAddr; sc_out<sc_uint<32>> SramAddr;
sc_out<sc_uint<32>> SramWData; sc_out<sc_uint<32>> SramWData;
sc_out<bool> SramNce; sc_out<bool> SramNce;
sc_out<bool> SramWrite; sc_out<bool> SramWrite;
sc_in<sc_uint<32>> SramRData; sc_in<sc_uint<32>> SramRData;
// $ScppAutoMember | // $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
SC_CTOR( SimpleDma ) : SC_CTOR( SimpleDma ) :
// $ScppInitializer | // $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
{ {
// $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] << SrcAddrC
> }
> sensitive << Done;
>
> SC_METHOD( RDataSelector );
> for( int i = 0; i < CH_NUM; ++i ) sensitive << RegRDa
>
> // $ScppEnd
/* $ScppInstance( /* $ScppInstance(
SimpleDmaCore, u_SimpleDmaCore, "SimpleDmaCore.h" SimpleDmaCore, u_SimpleDmaCore, "SimpleDmaCore.h"
"@clk|nrst|Sram.*@@", "@clk|nrst|Sram.*@@",
"///W", "///W",
) */ | ) Begin */
> u_SimpleDmaCore = new SimpleDmaCore( "u_SimpleDmaCore
> u_SimpleDmaCore->clk( clk );
> u_SimpleDmaCore->nrst( nrst );
> u_SimpleDmaCore->SrcAddr( SrcAddr );
> u_SimpleDmaCore->DstAddr( DstAddr );
> u_SimpleDmaCore->XferCnt( XferCnt );
> u_SimpleDmaCore->Run( Run );
> u_SimpleDmaCore->Done( Done );
> u_SimpleDmaCore->SramAddr( SramAddr );
> u_SimpleDmaCore->SramWData( SramWData );
> u_SimpleDmaCore->SramNce( SramNce );
> u_SimpleDmaCore->SramWrite( SramWrite );
> u_SimpleDmaCore->SramRData( SramRData );
> // $ScppEnd
/* $ScppInstance( /* $ScppInstance(
SimpleDmaReg, u_SimpleDmaReg[ CH_NUM ], "SimpleDm SimpleDmaReg, u_SimpleDmaReg[ CH_NUM ], "SimpleDm
"/clk|nrst//", "/clk|nrst//",
"/(Addr|WData|Write)/Reg$1/", "/(Addr|WData|Write)/Reg$1/",
"/(RData)/Reg$1Ch[]/W", "/(RData)/Reg$1Ch[]/W",
"//$1Ch[]/W", "//$1Ch[]/W",
) */ | ) Begin */
> for( int _i_0 = 0; _i_0 < CH_NUM; ++_i_0 ){
> u_SimpleDmaReg[_i_0] = new SimpleDmaReg(( std::st
> 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
// $ScppSigTrace | // $ScppSigTrace Begin
> #ifdef VCD_WAVE
> sc_trace( ScppTraceFile, clk, std::string( this->name
> sc_trace( ScppTraceFile, nrst, std::string( this->nam
> sc_trace( ScppTraceFile, RegAddr, std::string( this->
> sc_trace( ScppTraceFile, RegWData, std::string( this-
> sc_trace( ScppTraceFile, RegNce, std::string( this->n
> sc_trace( ScppTraceFile, RegWrite, std::string( this-
> sc_trace( ScppTraceFile, RegRData, std::string( this-
> sc_trace( ScppTraceFile, SramAddr, std::string( this-
> sc_trace( ScppTraceFile, SramWData, std::string( this
> sc_trace( ScppTraceFile, SramNce, std::string( this->
> sc_trace( ScppTraceFile, SramWrite, std::string( this
> sc_trace( ScppTraceFile, SramRData, std::string( this
> sc_trace( ScppTraceFile, SrcAddr, std::string( this->
> sc_trace( ScppTraceFile, DstAddr, std::string( this->
> sc_trace( ScppTraceFile, XferCnt, std::string( this->
> sc_trace( ScppTraceFile, Run, std::string( this->name
> sc_trace( ScppTraceFile, Done, std::string( this->nam
> #endif // VCD_WAVE
> // $ScppEnd
} }
}; };
view raw scpp_diff.cpp hosted with ❤ by GitHub

2018年5月11日金曜日

SystemC の sc_trace() をなるべく楽にする方法

SystemC で信号ダンプをするためには,自分でダンプしたい内部信号を階層含めて一個一個指定する必要があって超めんどくさい.一個一個,は他にも似たような記述があるので諦めるにしても,階層含めて,は根気良く書くのが無理なレベル.インスタンス階層ツリーを出してくれる機能とかあればいいんだけど,そういうのもなさげ.
どこのご家庭でもお困りですよね.
sc_trace( trace_f, hoge.fuga.signal_a, "hoge.fuga.signal_a" );
sc_trace( trace_f, hoge.piyo.signal_b, "hoge.piyo.signal_b" );

で,自分で試してみて,一番マシだと思った方法は,SC_CTOR() 内で sc_trace() すること.
#include <systemc.h>
#ifdef VCD_WAVE
#define TRACE( sig ) sc_trace( trace_f, sig, std::string( this->name()) + "." #sig )
extern sc_trace_file *trace_f;
#else
#define TRACE( sig )
#endif
SC_MODULE( hoge ){
sc_in_clk clk;
sc_in<bool> nrst;
sc_in<sc_uint<32>> a;
sc_in<sc_uint<32>> b;
sc_out<sc_uint<32>> c;
SC_CTOR( hoge )
//★不要 : clk( "clk" ), nrst( "nrst" ), a( "a" ), b( "b" ), c( "c" )
{
...
TRACE( clk );
TRACE( nrst );
TRACE( a );
TRACE( b );
TRACE( c );
}
...
};
view raw hoge.cpp hosted with ❤ by GitHub

this->name() で,インスタンス化されたときのこのモジュール名のフルパス名が取得できるので,sc_trace() の記述自体にはフルパスを記述する必要がなくなる.
この程度の記述であれば,ポート宣言のコピペ & 置換レベルで対応できる.
めんどくさいメンバイニシャライザでの信号名設定 (上記の★不要 の箇所) も不要.

なお trace_f は このモジュールがインスタンス化される前に sc_create_vcd_trace_file() しておく必要がある.

Cadence だとナントカ wizard でこの辺を自動化してくれるんだけど,パンピーには使えないしね...