LoginSignup
0
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-02

本記事は、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領域となる。
スタートアップでは、スタックフレーム用のエリアの設定として上のシンボルを使用しており、メモリ領域SRAMdefine = 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ディレクティブで定義した、どのメモリ空間に割り当てるかを定義する。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の定義例

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イメージも、C言語で作成可能となる。
(詳細は、cc65のマニュアルを参照。)

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

FEATURES

C言語ランタイムを用いて開発する場合は、コンフィグファイル(*.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__;
}
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2