注:以下の方式はアイディア段階であり、実験はまだなので、まだ何の実績もありません。
PicoBlaze3(KCPSM3)の命令アドレスは10bitなので、命令数は1024が限界です。 Virtex6やSpartan6向けにはもっと広い命令空間を持つPicoBlaze6があるのですが、Spartan3系では使用できません。
命令数が1024を超えた場合、複数のサブルーチンの共通処理をくくりだしてJUMPで繋いだり、レジスタsXが0,1,2のどれかの判定をCOMPARE sX, 1だけでやったり(0ならC、1ならZ、2ならNCかつNZ)と、1命令単位でカリカリに削る努力をしなければなりません。
そんな努力をしなくても救われるために、バンクメモリ方式を適用してみることにしました。
まず、PicoBlazeは、レジスタの値を命令アドレスとみなしてJUMPあるいはCALLするということができません。命令アドレスは10bit、データ幅は8bitなので、原理的に無理です。よって、s4にバンク番号、s5にサブルーチンのアドレスを入れて、トランポリンルーチンでs4の値にバンク切り替えしたあとにs5の値をアドレスとしてCALL、なんていうのは不可能です。
Banked Memoryの普通の方式は、メモリ空間を4ブロックぐらいに分割し、そのうち何ブロックかを窓として、窓の先に見える実メモリをとっかえひっかえするものです。
バンク切り替えは、切り替えの対象でないブロックにバンク切り替えルーチン(仮に、トランポリンルーチンと表記します)をおいて、トランポリンルーチンに「バンクを切り替え、目的のルーチンをCALLし、戻ってきたら切り替え前のバンクに戻してリターン」という仕事をさせるのがセオリーです。
トランポリンルーチンに頼らずに、切り替えの対象であるブロックを実行中にバンク切り替えをしてしまうと、自分の足場がなくなってしまうので普通はやりません。
普通はやらないのですが、PicoBlazeだと一番合理的な実装になる予感がしたので、やってみることにしました。
また、命令メモリ空間を分割するとただでさえ狭い空間がさらに狭くなるし、pBlazIDEでシミュレーションするのも困難になるので、 命令メモリ空間の分割はせずにメモリを丸ごと入れ替えてしまうことにしました。それでもデータメモリ(ScratchPad)は同一なので困らないはずです。
※以下の記述でnopと書いてある部分は、PicoBlazeにはnopが無いので実際にはload s0, s0です。また、ラベル名の最後に_がついているルーチンは、本体が別のバンクにあることを示しています。
bankA, bankB共通
bankA用
- 適当なプログラム
- call LCDinit_
- ...
- call LCDput_
- ...
- isr: 割り込みサービスルーチン
- ...
- ret
- ORG $3F3
- 3F3 LCDinit_: call bankchg_to_B
- 3F4 nop
- 3F5 LCDput_: call bankchg_to_B
- 3F6 nop
- 3F7 nop
- 3F8 bankchg_to_B: out s0, bankB_op
- 3F9 ret
- 3FA bankchg_prev: out s0, bankPREV_op
- 3FB ret
- 3FC isr_: nop
- 3FD call isr
- 3FE jump bankchg_prev
- 3FF isv: jump isr
bankB用
- LCDinit: LCD初期化処理
- ...
- ret
- LCDput: LCDに文字を出す処理
- ...
- ret
- ORG $3F3
- 3F3 LCDinit_: nop
- 3F4 call LCDinit
- 3F5 LCDput_: jump bankchg_prev
- 3F6 call LCDput
- 3F7 jump bankchg_prev
- 3F8 bankchg_to_A: out s0, bankA_op
- 3F9 ret
- 3FA bankchg_prev: out s0, bankPREV_op
- 3FB ret
- 3FC isr_: call bankchg_to_A
- 3FD nop
- 3FE nop
- 3FF isv: jump isr_
上記のようなプログラムと、「ポートアドレス$80または$81に書き込みがあったら、現在の選択バンクをスタック構造へpushし、$80ならbankAへ、$81ならbankBにバンク切り替えする」かつ「ポートアドレス$40に書き込みがあったら、スタック構造からpopした値にバンク切り替えする」モジュールをハードウェアとしてPicoBlazeに接続します。
※ちなみに、上記のモジュールはPicoBlazeのOUT_PORTを無視するので、s0の値はどうでもよいです。
bankA上のプログラムから、bankB上のLCD_initを呼び出す場合、その流れは以下のようになります。
| 実行される命令 | 命令の場所 | |
| 1 | call LCDinit_ | bankA |
| 2 | call bankchg_to_B | bankA 3F3 |
| 3 | out s0, bankB_op | bankA 3F8 |
| 4 | ret | bankA 3F9 |
| 5 | call LCDinit | bankB 3F4(この時点でbankBが選択されている) |
| 6 | LCDinitの処理が行われる | bankB |
| 7 | ret | bankB |
| 8 | jump bankchg_prev | bankB 3F5 |
| 9 | out s0, bankPREV_op | bankB 3FA |
| 10 | ret | bankB 3FB |
| 11 | bankAの、call LCDinit_の次の命令 | bankA |
bankBが選択されているときに割り込みが発生した場合は、4命令ぶんのオーバヘッドがあるもののbankAに存在するisrが呼び出され、isrでの処理が終わり次第bankBが再選択され戻ってくるので問題なしです。
...PicoBlazeで開発していると、こうやってソフトとハードを両方設計して、連係動作させて問題を解決するということできるのでとても楽しいです。 新人にSpartan3A Starter Kit(DigiKeyで2万ぐらいで購入できます)を渡して、「PicoBlazeを使ってナイトライダーを作れ」とかいう課題を与えれば短期間で両刀使いが育成できるかもしれませんね。