カスタム設計の MicroBlaze
追加 2011.3.4 プログラムメモリの初期化、defparam 作成ツール (β版 v0.17)追加 2013.8.27
最近のISEのソフト開発環境、SDK 追加 2011.3.24
カスタム設計の MicroBlaze、 risc2s.ngc を含むソース一式。XC3S200 で動作します追加 2011.3.26
カスタム設計の MicroBlaze、 XC6SLX45T で動作します 追加 2011.8.20 プロジェクトファイル一式追加。2011.8.30
カスタム設計の MicroBlaze、 XC4VFX12 で動作します 追加 2011.8.21 プロジェクトファイル一式追加。2011.8.29
カスタム設計の MicroBlaze、 XC6SLX45 で動作させ、DDR2メモリチェックに使った例 追加 2011.10.24 プロジェクトファイル一式追加。2011.10.24
カスタム設計の MicroBlaze、 XC6SLX45T で動作させ、DDR3メモリチェックに使った例 追加 2011.11.9 プロジェクトファイル一式追加。2011.11.9
プログラムはブロックメモリのみで可能。外部メモリではプログラムを走らせることができません。I/O、データメモリのみ使えます。外部メモリ可能−−>新たに作成
MicroBlaze の命令セットで、ほとんど使われない命令がある ほとんど使われない命令がある??

以下は、新設計のカスタムMicroBlaze
外部メモリでも動作可能なもの。全く新規に作成。パイプラインなし、高速クロック動作(5クロックで1命令) 追加 2013.2.3
実機検証用、Hello World! をUARTで出す( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.2.11
コンパイラで作成したプログラムを走らせる( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.2.11
LPDDRメモリでプログラムを走らせる( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.2.14
キャッシュを実装してスピードテスト( Avnet Spartan6 LX9 MicroBoard ) ソース一式追加 2013.2.24
SPIメモリを操作してみる( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.3.3
Xilinx の .ngc ファイルを作成して、ソースをネットリストに変換 追加 2013.6.10
イーサネットPHYを操作してみる。MACの設計( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.3.24


なぜソフトコアのCPUを組み込むのか

FPGAとマイコンを組み合わせてボードを作っていると、FPGAの回路のチェックや動作中の状態を把握するのに、マイコンから内部のレジスタやメモリを読み出すことがよくあります。また、特定のフリップフロップを、マイコンからセット、リセットして、回路の振る舞いを確認することもしばしばです。また、外部にマイコンを装備するほどではないとか、基板の実装面積が足りないとか、ニーズはさまざまですが、FPGA内部にCPUがあると、開発工数が減ることも確かです。その為に、FPGAメーカーもそれなりのソフトコアのCPUを提供してきました。当方は、XilinxのMicroBlazeや、ハードコアの内臓PowerPCを使ってきました。価格性能比では、マイコン単体を外部に装備するほうが勝りますが、簡単だがハードで作ると時間がかかる場合、CPUを内臓して解決するという設計スタイルが定着してきました。
さて、ソフトコアのCPUを内臓する場合、第一番に要求される仕様は、

ソフトの開発は、C言語などが使えること
であろうかと思います。さらに言うなら、

そのコンパイラが無償で使えること
 ではないでしょうか。

ソフトコアのCPUは、8ビットや簡単な16ビットなら作ってもたいした回路にはなりませんから、雑誌やWeb上でたくさん見つけられます。ところが、プログラミングは、たいていアセンブラなので、ほとんど使う気になりません。
そこで、コンパイラが簡単に使えるCPUとなると、68k系、80x86、PowerPC、ARM、SH、H8など、ポピュラーなCPUってことになります。特殊なものとして、PIC、8051、などもコンパイラが用意されていますが、こちらはほとんどC言語とは呼べないしろものです。ここで、Z80という選択はかなり魅力がありますが、回路が思ったより規模が大きくなり、そのくせ64kBの壁は、昨今の大量のメモリを扱う用途には力不足です。32ビットを直接扱えるCPUで、しかもRISCタイプということで、MicroBlaze がターゲットになりました。

EDK10の資料から、MicroBlazeの命令セットを調査
MicroBlazeの命令セットをを見ると、PowerPCと似ていることが解りますが、PowerPCと比較するとはるかに命令数が少なく、バイナリコードを見ると、空きエリアが多いのが目立ちます。主な特徴を列挙すると、
  1. 汎用レジスタは32個あるが、ハードウエア的に、またはソフトウエアであらかじめ機能が決まっているレジスタがある。
    1. レジスタ、r0 は、読み出すとき、常に0
    2. レジスタ、r15 は、サブルーチンからのリターンアドレスに使われる。これはハードウエアではなくコンパイラによる。
    3. レジスタ、r14 は、割り込み発生時に、プログラムカウンタ(PC)の退避として、ハードウエア的に使われる。
    4. レジスタ、r16、r17は、ブレークや、例外処理のときのPCの値を記憶する。
    5. レジスタ、r1 は、ソフト(コンパイラ)で指定されたスタックポインタとなる。
  2. 命令は、3オペランドのものがある。つまり、レジスタr0、r1、r2などが、3個同時にアクセスされる。
  3. サブルーチンコールでは、ハード的にスタックは使われず、戻り番地格納やスタックフレームはソフトで操作する。
  4. 同じく、サブルーチンからの戻りは、戻り番地が格納されたレジスタからPCに読み出される。
  5. いわゆる、<MOV>命令はなく、たとえば、r4をr5にコピーするとき、r5=r4+r0となり、加算命令で実現している。
  6. PowerPCでの32ビット即値は、16ビットを2回に分けて実現していたが、MicroBlazeでは、32ビット命令を2個使って、不可分の命令として実行し、ちょうど64ビット命令のように振舞う。PowerPCでは、1回の16ビット即値は、MSBの16ビットにロードされ、1回だけで使えるようになっているが、MicroBlazeではそれができない。

掛け算や、割り算、バレルシフタなどは実装せず、基本命令をピックアップして黄色くハイライトさせた命令表が、こちらです。inst_mb_ref_guide.pdf元の資料は、mb_ref_guide.pdf というEDK10のMicroBlazeのドキュメントです。黄色くハイライトしている命令が、実装した基本命令です。MicroBlaze用のCygwinコンパイラが生成するコードを抜き出しています。中には、アセンブラでしか(?)使えない命令も実装していますが、その命令を完全に実行できるわけではありません。たとえば、msr (マシンステータスレジスタ)に実装したフラグは、キャリーと、割り込み許可フラグだけです。ゼロフラグとか、サイン(負)フラグとかは、 MicroBlaze にはありませんので、フラグ類は少なくて済みました。無論、Cのソースに、浮動少数点などは使えませんし、関連する printf() 関数も使えません。デバッグ目的では printf() が無いと不便なので、整数と文字列さえ扱えればよいので、簡単なものを作って対応します。

とにかくGCCが吐き出したコードで動作するものを目標とする

パフォーマンスはその次ということで、パイプラインは2段(厳密に、2段か否かは疑問)となり、とりあえず1命令1クロックのRISC風のものができ、回路のデバッグ用として、2007年ごろから、いろいろと使ってきて、ほぼ問題がないので、近々に公開します。(ソースはとても公開できるような記述でないので、Xilinx のネットリスト、risc2s.ngc となります)
カスタム設計の MicroBlaze、 risc2s.ngc を含むソース一式。XC3S200 で動作します追加 2011.3.26

スペックは以下の通り。
  1. ほとんどの命令で、1命令1クロック。ブランチ命令、32ビット即値命令などは2クロック。
  2. メモリは、命令、データが共通。シングルポート接続。ハーバード形式は最小構成でメモリを多く使うのでやめる。
  3. SPARTAN3で、最高50MHz、Virtex4で66MHzぐらい。無論、実装回路が70%に増えるとスピードは、40MHz、50MHzが無難。SPARTAN6では、ゲート使用率10%で、66MHzを確認。( SPARTAN6 Evaluation kit SP605 XC6SLX45T )
  4. 3オぺランドのレジスタは、RAM16X1D を並べて2系統実装して、3アドレス対応。
  5. 乗算、除算、バレルシフトなどの命令、MMU、キャッシュ機能などなし。割り込みは可能。
  6. CPUコア部分には、UART、32ビットフリーカウンタ1個を内臓。
  7. GCCでコンパイルしたコードを実行できる。メモリや、周辺レジスタの内容の書き換え、読み出しが行える簡単なモニタを用意。
  8. スライス数は、約1350。これは、SPARTAN3の、XC3S200で75%ほど使う。(SPARTAN3 Starter kit で動作)
  9. メインメモリは、ブロックRAM ( RAMB16_S9 ) を最低4個で動作するが、メモリの初期化データ作成ツールでは、4個、8個、16個、32個を使うものを用意。
  10. 外部バスのスピードは、最低4クロック必要だが、SDRAMや、フラッシュメモリを接続してプログラムを動作させることができる。←訂正 2012.1.5
  11. 外部メモリでは、プログラムを走らせることはできません。I/O、データメモリのみ使えます
  12. ALTERAのCycloneでも動作を確認。快調に動く。XILINXよりクロック数を上げられるのではないか?。LE数は、約4200個 LE数が多いのは、Xilinxのように、レジスタを分散メモリで作れなかった為と思われる。reg 宣言で、1024個のフリップフロップと、デコーダ、マルチプレクサで、CPUコアの40%を占める!!

簡単なアセンブラプログラムでシミュレーションしてみる
最近、ISEでは、ModelSim(Xilinx Edition)が使えなくなり、代わりに ISE同梱のISimとなっています。ISEとの統合ができていて、ALTERAのQuartus2と連動する、ModelSimのような、いろいろな設定は不要です。

簡単なプログラムは、以下のような、0x100番地に0x12345678を書いて、それを読み出し、7を加算して0x100番地に書き、そのあと、0x104番地と0x100番地を読むものです。使ってる命令は4種類しかなく、しかもその中の brid はダイナミックストップの為のもので、シミュレーションの主な目的ではありません。アセンブラは、Cygwin の mb-as.exe を使って、リストを出力しています。ソースは、 cmbasm.s です。URLの関係上、テキストファイル名としています。
$ mb-as -ahls=cmbasm.lst cmbasm.s ( enter ) で作ります
リストファイル、cmbasm.lst
Xilinx MicroBlaze GAS Version 2.9.4 cmbasm.s 			page 1


   1              	/*
   2              		Custom Micro Blaze test asm
   3              	*/
   4              		.globl _start
   5              	        .section .text
   6              		.align 2
   7              	_start:
   8 0000 B0001234 	        addik	r3,r0,0x12345678	# 32ビット即値を r3 に設定
   8      30605678 
   9 0008 30800100 		addik	r4,r0,0x100		# アドレス0x100を、 r4 に設定
  10 000c F8640000 		swi	r3,r4,0			# r4 のアドレスに、r3 を書く
  11 0010 E8A40000 		lwi	r5,r4,0			# r4 のアドレスから32ビットデータを r5 に読む
  12 0014 30A50007 		addik	r5,r5,7			# r5 に 7 を加算する 0x1234567f になる
  13 0018 F8A40000 		swi	r5,r4,0			# r5 を、r4 のアドレスに書く
  14 001c E8C40004 		lwi	r6,r4,4			# r4 + 4 のアドレスから32ビットを r6 に読む。0x0
  15 0020 E8C40000 		lwi	r6,r4,0			# r4 のアドレスから32ビットを r6 に読む 0x1234567f
  16 0024 30C60001 	loop:	addik	r6,r6,1			# r6 に1を加算
  17 0028 B810FFFC 		brid	loop			# 繰り返し
  18 002c 30E70001 		addik	r7,r7,1			# r7 に1を加算
  19              		.func
  20              		.end
  21              	
Xilinx MicroBlaze GAS Version 2.9.4 cmbasm.s 			page 2


DEFINED SYMBOLS
            cmbasm.s:7      .text:00000000 _start
            cmbasm.s:16     .text:00000024 loop

NO UNDEFINED SYMBOLS
このリストから、同じ命令でも、最初の addik のようにコードの長いものがあり、32ビット即値のための、IMM 命令が自動的に追加されています。アセンブラレベルでは記述が同じでも、出来上がったバイナリコードでは、IMM 命令が追加されたことになっています。ここで、よく観察すると、IMM 命令は、ブランチ命令、brid と、よく似ていて、MSB4ビットが同じ、0xB となっています。内部の動作でも2命令を不可分に実行するシーケンスとなります。ブランチ命令 brid では、loop に戻るけれども、その次の命令、addik r7,r7,1 も実行してしまいます。こういうのを、遅延分岐と呼ぶらしいですが、ルネサスのSHシリーズマイコンでも同じですね。内部のパイプラインに取り込まれた命令を捨てずに実行して、効率をあげる手法です。

この簡単な命令を、ISim でシミュレーションしたスクリーンショットが、以下の2画像です。文字が小さいですが、Name 欄の maddr がメモリのアドレス、mdb が、データバスの状態です。extwd が、MicroBlazeがメモリにライトするデータを示し、extwe がHighでメモリにライトすることを示しています。ifen は、その時点の maddr が示すアドレスに対する、インストラクションフェッチが有効であることを示しています。有効な命令はその次のクロックでの、mdb の内容が、フェッチする命令コードを示しています。




外部メモリや、I/Oのアクセスタイミング

内部のブロックRAM( mainmem.v )でプログラム実行やデータアクセスをする場合は、アクセスタイムは1クロックですが、外部では、アドレスデコードを含めて通常5クロックかかります。その場合の典型的なタイミングです。

プログラムメモリの初期化、defparam の256ビット初期化データの怪

EDKを使っている限り、プログラムメモリの初期化は、出来上がった .bit ファイルを、data2mem コマンドでブロックRAMのデータ部分だけ変更しているので、ソフトが変更になっても、配置配線を始めからやる必要がありません。しかし、この data2mem コマンドを使うには、あらかじめブロックRAMの位置情報(X8Y12などと指定する)を、UCFで指定しておくのと、BMM ファイルを用意しておかないといけません。この方法は、また別の機会にやってみようと思います。回路規模が大きくなると、ちょっとしたソフトの変更でも配置配線に時間がかかって、とても高率が悪いからです。
ここでは、とりあえず、defparam ......... などと書く、verilog の文法に従った方法で、プログラムメモリの初期化を行います。

とは言うものの、この defparam で初期化するデータは256ビット単位となっており、しかも、256ビットのバス幅のメモリに対して初期化データを設定するような構造のようです。256ビットの値は、アドレス上位が左になり、0番地は一番右の32ビット部分になっているからです。通常、インテルとか、モトローラのHEXデータフォーマットのデータの並び順では、左の方がアドレスの小さい側なので、逆に並んでいます。

例として、XilinxのブロックRAMプリミティブである、RAMB16_S36 を以下のように使ったとし、メモリの0番地に32ビットデータ、0x12345678を、その次に、0x9ABCDEF0を初期化したいとすると、

module mainmem( addr,wdata,rdata,clk,we
);
input	[15:2] addr;
input	[31:0] wdata;
output	[31:0] rdata;
input	clk;
input	[3:0] we;
wire	[3:0] dop;
RAMB16_S36 ram0(.DO(rdata[31:0]), .DOP(dop[3:0]), .ADDR(addr[10:2]), .CLK(clk), 
	.DI(wdata[31:0]), .DIP(4'h0),. EN(1'b1), .SSR(1'b0), .WE(we[3:0] != 0));

 defparam ram0.INIT_00 = 256'h0000000000000000000000000000000000000000000000009ABCDEF012345678;

endmodule
という記述になります。0番地が0x12345678ですが、データのLSBがそのまま、defparam の256ビットデータの右端になっていて、いわゆる、インテルプロセッサのメモリへのバイトオーダーである、little endian の並びであることが判ります。RAMB16_S1_S36 を使ったとして、1ビット側から順に読み出すと、0,0,0,1となります。ともあれ、これは Xilinxの特性 ではなく、verilog の記述規則なのでそのまま使うしかありません。
上記に示したテストプログラムを、RAMB16_S36 に設定するには、
 defparam ram0.INIT_00 = 256'hE8C40004F8A4000030A50007E8A40000F86400003080010030605678B0001234;
 defparam ram0.INIT_01 = 256'h0000000000000000000000000000000030E70001B810FFFC30C60001E8C40000;

となります。初期化データは、アセンブラのリストから、エディタを使ってコピー&ペーストで比較的簡単に作れます。

短いテストプログラムとか、ソフトで扱うデータを、32ビットに限定するなら、バス幅32ビットのブロックRAMをそのまま使えますが、8ビットや16ビットの書き換えではそのまま対応できないので、(外部メモリへのアクセスのように、バイト単位に書き換える機能があれば、ブロックRAMを32ビット幅1個でもメインメモリとして使えますが、1クロックでのアクセスでは不可能)最低4個の8ビット幅のメモリを並列に接続して、32ビットのメモリにします。
4個のメモリを使って、初期化すると、以下のようになります。ライト時のバイト指定は、we[3:0] と4本必要です。
module mainmem( addr,wdata,rdata,clk,we
);
input	[15:2] addr;
input	[31:0] wdata;
output	[31:0] rdata;
input	clk;
input	[3:0] we;
wire	[3:0] dop;
RAMB16_S9 ram0(.DO(rdata[31:24]), .DOP(dop[3]), .ADDR(addr[12:2]), .CLK(clk), .DI(wdata[31:24]),
	.DIP(1'b0),. EN(1'b1), .SSR(1'b0), .WE(we[3]));
RAMB16_S9 ram1(.DO(rdata[23:16]), .DOP(dop[2]), .ADDR(addr[12:2]), .CLK(clk), .DI(wdata[23:16]),
	.DIP(1'b0),. EN(1'b1), .SSR(1'b0), .WE(we[2]));
RAMB16_S9 ram2(.DO(rdata[15:8]), .DOP(dop[1]), .ADDR(addr[12:2]), .CLK(clk), .DI(wdata[15:8]),
	.DIP(1'b0),. EN(1'b1), .SSR(1'b0), .WE(we[1]));
RAMB16_S9 ram3(.DO(rdata[7:0]), .DOP(dop[0]), .ADDR(addr[12:2]), .CLK(clk), .DI(wdata[7:0]),
	.DIP(1'b0),. EN(1'b1), .SSR(1'b0), .WE(we[0]));

 defparam ram0.INIT_00=256'h000000000000000000000000000000000000000030B830E8E8F830E8F83030B0;
 defparam ram1.INIT_00=256'h0000000000000000000000000000000000000000E710C6C4C4A4A5A464806000;
 defparam ram2.INIT_00=256'h000000000000000000000000000000000000000000FF00000000000000015612;
 defparam ram3.INIT_00=256'h000000000000000000000000000000000000000001FC01000400070000007834;

endmodule

メモリ初期化 defparam は、32ビットの命令を、各メモリに1バイトづつ分割して、ram0 ---> ram3 のLSBから順に左に書いていきます。これを手作業でするには、短いプログラムでも大変な作業になってしまいます。動作確認のごく初期の段階では、このように手動変換でもいいですが、コンパイラで作ったヘキサのデータから、defparam への変換プログラムを作ることにしました。インテルヘキサからモトローラSレコードへの変換とか、バイナリにも相互に変換できるものは、フリーでいくつも見つけられますが、このような特殊なものは簡単には見つかりません。表計算ソフトでも可能なようですが、なにぶん表計算はあまり使ったことがないので、Visual Studio 2005 で作ることにしました。実は、Visual Studio 2005 は以前から入手していたのですが、こういう簡単なツールを作るのに、ずっと VC++6.0 を使っていたので、ほとんど使わないままでした。たしかMSDN会員付きで、10万円以上したと思います。C++のコンパイラですが、多くは C のまま、且つ、ウィンドウズ対応は、Win32 API を直接呼び出す古典的なものがほとんどです。半分以上は、コンソールプログラムで用が足ります。VC++なら、無償のエクスプレス版でも十分だと、3年ほど前に知って、えらく高い買い物をしたと思ったこともありますが、MSDNはそれなりに有用で、Windowsの最新OSを10台まで使え、且つ認証期限まで60日もあるので、自作パソコンを作るには重宝しました。

どうせ作るなら、defparam だけでなく、インテル、モトローラ、CSV、単純HEXのみ、バイナリ(txt などもそのままバイナリ扱い)などが読め、出力は、defparam .hex .mot .csv dump .mif( QuartusUで使うメモリ初期化データ ) .hex( QuartusU と、ModelSim で使えるメモリ初期化データ。インテル形式とは8ビット以外ではアドレス表記が異なる。ワードアドレスとなっている)、そしてバイナリデータを出力できるものにします。

defparam のデータにも変換できる、多機能な HEXutils のスクリーンショットです。
このツールは、VC++2005 で作成しているので、Microsoft VC++2005用ランタイム 再頒布可能パッケージ が必要な場合があります。

おもな機能は以下の通り。
  1. ソース対応は、モトローラS、インテルHEX、CSV、単純HEXの羅列、バイナリ(bin)、すべて(バイナリ扱い)
  2. 出力形式は、defparam、mif、CSV、モトローラS、インテルHEX、QuartusUHEX、バイナリ、メモリダンプ。
  3. defparam の場合は、Xilinx の BRAM を、バス幅1,2,4,8,16,32ビットを指定できます。ただし出来上がるメモリパックは32ビットのメモリのみ。
  4. 内部バッファーは16MB。これ以上のバイナリでの長さには未対応。読み込み時には、アドレス情報を0x00FFFFFFでマスク。
  5. インテルHEX、モトローラSの場合は、出力時に、アドレス移動が可能。
  6. defparam mif csv Quartus2hex bin などでは、出力時に元の先頭アドレスは、0番地とみなします。
  7. 読み込み時に、バッファーを、指定データ(バイトのみ)で満たす機能があります。またそれをしない選択もあるので、2個のファイルをマージすることができます。オーバーラップしても警告はでません。
  8. モトローラS、インテルHEX、CSV、mif などで、バイトオーダーを指定できます。つまり、エンディアン変換機能。だだし、16、32ビット単位のみ。

現在まだデバッグ中、機能追加中ですが、defparam など作成する
旧β版はこちら。v0.1  旧β版0.11はこちら。v0.11  旧β版はこちら。v0.12
旧β版はこちら。v0.13 旧β版はこちら。v0.14 旧β版はこちら。v0.15 旧β版はこちら。v0.16
新β版はこちら。v0.17

  解凍して hexutils.exe をダブルクリック。インストーラなどは無く、そのまま動作します。defparam 出力に関しては、RAMB16_S1,RAMB16_S2,RAMB16_S4,RAMB16_S9 を使う回路で、で約7KBのモニタソフトの実機動作確認をしています。
このツールは、VC++2005 で作成しているので、Microsoft VC++2005用ランタイム 再頒布可能パッケージ が必要な場合があります。

ヘキサファイルなどの相互変換ツールとしては、かなり高機能だと思います。( 特にFPGA用HEXファイル変換 )
いままで作った単機能のコマンドライン変換ツールを集大成しています。
主な使い方
  1. 主な仕様。最大ソースサイズ:32MB、最大内部データバッファー:16MB、最大出力サイズ:256MB。
  2. アドレス付きHEXデータは、内部に読むとき、アドレス値を、0x00FFFFFFでANDしています。
  3. 右上の選択コンボボックスで、ソースのファイルタイプを指定します。
  4. そのすぐ左が、ブラウザ。ブラウザでもファイルタイプを選択できますが、コンボボックスが優先します。
  5. ファイルが存在すれば、ファイル名の絶対パスが表示されます。
  6. 左上の、Read ボタンで、ファイルを読みます。ファイルタイプで指定した方法で、内部のバッファーに読み、最小アドレス、などに報告します。
  7. ブランクは、途中でアドレスが増加して、データ指定の無かった部分のバイト数を示します。fill で指定された値のままです。
  8. fill チェックボタンが有効だと、ファイルを読む前にバッファーを右の値で満たします。値は2桁のヘキサで指定します。
  9. fill チェックボタンが無効だと、バッファーをそのままで、別のファイルを読み足すことができます。
  10. Display ボタンで、ソースファイルを別ウインドウに表示します。ソースがバイナリ指定の場合は、表示しません。
  11. 開始 は、バッファーに読まれた先頭アドレスで、ここは変更することができます。
  12. 終了+1 は、バッファーに読まれた最終アドレス+1で、ここは変更可能です。
  13. 作成時 offset は、出力が、インテルHEX、モトローラSのときのみ有効で、アドレス情報を変更できます。
  14. Big endian は、QuartusU.mif, QuartusU hex, Excel ファイルなどに出力するときに有効で、バイトオーダーを変換します。
  15. 横個数は、Excel .CSVファイル, 0xHEX, HEX, などで有効で、1行のデータ個数を指定します。
  16. S1-S3 は、モトローラSレコードで出力すのときのみ有効で、アドレス幅を指定しますが、アドレス情報が大きい場合は、S2、S3になります。
  17. data size (byte) は、defparam の場合は、ブロックRAM1個のバス幅を指定し、32ビット幅メモリの初期化データにします。
  18. data size (byte) は、mif, QuartusU hex, .CSV, 0xHEX, HEX, の場合で、1個のデータのバス幅を指定します。8,16,32のみ有効です。
  19. 出力形式の コンボボックスは、出力のファイルタイプを指定し、その下のブラウザでファイル名を指定します。
  20. 変換は、内部に読まれたバッファーから、出力ファイルを作成します。表示で、確認でき、保存でファイルに書きます。

割り込み要求、割り込み応答のシミュレーション波形


この図は、割り込みタイマーで、Custom MicroBlaze に割り込み要求を出し、0x10番地にジャンプしている様子です。
  1. 信号名、intreq が、タイマーで発生させた、外部からの割り込み要求信号。Highへの変化で有効。
  2. 黄色の縦カーソルの位置で、信号名 intreqr が1になっていますが、これがCPU内部で割り込み要求の立ち上がりエッジを検出したフラグです。この信号は、Custom MicroBlaze の risc2s.ngc の出力ポートで参照できる、testout[4] ( intreqr ) です。
  3. intreqr の1クロック後の信号、intseq が、CPUが、割り込みシーケンスに移行したことを示します。このタイミングで、iff、ifen がLow(無効)になっていて、メモリには、次の実行アドレスが出されていて、命令も読み出されていても( 命令は、0x30C00008 addik r6,r0,8 )、捨ててしまうことを示しています。
  4. 次のクロックで、CPUのプログラムカウンタが0x10となって、maddr に0x10が出され、ifen が有効となり、命令フェッチステートに移っています。
  5. 次のクロックで、メモリの0x10番地の命令、0xB0000000が読み出されて、iff 有効で、命令を実行していることを示しています。
実機、Digilent Spartan-3 Starter kit での割り込みテスト
Digilentは、Xilinxの教育用評価基板のメーカーで、数年前に以下の基板を出していました。当時、15,000円ほどで、かなり安かったと思います。この基板を持っている人はかなりいるのではないかと思います。使っているICは、XC3S200FT256 で、裏に16ビット幅のSRAMを2個実装されていて、オリジナルのデモ回路では、VGAに文字を出し、操作は、 Pico Blaze でした。MicroBlaze を入れるには、ロジック数が足らなかったのだと思います。

この基板に、Custom MicroBlaze を入れて、簡単なアプリを動かしてみます。モニタだけなら8kバイトに収まりますが、いろいろ評価するため、12個あるBRAMの内、8個を使って、メインメモリを16kBとします。割り込みタイマーとか、SRAMインターフェイスを入れると、使用ロジック数は、87%にもなってしまいました。この使用率で、Custom MicroBlaze は、40MHzで動作します。容量の小さなFPGAなので、40MHzでも若干余裕があります(50Mでは、なんとなく動くが操作ができなかった)。これがXC3S1500−676ピンとかだと、87%も使うと、40MHzはだめだったと記憶しています。33MHzぐらいにしたようです。
この基板で動く、回路一式がこちらです。CPUは、risc2s.ngc です(´∀`;;) 解説はまだありません。
この中で、risc2s.ngc は、ISEのプロジェクトファイルがある、同じフォルダに移すか、以下のように、Translate Properties で risc2s.ngc の所在を指示しておきます。TOPのRTLは sktcmbdemo1t.v で、ここから、 Custom MicroBlaze 本体の risc2s.ngc 用ラッパー( risc2s.v ) と、mainmem.v を呼び出します。


基板左のRS232Cコネクタとパソコンを接続し、ターミナルソフトを115200ボーで開いておいてJTAGでコンフィグすると、以下のように起動プロンプトが出ます。

プロンプト、CMB-Bug> が出ている状態で、i1 <enter> とコマンドを与えると、左下のLEDに、カウントアップ表示が出ます。LEDの点灯内容は、FPGAのハードウエアによる表示ではなく、Custom MicroBlaze にインターバル割り込みを与えて、割り込み処理の中で変数をカウントアップして、LED表示ポートに出力した結果です。i0 <enter> で、カウントが停止します。

また、CMB-Bug>S <enter> とコマンドを与えると、エラトステネスの篩いを実行して、約3秒後に結果が出ます。

モニタでは、メモリのダンプ、変更、任意番地からの実行、メモリのコピー、メモリの比較、パソコンからモトローラSレコード形式のファイルを送って、メモリにロードする機能があります。操作は、SH2用モニタとほとんど同じです。


Xilinx Spartan-6 FPGA SP605 Evaluation kit での動作テスト、同じ risc2s.ngc, mainmem.v を使っています

SPARTAN6のEvaluation Kitは、パソコンのPCIeスロットに入れて、PCIe のテストができたり、1Gのイーサネットが使えたり、盛りだくさんな評価ボードですが、ユーザーに開放している汎用ピンが、写真の上の横長い黒いコネクタとなっており、このコネクタに集められています。これでは簡単には利用できません。せめてコネクタの相手側ぐらいおまけに付けてほしいところです。

このボードですが、12Vから各電源を作っていますが、ほとんどが電源モジュールになっており、変換効率が悪いらしく、かなり発熱して触れなくなります。同期整流タイプのステップダウンDCDCコンバーターなら、もっと効率がいいはずで、95%ほどになるはずです。この基板の電源設計はかなり手抜きですね。

写真の下、PCIeコネクタのすぐ右の緑LEDが、ユーザーLEDで、4個使えるので、割り込みでカウントした8ビットのカウンタの、上位4ビットを出力しています。

ISE13.1 でのプロジェクトファイル一式はこちらです。 sp605cmbiof1.lzh  ←アクセスできない場合は日をあらためてください。


準備中

Xilinx Virtex4 ML403 Evaluation Platformでの動作テスト、同じ risc2s.ngc, mainmem.v を使っています

Virtex4 の Evaluation Platform は、ハードマクロのPowerPCを内臓したFPGAの評価ボードです。付属するCFカードに評価プログラムが書かれており、PowerPCでLinuxが走っているようです。電源ONで、テストプログラムが走り、いくつかの動作テストが選択できます。
このボードを使って、カスタム設計の MicroBlaze を走らせてみました。回路は、XC3S200 Starter kit と同じ、risc2s.gnc と、mainmem.v です。無論、TOPのボード固有の回路や、 ucf はボードに合わせてあります。以下は、同じように、割り込みでカウンタを操作して、LEDで表示しています。(右下の緑LED)
ISE13.1 でのプロジェクトファイル一式はこちらです。 ml403cmbiof.lzh  ←アクセスできない場合は日をあらためてください。

準備中

ALTERA、Cyclone1 ( EP1C12Q240C8 ) で動作させたスクリーンショットです


MicroBlaze のコンパイラ

Xilinxの組み込み開発環境、EDKには、MicroBlazeと、PowerPCのクロスコンパイラが付属しています。どちらもGNUで作成されており、実体は、Cygwinですから、もし、Cygwinの環境がセットアップされているなら、EDKからコンパイラツールをフォルダごとコピーして、パスを通すと、Cygwin環境でソフトを開発することができます。EDKには、いわゆるWebパックがないので、無償評価版をインストールしても、期限が来ると使えなくなりますが、コンパイラツールは、そのままCygwin環境があれば使うことができます。最近のISEでは(ISE v13.1で確認)、WebPackをインストールすると、EDKフォルダも作成され、その中に gnu フォルダもあり、microblaze\nt フォルダ以下にそれらしいコンパイラツールがありますが、この nt フォルダ以下を Cygwin の、usr\local フォルダ以下にコピーして、パスを通しても、使えないようです。その代わり、SDKのみをインストールして、gnu フォルダ以下に展開される、microblaze\nt フォルダをコピーすると、使えるようです。またこのツールは、Cygwin 環境が無くても使えるものです。ただ、make.exe などを使うには、gnuwin\bin フォルダにパスを通す必要があるようです。

コンパイラツールの場所は、EDKの、EDK(フォルダ)¥gnu¥microblaze¥nt¥ 以下にあり(EDK10.1の場合)、nt をフォルダごとCygwin¥usr¥local¥ 以下にコピーして、Cygwinの起動時に、

PATH=/usr/local/nt/bin:$PATH

の1行でパスのセットアップをしておくとコンパイラツールを使えます。

Cygwin環境が無い場合は、WindowsのDOSプロンプトでも使うことができます。その場合は、EDKフォルダに、cygwin¥bin があるので、Cygwinフォルダごと、どこかのドライブのルートディレクトリ(ここでは G:¥ とする)にコピーします。そのあと、Cygwinフォルダに、usr フォルダを作成して、その下に、local フォルダを作り、そのフォルダ以下に、上記の nt フォルダをコピーします。このようにしておいて、DOSプロンプトを起動し、次のような1行のBATでパスを設定します。

set path=g:\cygwin\bin;g:\cygwin\usr\local\bin;%PATH% ( setmbpath.bat )

設定したあとのフォルダの、G:\cygwin\usr\local\bin を見ると以下のようになります。この例は、EDK10.1をコピーしたものです。



MicroBlaze のコンパイラをDOSプロンプトで操作してみる

Cygwinのクロスコンパイラを動作させる環境が整ったところで、以下のようなファイルを作成します。
  1. makefile コンパイラやアセンブラ、その他のユーティリティーを制御する、メイクファイル。
  2. cmbmon.c MicroBlaze用のモニタ。メモリダンプやメモリ変更などを行うプログラム。
  3. siocrt0.s フレームポインタや、スタックポインタを設定する、電源ON後に実行されるプログラム
  4. crtinit.s 変数初期設定や、クリアを行うサブルーチン。
  5. cmbmon.x リンカースクリプト。各セクションを指定する。
ここで、cmbmon.c 以外は、一度作成すると、あまり変更する必要がなく、また、かなり汎用的なものなので、以下にその例を出しておきます。
makefile の例


電源ON後のスタートアップ、siocrt0.s の例。このソースは、EDK8.2のサンプルは、crt0.s を元に変更して作成しています


変数領域クリア、設定の、crtinit.s の例


リンカースクリプト、cmbmon.x の例。ここで、_stack を0x100ほど確保するような記述がありますが、siocrt0.s で、0x3FF0に設定しているので無効になります。この結果、セクションの指定は何もしていないことになり、0番地から順番に割り付けられます。プログラムは0番地からスタートします。このスクリプトは、EDK10.1の elf32microblaze.xr を元に作成しています。



以下は、DOSプロンプトで、make ( enter ) を実行したときのスクリーンショットです。


MicroBlaze の命令セットで、ほとんど使われない命令がある ??

アセンブラで書いても使いにくく、まして、コンパイラではまず生成されない命令群があるようです。それらの命令は、BEQ、BNE、BGT・・・などの、条件判定命令です。では、コンパイラでは何が使われているのかというと、BEQI、BNEI、BGTIなどが、使われているようです。

では、どのように使いにくいのか、命令セットの解説を調べると、BEQ命令は以下のような動作をするからと思われます。
BEQ ra,rb と書くと、
もし ra == 0 なら、PC = PC + rb という動作をします。アドレス計算は、PC 相対アドレスになるので、rb には、ラベルで設定した番地と、現在の PC のアドレスの差を用意しなければばらず、以下のような命令であらかじめ計算して、条件判断で分岐する必要があります。

BEQI命令を使うと、次の命令で、上記と同じ動作をします。


コンパイラが生成したコードを調べ、beq などの PC 相対分岐命令を探しても、多分見つけることはできないと思います。
もし、この命令が使われるとしたら、beqi では到達できない場所に直接ジャンプするときぐらいでしょう。


外部メモリでも動作できる、新たなカスタム設計の MicroBlaze 5クロック/1命令

内部ブロックメモリでしか命令を実行できないと、大きなプログラムを動作させることはできないので、全く新しく作り直すことにしました。パイプライン制御をあきらめ、単純に、命令フェッチ、命令デコード、命令実行、結果格納・・・と、順次実行させるものとしました。回路は解りやすく、変更も容易になるようにし、メンテナンス性を重視しました。出来上がったものは、結局内部ブロックRAMで1命令5クロックで動作します。このため、かなりパフォーマンスは低くなりましたが、CPUのみなら、Spartan3 で100MHzで動作可能です。4クロックでも動作するのですが、命令実行に、2クロックを割り当てて、高速なクロックでも動作できるようにしています。回路規模を大きくしても、75MHz程度が可能です。
動作テスト中に判明しましたが、EDK14.2( EDK フォルダの、gnu フォルダ以下にインストールされている)のコンパイラでは、動作しないことが判明しました。ここで使ってきているEDKのGCCコンパイラは、EDK10.1のもので、GCC4.1.1となっています。今後はEDK14.2のGCCツールチェーン(GCC4.6.2)に変えようと思ったけれど、14.2のMicroBlaze Core にいくつか命令が追加されていて、それが原因か否か、現在調査中です。
→→ EDK14.2のコンパイラでは動かなかった原因は、ディレイスロットでの命令に、ロード、ストア命令を使っていて、それが新らしい Custom MicroBlaze では正しく実行できなかった為だと判りました。GCC4.1.1の MicroBlaze クロスコンパイラでは、ディレイスロット付きの brlid 命令の次には、swi や、lwi などのメモリアクセス命令を配置しておらず、ライブラリ( libgcc.a libm.a ) でも使われていません。
brlid 命令の次の、swi や、lwi などのメモリアクセス命令でも実行できるようにすると、GCC4.6.2で作成した命令コードでも実行できるようになりました。このような、命令の並び方に起因する不具合は、単に命令単体で調査しても原因が解らず、開発中のCPUに、メモリアクセスのトレース機能や、未定義命令トラップ機能を実装して初めて解ってきました。この辺の詳細は後で詳しく解説する予定です。


新しく作成するにあたって、以下のようなクロック割り当てにします。レジスタ演算で、最短5クロックとなります。
  1. 命令フェッチは、アドレスストローブ1クロックと、メモリからのアクノリッジの最短2クロック。
  2. 命令デコードは1クロック。内部命令コードに変換。
  3. 命令実行は、1クロックか、2クロックの選択で、通常2クロック。
  4. 結果格納は、レジスタの場合1クロックとするが、次のアドレスストローブの時点で実行。このため0クロック。
  5. 結果格納がメモリの場合、アドレスストローブ1クロック、アクノリッジの最短2クロック。
  6. メモリからのアクノリッジを待つので、遅いメモリでも動作可能。内部ブロックメモリではアクノリッジで1クロック。
  7. 割り込みは、ディレイスロットが有る場合は受け付けない。また、IMM 命令の場合も受け付けない。これらは実質的に64ビット命令となり、分割できない。--> 最近のEDKでは、分割した位置で割り込みを受け付けるようです。
  8. 割り込みと関係するMSRのフラグ"IE"は、MTSとMFSで操作できる。他はキャリーフラグと、EEフラグのみ操作可能。
  9. 上記と関連して、未定義命令を実行しようとした場合、MSRのEEフラグが1だと、0x20番地にジャンプする。このときEEフラグがクリアされる。rted 命令は実装しない。EEフラグを操作するには、mts 命令を使う。未定義命令は、MSB6ビットのコードで実装していない命令のほか、できるだけ多義に渡って判定する。これは、 break 命令を実装しない代わりに、未定義命令でExceptionを発生させ、ソフトウエアや、ハードウエアを含めたデバッグに利用しようという意図である。
  10. 32個のレジスタファイルは、Xilinx専用にすると、ブロックRAMではなく、分散メモリになる。RAM16X1D プリミティブを使用。
  11. 上記でreg 宣言を行うと、XilinxではブロックRAMになり、ALTERAでも動作可能を目指す。(まだ未確認)
  12. キャッシュを搭載可能を考慮。ダイレクトマッピングで、命令キャッシュのみなら比較的小規模で可能と思われる。
  13. MMU機能を搭載可能を考慮。---> これは、Linux を走らせるぐらいの目的しかないと思われる。搭載するとしても、EDKに合わせないと使い物にならない。それに、かなり回路規模が大きくなるらしい。
  14. ブート機能を搭載可能を考慮。ブート機能は、0xFFFFF800あたりから実行し、外部フラッシメモリ、SPIメモリなどから、DRAMや内部ブロックRAMにコピーして起動できる機能。
  15. GCCのマイクロブレーズコンパイラが生成するコードを実行可能。除算、バレルシフト、不動小数点などはソフトのライブラリを使い、ハードウエア命令は当面インプリメントしない。
以下は、概略ブロック図。CPUの名称は、risc4s としています。当初、4クロックで1命令の予定だったので、この名前になっていますが、5クロックで1命令になりました。


内部ブロックメモリを使って実行した場合の波形は以下のようになります。
memas は、インストラクションフェッチの始まりで、命令メモリへのアクセスを始めることをメモリに通知します。
memack は、メモリから命令の読み出しが完了して、データバス(memdata)に命令データが確定したことを示します。
idecode は、インストラクションデコード
execinst は、インストラクション実行
write_reg は、インストラクション実行後、レジスタの書き換え。このタイミングで、次のインストラクションフェッチが始まります




これらをインプリメントしたものを、近日中にソース付きで公開します。現在のところ、CPU機能+メインメモリ+UART+タイマーで、1550スライス程度。XC3S200 にインプリメント可能(80%、100MHzは無理)。

今更SPARTAN3は古いので、(ザイリンクスのサイトで、SPARTAN3は表立って出てこない)今回は、以下のようなSPARTAN6の比較的安い評価ボードを使ってみます。AVNETの製品で、秋月電子通商で入手できるものです。このボードでは、たぶん回路規模を大きくしても、100MHzで動作すると思われます。基本構成で、約46%のスライス使用率となり、基本だけなら、120MHzの動作を確認しています。
Avnet Spartan6 LX9 MicroBoard

このボードには、
XC6SLX9-CSG324、LPDDR メモリ(200MHz、512Mビット)、
イーサネットPHY、USB−Serial(COMポート)、
USBーJTAGコンフィギュレーション
が搭載されていて、JTAGプログラマが無くても開発ができます。
また、AVNETのサイトから、多くのサンプルデザインファイルをダウンロードできます。

MicroBlaze で、Linux を動作させるという、紹介サイトや、雑誌の記事は華やかでおもしろく、かっこいいのですが、このサイトでは、目的が違っています。μClinux なら、ひょっとしたら動作可能かもしれませんが、μClinuxがどのような命令までサポートする必要があるか、調査が大変です。あくまも内臓回路のテストや評価が目的で、用がすんだら、外してしまいますが、問題が無ければそのまま残しておいて、エラーログを表示させたり、回路動作状況をモニタしたりという使い道があります。

簡単な命令をシミュレーションで始める

SOCを含むCPU全体をいきなりシミュレーションしても、まず動かないので、命令コードを入力して所定の結果が出るか否かの部分から始めます。これを行える回路に必要なのは、
  1. レジスタファイルを用意します。32ビットのレジスタを32個で、3ポートにします。3ポートメモリは標準で無いので、2ポートを2個使い、ライト時には同じデータを書いて、常に同一の内容になるようにしておきます。シミュレーションでは、reg 宣言でもいいですが、実機にインプリメントするときは、結構な容量になってしまいます。フリップフロップが1024個になるので、これだけで512スライスにもなってしまいますから、Xilinxの分散メモリで作成します。RAM16X1D( 16 bit メモリ ) を64個使って、32個のレジスタとし、それを2組で3ポートメモリとします。
  2. メモリにインストラクションフェッチ命令を出す。メモリはテストベンチ側で用意しておく。
  3. テストベンチ側で要求に応える。アドレスはなく、データバスに順番に命令を出すだけとします。
  4. データが確定(ACK応答を見る)すると、デコーダで機能を振り分ける。ADDI、ADD、RSUB、AND、OR、XOR、などを試してみます。
作成した、簡単なシミュレーションの ソース一式 です。(old version )
命令デコード、コード変換を行う、idecode.v は、最終に近いものです。SRCs フォルダに、risc4s.v idecode.v exeinst.v reg32.v regfxlib.v uart1.v(これは使っていません) があり、SW フォルダに、テスト用のアセンブラソース、gcc 用 makefile リンカスクリプトの asmtestloc.x 、出来上がった、HEXファイルから、シミュレーション用の32ビット命令( risc4.txt )を作成する、hexutils.exe などが含まれます。TB フォルダには、テストベンチと、命令語として読む、risc4.txt があります。gcc は、EDK14.2付属のGCC4.6.2です。ISE14.2をインストールしたEDKの gnu\bin と gnuwin\bin にパスを通せば、コマンドラインで( make enter )アセンブルできます。gnu のツールは、EDKのライセンスが無くても使用できます。

簡単な命令と言っても、32ビットのマシン語をそのままテストベンチの命令語ファイル、risc4.txt をエディタで書くのは大変なので、GCCツールを使いますが、ISE14.2をインストールした場所を、以下のようにしてコマンドラインで使えるように、パスを設定します。


アセンブラが使えて、ヘキサ形式の命令語ファイルができたとしても、テストベンチで使うヘキサ形式そのものではないので、ツールで変換できるようにしたものが、SRCs.lzh の、SW フォルダにある、 hexutils.exe β0.17 です。
以下は、アセンブラで作成したヘキサファイル asmtest.mot を、テストベンチ用の命令語 risc4.txt に変換しているときの、操作設定例です。



ロード、ストア命令を追加

いわゆる、RISCと名づけられたCPUは、メモリへのデータアクセスがこれらの命令に限定され、メモリ−−レジスタ間の演算などは無いことが多く、インプリメントは比較的楽ですが、メモリへのデータアクセス方法で大きなアーキテクチャーの選択が迫られます。それは、命令用メモリと、データ用メモリを、別個に設けるか否か、という選択です。ただこの選択は、MicroBlaze のCPUコアの問題ではなく、メモリインターフェイスの設計上の問題になります。
「命令用メモリと、データ用メモリを、別個に設ける」方式を、ハーバードアーキテクチャーと呼ばれるようですが、ググると、「命令キャッシュとデータキャッシュを分離した方式」という説明もあり、厳密な意味ではこちらが正解のようです。インテルの x86 にしろ、PowerPCにしろ、メジャーなハイエンドCPUでは、命令キャッシュとデータキャッシュは分離しています。ただ、外部のメモリ(DRAMで構成されるメインメモリなど)は分離していません。そのため、CPUチップと、外部のメインメモリの構成形態から見ると、x86 などほとんどは、ハーバードアーキテクチャーには見えません。もし、CPUチップの外部メモリ接続部分が、命令用とデータ用とに分離していたら、たしかにパフォーマンスは向上し、誤って命令が書き換わることも無く、安全かも知れませんが、比較的小さい命令用メモリにプログラムが入りきらない場合、データ用メモリから借りてくることができず、HDDと頻繁にスワップが起こって、とても遅いCPUになってしまいます。

ともあれ、Custom MicroBlaze では、当面キャッシュは実装しないので、命令もデータも同じメモリを共有した、見た目も内実もノイマン型CPUとします。FPGAのブロックRAMを使うと、デュアルポート機能を使って、パーフォーマンスの向上ができますが、1ポートしか設けない外部メモリでも同じように走るようにするには、この機能を外さないといけないし、そのために判定回路も必要になるので、ボトルネック満載のノイマン型とします。しかも、デコードと命令実行中にメモリバスを有効利用するという、パイプライン制御も外して、逐次実行とします。FPGAの回路のモニタリングや、テストを目的としたプロセッサなので、使いやすく(外部インターフェイスは単純なマイコンのバス)、使用ゲート数が少なく、高速なクロックまで耐えるというのを最優先するためです。ロード命令では以下のようになります。() 内は、ブロックRAMでの必要クロック数です。
命令フェッチ(2) > 命令デコード(1) > 命令実行(2) > メモリリード(2) > レジスタにライト(1)

作成した、ロード、ストア命令を追加した ソース一式 です。
ロード、ストア命令を実行するため、CPU の動作を管理している、cpustate に2つの状態を追加し、メモリへのアクセス要求(ストアの場合はライトするデータを用意する)、メモリからの応答(ロードの場合は、読んだデータの所定のデータ位置からレジスタに書く32ビットに変換し、レジスタ変更のフラグを立てる)、その後、cpustate を1にして、次の命令フェッチに移ります。
テストベンチ側では、CPUからの要求に従って、アドレスを割付け、ライトの場合は、シミュレーション用メモリを書き換える機能を追加します。
また、メモリアクセスをさせるテストプログラムを用意して、シミュレーション用プログラムデータ、risc4.txt を作成します。

シミュレーションで以下の命令を実行させて、結果を波形で見てみます。

以下は、ISim でシミュレーションしたときの、スクリーンショットです。右の黄色のカーソル位置は、”# 0x100 番地から 0x98761132 を r4 に読む”の命令を実行し、レジスタの、r4 を書き換える直前です。ハイライトさせている、 wd[31:0] は、レジスタへのライトデータ、regwenr はレジスタへのライトイネーブル、rd[31:0] は、レジスタ rd の内容です(リアルタイム表示)。レジスタ rd の内容は、次のクロックの立ち上がりで、0x98765432 から、0x98761132 になっているのが確認できます。



UARTを追加して1文字を送信

メモリへのライトができたので、テストベンチ側でUARTを実装し、UARTのチップセレクトを生成するアドレスデコーダと、メインメモリへのアクセスと区別する機能を入れます。UARTは最も基本的な機能のものとします。2文字以上を送信するとなると、「先に送った文字の送信が終わったか」という条件判定が必要になるため、それは次の段階とします。テストベンチと、ターゲットのプログラム操作で、UARTから信号が出てくるかという基本的な動作を確認します。UARTを実装して先にテストするのは、Custom MicroBlaze の操作ポートとして、非常に重宝するからです。

UARTを追加して1文字を送信する ソース一式 です。 CPUのコア部分は変更なしです。

UARTから1文字を送信するプログラムは以下のようになります。UARTのデータポートのアドレスは、0xFFFF0007 で、バイトポートです。ボーレートは、ISim ですぐに結果が見られるように、50Mbpsの設定にしています。nop を追加しているのは、UARTの 送信( txd )波形を見る間、CPUも動作するので、nop にして ISim のエラー表示を少なくするためです。

以下は、ISim のスクリーンショットです。カーソルの位置で、UARTのデータポートに、0x41を書いています。UARTからの送信波形は、txd です。最初にHighからLowに変化して2クロック間が、スタートビット、その後、LSB−−>D1,D2,D3,と続きます。テストベンチでUARTへの設定を50Mbpsにしているので、1ビットが2クロックです。



GCCコンパイラに対応するのに必要な命令の追加とテスト

GCCコンパイラに対応するには、残りの多くの命令を実装することになりますが、ブランチ命令、ブランチアンドリンク命令(サブルーチンコール)、ディレイスロット付き命令、条件判断命令、比較命令、シフト命令、そして割り込みや、例外処理に必要になってくる、MSRレジスタ操作命令の追加で、基本的な機能は完成します。そのほかの、乗算、除算、バレルシフト、浮動小数点、は、実装しません。GCCコンパイラのオプションで指定しない限り、これらの命令コードは生成されません。GCCのデフォルトオプションは以下です。なにも指定しなければ、乗算、除算、バレルシフト、浮動小数点コードは生成されません。

-mxl-soft-mul ハードウエア乗算命令を使わない。
-mxl-soft-div ハードウエア除算命令を使わない。
-mno-xl-barrel-shift ハードウエアバレルシフト命令を使わない。
-msoft-float ハードウエア浮動小数点命令を使わない。

GCCコンパイラに対応するのに必要な命令の追加とテスト ソース一式 です。

コンパイラで作成したプログラムを実行させる前に、ブランチ命令、サブルーチンコール命令、ディレイスロット付き命令(サブルーチンからのリターン命令は、rtsd のみで、ディレイスロット付きになる)などを使った以下のようなテストプラグラムを作成します。今までのテストプログラムとは違い、0番地以降の使用目的が決まっている領域、0〜0x4Fを、MicroBlaze のベクター領域として扱っています。そのため、リセットスタートの0番地が、いきなりブランチ命令となります。

このテストプログラムでは、0番地から _start1(リンカスクリプトで、0x50番地となるように指示している) に飛び、スタックポインタを設定し、次に _start2 にブランチします。このブランチ命令は、ディレイスロット無しなので、次の行の ”# 送信文字は、'A' ” の命令は実行されません。
次に、_start2 から、putcons をサブルーチンで呼び出しますが、ディレイスロット付きの、brlid なので、”addi r5,r0,'H' # 送信文字は、'H' ”を実行してから putcons にブランチします。r15 には、この命令のアドレスが記録され、リターンアドレスの情報となります。
次に、サブルーチン putcons では、r15 を退避してから putcon を呼び出します。r15 は、サブルーチンのネスティングがある場合は、必ず退避します。なぜ r15 なのか、MicroBlaze の仕様では決められていません。 r15 をサブルーチンからの戻りアドレスの記憶に使うのは、MicroBlaze の mb-gcc.exe が定めている規則です。r15 の退避はスタックエリアを使いますが、push という命令はなく、r1 を単なるポインタとしたストア命令で退避します。プリデクリメントや、ポストインクリメントの命令もないので、ポインタ変更も即値命令で行います。
putcon では、送信前に、送信中か否かのUARTのフラグをチェックして、0でなかったら0になるまで待ちます。テストベンチでは、50Mbpsなので、チェックは常に0になってしまいます。
putcons で、文字”H ”をUARTで送信したあと、”呼び出したもとのアドレス+8番地”に戻ってきます。このため、”rtsd r15,8 ”と、記憶していたアドレスに8を加算したアドレスにリターンします。
戻ってくると、r5 に 0x114ををロードし、2回右シフトして、0x45にした後、UARTのデータポートアドレス( 0xFFFF0007 )を r8 に設定して、ディレイスロット付きの、ブランチ命令で loop2 にブランチします。ディレイスロットでは、UARTのデータポートにストアしているので、UARTから、0x45が送信されます。

以下は、ISim でシミュレーションしたときの、スクリーンショットです。ハイライトさせている、txd 信号が、0x48 と、0x45 を送信しているのが確認できます。



これら一連のプログラムが正常に動作すれば、ここで、実機で検証してみます。実機では、risc4s_tb.v の代わりに、TOPモジュールに置き換え、外部ピンは、クロック、TX、RX、リセット のみになります。

実機検証用のTOPモジュールを作成して、Hello World!( Avnet Spartan6 LX9 MicroBoard )

いよいよ実際のFPGAで動作させてみますが、テストベンチでは、50Mbpsと 高速なUART でしたが、実機では、115200bpsとなりますので、UART回路を正しく使うため、送信開始は、必ずビジーチェックをします。また、1〜2文字出すだけでは面白くないので、定番の”Hello World!”を出し、その後、ターミナルから入力した文字をエコーバックするようにします。プログラムは以下になります。


このプログラムをアセンブルして、出来た testasm.mot から、メインメモリのブロックRAMの初期化データを、
このツールを使って→ hexutils.exe β0.17 mainmem.v として作成します。
このツールは、VC++2005 で作成しているので、Microsoft VC++2005用ランタイム 再頒布可能パッケージ が必要な場合があります。
以下は、アセンブラで作成したヘキサファイル asmtest.mot を、メインメモリの用の mainmem.v に変換しているときの、操作設定例です。
このツールの操作で重要なのは、defparam, 4 bit (0), Verilog header (チェック)の選択です。4 bit (0) は、ブロックRAMを8個使い、その8個のメモリの初期化データ、defparam を作成して、Verilog header を追加して、mainmem.v モジュールを作成します。



TOPモジュールでは、CPUのクロックを、66.66MHzのクロックから100MHzを生成するDCMのほか、メインメモリモジュール mainmem.v ( 上記のプログラムで初期化する defparam を含む )、アドレスデコーダー、タイマー、UART、を実装します。また、Avnet Spartan-6 LX9 MicroBoard のクロック入力ピン、UARTポート、リセット入力ピンを指定する、risc4s_soc_top.ucf を用意します。risc4s_soc_top.ucf は以下です。

実機で動作させるファイルはこちら。 ソース一式 です。
このソースでは、100MHzで動作させるものになっています。使用スライス数は、663で、LX9では46%の使用率となっています。ソースの、SOCTOP フォルダ以下には、トップモジュールの risc4lx9_soc_top.v、ucf ファイル、uart1.v、 mainmem.v があります。risc4s フォルダ以下は、CPU のコア部分があり、この2つのフォルダ以下すべてをISEで設定すれば、実機で動作する、bit ファイルが出来ます。SW フォルダ以下は、アセンブラソースと、makefile など、そして、変換ツール hexutils.exe と、verilog header file の、mainmem16k.v などがあります。risc4s フォルダ以下のCPUコア部分は、三角関数のライブラリなども使ってテストしていて、コンパイラで記述する限り問題なく動作すると思われます。
以下は、UARTターミナルで”Hello World!”を表示させ、キーボードから送信した文字がエコーバックで表示しているスクリーンショットです。


Hello World! を表示できたところで、次は、コンパイラで書いたプログラムを実行させる

FPGA回路のチェックや、基板のチェックを目的としたソフトプロセッサなので、UARTを介したコマンドライン感覚で使えるモニタは、必須機能になります。コマンドラインで操作できると、ターミナルソフト(テラタームなど)のマクロ機能を使えるので、製造ラインにおける装置のチェックなどを、自動的に行い、結果をファイルに残すことも簡単になります。
  1. リセットスタートで走る、アセンブラ記述のプログラムから、Cコンパイラで書いた、main を呼び出すように追加。
  2. Cコンパイラで使う変数領域のクリアを、アセンブラで記述する。
  3. Cで書いたプログラムから、アセンブラ記述のサブルーチンを呼び出すテストをする。
  4. Cコンパイラで割り込み処理関数を記述し、インターバルタイマで割り込みを発生させて動作の確認をする。
  5. Cコンパイラで書くプログラムに、メモリなどの変更や、任意番地からの実行ができるモニタ機能を入れ、UARTを介したコマンドで上記のテストができるようにする。
モニタの操作と機能はこちら→ Custom MicroBlaze モニタの操作
CPUコアのハードウエアには、以下のような機能を追加します。コンパイラが生成するコードの実行に関してはほぼ実装しているので、次は、例外処理機能( RTED命令など )の追加と関連するレジスタを追加します。これらの命令追加やスペシャルレジスタの実装は、それぞれ特殊な処理になってゲート数を増加させるので、必要最小限にとどめます。MSRのフラグは、EEフラグのみ追加とし、スペシャルレジスタは、EAR( Exception Address Register )と、ESR ( Exception Status Register )の未定義命令と、バスエラーとします。これらは、チェック目的のCPUでもプログラムのデバッグが必要だし、しかもハードウエア寄りのデバグの場合に検出手段として役立つと思われるからです。これらを実装すると、約80スライスほど増加して、LX9では約51%の Number of occupied Slices になります。 命令をいくつか追加するので、8ビットで中間コードを表現するのが困難になり、9ビットに拡張となります。このため、idecode.v と、execinst.v の両方で中間コードの扱いを修正します。

コンパイラで書いたプログラムを実行させる ソース一式 です。
モニタの機能以外に、三角関数 ( sin() ) の計算テスト、割り込みテスト、ハードウエア例外テストなどを行えるようにしたので、ブロックRAMを16個使って、ほぼ32kバイトになっています。
以下の図は、sin() 関数を、1度単位で、0〜100度まで、少数点以下9桁で表示させたときの、79度〜100度部分の結果です。sin() では、倍精度で計算し、有効桁9桁まで出しています。9桁になったのは、使ってる cprintf 関数 が、整数32ビットまでなので、1000000000.0 倍して整数に変換し、9桁指定で10進表示しています。9桁ではどこまで精度があるのかわからないので、倍精度浮動少数点の64ビットデータを、ヘキサでそのまま表示できるようにもしています。そして、Windowsのコンパイラ、VC++2008 ExpressEdition で同じ計算をさせて結果を比較すると、倍精度浮動少数点の64ビットのヘキサデータで、0〜100度で、完全に一致していました。少なくとも、VC++2008と、GCC4.6.2の MicroBlaze のライブラリでは、倍精度の sin 関数では一致しているということです。
コマンドは、CMB-Bug>F0,0,65,64


下の図は、VC++2008 Express Edition で同じ計算をしたもの。
コマンドは、>sintest 0 0 101 100


以下は、パソコンでの計算プログラム


三角関数のテスト以外の、未定義命令の実行テスト、メモリの無いアドレスへのアクセスによる、タイムアウトバスエラーの動作確認が以下のスクリーンショットです。
FPGAのコンフィギュレーション後、
Custom MicroBlaze AVNET LX9 microboard 2013.2.10-1 と起動メッセージが出て
CMB-Bug>sl7fc0 ( enter )
00007FC0=00000000 ff000000 ( enter ) で7FC0番地を、long で、0xFF000000 を書き、
CMB-Bug>e1 ( enter ) で、ハードウエア例外を可能にする命令 ( e1 ) で、MSRのEEレジスタを1にして、
CMB-Bug>g7fc0 ( enter ) で7FC0番地を実行すると、未定義命令なので、
Hardware Exception at PC=00007FC0 ADDR=00007FC0
Custom MicroBlaze AVNET LX9 microboard 2013.2.10-1
CMB-Bug> と、Hardware Exception が0x7FC0番地で発生したと報告しています。
また、
CMB-Bug>s7000000 ( enter ) で、0x7000000番地のメモリを変更しようとして
07000000=   アドレスは表示されたが、内容が表示されず、
Hardware Exception at PC=00001278 ADDR=07000000
Custom MicroBlaze AVNET LX9 microboard 2013.2.10-1
CMB-Bug> と、リード命令の 0x1278番地で、0x7000000番地をアクセスしようとして、バスタイムアウトしています。


LPDDRメモリでプログラムを走らせる( Avnet Spartan6 LX9 MicroBoard )

初版の Custom MicroBlaze では、プログラムはブロックRAMに入れておく必要がありましたが、新規開発版では外部メモリで動かすというのを第1の目標としていたので、いよいよ本命にとりかかります。今回使ったFPGAは、SPARTAN6 LX9 のMIGでDRAMが使える評価基板なので、LPDDR メモリを操作する回路を追加します。XilinxのMCBの使い方は、 こちらなどを参考にしていただいて、 CORE GeneratorでLPDDRのIPを作成します。IPは、名前を、mymig2 として別のプロジェクトで作成し、必要な部分( ipcore_dir\mymig2\user_design\rtl\ 以下すべて)を開発中のソースにフォルダごとコピーすれば使うことができます。LPDDRのIP設定は以下のようになります。


この表には出ていませんが、CORE Generator では、メモリのクロックは外部ピンから直接入力したクロックをソースにするようなRTLが作成されるので、rtl フォルダ以下にある、infrastructure.v を編集して、TOPモジュールで作成したCPUクロックの100MHzを使うようにします。
end else if (C_INPUT_CLK_TYPE == "SINGLE_ENDED") begin: se_input_clk
//***********************************************************************
// SINGLE_ENDED input clock input buffers
//***********************************************************************
/* // この部分をコメント
IBUFG u_ibufg_sys_clk
(
.I (sys_clk),
.O (sys_clk_ibufg)
);
*/

assign sys_clk_ibufg = sys_clk;// 追加
end

また、200MHzの外部入力クロックで作成したので、100MHzの入力で、メモリクロックを200MHzにするため、IPのTOPモジュール( mymig2.v )、の localparam を変更します。 上が変更前、下が変更後

そのほか、LPDDRに接続されるピンが、ipcore_dir\mymig2\user_design\par\mymig2.ucf としてジェネレートされているので、この中身を今まで使ってきた UCF ファイルに追加します。

メモリコントローラの制御

生成した、mymig2.v とCPUをインターフェイスするため、migctl.v を作成します。この中で、mymig2.v を呼び出します。CPUとは、64ビットのデータポートを使い、すべて100MHzで操作します。他に32ビットポートが2本ありますが、こちらはとりあえず使いません。CORE Generator の選択で128ビットで生成する方法もありますが、バスが1本だけになり、CPU以外で操作するには、自前でアービタを作成する必要があるので、64ビット+32ビット+32ビットになりました。
1回のアクセスで64ビットなので、CPUのアクセス幅32ビットでは、最大でも半分しか使うことができませんが、メモリ側では1クロック(200MHz)増えるだけなので、5nsはほとんど問題になりません。Xilinx のメモリコントローラユーザーズガイド( ug388.pdf )を元に、インターフェイスポートの信号に合わせて制御回路を作成しますが、Command Path Timing のタイミング図を見ると、コマンド( cmd_en )を出す1クロック前にコマンドコード( cmd_inst )などを確定させているように見えます。しかし、説明文によると、「クロックの立ち上がりで同時に内部に取り込まれる」となっているので、そのように制御します。だたライトの時は、1クロック先にデータを書いてからライトコマンドを出します。
リードの場合は、リード命令( 011 など) を出すと、メモリからデータが読まれ、rd_empty が LowになるとFIFOに読まれたということで、次に rd_en を出すのですが、この rd_en はリードパルス(rd_data にデータを出す)という意味とは違い、FIFOを進めるという信号です。複数のデータ( 8バイト単位 )をメモリから読んだ場合、rd_en がHighでクロックの立ち上がり後、次のデータが、rd_data に現れます。ですから、シングルアクセスしかしない場合は、rd_empty が Lowになれば、rd_data をレジスタにラッチすると同時に、rd_en をHighにします。

下の図は、バースト長2で、リード命令を出した場合のタイミングです。この図では、rd_empty が、命令後4クロックでLowですが、今回の mymig2.v でLPDDRメモリを操作した場合は、13クロックです。(リフレッシュがあるので時々大きくなる)


migctl.v ができたところで、CPUからアクセスするため、LPDDRの配置アドレスを決め、LPDDRのチップセレクトで migctl.v にアクセス要求を出し、migctl.v からの応答で完了するという回路を、TOPモジュールに作ります。LPDDRのマッピングは、0x8000000番地から32Mバイトという設定にしました。

LPDDRメモリ制御を実装したソース一式 → SRCsmiga.lzh 2013.2.17 更新
旧 SRCsmig.lzh はメモリダンプなどの表示に、Dhrystone の printf のため cprintf を改造したため、多くのバグがあります。 → 旧 SRCsmig.lzh 2013.2.15
ここには、0x8000000番地にマッピングしたLPDDRメモリで走らせるオブジェクトを作成する makefile, リンカースクリプトを含んでいます。そして、CPUの性能を簡単に測定する、Dhrystone 2.1 も、モニタに組み込んでいます。
Dhrystone 2.1 を走らせるには、起動後、
CMB-Bug>D2710 ( enter ) で、10000回実行して、結果を表示します。
ブロックRAMでは、
Microseconds for one run through Dhrystone: 48.6
Dhrystones per Second:                      20550 
になり、
CMB-Bug>l ( enter ) として、パソコンから cmbiofintd.mot を送ると
start loading address = 08000000
END.     bytes loaded = 00003CF6
 とLPDDRメモリの0x8000000番地から同じモニタがロードされるので、
CMB-Bug>g8000000  ( enter ) として、8000000 番地から実行すると、
Custom MicroBlaze AVNET LX9 microboard 2013.2.15-1
CMB-Bug>D2710 ( enter ) で、
Dhrystone Benchmark, Version 2.1 (Language: C)
Program compiled without 'register' attribute

Program compiled with optimizing option (-O2)

Please give the number of runs through the benchmark: 10000
Execution starts, 10000 runs through Dhrystone
と、Dhrystone 2.1 のループが始まり、
2秒ちょっとで、( 途中は省略 )
Microseconds for one run through Dhrystone: 249.0
Dhrystones per Second:                      4010 
と 約2.3 VAXMIPS の結果を出します。LPDDRメモリでは、ブロックRAMの約1/5のスピードになっています。このスピードは、調べてみると、昔のNECのパソコン、PC9801RA( 80386 16MHz キャッシュ無し DRAM )程度です。

キャッシュを実装してスピードテスト( Avnet Spartan6 LX9 MicroBoard )

キャッシュ無しで DRAM で実行すると、Dhrystone 2.1 で、80386 16MHz ぐらいというのが遅いのか早いのか、比較対象が古すぎるので、ルネサスマイコン、RX62Nなどと比較すると以下の差があります。
Dhrystoe 2.1
CPUと動作条件
 Dhrystones per Second 
 VAXMIPS 
risc4s BRAM gcc 4.6.2
 20550 
 11.69 
risc4s LPDDR メモリ gcc 4.6.2
 4010 
 2.3 
RX62N 96MHz( 内蔵 RAM ) gcc 4.6.0
 119330 
 67.9 
SH7144(SH2) 48MHz( 外部 16bit SRAM ) gcc 4.0.2
 約2500 
 約1.4 
SH7750(SH4) 240MHz( 外部 32bit SDRAM 80MHz) cache OFF gcc 4.6.0
 9527 
 5.4 
SH7750(SH4) 240MHz( 外部 32bit SDRAM 80MHz) cache ON gcc 4.6.0
 354609 
 201.8 

キャッシュを実装しようとすると、最低限のサイズでも、それなりにゲート数とブロックRAMが必要なのですが、どれだけ早くなるのか、試してみようと思います。キャッシュ制御を入れると、10%から15%ほど必要スライスが増えるようです。
Spartan6 LX9 で、スライスを60%ぐらい使ってる状態で、いろいろ回路を入れてみて気づいたのですが、ちょっとの回路を入れるか入れないかで、使用スライスが5%ほど増減することがあります。しかも、回路を増やすと、減るとこともありますし、
ちょっと判定の方法を変えるだけで、5%ほど増えたり減ったりするようです。

最低限のキャッシュとして、ブロックRAM1個で2kバイトになりますが( 汎用性を考え、 Spartan6 の1kBは使わない )、ダイレクトマッピングでも、タグメモリとして1個のブロックRAMが必要なので、2個必要です。ここで、ヒット率を考慮して、命令用と、データ用を分離し、ハーバードアーキテクチャ−としてみます。なぜ分離するかというと、命令はリードのみ、データはライト処理も必要だからです。命令用2kバイト、データ用2kバイト( 簡単にライトスルーのみ )、上位アドレスタグメモリとして、それぞれ独立した512バイト必要なので、合計4個のブロックRAMを使います。キャッシュの単位は、MIGのバス幅8バイトなので、ラインサイズは8バイトになります。Xilinxのキャッシュは、ラインサイズとして、16バイトと32バイトの選択になっていますが、MIGの制御が複雑になってくるので、8バイトのままとします。また、キャッシュ制御専用の命令は実装しません。キャッシュ制御命令を実装しなくても、メモリマップ上に、キャッシュメモリとタグメモリに直接アクセスできれば用は足りるからです。キャッシュを有効にするか、否かは、MSRレジスタのフラグ( ICEとDCE )で行うのですが、テストなので、メモリマップ上に設けたレジスタのフラグで行います。
また、DRAMに対してDMAで内容を変更したりすると、そのエリアのキャッシュを無効化 ( invalidate ) する必要がありますが、これもメモリマップ上に設けたタグメモリクリア専用エリアに、対象となるDRAMのアドレス値をライトすると、キャッシュに読み込まれていたら、そのタグメモリを無効化するようにします。

命令キャッシュの構成


データキャッシュの構成


キャッシュ関連のモニタ操作の追加
キャッシュ機能を実装しても、それらを設定するのに、キャッシュ制御レジスタを直接アドレス指定で操作するのは間違いも多いので、モニタのコマンドで操作できるようにします。キャッシュのON、OFFだけなら簡単ですが、キャッシュに読み込まれたエリアを部分的に無効化するため、タグメモリのフラグクリアは、タグメモリを直接アドレス指定で行うのは簡単ではありません。モニタコマンドで、これらを操作しているのが以下の例です。
Custom MicroBlaze AVNET LX9 microboard 2013.2.24-1
CMB-Bug>c ( enter ) として現在のキャッシュ設定を表示します
i-cache OFF
d-cache OFF
CMB-Bug>c1 ( enter ) として、命令キャッシュを有効にします
CMB-Bug>c
i-cache ON
d-cache OFF
CMB-Bug>c3 ( enter ) として、命令、データキャッシュを有効にします
CMB-Bug>c
i-cache ON
d-cache ON
CMB-Bug>
 以下の操作アドレス、0xA100000 は、0x8100000 のイメージアドレスで、キャッシュ無効で操作できるようにしています。
CMB-Bug>sla100000 ( enter ) として、0xA100000 番地から簡単な命令を書きます
0A100000=00000000 30601234 ( enter ) これは、addik r3,r0,0x1234 で、r3 を、0x1234 にします
0A100004=00000000 b60f0008 ( enter ) これは、rtsd r15, 8 で、モニタに戻ります
0A100008=00000000 80000000 ( enter ) これは、nop で、遅延スロットです
0A10000C=00000000 
0A100010=00000000 30609876 ( enter ) これは、addik r3,r0,0x1234 で、r3 を、0xFFFF9876 にします
0A100014=00000000 b60f0008 ( enter ) これは、rtsd r15, 8 で、モニタに戻ります
0A100018=00000000 80000000 ( enter ) これは、nop で、遅延スロットです
0A10001C=00000000  コントロールC
CMB-Bug>g8100000 ( enter ) 0x8100000 を実行します
ret= 00001234    g コマンドで、リターンしてくると、r3 の内容を表示します
CMB-Bug>g8100010 ( enter ) 0x8100010 を実行します
ret= FFFF9876
CMB-Bug>swa100002 ( enter ) で、キャッシュ対象の命令コードを強制的に変更します。
           DMA で変更するのと同じです
0A100002=1234 4321
0A100004=B60F  コントロールC
CMB-Bug>swa100012( enter ) で、キャッシュ対象の命令コードを強制的に変更します
0A100012=9876 6789
0A100014=B60F  コントロールC
CMB-Bug>g8100000 ( enter ) 0x8100000 を実行します
ret= 00001234    DMA で内容が変更されたにもかかわらず、前の結果と同じです。
          つまりキャッシュに読んだ命令を実行していることになります
CMB-Bug>g8100010 ( enter ) 0x8100010 を実行します
ret= FFFF9876         結果は以前と同じ、キャッシュに読んだ命令を実行しています

CMB-Bug>d8100000,8100020 ( enter ) 実行した番地を表示してみます
ADDR      0 1  2 3  4 5  6 7  8 9  A B  C D  E F  ascii
08100000 3060 4321 B60F 0008 8000 0000 0000 0000 *0`C!............*
08100010 3060 6789 B60F 0008 8000 0000 0000 0000 *0`g.............*
CMB-Bug>   r3 には、0x4321 、また、0x6789 がロードされる命令になっています

CMB-Bug>d6002800,6002820 ( enter ) で、キャッシュのタグメモリを表示してみます
ADDR      0 1  2 3  4 5  6 7  8 9  A B  C D  E F  ascii
06002800 0000 8200 0000 0000 0000 8200 0000 0000 *................*
06002810 0000 8200 0000 0000 0000 8200 0000 0000 *................*
  タグメモリは、16ビットですが、8バイト境界(ラインサイズ)に配置して表示できるようにしています

  ここで、命令キャッシュに読んだ領域を無効化するため、
  "cii" 命令で、0x8100000 から、0x10 バイト無効にします cache invalidate instruction
CMB-Bug>cii8100000,10 ( enter ) 
CMB-Bug>d6002800,6002820( enter ) で、キャッシュのタグメモリを表示してみます
ADDR      0 1  2 3  4 5  6 7  8 9  A B  C D  E F  ascii
06002800 0000 0000 0000 0000 0000 0000 0000 0000 *................*
06002810 0000 8200 0000 0000 0000 8200 0000 0000 *................*
  0x6002802 と、0x600280A 番地の、0x8200 が、0x0000 になっています。
    0x8200 は、16bit MSB が、有効フラグ、LSBの D13---D0 が、A24----A11 に対応し、
  DRAMアクセス時に一致すれば、キャッシュがヒットしたことになります


  無効化したので、再度実行してみます
CMB-Bug>g8100000
ret= 00004321  今度は、変更後の命令を実行したので、0x4321 となっています

  無効化していない方は以前のままです
CMB-Bug>g8100010
ret= FFFF9876


  0x8100010 の方も無効化して、キャッシュのタグメモリを表示して、実行してみます
CMB-Bug>cii8100010,10
CMB-Bug>d6002800,6002820
ADDR      0 1  2 3  4 5  6 7  8 9  A B  C D  E F  ascii
06002800 0000 8200 0000 0000 0000 8200 0000 0000 *................* g8100000 で実行して再びキャッシュが有効です
06002810 0000 0000 0000 0000 0000 0000 0000 0000 *................*
CMB-Bug>g8100010
ret= 00006789 無効化されたので、DRAMから再度読んで更新したので、正しい結果になります
CMB-Bug>

データキャッシュタグのクリアは、"ci" 命令で、ci8200000,10000 ( enter ) などとし、0x8200000 番地から 0x10000 バイトのエリアを無効化します。
DRAMは、0x8000000 --- 0x9FFFFFF の32Mバイトですが、0xA000000 --- 0xBFFFFFF で操作すると、キャッシュ機能をONにしてもキャッシュ対象にせずにアクセスできるようにしています。ルネサスマイコンの、SH3、SH4でのマッピング機能と同じようなものです。

以上のキャッシュを実装したソース一式→  SRCs_cache.lzh

キャッシュを有効にした回路のスピード比較。キャッシュ無しでも、今回の回路では、DRAMのアクセスで、4バイト先読みしているので、若干早くなっています。
Dhrystoe 2.1
動作条件
 Dhrystones per Second 
 VAXMIPS 
risc4s BRAM gcc 4.6.2
 20550 
 11.69 
risc4s LPDDR メモリ 旧回路
 4010 
 2.3 
risc4s LPDDR メモリ キャッシュ実装 cache OFF
 4970 
 2.8 
risc4s LPDDR メモリ キャッシュ実装 d-cache ON
 6430 
 3.66 
risc4s LPDDR メモリ キャッシュ実装 i-cache ON
 7080 
 4.03 
risc4s LPDDR メモリ キャッシュ実装 i-cache d-cache ON
 11050 
 6.29 


 SPIメモリを操作してみる( Avnet Spartan6 LX9 MicroBoard )

この評価基板のコンフィギュレーションに使ってるSPIメモリは、16Mバイトあり、FPGAのビットファイルのほか、デモプログラムとして、160kバイトほど書かれていますが( 0x200000 ---> 0x227790 ? )、ほとんど空いていて、0x500000 以降なら自由に使えるみたいです。( 0x400000 には、少しだけ書かれていて、0x401000 からでも使えます。)
SPIメモリ制御を実装したソース一式 → SRCsspi.lzh 2013.3.3
このボードの起動メッセージは次のようになっていて、デモプログラムを、SPIメモリからロードしています。

起動メッセージに、IPアドレスの記述があるところを見ると、イーサネットが動作するみたいで、LANケーブルを接続して "ping 192.168.1.10" と、パソコンからコマンドを送ると、返答があります。

Avnet 評価基板の工場出荷時のSPIメモリのヘキサデータを、 hexutils download  hexutils.exe でロードし、先頭から0x225D90バイトあたりを表示すると、以下のような箇所があって、起動メッセージの書式が格納されているのが判ります。
Avnet 評価基板の工場出荷時のSPIメモリのヘキサデータはこちら。 → ユーザー登録、ログインが必要



Avnet 評価基板のSPIメモリは、回路図から読み取ると N25Q128 で、データシートの82ページ(あたり)に以下のリード波形が載っています。

この波形を元に、SPIメモリを読む回路を作成します。SPIメモリのリードは比較的簡単な回路なのですが、後ほどSDカードにも流用したいので、ライトもできるようにします。FPGAのコンフィギュレーションに使ってるので、ライトのテストは慎重さが要ります。実際、テスト中にFPGAの bit データの一部を破壊してしまったので、PowerON起動ができなくなってしまいました。←ライトが完成してから復帰できました。

以下は、SPIメモリを操作する回路です。FPGA回路は簡単ですが、この回路を操作するためのTOPモジュールでのアドレスデコードとインスタンシエート、そしてMicroBlzeの操作ソフトも必要です。リード、ライトの区別(writeen)、バイト数指定(mode[1:0] 1、2、4バイト指定)、連続リードなのか、否か( mode[2] )、連続ライトなら0x100バイトで区切ること( ソフトでの操作 )などができます。
// simple spi control
module spictl2(
	clk,
	writedata,
	writeen,
	mode,
	readdata,
	readen,
	romaddr,
	romack,
	spibusy,
	spiclk,
	spidout,
	spidin,
	spicsn,
	reset
);
input		clk;
input	[31:0]	writedata;
input		writeen;
input	[3:0]	mode;
output	[31:0]	readdata;
input		readen;
input	[27:0]	romaddr;
output		romack;
output		spibusy;
output		spiclk;		// 50MHz 
output		spidout;	// SPIコマンド、データ出力
input		spidin;		// メモリからのデータ
output		spicsn;		// SPI chip select
input		reset;
//
reg	[3:0]	spiseq;
reg	[5:0]	spishiftcount;
reg	[5:0]	bitcount;
reg	[31:0]	spishiftr;
reg	spiclkr;
reg	spidoutr;
reg	spicsnr;
reg	spibusy;
reg	romack;
assign	spiclk = spiclkr;
assign	spidout = spidoutr;
assign	spicsn = spicsnr;
assign	readdata = spishiftr;
always@(posedge clk)
begin
	if(reset)
	begin
		spiseq <= 0;
		spicsnr <= 1;
		spiclkr <= 0;
		spidoutr <= 0;
		spibusy <= 0;
		romack <= 0;
	end
	else
	begin
		if(writeen)
		begin
			spiseq <= 1;// ライト
			spicsnr <= 0;
			spiclkr <= 0;
			spibusy <= 1;
			romack <= 0;
			bitcount <= 1;
			spidoutr <= 0;
		end
		else if(readen)
		begin
			spiseq <= 8;// リード
			spicsnr <= 0;
			spiclkr <= 0;
			spibusy <= 1;
			romack <= 0;
			bitcount <= 1;
			spidoutr <= 1;
		end
		case(spiseq)
		1:begin
//			spicsnr <= 0;
			case(mode[1:0])// ライトバイト数設定
			0:begin	spishiftr[31:24] <= writedata[7:0];
				spishiftcount <= 6'h08;
			end
			1:begin	spishiftr[31:16] <= writedata[15:0];
				spishiftcount <= 6'h10;
			end
			2:begin	spishiftr[31:8] <= writedata[23:0];
				spishiftcount <= 6'h18;
			end
			3:begin	spishiftr[31:0] <= writedata[31:0];
				spishiftcount <= 6'h20;//4バイト32ビット
			end
			endcase
			spiseq <= 2;
		end
		2:begin
			spishiftr[31:0] <= {spishiftr[30:0],1'b0};
			spidoutr <= spishiftr[31];
			spiclkr <= 0;
			spiseq <= 3;
		end
		3:begin
			spiclkr <= 1;
			bitcount <= bitcount + 1'b1;
			if(bitcount == spishiftcount)
			begin
				spiseq <= 4;
			end
			else
				spiseq <= 2;
		end
		4:begin
			if(mode[2])// mode[2] == 1 で、CSをHighにして終り
				spicsnr <= 1;
			spiclkr <= 0;
			romack <= 1;
			spibusy <= 0;
			spiseq <= 5;
			spidoutr <= 1;
		end
		5:begin	// end seq
			spiclkr <= 0;
			romack <= 0;
			spibusy <= 0;
			spiseq <= 0;
		end
		8:begin
			spishiftr[31:0] <= 32'h00000000;
			case(mode[1:0])// リードバイト数設定
			0:begin
				spishiftcount <= 6'h08;
			end
			1:begin
				spishiftcount <= 6'h10;
			end
			2:begin
				spishiftcount <= 6'h18;
			end
			3:begin
				spishiftcount <= 6'h20;//4バイト32ビット
			end
			endcase
			spiseq <= 9;
		end
		9:begin
			spiclkr <= 1;
			spiseq <= 10;
		end
		10:begin
			spishiftr[31:0] <= {spishiftr[30:0],spidin};
			spiclkr <= 0;
			bitcount <= bitcount + 1'b1;
			if(bitcount == spishiftcount)
			begin
				spiseq <= 11;
			end
			else
				spiseq <= 9;
		end
		11:begin
			if(mode[2])// mode[2] == 1 で、CSをHighにして終り
				spicsnr <= 1;
			romack <= 1;
			spibusy <= 0;
			spiseq <= 5;
			spidoutr <= 1;
		end
		endcase
	end
end
endmodule

以下は、この回路のテストベンチで、0x10番地から4バイト読むものです。
module spictl2_tb;
`timescale 1ns/1ns
reg	clk;	// risc4s
reg	reset;
reg	[31:0] writedata;
reg	writeen;
reg	readen;
reg	[3:0] mode;
wire	[31:0] readdata;
wire	spibusy;
wire	spiclk;
wire	spidout;
reg	spidin;
wire	spicsn;
wire	romack;
reg	[3:0] sendseq;
reg	[31:0] shift;
always
begin
	#5 clk = 0;
	#5 clk = 1;
end
initial
begin
	reset = 1;	clk = 1;	writedata = 0;	writeen = 0;//	romadr = 0;
	readen = 0;	mode = 0;	spidin = 0;
	#100 reset = 0;
	#10 writedata = 32'h03000010;	writeen = 1;	mode = 3;
	#10 writeen = 0;
	#700 readen = 1;	mode = 7;
	#10 readen = 0;
end
always@(posedge clk)
begin
	if(reset)
	begin
		sendseq <= 0;
	end
	else
	begin
		if((romack)&&(mode == 3))
		begin
			sendseq <= 1;
			shift <= 32'hAA995566;
		end
		case(sendseq)
		1:begin
				shift <= {shift[30:0],1'b0};
				spidin <= shift[31];
			sendseq <= 2;
		end
		2:begin
			if(spiclk)
			begin
				shift <= {shift[30:0],1'b0};
				spidin <= shift[31];
			end
		end
		endcase
	end
end
spictl2 u1(
	.clk(clk),
	.writedata(writedata),
	.writeen(writeen),
	.mode(mode),
	.readdata(readdata),
	.readen(readen),
	.romaddr(),
	.romack(romack),
	.spibusy(spibusy),
	.spiclk(spiclk),
	.spidout(spidout),
	.spidin(spidin),
	.spicsn(spicsn),
	.reset(reset)
);
endmodule

テストベンチのシミュレーション結果です。ハイライトさせているのが、CPUから読める32ビットの出力データです。



SPI操作回路を、risc4s とインターフェイスして操作するコマンドは、以下のものがあります。
SPIメモリ操作コマンド ライト、イレーズ時に、禁止領域警告付き(工場出荷時のデータエリア)
操作内容
 命令 
 解説 
 リード 
 rr500000,8100000,1000 
 SPI memory の0x500000 番地から、DRAM の0x8100000 番地に、0x1000 バイト読み出す 
 ライト 
 rw500000,8100000,1000 
 SPI memory の0x500000 番地に、DRAM の0x8100000 番地から、0x1000 バイト書く 
 イレーズ 
 re500000,3800 
 SPI memory の0x500000 番地から、0x4000 バイト消す。4kバイト単位 

SPIメモリ操作コマンド ライト、イレーズ時に、禁止領域警告なし
操作内容
 命令 
 解説 
 ライト 
 rW500000,8100000,1000 
 SPI memory の0x500000 番地に、DRAM の0x8100000 番地から、0x1000 バイト書く 
 イレーズ 
 rE500000,3800 
 SPI memory の0x500000 番地から、0x4000 バイト消す。4kバイト単位 


Xilinx の .ngc ファイルを作成して、CPUソースをネットリストに変換 追加 2013.6.10




以下に続く
イーサネットPHYをFPGAで操作してみる。MACの設計( Avnet Spartan6 LX9 MicroBoard ) 追加 2013.3.19




準備中


準備するもの

  1. Windows 上でLinux 環境を作る。 Cygwin
  2. MicroBlaze のコンパイラソース Xilinx 常に最新。EDKを使わない場合どれでもいい。
  3. EDKのV8.2のソースはすでに公開を停止していますが、古い fullsrc.tar.gz はここに残しています。

Cygwin のダウンロード

Cygwin は、ダウンロードしながらインストールできますが、全部ダウンロードしてから、ディスク上のデータからインストールするほうが問題が発生しにくいので、まずダウンロードのみ先に行います。これは、Cygwin のダウンロードは、途中でパッケージを読み飛ばしてしまって、必要なファイルを全部ダウンロードできないことがあるからです。そして抜けたファイルがあっても、インストール中に警告が出ないことがあるので、インストールが終っても正常に動作しないことがあります。特にクロスコンパイラをビルドするときには、多くのパッケージソフトを使うので、file not found とかのメッセージが出て、途中でエラーで止まってしまうことがあります。無論、クロスコンパイラをビルドするには、 configure のオプション設定 を間違えてエラーで止まることもしばしばなので、エラーの原因がパッケージ不足によるものなのか、configure の設定不足や指示間違いなのか、エラーメッセージから判読するのは、慣れてこないと簡単にできません。

Cygwin のセットアップ

 Cygwin は、開発環境にするので、 図 Devel 選択 をクリックして Install に変え、次へをクリック。
Cygwin は、ダウンロード


MicroBlazeは、Xilinx, Inc.の商標または登録商標です。

FPGAのCPU関連

例1:シリコンデザインテクノロジー デザインウエーブマガジン2005年1月号掲載記事がある。C1601 CPU
例2:HDL Simulator Veritak verilogシミュレータ、VHDL --> verilog 変換ツールの販売


ホームに戻る



inserted by FC2 system