2021-03-16 00:06 — asano
カテゴリー:
Z8S180の前にやってみたいことがあります。
Z180には未定義な命令を実行しようとした時に発生するTRAP割り込みというものがあります。これを捕まえてRST 38H
によるブレークのようにアドレスやレジスタが表示できれば便利でしょう。
ということで調べ始めたのですが、思った以上にハードルが高く簡単ではありませんでした。
まず未定義な命令を実行しようとした時に何が起こるかです。
- ITC(Interrupt TRAP Control)レジスタのTRAPビットが1になる
- PCの値がスタックに積まれる
- 論理アドレス0000Hにジャンプする
えっ、0000Hってリセットと同じではないですか。MMUで論理アドレス0000Hに異なる物理アドレスを設定していれば問題ありませんが、ROM, RAMともに16kBしかないSBCZ80でこのためだけにMMUを有効にするのはかなり面倒です。
救済策も用意されていて、1.のTRAPビットはリセットで0になるので0000HからのルーチンでこれをチェックすればリセットなのかTRAPなのかは判別できるようになっています。
次に問題となったのはレジスタの保存です。TRAP時にレジスタを表示するためにはすべてのレジスタ内容を保存しておかなくてはなりません。リセットかTRAPかの判別の過程でFレジスタは壊れてしまうのでその前に保存することが必要ですが、そのためにはスタックを経由しなくてはなりません。しかし判別前ではリセットの可能性もありその場合はSPがまだ設定されていません。SPを再設定するならSP自体も保存しておかないとTRAPだった場合に元のスタックに積まれていたはずのPCが取り出せなくなってしまいます。
さらにUniversal Monitor固有の難しさもあります。Z80, HD64180, Z280を同一バイナリで対応しようとしているため、HD64180(Z180)系以外の場合はTRAPビットのチェックをスキップしなくてはなりません。通常CPU判別ルーチンの結果がメモリに書かれていてそれを参照すればよいのですが、リセットだった場合はこれが利用できません。Z180系かどうかだけでも独自に判別の必要があります。
ここまでなんとか解決のめどがたったので実際にコードを書いて試してみたのですが思ったように動作しません。実はもう一つ落とし穴がありました。
前回書いたように内蔵I/O空間のアドレスを初期値の00H~3FHから0C0H~0FFHに移動しています。リセットされていればこのアドレスは00H~3FHに戻っていますが、TRAPの場合はそのままなので0C0H~0FFHのままです。TRAPビットのあるITCレジスタもこの中なので、判別しないとアドレスがわからず、アドレスがわからないと判別できないという鶏と卵のような状態なのでした。
両方の空間で読んでみれば何とかならないか、さらにアドレス設定のICRレジスタも両方の空間で読んでみて矛盾のない方を採用しようかとか考えてみましたが、どうしても確実性に難があります。
ここでかなり悩んだのですが、意外に単純な形で解決しました。
核の部分のコードを見ていきます。0000H番地から割り込みを禁止しただけでここに飛んできます。
78/ 100 : CSTART:
79/ 100 : ;; Identify HD64180/Z180 TRAP
80/ 100 : =>TRUE IF USE_180TRAP
81/ 100 : SAVE
82/ 100 : CPU Z180
83/ 100 : ED 73 00 BF LD (INBUF),SP ; Save SP
84/ 104 : 31 10 BF LD SP,INBUF+BUFLEN ; Temporary stack on INBUF area
85/ 107 : F5 PUSH AF
86/ 108 : ED 5F LD A,R
87/ 10A : 32 02 BF LD (INBUF+2),A ; Save R
88/ 10D : C5 PUSH BC
89/ 10E : 01 FF 00 LD BC,00FFH
90/ 111 : ED 4C MLT BC
91/ 113 : 78 LD A,B
92/ 114 : B1 OR C
93/ 115 : 20 1A JR NZ,CS0 ; Not HD64180/Z180
94/ 117 : ;; HD64180/Z180
95/ 117 : 3E C0 LD A,IOBASE
96/ 119 : ED 39 3F OUT0 (3FH),A
97/ 11C : ED 38 F4 IN0 A,(IOBASE+34H) ; ITC register
98/ 11F : 32 03 BF LD (INBUF+3),A
99/ 122 : E6 80 AND 80H
100/ 124 : 28 0B JR Z,CS0 ; Not TRAP
101/ 126 : 3A 03 BF LD A,(INBUF+3)
102/ 129 : E6 7F AND 7FH
103/ 12B : ED 39 F4 OUT0 (IOBASE+34H),A ; Reset TRAP bit
104/ 12E : C3 65 09 JP TRAPH
105/ 131 : CS0:
106/ 131 : ALL RESTORE
107/ 131 : [80] ENDIF
83行は現在のSPをモニタの行入力バッファ(最低16バイトはある)の先頭に保存します。この命令はFレジスタを変化させません。
84行で仮のスタック領域を行入力バッファの後半に設定します。この命令もFレジスタを変化させません。
85行でAFレジスタペアを仮スタックに退避します。これでA,Fレジスタを壊せるようになったのでいろいろなことができるようになります。
86,87行でRレジスタを保存します。Rレジスタは命令実行のたびにインクリメントされるので早めに保存しておかないと後で補正する時に面倒なことになってしまいます。
88~93行ではHD64180/Z180系かどうかの判別を行ない、違っていれば通常のリセット処理に戻します。
95,96行で内蔵I/O空間を0C0H~0FFHに移動させます。どっちだったか判別するのは諦めて強制的に0C0H~0FFHにしてしまえば良かったのです。
97,98行でITCレジスタを読み保存しておきます。
99,100行ではビット7のTRAPビットをチェックし、0だった場合は通常のリセット処理に戻します。SPはこの後で再初期化されるので仮スタックに積んだAF,BCはそのままでOKです。
101~103行ではITCレジスタのTRAPビットをクリアしておきます。リセットでもTRAPでもなく0000H番地に飛んでしまった時にTRAPルーチンに入らないためです。
あとは仮保存したレジスタ・残りのレジスタをRST 38H
の場合と同じように保存するだけです。
1561/ 966 : ;;; HD64180/Z180 TRAP Handler
1562/ 966 :
1563/ 966 : =>TRUE IF USE_180TRAP
1564/ 966 : TRAPH:
1565/ 966 : =>TRUE IF USE_REGCMD
1566/ 966 :
1567/ 966 : 3A 02 BF LD A,(INBUF+2)
1568/ 969 : 32 37 BF LD (REGR),A
1569/ 96C : ED 57 LD A,I
1570/ 96E : 32 36 BF LD (REGI),A
1571/ 971 : 22 24 BF LD (REGHL),HL
1572/ 974 : ED 53 22 BF LD (REGDE),DE
1573/ 978 : E1 POP HL
1574/ 979 : 22 20 BF LD (REGBC),HL
1575/ 97C : E1 POP HL
1576/ 97D : 22 1E BF LD (REGAF),HL
1577/ 980 : 08 EX AF,AF'
1578/ 981 : F5 PUSH AF
1579/ 982 : D9 EXX
1580/ 983 : 22 2C BF LD (REGHLX),HL
1581/ 986 : ED 53 2A BF LD (REGDEX),DE
1582/ 98A : ED 43 28 BF LD (REGBCX),BC
1583/ 98E : E1 POP HL
1584/ 98F : 22 26 BF LD (REGAFX),HL
1585/ 992 : DD 22 2E BF LD (REGIX),IX
1586/ 996 : FD 22 30 BF LD (REGIY),IY
1587/ 99A :
1588/ 99A : ED 7B 00 BF LD SP,(INBUF) ; Recover SP
1589/ 99E : E1 POP HL
1590/ 99F : 06 0A LD B,10 ; for R register adjustment
1591/ 9A1 : 3A 03 BF LD A,(INBUF+3) ; ITC
1592/ 9A4 : E6 40 AND 40H
1593/ 9A6 : 28 02 JR Z,TH0
1594/ 9A8 : 2B DEC HL
1595/ 9A9 : 04 INC B
1596/ 9AA : TH0:
1597/ 9AA : 2B DEC HL
1598/ 9AB : 22 34 BF LD (REGPC),HL
1599/ 9AE : ED 73 32 BF LD (REGSP),SP
1600/ 9B2 :
1601/ 9B2 : ;; R register adjustment
1602/ 9B2 : 3A 37 BF LD A,(REGR)
1603/ 9B5 : 90 SUB B
1604/ 9B6 : E6 7F AND 7FH
1605/ 9B8 : 47 LD B,A
1606/ 9B9 : 3A 37 BF LD A,(REGR)
1607/ 9BC : E6 80 AND 80H
1608/ 9BE : B0 OR B
1609/ 9BF : 32 37 BF LD (REGR),A
1610/ 9C2 :
1611/ 9C2 : 21 65 0A LD HL,TRAPMSG
1612/ 9C5 : CD EE 07 CALL STROUT
1613/ 9C8 : CD BE 07 CALL RDUMP
1614/ 9CB : C3 09 02 JP WSTART
1615/ 9CE :
1616/ 9CE : =>FALSE ELSE ; USE_REGCMD
1617/ 9CE :
1618/ 9CE : LD SP,(INBUF) ; Recover SP
1619/ 9CE : POP HL ; Drop return address
1620/ 9CE :
1621/ 9CE : LD HL,TRAPMSG
1622/ 9CE : CALL STROUT
1623/ 9CE : JP WSTART
1624/ 9CE :
1625/ 9CE : [1565] ENDIF ; USE_REGCMD
1626/ 9CE :
1627/ 9CE : [1563] ENDIF ; USE_180TRAP
大半はレジスタの値をメモリにコピーしているだけなので説明は省略します。RST 38H
のハンドラとよく似ているのでいずれ共通化できないか考えていきたいですね。
1588,1589でSPを復帰させ、TRAPによって保存されたPCを取り出します。このPCは不正命令を検出した時のPCで、命令の先頭アドレスから1あるいは2バイト進んでしまっているのでその補正を行ないます。
その補正が1590~1597行です。ITCレジスタのビット6を見れば進み量がわかるのでその分PCを戻します。同時にRレジスタの補正量も求めています。
Rレジスタも1あるいは2進んでしまっているので補正が必要です。こちらはさらにTRAP発生からRレジスタを退避するまでの間にも進んでしまっているので、その分も合わせて10~11という補正量を求めておきます。
1602~1609行で実際の補正を行ないます。Rレジスタはビット6からビット7への繰上りがないのでちょっと複雑になっていますね。
このRレジスタ、Z80ではリフレッシュしているアドレスですが、HD64180/Z180では互換性のための単なる命令カウンタで実際のリフレッシュアドレスは他にあります(ソフトウェアからアクセスできません)。
コメントを追加