前回は、PHP版のPICアセンブラを紹介しました。いろいろ物足りないところもあったと思います。今回はアセンブラの仕組みを解説しますので、ぜひほしい機能を追加してみてください(筆者は、昔のParallaxのアセンブラのローカルラベルやビットレベルのequが好きだったので、いつかやろうと思っています)。
また、言語を実装するときに毎回出てくるような課題も扱っています。アセンブラ以外の言語の実装に興味のある方も、ご一読いただければ幸いです。
基本的な処理
アセンブリ言語は、とてもシンプルです。左端にラベル、タブのあとに、命令と、あれば引数か並びます。セミコロンからあとはコメントになります。命令は、CPUの命令以外に、アセンブラに指示を出す擬似命令があります。
処理としては、まずコメントを取り除きます。そして、スペースかタブの並びで、行を3つに分割します。すると、先頭がラベル、2番目が命令、3番目が引数になります。命令によって、引数がないもの、1つのもの、2つのものがあるので、命令の種類ごとに分けて処理します。
アセンブラにはプログラムカウンタがあり、ここでは$orgを使用しています。CPU命令は、プログラムカウンタのアドレスに書き込まれます。たとえば、以下の例を見てください。
最初の「org 0x100」は擬似命令です。これは、プログラムカウンタを指定した値にします。次のnopはCPU命令なので、プログラムカウンタの0x100に割り当てられます。その次のsleepもCPU命令なので、0x101に割り当てられます。とても簡単ですね。
ところが、ラベルが出てくると、ちょっと面倒になります。これも具体例を見てみましょう。
最初にequという擬似命令があります。これは、countというラベルに0x20という値を擦り当てます。C言語でいう#defineのようなものだと思ってください。これがラベルの1つめの使い方です。
後半に「loop nop」という行があります。これは、この行のプログラムカウンタをラベルに割り当てるというものです。movlwの命令は0x100に、movwfの命令は0x101に格納されています。したがって、nopは0x102に格納されるので、loopは0x102になります。これは「loop equ 0x102」と書くのと同じことになります。
ここで1つ問題があって、「main org 0x100」と書いたらどうなるでしょう。厳密な仕様は調べられませんでしたが、mainは0x100になってほしいところです。このケースに対応するため、org擬似命令のみ、ラベルの処理の前に解釈するようにしています。他の命令の場合は、現在の(命令解釈前の)プログラムカウンタがラベルで使用されます。
さて、ループの場合、まず戻ってきたいところにラベルを定義して、そのあとにgoto文でラベルを指定します。goto文の時点では、すでにラベルが宣言されているところ注意してください。では、あるブロックをスキップする場合はどうなるでしょう。
前方参照
この例を見てください。
gotoの行を評価した時点では、main2のラベルはまだ読み込まれていません。このため、先頭から1行ずつ解釈していった場合は、引数か確定しません。これを前方参照とか、後方参照と呼びます(※)。
この場合でも、引数を未確定のままにして進めていくと、main2のラベルは0x104とわかります。そこで、まず第1段階でラベルをすべて読み込み、第2段階でラベルの値を使うという処理を行います。これを2パス方式といい、第1段階(パス1)ではラベルを読み込み、命令をチェックします。命令をチェックすることで、命令が格納されるアドレスがわかり、ラベルの値が確定します。PICの場合、命令は固定長なので、擬似命令のみを処理するような形になります。
第2段階(パス2)では、命令の引数を評価します。ラベルが使用されているところも、この段階で確定します。こうして、アセンブルをおこなうことかできます。
1つ悩んだのが、org擬似命令やequ擬似命令の引数に、ラベルを使用できるかどうかです。「led1 equ led0 + 1」のような記述をしたいことがありますが、循環参照など、対応が面倒な面もあります。そこで、その時点で定義されているラベルだけを使用できることにしました。
次回は、このアセンブラで出力したバイナリを、PICを書き込む方法を考えてみます。ご期待ください。