GAIO CLUB

2005年02月02日

SystemCによる組み込みシステム設計入門 C++の機能を駆使してHWモデルを定義する手法

GAIO CLUB 特集
GAIO CLUB【2005/2月号】
SystemCによる組み込みシステム設計入門
C++の機能を駆使してHWモデルを定義する手法とは
~ これからの設計手法「HW/SW同時設計」を考える ~
本特集では、システムLSI、SoC(システムオンチップ)を採用する組み込みシステム設計の新しい手法「HW/SW同時設計」について取り上げます。現在の組み込み開発では、製品の要求仕様に従い、実装設計前の段階で、ハード・ソフトを分離して個別に設計仕様が作成されるのが一般的です。設計方法に関しては、ソフト部分はC/C++言語によりソフトロジックが設計されており、LSI部分に関しては、Verilog-HDLやVHDLなどのHDL(ハードウエア記述言語)により設計されており、各々の設計方法は全く別のものです。

従来の組み込み機器では、マイコン、ASIC、アナログ部品など、個別の機能部品を組み合わせて構成されています。この場合は、各部品に対する要求仕様が明確に切り分け可能な場合が多く、マイコンソフト、ASIC設計は個別に行われる方が自然で効率的でした。
組み込みシステム機能定義の変化
しかしながら、最近の組み込み機器の傾向では、マイコン、DSPなど複数のプロセッサや、グラフィクス処理などの特定用途の処理回路、メモリなどを1つのチップに搭載し、SoCを開発する例が増えています。この様な例では、各機能ブロックの組み合わせで、システムの機能が決定される場合が多く、マイコンやDSPに実装されるソフトウエアと、HWの処理回路がお互いに影響し合う複雑な構成になっています。

そのため、設計の最初から、HW部分とマイコン/DSPのSWを分けて開発すること自体が難しく、設計の手法を変えざるを得なくなっています。

システムレベル設計

そこで、HW/SWの切り分けを後にして、まずはシステム全体を機能定義する手法が考えられました。ここでは、具体的な実装HWを意識せず、機能そのものをより「抽象度」の高い表現で記述します。これを「システムレベル設計」と呼んでいます。この定義に使用する設計言語としては、まだ確立された物はなく、候補としてJava、C++などが考えられますが、自由度やコンパイル後の動作検証速度の点でC++を用いる例が増えています。

一旦システム全体を定義した後で、プロセッサの性能や、想定するチップ単価などを考慮しながら、実装のためのHW/SWの切り分けが行われます。

例えば、MPEG動画の機能を実装する場合、一旦システム全体をC++で記述し、MPEG処理部分をMPUのソフトで実装するか、DSPのファームで実装するか、またはMPEG処理専用の回路をHWで実装するのかを、実装時に後で決定できることになります。

システムレベル検証

システム全体を一旦C++で定義することは、もう1つ大きなメリットを生みます。設計したシステムをC++コンパイラでコンパイルすることで、システムの動作を実際に確かめることができます。実装に入る前の段階で、設計したシステム全体の論理動作が正しいかどうかを検証することができるのです。(従来は、実際のHWが完成した後で、SWを組み合わせた総合試験でしか、システムの動作を確認する方法はありませんでした。)

一旦論理動作が確認されれば、後はこの論理を正しく実装すれば良いことになり、設計フェースの切り分けや、より有利な実装方法を選択できることになります。

C++のコンパイラは、Javaのインタプリタと異なり、Windowsなどのホストプラットフォームのネイティブコードを作成できるため、動作シミュレーションが高速に行えます。大規模なシステムの設計には、シミュレーションの高速動作は、重要な条件となります。
HW/SW同時設計の一般的な開発プロセス

SystemCとは

  • SystemCとはC/C++の機能を使って、HWを記述できるようにしたシステム設計言語です。SystemCは、世界的なワーキンググループにより、その仕様が決定されています。その歴史などについては、本稿では省略致します。WEBサイトなどを参照して下さい。(OpenSystemC Initiative : www.systemc.org
  • SystemC
  • SystemCは、標準的なC/C++をベースにして、HWをモデリングするためのデータタイプや、機能をブロック化するためのクラスライブラリが追加されて構成されています。標準的なC/C++が使用されていると言うことは、開発プラットフォームにかかわらず、そのコードをコンパイルすることで、実行可能なコードが作成できると言うことです。例えばWindwos環境に一般的なMicrosoft社の開発環境(Visual Studio.NET C/C++)を使用すれば、SystemCシミュレータを、Windows上に構築することが容易に可能です。

  • SystemC言語アーキテクチャ
SystemCは、C++言語の機能、「クラス」「コンストラクタ」「継承」「テンプレート」などを積極的に利用しています。SystemCでシステム記述を行うためには、C++言語の知識が必要です。

システムの抽象度とは

SystemCの特徴の1つは、システムを定義する際の「抽象度」を柔軟に使い分けることが可能であることです。システムをHWに実装する場合、実際には「応答遅延」「クロックへの同期」などの時間概念や、論理信号線の状態(Hレベル、Lレベル、不定、ハイインピーダンス)など、個々のHW設計に依存する部分があります。「抽象度」とは、これらの実装の要素をどの程度「詳細に」定義するかと言うことです。

最も「抽象度」が高いと言うことは、機能の入力条件と出力結果のみを定義しているもので、処理にかかる時間や、実際のHW配線などの事情は含みません。この高い「抽象度」でシステムを定義できることがSystemCのメリットにもなります。例えば、定義したシステムの可搬性を高めたり、動作確認のためのシミュレーションを高速に行えたりします。

SystemCには以下のような「抽象度レベル」が設定されています。これらの複数の抽象度レベルのモデルを単一の言語で記述及び実行することができます。

レジスタ転送レベル

現在LSI設計等で幅広く利用されているRTL(レジスタ転送レベル)モデルと等価なモデルです。これは、LSIの内部動作を、レジスタ(フリップフロップやラッチ)と組み合わせロジックで表現する設計レベルを指します。機能記述も各サイクルの精度を持ち、ブロック間の接続も実際のピン割付と等価な、ハードウェア・レベルの詳細記述がされています。

トランザクションレベル

RTLレベルはハードウエアを詳細に記述でき、論理合成ツールで実際のチップが作れますが、その反面、設計変更による手戻りが大きい、既存の設計資産の再利用が困難、シミュレーション時間が実用に耐えない程遅い、などの問題があります。これらを解決するRTLよりも抽象度の高い設計レベルとして、「トランザクションレベル」が注目されています。

このレベルは、一言で言えば、「信号レベル、またはクロック・レベルの詳細情報を省略して記述する設計レベル」となります。例えば、バスによるデータ転送をモデリングする場合、RTLレベルでは、バスに規定されたプロトコルに従って、データ転送以外の要素、例えばCPUの命令・データフェッチ・実行・キャッシュライトバックなど、全てを記述した上で、データ転送部分の検証を行わなければなりません。設計の早い段階で、まずデータ転送のスループットに着目して、動作検証を行いたい場合など、この様なRTLレベルでの検証は現実的ではありません。

トランザクションレベルのモデリングでは、データ転送の開始の時刻と終了の時刻、及び、そのデータ転送のアトリビュート(アドレス、データの値等)の情報を扱い、実行中の細かい時間情報(クロックレベル)は無視して扱います。このように抽象度を高めることで、十分なシミュレーションパフォーマンスを得ることができます。

アンタイムド・ファンクショナルモデル

文字通り時間概念を持たない機能だけを記述したモデルです。各機能は時間ゼロで実行され、各機能間のデータ転送(通信)も時間ゼロで完了します。ハードウエアの振る舞いのみを定義したモデルであり、実行可能なハードウエアのモデリングとして、最も抽象度の高いものです。

SystemCでのシステム構成方法

SystemCでは、システムの機能をブロック化し「モジュール」として定義します。さらに、モジュールは、機能の内容を定義した「プロセス」と、外界との通信を行うための「ポート」から構成されています。

このモジュールは、「チャネル」と呼ばれる通信方法を定義したブロックにより接続されます。チャネルの接続部には、「インタフェース」と呼ばれる接続の形式を定義したものが付いています。この様な構成を採ることで、機能や機能間の通信方法を完全に分離することができます。

一般的な開発では、システムの全てを新規に設計するケースは希であり、実際は実装まで作り込まれた既存の「モジュール」資産を流用したり、ここに新たに「抽象度」の高いモジュールを追加したりする場合が多く、モジュールの抽象度は様々です。各々のモジュールの「抽象度」も自由に選択でき、これらを混在させることも可能です。
SystemCでのシステムの構成ブロック

SystemCでのシステム記述

では、実際の記述方法を見て行きましょう。本稿では、記述の基本的な部分について解説致します。

1.モジュール

まず、システムの基本要素である「モジュール」と、モジュールを構成する「プロセス」について触れます。モジュールは、C++のクラスとして定義します。SystemCには、モジュールを定義するためのクラス sc_moduleクラスが用意されています。この派生クラス(注1)を作ることで、モジュールを定義します。この派生クラスの作成には、専用のマクロ SC_MODULE()が用意されています。
  • 次に、このモジュールのクラスのコンストラクタ(注2)を作成し、この中に、モジュールの実行部分であるプロセスの定義と、プロセス実行のトリガーとなる信号(クロックの立ち上がりなど)を指定する「センシティビティ」を定義します。コンストラクタの宣言には、専用のマクロSC_CTOR()が用意されています。

    【注1】: C++の機能で、元のクラスの変数や関数などを継承して利用する新しいクラス
    【注2】: C++のクラス定義で、クラス生成時に1度だけ呼ばれる特別な関数
  • モジュール

2.プロセス

プロセスは、モジュールの機能を記述するものです。ソフトウエアの世界では、複数の関数が同時に実行されることはなく、ロジックに従って順次コールされて実行されますが、SystemCでは定義したプロセス(実際は関数)は全て「同時並列に動作する」ものとして定義します。

SystemCには、SC_METHOD、SC_THREAD、SC_CTHREADの3種類のプロセスが定義できます。今回は、基本であるSC_METHODのみ解説します。
  • プロセスは通常無限ループとなっており、このループの途中に、イベント待ち関数wait()を使用して、トリガーを待つように構成します。プロセスを起動するためのトリガーの指定には、「センシティビティ」と言うSystemCの機能を使います。SystemCのモジュールの基底クラスsc_module には、sensitive と言う名前の特別な変数があり、この変数にC++機能の「<<」演算子を使用して、ポートとして定義したオブジェクトを渡すことで、プロセスを起動するためトリガーの指定が行えます。
  • プロセス
下の例は、プロセスを起動するためのポート信号 clkを定義し、これをトリガーにして、プロセスの実体である関数 func1()を起動する定義です。clkをクロック信号にマッピングすれば、クロックの変化に同期して、機能が動作するHWがモデリングできたことになります。
プロセス

3.インタフェース

インタフェースは、チャネルにアクセスするためのメソッド(方法)を定義したオブジェクトです。チャネルでは、インタフェースの宣言と、実際のインタフェースの動作定義は個別に行われます。
  • インタフェースの定義は、sc_interfaceクラスを継承して行います。クラスのメンバであるメソッド(read(), write())は、C++の機能を使って、仮想関数(virtual)として宣言し、この派生クラス(次のチャネルに当たる)で、実際の動作関数を定義するように促します。
  • インタフェース

4.ポート

ポートは、モジュールを、通信を定義したチャネルと接続するための端子です。ポートは、そのモジュールクラスのオブジェクトメンバとして宣言します。ポートには、ポートにアクセスするためのメソッド(方法)を定義したインタフェース(前述)を使用して行います。
  • 具体的には、以下のように、モジュールのメンバ変数であるテンプレートクラス sc_port にインタフェースをバインドし、これを、通じてアクセスできるポート p を宣言します。これはC++のテンプレート機能を駆使した表現です。信号はbool型(H/Lの2値)の場合です。
  • ポート
SystemCには、上の表現をまとめたテンプレートクラスが用意されており、sc_in p;と書くことができます。左のソース例で、プロセス起動のためのトリガーとした信号 clkも、ポートとして宣言されています。

5.チャネル

  • チャネルは、モジュール間の通信を定義するもので、インタフェースで宣言した通信関数を実装するためのクラスです。チャネルの定義(宣言)は、sc_chanelクラスと、実装すべき通信関数を宣言したインタフェースを継承します。SystemCには、「プリミティブ・チャネル」と呼ぶ通信機能のテンプレートクラスが、予め用意されています。(sc_buffer, sc_fifo, sc_mutex,sc_semaphore, sc_signalなど)
  • チャネル
C++の機能を駆使したモジュールとチャネルの構成

モジュールの接続と構成

最後に、このように機能定義を行ったモジュールを接続して、システムとして機能するように定義します。SystemCには sc_main()と言うメイン関数を持ち、これがトップレベルとなります。実際には、SystemCのシミュレーションフレームに、C++のmain()に相当する関数がありますが、ここからsc_main()が呼び出される仕組みになっています。

sc_main()には、必要なクロックの定義や、モジュール間をつなぐ信号線の定義などを行います。例えば、下のような回路を想定してみます。クロック信号 ckに同期して動作する2つのモジュールがあり、その1つはDフリップフロップです。

2つのモジュールは、以下のように定義されているとします。
モジュールの接続と構成
モジュールの接続と構成
トップレベルのsc_main()では、ckを作成し、この2つのモジュールを結線します。以下のようになります。
モジュールの接続と構成

動作デバッグのための機能

この様にして作成した機能ブロックが正しく動作するかを確認するために、SystemCには、シミュレーション経過をモニターするための「トレース」機能があります。モニターしたい信号や変数を、sc_trace()のメソッドで登録しておくことで、結果をファイルに書き出すようになっています。以下のようなコードを、前述のソースの sc_start(100); と入れ替えて記述します。
動作デバッグのための機能
このコードを開発プラットフォームのネイティブコンパイラ(Microsoft Visual Studio .NET c/c++ など)で実行コードを作成することで、そのままシミュレーション動作が行えます。

ガイオ「システムシミュレータ」との協調検証システム

ガイオでは、マイコンシミュレータ(ISS)と、SystemCでモデリングされたHWモデルの協調動作(コ・シミュレーション)システムの整備に取り組んでいます。ガイオのマイコンシミュレータカーネル「System-G / ソニック」には、クロック精度で外部プロセスとの同期を取るためのインタフェースが用意されています。これにより、マイコン上で実行されるSWと、SystemCでモデリングされたHWを協調検証することができます。

これからの開発手法である「HW/SW同時設計」における、「システムレベル検証」のフェーズにこのシミュレーションシステムを採用することで、ASIC実装開発の前の段階で、実際のマイコンソフトを使用した、システムレベルでの論理検証が可能になります。
SystemCによるモデルを使用したシステムレベル検証

人気のコラム

最新のコラム