オリジナル16ビットCPUコアMoscoviumを使った電子工作シリーズでは、Moscoviumを搭載したMoscoviumマイコンのROMにプログラムを書き込む手段が必要です。まず、プログラムとシステムのデバッグが終わって、FPGAボード(Sipeed Lichee Tang Primer)の不揮発コンフィギュレーションメモリに書き込む場合、Moscoviumアセンブラ(asm_mcvm.olv)を使ってMIFファイルを作成し、TD IDEの書き込みツールで書き込みます。
これはこれでOK。
問題は、アセンブラのプログラムデバッグ中の話です。
当初の計画では、TD IDEのRAMエディタ機能を使って、プログラムをROMへ書き込むことを想定していました。ところが、どうも、このRAMエディタ、安定しません。詳しくはこちらをご参照ください。
上図は、思った様に機能しなかったRAMエディタのスクリーンショットです。
新しいプログラムを書き込んだつもりが、どうもうまく動かず、データを変更せずに何度か書き込みをやり直してみると、その都度、症状が変化して行きます。RAMエディタで書き込んだデータが、アトランダムに時々不正なデータに化けているからです。
これでは、プログラムのデバッグどころではないのです。
Arduino UNOのブートローダは、UNOのMCUであるATmega328にリセットがかかった時に最初に動作するプログラムです。このプログラムは、ATmega328のUARTを有効化して、ホストからのコマンドに応じてプログラムデータをダウンロードし、ATmega328の内蔵Flashメモリにプログラムを書き込む働きをしています。
Arduino IDEで作成したプログラムが、「マイコンボードに書き込む」ボタンでUNOに書き込みできるのは、ATmega328のブートローダ(プログラム)の働きなのです。
328duino用にブートローダをコンパイルした記事:
「#10-6 328duinoをクロックアップ(完結編)」
Moscoviumにも同様なブートローダを備えて、Moscoviumのプログラムメモリ(ROM)にプログラムを書き込める様にするのが、今回のテーマです。
時に、ATmega328のプログラムメモリはFlashなので書き込み可能なのですが、FPGAボードのROMにデータを書き込むことが出来るか?と言うと、答えは否です。ただし、FPGAボードのROMマクロの中身はただのRAMなのです。RAMマクロの書き込み許可信号を不活性に固定して、リードオンリで使用しているのが、ROMマクロです。
よって、(1)ROMマクロへのデータの書き込みはできません、(2)でも、ROMマクロの代わりにRAMマクロを使っても何ら問題無く(速度面、論理規模面共)、かつ、書き込みも可能となります。自分の作る回路で、ある特定の条件を満たした時だけ、書き込み可能な様に作り込んでおきさえすれば良いのです。
普通に使う時(通常動作モードと呼びます)は、リードオンリのRAMとして使い、ROMへデータを書き込む時(ブートモードと呼びます)は、普通にリード/ライト可能なRAMとして機能させます。
通常動作モードでは、RAMマクロをリードオンリで使って、正にROMとして使います。通常動作モードと異なるブートモードで起動した時だけ、ROMと言う名のRAMマクロにデータを書き込めるような回路にしておきます。
プログラムのデバッグ中、プログラムバグによって誤ってROMを書き換えてしまうと、更にプログラムデバッグを難しくしてしまうので、その様なことが起きない様、完全に異なる別なモードで起動した時だけ、ROMデータの書き換えが出来るようにしておいた方が安全だからです。
通常モードはMCOC115最上位I/Fのreset_n信号でリセットした時に動作するモードですので、もう一本異なるリセット信号bootr_nを新設して、こちらの信号でもリセットがかかる様に論理を作り込みます。
bootr_n信号でリセットがかかった場合、内部のbootmd信号を1にすることでブートモードであることをROMブロックに伝え、CPUからROMへの書き込みによってROMデータを書き換えできるようにします。当然、通常のreset_nでリセットがかかった時にはbootmd=0なので、ROMへの書き込みができない様にしておきます。
bootr_n信号によってブートリセットが掛かると、MCOC115はブートモードに入ります。ブートモード(bootmd=1)では、専用のブートROMからプログラムをフェッチして、UARTからROMへ書き込むデータを受信する動作とします。
ブートモードでROMにプログラムのダウンロードが完了したら、FPGAボード上のユーザスイッチを押すと、ユーザリセット端子reset_nがアサートされて通常モードで起動します。
現在、リセットを制御しているのはシステムコントローラSYSCなので、ブートリセット(bootr_n)の制御もSYSCに持たせます。
さて、ブートローダの受信するデータは、例えば以下の様なデータをターゲットとします。
ブートローダが受信可能なデータ例:asm_mcvm.datw
上のデータはiverilogでシミュレーションする時、$readmemh()システムタスクで読み込み可能なデータファイルと同じものです。より一般的には、以下のフォーマットをダウンロード可能とします。
ブートローダがダウンロード可能なデータフォーマット:
ただ、上のデータ(asm_mcvm.datw)は、プログラムのデバッグを容易にするため、コメント部分にアセンブラの情報を残してあるため、必要な情報(アドレスとデータ)に対して冗長で無駄なデータをシリアル転送する分、ダウンロードに時間がかかってしまいます。
そこで、asm疑似命令で拡張子を".dat2"としてアセンブルした場合、以下の様に、書き込みに必要なデータだけを含むDAT2ファイルを出力する様にアセンブラ(asm_mcvm.olv)を改定しておきました。
asm "asm_mcvm.dat2" …… としてアセンブルすると、以下のファイルとなる。
ブートローダ受信用データファイル:asm_mcvm.dat2
デバッグ用の余計な情報が無い分、高速にダウンロードすることが可能です。
Moscovium用ブートローダは、Moscoviumアセンブラ(asm_mcvm.olv)で生成する組み合わせ論理ROM(RTL)に記憶します。ブートモードで起動すると、Moscoviumは組み合わせ論理ROMからブートローダプログラムをフェッチして、以下の動作を実行します。
ブートローダフロウ:
このフロウをひたすら繰り返します。組み合わせ論理ROMは、論理セル(LUT)を消費して肥大化するので、なるべく小さなプログラムとなる様に、上の処理以外の動作はしません。なので、起動時も受信中も、シリアルモニタに対してメッセージの類は一切送信しないことにします。
本当は、受信したデータのチェックサムとか、チェックデータを送信して、照合出来たりした方が良いのかもしれませんが、組み合わせ論理ROMの論理規模(LUT数)が大きくなるので、割愛します。
検討したフロウに従ってコーディングした、ブートローダのアセンブラソースを以下にリンクしておきます。
ブートローダROM生成用アセンブラソース:mcoc_boot.asm
上(↑)のファイルをアセンブルすると、下(↓)のRTLが生成される。
ブートローダROM(RTL):rom_boot_mat.v
※リンクしたRTL(rom_boot_mat.v)はデザインの一部となりますので、コンパイル(論理合成・配置配線)に必要となります。ダウンロードしてプロジェクトのrtlディレクトリに格納してください。もしくは、ご自身でソース(mcoc_boot.asm)をカスタマイズ後、アセンブルして生成してください。
プログラム先頭付近の定数設定で、ブートローダの通信速度を9,600baudにしてあります。いつもシリアルモニタで使う通信速度と合わせてアセンブルし直すことを推奨します。さもないと、シリアルモニタとして使う時と、プログラムをダウンロードする時で、Tera Term等通信ソフトウェアの通信速度設定を一々切り替えなければならなくなります。
尚、ブートモードに入った時に、全く何のメッセージも発信しないのはちょっと不安だったので、ブートローダプログラム(mcoc_boot.asm)の先頭付近にFPGAボードのオンボードLEDを光らせる記述を追加してあります(該当コード部分抜粋↓)。
// rom data download
ldbiu r6,0 // reset wptr(=r6)
loop:
# ********************************
# comment this if you do not need
// pilot led
mov r3,r6
andi r3,0x0e
eori r3,0x0c
lsfti r3,-1
ldwi r2,porout
stw [r2],r3
# ********************************
パイロットLEDが必要ない場合、もしくは、汎用I/Oポートの0〜2ビット目を他の用途で使用する場合などは上のコードをコメントして、RTL(rom_boot_mat.v)を生成してください。
ブートローダを使って、シリアル経由でROMにデータをダウンロードする際に必要なリソースについてまとめてみます。
実は、今回説明したブートローダ機能は、前回のH-FPU版MCOC115FP0440で既に組み込み済みとなっています。
つまり、デザインとしては、MCOC115FP0440(プロジェクトファイルはMCOC_FP.al)をコンパイルしてFPGAボードに書き込めばOKです。デザインデータは以下リンクよりダウンロード可能です。
I/O制約ファイルにブートリセット端子(bootr_n)とUART端子(uart_rxd)の制約が必要になるので、今回は以下のADCファイルをプロジェクトに追加した状態でコンパイルしてください。
I/O制約ファイル:MCOC_full.adc
尚、FPエディションより、ROM容量を変更可能なコンパイルオプション(↓)を新設してあります。
// compile option
//`define MCOC_ROM_8K
//`define MCOC_ROM_16K
上コードの様にコメントしたままにしておくと、デフォルトの4KバイトROMとなります。選択肢として8Kバイト版と16Kバイト版を用意しています。複数の`defineを有効にすると、最大のROM容量が選択されます。
8Kバイト版または16Kバイト版を選択した場合、ROM用RAMマクロのモジュール名が以下の通り変わりますので、ROM用RAMマクロ作成時、モジュール名と容量を合わせて生成してください。
`define | ROM容量 | ROM用RAMマクロ名 | サイズ |
MCOC_ROM_16K | 16Kバイト | ram16kb_mat(.v) | 16ビット×8,192ワード |
MCOC_ROM_8K | 8Kバイト | ram8kb_mat(.v) | 16ビット×4,096ワード |
なし | 4Kバイト | ram4kb_mat(.v) | 16ビット×2,048ワード |
各サイズのROM用RAMマクロは、TD IDEのIP Generatorを起動して各自生成する必要があります。
FPGAボードの他に、3.3Vインターフェイスが可能なUSB⇔UART変換モジュールを準備してください。筆者は、以下図の様なFTDIモジュールの電圧ジャンパ:3.3V側して使います。
FTDIとFPGAボードを以下表の通りに接続してください。FTDIはUSBケーブルでホストPCと接続します。
FPGAボード | 接続 | FTDI |
R7(uart_rxd) | ← | TXD |
T7(uart_txd) | → | RXD |
GND | ⇔ | GND |
× | VCC(ジャンパ:3.3V側) |
更に、例えば以下図の様な2端子タクトスイッチを準備して、FPGAボードのJ13端子と、その近くにあるGND端子(ピンソケット)間に挿入してください。
bootr_nに割り当てたJ13端子はデフォルトでpull-upが有効になっているので、通常状態ではHIGHレベルとなっています。これがGND端子との間にタクトスイッチで接続されているので、スイッチを押すとLOWレベルに下がってブートリセットがかかります。
以下、実験中の様子です。
上図で、2端子タクトスイッチがピンソケットに挿さっているのをご確認ください。上図の様に挿せる様、適切なGND端子から1端子空けた隣のJ13端子にbootr_n端子を割り当ててあります。
以下、実験サンプル(手順)です。
上手順を実行した時のデモ動画は以下の通りです。
期待外れだったTD IDEのRAMエディタには、早々に見切りをつけて、ブートローダを適用してみました。これで、ソフトウェアのデバッグ効率がぐん⤴と上がるはずです。