本HPではMML(Music Macro Language)を使って音楽を演奏するソフトウェアを作ってきました。
Arduinoマイコンボードではハードウェアがギブンなので、マイコンの汎用PWMタイマや割り込み機能などを駆使して、ソフトウェアで工夫しながら実装してきた、のが実態です。
Cmod A7ボード(FPGA)を使えば、音楽演奏に特化したハードウェアの設計が可能なので、MMLを使った音楽演奏を容易に実現可能なハードウェアの設計が可能です。これこそFPGAの醍醐味ですよ。
前回までにサウンドジェネレータSNDG1PBユニットの基本設計が完了していますので、今回はその続きから始めて、実装までこぎ着けようと思います。
ハードウェア設計側の事情で、いくつか、MMLの仕様に制限が生じました。以下、ご確認ください。
SNDG1PBユニットに於けるMMLの仕様制限:
いずれも、ソフトウェアや使い方で回避可能な仕様制限だと思われます。
ここで言う実時間とは、私たちが普段暮らしている中で経過している、1日24時間、1時間3,600秒、等という実際の時間のことを指します。
一方、PCのCPUや、マイコンのMCU等は、システムクロックが神様となって時間が経過しています。システムクロック周波数が固定されている場合でも、マイコンの時間軸は実時間とは無関係に経過します。増して、システムクロック周波数が可変である場合、システムクロックとして供給されるクロック周波数次第で、実時間との関係が変化します。
今回のSNDG1PBユニットでは、二者の間に存在する溝を埋める必要があります。もう一歩踏み込んでおくと、「実時間で動作することが望ましい」です。
現時点でMoscoviumマイコンは、システムクロック周波数fcpu=24MHzに固定されています。fcpu=24MHzのクロックで24回カウントすると、1μsの時間(実時間)を計測することができます。
24カウント×(1÷24MHz)=1μs
もし将来、fcpuを変更して16MHzやら、32MHzやら、異なるクロック周波数のMoscoviumマイコンを作った場合、fcpuに応じて1μsを計測するためのカウント数が変化する事になります。
現在のシステムクロック周波数fcpuが判れば、実時間を計測可能だ、と言う事です。
続いて、SNDG1PBユニットが実時間で動作する必要性についての考察です。
音楽を演奏するという事象に対して、それぞれの音には、音階毎に決まった周波数があります。音の演奏時間(4分音符や8分音符など)も、実時間で管理する必要があります。
仮に、fcpu=24MHzであることを前提として、決まった周波数と決まった長さで演奏する回路を「決め打ち」で作った場合を考えてみましょう。
SNDG1PBユニットとしては、fcpuが変化しても、同じ音程、同じ長さで演奏できる事が望ましいのです。
この課題を解決する手段としては、まず何らかの方法で現在のシステムクロック周波数を知る必要があります。
SNDG1PBユニットの外部から、現在のクロック周波数をBCD(Binary Coded Decimal)形式で指定できるようなインターフェイスにしておきます。
wire [7:0] clk_mhz=8'h24;
上のコード例はfcpu=24MHzの場合です。もし、fcpu=32MHzに変更する場合は、clk_mhz[7:0]=8'h32です。
clk_mhz[7:0]信号について基本的には、動作中変化しない固定値を推奨します。ただし仕様上は、動的な変更も可能だとしておきます。回路動作中に値を変更した場合は、変更前後で動作に不整合が生じるため、それを回避する方法はソフトウェア側で提供する必要があります。
SNDG1PBユニット内の実装としては、fcpuでカウントアップするタイマを1個作成します。
BCDタイマ実装例) Verilog-HDL
// micro second BCD prescaler
reg [7:0] cntbcd;
wire [7:0] cntbcd_inc;
wire ovfbcd=( cntbcd_inc[7:0]==clk_mhz[7:0] );
always @(posedge clk)
begin
if (!rst_n || ovfbcd)
cntbcd[7:0]<=8'h0;
else
cntbcd[7:0]<=cntbcd_inc[7:0];
end
assign cntbcd_inc[7:0]=
(cntbcd[7:0]==8'h99)?
8'h0:
(cntbcd[3:0]==4'h9)?
{ cntbcd[7:4] + 4'h1,4'h0 }:
{ cntbcd[7:4],cntbcd[3:0] + 4'h1 };
clk_mhz[7:0]がBCDなので、上のタイマもBCDタイマとなっています。8'h00〜8'h09までカウントしたら、次のクロックで8'h10となり、8'h10〜8'h99までカウントして、8'h00に戻ります。BCDタイマなので、各桁の数値が4'ha〜4'hfを示す事はありません。
タイマカウンタcntbcd[7:0]がclk_mhz[7:0]と一致した時点で、1μsの実時間が経過したという事なので、cntbcd[7:0]を8'h0にリセットすると同時に、次段のマイクロ秒タイマをカウントアップします。
これで、実時間(マイクロ秒)を計測することができます。ここまで出来たら、1,000μsで1ms、1,000,000μsで1s(または、1,000msで1s)の実時間を順次計測可能です。
色々考えた結果、clk_mhz[7:0]はBCDで指定する事に決めました。バイナリではなくBCDにした理由は、「10進数の桁シフトが容易だから」です。
マイクロ秒をカウントするのは、8'h0〜clk_mhz[7:0]までをfcpuでBCDカウントすれば良いことは前節にて議論済みです。では、ミリ秒はどうでしょうか?ミリ秒をカウントするには、clk_mhz[7:0]を×1,000倍して、20'h0〜(clk_mhz[7:0]×1,000−1)までカウントする事になります。
定数とは言え、掛け算(×1,000)が必要です。RTLで記述すると、恐らく乗算器マクロを推論してくるでしょう。ですが、BCDを採用したことで、clk_mhz[7:0]の×1,000倍は、以下の様な組み合わせ論理で演算可能です。
wire clk_khz[19:0]={ clk_mhz[7:0],12'h000 };
ただし、計算結果clk_khz[19:0]も、BCDとなります。
もし、clk_mhz[7:0]のバイナリ値が必要なケースがあっても、以下の算数にて変換可能です。
RTLだと、以下の組み合わせ論理に相当します。
wire [6:0] clk_mhz_bin={ clk_mhz[7:4],3'h0 } + { 2'h0,clk_mhz[7:4],1'b0 } + { 3'h0,clk_mhz[3:0] };
7ビット加算器が×2個なので、論理規模増加に対するインパクトは小さいです。
逆に、バイナリからBCDへの変換では剰余算(割り算)が必要になり、RTLの組み合わせ論理で実現するのは困難となります。
組み合わせ論理に関して見ると、バイナリ→BCD変換は困難なのに対して、BCD→バイナリ変換は容易なので、どちらにも対処可能なBCDを選んだのが背景です。
今まで作って来たI/Oユニットの中で、実時間対応が必要なユニットは、以下の2つでした。
SYSTIMユニットに関しては、前節で解説したclk_mhz[7:0]信号を引き込んでおり、対応済みです。
RTC400ユニットは、そもそも、Cmod A7ボード(FPGA)に乗り換えた後、未対応(未リリース)です。
未対応の理由は、RTC400ユニットの内部にfcpu=24MHzから疑似32,768Hzクロックを作り出す非整数比分周器があるのだけど、これを可変周波数に対応するのが困難だったためです。
通常のTIM162ユニット等では、そもそも周期レジスタなどの仕様がシステムクロック周波数を基準としたクロック数なので、fcpuが変動すればタイマの周期が変動する事は明らかで、それが仕様です。従って、実時間対応は不要なユニットであると結論できます。
IDREGユニットのidrgfcpuレジスタからfcpu値をkHz単位で読み出し可能なので、これを使って周期を調整する事はソフトウェアによって対処可能です。
UART8N1ユニット等々、その他のユニットも同じ考えで、実時間対応は不要です。
本節で議論してきたように、実時間に対応したユニットは中々面倒な処置が要求されるため、厳に必要なユニットに限定すべきです。今回のSNDG1PBユニットに実時間対応が必要な事は、本節最初の議論で明確です。
この先の実験に使える用に、SNDG1PBユニットを2チャンネル搭載したMBOC115GS3208マイコンを作っておきました。
動作確認の実験に必要な機能:
開発環境としてBlockly for Moscoviumを使う予定なので、CPUにはMoscovium-BSを選択し、念のためH-FPUを搭載して、かつROMリッチな設定としてあります。その他は最小限の機能だけを搭載しているので、コンパクトなマイコンに収まっています。
Cmod A7ボード等、XilinxのFPGAに書き込むのに必要な、Moscoviumマイコンの設計データ(Vivadoのプロジェクトファイル一式を含む)はGitHubで提供しています。以下バナーよりアクセスしてください。
デザイン(回路)をCmod A7ボードに書き込む手順については、こちらをご確認ください。
SNDG1PBユニット搭載MBOC115GS3208マイコンのコンパイル(論理合成・配置配線)結果を以下に示します。
コンパイル結果:
SNDG1PBユニット単体(1チャンネル)では、200LUT(+130FF)程の論理規模でした。普通の汎用16ビットタイマTIM162ユニットが120LUT(+150FF)程なので、妥当な論理規模だと考えられます。
Cmod A7ボードの外部に、パッシブブザーを駆動するための外付け回路が必要です(下図、参考回路例)。
上図には、2種類の回路例を示しています。左半分の回路が推奨回路、右半分は参考回路です。
パッシブブザーは電磁的な素子なので、逆起電力やサージ電圧からマイコン端子を保護するための回路が必要です。詳しくは、こちらの解説をご覧ください。
SNDG1PBユニットを2チャンネル使うなら、上図左側の推奨回路を2組作成してください。
この回路は、2組同じ定数で回路を作り込んでも、同じ音量になるとは限りません。パッシブブザーやNPNトランジスタの個体差(hFE等)によって、流れる電流に差が生じるためと考えられます。2組の回路で同じ音階を演奏させてみて、ほぼ同じ音量となる様にR3を調整してください。
また、この回路はパッシブブザーに十分な電流を流せるため、大きな音を鳴らすことができます。音楽を演奏しようとする場合、その大きな音は逆に耳障りでしかないので、同じくR3の値で、丁度良い音量に調整することもお忘れなく。
この回路自体は、Arduino UNOやDUE等、他のマイコンボードで音楽演奏する場合にも流用可能です。ミニブレッドボードなどに作り込んで保持しておけば、毎回使う度に組み立てる手間を省けます。
今回のお買い物は以下の通りです。
今回の工作で、パッシブブザーが複数個必要だったのですが、手元には1個しか無かったので追加購入しました。Amazonの中華ショップで、配送料無料でした。
また、ブレッドボードに回路を作る時、U字ジャンパ線はとても重宝する部品です。特に短いジャンパ線は使用頻度が高いため、数が足りなくなってしまいました。そこでU字ジャンパ線も、こちらのI2C EEPROMモジュールなどと一緒に購入しておきました。
1個目のU字ジャンパ線(同等仕様)を購入したのは、こちらの記事でした。当時の263円と比べて、だいぶ高くなった印象です(💴×2倍以上!)。
これも政府与党の無策による物価高の影響(ブーイング)ですよ。すべて頭(総裁)の性にしてケツまくろうとしてるみたいですが騙されてはダメです。腐っているのは頭だけではなく、全身(自民党)なんですから。現に物価高は、なお進行中で収まる気配も見えず、与党内の誰一人として有効な対策を打とうとしていません。
次回、ソフトウェア開発環境の整備〜実験まで終わらせて、テーマを閉じたいと思います。