SystemC のビルド
SystemC はハードウェアを C++ で実装したもので、ソフトウェアロジックとの高い親和性が期待できるものと解釈している。SystemC 自体はライブラリでスタティックライブラリとユーザーが書いた C++ のコードをビルドして、実行バイナリからシミュレーションを行える。(PLD への論理合成はどうやるのか不明)
SystemC はソースが配布されているのでそれをビルドする必要がある。2012年7月に最新版である 2.3.0 が5年ぶりに release されているのでこれを使う。やり方が説明されている 2.2.0 にしたかったが configure が通らなかった。
version 2.3.0 から cygwin と mingw もちゃんとビルドできるようになっているので ./configure; make するだけでビルドが通った。
o Windows XP Prof. SP3 (Cygwin 1.5.25) with GNU C++ compiler version gcc-4.3.2 o Windows XP Prof. SP3 (Msys 1.0.16) with MinGW32 GNU C++ compiler version gcc-4.5.2 through gcc-4.6.2
今回 SystemC を選んだ理由はビデオコントローラのシミュレーションと画像表示を1つの exe ファイルで完結できる点や、ソフト側での ROM/RAM イメージの高速処理を期待している。
main, sc_main の扱い
SystemC は main() -> ライブラリの初期化 -> sc_main() という流れで実行するのだが、 GUI のウィンドウからシミュレータを呼び出したいので、 main を取られると不便。
これは systemc-2.3.0/src/sysc/kernel/sc_main.cpp に記載されている。
int main( int argc, char* argv[] ) { return sc_core::sc_elab_and_sim( argc, argv ); }
というわけでなので sc_main.cpp をコンパイル,ビルドしないように Makefile.am の CXX_FILES から sc_main.cpp を取り除く。systemC を動かす場合は記載通り sc_core::sc_elab_and_sim() を呼び出せばよい。
ソースを書いてみる
ROM.RAMイメージのバイナリファイルを読み込んで RAM に展開してくれればよいのでそれっぽいソースを書いてみた。
SC_MODULE(Ram) { sc_inm_clk, m_we; sc_in m_a; sc_in m_d; sc_out m_q; sc_signal *const data; void thread() { while(true){ wait(m_clk.posedge_event()); m_q.write(data[m_a.read() & 0x1fff]); if(m_we.read() == 1){ data[m_a] = m_d; } } } SC_HAS_PROCESS(Ram); Ram(sc_module_name n) : sc_module(n), data(new sc_signal [0x2000]), m_clk("clk"), m_a("a"), m_d("d"), m_q("q") { for(int i = 0; i < 0x2000; i++){ data[i] = 0x44; } SC_THREAD(thread); } ~Ram() { delete [] data; } };
RAM の部分は sc_signal
thread は verilog でいう always を意識した書き方にした。無限ループに入る前には RAM データを初期化できるのでここにバイナリデータを流し込むメソッドをいれればよいことがわかった。
SC_MODULE(RamController) { sc_inm_clk; sc_out m_q; sc_signal m_ram_a; sc_signal m_ram_we; sc_signal m_ram_d, m_ram_q; Ram m_ram; void thread() { while(true){ wait(m_clk.posedge_event()); m_ram_a = m_ram_a.read() + 1; m_q.write(m_ram.m_q.read()); } } SC_HAS_PROCESS(RamController); RamController(sc_module_name n) : sc_module(n), m_clk("clk"), m_q("q"), m_ram("RAM") { m_ram_a = 0; m_ram_d = 0xaa; m_ram_we = 0; m_ram.m_clk(m_clk); m_ram.m_we(m_ram_we); m_ram.m_d(m_ram_d); m_ram.m_a(m_ram_a); m_ram.m_q(m_ram_q); SC_THREAD(thread); } };
RAM のアドレスをインクリメントしてデータの変化を見るための上位モジュール。
ここではまったのは下位モジュールに渡す配線をクラス内部の変数にしないといけないこと。最初は thread() やコンストラクタのなかに変数宣言をいれて渡していたのだが、ライブラリ側から配線がないと文句を言ってきたり、それを言う前に落ちたりした。
もう1つ注意をしたい点は m_ram_a のインクリメント処理である。 テンプレート sc_signal には +=, ++ 演算子は定義されていないのでベーシックな書き方をしないといけない。さらにいうと値の読み出しは .read() を使った方がよいらしい(やらなくてもできるけど)。 = 演算子は .write(val) ということらしいが、これも明示した方がよいという記述も見かけた。