PIC18Fシリーズの新しいチップでは、拡張命令がサポートされています。コンパイラでコードが最適化されるように考えられたもののようです。
従来の通常の命令セットと拡張命令セットの切り替えは、コンフィグで行うので、実行時の切り替えはできません。
拡張命令は、単に、新しい命令が追加されただけでなく、レジスタをアドレスするほとんどの命令で動作が変わります。言わば、全命令が拡張命令になるようなものです。
PIC18Fでの通常のアドレスは、アクセスバンクを用いる方法と、BSR(バンクセレクタ)を用いる方法の二つがあり、これらは、アセンブラ命令語の最後に「ACCESS」をつけると、アクセスバンク。「BANKED」をつけるとBSRアクセスになっていました。このうち、アクセスバンクを用いる部分が拡張されました。
ACCESS / BANKED は書くのが面倒ですが、省略しても適宜アセンブラが判断して解釈してくれるようです。
いずれの方法でも、オペランドが表すアドレスは0x00-0xffの1バイトアドレス。拡張しない場合には、固定のアクセスバンクをアドレスしたのですが、拡張命令では、SFR(機能レジスタ)に対するアクセスは従来通りアクセスし、SFRが割り付けられていないアドレスに対しては、オペランドが、FSR2に対するオフセットとして働きます。
アセンブラの記法も従来のとおりです。
MOVF PORTA,ACCESS
のように書きます。BSRに関わり無く、いつでもSFRにアクセスできるのは、今までと変わりません。
アセンブラの記法が変わり、次のように書きます。
MOVF [0x20]
「[]」で囲んだ中には、オフセットを書きます。これで、アドレスは、FSR2+0x20を示します。オフセットつき相対アドレスが実現できたわけです。
SFRは、実際には、アクセスバンクの0x60-0xFFに割り当てられています(明示されていませんが、すべてのプロセッサで固定でしょう)。従って、オフセットが利用できる範囲は、0x00-0x5fとなっています。
例えばC言語でのスタックフレームを考えたとき、FSR2をスタックフレームとして利用しようという設計思想のようです。PIC18Fでは、サブルーチンや割り込みの戻りアドレスは、固定のハードウェアスタックにセットされるので、FSR2によるスタックフレームは、引数やローカル変数を指し示すために利用します。FSR2からのオフセットでアドレスを指定できるので、面倒なアドレス計算無しに、引数、ローカル変数をアクセスできる仕掛けが追加されたわけです(96バイトの範囲内ですが…)。
また、命令をフルに利用するには、スタックの構造としては、減少式(アドレスの若い方へスタックが伸びていく)で、FSR2が空のスタック位置(スタック先頭−1)を指すようにした方が便利なようです。
また、PUSH操作は、FSR2のアクセス窓を用いて行いますが、アクセス窓機能には、PREDEC2がありません。POSTDEC2を使用することになります。
MOVF POSTDEC2 ; *FSR2=WREG then FSR2--
POP操作は、同様にFSR2のアクセス窓のPREINC2を使います。
MOVWF PREINC2 ; FSR2++ then WREG=*FSR2
もちろん、PUSH / POP 操作で、自前でFSR2を操作することにすれば、FSR2がスタックトップを示す形式も可能ですが、コードが増えます。
; PUSH ; SUBFSR 1 ; FSR2--; MOVWF INDF2 ; *FSR2=WREG ; ; POP ; MOVWF POSTINC2 ; WREG=*FSR2 then FSR2++ ;
新たに、拡張命令として追加された命令は、ADDFSR / ADDULNK / CALLW / MOVSF / MOVSS / PUSHL / SUBFSR / SUBULNK のわずか8命令。主に、スタックフレームの操作に関する命令です。
サブルーチン内のスタックフレームを生成する(ENTER)には、スタック・フレームのサイズに応じてスタック・フレーム・ポインタ(=FSR2)を増減する必要があります。
減少式のスタック(アドレスの若い方へスタックが伸びていく)ばあいには、SUBFSR が使用できます。SUBFSR はFSR2から定数を減算するので、その定数分のスタックフレームが確保されます。
逆に、サブルーチンから戻る場合(LEAVE)には、放棄するバイト数分だけFSR2に加算しなければなりません。ADDULNK は、定数をFSR2に加算し、さらに、RETURN 機能を兼ねるというお得な命令です。
従って、減少式のスタックの場合には、SUBFSR / ADDULNK の組合せで簡単にサブルーチン内のスタックフレーム生成/放棄ができます。
同様に、増加式のスタックの場合には、ADDFSR / SUBULNK の組合せを用いれば良いわけです。通常は、減少式のスタックを用いた方が簡単でしょう。
レジスタ操作で最も多いのは単なる複写。PIC18FではMOVFFという1命令でバンクを超えて、しかも、WREGを壊さずに複写できる命令があります。しかし、これをスタックフレーム内の複写に使用するには、アドレス計算をしなければならず、煩雑です。この命令のスタックフレーム内でのサポート命令が、MOVSS 命令。次のように書きます。
MOVSS [0x10],[0x20]
これで、FSR2+0x10の内容をFSR2+0x20へ複写します。スタックフレーム内でお手軽にデータ複写ができるわけです。
さらに、スタックフレームから、指定アドレスへの複写もサポートされています。MOVSF命令です。
MOVSF [0x10],DESTNAME
バンクを意識せずに一命令でスタックフレームから、任意のアドレスに複写ができます。逆向きもあれば、もっと、便利だったのでしょうけれど…
PUSHL は、FSR2で示されるスタックに定数をPUSHします。このとき、FSR2が減少するので、この命令は減少式のスタックを想定していることになります。WREGを経由せずに定数をPUSHできるところが、便利な点です。
PIC18Fの間接呼び出しは、ちょっと、面倒。アドレスは3バイトあるので、基本的には、PCLATU / PCLATH に事前にセットして置き、最後に PCL に書き込みをするとそこへ、ジャンプします。あくまでジャンプなので、一旦、CALLしてその中から間接ジャンプする必要がありました。
CALLWは、その不便を多少、解消するもの。WREGの値を元にその場で、間接CALLを行ってくれます。分岐先は、PCLATU / PCLATH / WREG なので、実際には、分岐先は256バイトの範囲の制限があるし、普通には、256バイトバウンダリーのテーブルの方が楽になるという制限つきなのですが…
実は、このCALLWそれほど、便利かというと、それほどでもありません。PCLATU / PCLATH のセットを事前にする必要があります。ところが、PIC18Fでは、PCLを読むとPCLATU / PCLATH に 実際のPCの該当値をセットする機能があるので、これを利用して簡単にテーブル呼び出しを実現する例がマニュアルに書かれています。
MOVF OFFSET, W CALL TABLE . . ORG nn00h TABLE ADDWF PCL RETLW nnh RETLW nnh RETLW nnh . .
ミソは、ADDWF PCL。PCLに加算するということは、PCLを一度読んでから、加算をしてPCLに書き込んでいます。この動作を、細かく見ていくと、PCLから読んだ時点で、PCLATU / PCLATH には、現在のPCの該当バイトがセットされます。PCLに書き込んだ時点で間接ジャンプします。このお陰で、PCLATU / PCLATH をセットするコードを書かなくても済むので、単純なテーブル引用ならば、この書き方のほうがCALLWを使うよりも楽な気がします。
本格的な計算を行う間接コールならば、余計なCALLスタブがいらない分だけ、便利になっています。
アセンブラで拡張命令を使う場合、ポイントになるのは、FSR2の扱いです。FSR2をスタック・フレーム・ポインタとして使うならば、サブルーチン内に用意にスタック・フレームを設けられるので、簡単に、ローカル変数を使用できます。これは、プログラムを作るときに、とても、便利な機能です。
他方で、スタック・フレームポインタとして使わなければ、オフセット付きのインデックス・アクセスができるので、構造体などを扱う場合には、とても、楽になります。構造体のメンバーがポインターで与えられたとき、メンバーのアドレスを計算しなくても直接アクセスできるのは、こちらも、便利な機能。メンバーを読み取るだけなら、FSRのアクセス窓PLUSWを使えばできるのですが、これは、書き込みは無理だし、WREGを使用してしまいます。
ADDFSR / SUBFSR は、別の見方をすると、2バイト±1バイトの定数加減算機能。オフセット計算などが1命令でできるので、便利な命令です。対象が、FSR2に限られているのでそこが難点。