SSブログ
English Version

レトロマイコン86ボードの構想(その4)V20とPICのI/F確認 [8086]

 前回の記事でPICからメモリに書込んだプログラムがV20で実行できることを確認できたので今回はPICとV20のインタフェース部の動作について確認しました。

 前回書いたようにアセンブラはNASMを使っていますがマニュアルにはできるように書いてある

  SEGMENT DATA
  MOV AX,DATA

でのセグメント値の参照やセグメントの絶対アドレス指定の

  SEGMENT CODE ABSOLUTE=0xffc0

でエラーやワーニングが発生したり、ORGを2カ所に書くとエラーになったりで悩みましたが、8086のメモリモデルとしてコードセグメントとデータセグメントを同じ値にするtinyモデルにし、データは EQUステートメントでアドレス指定することでPICとV20のインタフェースを確認しました。

 PICとのインタフェースの仕掛けとして、回路は初回の記事を参照して頂くとして処理概要としては
  1. V20からの処理要求
    I/F用のワークエリアにパラメータを設定し、A15=1のアドレスにI/O命令を発行する。
  2. READY信号がinactiveに遷移
    PICのコンパレータの出力(オープンドレイン)を使ってハード的にREADY信号がinactiveとなりV20はwait状態になる。
  3. PIC側での処理
    PIC側ではREADY信号を監視していてREADY信号がinactiveになったらコンパレータ出力を無効にし(プルアップしているのでREADY信号はactiveになる)HLDRQをactiveにしてバスの使用権を獲得し、ワークエリアに設定された要求を処理後、結果をワークエリアに保存する。
  4. PIC側のバスの開放
    バスを開放し、コンパレータの出力を有効にする。

となります。

 V20側のアセンブルリストは下記のとおりです。処理内容としてはこの手の確認では定番の"Hello!"の表示です。

インタフェース試験でのV20側アセンブラソース
1 ;***************************** 2 ; Pic24V20 Pic interface test 3 ; 2019/07/28 by skyriver 4 ;***************************** 5 6 7 CPU 8086 8 9 SEC_SZ EQU 512 ; sector size 10 WRK_START EQU 01e0H ; work start adr 11 12 FC_EXIT equ 0 ; EXIT CP/M 13 FC_CONST equ 1 ; CONST 14 FC_CONIN equ 2 ; CONIN 15 FC_CONOUT equ 3 ; CONOUT 16 FC_READ equ 4 ; READ SD Card 17 FC_WRITE equ 5 ; WRITE SD Card 18 FC_PUNCH equ 6 ; PUNCH 19 FC_READER equ 7 ; READER 20 21 SdBuf EQU WRK_START 22 PicWrk EQU SdBuf + SEC_SZ 23 24 STRUC tPic ; Pic I/F work area 25 00000000 .FuncNo RESB 1 ; function No 26 00000001 .RetVal RESB 1 ; return value 27 00000002 .CoData RESB 1 ; conout data 28 00000003 .SelDk RESB 1 ; selected disk 29 00000004 .Track RESB 2 ; track No 30 00000006 .DmaAdr RESB 2 ; DMA adr 31 00000008 .Sector RESB 1 ; sector No. 32 ENDSTRUC 33 34 ; *** memory assign *** 35 36 ; ffc00 ffc0:0000 code area 37 ; 38 ; ffde0 ffc0:01e0 SD buf 512byte 39 ; fffe0 ffc0:03e0 work 16byte 40 ; ffff0 ffff:0000 reset jmp 41 42 43 ; SEGMENT CODE ABSOLUTE=0xffc0 44 SEGMENT CODE 45 46 ORG 0 47 48 00000000 8CC8 START: MOV AX,CS 49 00000002 8ED8 MOV DS,AX 50 00000004 8EC0 MOV ES,AX 51 00000006 8ED0 MOV SS,AX 52 00000008 BCE001 MOV SP,WRK_START 53 0000000B BB[3500] MOV BX,MESG 54 0000000E BA0080 MOV DX,8000H 55 00000011 E80D00 CALL PUTS 56 00000014 E80300 CALL EXIT 57 00000017 E9FDFF JMP $ 58 59 0000001A C606E00300 EXIT: MOV BYTE [PicWrk + tPic.FuncNo],FC_EXIT 60 0000001F EE OUT DX,AL 61 00000020 C3 RET 62 63 64 ; print string 65 ; BX <- message adr 66 ; DX <- I/O Adr ( > 0x7fff ) 67 ; 68 00000021 C606E00303 PUTS: MOV BYTE [PicWrk + tPic.FuncNo],FC_CONOUT 69 00000026 8A07 CONO_L: MOV AL,[BX] 70 00000028 43 INC BX 71 00000029 08C0 OR AL,AL 72 0000002B 7407 JZ CONO_E 73 0000002D A2E203 MOV [PicWrk + tPic.CoData],AL 74 00000030 EE OUT DX,AL 75 00000031 E9F2FF JMP CONO_L 76 00000034 C3 CONO_E: RET 77 78 79 00000035 48656C6C6F210D0A00 MESG: DB 'Hello!',13,10,0 80


 PIC側でのリスト表示後実行した際のログを以下に示します。ログの末尾にあるように想定通り"Hello!"が表示されました(^^)/

V20とPICのI/F確認時のpicleソース
:l 1:# Pic24V20 Pic interface test 2019/07/28 2:# by skyriver 3: 4:use LibCpm; 5:use LibSpi; 6: 7:var ProgWrk,_WrkVal,_PicDma,WrkTrk; 8: 9:proc MemDsp( adr ) { 10: var i; 11: PmpSetAdr( adr ); 12: i = MemRd(); 13: PrnStr_("\n"); 14: PrnHex_(adr); 15: PrnStr_(" :"); 16: for ( i=0; i<8; i=i+1 ) { 17: PrnStr_(" "); 18: PrnHexB_(MemRd()); 19: } 20:} 21: 22: 23:proc m( data ) { 24: MemWr( data ); 25:} 26: 27: 28:proc PicSrv() { 29: var i,fno,rval; 30: 31: while (1) { 32: while (LATC[-1]&$40) {} # wait ready off 33: CMCON[0] = CMCON[0]&$feff; # CMP1 off 34: PmpOn(); 35: PmpSetAdr(ProgWrk); 36: fno = MemRd(); # dummy 37: for (i=0;i<3;i=i+1) { 38: _WrkVal[i] = MemRd(); 39: } 40: fno = _WrkVal[0]; # function No 41: 42: if (fno=1) { # CONST 43: if (InpChk_()) { 44: rval = $ff; 45: } else { 46: rval = 0; 47: } 48: } 49: else if (fno=2) { # CONIN 50: rval = InpChar_(); 51: } 52: else if (fno=3) { # CONOUT 53: PrnChar_( _WrkVal[2] ); 54: } 55: else if (fno=0) { 56: break; 57: } else { 58: PrnStr_("\nFunc err"); 59: } 60: PmpSetAdr(ProgWrk+1); 61: MemWr(rval); 62: 63: PmpOff(); 64: BusRelease(); 65: CMCON[0] = CMCON[0]|$0100; # CPM1 on 66: } 67:} 68: 69:proc main() { 70: var adr; 71: init(); 72: initPmp(); 73:# initSpi(); 74:# initSd(); 75: 76: ProgWrk = $ffe0; # work addr(fffe0) 77: _PicDma = $fde0; 78: _WrkVal = Alloc( 5 ); 79: WrkTrk = Alloc( 2 ); 80: 81: LATA[0]=LATA[0]|1; # on reset:a0 82: BusReq(); 83: LATA[0]=LATA[0]&$fffe; # off reset:a0 84: 85: PmpOn(); 86: 87: PmpSetAdr($07f0); 88: m($ea);m($00);m($00);m($c0);m($ff); 89: 90:# MemDsp($7f0); 91: 92: adr = $400; # adr:ffc00 93: PmpSetAdr(adr); 94: 95: m($8c);m($c8);m($8e);m($d8);m($8e);m($c0);m($8e);m($d0); 96: m($bc);m($e0);m($01);m($bb);m($35);m($00);m($ba);m($00); 97: m($80);m($e8);m($0d);m($00);m($e8);m($03);m($00);m($e9); 98: m($fd);m($ff);m($c6);m($06);m($e0);m($03);m($00);m($ee); 99: m($c3);m($c6);m($06);m($e0);m($03);m($03);m($8a);m($07); 100: m($43);m($08);m($c0);m($74);m($07);m($a2);m($e2);m($03); 101: m($ee);m($e9);m($f2);m($ff);m($c3);m($48);m($65);m($6c); 102: m($6c);m($6f);m($21);m($0d);m($0a);m($00); 103: 104: PmpOff(); 105: 106: LATA[0]=LATA[0]|1; # on reset:a0 107: BusRelease(); 108: Timer_ = 1; 109: while ( Timer_ ); 110: LATA[0]=LATA[0]&$fffe; # off reset:a0 111: 112: while ((LATC[-1]&$40)=0); # wait ready on 113: 114: PicSrv(); 115: 116: BusReq(); 117:} :run Hello! :


 この時のロジアナ波形を貼っておきます。左端でRESETがオフになることでV20が動作を開始し、中央部のHLDACがhighになっている部分がV20からの'H'(Hello!の最初の文字)出力要求に対してPICが処理している部分となります。

PICとV20のインタフェース確認時の波形サンプル



[TOP] [ 前へ ] 連載記事 [ 次へ ]

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

レトロマイコン86ボードの構想(その3)V20の動作確認 [8086]

 今回は8088互換チップであるV20(uPD70108H)の簡単な動作確認を行ってみたので記録しておきます。
 前回のPIC24FJのPMP(パラレルマスタポート)機能の確認ではPICとメモリを実装した状態でしたが、ブレッドボードにV20の配線を追加したところ、不安定な状態でなかなか改善しなかったので古いブレッドボードをあまり使っていないものに交換し再度配線し直したところなんとか安定動作するようになりました。

 しかし後述するようにV20のクロックが8MHzの場合は安定に動作していますが、16MHzでは動作しません。

 V20の動作確認をするにあたり、ハンドアセンブルでは辛いのでアセンブラ環境が必要になりますが、free(BSDライセンス)でWindows環境でも使えるNASMを使うことにしました。
 NASMはダウンロードしたアーカイブファイルを解凍してpathを通せばいいだけなのでインストールも楽でWindowsのレジストリが変更されることもありません。

 リンカはVisualStudioのものが使えるようですが、単一のソースファイルであれば(複数ソースをリンクしないのであれば)、-o オプションでバイナリファイルを作成するとアセンブルされたコードの入ったファイルが生成されます。
 このバイナリファイルをPic24CPMの開発時に作成したpicleのステートメントに変換するツールを使いpicleのステートメントに変換し、picleソースに入れ込んでいます。

 ニーモニックの表記でメモリアクセス時は[]で囲むように統一されているのでワークエリアのアドレスなのか、中身なのか混乱することが無い等、インテルのニーモニックより改善されていて使い易くなっています。しかし英語のマニュアルが217ページあり、斜め読みするだけでもある程度の時間が必要です(プアなマニュアルよりは細かく記載されている方が絶対いいけどね)

 V20の動作確認のために下のリストのような簡単なものを作りました。

V20動作確認ソース
1 ;***************************** 2 ; Pic24V20 test 3 ; 2019/07/26 by skyriver 4 ;***************************** 5 6 7 CPU 8086 8 9 segment code 10 11 org 0200h 12 13 00000000 8CC8 start: mov ax, cs 14 00000002 8ED8 mov ds, ax 15 00000004 B83412 mov ax,1234h 16 00000007 A3[0D00] mov [data],ax 17 0000000A E9FDFF jmp $ 18 0000000D 90 data: nop 19 0000000E 90 nop 20 0000000F 90 nop 21 00000010 90 nop 22


 処理内容としてはプログラムの末尾に0x1234を書込むという単純なものです。
 メモリ(K6T4008C1B-DB70)は512KBのものを1個使用していて、V20のメモリ空間(1MB)の半分のサイズですが、V20のリセット直後の実行アドレスがメモリのエンド領域(0xffff0)なのでメモリ空間の後半に前半と同じイメージが見えてアクセスできるようにしています。

 アセンブルとpicleステートメントへの変換操作は

アセンブルとpicleステートメントへの変換操作
C:\src\picle\V20\asm\nasm>type asm.bat nasm -l %1.lst -o %1.bin %1.asm C:\src\picle\V20\asm\nasm>asm v20test C:\src\picle\V20\asm\nasm>nasm -l v20test.lst -o v20test.bin v20test.asm C:\src\picle\V20\asm\nasm>bin2state <v20test.bin >v20test.txt


こんな感じです。

 下記はV20動作テスト用のpicleソースを表示後、実行した際のログです。
 V20のプログラム実行により0x020dからの2バイトが想定通り0x1234に書き換えられていることが判ります。
 41行目でV20がリセット直後に実行する0xffff0アドレスに 0000:0200 へのファージャンプ命令を書込んでいます。

PMP機能確認用picleソース
:l 1:# V20 run test 2019/07/26 2:# by skyriver 3: 4:use LibCpm; 5:use LibSpi; 6: 7: 8:proc MemDsp( adr ) { 9: var i; 10: PmpSetAdr( adr ); 11: i = MemRd(); 12: PrnStr_("\n"); 13: PrnHex_(adr); 14: PrnStr_(" :"); 15: for ( i=0; i<8; i=i+1 ) { 16: PrnStr_(" "); 17: PrnHexB_(MemRd()); 18: } 19:} 20: 21: 22:proc m( data ) { 23: MemWr( data ); 24:} 25: 26: 27:proc main() { 28: var adr; 29: init(); 30: initPmp(); 31:# initSpi(); 32:# initSd(); 33: 34: LATA[0]=LATA[0]|1; # on reset:a0 35: BusReq(); 36: LATA[0]=LATA[0]&$fffe; # off reset:a0 37: 38: PmpOn(); 39: 40: PmpSetAdr($07f0); 41: m($ea);m($00);m($02);m($00);m($00); 42: 43: MemDsp($7f0); 44: 45: adr = $0200; 46: PmpSetAdr(adr); 47: 48: m($8c);m($c8);m($8e);m($d8);m($b8);m($34);m($12);m($a3); 49: m($0d);m($02);m($e9);m($fd);m($ff);m($90);m($90);m($90); 50: m($90); 51: 52: dump(adr); 53: PmpOff(); 54: 55: LATA[0]=LATA[0]|1; # on reset:a0 56: BusRelease(); 57: Timer_ = 1; 58: while ( Timer_ ); 59: LATA[0]=LATA[0]&$fffe; # off reset:a0 60: 61: Timer_ = 10; 62: while ( Timer_ ); 63: 64: BusReq(); 65: PmpOn(); 66: PrnStr_( "\n------------" ); 67: MemDsp($7f0); 68: dump(adr); 69: PmpOff(); 70:} :run 07F0 : EA 00 02 00 00 DD 61 FF 0200 : 8C C8 8E D8 B8 34 12 A3 0D 02 E9 FD FF 90 90 90 0210 : 90 90 22 CC 22 AD 61 FA B1 BB 1A E5 02 A7 08 FB 0220 : 35 AC 09 7D 9C 16 80 DD 42 BB 96 BB 04 3D D0 7B 0230 : 51 FB 5E D7 AA 23 C0 2F 51 34 1C F6 01 FD 16 FB 0240 : 70 7F 27 7B B0 3E 0A 56 42 F3 74 DB 40 5B 01 B6 0250 : 0A BE 6E BB 84 5E 48 D5 D3 DF 04 56 20 75 03 1A 0260 : 7C AA 0D 7C 8A BA C9 8F CE DD 3E E5 B8 FB 01 F7 0270 : 91 F8 E9 3A 03 EF 02 F6 CA F9 00 F7 45 EF 51 7E ------------ 07F0 : EA 00 02 00 00 DD 61 FF 0200 : 8C C8 8E D8 B8 34 12 A3 0D 02 E9 FD FF 34 12 90 0210 : 90 90 22 CC 22 AD 61 FA B1 BB 1A E5 02 A7 08 FB 0220 : 35 AC 09 7D 9C 16 80 DD 42 BB 96 BB 04 3D D0 7B 0230 : 51 FB 5E D7 AA 23 C0 2F 51 34 1C F6 01 FD 16 FB 0240 : 70 7F 27 7B B0 3E 0A 56 42 F3 74 DB 40 5B 01 B6 0250 : 0A BE 6E BB 84 5E 48 D5 D3 DF 04 56 20 75 03 1A 0260 : 7C AA 0D 7C 8A BA C9 8F CE DD 3E E5 B8 FB 01 F7 0270 : 91 F8 E9 3A 03 EF 02 F6 CA F9 00 F7 45 EF 51 7E :


 冒頭でも書いたように V20(16MHz対応版)のクロックを16MHにすると動作しなかったので8Mhzにして動作確認しています。
 それぞれのクロックでのメモリリード時の波形サンプルを下図に示します。この時のロジアナのサンプリング周波数は50MHzなので分解能は20nsです。

 16MHzの波形の方はアドレスラッチストローブ(LATCH)の立下りととアドレス線(AD0)の変化までの時間が20ns(50MHz相当)なので結線の接触抵抗が生じるブレッドボードでは厳しいかもしれません。回路的にはV20のクロックをジャンパピンにより8MHz/16MHzの切り換え可能にし、プリント基板化した際に16Mzで動作するか確認する予定です。

クロック8MHz時のメモリリード波形サンプル(結果:OK)


クロック16MHz時のメモリリード波形サンプル(結果:NG)


 今回使用しているV20は uPD70108HCZ-16 なのですがreset中のWR/、RD/信号に関して、マニュアルにはハイレベルに保たれると書かれていますが、今回使用しているものでは下図のロジアナ波形のOE/とWR/に示すようにRESETがハイレベルの時でもローレベル(プルアップするとハイレベルになったのでハイインピーダンス状態と思われる)でした。
 reset解除後、6バイト読込んでからジャンプ(A15:low)していますが、ジャンプ命令は5バイトなので最後の1バイトはプリフェッチによるアクセスですね。

 また、hold 時にASTBがハイインピーダンスにならないことも、今回のようにアドレスとデータを分離前のバスをPICで制御する場合は使いづらいです。ロジックICを使えば簡単に対応できますが、今回は抵抗を使って対処しています。
★2019/11/04 追記 {
 抵抗での対処ではV20のドライブ能力が低くなるため、不安定だったのでASTBをプルダウンし、ショットキーを介してHighレベルに引っ張れるようにしました(Hold中のLowレベル時はPIC側に影響を与えない)
}

マニュアル記載内容


reset解除直後の波形サンプル



[TOP] [ 前へ ] 連載記事 [ 次へ ]

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

レトロマイコン86ボードの構想(その2) [8086]

 86ボード制作ですが、PIC24FJのPMP(パラレルマスタポート)機能について実験してみました。

 今回はシリアル通信をアサインするピンを変更するのでブートローダー側のシリアルも変更が必要でPic24FJ用ブートローダーのOneBitLoaderを入れるところからです。
 久々にOneBitLoaderの最初の立ち上げ画面を見ました。
 次にローダーを使ってpicleコンパイラを書込み、その後はpicleのセルフコンパイル環境でソースを変更し、コンパイルの繰り返し作業なのでローダーすらほとんど使いません。

OneBirLoaderの最初の立ち上げ画面


 V20(8088相当品)を使用することを想定しているので8bitのデータバスに下位アドレスがマルチプレクスする半多重化アドレッシングモードを使用します。
 データ線とアドレス線がマルチプレクスされることでPIC24側のI/Oに少し余裕ができます。
 PMP機能では下図のようにタイミングをパラメータにより色々設定可能なので数通りのパラメータパターンについてロジアナで確認してみました。

PMP半多重化アドレッシングタイミング


 タイミングの調整要素としては上図に書かれているように WAITB,WAITM,WAITE(それぞれBegin、Middle、Endの略だと思う)があり、全てゼロの場合の最速タイミングが下図です。PIC24のクロックは内蔵クロックを使用して32MHzにしています。
 モニタした信号はメモリアクセスのための下記の信号です。
  • MCS/ チップセレクト
  • OE/ リードストローブ
  • WR/ ライトストローブ
  • LATCH 下位アドレスラッチ用のHC573へのG信号
  • AD0 データ線のLSBビットの信号

PMPライトタイミング(wait無し)


 今回もアクセスタイムが70nsのメモリを使用する予定なので WR/ はもっと広げないと駄目ですね。LATCHもマージンを大きくし、ノイズの発生も少なくするためにもっと広げたいと思います。
 下の図が3通りのパラメータで設定した場合のロジアナ波形です。WR/幅が140nsでlatch幅が80nsでlatch後20ns程度アドレスを保持しているWAITB:1,WAITM:2,WAITE:1 を採用することにします。
 その時、PIC の PMCON と PMMODE のレジスタの設定値はそれぞれ 0x0ba0、0x0a49 になります。
★2019/07/15 修正
 採用パターンのWAITEを0から1に修正(latch後のアドレス保持時間の確保のため))

PMPライトタイミング(WAITB:1, WAITM:1, WAITE:1)


PMPライトタイミング(WAITB:1, WAITM:2, WAITE:1)


PMPライトタイミング(WAITB:1, WAITM:2, WAITE:0)


 参考として WAITB:1,WAITM:2,WAITE:1 時のメモリリードタイミングが下図です。

PMPリードタイミング(WAITB:1, WAITM:2, WAITE:1)


 今回の確認に使用したpicleソースと実行結果を貼っておきます。

PMP機能確認用picleソース
:l 1:# V20 memory test 2019/07/13 2:# by skyriver 3: 4:use LibCpm; 5:use LibSpi; 6: 7:var Pcon; 8: 9:proc initP() { 10: PMCON=$0600; #PMMODE[1],PMADDR[2] 11: PMMODE=$0602; 12: PMADDR=$0604; 13: PMDAT=$0608; 14: PMAEN=$060c; 15: PMSTAT=$060e; 16: 17: Pcon=$0ba0; # 0000 1011 1010 0000 18: PMCON[0]=Pcon; 19: PMMODE[0]=$0a49; #0000 1010 0100 1001 20: PMAEN[0]=$4701; # PMAEN 0100 0111 0000 0001 21:} 22: 23: 24:proc PmOn() { 25: BusReq(); 26: while ((LATC[-1]&$100)=0) {} # wait HOLDAC 27: PMCON[0] = Pcon|$8000; 28:} 29: 30: 31:proc PmOff() { 32: PMCON[0] = Pcon; # PMP off 33:} 34: 35: 36:proc MemDsp( adr ) { 37: var i,j; 38: PmpSetAdr( adr ); 39: i = MemRd(); 40:# for ( i= 0; i < 16; i=i+1 ) { 41: for ( i= 0; i < 1; i=i+1 ) { 42: PrnStr_( "\n" ); 43: PrnHex_( adr ); 44: PrnStr_( " :" ); 45: for ( j=0; j< 8; j=j+1 ) { 46: PrnStr_( " " ); 47: PrnHexB_( MemRd() ); 48: } 49: adr = adr + 16; 50: } 51:} 52: 53:proc main() { 54: var adr; 55: init(); 56:# initPmp(); 57: initP(); 58:# initSpi(); 59:# initSd(); 60: 61: LATA[0]=LATA[0]|1; # on reset:a0 62: 63:# PmpOn(); 64: PmOn(); 65: PmpSetAdr(0); 66: MemWr($01); 67: MemWr($02); 68: MemWr($04); 69: MemWr($08); 70: MemWr($10); 71: MemWr($20); 72: MemWr($40); 73: MemWr($80); 74: 75: MemDsp(0); 76: 77:# BusRelease(); 78: LATA[0]=LATA[0]&$fffe; # off reset:a0 79:} :run 0000 : 01 02 04 08 10 20 40 80 :



[TOP] [ 前へ ] 連載記事 [ 次へ ]

nice!(1)  コメント(0) 
共通テーマ:趣味・カルチャー

Z80での10の高速除算方法 [Z80]

 今回はtwitterで見かけたZ80で10で割る処理に関するお題について書いてみます。

 お題としてはZ80で100未満の数値を10で除算処理する場合、如何に高速にできるか?というもので

    Breg ÷ 10 = Hreg ... Areg

という処理です。

 具体的な処理例も挙げられていて次の処理を高速化できるか? という問題です。
 100要素分の回答の配列から引っ張るのが一番速いですがそれは無しという条件付きです。
 尚、以下に示す各処理は0..99の入力に対するリターン値(AregとHreg)が全て同じであることを確認済みのものです。

処理例
; +++ asking method +++ 78 chk10: ld a,b ; input value 21 00A0 ld hl, 10 SHL 4 BD cp l 38 03 jr c,chk0 95 sub l 26 10 ld h,16 CB 0D chk0: rrc l BD cp l 38 03 jr c,chk1 95 sub l CB DC set 3,h CB 2D chk1: sra l BD cp l 38 03 jr c,chk2 95 sub l CB D4 set 2,h CB 2D chk2: sra l BD cp l 38 03 jr c,chk3 95 sub l CB CC set 1,h CB 2D chk3: sra l BD cp l 38 03 jr c,put2 95 sub l CB C4 set 0,h C9 put2: ret


 上記の処理内容を見ると通常の割算処理のループを展開したような形になっています。
 ループが展開されているので被除数が小さい場合には上位桁の演算をスキップするショートカット方式を最初に思いつきました。
 ショートカットすることで

    0~39なら -54clk
   40~99なら+18clk

となり平均すれば-10.8clk分速くなりそうです。

最初に思いついたショートカット方式
; +++ my first method +++ ; when 40..99 0..39 78 my1_10: ld a,b ; input value 4 4 21 0028 ld hl, 10 SHL 2 ; 10 10 BD cp l ; add 4 4 38 18 jr c,my1_2 ; add 7 12 2E A0 ld l,10 SHL 4 ; add 7 BD cp l ; 0a0h ; 4 38 03 jr c,my1_0 ; 7 12 ave 15 95 sub l ; 4 26 10 ld h,16 ; 7 CB 0D my1_0: rrc l ; 4 BD cp l ; 050h ; 4 38 03 jr c,my1_1 ; 7 12 ave 15.5 95 sub l ; 4 CB DC set 3,h ; 8 CB 2D my1_1: sra l ; 4 BD cp l ; 028h ; 4 38 03 jr c,my1_2 ; 7 12 ave 15.5 95 sub l ; 4 CB D4 set 2,h ; 8 CB 2D my1_2: sra l ; 4 4 BD cp l ; 014h ; 4 4 38 03 jr c,my1_3 ; 7 12 ave 15.5 15.5 95 sub l ; 4 4 CB CC set 1,h ; 8 8 CB 2D my1_3: sra l ; 4 4 BD cp l ; 00ah ; 4 4 38 03 jr c,my1_4 ; 7 12 ave 15.5 15.5 95 sub l ; 4 CB C4 set 0,h ; 8 C9 my1_4: ret


 しかし、既に下記のバイナリサーチのような処理案(「引き放し法」と言うそうです)が出ていて処理内容が複雑そうですが確かに速そうです。

引き放し法
; +++ twitter's solution +++ DIV10: 78 LD A,B ; 4 D6 50 SUB 80 ; 7 38 15 JR C,ADD40 ; 7,12 26 08 LD H,8 ; 7 D6 28 SUB 40 ; 7 38 15 JR C,ADD20 ; 7,12 SUB20: CB D4 SET 2,H ; 8 D6 14 SUB 20 ; 7 38 13 JR C,ADD10 ; 7,12 SUB10: CB CC SET 1,H ; 8 D6 0A SUB 10 ; 7 38 11 JR C,NEG_EX ; 7,12 POS_EX: CB C4 SET 0,H ; 8 C9 RET ; 10 ADD40: 26 00 LD H,0 ; 7 C6 28 ADD A,40 ; 7 38 EB JR C,SUB20 ; 7,12 ADD20: C6 14 ADD A,20 ; 7 38 ED JR C,SUB10 ; 7,12 ADD10: C6 0A ADD A,10 ; 7 38 EF JR C,POS_EX ; 7,12 NEG_EX: C6 0A ADD A,10 ; 7 C9 RET ; 10


 ぱっと見は処理内容が解り辛いですが、10 x (2のべき乗) の大きい方(80)から引いていき(キャリが出たら次は足す)、10未満になったら終了するような処理が見えてきます。
 最初に書いた処理例では比較(CP)で確認後減算(SUB)していますが、比較による確認無しに減算し、マイナスになっても元に戻さず(だから「引き放し法」)次に半分の値を足すことで結果として半分の値を引いたのと等価になるというものです。

 しかし、入力値の条件が100未満なので80を引けた場合、直後に40を引いているのは無駄なように見えます。
 80を引いた後は0..19の範囲内なので10を引けるかのチェックをした方が早く収束するはずなので改善してみたものが下の処理です。

引き放し法の改善案
; +++ my improve method +++ MDIV10: 78 LD A,B ; 4 D6 50 SUB 80 ; 7 38 14 JR C,MADD40 ; 7,12 26 08 LD H,8 ; 7 ; SUB 40 ; 7 del ; JR C,ADD20 ; 7,12 del C3 0512 JP MS10 ; 10 add MSUB20: CB D4 SET 2,H ; 8 D6 14 SUB 20 ; 7 38 13 JR C,MADD10 ; 7,12 MSUB10: CB CC SET 1,H ; 8 D6 0A MS10: SUB 10 ; 7 38 11 JR C,MNEG_EX ; 7,12 MPOS_EX: CB C4 SET 0,H ; 8 C9 RET ; 10 MADD40: 26 00 LD H,0 ; 7 C6 28 ADD A,40 ; 7 38 EB JR C,MSUB20 ; 7,12 MADD20: C6 14 ADD A,20 ; 7 38 ED JR C,MSUB10 ; 7,12 MADD10: C6 0A ADD A,10 ; 7 38 EF JR C,MPOS_EX ; 7,12 MNEG_EX: C6 0A ADD A,10 ; 7 C9 RET ; 10


 発想を変えて、お題がZ80なのでBCD関連の命令をうまく利用できないか考えてみた結果が次の処理です。
★追記 2019/07/13 {
 思考の順番としては100要素の回答テーブルの各要素の規則性は単純なのでもっと短いテーブルにできないものか?という発想から考えた結果として上位ニブルに対するBCDテーブルに行き着きました。
}
 RRD命令のクロック数が多いのが気になりますがこれも結構速そうですね^^

 気合を入れれば平均クロック数を算出して上の処理と比較できますが夜の作業としては他の作業をしたい(上の処理は分岐確率が50%でないところがあるので更にややこしい)

 今時のマイコンであれば統合開発環境内で実行時のクロック数はシミュレータですぐに確認できるのですが、Z80のシミュレータで実行クロック数を確認できるものを少し探しましたが見当たりませんでした。
 0..99をループで回し開始/終了時にI/O命令を実行してロジアナで確認することはある程度容易にできますが、それもやぼったいので、クロック数をモニタできるシミュレータが見つかったら比較してみたいと思います。

現時点で最新の高速処理案
; +++ my method +++ mydiv: 21 0707 ld hl,wrk ; 10 70 ld (hl),b ; 7 set input value AF xor a ; 4 ED 67 rrd ; 18 6E ld l,(hl) ; 7 set high nible 86 add a,(hl) ; 7 add high nible's BCD to low nible 27 daa ; 4 BCD B7 or a ; 4 clear flag 27 daa ; 4 BCD(for high nible) 2E 07 ld l,wrk and 00ffh ; 7 77 ld (hl),a ; 7 AF xor a ; 4 ED 67 rrd ; 18 66 ld h,(hl) ; 7 C9 ret ; 10 org ($ + 255) and 0ff00h ; BCD 00 tbl: db 000h ; 00h 16 db 016h ; 10h 32 db 032h ; 20h 48 db 048h ; 30h 64 db 064h ; 40h 80 db 080h ; 50h 96 db 096h ; 60h wrk: ds 1


★追記 2019/08/01
 このブログの読者にはあまり人気が無かったこのページのアクセスが最近多くなりました。

yasuokaの日記: Z80における定数10の除算は、商と余りのどちらを先に求めるべきか

関連の書込み記事からのアクセスのおかげのようです^^

 51/512倍することで商を出す手法で末尾に ret を加えると108clk(ステート)で上記のもの(118clk)より高速です(上記のものもRRD命令部をシフト化して数clkの短縮は可能ですが)。

 今回の問題では入力範囲が0..99なのでこのような分数演算の手法では誤差を1%未満にする必要があります。私も13/128でやってみたところ誤差が1%以上なので回答が途中からズレた結果になりました。
 また、整数演算なので誤差が1%未満であっても誤差の範囲内の変動で回答が影響を受けないように inc a で微調整されています。

 普通に考えると16bitでシフトしたいところですが、なんと8bitのシフトや演算で処理を完結しています・・すばらしい!

 私も新たなアイディアを思いついたら追記したいと思います。


★追記 2019/08/02
 アイディアが浮かんだので試してみた結果をメモしておきます。
 今回、複数のアイディアを盛り込み、結果として上記の分数方式(108clk)より4clk短縮(104clk)できました。(^^)/
★変更 2019/08/03 {
yasuokaの日記: Re:Z80における定数10の除算は、商と余りのどちらを先に求めるべきかの記事がアップされ剰余を先に求める方法で103clk(最後にretを追加した場合)達成とのこと。
 今回追記した処理に関してもまだ少しのりしろがあったので一部見直し103clkになりました^^
}

 今回適用した主要なアイディアとしては、少数演算の使用と上位/下位のnibleを逆転することによる4bitシフト処理の省略です。
 後者についてはピンと来ないかも知れませんが以下に概要を説明します。

  1. 小数点で処理
     分数方式だと前回記載したように誤差が1%未満の条件で分子と分母の組合せを色々考えることになりますが、0.1を2進数で表現し、その乗算値を求めればいいのでは?と考えました。
     小数点であれば分数のように組合せを考える必要が無いし、誤差は桁数に依存し一意に決まります。
     また、シフトが一方向なので往復するようなシフト処理が発生しません。

     下表に示すように 0.1を2進数で表すと2進数の循環小数になります。
     下表の1~9(橙色部分)の合計は 0.099609 なのでここまで算出すれば1%未満という条件を満たす誤差範囲になるはずです。


  2. 小数点の桁のアサイン
     速度優先で計算は8bitで行うので8bit分の有効桁数を維持するために表中のNo.1~3のゼロの部分は省略してNo4をMSBに割り振ります。
     この場合、16倍したことになるので最終的に上位nibleに結果が求まることになり、結果を取り出すために4回のシフト処理が必要になりますが、この問題は後述の対策で解決します。

  3. 循環小数の求め方
     表中のNo4と5の合計を求め、1/16倍(4bitシフト)すればNo8と9の合計になります。

  4. 上位/下位ニブルの逆転処理
     上記の循環小数のための4bitシフトでRRCAを使用することで、上位のnibleと下位のnibleが入代ります。
     ここでLSB側のnibleの方を上位、MSB側のnibleを下位と考えるとシフト前の値がシフト後の値の1/16と考えることができます。
     また、0.1倍した結果がLSB側のnibleに入るようになるので、前述した結果を取り出す際の4回のシフト処理が不要になります。

  5. 加算部の処理
     表中のNo.4,5にNo.8,9を加算する際は、MSB側の下位nibleから上位nibleへ桁上り(キャリー)が発生するか否かが必要な情報であり、キャリーが発生した場合、LSB側の上位nibleに反映するために、No.8,9を加算後にキャリ付き加算(ADC)を実行しています。

  6. 剰余の計算
     商が求まったら、商の10倍を入力値から減算して余りを算出しています。


小数点で0.1倍した改善処理
      ; +++ my method2 +++ 78 MYDEV2: LD A,B ; 4 F6 01 OR 1 ; 7 1F RRA ; 4 88 ADC A,B ; 4 4F LD C,A ; 4 0F RRCA ; 4 0F RRCA ; 4 0F RRCA ; 4 0F RRCA ; 4 5F LD E,A ; 4 81 ADD A,C ; 4 3E 00 LD A,0 ; 7 8B ADC A,E ; 4 E6 0F AND 0FH ; 7 67 LD H,A ; 4 87 ADD A,A ; 4 87 ADD A,A ; 4 84 ADD A,H ; 4 2F CPL ; 4 07 RLCA ; 4 88 ADC A,B ; 4 C9 RET ; 10


★追記 2019/12/27
 下記の変更で3ステート速くなり、サイズも1バイト小さくなります。

改善部
 5F                LD      E,A     ;  4
 81                ADD     A,C     ;  4
 3E 00             LD      A,0     ;  7
 8B                ADC     A,E     ;  4

         ↓

 81                ADD     A,C     ;  4
 3F                CCF             ;  4
 99                SBC     A,C     ;  4
 3C                INC     A       ;  4


★追記 2021/12/03
 上記の変更でEregが非破壊になりました。更にCregを使用している3箇所をHreg使用に変更することでCregも非破壊にできます。


★追記 2019/08/07
 分数方式についての考察を少し書いてみます。

 初めに前回の小数点方式について動作を判り易く図解してみます。
 下図で一番上に書いてある「x 1.5」の行は被除数を1.5倍したものです。
 0.1を2進数で表すと上記のように「0001100110011・・・」の循環小数になるので「1.5倍」の数を4ビットずつシフトしながら無限回並べたものの合計になります。

 上の小数点方式の説明で「上位/下位ニブルの逆転処理」と書いた部分でキャリーを足している対象は正確に言うと下図の「整数部分」になります。

小数点方式の処理イメージ図


 因みに4bit周期の循環小数なのでうまくやれば誤差無しに計算可能で(循環部で桁上りした場合、下の桁からも必ず桁上りしてくるので)、小数点部分を正確に求められれば、小数点の上位3bitの2倍と8倍を足すことで余りの値も計算結果から求められるはずなのですが、
  • 8bitでは処理が難しい
    シフトする塊が被除数の3倍なので桁落ちせずに計算するには 99 x 3 = 297 まで扱う必要があり、1バイト処理では難しい
  • 小数部からの余りの取り出し処理
    現状の商から余りを求める部分と比較し大きな効果が出るとは思えない。

の理由から断念しました。

 話を分数方式に戻すと分母が2のべき乗の場合、分子のビットパターンは結局上記の0.1の循環小数と同様になり(分母が2のべき乗なら分子は0.1の循環小数のパターンをシフトしたものの近似になるのは当然)、処理内容は前回書いた小数点方式とほぼ同様になります。

 試しに 25.5 / 256(1バイトなので判り易い)を例にすると下図のように1.5倍したものを RLCA で4回左シフトし、整数部は下位4bitに折り返した形になります(折り返し部分は不要なのですが、上の小数点方式の観点で見れば、演算精度を上げる方向に作用するのでワザワザ削除する必要はない)。
 シフト前とシフト後の値の合計を取り、発生したキャリーを整数部に加える処理となり、前回書いた小数点方式と処理内容は同じになります。
 小数点方式も同様ですが(ウルトラCのアイディアがあれば)循環小数点部分の処理の高速化については検討の余地があるかもしれません。

分数方式(25.5/256)の処理イメージ図


★追記 2019/09/16
 「Z80における2進→BCD変換」というお題を見つけたのでWeb上のMSX環境で凸撃兵さんが作られたのものを改造してMSXPen環境で確認してみました。


★追記 2023/04/22
 下記の記事で 8bit 符号無し整数の高速な乗算方法を提案しました。


nice!(0)  コメント(7) 
共通テーマ:趣味・カルチャー

3チップ構成Pic24CPM68Kマイコン(CP/M-68K起動までの作業まとめ) [68K]

 今回作成した3チップ構成のワンボードマイコン(下の写真)でCP/M-68Kが動作しているようなのでCP/M-68Kを立ち上げるまでに必要な作業を整理しました。

 使用しているチップは下記のとおり、3チップ構成で汎用ロジックICやPLD等は使っていません。
  • MC68008P10
    10MHz版のバスが8bit幅の68Kチップ。PICからクロックを供給する関係で8MHzで動作。
  • K6T4008C1B-DB70
    512KBのSRAM。アクセスタイムは70ns。
  • PIC24FJ64GA004
    16bitPICマイコン。セルフコンパイラの独自言語picleを使って制御

Pic24CPM68K

  1. 事前にダウンロードするもの

    1. 今回作成したPic24CPM68K用のファイル
       PICファーム全体のhexファイル(これを書込めばPIC側の準備は完了)とIPL、bios等のCP/M関連のソース等をアーカイブした下記のファイルをダウンロードする。

      ★変更 2022/02/21
       CPM本体をメモリの上位に移動し、TPAが0400Hから始まる前提のプログラムも動くようにした
       旧バージョンから変更する場合はA:ドライブ内の CPM.SYS を新しいものに入れ替えるだけです


    2. cpmtools
       the Wonderfully Ancient World of CP/Mのサイトでhttp://www.cpm8680.com/cpmtools/cpmtoolsWin32.zipのリンクからダウンロード

    3. CP/M-68K関連ファイル
      CP/M-68K Ver1.3のファイルは下記から入手しました。
    4. DD for Windows
       作成したCP/MディスクイメージをSDカードに書込むユーティリティ
       DD for Windowsからダウンロード。
      ※2018/05/12現在での最新版はVer.0.9.9.8です(これを使用しています)


  2. PIC24FJ64GA004側の準備
    • ワンボードマイコン基板
       回路図は下記のとおりです。詳細は「3チップ構成68Kマイコンの構想(その11)回路図整理とパターン設計」の記事を参照してください。
       SDカードホルダはebayで購入したものを使っていますが、秋月電子通商さんで販売している「SDカードコネクタ SD381200-S304」も使えると思います。

       今回作成したプリント基板のガーバーデータをseeedさんの「Fusion gallery」の「Geek product」コーナーで公開しました。
      CP/M-68K board consisting three chips

      3チップ構成の68Kマイコンボードの回路図
      ※R3は+5Vにプルアップするつもりでしたが3.3Vへのプルアップでも動作することを確認しました^^;

    • PICのファーム
       上記でダウンロードしたファイルのPICディレクトリ直下のhexファイルをPIC24FJ64GA004に書き込めば準備完了です。

      1. PICにPICKIT3等で書込みを行う場合はPL2コネクタに接続してください。
         書込み時に使用するピンはSDカードと兼用しているので、SDカードを抜いた状態で書込んでください(68Kやメモリは挿した状態でOKです)。
         書込み器を接続した状態ではSDカードのアクセスエラーが発生するので、書込み器を外してからCP/Mを起動してください。

      2. 68Kとの連携処理等はpicle言語で記述していて、自動起動状態にしているので電源ONでCP/Mが起動します。

      3. cpmディレクトリに入っている「EXIT.68K」をCP/M上で起動するとCP/Mから抜けてpicleコンパイラの世界に戻ります(runコマンドでCP/M再起動)

      4. picleのソースもpic用hexファイルに入れてあるのでセルフコンパイル環境で機能変更が可能です。
         picleソースの表示方法やpicleの使用方法については「独自言語 picle on PIC24FJ」や「独自言語 picle compiler on PIC24FJ」を参照してください。

      5. TeraTermのシリアル設定は38400bps、8bit、Parity無しです。

      6. SDカードの抜き差しをPICが監視しています。抜いた状態ではERSDのLEDが点滅します。SDカードを抜いてから挿入するとSDカードを再度イニシャライズ(保存データは消えない)するので継続してSDカードのアクセスが可能です。

      7. 「OPE」スイッチでコンソールのエスケープシーケンスの有効/無効を切り替えられます。
         無効にした場合、PIC側の処理でコンソールデータの書換えが発生しないのでコンソール用シリアル通信でバイナリデータの透過性が保証できるようになります(バイナリデータ転送時に利用)。


  3. ディスクイメージの作成
     ダウンロードしたcpmtoolsを展開したフォルダにPic24CPM68K用アーカイブ内にあるdiskdefs、cpmboot.img及びsysgen68.batファイルを置きます。
     上記でダウンロードしたCP/M-68K Ver1.3のアーカイブ内にある「DISK1」~「DISK9」のフォルダをcpmtoolsを展開したフォルダ直下にコピーします。
     DISK1フォルダにはPic24CPM68K用アーカイブ内にあるCPM.SYSを上書きでコピーします。
     sysgen68.bat内を見れば判るように「DISK10」(Jドライブ)までのファイルイメージを作成するようになっているのでDISK10のフォルダを作成し、Pic24CPM68K用アーカイブ内にあるSKED.RELやEXIT.68K(その他、CP/MのJドライブに入れたいファイル)を「DISK10」フォルダに入れます。
     DISK1~DISK10までのフォルダの準備ができたら、sysgen68を実行すると「diskimage」というファイルが生成されます。


  4. SDカードへの書込み
     SDHCタイプ(2GBより大きく32GB以下)のSDカードに、上記操作で作成した diskimage を DD for Windows で書き込みます。
     SD書込みソフトは実行ファイルを右クリックして表示されるポップアップメニュで「管理者として起動」を選択して起動する必要があります。


  5. CP/Mの起動
     上記で作成したSDカードを入れた状態で電源を入れれば、自動的にCP/Mが立ち上がります。
     ダウンロードしたPic24CPM68Kファイルに入っている「EXIT.68K」を実行することで、CP/M-68Kから抜け、PIC上のpicleコンパイラの環境に戻ります。


★2019/07/07 追記 {
 Pic24CPM68Kのアーカイブファイルに入れた方が良かったかもと後で思いましたが、CP/M-68Kとパソコン間でファイル送受信したい場合には「3チップ構成68Kマイコンの構想(その10)XMODEMアプリの作成」の記事に書いた拙作のCP/M-68K用のxmodemアプリを使用するとメチャ便利です。パソコン側ではTeraTermのxmodem機能を使います。
 コンソール出力はPIC側でCP/M時代のエスケープシーケンスの一部をANSIエスケープシーケンスに変換しているのでTeraTermのディフォルトターミナルであるVT100でもWordMasterやWordStarが動きます。
 しかし、このためにコンソール出力データがPICの変換処理で変更されてしまい、バイナリデータの透過性が保証されません。

 Pic24CPM68KではBIOSの汎用入出力(CP/M-80で言うPUN/RDRデバイス)をコンソール用のシリアルを使い、バイナリデータの透過性を保証できるように実装しているので xmodem 使用時に /p のオプションを付けることでファイル送受信をバイナリ透過性のある汎用入出力で行えます。
 また、Pic24CPM68KのOPEスイッチを押すことで上記のPIC側でのエスケープシーケンス変換を有効/無効に切り換えれるのでxmodem使用時に変換処理を無効状態にして /p オプション無しで使うことも可能です。
}

★2019/07/08 追記
 上記の「WordMasterやWordStarが動きます」はCP/M-80(Pic24CPM)での話です^^;
 Pic24CPM68Kでも同様にエスケープシーケンスの変換処理が実装されています。


【参考リンク】
 下記のwebサイトを参考にさせて頂きました。



★追記 2023/06/28 {
 MC68K を使用したチップサイズとほぼ同じサイズの CP/M-68K ボード(Pic24MC68K)について記載した記事のリンクを追加しました。

小型CP/M-68Kボード(Pic24MC68K)
}


[TOP] [ 前へ ] 連載記事 [ 次へ ]

nice!(0)  コメント(3) 
共通テーマ:趣味・カルチャー