本記事は、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__;
}