チラシの裏は意外と白くない

最近忘れっぽくなったので調べたことをチラシの裏に書きます

SystemCで4bitカウンタを作ってみた①

SystemCに同梱されているexampleを勉強するつもりだったけど、結構複雑で言語のお作法がわからないときつい感じだったので、簡単なカウンタ回路をフルスクラッチで作ってみた。これで基本的な構成を理解したい。環境構築にあたって、以下のサイトを大いに参考にさせてもらった…というかほぼ一緒。 w.atwiki.jp

準備したファイルの概要

ファイル名 概要
main.cpp テストベンチトップ。4bitカウンタのインスタンス、テストベンチの記述
counter.h 4bitカウンタのモジュール定義。どんなポート・変数・プロセスを持っているかを記載
counter.cpp counter.hで宣言したプロセスの中身を記載。今回はコンストラクタとカウンタ部分の2つ

counter.h

  • Counter4というモジュール
  • 入力はclk、出力はout。outは符号なし4bitで、カウンタ値を出力させる
  • コンストラクタは2種類の書き方ができる
#ifndef __COUNTER_H__
#define __COUNTER_H__

#include <systemc.h>

SC_MODULE( Counter4 )
{
    // ports
    sc_in_clk             clk;
    sc_out < sc_uint<4> > out;

    // signals

    // variables

    // process functions
    void main_thread();

    // functions
    // constructor
#if 0
    SC_HAS_PROCESS( Counter4 );
    Counter4( sc_module_name );
#else
    SC_CTOR( Counter4 );
#endif
};

#endif // __COUNTER_H__

counter.cpp

  • コンストラクタの中身で、プロセスを定義
    • SC_THREADでmain_thread()関数を呼び出す
    • Verilogのalwaysのイメージ
    • sim中で1度だけ呼び出される。そのため無限ループでずっと回しておくのが一般的
    • wait()を入れられる
    • always文ということはsensitivityリストが必要なはずだが、本例では記載がない。これは後で確認する
  • main_thread()の実体
    • clk信号の立ち上がりイベントを待つ
      • 上記のsensitivityの話のからみで、ここでクロック同期を実現している?
    • あとはただカウントアップして、カウンタ値が15になったら0にクリアする
#include "counter.h"

// constructor
Counter4::Counter4( sc_module_name name )
    : sc_module( name )
    , clk( "clk" )
    , out( "out" )
{
    // process setting
    SC_THREAD( main_thread );
}

void Counter4::main_thread()
{
    sc_uint<4> cnt = 0;
    out.write( 0 );

    while (true)
    {
        wait (clk.posedge_event() );
        if(cnt == 0xf){
            cnt = 0;
        } else {
            cnt++;
        }
        out.write( cnt );
    }
}

main.cpp

#include <systemc.h>
#include "counter.h"

int sc_main( int argc, char* argv[] )
{
    Counter4 *counter4_i;

    // signals
    sc_clock                 clk( "clk", 10.0, SC_NS );
    sc_signal< sc_uint <4> > out;

    // module instance & binding
    counter4_i = new Counter4( "Counter4" );
    sc_assert( counter4_i );
    counter4_i->clk( clk );
    counter4_i->out( out );

    // vcd dump
    sc_trace_file *trace_f;
    trace_f = sc_create_vcd_trace_file( "systemc" ); // 出力ファイル名を指定する
    sc_assert( trace_f );
    trace_f->set_time_unit( 1.0, SC_NS );
 
    sc_trace( trace_f, clk, "clk" );
    sc_trace( trace_f, out, "out" );

    // initalize
    sc_start( SC_ZERO_TIME );
    cout << sc_time_stamp() << " : " << "out = " << out.read() << endl;

    // simulation
    sc_start( 10.0, SC_NS );
    cout << sc_time_stamp() << " : " << "out = " << out.read() << endl;

    sc_start( 10.0, SC_NS );
    cout << sc_time_stamp() << " : " << "out = " << out.read() << endl;

    # 200nsまでモニタ。ここでは省略。

    //
    delete counter4_i;
    sc_close_vcd_trace_file( trace_f );

    return 0;

}

実行結果

実行結果は以下。10nsごとにout値をモニタしている。ちゃんと15→0にリセットされているように見える。 とりあえず超簡単なモジュールとテストベンチを作って、理解は進んだ。不明な点について、デザインを変えつつもう少し調べてみる。

$ ./run.x 

        SystemC 2.3.3-Accellera --- Mar  8 2020 14:00:56
        Copyright (c) 1996-2018 by all Contributors,
        ALL RIGHTS RESERVED

Info: (I703) tracing timescale unit set: 1 ns (systemc.vcd)
0 s : out = 0
10 ns : out = 1
20 ns : out = 2
30 ns : out = 3
40 ns : out = 4
50 ns : out = 5
60 ns : out = 6
70 ns : out = 7
80 ns : out = 8
90 ns : out = 9
100 ns : out = 10
110 ns : out = 11
120 ns : out = 12
130 ns : out = 13
140 ns : out = 14
150 ns : out = 15
160 ns : out = 0
170 ns : out = 1
180 ns : out = 2
190 ns : out = 3
200 ns : out = 4
210 ns : out = 5

宿題事項

  • コンストラクタの記述に関して
  • 型について調べる。sc_clockとかsc_in_clkとか。
  • SC_THREADにsensitivityがない理由は?
  • sc_assertを調べる