SystemCでFIFOテスト

# はじめに
SystemCがいつまでたっても書けないのでFIFO設計してみました。Qiitaの投稿理由は9割自分用メモです。おそらくほとんどの人に役に立たない内容です。FIFOが論理合成可能なのか未検証です。初心者なので間違いを見つけ次第指摘していただけると幸いです。

# 仕様
今回はSystemCで適当に設計してみたかっただけなのでFIFOの仕様は以下を想定しました。
※使えるレベルで設計していません

- 立上がり同期リセット
- データ入力8bit
- Enable書き込み読み出し
- 深さ16

## 構成
![main3.png](https://qiita-image-store.s3.amazonaws.com/0/97833/8017a484-f0de-bde8-fce5-09be475c9b8d.png)
モジュール構成図
### 構成メモ
- main：SystemCのmain関数
    - Systemインスタンス呼び出し
        - test_pattern：テストパターンモジュール
        - my_fifo：FIFOモジュール(**合成予定**)
    - Dump：波形ダンプ記述



## 出力
ダンプした結果を以下に記載します。読み出し信号から１サイクル後にデータが出力されているのが確認出来ました。

![スクリーンショット 2018-09-08 14.09.32.png](https://qiita-image-store.s3.amazonaws.com/0/97833/f4df3747-c8c9-72c5-7ce9-03974638a6a4.png)


# FIFOモジュール

```c++:my_fifo.h
#pragma once
#include <systemc.h>

SC_MODULE(my_fifo)
{
	//ポート宣言
	sc_in<bool> clk;
	sc_in<bool> rst;
	sc_in<bool> en_i;
	sc_in<sc_uint<4> > data_i;
	sc_in<bool> en_o;
	sc_out<sc_uint<4> > data_o;
	sc_out<bool> empty_o;
	sc_out<bool> full_o;

	//メンバ変数
	sc_signal<sc_uint<4> > data[16];
	uint8_t addr;

	//メンバ関数
	void reset();
	void write_thread();
	void read_thread();
	void status_thread();

	SC_CTOR(my_fifo)
	{
		SC_CTHREAD(write_thread, clk.pos());
		reset_signal_is(rst, true);
		SC_CTHREAD(read_thread, clk.pos());
		reset_signal_is(rst, true);
		SC_CTHREAD(status_thread, clk.pos());
		reset_signal_is(rst, true);
	}
};
```
動作は3つに分けて適当に記述しました。

- リセット
- 書き込み
- 読み出し


```c++:my_fifo.cpp
#include "my_fifo.h"

void my_fifo::reset()
{
	//アドレス初期化
	addr = 0;

	//メモリ初期化
	for (int i = 0; i < 16; i++) {
		data[i] = 0;
	}
	wait();
}

void my_fifo::write_thread()
{
	reset();
	while (true) {
		if (en_i.read() == 1) {
			data[addr] = data_i.read();
			if(addr != 15) addr++;
		}
		if (en_o.read() == 1) {
			for (int i = 0; i < 15; i++) {
				data[i] = data[i + 1];
			}
			if(addr != 0) addr--;
		}
		wait();
	}
}

void my_fifo::read_thread()
{
	//ポート初期化
	data_o.write(15);
	wait();
	while (true) {
		data_o.write(data[0]);
		wait();
	}
}

void my_fifo::status_thread()
{
	//ポート初期化
	empty_o.write(1);
	full_o.write(0);
	wait();
	while (true) {
		if (addr == 0) {
			empty_o.write(1);
		}
		else {
			empty_o.write(0);
		}

		if (addr == 15) {
			full_o.write(1);
		}
		else {
			full_o.write(0);
		}

		wait();
	}
}
```

# テストベンチ

Systemでモジュールの結線を行っています。それをmain関数でインスタンス化し、階層アクセスして必要なポートをダンプします。クロックは特殊な書き方をしないとmain関数以外で記述出来ない仕様っぽいのでSystemはclkとrstのみコントロール出来るようにポート宣言しています。

```c++:Source.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <systemc.h>
#include "testbench.h"

//クロックステップ[ns]
#define CLK_STEP 10

//TOPインスタンス
static System * m_system = NULL;

int sc_main(int argc, char** argv)
{
	//クロックリセット
	sc_clock clk("clk", CLK_STEP);
	sc_signal<bool> rst;

	//TOP接続
	m_system = new System("system");
	m_system->clk(clk);
	m_system->rst(rst);

	//波形ダンプ
	sc_trace_file *trace_f;
	trace_f = sc_create_vcd_trace_file("system");
	sc_trace(trace_f, clk, "clk");
	sc_trace(trace_f, rst, "rst");
	sc_trace(trace_f, m_system->data_i, "data_i");
	sc_trace(trace_f, m_system->en_i, "en_i_i");
	sc_trace(trace_f, m_system->data_o, "data_o");
	sc_trace(trace_f, m_system->en_o, "en_i_o");
	sc_trace(trace_f, m_system->empty_o, "empty_o");
	sc_trace(trace_f, m_system->full_o, "full_o");

	//テストベンチ
	rst = 1;
	sc_start(CLK_STEP * 1, SC_NS);
	rst = 0;
	sc_start(CLK_STEP * 1000, SC_NS);
	//テスト終了
	sc_close_vcd_trace_file(trace_f);
	cout << endl;
	cout << "----------------------------------------" << endl;
	cout << "finish sim!!" << endl;
	cout << "----------------------------------------" << endl;
	cout << endl;

	return 0;
}
```

```c++:testbench.h
#pragma once
#include <systemc.h>
#include <fstream>
#include "test_pattern.h"
#include "my_fifo.h"
using namespace std;

SC_MODULE(System)
{
	//ポート宣言
	sc_in<bool> clk; 
	sc_in<bool> rst;

	//メンバ変数
	sc_signal<bool> en_i;
	sc_signal<sc_uint<4> > data_i;
	sc_signal<bool> en_o;
	sc_signal<sc_uint<4> >data_o;
	sc_signal<bool> empty_o;
	sc_signal<bool> full_o;

	//モジュールインスタンス
	test_pattern pattern0;
	my_fifo fifo0;

	SC_CTOR(System)
		: pattern0("pattern0")
		, fifo0("fifo0")
	{
		//インスタンス(pattern0)
		pattern0.clk(clk);
		pattern0.rst(rst);
		pattern0.en_i(en_i);
		pattern0.data_i(data_i);
		pattern0.en_o(en_o);
		pattern0.data_o(data_o);
		//インスタンス(fifo0)
		fifo0.clk(clk);
		fifo0.rst(rst);
		fifo0.en_i(en_i);
		fifo0.data_i(data_i);
		fifo0.en_o(en_o);
		fifo0.data_o(data_o);
		fifo0.empty_o(empty_o);
		fifo0.full_o(full_o);
	}
};
```

# テストパターンモジュール

テストパターンを出力するモジュールも分かりやすいように分離して記述しました。

```c++:test_pattern.h
#pragma once
#include <systemc.h>

SC_MODULE(test_pattern)
{
	//ポート宣言
	sc_in<bool> clk;
	sc_in<bool> rst;
	sc_out<bool> en_i;
	sc_out<sc_uint<4> > data_i;
	sc_out<bool> en_o;
	sc_in<sc_uint<4> >data_o;

	//メンバ関数
	void pattern_proc();
	void dump_proc();

	SC_CTOR(test_pattern)
	{
		SC_CTHREAD(pattern_proc, clk.pos());
	}
};
```

```c++:test_pattern.cpp
#include "test_pattern.h"

void test_pattern::dump_proc()
{
	const std::string output = "output.txt";
	std::ofstream ofs(output, std::ios::out);

	for (int i = 0; i < 30; i++) {
		en_o.write(1);
		ofs << data_o.read() << endl;
		wait();
	}
	en_o.write(1);
}

void test_pattern::pattern_proc()
{
	wait();
	for (int i = 0; i < 16; i++) {
		en_i.write(1);
		data_i.write(i);
		wait();
	}
	en_i.write(0);
	wait();

	dump_proc();

	while (true) {
		wait();
	}
}
```

# おわりに
検索してもSystemCの書き方の参考サイトがなかなか出てこなかったのですが、[忘れん坊将軍の備忘録：SystemC](http://shusaku721-bibou6.seesaa.net/tag/articles/SystemC)が参考になりました。今回はシミュレーションのみで動作確認をしたので、後日Vivado HLSで論理合成を行い動作確認していきたいと思います。