石原 博の覚書

電子工作に関する日々の覚書を記載します

CP/M 68K高速化2

2024-01-21 11:56:36 | 日記
せっかくCP/M 68Kが動き始めたのに遅い。原因はSDカードのSPIアクセスにあることはわかっている。
すべて68Kのソフトウエアで行っているため。

そこで少しでも速くならないかと、さらにコードを見直した。

従来は読み込みと書き込みを一つのサブルーチンで行っている。spiの仕様的にはそうなのかもしれないが、
読みながら次のコマンドを書き込むなどは今回のプログラムでは行っていない。
なのに同時にすることで余計に時間がかかっている。
*
spi8r2:	move.b	#$ff,d0		*spi read (write $ff)
spi8w2:	moveq	#7,d5		*spi write
spiloop2:
	btst	d5,d0
	beq	lout2
	bset.b	d7,(a3)		*MOSI=H
	bra	spiout2
lout2:	bclr.b	d7,(a3)		*MOSI=L
spiout2:bset.b	d3,(a3)		*SCK=H
	btst.b	d4,(a3)		*MISO
	beq	lin2
	bset	d5,d0
	bra	spiin2
lin2:	bclr	d5,d0	
spiin2:	bclr.b	d3,(a3)		*SCK=L
	dbra	d5,spiloop2
	rts

そこで読み込みと書き込みを分離した。
*
spi8r2:	move.b	#$ff,d0		*spi read (write $ff)
	moveq	#7,d5
	bset.b	d7,(a3)		*MOSI=H
spiloop2r:
	bset.b	d3,(a3)		*SCK=H
	btst.b	d4,(a3)		*MISO
	beq	lin2
	bset	d5,d0
	bra	spiin2
lin2:	bclr	d5,d0	
spiin2:	bclr.b	d3,(a3)		*SCK=L
	dbra	d5,spiloop2r
	rts
*
spi8w2:	moveq	#7,d5		*spi write
spiloop2w:
	btst	d5,d0
	beq	lout2
	bset.b	d7,(a3)		*MOSI=H
	bra	spiout2
lout2:	bclr.b	d7,(a3)		*MOSI=L
spiout2:bset.b	d3,(a3)		*SCK=H
	bclr.b	d3,(a3)		*SCK=L
	dbra	d5,spiloop2w
	rts
*
結果
 1分13秒かかっていたCのコンパイルが52秒と少し速くなった。
 ロジックアナライザで確認すると、以前約150Kbps(読むのが0の時と1の時では時間が変わる)だったものが、
 266Kbps程度となった。

ロジックアナライザの結果


CP/M 68K高速化

2024-01-14 11:44:00 | 日記
せっかくCP/M 68Kが動き始めたのに遅い。原因はSDカードのSPIアクセスにあることはわかっている。
すべて68Kのソフトウエアで行っているため。

そこで少しでも速くならないかとコードを見直した。

spi8rが従来のコードで、spi8r2が変更したコード。要するに絶対アドレッシングをアドレスレジスタ間接に変更しただけ。
少しでも速くするため、bsetやbclrのイミーディエイト指定もデータレジスタの設定を利用した。

spi8r:	move.b	#$ff,d0		*spi read (write $ff)
spi8w: 	moveq	#7,d5		*spi write
spiloop:btst	d5,d0
	beq	lout
	bset.b	#MOSI,PDR+1	*MOSI=H
	bra	spiout
lout:	bclr.b	#MOSI,PDR+1	*MOSI=L
spiout:	bset.b	#SCK,PDR+1	*SCK=H
	btst.b	#MISO,PDR+1	*MISO
	beq	lin
	bset	d5,d0
	bra	spiin
lin:	bclr	d5,d0	
spiin:	bclr.b	#SCK,PDR+1	*SCK=L
	dbra	d5,spiloop
	rts
*
spi8r2:	move.b	#$ff,d0		*spi read (write $ff)
spi8w2:	moveq	#7,d5		*spi write
spiloop2:
	btst	d5,d0
	beq	lout2
	bset.b	d7,(a3)		*MOSI=H
	bra	spiout2
lout2:	bclr.b	d7,(a3)		*MOSI=L
spiout2:bset.b	d3,(a3)		*SCK=H
	btst.b	d4,(a3)		*MISO
	beq	lin2
	bset	d5,d0
	bra	spiin2
lin2:	bclr	d5,d0	
spiin2:	bclr.b	d3,(a3)		*SCK=L
	dbra	d5,spiloop2
	rts

これをアクセスするブロックを読み出す部分は以下のとおり
readsd:	bsr	cmd17
	tst.b	d0		*data packet error
	bne	readsd_err
	bsr	skipff		*wait
	cmpi.b	#$fe,d0		*DATA TOKEN
	bne	readsd_err
	movea.l	DMASD,a0
	move.w	#$1ff,d6	*512byte
	movea.l	#PDR+1,a3
	move.w	#MOSI,d7
	move.w	#MISO,d4
	move.w	#SCK,d3
rd1:	bsr	spi8r2
	move.b	d0,(a0)+
	dbra	d6,rd1
	bsr	spi8r		*CRC SKIP
	bsr	spi8r		*CRC SKIP
	moveq	#0,d0
	bra	csh

高速に読むことが必要な部分だけspi8r2に変更している。
(高速化のためできるだけスタックは使いたくないが、アセンブラで組んでいるためレジスタの遣り繰りを考えるのが面倒)

結果
 1分39秒かかっていたCのコンパイルが1分13秒と少しだけ速くなった。
 ロジックアナライザで確認すると、spi8rだと約100Kbps spi8r2だと約150Kbps(読むのが0の時と1の時では時間が変わる)

 フロッピーディスクでは250Kbpsや500Kbpsらしいのでそれより遅い。体感もそんな感じ。

その他
 せっかく動いたので、ソフトウエアを探したがCP/M80のように大量にあるわけではない。
 それならと、Cで記述されたフリーソフトを探したが、今の時代ANSI規格だし変数名が8文字以内というのがネックになる。
 昔は68Kに憧れていたんだけどなぁ

ロジックアナライザの結果
CMD17を送出(spi8w)後、読み込み(spi8r)で$FEを待ち、その後spi8r2で高速読み込。
spi8rだと約100Kbps, spi8r2だと約150Kbpsであることがわかる。


デジットのジャンク68Kボード(2021年購入)でCP/M 68Kが動いた

2024-01-07 21:33:12 | 日記
デジットのジャンク68kボードでForthとSDカードが動いたので、次にCP/M68Kを動かしてみた。
(以前68K-MBCでCP/M68Kを動かしたことがあるが、今度はBIOSから作成)

ジャンク68KボードはRAMがTC55257x2 なので64KBしかない。
CP/M68Kでは最低128KB必要とのことなので、TC551001x2を購入し使用。これで256KBは確保出来る。
(ボード上のジャンパはEをショートのままでOK)

skyriverさん(https://piclabo.blog.ss-blog.jp/Pic24CPM68K)
お気楽オーディオキット資料館さん(http://www.easyaudiokit.com/bekkan2020/try_cpm68k/cpm68k.html)
を参考にさせていただきました。

[1]CP/M領域
「お気楽オーディオキット資料館」さんのところからCP/M68Kv1_3をダウンロードし展開。
(http://www.easyaudiokit.com/bekkan2020/try_cpm68k/cpm68k.html)

skyriverさんのところで指摘(https://piclabo.blog.ss-blog.jp/ProblemOfCPM400SR)されているように、
CPM400.SRには問題があるようだ。googleで調べると同様の記載があちらこちらに
------https://www.retrobrewcomputers.org/forum/index.php?t=msg&th=573&start=0&)-------
As you can see the _init address is at 0x6000 * but * there are various variables in the bss section that are inside the CBIOS area (stating at 0x6000), so the resulting file simply can't work...

So there are two possible solutions:

1. put a JMP 0x6200 at the beginning (at $6000) so the CBIOS can continue from 0x6200 outside the bss section of CPM400.SR;

2. use a patched CPM400.SR file where the jump to _init is modified to 0x6200 (instead to 0x6000).
----------------------------------------------------------------------------------

今回はROMに書き込んだCP/MをRAMに転送し、その後RAMに切り替えて使用することを考えているので、CPM400SRの方が都合がよい。
そこでBIOSは0x6200からとし、0x6000には0x6200(BIOSの_init)へのJMPを書き込んで問題を回避した。

0000------
Interrupt Vectors
0400------
CPM400
6000------
BIOS_init_patch (CPM400 workarea)
6200------
BIOS
7fff------
TPA
3ffff------

ROM内の0〜CCP,BDOS,BIOSを含めた32KBのデータを起動時にRAM(起動時は 400000)に転送し、その後RAMを0〜に設定し切り替える。


[2]アセンブル時の問題
BIOSは「CPM-68K_System_Guide_Jan83.pdf」を参考にしたが、EASy68Kのbugのためアセンブル出来ない。(エラーも出ない)
以下の movea.l 6(pc,d0),a0の部分だが、Program Counter Relative with Indexが、EASy68Kではちゃんとアセンブル出来ない。

traphndl:
cmpi Infuncs,dO
bcc trapng
lsl t2,dO
movea.l 6(pc,dO) ,aO
jsr (aO)
trapng:
rte
biosbase:

テストすると以下のようにオフセットに何を指定しても同じコードが出ていることがわかる。その上エラーも出ない。
0000000C 207B 00FE 8 move.l (PC,D0),A0
00000010 207B 00FE 9 move.l (0,PC,D0),A0
00000014 207B 00FE 10 move.l (2,PC,D0),A0
00000018 207B 00FE 11 move.l (lab,PC,D0),A0

Webで調べるとつのぜみさんのところ(https://tunozemichan.hatenablog.com/entry/2023/04/19/204452)では、
BIOSで以下のコードを使用していた。(バグに気づいてたのか。ロジックとしてはこの方が素直だよね)

traphndl:
move.l #biosbase,a0
cmpi #nfuncs,d0
bcc trapng
lsl #2,d0
move.l 0(a0,d0),a0
jsr (a0)
trapng:
rte
biosbase:

また68KMBCでは以下のとおり。

traphndl:
cmpi #nfuncs,d0
bcc trapng
lsl #2,d0 ; Multiply bios function by 4

**X won't assemble here: movea.l 6(pc,d0),a0 * get handler address
**X replace with this:

movea.l *+8(pc,d0),a0 ; Get handler address
jsr (a0) ; Call handler

またhttps://github.com/dwildie/cpm-68k/blob/master/bios/bios.s では

handler: CMPI.W #funcCount,%D0
BCC h1
EXT.L %D0
LSL.L #2,%D0 | multiply bios function by 4
LEA fTable,%A0 | Get offset into table
MOVE.L (%A0,%D0.W),%A1 | get handler address
JSR (%A1) | call handler
h1: RTE

みんな気づいていろいろしているようだ。

私の場合は以下のように回避した
traphndl:
cmpi #nfuncs, d0
bcc trapng
lsl #2,d0
dc.b $20,$7b,$00,$06 * EASy68K bug move.l (biosbase,PC,d0),a0
jsr (a0)
trapng: rte


[3]ROMへの書き込み
ROMはデバッグに何度も書き換えが必要なのでWinbond W27C512-45を使っている。
書き込みはMiniPro。8bit単位のROMが2個(UpperとLower)なので、2回に分ける必要がある。
CPM400.SRは objcopy -I srec -O binary 68Kv1_3/DISK9/CPM400.SR CPM400.bin でバイナリにしておく。
BIOSもEASy68Kでアセンブルしたものは、モトローラのS-record形式なのでバイナリにしておく
(例えば srec_cat bios.S68 -o bios.bin -binary)

Upper側はlowbyte。MiniProの書き込みソフトで、「load a low byte of a WORD(2bytes)」があるのでそれを使う
Lower側はhighbyteで、「load a high byte of a WORD(2bytes)」ただしアドレスには注意が必要

「load a low byte」として
自分で作ったBIOS.68KはFrom File Start Addr 00000 TO Buffer Start Addr 00000 としてClear Buffer with deault
CPM400.binは$400~だがFrom File Start Addr 00000 TO Buffer Start Addr 00200 としてdisable で LOADし
Upper側のROMに書き込み

「load a low byte」として
自分で作ったBIOS.68KはFrom File Start Addr 00000 TO Buffer Start Addr 00000 としてClear Buffer with deault
CPM400.binは$400~だがFrom File Start Addr 00000 TO Buffer Start Addr 00200 としてdisable で LOADし
Lower側のROMに書き込み


[4]SDカード
BIOSはDISK I/Oが面倒。CP/Mは1セクタ128バイトなのに対して、SDカードの1ブロックは512バイト。
ブロックの先頭128バイトを使用するという(「pineconeの電子工作」さんがCP/M80でSDカード使用時に行ったような)手段もあるが、
その場合はデータの作成が面倒。そこで私は簡単なブロッキング・デブロッキングを組み込んだ。
このため初期のSDCARDを作るのは簡単になった。

以前はForthでのSPIアクセスに成功していたため、出来るはずだが、なれない68KのアセンブラではSDカードの初期化を含め苦労した。
少しでもアクセスを速くしたかったが、初期化時のSPIの伝送速度を100k~400kbpsとか。
実際作ってロジックアナライザでSCKを確認すると、16MHzのクロックを使用する68Kでもほぼ100Kbpsしか出ない。
(以下のように絶対アドレッシングを使用しているため。アドレスレジスタを使ってアクセスすると倍ぐらいには出来そう)

spi8r: move.b #$ff,d0 *spi read (write $ff)
spi8w: moveq #7,d5 *spi write
spiloop:btst d5,d0
beq lout
bset.b #MOSI,PDR+1 *MOSI=H
bra spiout
lout: bclr.b #MOSI,PDR+1 *MOSI=L
spiout: bset.b #SCK,PDR+1 *SCK=H
btst.b #MISO,PDR+1 *MISO
beq lin
bset d5,d0
bra spiin
lin: bclr d5,d0
spiin: bclr.b #SCK,PDR+1 *SCK=L
dbra d5,spiloop
rts


[5]DISKのフォーマット
CP/M80の経験があるのでディスクパラメタブロックは容易に出来ると思ったが、実はレイアウトに少し差がある。
(以下dummyの部分。16ビットCPUのアライメントの問題なのか)
68Kではファイルのサイズが大きいので片面単密では全く容量が足りない。
ディスク交換するわけではないので、IBMフォーマットは不要だが、慣例としてA,BをIBMフォーマット、Cを8MBとした。

* Floppy 26sector 77track IBM format
dpb: dc.w 26 *SPT
dc.b 3 *BSH 1block=1024
dc.b 7 *BLM
dc.b 0 *EXM
dc.b 0 *dummy
dc.w 242 *DSM disk_size-1 (77-2)*26*128/1024-1=242.75 (track-offset)*SPT*128/BLOCK-1
dc.w 63 *DRM
dc.w $c000 *Alloc0, Alloc1
dc.w 16 *CKS
dc.w 2 *offset
*
* HDD 8mb 256sector 255track
dpb2: dc.w 256 *SPT
dc.b 4 *BSH 1block=2048
dc.b 15 *BLM
dc.b 0 *EXM
dc.b 0 *dummy
dc.w 4095 *DSM disk_size-1 256*256*128/2048-1=4095
dc.w 1023 *DRM
dc.w $ffff *Alloc0, Alloc1
dc.w 0 *CKS
dc.w 0 *offset


* A:
dph0: dc.l 0 *xlt
dc.w 0
dc.w 0
dc.w 0
dc.l dirbuf
dc.l dpb
dc.l ckv0
dc.l alv0
*
* B:
dph1: dc.l 0 *xlt
dc.w 0
dc.w 0
dc.w 0
dc.l dirbuf
dc.l dpb
dc.l ckv1
dc.l alv1
*
* C:
dph2: dc.l 0 *xlt
dc.w 0
dc.w 0
dc.w 0
dc.l dirbuf
dc.l dpb2
dc.l ckv2
dc.l alv2

dirbuf: ds.b 128
ckv0: ds.b 16 *(drm0+1)/4 (63+1)/4=16
alv0: ds.b 32 *dsm0/8+1 242/8+1=32
ckv1: ds.b 16 *(drm1+1)/4 (63+1)/4=16
alv1: ds.b 32 *dsm1/8+1 242/8+1=32
ckv2: ds.b 256 *(drm2+1)/4 (1023+1)/4=256
alv2: ds.b 513 *dsm2/8+1 4095/8+1=513
*

===cpmtools/diskdefs===
diskdef 8mb
seclen 128
tracks 256
sectrk 256
blocksize 2048
maxdir 1024
skew 1
boottrk 0
os 2.2
end


[6]ディスクイメージの作成
256track, 256sec/track, 128byte/sec として bs=128, count=65536としている。

dd if=/dev/zero of=diskimagec bs=128 count=65536
mkfs.cpm -f 8mb diskimagec

内容は68Kv1_3のDISK1〜9をまとめたフォルダCを作成し
cpmcp -f 8mb diskimagec ../68Kv1_3/C/* 0:
とした。

注意
 Web上では 「cpmcp -f cpm_a diskimage ./a/*.* 0:」と*.*としている場合があるが、
 CP/M68Kでは、拡張子のない「AS68INIT」がある。*.*ではコピーされない。


実際のSDカードへの書き込みはA, Bの領域をあけてseekしている
dd bs=512 seek=1002 if=./diskimagec of=/dev/sdb

A,Bドライブは 77track, 26sec/track, 128byte なので 256256byte
512ブロック単位では500.5 -> 501ブロック
Aドライブは0〜500ブロック、Bドライブは501〜1001ブロックなので、seek=1002としている。


BIOS内でdisk, track, secからトータルセクタへの変換は以下のようにしている。
* block = sector + track*n + offset
* A,B:77track 26sector C:256track 256sector
* A sector+track*26 B sector+track*26+77*26 C sector+track*256+77*26*2
getsect:movea.w seldrv,a0 *seldrv=0,1,2
adda.w a0,a0
adda.w #blkp,a0
move.w (a0),d0 *get sector/track
mulu track,d0 *track
add.w (6,a0),d0 *disk offset
add.w sector,d0
rts
*
blkp: dc.w 26 *A:sector
dc.w 26 *B:sector
dc.w 256 *C:sector
dc.w 0 *A:sector offset 0 --- (floor(77*26/4)+1)*4-1
dc.w 2004 *B:sector offset (floor(77*26/4)+1)*4 --- (floor(77*26*2/4)+1)*4-1
dc.w 4008 *C:sector offset (floor(77*26*2/4)+1)*4 --- (floor(256*256/4)+1)*4-1


[7]RELから68Kへ
reloc1.subからreloc9.subを実行し、リロケータブルファイルから通常の形式に変換
9分53秒程度


[8]Cコンパイラ
せっかくCP/M68KなのでCコンパイラを動かす
TRY! CPM-68Kの巻き(http://www.easyaudiokit.com/bekkan2020/try_cpm68k/cpm68k.html)にあるように
事前に AS68.REL -I AS68INIT を実行して置く必要がある。

C>AS68 -I AS68INIT
68000 assembler initialized

C>c test



C>CP68 -I 0: TEST.C TEST.I

C>C068 TEST.I TEST.1 TEST.2 TEST.3 -F

C>ERA TEST.I

C>C168 TEST.1 TEST.2 TEST.S

C>ERA TEST.1

C>ERA TEST.2

C>AS68 -L -U -S 0: TEST.S

C>ERA TEST.S

C>clink test



C>LO68 -R -U_NOFLOAT -O TEST.68K 0:S.O TEST.O .O .O .O .O .O .O .O .O 0:CLIB

C>test


OK


実行時間を測定するとやっぱり遅い。コンパイルで1分39秒。リンクで1分11秒(合わせて2分50秒)
(お気楽オーディオキット資料館さんのところではコンパイル・リンク合わせて1分50秒とのこと)

お気楽オーディオキット資料館さんのところではPICがSDカードとやり取りしているのに対して、
こちらは68KのソフトウエアでSPI接続しているからなあ。実測100Kbpsだし。
動くからまあ良いか。


[9]その他
・Easy68Kのmacroでは引数に文字列を渡す場合、シングルクオートだが空白を含めることができない。
 (クオートを考慮せずセパレータとして見ているようだ)
・シリアルの入出力は以前のForthのを流用。割り込みは使用していない


このボードは、TMP68301 16MHzでメモリ256KB,昔憧れた68Kも案外スピードが遅いことに驚いた。
SDカードのアクセスが遅いとはいうものの、MacPlusって68K 8MHz メモリ1MB 3.5inchFDDだったんだよねぇ。
(初代Macは128KBメモリ) 信じられないなあ。