本記事は、MOS6502、及びその互換CPU用のクロス開発環境CC65についての記事となる。
cc65は様々なプラットフォームを想定しており、Visual C++等でWindowsアプリケーションを開発する際と異なる点として、リンク時にメモリ空間を定義したコンフィグファイル(*.cfg)が必要になる点となる。
(特定のプラットフォームは、リンカーld65.exeの内部にコンフィグファイルを持っており、-tオプションでプラットフォームを指定する事により、コンフィグファイル(*.cfg)の指定を省略することができる。)
ここでは、独自にコンフィグファイル(*.cfg)を記述する場合についての記述方法について解説する。
MEMORY
MEMORYディレクティブでは、ターゲットとするプラットフォームのメモリマップを定義する。
また、エミュレーター用のヘッダー情報も、メモリマップとして定義することができる。
メモリ空間の名前は、アルファベットから始まり、その後はアルファベットもしくは数字で構成され、コロン:の前で終わる。
続いて、各属性の指定をし、名前に対する属性の定義は、セミコロン;で終わる。
属性について
start
メモリ空間の開始アドレスを指定する。
複数のメモリ空間にて、同じアドレスを設定する事により、バンク切り替えのあるROMイメージも作成する事ができる。
また、RAM空間についても同じアドレスを指定可能であり、同じアドレスのRAM空間を異なる名前のメモリ名(異なるセグメント)から共有する事ができる。これは、当時S-RAMは高価であった事より、低容量のS-RAMしか実装されなかった様なプラットフォームにおいて、少ないRAM空間を節約してより有効に使用できる可能性がある。
例えば、ハードウェア上に2kByteのS-RAM(アドレス0000h〜07FFh)しか実装されない場合、以下のように3ページをシステムが使うとすると、ユーザーが自由に使える領域は、残り5ページしか無い。そのような場合に、有効かもしれない。
- ゼロページに256byte、
- CPUのスタック領域に256byte、
- 更にスプライトのDMA転送用に256Byte
size
メモリ空間のサイズを指定する。
file
ROMイメージとしてファイル出力する場合のファイル名を指定する。
(ファイル名はダブルコーテーション"で囲む。)
%Oはデフォルトの出力名となる。
fill
yesの場合、ビルド結果がメモリエリアのサイズに満たない場合、0で埋める。
define
yesの場合、メモリ空間に対して以下の3つのシンボルを作成する。
例えば、メモリ空間の名前がSRAMの場合は、
__SRAM_START____SRAM_SIZE____SRAM_LAST__
なお、cc65のCラインタイムでは、SRAMはC言語のスタックフレーム用のエリアとして使用しているRAM領域となる。
スタートアップでは、スタックフレーム用のエリアの設定として上のシンボルを使用しており、メモリ領域SRAMはdefine = yesである必要がある。
MEMORYの定義例
SYMBOLS {
__STACKSIZE__: type = weak, value = $0300; # 3 pages stack
}
MEMORY {
ZP: start = $02, size = $60, type = rw, define = yes;
# iNES Cartridge Header
HEADER: start = $0, size = $10, file = %O ,fill = yes;
# 2 16K ROM Banks
# - startup
# - code
# - rodata
# - data (load)
ROM0: start = $8000, size = $4000, file = %O ,fill = yes, define = yes;
ROM1: start = $C000, size = $3ffa, file = %O ,fill = yes, define = yes;
# Hardware Vectors at End of 2nd 8K ROM
ROMV: file = %O, start = $FFFA, size = $0006, fill = yes;
# 1 8k CHR Bank
ROM2: file = %O, start = $0000, size = $2000, fill = yes;
# standard 2k SRAM (-zeropage)
# $0100-$0200 cpu stack
# $0200-$0500 3 pages for ppu memory write buffer
# $0500-$0800 3 pages for cc65 parameter stack
SRAM: file = "", start = $0500, size = __STACKSIZE__, define = yes;
# additional 8K SRAM Bank
# - data (run)
# - bss
# - heap
RAM: file = "", start = $6000, size = $2000, define = yes;
}
SEGMENT
SEGMENTディレクティブでは、各々のセグメントが、上のMEMORYディレクティブで定義した、どのメモリ空間に割り当てるかを定義する。C言語ソース、及びアセンブリ言語ソースでは、コード・データを配置するメモリとして、このセグメントを指定する。
セグメントの名前は、アルファベットから始まり、その後はアルファベットもしくは数字で構成され、コロン:の前で終わる。続いて、各属性の指定をし、名前に対する属性の定義は、セミコロン;で終わる。
属性について
load
セグメントを配置するメモリ領域。
下の例では、セグメントSTARTUP,LOWCODE,ONCE,CODE,RODATA,DATAは、全てメモリROM0に配置される。
type
セグメントのタイプを設定する。タイプは以下の4つがある。
- ro 読み込みのみ(Raed Only)可能なことを意味する。
- rw 読み書き(Raed Write)が可能なことを意味する。
- bss 未初期化領域であることを意味する(※)。
- zp ゼロページ領域を意味する。
type = bssの場合、リンカーはこのエリアを出力しないため、開発者がスタートアップ時に0初期化しなければならない。そのため、下記のようにセグメントBSSではdefine = yesを指定し、シンボルを作成しておき、初期化に利用する。
但し、デフォルトのスタートアップコードcrt0.sを利用する場合、BSSセグメント内のデータに関しては0初期化される。
(crt0.sからzerobss.sが呼び出されており、この中でシンボル__BSS_RUN__及び__BSS_SIZE__が利用されている。)
define
yesの場合、メモリ空間に対して以下の3つのシンボルを作成する。
例えば、セグメントの名前がBSSの場合は、
__BSS_LOAD____BSS_RAN____BSS_SIZE__
run
ROMに配置されたコードを、スタートアップにてRAMにコピーする際に、コピー先のメモリ領域を指定する。
下の例の場合は、セグメントDATAは、メモリ領域ROM0に配置され、スタートアップにてメモリ領域RAMにコピーされる事を示している。
リンカーが自動で転送コードを生成する訳ではないため、開発者がスタートアップにデータを転送するコードを記述しなければならない。そのため、下記のように、run = RAMが設定されたセグメントDATAではdefine = yesを指定し、シンボルを作成しておき、データ転送コードに利用する。
但し、デフォルトのスタートアップコードcrt0.sを利用する場合、DATAセグメントに関してはデータが転送される。
(crt0.sからcopydata.sが呼び出されており、この中でシンボル__DATA_LOAD__, __DATA_RUN__, 及び__DATA_SIZE__が利用されている。)
optional
yesの場合、セグメントを使用しているオブジェクトファイルが見つからない場合に発行される警告を抑制します。
SEGMENTの定義例
SEGMENTS {
HEADER: load = HEADER, type = ro;
STARTUP: load = ROM0, type = ro, define = yes;
LOWCODE: load = ROM0, type = ro, optional = yes;
ONCE: load = ROM0, type = ro, define = yes, optional = yes;
CODE: load = ROM0, type = ro, define = yes;
RODATA: load = ROM0, type = ro, define = yes;
DATA: load = ROM0, run = RAM, type = rw, define = yes;
PCMDATA: load = ROM1, type = ro, define = yes;
VECTORS: load = ROMV, type = rw;
CHARS: load = ROM2, type = rw;
BSS: load = RAM, type = bss, define = yes;
HEAP: load = RAM, type = bss, optional = yes;
ZEROPAGE: load = ZP, type = zp;
}
C言語上の実装
C言語での開発時は、デフォルトの設定では以下のセグメントに配置される。
ZEROPAGE以外は、それぞれ#pragmaによって変更可能であり、多バンクのROMイメージも、C言語で作成可能となる。
(詳細は、cc65のマニュアルを参照。)
| セグメント | 内容 |
|---|---|
| CODE | プログラムコード |
| RODATA | 定数データ (const宣言された変数など) |
| DATA | 初期化済みデータ(変数の宣言時に値が定義されたもの) |
| BSS | 未初期化データ(変数の宣言時に値が定義されないもの) |
| ZEROPAGE | C言語ランタイム用の領域、及びRegister変数など |
FEATURES
C言語ランタイムを用いて開発する場合は、コンフィグファイル(*.cfg)に以下の記述も必要。
FEATURES {
CONDES: type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__,
segment = ONCE;
CONDES: type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__,
segment = RODATA;
CONDES: type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__,
segment = RODATA,
import = __CALLIRQ__;
}