Posts Tagged with "Ultra96"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

ゲームFSMとサウンドFSMの連携

posted by sakurai on June 5, 2020 #271

Ultra96においてBSVで開発

元々Verilog版では、コマンドバッファに書き込むだけで特に何もしなくても動作していました。今回BSVで再設計する際に、サウンドを4chとし、取りこぼしを避けるために考えたのがサウンドキュー(FIFO)でした。

図%%.1
図271.1 サウンドキュー

これはVivadoのFIFOジェネレータで作成したため、最小段数でもかなり深く1024段程度となっています。 実験したところ、確かに取りこぼしは無いのですが、一方、サウンドがゲームとズレて行き、まるでサウンドレコーダのような動作になってしまいました。そのため、FIFOを1段に修正しました。FIFOジェネレータでは1段のFIFOは作成できないのでVerilogで記述しました。1段のためFIFOと呼ぶのはおかしいのでコマンドバッファと呼ぶことにします。

コマンドバッファには、ゲームFSMからコマンドが来たことを示すフラグemptyを設け、書き込むと!emptyとなるようにします。サウンドFSMからは!emptyの時に新たにコマンドが来たと判断し、コマンドを読んだ後にemptyに変更します。

図%%.2
図271.2 1段バッファに変更

Artyボード移植後

Ultra96ではこれで動作していたのですが、Artyボードに移植後に自機増加音が無視されることに気づきました。サウンドFSMが取りこぼしているのだと推測し、再考すると、ゲームFSMが不必要に速いことに気づきました。DSOを接続して調べたところ、96.4%がウェイトだと判明したので、これを1MHzに落としたところ、動作するようでした。

ところが実験すると、依然として自機増加音(コマンドNo.9)が無視されるようです。そこで、ILAを接続して、

  • サウンドコマンド
  • サウンドFSMステート
  • コマンドバッファemtpy
  • サウンドFSM内部フラグ(fNO9)

を観測しました。最後の内部フラグfNO9は自機増加音がプリエンプトされないように割込みを禁止するためのフラグで、コマンドNo.9を受け付けた際にTrueになる信号です。

図%%.3
図271.3 ILA波形(NG)

図271.3はゲームFSMクロックを2MHzとして取得したものですが、フラグfNO9がTrueにならず、コマンドNo.9を無視しています。その原因は、サウンドFSMが受け取る前に次のコマンドNo.4を上書きしているためです。

従って、コマンドの書き込みの際にemptyである場合のみ書き込み、!emptyの場合は捨てる処理を行います(図271.4のマゼンタ矢印の処理を追加)。

図%%.4
図271.4 両側でemptyを確認するように修正

このように修正したところ、No.9の次のコマンドが!empty(=buffer full)のため捨てられることにより、図271.5のように受け付けられるようになりました。
図%%.5
図271.5 ILA波形(OK)

FIFOではないので、原理上取りこぼしは防げないものの、実用上これで動作するようです。

左矢前のブログ 次のブログ右矢

6809 IPの修正(2)

posted by sakurai on October 3, 2019 #162

6809 IPは、Verilogステートマシン設計されているため、Vivadoのロジックシミュレータでステート毎に値をチェックして行きました。飛び先である\$FF12を用意されているものの、PCに書き込まれていないことが判りました。とは言え、JMP命令は正しくデコードされ、内部信号のop_JMP(JMP命令であることを示すデコード信号)は正しく出力されています。

図%%.1
図162.1 修正前の6809CPUの動作

ステート遷移を修正し、レジスタ書き込みで終わっている箇所をジャンプステートに修正し、さらに、PCへの書き込みアサートを追加しました。

ステートの遷移は、修正前が、0f,16, 17, 18, 19, 11, 12, (09=次の命令フェッチ)のようになっているのに対して、修正後は0f, 16, 17, 18, 19, 1b, (09=次の命令フェッチ)のように遷移し、かつ、オレンジ色で示したk_write_pc信号がアサートされ、new_pcである\$FF12\$FF12がPCにセットされています。結果として正しく\$FF12にジャンプしています。

図%%.2
図162.2 修正後の6809CPUの動作

修正前は誤ってステートが、\$11(SEQ_GRAL_ALU), \$12(SEQ_GRAL_WBACK)と、レジスタ書き込みのステート遷移となっているのに対して、修正後は\$1b(SEQ_JMP_LOAD_PC)と、JMP命令のステートに遷移しています。

例えばメインフレームのような大型機をどのようにテストしているかと言えば、もちろん単体命令をひとつづつテストすることも行いますが、キャッシュ、仮想記憶、ページングの記憶階層があります。そのためのタイミングによるバグの可能性があるので、ランダム試験を実施します。命令を乱数で発生し、ただし不正命令とはならないように制約をかけ、ソフトシミュレータで正解値を取得します。これを論理シミュレータにかけ、結果値であるレジスタの値やメモリの値が正しいかをチェックします。メモリの値はひとつずつチェックすることなく、領域のチェックサムを取ることで確認します。

さらにこれを発展させ、実機にランダムプログラム生成プログラムを載せて、実行時に正解値を生成しチェックします。一見バグ値どうしを比較してOKとなりそうですが、いろいろと条件を変え、命令をページクロスさせて前半と後半でキャッシュやページのヒットミスを変えることで、演算値が変わることがあり得ます。これはプロセッサがパイプライン動作していることから、バグによってはタイミングによって結果が変わることがありうるためです。

ということで、メインフレーム並みの信頼性を保証するなら、上記のようなテストを実施しなければなりません。


左矢前のブログ 次のブログ右矢

6809 IPの修正

posted by sakurai on September 27, 2019 #161

FM=7のハードウェアエミュレーションをFPGAで実行するプロジェクトを遂行していましたが、FM-7のROMイメージもZ80カードI/Fを用いることにより、すんなり抜き出すことができました。

それをVivadoに持ち込むにあたって、COEファイルという形式に変換し、OpenCoresからダウンロードした6809を動かしたのですが、動作しないようです。実機の動作と比較するために、実機にはロジックアナライザを接続しました。

前記事のArduino-Z80I/FカードにはFM-7の内部バスが現れているので、実はArduinoは動かさなくても、ロジックアナライザを接続すれば、メインCPUである6809の動作が把握できます。ロジックアナライザも昔は数百万円したかと思いますが、私の購入したものは17,000円くらいでした。これで32CH 200K 400MSa/sですから、アドレス、データ、主要制御信号を観測するのに十分です。

●Arduinoマイコンが870円(送料別) https://www.banggood.com/RobotDyn-Mega-2560-PRO-Embed-CH340G-ATmega2560-16AU-Development-Module-Board-With-Pin-Headers-For-Arduino-p-1397734.html

●ボード作成が5枚で4.9 USD https://www.fusionpcb.jp/

●コネクタは少々高くて743円(送料別) https://jp.rs-online.com/web/p/pcb-headers/6020498/

●ロジックアナライザは消耗品ではないので高いですが、それでも17,299円 https://www.banggood.com/Hantek-4032L-Logic-Analyzer-32Channels-USB-Oscilloscope-Handheld-2G-Memory-Depth-Osciloscopio-Portat-p-1376105.html

のように比較的気軽に試すことができます。

さて、このような環境でFPGAのシミュレーションを実行したら、おかしな点を見つけました。具体的にはJMP命令が動作しません。著者に連絡を取り、報告したところ、「It doesn't surprise me that a couple (or more) things do not work.」とのことで、自分でなんとかするしかなさそうです。FM-7システムの中で6809をデバッグするのは大変なので、新たに図のような単体テスト環境を作成しました。

図%%.1
図161.1 6809 IP単体試験回路図

6809 CPUに、リセット、クロック、ROMを接続しただけです。RAMはありません。このROMに以下のようなテストプログラムを置き、実行させたところ、

FF00         start  org   \$ff00
           
FF00 8EFF12         ldx   #lab1
           ;    jmp   0,x
FF03 6E00          fcb   \$6e,\$00
           
FF05 8EFF15     lab2  ldx   #lab3
FF08 6E01          jmp   1,x
               
FF0A 8EFF12     lab4  ldx   #lab1
FF0D 6E84          jmp   ,x
FF0F 7EFF0F         jmp *
               
FF12 7EFF05     lab1  jmp   lab2
FF15 12       lab3  nop
FF16 7EFF0A         jmp   lab4
           
FFFE             org   \$fffe
FFFE FF00          fdb   start
           ;
0000             end

最初の\$FF03番地のJMPを、JMPせずに実行してしまいます。JMP命令でもインデックスアドレシングのみ動作しないようです。ちなみに、JMP ,XとJMP 0,Xは動作は同じですが、オペコードは異なります(\$6E,\$00と\$6E,\$84)。が、動作が同じであるため、JMP ,Xと書いてもJMP 0,Xのオペコードがアセンブラから出力されるため、やむなく上記のようにFCBで記述しました。


左矢前のブログ 次のブログ右矢

6809の割込み動作(8)

posted by sakurai on August 23, 2019 #155

IRQ/FIRQが正常動作したので、メインCPUとサブCPUの協調動作を実行してみます。

  1. メインCPU
  • サブCPUのreadyを待つ
  • サブCPUをhaltする
  • 共有メモリにデータを書き込む
  • 共有メモリの先頭のMSB=0で指示とする
  • サブCPUのhaltを解除する
  1. サブCPU
  • リセット後サブCPUの状態はハード的にbusy
  • サブCPUの状態をreadyにする
  • 共有メモリの先頭のMSB==0となるのを待つ
  • サブCPUの状態をbusyにする
  • 共有メモリの内容をデコードする

図%%.1
図155.1 協調動作フローチャート

このフローに基づき、協調動作のシミュレーションを行ったところ、正しく動作しました。


左矢前のブログ 次のブログ右矢

6809の割込み動作(7)

posted by sakurai on August 22, 2019 #154

タイムチャートを見直すと、例外処理中でEフラグをクリアする処理は入っていますが、一方のセット信号が初期状態からずっと不定です。このため、Eフラグが不定となっているようです。従って、セット信号を初期状態でネゲートします。

図%%.1
図154.1 割込みタイムチャート

k_set_eの初期化をしたところ、正しく動作しました。このIPは総じて割込みの実装が中途半端にしかされておらず、割込みは実質テストしていないようです。プロセッサにおいて割込みが動作しないのは致命的であり、使い物になりません。


左矢前のブログ 次のブログ右矢

6809の割込み動作(6)

posted by sakurai on August 21, 2019 #153

次にFIRQを実装します。同様にFフラグセット、クリア論理を入れましたが、割込みハンドラまでは正しく動作するものの、RTI命令の実行時にスタックを過剰に回復しています。積んだ以上に回復するという、いわゆるスタックアンダーフローを起こしてしまいます。

図%%.1
図153.1 割込みタイムチャート

まず、FIRQの動作を見てみると図153.2のようになります。Eフラグをクリアしてからpushすると書かれていますが、クリアされずXとなっています。

図%%.2
図153.2 FIRQの動作

次に、RTIの動作を見てみると図153.2のようになります。Eフラグは0であるべきなので、スタックからはCCR及びPCのみが回復されるはずです。が、タイムチャートではさらに他のレジスタも回復しているようです。

図%%.3
図153.3 RTIの動作

この誤動作の原因は、CCR[7]のEフラグが不定となっていることです。従って、図153.2にあるとおり、例外処理中でEフラグをクリアする処理を入れれば動作するはずです。


左矢前のブログ 次のブログ右矢

6809の割込み動作(5)

posted by sakurai on August 20, 2019 #152

割込み禁止の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)};

図%%.1
図152.1 割込みタイムチャート

このタイマ割込みによるハンドラの実行と復帰は、タイマの周期で正しく繰り返されます。割り込みハンドラでは正しく割り込みが禁止され、RTIにより割り込み許可として元のプログラムに戻ります。元のプログラムではタイマ割り込み待ち状態となり、タイマ割り込みが入ると再びスタックへのレジスタ退避やベクタフェッチ等の割り込み処理を行います。


左矢前のブログ 次のブログ右矢

6809の割込み動作(4)

posted by sakurai on August 19, 2019 #151

そこで、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ステート遷移する動作は無くなり、フェッチしたアドレスに飛び、飛び先である割込みルーチンを実行するようになりました。

ところが、不具合がありました。割込みルーチンで割込み原因クリアをしているにも関わらず、再度の割込みが入りません。

図%%.1
図151.1 割込みタイムチャート

前稿では、図151.1のタイムチャートに示すように、Eフラグと同タイミングで仮にIフラグもセットするように修正しました。ところが、スタックにCCRを退避する前にIフラグを割込み禁止にしたため、割り込み禁止状態のCCRが退避され、割り込みルーチンの最後で回復されたものです。つまりIフラグのセットが早すぎたわけです。

正しくは、図151.2の6809割込みにあるように、IフラグはCCRを退避してからセットしなければなりません。安直にEフラグと同タイミングでセットしたため、割込みが再度かからなくなってしまったものです。

図%%.2
図151.2 割込み動作

ここまで動けば後一息、IフラグセットをCCRの退避後まで遅延させれば、動作するはずです。

左矢前のブログ 次のブログ右矢

6809の割込み動作(3)

posted by sakurai on August 16, 2019 #150

調査の結果、割込み後のIRQマスクをCCRに全く反映していませんでした。ちなみに、CCRの仕様は6809マイコン・システム設計作法によれば、以下の図のようになっています。

図%%.1
図150.1 CCR

これだと動作が分からないのですが、その次に以下のような説明があります。

図%%.2
図150.2 Iフラグの説明

ついでにEフラグの説明も載せておきます。

図%%.3
図150.3 Eフラグの説明

Verilogソースコードを見ると、CCRのうち、Iフラグは全く設定されていないようですが、EフラグはIRQ割込みの際にセットされていたので、とりあえず、Eフラグを立てるタイミングでIフラグにコピーしました。本来は割込みの種類によりIフラグとFフラグのセット仕分けをしなければなりません。

ところが、タイムチャートのようにIフラグをセットしたので、これで正常動作するかと思ったのですが、動作は変わりません。Iフラグは参照もされていないようです。つまり、このIPの割込みは全く動作しないということができます。


左矢前のブログ 次のブログ右矢

6809の割込み動作(2)

posted by sakurai on August 15, 2019 #149

内部ステートを表示します。割込みがかかってから、スタックにプッシュし、ベクタフェッチするまではOKですが、その後にさらにスタックプッシュが入るところが誤っています。

図%%.1
図149.1 割込み動作のタイミングチャート

ステート遷移は以下のようになっています。

\$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処理)


左矢前のブログ 次のブログ右矢


ページ: