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
のようにマクロ定義により行います。