9 |
BSVによるサウンドFSMの再設計 (10) |
Posts Tagged with "FPGA"
既に発行済みのブログであっても適宜修正・追加することがあります。We may make changes and additions to blogs already published.
9 |
BSVによるサウンドFSMの再設計 (9) |
OneStageソース
前稿で述べたルールの前後にインタフェース、レジスタ宣言、メソッド宣言を加えてソースが完成しました。
// OneStageのインタフェース定義 interface Fifo_ifc; (* prefix="" *) // GameFSMからの書き込み有効信号 method Action if_write_enable((* port="wr_en" *)Bool wen); (* prefix="rd" *) // 各SoundFSMからの読み取り有効信号 (0~3) method Action if_read_enable0(Bool en0); (* prefix="rd" *) method Action if_read_enable1(Bool en1); (* prefix="rd" *) method Action if_read_enable2(Bool en2); (* prefix="rd" *) method Action if_read_enable3(Bool en3); (* result="empty" *) // キューが空かどうかを返すメソッド method Bool if_empty(); endinterface // OneStageの実装 (* synthesize, always_ready, always_enabled *) module mkOneStage(Fifo_ifc); // 入力信号のレジスタ Reg#(Bool) in_wen <- mkReg(False); // 書き込み有効 Reg#(Bool) in_ren0 <- mkReg(False); // 読み取り有効 (SoundFSM0) Reg#(Bool) in_ren1 <- mkReg(False); // 読み取り有効 (SoundFSM1) Reg#(Bool) in_ren2 <- mkReg(False); // 読み取り有効 (SoundFSM2) Reg#(Bool) in_ren3 <- mkReg(False); // 読み取り有効 (SoundFSM3) // キューが空かどうかの状態 Reg#(Bool) in_empty <- mkReg(True); // キューへの書き込みルール rule rule_write (in_wen && in_empty); in_empty <= False; // キューを!emptyにする endrule // キューからの読み取りルール rule rule_read ((in_ren0 || in_ren1 || in_ren2 || in_ren3) && !in_empty); in_empty <= True; // キューをemptyにする endrule // 各メソッドの実装 method Action if_write_enable(Bool wen); in_wen <= wen; endmethod method Action if_read_enable0(Bool en0); in_ren0 <= en0; endmethod method Action if_read_enable1(Bool en1); in_ren1 <= en1; endmethod method Action if_read_enable2(Bool en2); in_ren2 <= en2; endmethod method Action if_read_enable3(Bool en3); in_ren3 <= en3; endmethod method Bool if_empty(); return in_empty; // キューが空かどうかを返す endmethod endmodule: mkOneStage
8 |
BSVによるサウンドFSMの再設計 (8) |
OneStageの変更
OneStageとはGameFSMとSoundFSMのインタフェースを行うモジュールです。元々FIFOだったのですが、FIFOの段数だけサウンドがレコードされていき、遅延して演奏されるようになったので、リアルタイム性のため1段のFIFO(=One Stage FIFO)としました。
前回Verilogで設計したものを今回BSVに移植します。ロジック的には大したことのないモジュールでVerilogで書いても工数はあまり変わりません。
図504.1にステート遷移図を示します。1段のキューを模擬する動作であり、
- emptyかつwriteの場合は!empty (=full)に遷移します。
- !empty (=full)かつreadの場合はemptyに遷移します。
通常、GameFSMが書き込むことにより!emptyとなった場合は、演奏途中でもemptyを見ているため、SoundFSMはすぐに読み出します。すぐにreadを返すためまたemptyとなります。
例外は自機増加音で、プリエンプション禁止であるため、!emptyとなってもreadが返りません。従って演奏が終わるまで!emptyのままとなります。出力側はemptyを見ているため、この場合は出力が待たされ取りこぼしが防止されます。
BSVでの記述
これはステートマシンなので、以前に示したように、
- ステートベース設計
- シーケンスベース設計
の2通りの設計手法があります。自動FSMによるシーケンスベース設計も可能ですが、あまりにも簡単なステートマシンなので、ステート遷移のルールを直に記述します。ren信号はread enableの意味です。
図504.1をBSVのルール文で書くと
rule rule_write (in_wen && in_empty); in_empty <= False; endrule rule rule_read ((in_ren0 || in_ren1 || in_ren2 || in_ren3) && !in_empty); in_empty <= True; endrule
となります。
- write_enable (in_wen)はキューへの書き込みのためのGameFSMからの出力です。
- read_enable (in_ren0~3)はキューからの引き取りのためのSoundFSMからの出力ですが、SoundFSMが4種類あるので、4つの信号となります。
別々のルールにおいて、同一資源であるin_emptyに書き込むのが若干気になります。スケジューリング時点でエラーが出るかもしれないと思いましたが、出ませんでした。これは、2つのルールがそれぞれemtpyと!emptyでガードされており、ルールに重なりが無いので、同一リソースへのライトハザードが起きないためでしょう。
「BSV (Bluespec SystemVerilog)を用いたサウンドFSMのシーケンスベースによる再設計」と題する記事をQiitaに投稿しました。
前回のBSVによる設計ではGameFSMとSoundFSMで敢えて異なる設計手法としましたが、今回はSoundFSMもシーケンスベース設計に変更しました。その理由は、ステートベース設計ではステート分解を人力で行うため、高級言語のメリットがあまり出ないためです。さらに、同じ機能をステートベース設計とシーケンスベース設計とで設計してみて、結果の違いを見たかったためです。
6 |
BSVによるサウンドFSMの再設計 (7) |
BSVソース
完成したBSVのソースを貼り付けます。1つのソースで4種のFSMを合成し分けているため、やや複雑になっています。
SoundFSM.bsv: // ステートマシンライブラリのインポート import StmtFSM::*;
// サウンドコードの定義 `define SOUND1_ON 1 // 自弾発射音_ON `define SOUND2_ON 2 // 自機爆発音_ON `define SOUND3_ON 3 // インベーダ爆発音_ON `define SOUND4_ON 4 // インベーダ歩行音1_ON `define SOUND5_ON 5 // インベーダ歩行音2_ON `define SOUND6_ON 6 // インベーダ歩行音3_ON `define SOUND7_ON 7 // インベーダ歩行音4_ON `define SOUND8_ON 8 // UFO爆発音_ON `define SOUND9_ON 9 // 自機増加音_ON `define SOUND10_ON 10 // UFO飛行音_ON `define SOUND10_OFF 11 // UFO飛行音_OFF `define NULL 'h80 // 無音 // 各FSMが起動する条件を定義 `define COND_FSM0 !emptyf && (code == `SOUND1_ON || code == `SOUND2_ON || code == `SOUND9_ON) `define COND_FSM1 !emptyf && (code == `SOUND3_ON) `define COND_FSM2 !emptyf && (code == `SOUND4_ON || code == `SOUND5_ON || code == `SOUND6_ON || code == `SOUND7_ON) `define COND_FSM3 !emptyf && (code == `SOUND8_ON || code == `SOUND10_ON || code == `SOUND10_OFF) // 型定義 typedef UInt#(15) Addr_t; // アドレス型 typedef UInt#(8) Data_t; // データ型 typedef Bit#(4) Code_t; // サウンドコード型 // インターフェース定義 interface FSM_ifc; method Action sound(Code_t code); // サウンドコードを受け取る method Action rom_data(Data_t indata); // ROMデータを受け取る method Action sync(Bool lrclk); // シンクロ信号を受け取る method Action empty(Bool flag); // FIFOが空かどうかのフラグを受け取る method Addr_t rom_address(); // 現在のROMアドレスを返す method Data_t sdout(); // 現在のサウンドデータを返す method Bool soundon(); // サウンドがONかどうかを返す method Bool fifo_ren(); // FIFOから読み出し可能かどうかを返す endinterface // サウンドFSMの生成 (* synthesize,always_ready,always_enabled *) `ifdef FSM0 module mkSoundFSM0(FSM_ifc); `elsif FSM1 module mkSoundFSM1(FSM_ifc); `elsif FSM2 module mkSoundFSM2(FSM_ifc); `elsif FSM3 module mkSoundFSM3(FSM_ifc); `endif // ワイヤとレジスタの宣言 Wire#(Code_t) code <- mkWire, // サウンドコード用のワイヤ current <- mkRegU; // 現在のサウンドコード用のレジスタ Wire#(Bool) lrclk <- mkWire; // 左右クロック用のワイヤ Reg#(Data_t) romdata <- mkRegU, // ROMデータ用のレジスタ data <- mkRegU, // データ一時保存用 dout <- mkReg(`NULL); // 出力用データ Reg#(UInt#(32)) workd <- mkRegU; // 作業用データ Reg#(UInt#(15)) dcount <- mkRegU; // データカウント用 Reg#(Addr_t) worka <- mkRegU, // 作業用アドレス romaddr <- mkRegU, // ROMアドレス用 addr <- mkRegU; // 一時アドレス用 Reg#(UInt#(8)) ii <- mkReg(0); // ループカウンタ Reg#(Bool) son <- mkReg(False), // サウンドONフラグ sonEarly <- mkReg(False), // 早期サウンドONフラグ ren <- mkReg(False), // 読み取り許可フラグ emptyf <- mkReg(True); // 空フラグ // FSM3専用のUFOフラグ `ifdef FSM3 Reg#(Bool) fUFO <- mkReg(False); `endif // サブ関数:メモリからデータを読み取る // READ MEM // input: worka // output: romdata; function Stmt readmem; return (seq addr <= worka; // アドレスをセット noAction; // アクションなし(データセットアップタイム) data <= romdata; // データを読み取る endseq); endfunction // サブ関数:カウント値を読み取る // READ COUNT // input: romaddr // output: (romaddr,...,romaddr+3) => dcount; // romaddr + 4 => romaddr; function Stmt readcount; return (seq workd <= 0; for (ii <= 0; ii <= 3; ii <= ii + 1) seq worka <= romaddr + extend(3-ii); readmem; // メモリからデータを読み取る if (ii == 3) dcount <= truncate(workd<<8) | extend(romdata); else workd <= workd<<8 | extend(romdata); endseq romaddr <= romaddr + 4; // アドレスを更新 endseq); endfunction // メインのステートマシン Stmt main = seq while(True) seq // 初期化アクション action dout <= `NULL; sonEarly <= False; son <= False; ren <= False; endaction // 条件に応じて待機 `ifdef FSM0 await(`COND_FSM0); action ren <= True; current <= code; endaction `elsif FSM1 await(`COND_FSM1); action ren <= True; current <= code; endaction `elsif FSM2 await(`COND_FSM2); action ren <= True; current <= code; endaction `elsif FSM3 await(`COND_FSM3 || fUFO); // FSM3はUFOフラグも考慮 if (`COND_FSM3) action fUFO <= (code == `SOUND10_ON); // UFOフラグをセット ren <= True; current <= code; endaction else if (fUFO) action current <= `SOUND10_ON; // UFOフラグがTrueならUFO音を継続 endaction `endif // FIFOが空でないことを確認 await(emptyf); ren <= False; // UFO音のオフコマンド処理(FSM3専用) `ifdef FSM3 if (code == `SOUND10_OFF) continue; `endif // LRクロックのエッジにシンクロ await(lrclk); await(!lrclk); delay(4); // サウンドコードに基づいてROMアドレスを設定 action case (current) `ifdef FSM0 `SOUND1_ON: romaddr <= 0 + 16; `SOUND2_ON: romaddr <= 3422 + 16; `SOUND9_ON: romaddr <= 16150 + 16; `elsif FSM1 `SOUND3_ON: romaddr <= 0 + 16; `elsif FSM2 `SOUND4_ON: romaddr <= 0 + 16; `SOUND5_ON: romaddr <= 1266 + 16; `SOUND6_ON: romaddr <= 2836 + 16; `SOUND7_ON: romaddr <= 4406 + 16; `elsif FSM3 `SOUND8_ON: romaddr <= 0 + 16; `SOUND10_ON: romaddr <= 25968 + 16; `endif endcase endaction // カウント値を読み取り、次のROMアドレスを計算 readcount; romaddr <= romaddr + extend(dcount) + 4; // 再度カウント値を読み取り、ROMアドレスを調整 readcount; romaddr <= romaddr - 1; // サウンドデータの再生 while (!((dcount == 0) || `ifdef FSM0 (`COND_FSM0 && current !=`SOUND9_ON))) seq `elsif FSM1 (`COND_FSM1)))seq `elsif FSM2 (`COND_FSM2))) seq `elsif FSM3 (`COND_FSM3))) seq `endif if (sonEarly == False) seq readmem; // データ読み出し(3 clock) action sonEarly <= True; son <= False; // サウンドオフ dout <= `NULL; // データ無効 endaction endseq else seq readmem; // データ読み出し(3 clock) action son <= True; // サウンドオン dout <= romdata; // データ出力 endaction endseq delay(11); // readmemが3クロック、その次のactionが1クロックで計4クロック。 // さらにdelay()後の終端処理の1クロックを加えて、whileループが16クロックに // なるように11クロック遅延を挿入 action romaddr <= romaddr + 1; worka <= romaddr + 1; dcount <= dcount - 1; endaction endseq // UFOフラグをリセット(FSM3専用) `ifdef FSM3 if ((code == `SOUND8_ON || code == `SOUND10_OFF) && !emptyf) fUFO <= False; `endif endseq endseq; // 自動ステートマシン生成 mkAutoFSM(main); // メソッド実装 method Action sound(Code_t incode); code <= incode; endmethod method Action rom_data(Data_t indata); romdata <= indata; endmethod method Addr_t rom_address(); return addr; endmethod method Data_t sdout(); return dout; endmethod method Bool soundon(); return son; endmethod method Action sync(Bool inlrclk); lrclk <= inlrclk; endmethod method Bool fifo_ren(); return ren; endmethod method Action empty(Bool flag); emptyf <= flag; endmethod `ifdef FSM0 endmodule: mkSoundFSM0 `elsif FSM1 endmodule: mkSoundFSM1 `elsif FSM2 endmodule: mkSoundFSM2 `elsif FSM3 endmodule: mkSoundFSM3 `endif
これをverilogに合成するには、FSM0であれば、
bsc -verilog -D FSM0 SoundFSM.bsv
のようにマクロ定義により行います。
5 |
BSVによるサウンドFSMの再設計 (6) |
サウンドデータ修正手順
ゲームサウンドの改良のため、サウンドROMの内容を修正します。サウンドROMの作成法は記載していなかったので、その備忘のためでもあります。以下に手順の大略を示します。
- オリジナルサウンドを(http://www.classicgaming.cc/classics/space-invaders/sounds)で入手する。Player shootingとKilled Space Invaderのサウンドの表示が誤って入れ替わっているようです。
- Windows上のサウンドツールである Audacityにより適宜音量を調整する。そのままだと歩行音とのバランスが悪く歩行音が聞こえにくいため、歩行音以外の音量を下げて平均化する。ただし後述のように、頻繁に鳴る音でなければ多少大き目でも良い。
- waveを8bitに圧縮変換する。コマンドは
ffmpeg -i input.wav -ac 1 -ar 11025 -acodec pcm_u8 -fflags +bitexact -flags:v +bitexact -flags:a +bitexact output.wav
ここで、拡張子に.wavが必要。
- サウンド毎のwaveバイナリが作成されたので、チャネル毎にwaveをcatでまとめ、ROMイメージのバイナリファイルを作成する。
Ch.0 --- Code 1, 2, 9
Ch.1 --- Code 3
Ch.2 --- Code 4, 5, 6, 7
Ch.3 --- Code 8, 10 - wcによりエントリアドレスを調べておく。エントリアドレスは、直前のROM累積サイズ+16。
- make_coe(以下)コマンドにより、バイナリからVivadoの読めるCOEファイルに変換する。
export input=\$1.bin
export output=\$1.coe
echo 'memory_initialization_radix=16;' > \$output
echo -n 'memory_initialization_vector=' >> \$output
od -An -t x1 -v < \$1.bin >> \$output
echo ';' >> $output
修正点
- Code2 (自機爆発音) ---- 若干延長してフェードアウトエンベロープを加えた。
- Code1 (自機弾発射音), 3 (インベーダ爆発音), 9 (自機増加音) ---- 歩行音とのバランスで音量を下げすぎたため、音量を若干上げた。
頻繁に鳴る音、例えばインベーダ破壊音が大きめだとうるさく感じますが、まれな音、例えば自機増加音や自機破壊音は大きめでも問題ないことがわかりました。ゲームバランスと言われますが、音量にもバランスがあるようです。
修正後のサウンドROM構造
Channel | Code | Sound | Start | Size [bytes] | Entry=Start+16 |
---|---|---|---|---|---|
自機音 チャネル(#0) |
1 | 自機弾発射音 | 0 | 3,422 | 0+16 |
2 | 自機爆発音 | 3,422 | 12,728 | 3,422+16 | |
9 | 自機増加音 | 16,150 | 5,500 | 16,150+16 | |
合計 [bytes] (32KB ROM使用率) | 21,650 (66%) | ||||
インベーダ音 チャネル(#1) |
3 | インベーダ爆発音 | 0 | 4,622 | 0+16 |
合計 [bytes] (32KB ROM使用率) | 4,622 (14%) | ||||
インベーダ音 チャネル(#2) |
4 | インベーダ歩行音 1 | 0 | 1,266 | 0+16 |
5 | インベーダ歩行音 2 | 1,266 | 1,570 | 1,266+16 | |
6 | インベーダ歩行音 3 | 2,836 | 1,570 | 2,836+16 | |
7 | インベーダ歩行音 4 | 4,406 | 2,180 | 4,406+16 | |
合計 [bytes] (32KB ROM使用率) | 6,586 (20%) | ||||
UFO音 チャネル(#3) |
8 | UFO爆発音 | 0 | 25,968 | 0+16 |
10 | UFO飛行音 | 25,968 | 1,846 | 25,968+16 | |
合計 [bytes] (32KB ROM使用率) | 27,814 (85%) |
ROMのエントリポイントは上記のとおりStartアドレスから+16となります。
25 |
BSVによるサウンドFSMの再設計 (5) |
プチ音の再修正
ゲームFSMからのコマンドにより、サウンドはサウンドFSMで演奏されます。サウンドとサウンドの間は、サウンドが鳴り続けないように、一旦OFFにします。
シミュレーション波形を観測している際に、UFO飛行音は連続して再生されるので、一旦サウンドをOFFにするとプチ音がするのではないかと思い、ONを継続するように変更したところ、却ってプチ音がするようになりました。
確かにステートベース設計の旧版では一旦サウンドをOFFにしていたため、そのように戻したところ、プチ音は聞こえなくなりました。
修正前はson=False、dout=NULLのためにプチ音が鳴っていると思い、son=True, dout=前データを継続としましたが、却ってプチ音が鳴るようです。波形は再修正後です。
サウンドFSM(UFO音チャネル#3)のアルゴリズム
フラグのON/OFFアルゴリズムを表500.1にまとめました。
No. | コード | キュー | ハンドシェーク(rd_en ) | サウンド間 | フォーマット&演奏 |
---|---|---|---|---|---|
1 | UFO爆発, UFO飛行音 | !empty | rd_en <= True | son <= False | Play |
2 | UFO飛行音 OFF | !empty | rd_en <= True | son <= False | - |
3 | UFOフラグ | - | rd_en <= False | son <= False | UFO飛行音Play |
4 | 他FSMコード | !empty | rd_en <= False | son <= False | - |
上記でプチ音対策で修正した箇所は表500.1の緑色表示の部分です。一旦son <= TrueとONを継続するように修正しましたが、OFFに戻しました。
まず、通常のサウンドNo.1及び3は以下のフローとなります。
- GameFSMはempty == Trueの場合にコードを出力し、同時にOneStageFSMに対してwr_enを発行します。
- OneStageFSMはempty = False(=Full)とします。
- SoundFSMは正当コードかつempty == False (=Full)を待ち、入力されたらrd_en = TrueをOneStageFSMに返します。
- OneStageFSMはempty = Trueとします。
- GameFSMはempty == Trueを待ち、入力されたら終了です。
- SoundFSMもempty == Trueを待ち、入力されたらrd_en = Falseとします。
- SoundFSMはUFOオフコマンドならUFOフラグをOFFして終了します。
- SoundFSMはUFOオフコマンドでなければフォーマットをデコードします。
- SoundFSMはサウンドをカウント分演奏します。
次に、通常のサウンドで無い場合No.2は、UFOフラグがTrueの時であり、GameFSMのコードがキューに無くてもUFO飛行音を演奏し続けます。
- GameFSMはなにもしません。
- SoundFSMはUFOフラグ==Trueの場合には、実行しますがrd_en = Trueは返しません。これはキューに何もないためです。
- SoundFSMは内部的にはUFO飛行音と扱います。
- SoundFSMはフォーマットをデコードします。
- SoundFSMはサウンドをカウント分演奏します。
22 |
BSVによるサウンドFSMの再設計 (4) |
サウンドFSM毎に固有のROMを持ちますが、ROMとアクセスアドレス、データの関係表です。各コードは3段のエントリとなっており、それぞれフォーマット格納アドレス、フォーマット長、データ格納アドレス、データ長、サウンド格納アドレス、サウンドデータの順となっています。
FSM No. | コード | アドレス | データ |
---|---|---|---|
自機音 チャネル(#0) |
1 | 0013, 0012, 0011, 0010 | 00,00,00,10 |
002b, 002a, 0029, 0028 | 00,00,10,b1 | ||
002c,002d,002e,002f,... | 81,81,82,83,... | ||
2 | 10f1,10f0,10ef,10ee | 00,00,00,10 | |
1109,1108,1107,1106 | 00,00,22,1b | ||
110a,110b,110c,110d,... | 7b,79,7b,79,... | ||
9 | 3339,3338,3337,3336 | 00,00,15,4f | |
3351,3350,334f,334e | 00,00,10,b1 | ||
3352,3353,3354,3355,... | 7c,7c,7b,7c,... | ||
インベーダ音 チャネル(#1) |
3 | 0013, 0012, 0011, 0010 | 00,00,00,10 |
002b, 002a, 0029, 0028 | 00,00,11,e1 | ||
002c,002d,002e,002f,... | 83,85,84,81,... | ||
インベーダ音 チャネル(#2) |
4 | 0013, 0012, 0011, 0010 | 00,00,00,10 |
002b, 002a, 0029, 0028 | 00,00,04,c5 | ||
002c,002d,002e,002f,... | 76,77,74,74,... | ||
5 | 0505,0504,0503,0502 | 00,00,00,10 | |
051d,051c,051b,051a | 00,00,05,f6 | ||
051e,051f,0520,0521,... | 7a,78,78,77,... | ||
6 | 0b27,0b26,0b25,0b24 | 00,00,00,10 | |
0b3f,0b3e,0b3d,0b3c | 00,00,05,f6 | ||
0b40,0b41,0b42,0b43,... | 7a,7b,7a,7a,... | ||
7 | 1149,1148,1147,1146 | 00,00,00,10 | |
1161,1160,115f,115e | 00,00,08,58 | ||
1162,1163,1164,1165,... | 78,7a,77,78,... | ||
UFO音 チャネル(#3) |
8 | 0013, 0012, 0011, 0010 | 00,00,00,10 |
002b, 002a, 0029, 0028 | 00,00,65,43 | ||
002c,002d,002e,002f,... | 7e,78,7b,7b,... | ||
10 | 6583,6582,6581,6580 | 00,00,00,10 | |
659b,659a,6599,6598 | 00,00,07,0a | ||
659c,659d,659e,659f,... | 99,86,7a,7a,... |
バスアクセスのシミュレーションエラーが出たため、まとめておこうと思い上記の表を作成しましたが、エラーの原因はROMの内容が古かったためでした。
19 |
BSVによるサウンドFSMの再設計 (3) |
テストケース
設計で最重要なのがテストケース作成です。バグの無い設計のためには、テストケースを網羅的に作成して検証する必要があります。
FSM No. | テストケース | Pass/Fail | ||
---|---|---|---|---|
No. | 内容 | V1 (State) | V2 (Seq.) | |
自機音 チャネル(#0) |
1 | CODE1演奏中にCODE2がプリエンプト可能なこと | Pass | Pass |
2 | CODE1演奏中にCODE9がプリエンプト可能なこと | Pass | Pass | |
3 | CODE9演奏中にCODE1がプリエンプト不可能なこと (自機増加音が妨げられないこと) |
Pass | Pass | |
4 | CODE3を無視すること | Pass | Pass | |
インベーダ音 チャネル(#1) |
5 | CODE3演奏中にCODE3がプリエンプト可能なこと (実際には起こらないため不要) |
Pass | Pass |
6 | CODE1を無視すること | Pass | Pass | |
インベーダ音 チャネル(#2) |
7 | CODE4演奏中にCODE5がプリエンプト可能なこと | Pass | Pass |
8 | CODE5演奏中にCODE6がプリエンプト可能なこと | Pass | Pass | |
9 | CODE1を無視すること | Pass | Pass | |
UFO音 チャネル(#3) |
10 | CODE10演奏中にCODE8がプリエンプト可能なこと | Pass | Pass |
11 | CODE10演奏後にCODE10OFFが来るまで演奏を継続すること | Pass | Pass | |
(12) | (11で)CODE10演奏中にプチプチ音が鳴らないこと | Pass | Pass | |
13 | CODE10演奏中にCODE8がプリエンプトし最後にOFFになること | Pass | Pass | |
14 | CODE10演奏中にCODE8がプリエンプトしOFFがプリエンプト した後OFFになること |
Pass | Pass | |
15 | CODE1を無視すること | Pass | Pass |
プログラムの対称性に鑑み、最小必要パターンのみとしました。パターンは必要に応じ、随時追加削除していきます。
18 |
BSVによるサウンドFSMの再設計 (2) |
チャネルとコードの対応
サウンドの説明 | CODE番号 | サウンドチャネル | |
---|---|---|---|
ON | OFF | ||
自機弾発射音 | 1 | ー | 自機音チャネル(#0) |
自機爆発音 | 2 | ー | |
自機増加音 | 9 | ー | |
インベーダ爆発音 | 3 | ー | インベーダ音チャネル(#1) |
インベーダ歩行音1 | 4 | ー | インベーダ音チャネル(#2) |
インベーダ歩行音2 | 5 | ー | |
インベーダ歩行音3 | 6 | ー | |
インベーダ歩行音4 | 7 | ー | |
UFO爆発音 | 8 | ー | UFO音チャネル(#3) |
UFO飛行音 | 10 | 10+16 |
チャネル起動条件
それぞれのサウンドチャネルは別のFSMにより駆動されます。それらの起動条件を上げると、
- チャネル0(FSM0) ---- (CODE1_ON || CODE2_ON || CODE9_ON) && !empty
- チャネル1(FSM1) ---- CODE3_ON && !empty
- チャネル2(FSM2) ---- (CODE4_ON || CODE5_ON || CODE6_ON || CODE7_ON) && !empty
- チャネル3(FSM3) ---- ((CODE8_ON || CODE10_ON || CODE10_OFF) && !empty) || UFO
- 例外処理: チャネル3においてはCODE10_ONの場合、OFFが来るまで演奏し続けるようにUFOのフラグを立てますが、起動時にUFOフラグだった場合はコマンドが無くてもCODE10_ONとして起動します。
チャネル終了条件
それぞれのサウンドチャネルは演奏を終了することがあります。カウンタが0になる場合またはプリエンプションです。具体的な終了条件(もしくは割り込み条件)を上げると、
- カウンタが0になる (|| 以下のプリエンプション)
- チャネル0(FSM0) ---- ((CODE1_ON || CODE2_ON || CODE9_ON) && !empty) && (current != CODE9_ON)
- 例外処理: チャネル0(FSM0) ---- CODE9_ONの場合はプリエンプション禁止のため、currentを見てCODE9_ON(自機増加中)だったら割り込まないようにします。
- チャネル1(FSM1) ---- CODE3_ON && !empty
- チャネル2(FSM2) ---- (CODE4_ON || CODE5_ON || CODE6_ON || CODE7_ON) && !empty
- チャネル3(FSM3) ---- (CODE8_ON || CODE10_ON || CODE10_OFF) && !empty
- 全チャネル(FSM0~3) --- コードがある場合はキューから取り出し先頭に戻ります。
このように、起動条件の例外はUFOフラグが立っている場合であり、終了(割り込み)条件の例外は自機が増加中に割り込まない場合です。
ページ: