最近の更新 (Recent Changes)

2022-08-24
2015-11-04
2014-10-14
2014-07-01
2014-06-29

Latest File Release

isesimutil (r230)2012-01-01 23:07
nbox_util (r247)2012-03-11 22:19
noodlybox (0012)2010-01-01 19:46
TimingChartViewer (r245)2012-02-15 00:10

Wikiガイド(Guide)

サイドバー (Side Bar)

DocumentForDpiSample

ご注意 : 開発中のため、実際の動作と違っている箇所や、未実装な部分があります。

必要なドキュメント

  • クラス・ソースファイルとその機能
  • ユーザプログラムの作り方
  • ユーザプログラムを実機で使うためには
  • 対応プロセッサを追加するには
  • どうやって動いているのか(技術解説)

クラス・ソースファイルとその機能

c/c_side.cpp

このソースファイルが、シミュレータとC++実装部とのインタフェースです。 MPU.vをvlog -dpiheaderで処理して作ったヘッダ MPU_dpi.hを#includeして、 インポート対象となっている関数を実装しています。

MpuIf (c/cMpuIf.h) 基底クラス

ユーザプログラムに対して、プロセッサのインスタンスを操作(read,write)する方法を提供するインタフェースクラスです。 プロセッサに依存させないために、ユーザプログラムではMpuIfの派生クラスのヘッダ(Sh7751.hなど)は#includeしてはいけません。代わりにcMpuIf.hを使用してください。

Mpu (c/cMpu.h) MpuIfから派生

ライブラリプログラムに対して、プロセッサのインスタンスを操作(createなど)する方法を提供するクラスです。 対応プロセッサを追加するときは、Sh7751クラスと同様にしてMpuクラスをpublic継承したクラスを作ります。 このクラスはmodel/MPU.vと対になっています。

Sh7751 (c/cSh7751.h) Mpuから派生

プロセッサの動作を実装するクラスの一例です。 このクラスはmodel/SH7751.vと対になっています。

MpuFactory (c/cMpuFactory.h) 基底クラス

Singleton(インスタンスが一個だけ)のクラスです。 Mpu派生クラスのインスタンスを生成するのが役割です。

MpuFactoryEnlarger (c/cMpuFactoryEnlarger.h) 基底クラス

MpuFactoryに対して、実行時(ライブラリがロードされて静的オブジェクトの初期化が行われるとき)にプロセッサの作り方を 教える役割を持つクラスです。 Sh7751.cppの末尾のように、Mpuから派生したクラスの実装の中で使います。

Absorber (c/cAbsorber.h) クラスのテンプレート

シミュレータと実機の違いを吸収するためのテンプレートです。 テンプレートから作られたu32_c, u16_cなどが、FPGA構造体を定義するヘッダで使用されます。 なお、マクロREALWORLDが未定義の場合は、u32_cなどはCの組み込み型(unsigned int)のtypedefになり、Cでコンパイル 可能になります。

Fpga (c/cFpga.h) 基底クラス

csvファイルの名前クラスの基底クラスになります。

csvファイルの名前 (dpisample/msimv/csvファイルの名前.h)

csvファイルを元に自動作成されるクラスです。メンバ変数に、csvに書いたレジスタ群が入ります。 マクロ REALWORLD が定義されている場合はC++でなくCコンパイラでもコンパイル可能です。

dpisample/c/accessfpga.cpp

ユーザプログラムの例です。Tcl実装版のaccessfpga.cを移植したものですが、 レジスタの参照および代入がより自然に行えるようになっています。

ユーザプログラムの作り方

FPGAのレジスタ情報が記述されたFpganame.csvファイルを作ります。 csvファイルからFpganame.hとFpganame.cppが自動作成されるので、 ユーザプログラム(以降user.cppと表記)で#include "Fpganame.h"します。 そうすると、Fpganameの持つレジスタfooは fpganame.foo = 0x12345678; のような代入をしたり、 unsigned value = fpganame.foo;のような参照をしたりできるようになります。

ユーザプログラムの中で時間待ちをしたいときは、fpganame.nop(uint32_t clocks)を呼び出してください。 クロック数ですから、時間を指定したい場合はnop(時間/クロック周期)としてください。 なお、fpganame.nop(uint32_t clocks)はマクロREALWORLDが定義されている場合はコンパイルエラーになります。

ユーザプログラムを実機で使うためには

マクロ REALWORLD を定義した状態で、hoge.cppとuser.cppをコンパイルします。 REALWORLDが定義されているとhoge.cppはC++でなくCコンパイラでもコンパイル可能なので、 実機ではC++コンパイラが使えないというときでも大丈夫です。 user.cppは、C++の機能に依存しないように書けばCコンパイラでコンパイルできます。

対応プロセッサを追加するには

SH7751.vを参考にしてNEWCPU.vを作成し、Sh7751.hとSh7751.cppを参考にしてNewcpu.hとNewcpu.cppを作ります。

注意点

  • 制御系の信号(CS,OE,WEなど)は、Verilog側とC++側でビット位置を一致させなければなりません。

備考

  • Newcpu::create(int mpu_id, const char *mpu_name)の 実装で、複数の文字列とマッチするようにすれば、仕様が少しだけ異なるプロセッサを同一のC++実装でサポートすることも可能です。

どうやって動いているのか

ステートマシンをC++で実装したような構造になっています。以下はSh7751.h/cppを参照しながら読んでください。

  • 主状態はstate、副状態はsubStateに入ります。
  • stateはメンバメソッドへのポインタになっています。
  • execAtFall/Riseもメンバメソッドへのポインタですが、 状態ではなく「次の↓エッジ/↑エッジでの出力信号操作」が入ります。
stateには、次の↑エッジの時に実行するメソッドへのポインタが入っていて、 stFetch stRead stFetch stWrite ...のように変化します。 各st???メソッドは、subStateをFIRST→READBODY→READTAILのように更新し、最後にstateをstFetchに更新します。


各st???は、呼び出されたそのときでなく、次の↓エッジや↑エッジで信号操作をしなければならないことがあり、 そのようなときはexecAtFall/Riseにメソッドへのポインタを代入します。(コールバックのような感じです)


ちなみに、execAtFallのほうがexecAtRiseよりも時間的に先に実行されます。


シミュレータ側で時間が経過するのは、C++側でv_waitForClk()が呼ばれたときです。 v_waitForClk()の呼び出し1回につき、原則的に1クロック(readでは2クロックのこともある)ぶん経過します。

フロー

  1. C++側にて、次のクロックエッジで実行するメソッドへのポインタを atRise,atFallに代入する
  2. C++側から、v_waitForClk()を呼び出す
  3. シミュレータでv_waitForClkが実行される 次のクロックエッジ(↑または↓)を待って、シミュレータ側が、 c_reflect()を呼び出す(このとき、入力信号が Verilog HDL側からC++側に渡される)
  4. C++側で、atRiseとstate、または、atFallを実行する。 atRise,state,atFallで 呼び出されるメソッドは、出力信号の元になる構造体を更新する
  5. c_reflect()がリターンする。
  6. シミュレータは、c_reflect()がリターンしてくると出力信号 を更新する
  7. シミュレータ側で、v_waitforClk()がリターンする。

デバッグ

vsim -L unisims_ver -sv_lib cside tDPIの実行後に、プロセスマネージャでvsimk.exeの プロセスidを確認し、それに対してinsightでattachすることが可能、 というところまでは分かりました。


しかし、一回Attachした後にcontinueすると、CPU負荷100%になって進まなくなるという状態になってしまいます。


それがなぜなのか、また、回避策があるのかは今のところ不明です。ModelSim側でデバッガを検出してわざと遅くしているような気がするので、私(molelord)は調べるのをあきらめました。