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

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

RGB2YUV回路(BCA)

前回TLMで作成したRGB2YUV回路をBCAに変更する。

変更概要

  • いくつかの記述はSystemC2.3.3では使用不可になっている。これはCQ出版の正誤表に記載有
  • tbとdutで画像データをやり取りするチャネルについて、TLMではsc_fifoを使用していたがBCAではsc_signalでデータ部とハンドシェイク信号(ready,valid)を明示的に使用する
  • リセット信号の追加
    • コンストラクタのプロセス宣言部分にreset_signal_is(信号名, 極性);で同期リセット信号を明示
    • これの有無による違いがよくわからない。高位合成向け記述?
    • async_reset_signal_is(信号名, 極性); で非同期リセット
  • dutの端子(Bus I/F)部分はサイクル精度で記述
  • VCDダンプ記述の追加
    • TLMに対して、BCAと同様のVCDダンプ記述を入れてみたところ、エラーでsimが実行できなかった(コンパイルはできた)。別途検討
$ ./run.x 

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

Error: (E519) wait() is only allowed in SC_THREADs and SC_CTHREADs: 
        in SC_METHODs use next_trigger() instead
In file: ../../../src/sysc/kernel/sc_wait.cpp:94

コード変更箇所

rgb2yuv.h

  • TLMではsc_fifo_in, sc_fifo_outを使用して、データ入出力ポートを定義していたが、sc_in, sc_outに変更。ハンドシェイク用のready, validを明示的に追加
  • 初期化関数、データ入出力関数を追加
  • SC_THREADから合成可能なSC_CTHREADに変更。リセット信号を関連付け
    • reset信号記述については、後ほど確認する
SC_MODULE(rgb2yuv){
    //ports
    sc_in  < bool >   clock;
    sc_in  < bool >   reset; // reset信号を追加

    sc_in  < rgb8_t > offset_in; // チャネルをsc_fifo_in -> sc_inに変更
    sc_in  < rgb8_t > rgb_in; // チャネルをsc_fifo_in -> sc_inに変更
    sc_out < bool >   in_ready; // ハンドシェイク信号を追加
    sc_in  < bool >   in_valid; // ハンドシェイク信号を追加

    sc_out < yuv8_t > yuv_out; // チャネルをsc_fifo_out -> sc_outに変更
    sc_in  < bool >   out_ready; // ハンドシェイク信号を追加
    sc_out < bool >   out_valid; // ハンドシェイク信号を追加

    // member functions
    void   initialize(); // 初期化関数を追加
    rgb8_t get(); // データ入力関数を追加
    void   put(yuv8_t &yuv); // データ出力関数を追加
    void   thread0();
    yuv8_t calc_rgb2yuv(rgb8_t& rgb);

    // member variables
    rgb8_t rgb;
    yuv8_t yuv;

    // constructor
    SC_CTOR(rgb2yuv) {
        SC_CTHREAD(thread0, clock.pos()); // SC_THREAD -> SC_CTHREADに変更
//        watching(reset.delayed() == 0); // SystemC2.3.3では使用不可
        reset_signal_is(reset, false); // reset定義
    }
};

rgb2yuv.cpp

  • thread0()にwait()を挿入して、1cycle目でinitialize()をコール、2cycle目でoffsetの読み込み、3cycle目で変換処理をするようにしている 
    • while文の中にはwait()がないので、1cycleで処理しようとする。ただし、get(), put()関数内のハンドシェイク待ちでクロックを待つことになる
    • TLMではclockの概念なく、1cycleでoffsetの読み込みと変換処理が実行されていると思われる
  • dutから出力されるハンドシェイク信号(in_ready, out_valid)を0初期化するintialize()関数を新規追加
  • ハンドシェイク信号を用いたデータリード関数get()を新規追加
    • readyアサート→valid待ち→readyネゲート+データリード
  • ハンドシェイク信号を用いたデータライト関数put()を新規追加
    • ready待ち→validアサート+データライト→validネゲート
  • 変換関数本体calc_rgb2yuv()はTLMと同じ
void rgb2yuv::thread0()
{
    // internal variables
    rgb8_t offset;

    // call reset function
    initialize(); // 初期化関数の呼び出しを追加
    wait(); // clock edgeを待つ

    offset = offset_in.read();
    wait(); // clock edgeを待つ

    while(true) {
        //  read data
        rgb = get(); // get関数を使用してデータをリードするように変更
        // add offset
        rgb = offset + rgb;
        // translation
        yuv = calc_rgb2yuv(rgb);
        // output result
        put(yuv); // put関数を使用してデータをライトするように変更
    }
}

// reset function
void rgb2yuv::initialize() // in_ready, out_validを0初期化する関数を新規追加
{
    in_ready = 0;
    out_valid = 0;
}

// get data function
rgb8_t rgb2yuv::get() // ハンドシェイク信号を用いたデータリード関数を新規追加
{
    in_ready = 1; // readyをアサート
//    wait_until(in_valid.delayed() == 1); // SystemC2.3.3では使用不可
    do { wait(); } while ( !in_valid.read() ); // tbからのvalid信号がHになるまで待つ
    in_ready = 0; // readyをネゲート
    
    return rgb_in.read(); // データリードした結果を戻り値として返却
}

// put data function
void rgb2yuv::put(yuv8_t& yuv) // ハンドシェイク信号を用いたデータライト関数を新規追加
{
//    wait_until(out_ready.delayed() == 1); // SystemC2.3.3では使用不可
    do { wait(); } while ( !out_ready.read() ); // readyがHになるまで待つ
    yuv_out.write(yuv); // データを出力
    out_valid = 1; // validをアサート
    wait(); // 1cycle待ち
    out_valid = 0; // validネゲート
}

yuv8_t rgb2yuv::calc_rgb2yuv(rgb8_t& rgb)
{
    yuv8_t yuv; // local variable

    yuv.y = ( rgb.r*77    + rgb.b*151 + rgb.b*29 ) / 256;
    yuv.u = ( rgb.r*(-43) - rgb.b*85  + rgb.b*128) / 256 + 128;
    yuv.v = ( rgb.r*128   - rgb.b*107 - rgb.b*21 ) / 256 + 128;

    // output internal variables
    cout << "rgb = (" << rgb << "), yuv = (" << yuv << ")" << endl;
    return yuv;
}

tb.h

  • ポート宣言をrgb2yuvと対になるように変更
SC_MODULE(tb) {
    // ports
    sc_in  < bool > clock;
    sc_out < bool > reset; // reset信号を追加
    
    sc_out < rgb8_t > offset_out; // チャネルをsc_fifo_out -> sc_outに変更
    sc_out < rgb8_t > rgb_out; // チャネルをsc_fifo_out -> sc_outに変更
    sc_in  < bool >   out_ready; // ハンドシェイク信号を追加
    sc_out < bool >   out_valid; // ハンドシェイク信号を追加

    sc_in  < yuv8_t > yuv_in; // チャネルをsc_fifo_in -> sc_inに変更
    sc_out < bool >   in_ready; // ハンドシェイク信号を追加
    sc_in  < bool >   in_valid; // ハンドシェイク信号を追加

    // file pointers
    FILE *fi;
    FILE *fo;

    // member functions
    void source();
    void sink();

    // constructor
    SC_CTOR(tb) {
        // open and check file
        if ((fi = fopen(input_file, "r")) == NULL) {
            fprintf(stderr, "Can't open file : %s\n", input_file);
            sc_stop();
        }
        if ((fo = fopen(output_file, "w")) == NULL) {
            fprintf(stderr, "Can't open file : %s\n", output_file);
            sc_stop();
        }
    
        // processes
        SC_THREAD(source);
        sensitive << clock.pos();
        SC_THREAD(sink);
        sensitive << clock.pos();
    }

    // deconstructor
    ~tb() {
        fclose(fi);
        fclose(fo);
    }
};

tb.cpp

  • source()関数
    • 初期化処理追加
      • 2cycle期間resetアサート・validを0初期化、3cycle目でリセット解除
    • ハンドシェイク信号を使用したデータ出力
      • ready待ち→validアサート+データ出力→validネゲート
  • sink()関数
    • ハンドシェイク信号を使用したデータ読み込み
      • readyアサート→valid待ち→データ読み込み+readyネゲート
        • 実際にはreadyネゲート→次のreadyアサートの間でwait()がないため、readyは常時アサートされそう
void tb::source()
{
    int data_r, data_g, data_b;
    rgb8_t rgb;

    // initialize
    reset = 0; // resetアサート
    out_valid = 0; // validを0初期化
    wait(2); // 2cycle待ち

    reset = 1; // reset解除
    // read from file
    fscanf(fi, "%d", &data_r);
    fscanf(fi, "%d", &data_g);
    fscanf(fi, "%d", &data_b);

    // assign to struct
    rgb.write(data_r, data_g, data_b);

    // output offset
    offset_out.write(rgb);
    wait();

    // output RGB data
    for(int i=0; i<NUM; i++) {
        fscanf(fi, "%d", &data_r);
        fscanf(fi, "%d", &data_g);
        fscanf(fi, "%d", &data_b);
    
        rgb.write(data_r, data_g, data_b);
        
        // output data with handshaking
//        wait_until(out_ready.delayed() == 1); // SystemC2.3.3では使用不可
        do { wait(); } while ( !out_ready.read() ); // ready待ち
        rgb_out.write(rgb); // rgbデータ出力
        out_valid.write(1); // validアサート
        wait(); // 1cycle待ち
        out_valid.write(0); // validネゲート
    }
}

void tb::sink()
{
    yuv8_t yuv;
    fprintf(fo, " y  u  v\n");

    for(int i=0; i<NUM; i++) {
        in_ready = 1; // readyアサート
//        wait_until(in_valid.delayed() == 1); // SystemC2.3.3では使用不可
        do { wait(); } while ( !in_valid.read() ); // valid待ち
        // read output data
        yuv = yuv_in.read(); // データ読み込み

        // output read data to file
        fprintf(fo,"%3d  %3d  %3d\n", (int)yuv.y, (int)yuv.u, (int)yuv.v);
        in_ready = 0; // readyネゲート
    }
    sc_stop();
}

main.cpp

  • TLMではsc_fifo, を使用して、接続信号を定義していたが、sc_signalに変更。ハンドシェイク用のready, validを明示的に追加
  • VCDダンプ記述を追加
int sc_main( int argc, char *argv[])
{
    // clock
    sc_clock clock("clock", 10.0, SC_NS);

    // conecting wires
    sc_signal < bool >   reset; // reset信号新規追加
    sc_signal < bool >   rgb_ready; // ハンドシェイク信号を新規追加
    sc_signal < bool >   rgb_valid; // ハンドシェイク信号を新規追加
    sc_signal < bool >   yuv_ready; // ハンドシェイク信号を新規追加
    sc_signal < bool >   yuv_valid; // ハンドシェイク信号を新規追加

    sc_signal < rgb8_t > offset; // sc_fifo -> sc_signalに変更
    sc_signal < rgb8_t > rgb; // sc_fifo -> sc_signalに変更
    sc_signal < yuv8_t > yuv; // sc_fifo -> sc_signalに変更

    // instanciate
    rgb2yuv i0("rgb2yuv");
    tb t0("tb");

    // conectivity
    i0.clock(clock);
    i0.reset(reset);
    i0.offset_in(offset);
    i0.rgb_in(rgb);
    i0.in_ready(rgb_ready);
    i0.in_valid(rgb_valid);
    i0.yuv_out(yuv);
    i0.out_ready(yuv_ready);
    i0.out_valid(yuv_valid);

    t0.clock(clock);
    t0.reset(reset);
    t0.offset_out(offset);
    t0.rgb_out(rgb);
    t0.out_ready(rgb_ready);
    t0.out_valid(rgb_valid);
    t0.yuv_in(yuv);
    t0.in_ready(yuv_ready);
    t0.in_valid(yuv_valid);

    // dump VCD
    sc_trace_file *fp;
    fp = sc_create_vcd_trace_file("rgb2yuv");
    sc_trace(fp, clock,     "clock");
    sc_trace(fp, reset,     "reset");
    sc_trace(fp, offset,    "offset");
    sc_trace(fp, rgb_ready, "rgb_ready");
    sc_trace(fp, rgb_valid, "rgb_valid");
    sc_trace(fp, rgb,       "rgb");
    sc_trace(fp, yuv_ready, "yuv_ready");
    sc_trace(fp, yuv_valid, "yuv_valid");
    sc_trace(fp, yuv,       "yuv");

    // start sim
    sc_start();
    return 0;
}