2018.7.4
前へ
次へ
ホームページトップへ戻る


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

前へ
次へ
ホームページトップへ戻る