22 |
6809の割込み動作 (7) |
Posts Tagged with "FPGA"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
21 |
6809の割込み動作 (6) |
次にFIRQを実装します。同様にFフラグセット、クリア論理を入れましたが、割込みハンドラまでは正しく動作するものの、RTI命令の実行時にスタックを過剰に回復しています。積んだ以上に回復するという、いわゆるスタックアンダーフローを起こしてしまいます。
まず、FIRQの動作を見てみると図153.2のようになります。Eフラグをクリアしてからpushすると書かれていますが、クリアされずXとなっています。
次に、RTIの動作を見てみると図153.2のようになります。Eフラグは0であるべきなので、スタックからはCCR及びPCのみが回復されるはずです。が、タイムチャートではさらに他のレジスタも回復しているようです。
この誤動作の原因は、CCR[7]のEフラグが不定となっていることです。従って、図153.2にあるとおり、例外処理中でEフラグをクリアする処理を入れれば動作するはずです。
20 |
6809の割込み動作 (5) |
割込み禁止のCCRへの反映を遅延させるタイミングを作り出しました。全レジスタの退避が終わった後のベクタフェッチは前々稿によれば、SEQ_MEM_READ_Hステートですが、そのタイミングでレジスタにCCR書き込みを指示することを考えます。このステートにはexceptionでない場合というif文がありますが、注目する場合はexception中なので、ちょうどelseの部分に以下の文を記述します。
if (k_set_i) begin
k_set_i_timing <= 1;
k_set_i <= 0;
end
k_set_i_timingとして、k_set_iを遅延させたCCR反映タイミング信号を作り出し、これをレジスタに与えます。なお、このk_set_iはSEQ_IRQステートに入った時点でセットし、一方、RTI命令でk_clear_iをセットします。
これにより正しく割込みが入り、割り込みが禁止されたかと思ったのですが、RTIから戻るところで再度割込みが入る現象が起きました。解析すると、割込みハンドラで割込み原因をクリアしたにも関わらず、CPU内部で割込みをまだ保持していることが原因と判りました。そのため、RTI実行により割り込み許可された途端にまた割り込み処理に移行してしまいます。そこで以下のように、割込み要因を無視するタイミングゲート処理を入れたところ、正しく動作するようになりました。
(修正前)
if (!k_reg_irq[2])
k_reg_irq <= { k_reg_irq[1:0], (!cpu_irq_n)};
(修正後) if (!k_reg_irq[2])
k_reg_irq <= { k_reg_irq[1:0], (!cpu_irq_n & !k_set_i & !k_set_i_timing)};
このタイマ割込みによるハンドラの実行と復帰は、タイマの周期で正しく繰り返されます。割り込みハンドラでは正しく割り込みが禁止され、RTIにより割り込み許可として元のプログラムに戻ります。元のプログラムではタイマ割り込み待ち状態となり、タイマ割り込みが入ると再びスタックへのレジスタ退避やベクタフェッチ等の割り込み処理を行います。
19 |
6809の割込み動作 (4) |
そこで、CCRを参照して割込みに入る入らないを決定させようとしたら、ソースコードで以下のような定義文を見つけました。
// flags used in MC6809_cpu.v
`define FLAGI regs_o_CCR[5]
`define FLAGF regs_o_CCR[6]
`define FLAGE regs_o_CCR[7]
前稿の図150.1のCCR構成を見てもわかるように、FLAGIのビット位置が誤っています。Iフラグの位置はbit5ではなくbit4です。これを正しいbit位置の4に修正したところ、ベクタフェッチの後に再度IRQステート遷移する動作は無くなり、フェッチしたアドレスに飛び、飛び先である割込みルーチンを実行するようになりました。
ところが、不具合がありました。割込みルーチンで割込み原因クリアをしているにも関わらず、再度の割込みが入りません。
前稿では、図151.1のタイムチャートに示すように、Eフラグと同タイミングで仮にIフラグもセットするように修正しました。ところが、スタックにCCRを退避する前にIフラグを割込み禁止にしたため、割り込み禁止状態のCCRが退避され、割り込みルーチンの最後で回復されたものです。つまりIフラグのセットが早すぎたわけです。
正しくは、図151.2の6809割込みにあるように、IフラグはCCRを退避してからセットしなければなりません。安直にEフラグと同タイミングでセットしたため、割込みが再度かからなくなってしまったものです。
16 |
6809の割込み動作 (3) |
調査の結果、割込み後のIRQマスクをCCRに全く反映していませんでした。ちなみに、CCRの仕様は6809マイコン・システム設計作法によれば、以下の図のようになっています。
これだと動作が分からないのですが、その次に以下のような説明があります。
ついでにEフラグの説明も載せておきます。
Verilogソースコードを見ると、CCRのうち、Iフラグは全く設定されていないようですが、EフラグはIRQ割込みの際にセットされていたので、とりあえず、Eフラグを立てるタイミングでIフラグにコピーしました。本来は割込みの種類によりIフラグとFフラグのセット仕分けをしなければなりません。
ところが、タイムチャートのようにIフラグをセットしたので、これで正常動作するかと思ったのですが、動作は変わりません。Iフラグは参照もされていないようです。つまり、このIPの割込みは全く動作しないということができます。
15 |
6809の割込み動作 (2) |
内部ステートを表示します。割込みがかかってから、スタックにプッシュし、ベクタフェッチするまではOKですが、その後にさらにスタックプッシュが入るところが誤っています。
ステート遷移は以下のようになっています。
\$09 SEQ_FETCH
\$0F SEQ_DECODE
\$33 SEQ_PC_READ_L
\$34 SEQ_PC_READ_L_1
\$35 SEQ_PC_READ_L_2
\$1B SEQ_JMP_LOAD_PC
\$09 SEQ_FETCH
\$03 SEQ_IRQ
(ここから)
\$20 SEQ_PREPUSH
\$22 SEQ_PUSH_WRITE_L
\$23 SEQ_PUSH_WRITE_L_1
\$24 SEQ_PUSH_WRITE_H
\$25 SEQ_PUSH_WRITE_H_1
(16バイトストアを4回繰り返し、PC, US, IY, IX)
\$20 SEQ_PREPUSH
\$22 SEQ_PUSH_WRITE_L
\$23 SEQ_PUSH_WRITE_L_1
(8バイトストアを4回繰り返し、DP, B, A, CC)
\$36 SEQ_MEM_READ_H
\$37 SEQ_MEM_READ_H_1
\$38 SEQ_MEM_READ_H_2
\$3A SEQ_MEM_READ_L_1
\$3B SEQ_MEM_READ_L_2
\$08 SEQ_LOADPC
\$09 SEQ_FETCH
\$03 SEQ_IRQ
(再度IRQ処理)
14 |
6809の割込み動作 |
メインCPUとサブCPUはお互いに割込みを掛け合います。メインCPUからは通常、サブCPUのbusyを確認し、readyであればサブCPUをHALTし、共有メモリにコマンドを書き込んだ後、HALTをネゲートします。サブCPUはすぐにbusyにした後、コマンドを解析し、処理を行います。
これが通常シーケンスですが、例えば時間のかかる処理をサブCPUがやっていた場合に中止させたい時には割込みをかけるしかありません。これをCANCEL割込みといいます。逆に、サブCPUからメインCPUに用事がある場合にはATTENTION割込みをかけます。
さて、メインCPUの割込みの一つにタイマー割込みがあり、これをシミュレーションしてみます。タイマー割り込みはIRQに接続されているため、IRQ割り込みハンドラをプログラムします。IRQは全レジスタのセーブリストアが自動で行われます。割り込みハンドラでは、まずタイマ割り込み要因のクリアを行った後、要因レジスタは負論理であるため、反転してメモリに書きだした後でRTIで元の命令列に戻ります。
1000 stack equ \$1000
2000 ustack equ \$2000
FE00 start org \$fe00
FE00 10CE1000 lds #stack
FE04 CE2000 ldu #ustack
FE07 8EFD02 ldx #\$FD02 ; IRQ Mask Reg
FE0A 86FF lda #\$FF ; Timer IRQ unmask
FE0C A784 sta ,X ; Timer IRQ enabled
FE0E 20FE bra *
FE10 firq equ *
FE10 irq equ *
FE10 8EFD03 ldx #\$FD03 ; IRQ event reg
FE13 A684 lda ,X ; IRQ clear
FE15 8AFB ora #\$FB
FE17 70FE17 eora #\$FF
FE1A B73000 sta \$3000
FE1D 3B rti
FFF6 org \$fff6
FFF6 FE10 fdb firq
FFF8 org \$fff8
FFF8 FE10 fdb irq ; Timer IRQ
FFFE org \$fffe
FFFE FE00 fdb start
0000 end
まずリセット後にFD02をライトし、割込みマスクを許可します。'1'をライトすると許可となります。
次にタイマー割込みがかかるとスタックに全レジスタを退避します。PC, US, IY, IX, DP, B, A, CCの12バイトです。
次に\$FFF8から2バイトをフェッチします。ところが、\$FFF8の示すアドレスにジャンプする前に再度全レジスタをスタックに退避しています。
割込みが入った場合、直ちに割込みマスクがかからなければ、無限に割込みが入るので、これはCPUのIRQマスクのバグと思われます。
9 |
メイン・サブシステムI/Oアドレスデコード |
アドレスデコード及びレジスタ類を実装していきます。FM-7のI/Oの情報は参照場所にあります。メインシステムI/Oアドレスマップはその1から、サブシステムI/Oアドレスマップはその7からです。
メインシステム
FM-7メインシステムは64KbitDRAMが存在するため、フルデコードされています。
- $FD00 R キーボードデータ bit8
- $FD00 R キーボードデータ bit7-bit0
- $FD02 W 割込み(IRQ)マスクレジスタ(タイマ、キーボード)
- $FD03 R 割込み(IRQ)フラグレジスタ(タイマ、キーボード)
- $FD04 R 割込み(FIRQ)フラグ (ブレーク、アテンション)
- $FD05 R サブ状態 (busy)
- $FD05 W サブ・Z80制御 (HALT, Cancel, Z80)
- $FD37 W マルチページレジスタ (リセット時イネーブル)
- $FD38 R パレットレジスタ0から読み出し(初期値=黒)
- $FD38 W パレットレジスタ0へ書き込み
- $FD39 R パレットレジスタ1から読み出し(初期値=青)
- $FD39 W パレットレジスタ1へ書き込み
- $FD3A R パレットレジスタ2から読み出し(初期値=赤)
- $FD3A W パレットレジスタ2へ書き込み
- $FD3B R パレットレジスタ3から読み出し(初期値=マゼンタ)
- $FD3B W パレットレジスタ3へ書き込み
- $FD3C R パレットレジスタ4から読み出し(初期値=緑)
- $FD3C W パレットレジスタ4へ書き込み
- $FD3D R パレットレジスタ5から読み出し(初期値=シアン)
- $FD3D W パレットレジスタ5へ書き込み
- $FD3E R パレットレジスタ6から読み出し(初期値=黄)
- $FD3E W パレットレジスタ6へ書き込み
- $FD3F R パレットレジスタ7から読み出し(初期値=白)
- $FD3F W パレットレジスタ7へ書き込み
図144.1にメインシステムブロック図を示します。今回はアドレスデコーダとそれからアクセスされるレジスタを分離して設計しました。モジュラー化のためです。図では上方左側にアドレスデコーダ、上方右側にレジスタが配置されています。
サブシステム
FM-7サブシステムはフルデコードされていません。
- $D400 R キーボードデータ bit7-bit0
- $D401 R キーボードデータ bit8
- $D402 R キャンセルIRQ ACK
- $D404 R メインCPUへのアテンションIRQ
- $D408 R CRT ON
- $D408 W CRT OFF
- $D409 R VRAM Access Set
- $D409 W VRAM Access Reset
- $D40A R Ready
- $D40A W Busy
- $D40D R INS LED ON
- $D40D W INS LED OFF
- $D40E W VRAM Offset Address bit13-8
- $D40F W VRAM Offset Address bit7-0
図144.2にサブシステムブロック図を示します。図では真ん中にアドレスデコーダとレジスタを階層ブロックにしたモジュールが配置され、その内部左側にアドレスデコーダ、右側にレジスタが配置されています。
8 |
6809の改造 - HALT端子の追加 (3) |
もう少し詳しく見てみます。HALTREQがアサートされたのは、たまたま\$E021のblo命令の第1バイト\$25をフェッチした直後でした。図143.2に示すように、blo命令の\$25と\$F4のフェッチの間に入ったことになります。
HALTREQが"blo l2"命令の間であるため、すぐにはHALTステートに遷移せず、blo命令の第2バイトである\$E022の\$F4のフェッチが行われます。
前稿で新設したhalt_save_stateは、HALTに入る直前のステートを保存するようにしたため、HALTステートに入るまではサイクル毎に書き換えられています。
分岐命令をデコードし分岐が実行された後に、アドレスが分岐先である\$E017に変化しています。分岐命令はPCを変更するだけであり、これで分岐命令の実行は完了です。
分岐先命令フェッチの前で新設のHALTステート(\$40)に遷移し、次のサイクルでHALTACKがアサートされています。HALTステートに遷移したので、halt_save_stateは、HALTステートの前の値(\$09)を保持し、更新は止まります。
一方、HALTREQネゲート時の状態を観測すると、前稿でも見たように、内部ステートが回復され、分岐先である\$E017、\$E018の命令を順次フェッチしています。このように分岐先命令のフェッチが実行されており、命令の切れ目、具体的には分岐命令の実行後にHALTに入り、分岐先命令の実行前にHALTから出る動作を正しく実行しているようです。
6 |
6809の改造 - HALT端子の追加 (2) |
前稿のコードを追加し、シミュレーションしたところ、HALTをアサートしてもHALTステートへの遷移ができないことが判りました。前稿のコードのように、外部信号により内部フラグを立て、内部フラグが立っていたらすぐにstateをHALTステートに遷移させたのですが、別の要因によりstateが上書きされてしまうようです。
そこで、同種と思われるNMIのコードのように、HALTステート遷移を「NMIの内部フラグを見てNMIステートに遷移する場所」に移動したところ、正しく動作しました。シミュレーション結果を見ると、HALTがかかっても直ちにstateを遷移するのではなく、継続したバスサイクルを終了した命令の切れ目でHALTがかかっていることがわかります。
つまり、前稿のようにすぐにstate遷移させても上書きされてしまい、NMIのように命令の切れ目で判定して遷移させるのが正しいということです。
HALTREQがアサートされてから、次のサイクルで内部フラグが立ちますが、数サイクル遅延した後にHALTステートに入っています。その後HALTACKが出力されます。
一方、HALTREQがネゲートされると、次のサイクルで内部フラグが落ち、次のサイクルでHALTACKが落ちると同時にHALTステートに入る前に保存されていたステートから再開します。こちらは遅延していません。
ページ: