石原 博の覚書

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

AVR CPMフディスクパラメタブロックの設定変更

2021-08-22 18:39:58 | 日記

CP/Mを幾つか動作させ、アプリケーションもいろいろ試してみた。しかしディレクトリの階層構造がないのは辛い。
A:に基本的なコマンドを入れ、B:〜に実験するプログラムを入れるとして、理論上はB〜Pまで15のフォルダが作れるとはいうものの、出来合いのBIOSではせいぜいB:、C:ぐらいまで。microSDの容量はCP/Mの世界から見ればとんでもなく大きく、使わないのはもったいないので出来るだけ使うことにした。

ただしAVRCPMでは、dsk_fsys.asmで以下の定義があり、A〜Hまでしか出来ない
 .equ MAXDISKS       = 8  ;Max number of Disks (partitions)

変更箇所はipl.asm, bios.asm, dsk_fsys.asm, dskdefs。ただしbiosが大きくなりすぎた場合はCPM.SYSも変更する必要が出る。ドライブを増やすとアロケーションベクタが増えるため得策ではない。そこで8ドライブで十分と考えた。

他の領域を増やす方法としてはuserコマンドがある。user 0 や user 1 としてユーザを切り替えて使用することが出来る(0〜15の16ユーザまで可能)。これだと1つのドライブに複数ユーザ分の領域を取ることが出来る。ところが登録出来るファイル数を超えると「NO DIRECTORY SPACE」というエラーとなる。そのためディレクトリエントリも増やしておきたい。

以前はneko Javaさんの「CP/Mディスクイメージ作成手順メモ」http://star.gmobb.jp/koji/cgi/wiki.cgi?page=CP%A1%BFM%A5%C7%A5%A3%A5%B9%A5%AF%A5%A4%A5%E1%A1%BC%A5%B8%BA%EE%C0%AE%BC%EA%BD%E7%A5%E1%A5%E2
を参考にディスクを作成したのだが、今回は必要な設定を調べるため、複数のCP/MのBIOSからディスクパラメタブロック(DPB)を抽出しリスト化した(標準8inch、AVR-CP/M(RAMディスクとneko Javaさん記載のもの)、z80-mbc2、multicomp、Z80pack-1.36)

・Z80-MBC2やmulticompのDSMが2047ではなく2043、コメントが .DW 2043 ;(2047-4) DSM - Storage size (blocks - 1)となっているので意図的
・Z80-MBC2のAllocation Vector(256じゃなくて257?)
 が理解出来ない。

今回の場合はちょうどZ80pack-1.36のHDBLKが使えそう。ディレクトリエントリ数も最大1024まである。このままではシステム領域がないのでオフセットを1と変更して使用しよう。

修正ポイント
[1]ipl.asm
 トラック0のセクタ0はIPLが入っている。セクタ1〜44までの44トラック(5.6KB)はCCP, BDOSが使用。セクタ45〜127までの83トラック(10KB)はBIOSが使えることになる。とはいうもののBIOSが大きくなるとCP/Mの大きさも変える必要がある(CPM.SYSの作り変えを伴う)

;            CCP   BIOS  BIOSの容量 BIOSに許されるセクタ数
; 56K CP/M   c400  da00  9.7KB     76 
; 58K CP/M   cc00  e200  7.6KB          60
; 60K CP/M   d400  ea00  5.6KB          44
; 62K CP/M   dc00  f200  3.5KB          28    <-- 以下のBIOSは約2.9KBのため、これが使える
; 64K CP/M   e400  fa00  1.5KB          12

今回の例では約8MBの8ドライブで、アロケーションベクタだけでも255x8=2KBは必要になるが、BIOSはトータル2.9KBとなるため、62K CP/M版のCPM.SYSが使える。

> ld b,72    ;44(CCP+BDOS)+28(BIOS)=72
> ld de,$0001 ;d=trk e=sec ;ここではセクタは0オリジンなので注意
> ld hl,$dc00 ;CCP


[2]bios.asm
 デイスクを大容量化し、8ドライブ+RAMドライブ1とする。

drvtbl: ;9drive
 dw     dpha
 dw     dphb
 dw     dphc
 dw     dphd
 dw     dphe
 dw     dphf
 dw     dphg
 dw     dphh
 dw     dphi
;
dpbrd:  ;ram disk
 dw      32              ;sec per track
 db      3               ;block shift BSH 1028=128*(2^3)
 db      7               ;block mask  BLM 1028/128-1=7
 db      0               ;extnt mask  EXM
 dw      55              ;disk size-1 DSM 128*32*(16-2)/1024-1=55
 dw      31              ;directory max DRM 2*16-1
 db      128             ;alloc0
 db      0               ;alloc1
 dw      0               ;check size  CKS (DRM+1)/4=(31+1)/4=8 取り外し不可のため0
 dw      2               ;offset
;
hdblk:
 dw      128             ;sec per track
 db      4               ;block shift BSH 2048=128*(2^4)
 db      15              ;block mask  BLM 2048/128-1=15
 db      0               ;extnt mask  EXM
 dw      2039            ;disk size-1 DSM 255*128*128/2048-1=2039
 dw      1023            ;directory max DRM 256*128/32-1=1023
 db      255             ;alloc0
 db      255             ;alloc1
 dw      0               ;check size 取り外し不可のため0
 dw      1               ;offset
;                                
dpha:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv0
dphb:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv1
dphc:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv2
dphd:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv3
dphe:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv4
dphf:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv5
dphg:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv6
dphh:
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,hdblk    ;dir buff,parm block
 dw      0
 dw      alv7
;
dphi:   ;ramdisk
 dw      0000h,0000h     ;no translate table
 dw      0000h,0000h     ;scratch area
 dw      dirbuf,dpbrd    ;dir buff,parm block
 dw      0
 dw      alv8
;

[3]dsk_fsys.asm

AVR-CPMのエミュレータ側だが、ここでもディスクの定義が必要

dpbdat_avrcpm:    ;
 .db 0x00,128  ;sector offset, low(spt)
 .db 0x00,4  ;high (spt), block shift
 .db 15,0x00  ;bock mask, extent mask
 .db 0xF7,0x07  ;disk size - 1,
 .db 0xFF,0x03  ;dir max
 .db 0xFF,0xFF  ;alloc0, alloc1
 .db 0x00,0x00  ;chk size
 .db 0x01,0x00  ;offset


[4]diskdefs

ディスク内容を生成するための定義も変更が必要

# HDBLK -----------------
diskdef HDBLK
  seclen 128
  tracks 255
  sectrk 128
  blocksize 2048
  maxdir 1024
  skew 1
  boottrk 1
  os 2.2
end

ちなみにこれを利用して、以下のシェルスクリプトでコンテンツを作成(contentsフォルダ内にAからHのディレクトリを作成(ドライブ相当)、その下に0〜15のディレクトリを作成(ユーザ相当)している。これはRunCPMで使用されている構造と同じ)

===コンテンツ作成シェルスクリプト===
#62K CP/M  CCP  BIOS
#          DC00 F200
# F200-DC00=1600 $1600=5632  5632/128=44sector
# 10000-F200=e00 ->28sector
dd conv=sync bs=128 count=1 if=ipl.bin > cpm.bin
dd conv=sync bs=128 count=44 if=cpm.sys >> cpm.bin
dd conv=sync bs=128 count=28 if=bios.bin >> cpm.bin

echo make contents
#255track 128sector/track 128byte/sec
drive=( A B C D E F G H )
b=16384
c=255

for d in ${drive[@]}; do
  dd conv=sync bs=${b} count=${c} if=/dev/zero > CPMDSK_${d}.IMG
  if [ ${d} = "A" ]; then
    mkfs.cpm -f HDBLK -b cpm.bin -L DRIVE_${d} CPMDSK_${d}.IMG
  else
    mkfs.cpm -f HDBLK -L DRIVE_${d} CPMDSK_${d}.IMG
  fi   
  for i in `seq 0 15`; do
    if [ -d contents_folder/${d}/${i} ]; then
      cpmcp -f HDBLK CPMDSK_${d}.IMG contents_folder/${d}/${i}/*.* ${i}:
      echo ${d} ${i}
    fi
  done
done

 


DENVBAS

2021-08-13 15:31:08 | 日記

CP/Mが動くハードが何種類も出来たが、それだけでは面白くない。

有り難いことに、インターネット上に様々なソフトがある。
そこでいくつかダウンロードして動かしてみた。

CPMUG#17 のDENVBAS

CATALOG.17には以下のとおり記載があり、CP/M対応ではない
----
17.3 16K DENVBAS.ASM DENVER TINY BASIC FROM DR. DOBBS
    NO CP/M I/O YET
----

てっとり速く、コンソール入出力だけCP/Mに対応。(他にもASMでアセンブル出来ないためENDM=>ENDMXと変更)

$ git diff
diff --git a/DENVBAS.ASM b/DENVBAS.ASM
index d303cb8..5242f7e 100644
--- a/DENVBAS.ASM
+++ b/DENVBAS.ASM
@@ -1,5 +1,35 @@
-       ORG     3
-; 
+; CP/M I/O
+;--CP/M interface---------------
+BDOS   EQU     5
+ORIGIN EQU     100H
+;
+       ORG     ORIGIN
+       JMP     STRT
+;
+INPUT:
+CIN:   PUSH    H
+       PUSH    D
+       PUSH    B
+       MVI     C,1
+       CALL    BDOS
+       POP     B
+       POP     D
+       POP     H
+       RET
+;
+COUT:  PUSH    H
+       PUSH    D
+       PUSH    B
+       PUSH    PSW
+       MOV     E,A
+       MVI     C,2
+       CALL    BDOS
+       POP     PSW
+       POP     B
+       POP     D
+       POP     H
+       RET
+;
 STAK   EQU     7100H
 ; 
 ; TINY BASIC INTERPRETER
@@ -410,10 +440,10 @@ TPI2:     IN      TAPU
        RZ      
        INX     H
        JMP     TPIN+2
-TVTI:  JMP     3F08H
+TVTI:  JMP     CIN
 TVTO:  PUSH    B
        MOV     C,A
-       CALL    3809H
+       CALL    COUT
        POP     B
        RET
 ; 
@@ -524,7 +554,7 @@ INMS:       DB      'I','N'+128
 RTMS:  DB      'RE','T'+128
        LXI     D,ENMS
        CALL    TST
-       JMP     ENDM
+       JMP     ENDMX
 ENMS:  DB      'EN','D'+128
        LXI     D,LSMS
        CALL    TST
@@ -560,7 +590,7 @@ LDMX:       CALL    TSTV
        JNC     DMER
        LXI     D,DMC2
        CALL    TST
-       JMP     LDMX:
+       JMP     LDMX
 DMC2:  DB      ','+128
        CALL    DONE
        JMP     NXT
@@ -1402,7 +1432,7 @@ MOFE:     MVI     L,80
        JMP     ERR1
 DZER:  MVI     L,85
        JMP     ERR1
-ENDM:  MVI     L,90
+ENDMX: MVI     L,90
        JMP     ERR1
 SUFE:  MVI     L,95
        JMP     ERR1
(END)

アセンブルは問題なく終了
>ASM DENVBAS
CP/M ASSEMBLER - VER 2.0
V0619 0EFF       MVI C,-1
0CE2
009H USE FACTOR
END OF ASSEMBLY

RunCPM Version 5.3 (CP/M 2.2 60K)

>LOAD DENVBAS

FIRST ADDRESS 0100
LAST  ADDRESS 0AFD
BYTES READ    09FE
RECORDS WRITTEN 14


予約語は以下のとおり。BASICの一方言と言える。FORはない。
LET、GOTO、GOSUB、PR、IF、IN、RET、END
LIST、RUN、CLEAR、TAPE、LOAD
DIM、SIZE、REM、CLRS
RND

なおTAPEやLOADは、テープからの入力でIN命令を使っている。ハードが対応してないので使えない。

>DENVBAS
>10 A=0
>20 PR A
>30 A=A+1
>40 IF A<10 GOTO 20
>50 END
>LIST
 10 A=0
 20 PR A
 30 A=A+1
 40 IF A<10 GOTO 20
 50 END
>RUN
0
1
2
3
4
5
6
7
8
9

90 AT 50
>

注意点は行番号が1バイトであること。実用的とは言えない。(いまさらTinyBasicで実用って何とも言えるが)。
ただしEOFが01Hであるので、行番号1は使えない。


Z80-MBC2のIOについて

2021-08-09 16:54:33 | 日記

ブートについて調べたのに引き続き、入出力部分を簡単に整理してみた。(S220618_IOS-LITE-Z80-MBC2.ino)

Z80のINあるいはOUT命令では、メモリ空間とは別のIO空間に対してアクセスする。この時MREQではなくIOREQが有効になる。
そこでIOREQでRSラッチを起動し、WAITをかける。CPUは出力あるいは入力状態で停止するので、その状態のデータバスを読み取る。解除はWAIT_RESでRSラッチを解除する。
(RSラッチは初期状態が不明なため、AVRは初期処理でWAIT_RESをLに、PG LOAD前にはHにしている)

 

気をつけるところは、WAIT解除の部分。以下のようにBUSREQを有効にしてからWAITを解除しその後BUSREQを解除している。Twステートを抜けた後T4ステートになる関係か?
INTERRUPTは現状では使っていないようだ。


WRITE
      // Control bus sequence to exit from a wait state (M I/O write cycle)
      digitalWrite(BUSREQ_, LOW);                 // Request for a DMA
      digitalWrite(WAIT_RES_, LOW);               // Reset WAIT FF exiting from WAIT state
      digitalWrite(WAIT_RES_, HIGH);              // Now Z80 is in DMA, so it's safe set WAIT_RES_ HIGH again
      digitalWrite(BUSREQ_, HIGH);                // Resume Z80 from DMA

READ
        // Control bus sequence to exit from a wait state (M I/O read cycle)
        digitalWrite(BUSREQ_, LOW);               // Request for a DMA
        digitalWrite(WAIT_RES_, LOW);             // Now is safe reset WAIT FF (exiting from WAIT state)
        delayMicroseconds(2);                     // Wait 2us just to be sure that Z80 read the data and go HiZ
        DDRA = 0x00;                              // Configure Z80 data bus D0-D7 (PA0-PA7) as input with pull-up
        PORTA = 0xFF;
        digitalWrite(WAIT_RES_, HIGH);            // Now Z80 is in DMA (HiZ), so it's safe set WAIT_RES_ HIGH again
        digitalWrite(BUSREQ_, HIGH);              // Resume Z80 from DMA

INTERRUPT
        // Control bus sequence to exit from a wait state (M interrupt cycle)
        digitalWrite(BUSREQ_, LOW);               // Request for a DMA
        digitalWrite(WAIT_RES_, LOW);             // Reset WAIT FF exiting from WAIT state
        digitalWrite(WAIT_RES_, HIGH);            // Now Z80 is in DMA, so it's safe set WAIT_RES_ HIGH again
        digitalWrite(BUSREQ_, HIGH);              // Resume Z80 from DMA


void serialEvent()
// Set INT_ to ACTIVE if there are received chars from serial to read and if the interrupt generation is enabled
{
  if ((Serial.available()) && Z80IntEnFlag) digitalWrite(INT_, LOW);
}

void printBinaryByte(byte value)
{
  for (byte mask = 0x80; mask; mask >>= 1)
  {
    Serial.print((mask & value) ? '1' : '0');
  }
}


Z80-MBC2のブートについて

2021-08-05 17:49:27 | 日記

昔からZ80をAVRなどでコントロールすることは考えたことが何度かあった。しかしデータバスが8bitで、アドレスバスが16bitで MREQ, RD, WR, RESET, BUSREQ, BUSACKとコントロールするバスが多い。ラッチしてとか、アドレスバスはバイナリカウンタでとか、3ステートのコントロールがとか考えて複雑になりすぎて諦めていた。

ところがZ80-MBC2では思いもよらない方法でこれを回避している(アドレスバスで使っているのは1ビットだけ)
そこでブート部分を簡単に整理してみた。(S220618_IOS-LITE-Z80-MBC2.ino)

メモリは128KB
BANK0, BANK1で32KB毎のバンク
BANK0=Hの場合は通常の64KB

 

[0]初期状態は
  CPU    RESET=L、WAIT=L、INT=H、BUSREQ=H という完全停止
  メモリ  CE2=Hとしてアクセス可能状態

  MREQ、RD、WR、AD0はavrから読み込める状態になっている

[1]メモリマップセット
 BANK0=H, BANK1=L

[2]リセット(singlePulsesResetZ80())
 RESET=Lとしていはいるが、CLKが停止しているのでCPUは実際にRESETされているわけではない。
 この状態でクロックを入れる。(RESET=Lで6クロック、RESET=Hで2クロック)
  この段階でRESETはHになっている

[3]メモリへ書き込み
 スタートアドレスが0なら、そのままスタートアドレスから書き込むが、そうでなければ0〜3バイトにスタートアドレスへのJMP命令を書き込む。

[3-1]メモリへの書き込み詳細(loadHL(BootStrAddr))
 ・クロックを1回入れる->M1サイクルのT1ステート
 ・CE2=LとしてメモリをHiZ状態として、データバスに LD HL, のオペコードを書き込む
 ・クロックを2回入れる->M1サイクルのT2, T3ステート
 ・AVRのデータバスをinput状態に戻し、クロックを2回->M1サイクルのT4ステート、次のM2サイクルのT5ステート
 ・AVRのデータバスをoutput状態にし、BootStrAddrの下位8ビットを出力し、クロックを3回->M2サイクルT6,T7,M3サイクルT8ステート
 ・AVRのデータバスにBootStrAddrの上位8ビットを出力し、クロックを2回->M3サイクルT9, T10ステート
 ・AVRのデータバスをinput状態に戻しCE2=Hとしてメモリを有効状態とする
 
[3-2]メモリへの書き込み詳細(loadByteToRAM())
 ・クロックを1回入れる->M1サイクルのT1ステート
 ・CE2=LとしてメモリをHiZ状態として、データバスに LD (HL), のオペコードを書き込む
 ・クロックを2回入れる->M1サイクルのT2, T3ステート
 ・AVRのデータバスをinput状態に戻し、クロックを2回->M1サイクルのT4ステート、次のM2サイクルのT5ステート
 ・AVRのデータバスをoutput状態にし、メモリへ書き込むべき8ビットを出力し、クロックを2回->M2サイクルT6,T7ステート
 ・AVRのデータバスをinput状態に戻し、メモリを有効状態としてクロックを3回->M3サイクルのT8, T9, T10ステート

 ・クロックを1回入れる->M1サイクルのT1ステート
 ・CE2=LとしてメモリをHiZ状態として、データバスに INC HL のオペコードを書き込む
 ・クロックを2回入れる->M1サイクルのT2, T3ステート
 ・AVRのデータバスをinput状態に戻し、メモリを有効状態としてクロックを3回->M1サイクルT4, T5, T6

[4]クロックの供給
 リセットして、クロックを供給して、リセット解除(Hにする)
 クロックはAVRのTimer2を使用 (8MHz or 4MHz)

要するに、z80に1バイト書き込みプログラムを強制的に読み込ませ、z80自身にメモリへ書き込みをさせている。このためアドレスの管理やMREQ, RD, WR などのタイミングを含めて制御はすべてz80自身が行ってくれることになる。

その他
 メモリのバンクについては、ダイオードを使っている。ちょっとわかりにくかったので調べた。
 32KB単位のバンクで128KBメモリなので4つのブロックがある。00000-07FFFが1つ目のブロック、08000-0FFFFが2つ目のブロック...。CP/M3.0は、OSのある上位32KBは変化せず下位32KBだけ切り替えているので、以下のような割当になっている。