cc65のコンフィグファイルについて

本記事は、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の定義例

``` example.cfg
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ディレクティブで定義した、どのメモリ空間に割り当てるかを定義する。Ｃ言語ソース、及びアセンブリ言語ソースでは、コード・データを配置するメモリとして、このセグメントを指定する。

セグメントの名前は、アルファベットから始まり、その後はアルファベットもしくは数字で構成され、コロン`:`の前で終わる。続いて、各属性の指定をし、名前に対する属性の定義は、セミコロン`;`で終わる。


## 属性について

### load

セグメントを配置するメモリ領域。
下の例では、セグメント`STARTUP`,`LOWCODE`,`ONCE`,`CODE`,`RODATA`,`DATA`は、全てメモリ`ROM0`に配置される。

### type

セグメントのタイプを設定する。タイプは以下の4つがある。

  - ro  読み込みのみ（Raed Only）可能なことを意味する。
  - rw  読み書き（Raed Write）が可能なことを意味する。
  - bss 未初期化領域であることを意味する（※）。
  - zp  ゼロページ領域を意味する。

`type = bss`の場合、リンカーはこのエリアを出力しないため、開発者がスタートアップ時に０初期化しなければならない。そのため、下記のようにセグメント`BSS`では`define = yes`を指定し、シンボルを作成しておき、初期化に利用する。

但し、デフォルトのスタートアップコード`crt0.s`を利用する場合、`BSS`セグメント内のデータに関しては０初期化される。
（`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の定義例

``` example.cfg
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イメージも、Ｃ言語で作成可能となる。
（詳細は、cc65のマニュアルを参照。）

| セグメント | 内容 |
|-----------|------|
| CODE      | プログラムコード |
| RODATA    | 定数データ (const宣言された変数など) |
| DATA      | 初期化済みデータ(変数の宣言時に値が定義されたもの) |
| BSS       | 未初期化データ(変数の宣言時に値が定義されないもの) |
| ZEROPAGE  | Ｃ言語ランタイム用の領域、及びRegister変数など |


# FEATURES

Ｃ言語ランタイムを用いて開発する場合は、コンフィグファイル(`*.cfg`)に以下の記述も必要。

``` example.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__;
}
```