78K0/Kx2ではメモリ・バンクの機能が導入されましたが、これはどのようなものでどのように使うのでしょうか。
【はじめに】
8ビットマイコンではそのアーキテクチャから、16本のアドレス信号を用いて64Kバイトまでのメモリを使うのが一般的です。
マイコンの基本的なアーキテクチャ(命令体系)を変更すれば、64kバイト以上のメモリを使うことができますが、それでは全く別のマイコンになってしまいます。
(FAQの"
マイコンとメモリ"も参照ください。)
【メモリ・バンク】
従来のマイコンの仕様を大きく変更することなく64Kバイト以上のメモリをアクセスするために、バンク・スイッチングやメモリ・バンクと呼ばれる方法が用いられます。これは、マイコンが直接扱えるメモリの範囲(この場合には64Kバイト)はそのままですが、その一部の領域にメモリをいくつか重ねているものです。下記にイメージを示します。
通常のメモリ配置では、アドレスに対してメモリが1対1に対応していますが、右側に示されたメモリ・バンク方式の場合、バンク領域(ここでは8000〜BFFFの16Kバイトとします)では一つのアドレスに対して複数のメモリ(この図では4つのバンク)が対応しています。一つのアドレスに対応したメモリの数を増やすほど扱えるメモリを増やすことができます。ただし、一つのアドレスに対して複数のメモリが対応していると言っても、それらが全てアクセスできる状態になっているわけではありません。このバンク領域は前もって、バンク選択信号で使用する(アクセスできる)メモリ・バンクを一つ指定しておきます。その上でバンク領域をアクセスすると、指定しておいた一つのメモリ・バンクを選択してアクセスできます。このときの手順は以下のようになります。
| 操作内容 | | 対応したハードウェアの動作 |
| (1) 使用したいバンクを選択 | → | バンク選択信号でメモリ・バンクの1つが選択される |
| (2)バンク領域をアクセス | → | (1)で指定したメモリ・バンクの該当アドレスのメモリをアクセス |
|
このように2つの手順を組み合わせることで、任意のメモリ・バンクの任意のアドレスのメモリをアクセスすることができます。ただし、これはプログラムが通常のメモリで動作中にメモリ・バンクをアクセスする方法です。プログラムがメモリ・バンクで動作中には別のメモリ・バンクを直接アクセスすることはできません。別のメモリ・バンクをアクセスするには、プログラムを通常のメモリに移してからメモリ・バンクを切り替えてからアクセスする必要があります。
【メモリ・バンクの使い方】
メモリ・バンクはマイコンの仕様を大きく変更することなくメモリを拡張するために使う方法ですので、マイコンを使う上では頻繁に使うものではありません。特にメモリアクセスの効率を考えると通常のメモリとメモリ・バンクの用途を明確にしておき、頻繁にバンクを切り替えないようにして使ってください。メモリの使い分けとしては
| 通常メモリ領域 | : | プログラム本体、共通的に使用するルーチンやデータ 割り込みのようにプログラム実行とは非同期の処理 処理時間に制限のある処理 |
| バンク領域 | : | バンク領域内でクローズする処理及びそのデータ 使用頻度の低い処理やデータ 処理時間に余裕がある処理 アクセス時間に制限が少ないデータ |
を目安と考えてください。
この考え方に従うと、メモリ・バンクをまたぐような処理は限られてきます。そのため、メモリ・バンクをまたぐ処理が必要なときだけ通常メモリの固定されたアドレスにバンクを切り替える処理を準備し、そこを使って切り替えます。この方法は比較的単純なプログラムで済みます。この方式を用いてメモリ・バンク0からメモリ・バンク1のサブルーチンをコールする例について、処理の概要を下図に示します。
この例は、メモリ・バンク1のTARGETと言うサブルーチンをメモリ・バンクを越えてコールする場合です。通常メモリ領域にこのサブルーチンを呼びたすための踏み台になる処理としてENTRYと言うサブルーチンを用意します。ここでメモリ・バンク切り替えのための処理を行い、目的のサブルーチンTARGETをコールし、メモリ・バンクを元に戻してリターンします。
この状態で、

メモリ・バンク1から通常メモリ領域のENTRYをサブルーチンコールすると、そこから

メモリ・バンク1のTARGETがコールされます。TARGETでの処理が完了すると、

一旦ENTRYに戻ってきて、そこから

メモリ・バンク0に戻ります。このように、サブルーチン毎に通常メモリに踏み台を準備することで、他のメモリ・バンクのサブルーチンを使用できます。
実際に使用する場合、通常メモリに準備する具体的なプログラム例は以下のようになります。なお、作業用(データ保持やデータ受け渡し)にsaddr領域のメモリを使用します。saddr領域についてはFAQの"
マイコンとメモリ"を参照ください。
(1)プログラム例1
このプログラムではレジスタの内容を破壊しないようにsaddr領域のRAM1バイト(R_BNKN)を作業用で使用しています。
| ; | |
| ; | 入力パラメータ |
| ; | なし |
| ; | 出力パラメータ |
| ; | なし |
| ; | |
| ENTRY: | | | |
| MOV | R_BNKN,A | ;Aレジスタを保存 |
| MOV | A,#BANKNUM TARGET | ;サブルーチンのバンク番号を指定 |
| XCH | A,BANK | ;戻り先のバンク読み出しとサブ |
| | | ;ルーチンのバンク番号セット |
| XCH | A,R_BNKN | ;Aレジスタ復帰、元バンクセーブ |
| CALL | !TARGET | ;実際のサブルーチンをコール |
| XCH | A,R_BNKN | |
| MOV | BANK,A | ;戻り先バンク設定 |
| MOV | A,R_BNKN | |
| RET | | |
(2)プログラム例2
どうしても、複数のメモリ・バンク間を行き来する必要がある場合には上記のプログラムを複数並べる必要があります。しかし、例1のプログラムは別バンクのサブルーチンをコールする際に戻り先のバンク番号をsaddr領域の作業用メモリ(R_BNKN)に保存しているので、バンク間の多重の呼び出しには対応できません。このような場合には、以下に示すように破壊したくない値(戻り先のバンク番号)をスタックに退避するように処理を変更してください。今回はAXレジスタをワークとして使用するので、作業用にsaddr領域に2バイトのRAM(RSAVEAX)を準備します。
| ; | |
| ; | 入力パラメータ |
| ; | なし |
| ; | 出力パラメータ |
| ; | なし |
| ; | |
| ENTRY2: | | | |
| MOV | RSAVEAX,A | ;Aレジスタを保存 |
| MOV | A,#BANKNUM TARGET | |
| XCH | A,BANK | ;戻り先のバンク読み出しとサブ |
| | | ;ルーチンのバンク番号セット |
| PUSH | AX | ;戻り先バンクをスタックにセーブ |
| MOV | A, RSAVEAX | ;Aレジスタ復帰 |
| CALL | !TARGET | ;実際のサブルーチンをコール |
| MOVW | RSAVEAX,AX | ;AXレジスタセーブ |
| POP | AX | |
| MOV | BANK,A | ;戻り先バンク設定 |
| MOVW | AX,RSAVEAX | ;AXレジスタ復帰 |
| RET | | |
(3)プログラム例3
メモリ・バンク間を行き来するサブルーチンコールが多くなると、その数だけ踏み台となるプログラムを準備するのは効率的ではありません。その場合には、以下のような汎用のプログラムを使います。このプログラムはsaddr領域のR_BANKにコールするサブルーチンのバンク番号、R_BNKAにアドレスを設定した状態でコールします。また、処理の中で作業用に2バイトのRAM(RSAVEAX)を使用します。このプログラムはサブルーチンのコールでも、逆にサブルーチンからの復帰でもレジスタの値は破壊しません。そのために、ダミーコールを用いて2段階でサブルーチンコールを処理しています。
動作として、

で必要なコールするサブルーチンのバンク番号やアドレスをsaddr領域に設定しておきます。その後

で踏み台プログラムをコールします。踏み台プログラムではsaddr領域のバンク番号をBANKレジスタにセットした後に

後処理部(BNLCALSA2)をサブルーチンコールします。後処理部ではサブルーチンのアドレスをスタックに設定し、

RET命令により指定されたサブルーチンに分岐します。
サブルーチンでは、必要な処理を行い、

RET命令により踏み台プログラムに戻ります。踏み台プログラムではBANKレジスタにコール元のバンク番号に戻して、

RET命令によりコール元のプログラムに戻ります。
具体的なプログラムは以下の通りです。
| ; | |
| ; | 入力パラメータ |
| ; | R_BNKN:参照先のバンク番号 |
| ; | R_BNKA:参照先のアドレス |
| ; | 出力パラメータ |
| ; | なし |
| ; | |
| ENTRY3: | | | ;メモリ・バンク間コール処理ルーチン |
| MOVW | RSAVEAX,AX | ;AXレジスタをセーブ |
| MOV | A,R_BNKN | ;コール先バンク番号を取得 |
| XCH | A,BANK | ;バンク変更と戻り先バンクの取得 |
| PUSH | AX | ;戻り先のメモリ・バンク番号をスタックに |
| CALL | !BNKCALSA2 | ;コール先に分岐するためのダミーコール |
| MOVW | RSAVEAX,AX | ;AXレジスタをセーブ |
| POP | AX | ;戻り先バンク番号取得 |
| MOV | BANK,A | ;戻り先のメモリ・バンク番号を指定 |
| MOVW | AX,RSAVEAX | ;AXレジスタを元に戻す |
| RET | | ;コール元にリターン |
| BNKCALSA2: | | | |
| MOVW | AX,R_BNKA | ;コール先のアドレス指定 |
| PUSH | AX | ;コール先をスタックにセット |
| MOVW | AX,RSAVEAX | ;元のAXレジスタの値を復帰 |
| RET | | ;コール先に分岐 |
このままでは、この踏み台を使うたびにサブルーチンのバンク番号とアドレスを設定する処理を記述しないといけないので使い辛くなります。これを避けるには、以下のようにマクロを定義します。
| BNKCAL | MACRO | PARA1 | |
| MOV | R_BNKN,#BANKNUM PARA1 | ;参照先のメモリ・バンク番号を格納 |
| MOVW | R_BNKA,#PARA1 | ;参照先のアドレスを格納 |
| CALL | !ENTRY3 | |
| ENDM | | |
これで、3命令を記述する替わりに、新たにマクロ定義したBNKCAL命令を用いて以下のようにプログラムを記述するだけで簡単にこの踏み台プログラムを使うことができます。
(4)メモリ・バンク間のプログラム分岐処理
以上、メモリ・バンクをまたぐサブルーチン・コールを説明しましたが、ここでは分岐についてのプログラム例を示します。
固定アドレスへの分岐処理の場合には、通常メモリ領域に単純に以下の2命令を準備します。単純な処理ですので、これを必要なだけ並べても実用になります。分岐元のプログラムは単純にこの踏み台プログラムに分岐するだけです。
| ; | |
| ; | 入力パラメータ |
| ; | なし |
| ; | 出力パラメータ |
| ; | なし |
| ; | |
| ENTRY4: | | | |
| MOV | BANK,#BANKNUM TARGET | |
| BR | !TARGET | |
参考として、踏み台としての汎用のメモリ・バンク間の分岐プログラムの例を以下に示します。かなりの命令が必要なこととこれを使用するためには前もって分岐先のバンク番号とアドレスをsaddr領域にセットする必要があることからとても効率的とは言ないので、あくまで参考にとどめてください。
| ; | |
| ; | 入力パラメータ |
| ; | R_BNKN:参照先のバンク番号 |
| ; | R_BNKA:参照先のアドレス |
| ; | 出力パラメータ |
| ; | なし |
| ; | |
| ENTRY5: | | | |
| MOVW | RSAVEAX,AX | ;AXレジスタをセーブ |
| MOV | A,R_BNKN | ; |
| MOV | BANK,A | ;分岐先のメモリ・バンク番号を指定 |
| MOVW | AX,R_BNKA | ;分岐先のアドレス指定 |
| PUSH | AX | ;分岐先アドレスをスタックにセット |
| MOVW | AX,RSAVEAX | ;分岐元のAXレジスタ復帰 |
| RET | | ;分岐先に分岐 |
この場合にも以下のマクロを定義しておけば「BNKBR TARGET」と記述するだけで済みます。
| BNKBR | MACRO | PARA1 | |
| MOV | R_BNKN,#BANKNUM PARA1 | ;参照先のメモリ・バンク番号を格納 |
| MOVW | R_BNKA,#PARA1 | ;参照先のアドレスを格納 |
| BR | !ENTRY5 | |
| ENDM | | |
(5) メモリ・バンク間のデータ参照処理
次に、メモリ・バンクを跨いだデータ参照処理のプログラム例を示します。ここの例では、必要なパラメータはレジスタを用いて受け渡しています。
| ; | |
| ; | 入力パラメータ |
| ; | Aレジスタ:参照先のバンク番号 |
| ; | HLレジスタ:参照先のアドレス |
| ; | 出力パラメータ |
| ; | Aレジスタ:参照結果 |
| ; | |
| ENTRY6: | | | |
| XCH | A,BANK | ;参照元と参照先のバンク番号の交換 |
| MOV | R_BNKN,A | ;参照元のメモリ・バンク番号を退避 |
| MOV | A,[HL] | ;目的の値をリード |
| XCH | A,R_BNKN | ;参照元のメモリ・バンク番号を取得 |
| MOV | BANK,A | ;参照元のメモリ・バンク番号を指定 |
| MOV | A,R_BNKN | ;参照結果をAレジスタに復帰 |
| RET | | |
汎用のサブルーチンコールや分岐と入力パラメータを統一した場合の例も参考に示しておきます。
| ; | |
| ; | 入力パラメータ |
| ; | R_BNKN:参照先のバンク番号 |
| ; | R_BNKA:参照先のアドレス |
| ; | 出力パラメータ |
| ; | Aレジスタ:参照結果 |
| ; | |
| ENTRY7: | | | ;メモリ・バンク間参照用サブルーチン |
| PUSH | HL | ;HLレジスタの内容を退避 |
| MOV | A,R_BNKN | ; |
| XCH | A,BANK | ;参照元と参照先のバンク番号の交換 |
| MOV | R_BNKN,A | ;参照元のメモリ・バンク番号を退避 |
| XCHW | AX,HL | ;Xレジスタをセーブ |
| MOVW | AX,R_BNKA | ; |
| XCHW | AX,HL | ;参照先のアドレスを指定 |
| MOV | A,[HL] | ;目的の値をリード |
| XCH | A,R_BNKN | ;参照元のメモリ・バンク番号を取得 |
| MOV | BANK,A | ;参照元のメモリ・バンク番号を指定 |
| MOV | A,R_BNKN | ; |
| POP | HL | ;HLレジスタの内容を復帰 |
| RET | | ;復帰 |