Z80

SDCC用の環境構築

2012年5月22日

Z80用のコンパイラとして、何か使えるかなと思い、Webを色々検索してみると、SDCC (Small Device C Compiler)というものを見つけた。ライセンスはGPLだから、普通に使える。

使い方に関する情報が多くはない。それでも先人の方々がいらっしゃるので、初めての人間には非常に助かる。以下のページを参考にして、KM-Z80用(MZ-80K用)のソフト開発環境を構築してみた。
Z80のC言語クロスコンパイル(SDCC)(1) - Resilient Mind
SDCCでクロス開発環境をインストールと使い方(Z80用) - Tosikの雑記

コンパイルは、

sdcc main.c -mz80

のように指令すればよいようだ。インテル方式のHEXファイル(*.ihx)が作成される。ただ、このデフォルトのコンパイルでは色々と問題があって、そのままでは使えないらしい。スタックの開始位置が0xFFFFになっていたり、スタートアドレスが0x0100になっていたりするので、これをKM-Z80用に変更する必要がある。上記2つのリンク先両方で説明されているが、専用のCRT (C Run Time)を作成して、それを利用すればよいとのこと。MZ-80K用のソフトでは、コールドスタートとホットスタートの2つが用意されているのが常だから、0x1200がコールドスタート、0x1203がホットスタートとするようなランタイムとし、アセンブラコード(ファイル名:crt.asm)を次のようにした。

.globl _init
.globl _main
call _init
jp _main

C のソースコードを書くときに、main()関数とinit()関数の2つを準備することになる。このアセンブラコードからオブジェクトを、次のようにして作成した。

sdasz80 -o crt.o crt.asm

この"crt.o"ファイルを取り込むようにリンクすれば、KM-Z80用に利用できる。次のようにコンパイル(+リンク)すればよい。

sdcc main.c -mz80 --code-loc 0x1200 --no-std-crt0 -Wlcrt.o

"sdcc -mz80"というコマンドで、コンパイルとリンクが続けて行われる。ただ、この方法だと、一つのCファイルしか使えない。中規模以上のプログラムになると、複数のCファイルが必要になる。これを行うには、コンパイルのみをまず行い、最後に複数のオブジェクトをリンクすればよい。次のように行えばよいらしい。

sdcc main.c -mz80 -c
sdcc sub.c -mz80 -c
sdcc main.rel sub.rel -mz80 --code-loc 0x1200 --no-std-crt0 -Wlcrt.o

これを自動で行うバッチファイル(Windows用)を次のように作成した。

@if exist *.ihx del *.ihx
@if exist *.asm del *.asm
@for %%i in (*.c) do sdcc %%i -mz80 -c
sdcc *.rel -mz80 --code-loc 0x1200 --no-std-crt0 -Wlcrt.o
@ren *.ihx release.ihx
@if exist *.lk del *.lk
@if exist *.lst del *.lst
@if exist *.map del *.map
@if exist *.noi del *.noi
@if exist *.rel del *.rel
@if exist *.rst del *.rst
@if exist *.sym del *.sym
@if exist *.cdb del *.cdb
@if exist *.mem del *.mem
@if exist *.omf del *.omf

これで、このバッチファイルを実行するだけで、"release.ihx"というHEXファイルが出来上がる。あとは、このHEXファイルをMZTファイルもしくはMZFファイルに変換し(多分、VBScriptで行ける)、mzt2wav.exe もしくは mzf2wav.exeを用いてWAVファイルに変換すれば、KM-Z80上で実行できるようになるはずだ。

120523 追記
ihxファイルをmztファイルに変換するスクリプト(VBScript)は、以下の通り。VBScriptでバイナリデータを扱うためのクラスを以下のリンク先を参考に使わせていただいた。
ADODB.Streamに書き込めるバイナリデータを生成するクラス

option explicit
dim fileName, loadAddress, startAddress, ihxFileName, mztFileName

fileName="KM-BASIC"
loadAddress=&h1200
startAddress=&h1200
ihxFileName="release.ihx"
mztFileName="release.mzt"

makeMzt fileName,loadAddress,startAddress,hexData(ihxFileName,loadAddress),mztFileName
msgbox mztFileName & " is created."

function hexData(fname,loadAddr)
	Dim fobj, line, addr, b1, b2, b3
	dim hdata
	hdata=string("0",fileLength(fname,loadAddr)*2)
	set fobj=CreateObject("Scripting.FileSystemObject").OpenTextFile(fname,1)
	while not fobj.AtEndOfStream
		line=fobj.ReadLine()
		if left(line,1)=":" then
			b1=cByte("&H" & mid(line,2,2)) 'length
			b2=cByte("&H" & mid(line,4,2)) 'address MSB
			b3=cByte("&H" & mid(line,6,2)) 'address LSB
			addr=b2*256+b3-loadAddr
			if 0<b2 then
				hdata=left(hdata,addr*2) & mid(line,10,b1*2) & mid(hdata,addr*2+b1*2+1)
			end if
		end if
	wend
	hexData=hdata
end function

function fileLength(fname,loadAddr)
	Dim fobj, line, flen, b1, b2, b3
	set fobj=CreateObject("Scripting.FileSystemObject").OpenTextFile(fname,1)
	flen=loadAddr
	while not fobj.AtEndOfStream
		line=fobj.ReadLine()
		if left(line,1)=":" then
			b1=cByte("&H" & mid(line,2,2)) 'length
			b2=cByte("&H" & mid(line,4,2)) 'address MSB
			b3=cByte("&H" & mid(line,6,2)) 'address LSB
			if flen<b2*256+b3+b1 then flen=b2*256+b3+b1
		end if
	wend
	fileLength=flen-loadAddr
end function

sub makeMzt(fname,load,start,hData,saveFile)
	dim stream, bstream, i, fsize

	fsize=len(hData)/2

	fname=left(fname,15)
	for i=1 to 15
		fname=fname & chr(&h0D)
	next
	fname=left(fname,16)

	set bstream=new ByteStream
	set stream=CreateObject("ADODB.Stream")

	stream.open
	stream.type=1

	stream.write bstream.getByte(&h01) '0x01: machine code
	for i=1 to 16
		stream.write bstream.getByte(asc(mid(fname,i,1)))
	next
	stream.write bstream.getByte(&h00)
	stream.write bstream.getByte(fsize mod 256) 'byte size LSB
	stream.write bstream.getByte(fsize \ 256)   'byte size MSB
	stream.write bstream.getByte(load mod 256)  'load address LSB
	stream.write bstream.getByte(load \ 256)    'load address MSB
	stream.write bstream.getByte(start mod 256) 'start address LSB
	stream.write bstream.getByte(start \ 256)   'start address MSB
	for i=1 to 104
		stream.write bstream.getByte(&h00) 'Comment
	next
	for i=1 to fsize
		stream.write bstream.getByte(cByte("&h"&mid(hData,i*2-1,2)))
	next

	stream.SaveToFile saveFile,2
	stream.close
end sub

' Following class was fetched from:
' http://sei.qee.jp/docs/program/hta/sample/bstream.html

Const ENCODE_UNICODE = "unicode"

' StreamTypeEnum
Const adTypeBinary = 1
Const adTypeText = 2

'*********************************************************************
' ByteStreamクラス
' version 1.1
'*********************************************************************
Class ByteStream
	Private innerArray(255)
	'=================================================================
	' クラスの初期化処理
	'=================================================================
	Private Sub Class_Initialize()

		Dim wkStream
		Set wkStream = WScript.CreateObject("ADODB.Stream")
		wkStream.Type = adTypeText
		wkStream.Charset = ENCODE_UNICODE
		wkStream.Open

		Dim i
		For i=0 To &hff
			wkStream.WriteText ChrW(i)
		Next
		wkStream.Position = 0
		wkStream.Type = adTypeBinary

		If ("fe" = LCase(Hex(AscB(wkStream.Read(1))))) Then
			wkStream.Position = 2
		End If

		For i=0 To &hff
			wkStream.Position = wkStream.Position + 1
			innerArray(i) = wkStream.Read(1)
		Next

		wkStream.Close
		Set wkStream = Nothing
	End Sub
	'=================================================================
	' 指定した数値のByte()を返す
	'=================================================================
	Public Function getByte(num)
		If (num < 0) Or (UBound(innerArray) < num) Then
			getByte = innerArray(0) '0x00を返す
		Else
			getByte = innerArray(num)
		End If
	End Function
End Class

使用の際は、4-8行目を書き換える必要がある。

コメント

コメントはありません

コメント送信