また一見何を言っているのかわからない投稿タイトルになりましたが、これにはレトロコンピューティング特有の事情があります。以下詳細を説明します。
Ryo Mukai @uCOM80 様が小型 FPGA モジュール Tang Nano 20K を使って MC6809 用の SBC 回路ボードを設計されました。
TangNano-5V https://github.com/ryomuk/tangnano-5V/tree/main
(リポジトリ内 applications/TangNano6809MEM サブプロジェクトを参照)
TangNano6809MEM ボードは Tang Nano FPGA チップが 3.3V-5V レベル変換回路を介して MC6809 に接続するようになっており、FPGA 内にクロック、メモリ、ACIA に相当する回路が構成されています。全体として電脳伝説 @vintagechips 様作 SBC6809 相当の機能が実装されます。以前 SBC6809 のメモリマップに合わせて変更を加えた Assist09 モニタの ROM イメージがそのまま使えました。
作者 Ryo Mukai 様はそのあたりも考慮されていて、上掲の Tang Nano 5V プロジェクトリポジトリには ROM イメージ BIN ファイルを FPGA 用の Verilog モジュールファイル(rom.v)に変換するスクリプトが用意されています。これにより作業がスムーズに進みました。
さらにこのシステムは FPGA 内にハードウェアを追加できるという大きな利点があります。これを利用して、SBC6809 版の Assist09 ではオフにしてあったタイマ割込みによるトレース/ステップ機能の Verilog による実装を試みます。
Assist09 トレース/ステップ機能と MC6840 PTM タイマの動作
Assist09 モニタプログラムのトレース/ステップ機能は、MC6840 PTM タイマによってトリガされる NMI 割込みを利用します。トレース/ステップ実行時は Assist09 の CTRACE ルーチンが呼び出されますが、ルーチンの最後で PTM タイマが起動されています。下記のコードです。
1550-1551 行によって PTM Timer1 の 2 バイト分のタイマパラメータレジスタ(PTM レジスタアドレス $02-$03)に $07, $01 が書き込まれます。パラメータレジスタの下位バイト(PTM アドレス $03)へ値が書き込まれたタイミングで Timer1 のカウントが始まります。
MC6840 PTM データシートによると、Assist09 の初期化コードによりシングルショットタイマとして設定された Timer1 は下図のような動作を行います。
ここで T は 6809 システムクロック E の周期、M はタイマパラメータの上位バイト値、L は下位バイト値です。t0 がタイマのカウント開始時刻になります。6809 の NMI には上図の波形を反転した信号が加えられるため、t0 から (L+1)*(M+1) – L サイクル後に NMI がトリガされることになります。前掲リストの CTRACE で書き込まれるパラメータ値(M=$07、L=$01)をあてはめて計算すると t0 から 15 サイクル後に NMI がトリガされます。
この 15 サイクルというのは前掲リスト 1552 行、RTI 命令の実行サイクル数に対応する値です。RTI が完了すると 6809 の実行はユーザプログラムに戻り、このときユーザプログラムを 1 命令だけ実行します。その実行サイクル中に NMI が発生するので、1 命令分の実行が完了すると 6809 の実行は再びトレースルーチンに移ります。1552 行のコメントはこの動作を説明しています。
Verilog による NMI 用タイマの作成
上記の解析から判断すると、以下の動作を実装することで Assist09 のトレース/ステップを実行する必要最低限のタイマが作成できます。
- タイマ開始トリガを入力するとタイマ起動
- タイマ起動から 15 サイクルカウント後に NMI 出力(出力を立ち下げる)
- NMI 出力立ち下げ後、1 サイクルの間 L レベルを保持。その後 H レベルに戻して NMI 出力終了
- システムリセット時、および NMI 終了時は NMI 出力を H にセットし、タイマカウンタをリセットしてカウントはストップ
つまり待機期間 15 サイクル、アサート期間 1 サイクルという定数シングルショットタイマを作成します。この考えで書いた Verilog モジュール oneshottmr はこの gist のようになります(注:初学者が書いたコードなので流用はおすすめできません)。
このモジュールを TangNano-5V プロジェクトの TangNano6809MEM アプリケーションのプロジェクトに追加し、top.v に次の動作をするコードを追加します。
- モジュール oneshottmr を ‘ptm’ という名前でインスタンス化
- ptm の出力を NMI に接続
- ptm のクロック入力を E に接続
- ptm のリセットをシステムリセットに接続
- MPU がアドレス $8003 に $00 以外の値を書き込んだら ptm タイマ開始トリガをアサート。1 サイクル後にアサート停止
タイマの動作サイクル長(15+1)は固定値なのでアドレス $8003 へ書き込むパラメータ値には($00 を除いて)意味がありません。タイマを開始するタイミングを知らせるためだけにレジスタ書き込み操作を行います。
値 $00 の書き込みを無視しているのは、Assist09 コード中(CTRACE 以外の部分)に、PTM 初期化のため $8003 へ $00 を書き込む処理が含まれているためです。この初期化処理中は Timer1 はまだ有効化されていないので、書き込みがあってもタイマを起動するべきではありません。なおその他の PTM レジスタ($8000-$8007 のうち $8003 を除いたアドレス)を読み書きしても、このシステムは何も反応しません。Assist09 コードは PTM 初期化のためにこのアドレス範囲に幾分書き込みを行いますが読み出しは一切行わないので、これらのレジスタに関するシステム応答処理を省いても問題ありません。
Assist09 のアセンブラソースは、PTM ベースアドレスを $8000 にセットしたうえで再アセンブルします。生成されたバイナリファイルを TangNano6809MEM の rom.v コードへ変換します。
また実ハードウェアの配線変更として、デフォルトではプルアップ抵抗のみに接続されている 6809 NMI ピンに Tang Nano 20 ピン(NMI 出力として設定済み)を接続します(3.3V-5V 変換回路を経由しています)。下記画像右下の短い黄色いジャンパピンがその接続です。
以上の準備を済ませ、実際に TangNano6809MEM ボードを動かした画面が次のとおりです。トレース/ステップコマンドの動作が確認できます。
画面上に見える Assist09 への個々のコマンド操作を以下に説明します。
- M コマンド… 次のユーザプログラムを入力します。
nop
nop
nop
swi ; Invoke MONITOR
fcb $10
- S コマンド…現在のスタックレベル以上でトレースを抑制します。これによりモニタに戻ったあともトレースが続くことを防ぎます。
- D コマンド … 入力したプログラムエリアをダンプ表示します。
- B コマンド … アドレス $1000 にブレークポイントを設定します。
- G コマンド… アドレス $1000 からプログラムを実行します。ブレークポイントにより最初の命令アドレス $1000 で停止し、全レジスタ表示します。
- T コマンド … 以降 10 命令分をトレース実行します。1 命令実行するごとに全レジスタ表示されます。先行する S コマンドの設定により、10 命令分トレースする前にモニタに実行が戻った場合は、以後はトレース実行されません。上記では $1000 から 4 命令実行後、SWI でモニタに戻るとトレース実行は停止します。
- G コマンド … 再びアドレス $1000 からプログラムを実行します。ブレークポイント $1000 で実行停止します。
- ‘.’ コマンド x4 回 … 1 命令ずつステップ実行します。アドレス $1003 の SWI でモニタに戻るので、’?’ で始まる行でユーザプログラム終了時の全レジスタ表示が行われます。
念のためオシロで 6809 の E と NMI ピンを測定してみました。トレース、ステップ実行のたびに NMI がトリガされる様子が観測できます。NMI 信号をオシロのキャプチャトリガとして設定して測定すると下図のようになります。
タイマ開始のトリガとなるイベント「アドレス $8003 への書き込み」はオシロ入力ではキャプチャが難しいので、いつタイマが発動したかはこの画面では確認できません。発動後 15 サイクル経過した時点で NMI がトリガされる様子のみ測定されています。立ち下がりのみ急峻になる、NMI として理想的な波形となっています(プルアップした NMI ピンを GND に落とすとこうなるようです)。
iVerilog シミュレータによる検証
いちおう所定通り Assist09 のトレース/ステップ機能が動作しているので Verilog によるタイマが作成できた、とは言えますが、やはり本当にタイマ開始後 15 サイクルで NMI がトリガされているのかどうかを確認したいところです。
このためには、Verilog シミュレータを使って oneshottmr モジュールの動作を確認します。Verilog シミュレータとしてオープンソースの Icarus Verilog(iverilog)を使用します。Tang Nano 開発ツール Gowin FPGA Designer がインストールされているのと同じ Windows 10 マシンにインストールしました。
gist にシナリオモジュールおよび実行スクリプトファイル(Windows では Git Bash シェルから実行可)を上げておきました。
スクリプト実行によりシミュレーション結果データ scenario.vcd が作成されます。この結果データは gtkwave プログラム(iverilog とともにインストールされる)で視覚化できます。下図に説明キャプション付きで gtkwave の結果画面を示します。
画面中の説明通り、trigger 入力立ち上がりによってタイマが起動し、システムクロック clk (=6809 の E)15 サイクル経過後に NMI がアサートされている様子がわかります。なおタイマは clk の立ち上がり(posedge)に同期して動作するので、システムサイクル周期 50% の時点で NMI がアサートされます。
まとめ
Ryo Mukai @uCOM80 様の Tang Nano-5V システムが非常に興味深かったためサブプロジェクトの 6809 ボードを作成してその構成を学び、みようみまねで Verilog による NMI 割込みタイマモジュールを追加しました。
自作タイマモジュールによってまがりなりにも Assist09 の割込みとトレース実行が動いたようですが、なにぶん経験の乏しい初学者なので実際の動作が適切か確信が持てません。そこで iVerilog でシミュレーション結果を確認し、想定通り動いていると判断しました。
本格的なシミュレーション検証では境界値テストなどより厳密な条件によるチェックも行うと思いますが、そのための方法論などについてはまだよくわかっていません。教科書や資料にあたってさらに勉強すべきところです。
ただ、Verilog シミュレーションとはいっても、今回作ったサイクル長固定のタイマ程度の簡単な機構であれば実行手順と結果の確認は GAL + WinCUPL で行うシミュレーションテストと大差ないようです。逆に言えば GAL と FPGA は規模の差こそあれ地続きの技術なのだな、という感想を持ちました。