前回、CPUに割り込み機能を追加して、割り込みコントローラINTCを作成しました。今回は、割り込み要求を発生するI/Oユニットとして、何かと使い道のある汎用16ビットタイマユニットを設計したいと思います。
これで、「マイコンを自作する」をテーマとしたシーズン15が完結します。
シーズン15の締めに、割り込みに対応したタイマユニットを設計します。以下、設計方針です。
設計方針:
上の設計方針を元に作成したレジスタ仕様は以下の通りとなります。
Addr | Name | R/W | Bit:15〜8 | Bit:7〜0 | Memo | ||||||||||||||
0xf040 | timctl | RW | − | − | − | − | − | − | − | − |
OCMA [1:0] |
OCMB [1:0] |
− | − | − |
CN TE |
timer control | ||
0xf042 | timflg | RW | − | − | − | − | − | − | − | − |
OV FF |
CM AF |
CM BF |
− |
OV FE |
CM AE |
CM BE |
− | interrupt flag |
0xf044 | timpsc | RW | PSC[15:0] | prescaler | |||||||||||||||
0xf046 | timcnt | RW | TCNT[15:0] | counter | |||||||||||||||
0xf048 | timprd | RW | PRD[15:0] | period | |||||||||||||||
0xf04a | timcma | RW | CMA[15:0] | compare match a | |||||||||||||||
0xf04c | timcmb | RW | CMB[15:0] | compare match b | |||||||||||||||
0xf04e | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
※アドレスはユニット0(tim0)のもの。ユニット1(tim1)のアドレスは0xf050〜0xf05f。
BUSC出力のセレクト信号はbcs_tim0_nおよびbcs_tim1_n(2ユニット分)です。上表の範囲でアサートされるので、TIM162ユニット内では、アドレスbadr[3:0]を復号してレジスタを特定しています。このI/Oユニットはバイトアクセス禁止です。必ずワードアクセスしてください。
以下図にブロック図を示します。
モジュール名は、16ビットタイマと2本のPWM出力から、TIM162としました。
周期レジスタtimprd設定値によって、タイマカウンタtimcnt=timprdとなると、次のカウントタイミングでtimcnt=0にリセットされて、カウントを続けます(下図)。
※上図で、clkは24MHzクロック、cupは内部プリスケーラカウンタのオーバフロウ時に出力されるカウントアップ信号です。上図の例では、timpsc=4と設定した場合に相当します。
また、この時、timflgレジスタのオーバフロウOVFFフラグ🏁が1にセットされます。
結果、タイマカウンタtimcntは、0〜timprdまでカウントして0に戻るので、周期はtimprd+1です。タイマの周期は、以下の式で算出できます。
周期T=(timprd+1)×(timpsc+1)/fcpu[s],ただしfcpu=24MHz
コンペアマッチレジスタtimcmxにセットした値によって、timcnt=timcmx−1となって、次のカウントタイミングでtimcmxと一致すると、割り込みフラグレジスタtimflgのCMxFフラグに1がセットされます(下図)。
timcmx>timprdとなる値に設定すると、コンペアマッチは発生しません(CMxFフラグはセットされない)。
制御レジスタtimctlのOCMxフィールドの設定によって、PWM信号timr_pwmxを出力することが出来ます。
下図に、OCMx[1:0]=2'b01の場合のPWM出力例を示します。
OCMx[1:0]=2'b01時のPWM出力は、timcnt=0でセット、timcnt=timcmxでクリアとなる、上図の様な波形となります。
PWM出力タイミング:
極限ケース:
その他の設定値:
ArduinoコアライブラリのanalogWrite()関数の様に、デューティを0〜255で指定してPWMを出力したい場合、以下の様に設定します。
デューティを0〜255の範囲で指定したい場合には、周期レジスタをtimprd=<デューティ最大値(255)>−1に設定するのがミソです。こうすることで、timcmx=0の時はPWM出力=LOW固定、timcmx=255の時はPWM出力=HIGH固定出力を得ることが出来ます。
前回設計した割り込みコントローラINTCと、今回設計した汎用16ビットタイマTIM162をMoscoviumマイコンに組み込んで完成させます。尚、汎用タイマは何かと使い道があると思われるので、同じタイマを2個、tim0およびtim1のインスタンス名で組み込むこととしました。
実際にFPGAボード(Sipeed Lichee Tang Primer)に書き込んで実験する時には、LEDキャンドルを作って見ようと思いますので、以下の様にI/O制約をかけておきます。
I/O制約:MCOC_candle.adc
※タイミング制約は、特に変更有りません。
以下ページからダウンロードできるようにしておきました。型名はMCOC115IP0408、プロジェクト名はMCOC_IP.alです。
TD IDEを使ってコンパイル(論理合成・配置配線)してみた結果を以下にまとめます。
最上位モジュールを組み上げた勢いで、コンパイルまで実施してしまいましたが、FPGAボードにはまだ書き込まずに、まずは、シミュレーションで動作を確認しておこうと思います。
前回と今回で修正したり新規に作成したモジュールは、CPU(Moscovium)、INTC162、TIM162の3個です。INTCとTIM162については、今まで通り、目標仕様に則ってレジスタを叩き、シミュレーションで確認して行けば良いでしょう。
問題はCPUですね。命令のセルフテストみたいにCPUだけで閉じる検証と違って、周辺I/Oユニットから割り込みを要求してもらわないと検証できません。検証項目によっては、「このタイミングでirqがアサートされたら、○○○」、「このタイミングでirqがアサートされていなければ、○○○」みたいな検証項目もあるでしょうから、一工夫必要です。
// interrupt request
reg [3:0] irq_pin;
reg [7:0] irq_wait;
always @(posedge clk)
begin
irq_pin[3:0]<={ top.bdatw[13:12],top.bdatw[9:8] };
irq_wait[7:0]<=top.bdatw[7:0];
if (top.brdy && top.bcmd[1] && top.badr[15:0]==16'hf000)
begin
@(posedge clk);
repeat (irq_wait[7:0])
@(posedge clk);
if (irq_pin[3])
intc_int1<=irq_pin[1];
if (irq_pin[2])
intc_int0<=irq_pin[0];
end
end
↑CPUの割り込み機能をテストする目的では、割り込み(irq信号)を、起こしたいタイミングで起こせることが必要なため、スティミュラス(test.v)に上の様なコードを追加して、起こしたいタイミングで割り込みを起こすようにしています。
使用したのはINTCの外部割り込み端子(intc_int0,intc_int1)です。
シミュレーションの終了条件と同様に、マイコンの内部信号をモニタしていて、CPUが特定のアドレス(16'hf000)に書き込みアクセスした時、その書き込みデータを条件として外部割り込み端子(intc_int0,intc_int1)を操作しています。
外部割り込み端子操作条件:
例えば以下の様なコードを実行すると、実行の16クロック後に外部端子intc_int1が立ち下がりエッジを生成して、INTCの外部端子割り込みがかかります(intextレジスタのEIT1[1:0]=2'b01、EIE1=1に設定した場合)。
equ simintr,0xf000
ldbiu r0,0x2010 // intc_int1=0, at 0x10(=16) cycles later
ldwi r7,simintr
stw [r7],r0 // この書き込みサイクル実行から16(0x10)クロック後
※intc_intx端子のシンクロナイザやスティミュラスが書き込みイベントを認知するタイミングなどによって、irq信号のアサートタイミングは数サイクル前後する可能性があります。書き込みデータを使って、相対的なタイミングを1サイクル単位で調整可能なので、最終的にはシミュレーションで確認しながらタイミングを調整して検証します。
割り込み端子さえ制御できたら、後は、「ちゃんと割り込みがかかるか?」「INTCのフラグを読み出して、クリアできるか?」等々、機能を検証して行けば良いだけです。
もし、実機(FPGAボード)で同様な検証をしようと思ったら、外部割り込み端子にスイッチなどを接続して、手動で割り込みを起こす必要があります。当然、「この命令の実行中にirq信号をアサートしたい」なんて、ジャストタイミングに当てるのは困難ですので、やはり、予めシミュレーションで精密に検証しておく必要があるでしょう。
などと言いながら……。筆者はあまりまともな検証はしていないので、ご注意ください。ザっと、ポイントとなる動作を確認しただけで終了しています。趣味&DIYなので、「自分の使い方で不具合が起こらなければOK」と言う考え方ですm(1__)m。
将来、自分の使い方でバグが見つかったら、その都度、ちょいちょい修正して使うと思います。
新規に作成・変更したモジュール(CPU、INTC162、TIM162)のテストは、ザっとですがシミュレーションで確認済みです。本HPは、趣味&DIYの世界ですので、FPGAボードではいきなり実際のアプリケーションを動かしてみて、実機動作を確認してしまいます\(1^^)/。
※職業として設計する方々は、しっかりと検証をお願いします(1^^;)。
本HPでは何度目かの登場となりますが、アプリケーションはLEDキャンドルです。
Flash 2KバイトのATtiny2313では1,024点を押し込むのでやっと、だったのだけど、今回のMoscoviumマイコンは4KバイトROMを搭載しているので、2,048点くらいにできたでしょう……。
2,048点の1/fゆらぎ乱数テーブルを作っても良かったし、何ならROMマクロを作り直して8Kバイトに拡張したら、オリジナルの4,096点1/fゆらぎ乱数列だって載ったのだけど、面倒くさがりの悪い癖が……(1^^;)っ。ということで、ATtiny2313用に作成した1,024点1/fゆらぎ乱数列を使い回して、プログラムを組みました。
以下にリンクしておきます。
MCOC115用プログラム:mcoc_candle.asm
1,024点1/fゆらぎ乱数列データ:mcoc_candle.incl
MCOC115用インクルードファイル:mcoc115.incl
Moscoviumアセンブラ(asm_mcvm.olv)でアセンブルしてMCOC115のROMに設定しておくと、シリアルモニタから以下のコマンドを受け付けるようになっています。
リセット解除後のデフォルトで、LEDキャンドルがONしていますので、シリアルモニタ無しでも自立します。シリアルモニタで通信する場合には、こちらの接続を確立してください。
実験サンプル:【 】内はシリアルモニタからの送信データ
【col,0,32,255】 …… RGB LEDの発光色を変えてみました。
OK: col,0,32,255
今回のプログラム(mcoc_candle.asm)では、1/fゆらぎ乱数列の更新間隔の計測にTIM162のオーバフロウ割り込みを使い、同じくTIM162のPWM出力でLEDを駆動しています。当然、その機能を果たすためには、INTC162やCPU(Moscovium)の割り込み機能が正常動作する必要があります。
即ち、Moscoviumに割り込み機能を追加するに当たって、新規に設計または設計変更したモジュールを一通り使いました。検証としては全く不完全ではありますが、シミュレーションでの検証と合わせて、個人的には満足いたしました。
しかも、この規模(mcoc_candle.asm)のプログラムがそれらしく動いているので、CPU(Moscovium)の動作もだいぶ安定しているみたいです。
「FPGAボードを使って、マイコンを自作する」テーマで進めてきたシーズン15は、今回で一応決着です。長らくのお付き合いありがとうございました。ようやくマイコンが出来上がったところで、まだまだやりたいことはいっぱいあるので、以後、レギュラシーズンの中で引き続き遊んでみたいと思います。