16ビットマイコンボードの製作
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
いつか使ってみるつもりで入手してそのまま置いてあった16ビットCPUのことを思い出しました。
AMD社のAM188です。
その名の通り、CPUコアは80188互換の16ビットCPUです。
そのAM188を使った16ビットマイコンボードの製作記事です。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[第48回]
●8086の最高に悩ましい問題(CSとDS)
このテーマについては[第32回]、[第33回]、[第40回]に書いてきました。
それぞれに悩ましい問題なのですが、私にとって最高に悩ましい問題は、今回のテーマ「CSとDS」です。
8086のアセンブラプログラムで避けて通れないのがセグメントの概念です。
あ、いや、ユーザーレベルではセグメントについては全く考えなくても通ることが可能です。
しかし開発者レベルでは、セグメントの概念は時として悩ましい問題となります。
CSとDSの問題については、8086BASICシステムのプログラム作業を開始した時点ではそれほど悩ましい問題とは思っていませんでした。
それが悩ましい問題であることに気が付いたのは浮動小数点演算プログラムの作業にかかってからでした。
これは当8086BASICシステムにとってはなかなかに困った問題です。
8086版BASICは現在開発を進めておりますND80KL/86ボードにAM188CPUボードを搭載したときの基本OSです。
BASICなのにOSか?と疑問に思われるかもしれませんが、昔のPC9801のようなイメージです。
ND80KL/86の名前にありますように、CPUとしてはZ80互換のKL5C80A12を搭載することもできます。
この場合にはND80Z3.5の高速かつ高機能版となります。
メインOSとしてはND80Z3.5のZB3BASICを考えていますが、当社のKL5C80A12ボード(ZBKシリーズ)に搭載しているZBKV3BASICも搭載するつもりで、現在その作業の途中でもあります。
ND80KL/86はプラットフォームともいうべきベースボードの上に8ビットのKL5C80A12を搭載すれば高速高機能のZ80マイコンボードとなり、AM188を搭載すれば16ビットの8086マイコンボードとなります。
ハードウェアのプラットフォームが共通ですので、両者はハードウェア的には同じ設計思想の上にあります。
そのベースにあるのは、アドレスは異なりますが、システムはROMで、データとユーザプログラムはRAMで、というところです。
最近のパソコンは大容量のDRAMを利用して、システムもデータもアプリケーションプログラムもRAM上に置くのが当然のことになっています。
そのメリットは考えるまでもありません。
システムプログラムをRAMにロードすることによって、自由にシステムを構築、改変することができます。
デメリットはシステムが大きくなるに比例してロード時間が増大し、またシステムを保存するためのハード装置の容量も増大することです。
Windowsのように巨大なOSではメリットのほうが圧倒的でしょう。
その一方でND80KL/86のように小さなシステムでは、RAM方式はシステムプログラムをどこに格納するのかというのが問題になります。
ND80KL/86はND80Z3.5と同様にWindowsパソコンとUSB接続する機能も当然搭載していますから、Windowsパソコンのハードディスクを利用する、という考え方も成り立ちます。
しかしND80Z3.5以来、というよりTK−80互換ということでスタートした初代ND80からずっとシステムプログラムはROMでやってきましたから、やはりその線でいきたいと思います。
今回の問題は、つまるところシステムプログラムをROMに置くのか、それともRAMに置くのか、という点に集約できます。
ソフトウェア的に見れば、普通はシステムがROMにあってもRAMにあってもそれほど問題にはなりません。
ただハードウェアから見ればKL5C80A12の場合には0000はROMでなければならないという制約があります。
CP/M互換DOSを走らせるためにはハードウェアでの工夫が必要です。
なおシステムソフトを開発する場合にRAM上にプログラムがあれば、RST7(FF)を利用したブレークポイントを設定できるという大きなメリットがあります。
またシステムプログラムをRAMにロードできれば、デバッグ中のプログラムを毎回ROMに書いてテストするという面倒な手間も省けます。
ということでZ80用のシステムプログラムをデバッグする場合には、システムをRAMにロードする、といういわば裏のシステムテクニック(?)を使っています。
ともあれそのような8ビットZ80システムの流れをうけてAM188の場合にもシステムプログラムはROMで、そしてデータとユーザープログラムはRAMに置くというのがND80KL/86システムの基本コンセプトとなります。
KL5C80A12の場合にはROMは始まりが00000にあるような位置に置き、RAMは終わりがFFFFFにあるような位置に置きます。
AM188(8086も同じ)の場合にはROMはFFFF0を含むアドレスに置かなければなりません。
RAMはどこでもよいのですが、結局のところ割込みテーブルアドレス00000を含むアドレスに置くことになります。
AM188の場合には、ここでセグメント問題が出てきます。
AM188(8086も同じ)には4つのセグメントレジスタがあって、そこに設定する値によって1MBのメモリ空間の中の任意の64KB(ただし開始アドレスの下位4ビットは必ず0000)を4つまでアクセスすることができます。
しかし完全に自由ではなくて、そこには制約があります。
CS(Code Segment)レジスタで指定する64KBはその名前の通りプログラム領域で、PC(Program Counter)の値はこのセグメント内のアドレスを示します。
DS(Data Segment)レジスタで指定する64KBはその名の通りデータ領域で、データとしてメモリアドレスを指定する命令は一部の例外を除いてDS内のアドレスを示します。
SS(Stack Segment)レジスタで指定する64KBはスタック領域で、PUSH、POP命令はSSのメモリアドレスに対して働きます。
またSPはSS内のアドレスを示します。
このほかにES(Extra Segment)がありますが、これはちょっと特殊なので普段は意識しなくてもよいかと思います。
問題はCSとDSです。
たとえば
MOV AL,[89AB]
という命令を考えた場合、この命令のコード
A0AB89
は当然CSのメモリアドレスに置かなければなりません。
89ABという値もCSに置くことになるわけですが、その89ABが示すメモリアドレスはDS内のアドレスになります。
いちいちこんなことを考えていたのではプログラムは書けません。
そこでユーザーがプログラムやデータを書いたり読んだりするメモリアドレス8000〜FFFF(これはユーザーの便を考えてND80Z3.5と同じ領域を割り当てています)はCSとDSで同じメモリ(RAM)領域を割り当てています。
そしてSSにも同じRAM領域を割り当てています。
こうすることでユーザーはセグメントの呪縛から開放されて、8080やZ80の知識だけでプログラミングをすることができます。
しかし8086BASICシステムプログラムを書いている私にとっては、呪縛のままです。
先に書きました通り、FFFF0を含むアドレスにはROMを置きます。
そしてシステムプログラムはそのROMに置きます。
CSにはF000を指定します(この場合プログラムが置かれるメモリアドレスはF0000〜FFFFFになります)。
一方でDSには0000を指定します(この場合データが置かれるメモリアドレスは00000〜0FFFFになります)。
プログラムとデータとで全く重なりがありませんが、ま、そこは開発者である私がしっかり意識してシステムプログラムを書けばよいだけの話です。
8086BASICシステムが起動して、ユーザーが自由にプログラムやデータを書いたり読んだりできるモードにある間だけは、一時的にCS=DS=0000にします(SSとESはつねに0000にしてあります)。
実は。
今までの8086BASICシステムプログラムの作成、デバッグは、CS=DS=0000にして、システムプログラムをその0100にロードして作業してきました。
RAM上でデバッグするほうがはるかに楽だからです。
Z80や8080ではRAM上のプログラムのデバッグとして、ブレークポイントを設定するのに命令コードをFFに書き換えることで行ないます。
8086プログラムでは同じことを命令コードをCCに置き換えることで行なえます。
また上にも書きましたように、CS=DS=0000にすることで、デバッグ中のシステムプログラムをいちいちROMに書かなくてもRAMにロードしてすぐにデバッグできますから、これはとても楽です。
過去のログからそこのところをお見せしますとこのようになります。
logfile nd80klog\06240934.txt open ND80KL/86に接続しました 0001 001D - z 0003 0339 - *** nd80kl/86(am188) basic **** >/ld 86rom4v.bin,0100 loading 86ROM4V.BIN ...7ef5(32501)bytes loaded,from 0100 to 7FF4 >cm 1878 1878 F0-00 1879 BF- >jp 1000 > |
システムを起動した時点ではシステムプログラムはCS=F000のROM上で実行されています。
/LD 86ROM4V.BIN,0100でデバッグ中のシステムプログラムをDS=0000(RAM領域)にロードしています。
0000〜00FFの割込みテーブルを壊さないように0100にロードしています(割込みテーブルは0000〜03FFの1KBですが、今のところ前の方しか使っていません)。
次にRAMにロードしたシステムプログラムの一部を書き換えます。
1878をF0から00に書き換えています。
ブレークしたときにCS=F000の割込みプログラムではなくてCS=DS=0000の割込みプログラムにジャンプするためです。
当然デバッグに必要なシステムプログラム部分はROMとRAMとで一致していることが前提です。
最後にJP 1000を実行します。
マシン語デバッグツールのJPコマンドは「RAM」の指定アドレス(CS=DS=0000に設定したうえで、指定するメモリアドレス)にジャンプします。
このあたりのことについては[第26回]に書いています。
これでやっと本題に落ち着きました。
今までのデバッグ作業はCS=DSの状態で行なってきましたので特に問題はありませんでした。
ところが浮動小数点計算プログラムのデバッグに入ったところで気が付いてしまいました。
これってまずいんじゃないの?
下はSIN()関数のプログラムです。
[02042] ;;;SIN[RAD] [02043] 4E0E F606A1F101 FSIN:TEST [*DFLSW]B,01 [02044] 4E13 7403 JZ FSIN1 <4E18> [02045] 4E15 E95A1F JMP DSIN <6D72> [02046] 4E18 E892FF FSIN1:CALL KAKKOD <4DAD> [02047] 4E1B A0FFF1 FSIN2:MOV AL,[*FA8] [02048] 4E1E 0AC0 OR AL,AL [02049] 4E20 50 PUSH AX [02050] 4E21 7905 JNS FSI2 <4E28> [02051] 4E23 32C0 XOR AL,AL [02052] 4E25 A2FFF1 MOV [*FA8],AL [02053] 4E28 A0FEF1 FSI2:MOV AL,[*FA7] [02054] 4E2B 3CF6 CMP AL,F6 [02055] 4E2D 7879 JS FSIE <4EA8> [02056] 4E2F BE044F FS22:MOV SI,*F2PI [02057] 4E32 E8B5FD CALL FCMPCS <4BEA> [02058] 4E35 7809 JS FSI3 <4E40> [02059] 4E37 BE044F MOV SI,*F2PI [02060] 4E3A E887FA CALL FSUBCS <48C4> [02061] 4E3D EBF090 JMP FS22 <4E2F> [02062] 4E40 BEF04E FSI3:MOV SI,*FPAI [02063] 4E43 E8A4FD CALL FCMPCS <4BEA> [02064] 4E46 780A JS FSI4 <4E52> [02065] 4E48 58 POP AX [02066] 4E49 3480 XOR AL,80 [02067] 4E4B 50 PUSH AX;PUSH AF [02068] 4E4C BEF04E MOV SI,*FPAI [02069] 4E4F E872FA CALL FSUBCS <48C4> [02070] 4E52 BEFF4E FSI4:MOV SI,*FPI2 [02071] 4E55 E892FD CALL FCMPCS <4BEA> [02072] 4E58 780C JS FSI5 <4E66> [02073] 4E5A BEF04E MOV SI,*FPAI [02074] 4E5D E869F9 CALL HLRBCS <47C9> [02075] 4E60 E832F9 CALL XAB <4795> [02076] 4E63 E867FA CALL FSUB2 <48CD> [02077] 4E66 BEFF4E FSI5:MOV SI,*FPI2 [02078] 4E69 E8E5FA CALL FDIVCS <4951> [02079] 4E6C E82EFE CALL APSH <4C9D> [02080] 4E6F BEFBF1 MOV SI,*FA4 [02081] 4E72 E844F9 CALL HLRB <47B9> [02082] 4E75 E865FA CALL FMUL2 <48DD> [02083] 4E78 BED0F0 MOV SI,*FB0 [02084] 4E7B E8EEFD CALL APUT <4C6C> [02085] 4E7E BE094F MOV SI,*SIT1 [02086] 4E81 E850FA CALL FMULCS <48D4> [02087] 4E84 BE0E4F MOV SI,*SIT2 [02088] 4E87 E84200 CALL ADMLCS <4ECC> [02089] 4E8A BE134F MOV SI,*SIT3 [02090] 4E8D E83C00 CALL ADMLCS <4ECC> [02091] 4E90 BE184F MOV SI,*SIT4 [02092] 4E93 E83600 CALL ADMLCS <4ECC> [02093] 4E96 BEFF4E MOV SI,*FPI2 [02094] 4E99 E840F9 CALL FADDCS <47DC> [02095] 4E9C BEFBF1 MOV SI,*FA4 [02096] 4E9F E817F9 CALL HLRB <47B9> [02097] 4EA2 E812FE CALL APOP <4CB7> [02098] 4EA5 E835FA CALL FMUL2 <48DD> [02099] 4EA8 BEEB4E FSIE:MOV SI,*FLMT [02100] 4EAB E83CFD CALL FCMPCS <4BEA> [02101] 4EAE 7909 JNS FSIE2 <4EB9> [02102] 4EB0 E824F8 CALL ACLR <46D7> [02103] 4EB3 58 POP AX [02104] 4EB4 8B3EC8F0 MOV DI,[*DCNT] [02105] 4EB8 C3 RET [02106] 4EB9 58 FSIE2:POP AX [02107] 4EBA 8B3EC8F0 MOV DI,[*DCNT] [02108] 4EBE 0AC0 OR AL,AL [02109] 4EC0 7801 JS FSIE22 <4EC3> [02110] 4EC2 C3 RET [02111] 4EC3 A0FFF1 FSIE22:MOV AL,[*FA8] [02112] 4EC6 3480 XOR AL,80 [02113] 4EC8 A2FFF1 MOV [*FA8],AL [02114] 4ECB C3 RET |
SIレジスタにFPAI、FPAI2、SIT1、SIT2、SIT3…のアドレスを入れて、サブルーチンをコールしています。
これらは定数データのアドレスです。
[02133] ;;;FPAI [02134] 4EF0 ED FPAI:DB ED [02135] 4EF1 87 DB 87 [02136] 4EF2 64 DB 64 [02137] 4EF3 02 DB 02 [02138] 4EF4 00 DB 00 [02139] ;;;F90D [02140] 4EF5 00 F90D:DB 00 [02141] 4EF6 00 DB 00 [02142] 4EF7 5A DB 5A [02143] 4EF8 07 DB 07 [02144] 4EF9 00 DB 00 [02145] ;;;FP180 [02146] 4EFA 1A FP180:DB 1A [02147] 4EFB 7D DB 7D [02148] 4EFC 47 DB 47 [02149] 4EFD FB DB FB [02150] 4EFE 00 DB 00 [02151] ;;;FPI2 [02152] 4EFF ED FPI2:DB ED [02153] 4F00 87 DB 87 [02154] 4F01 64 DB 64 [02155] 4F02 01 DB 01 [02156] 4F03 00 DB 00 [02157] ;;;F2PI [02158] 4F04 ED F2PI:DB ED [02159] 4F05 87 DB 87 [02160] 4F06 64 DB 64 [02161] 4F07 03 DB 03 [02162] 4F08 00 DB 00 [02163] ;;;SIT1 [02164] 4F09 DD SIT1:DB DD [02165] 4F0A 6B DB 6B [02166] 4F0B 4F DB 4F [02167] 4F0C F4 DB F4 [02168] 4F0D 00 DB 00 [02169] ;;;SIT2 [02170] 4F0E 32 SIT2:DB 32 [02171] 4F0F 93 DB 93 [02172] 4F10 4C DB 4C [02173] 4F11 F9 DB F9 [02174] 4F12 80 DB 80 [02175] ;;;SIT3 [02176] 4F13 2C SIT3:DB 2C [02177] 4F14 9A DB 9A [02178] 4F15 51 DB 51 [02179] 4F16 FD DB FD [02180] 4F17 00 DB 00 [02181] ;;;SIT4 [02182] 4F18 F0 SIT4:DB F0 [02183] 4F19 AE DB AE [02184] 4F1A 52 DB 52 [02185] 4F1B 00 DB 00 [02186] 4F1C 80 DB 80 |
これらの定数はROMに書いていますから、CSのエリアに置いてあることになります。
しかしSIで参照する値はデータですからDSになければなりません。
幸い今はRAMでデバッグしていますから、CS=DSなのでノープロブレムです。
しかししかし。
このままではデバッグが済んでこのままROMにプログラムを焼いたら、正しく実行できません。
ではどうするか?
取るべき対策は2つです。
その1つはシステム起動時に定数データをROMからRAMアドレスにコピーします。
しかしそれは全く面白くないですねえ。
ROMに書いてあるデータをそのままRAMにコピーする?
そんなものはクソ食らえ、であります。
すると残る対策、プログラムの必要部分を全部書き換える、ということになります。
これも相当に「クソ」でありますけれど、私の場合、こういう場合は悪態をつきながら、プログラムを書き換えていってしまいます。
ええ。
こんなクソシステムを開発したインテルの8086開発チームを思い切り呪いながら、です。
実は上でお見せしたSIN()のリストはそのように対策をしたあとのプログラムです。
定数をSIレジスタにセットしたあとコールしているサブルーチンはFCMPCSとかFSUBCSのように名前の後ろにCSがついています。
たとえばFCMPの例です。
もとはFCMPだけだったのですが、今回の対策のためにFCMPCSを追加しました。
下のリストはその一部です。
[01744] ;;;FCOMP [01745] 4BEA E8DCFB FCMPCS:CALL HLRBCS <47C9> [01746] 4BED EB0490 JMP FCMP2 <4BF3> [01747] 4BF0 E8C6FB FCMP:CALL HLRB <47B9> [01748] 4BF3 E8F1FA FCMP2:CALL TAZR <46E7> [01749] 4BF6 750D JNZ FCM22 <4C05> [01750] 4BF8 8BC3 MOV AX,BX [01751] 4BFA 0BC7 OR AX,DI [01752] 4BFC 7501 JNZ FCMP21 <4BFF> [01753] 4BFE C3 RET |
上のリストでは両者の違いは見えません。
先頭でコールしている、HLRBCS、HLRBがカギです。
もとはHLRBだけだったところ、今回の対策のためにHLRBCSを追加しました。
なんだかいやーな雰囲気です。
インテルのクソ野郎であります。
[01241] 47B6 BEFBF1 FARB:MOV SI,*FA4 [01242] ;;;[HL] TO REG B [01243] 47B9 B200 HLRB:MOV DL,00 [01244] 47BB 8A34 MOV DH,[SI] [01245] 47BD 8BFA MOV DI,DX [01246] 47BF 46 INC SI [01247] 47C0 8B1C MOV BX,[SI] [01248] 47C2 46 INC SI [01249] 47C3 46 INC SI [01250] 47C4 8B04 MOV AX,[SI] [01251] 47C6 8BF0 MOV SI,AX;? LD L,A [01252] 47C8 C3 RET [01253] ; [01254] 47C9 B200 HLRBCS:MOV DL,00 [01255] 47CB 2E CS: [01256] 47CC 8A34 MOV DH,[SI] [01257] 47CE 8BFA MOV DI,DX [01258] 47D0 46 INC SI [01259] 47D1 2E CS: [01260] 47D2 8B1C MOV BX,[SI] [01261] 47D4 46 INC SI [01262] 47D5 46 INC SI [01263] 47D6 2E CS: [01264] 47D7 8B04 MOV AX,[SI] [01265] 47D9 8BF0 MOV SI,AX;? LD L,A [01266] 47DB C3 RET |
HLRBCSは[SI]を参照している命令の前にCS:がついています。
セグメント変更プリフィクスです。
[SI]はデフォルトではDSのメモリを参照します。
そのままではCSに置いたデータを参照することはできません。
メモリを参照する命令の前にセグメント変更プリフィクスを置くと、その命令に限ってセグメントを変更することができます。
MOV DH,[SI]
MOV BX,[SI]
MOV AX,[SI]
はいずれもDSのメモリを参照しますが、
CS:
MOV DH,[SI]
CS:
MOV BX,[SI]
CS:
MOV AX,[SI]
はいずれもCSのメモリを参照します。
そういうことなら、全部XXXCSプログラムに変更してしまえばよいではないか?
ごもっともであります。
そうしたいのは山々なのですけれど、そうもいかない事情があるのです。
下のリストは上でお見せしたSIN()の一部です。
[02077] 4E66 BEFF4E FSI5:MOV SI,*FPI2 [02078] 4E69 E8E5FA CALL FDIVCS <4951> [02079] 4E6C E82EFE CALL APSH <4C9D> [02080] 4E6F BEFBF1 MOV SI,*FA4 [02081] 4E72 E844F9 CALL HLRB <47B9> [02082] 4E75 E865FA CALL FMUL2 <48DD> [02083] 4E78 BED0F0 MOV SI,*FB0 [02084] 4E7B E8EEFD CALL APUT <4C6C> [02085] 4E7E BE094F MOV SI,*SIT1 [02086] 4E81 E850FA CALL FMULCS <48D4> |
FDIVCS、FMULCSはCS付きですが、HLRB、APUTにはCSが付いていません。
FA4、FB0はデータ領域に置いたワークエリアなので、CSとDSを分離したときには、ここをCS付きのサブルーチンにすることはできません。
つまりFSUBもFADDもFMULTもFDIVも、そしてそのほかのサブルーチンも、ある場合にはCS領域の定数を参照しますが、ある場合にはDS内のワークエリアを参照します。
必然的に必要なサブルーチンはDS用とCS用の2本立てにならざるをえません。
なかなかに厄介なことです。
もう一度、インテルのクソヤロー、であります。
いえ。
これだけのことでしたら、まだかわいげのあるクソ野郎であります。
次回に続きます。
16ビットマイコンボードの製作[第48回]
2018.7.4upload
前へ
次へ
ホームページトップへ戻る