![]() |
4 |
メモリをBSVで書く (4) |
3点目 サウンドROMをmkSoundROMにする

図1135.1 Sound ROM書き換え前後
サウンドは4チャネルあり、チャネルごとに手書きのsndch.vがFSMのmkSoundFSMnとROMのrom.vを並べ、rom_addressとrom_dataの2本で結んでいました。パターンROMと同じ形にして、ROMをmkSoundROMnという別モジュールにし、FSMがその中に置いてメソッドで呼ぶようにしました。FSMはWAVを読んでPCMを出す機能、ROMは内容を持つ機能で、機能が別なので別モジュールのままです。
書き換え前のsndch.vはCHに加えてDEPTHとFILEをパラメータに取り、sound.vがチャネルごとに語数とファイル名を渡していました。
module sndch #(
parameter CH = 0,
parameter DEPTH = 21650,
parameter FILE = "ch00.mem"
) (
rom #(.WIDTH(8), .AWIDTH(15), .DEPTH(DEPTH), .FILE(FILE)) u_rom (
.clk (clk),
.addr (rom_addr),
.q (rom_q)
);
sndch #(.CH(0), .DEPTH(21650), .FILE("ch00.mem")) u_ch0 (
FSMの側は、アドレスをレジスタaddrに置いてrom_addressポートから出し、rom_dataポートから毎周期romdataレジスタに取り込んでいました。
書き換え前
interface FSM_ifc;
method Action sound(SoundCode_t incode);
method Action rom_data(Data_t indata);
method Action sync(Bool lrclk);
method Action empty(Bool flag);
method Addr_t rom_address();
method Data_t sdout();
method Bool soundon();
method Bool fifo_ren();
endinterface
Reg#(Data_t) romdata <- mkRegU, // ROM 読み出しデータ(毎サイクル入力)
dout <- mkReg(`NULL); // PCM 出力(無音=`NULL)
Reg#(UInt#(32)) workd <- mkRegU;
Reg#(UInt#(15)) dcount <- mkRegU; // 残りサンプル数(WAV data チャンクサイズ)
Reg#(Addr_t) worka <- mkRegU, // readMem への引数(読みたい番地)
romaddr <- mkRegU, // WAV 読み出しポインタ
addr <- mkReg(0); // rom_address ポートへの出力値
function Stmt readMem;
return (seq
addr <= worka;
noAction;
noAction;
endseq);
endfunction
if (ii == 3) dcount <= truncate(workd<<8) | extend(romdata);
else workd <= workd<<8 | extend(romdata);
method Action rom_data(Data_t indata);
romdata <= indata;
endmethod
method Addr_t rom_address();
return addr;
endmethod
書き換え後は、SoundFSM.bsvの末尾にROMのモジュールを足しました。FSMと同じ-D FSM0から3で作り分け、語数とファイル名は変種ごとにここで決まります。
// =================================================================
// SoundROM: 効果音 ROM(1ソース4変種)
// FSM と同じ -D FSM0〜FSM3 で mkSoundROM0〜3 を作り分け、語数と
// ファイル名を変種毎に持つ。実体は BRAMCore ライブラリの
// mkBRAMCore1Load で、生成 Verilog では bsc 付属の BRAM1Load.v が
// $readmemh でファイルを読む。req したアドレスの内容が次の周期から
// data に出て次の req まで保持される同期 ROM。
// FSM とは機能が別なので、同じソースに置くが別モジュールにしてある。
// =================================================================
`ifdef FSM0
`define ROM_DEPTH 21650
`define ROM_FILE "ch00.mem"
`elsif FSM1
`define ROM_DEPTH 4622
`define ROM_FILE "ch01.mem"
`elsif FSM2
`define ROM_DEPTH 6586
`define ROM_FILE "ch02.mem"
`elsif FSM3
`define ROM_DEPTH 27814
`define ROM_FILE "ch03.mem"
`endif
interface ROM_ifc;
(* always_ready *) method Action req(Addr_t addr);
(* always_ready *) method Data_t data();
endinterface
(* synthesize *)
`ifdef FSM0
module mkSoundROM0(ROM_ifc);
`elsif FSM1
module mkSoundROM1(ROM_ifc);
`elsif FSM2
module mkSoundROM2(ROM_ifc);
`elsif FSM3
module mkSoundROM3(ROM_ifc);
`endif
BRAM_PORT#(Addr_t, Data_t) rom <- mkBRAMCore1Load(`ROM_DEPTH, False, `ROM_FILE, False);
method Action req(Addr_t addr) = rom.put(False, addr, ?);
method Data_t data = rom.read;
endmodule
FSMはそれを中に置き、readMemでreqを呼び、データはrom.dataで読みます。インタフェースからrom_dataとrom_addressが消え、addrとromdataの2つのレジスタも消えました。
interface FSM_ifc;
method Action sound(SoundCode_t incode);
method Action sync(Bool lrclk);
method Action empty(Bool flag);
method Data_t sdout();
method Bool soundon();
method Bool fifo_ren();
endinterface
// 効果音 ROM。機能が別なので末尾の別モジュール mkSoundROMn にしてあり、
// 本FSMはそれを置いて readMem で req と data を呼ぶ。
`ifdef FSM0
ROM_ifc rom <- mkSoundROM0;
`elsif FSM1
ROM_ifc rom <- mkSoundROM1;
`elsif FSM2
ROM_ifc rom <- mkSoundROM2;
`elsif FSM3
ROM_ifc rom <- mkSoundROM3;
`endif
function Stmt readMem;
return (seq
rom.req(worka);
noAction;
noAction;
endseq);
endfunction
if (ii == 3) dcount <= truncate(workd<<8) | extend(rom.data);
else workd <= workd<<8 | extend(rom.data);
action
son <= True;
dout <= rom.data;
endaction
reqの中身は次の周期からdataに出て、次のreqまで保持されます。前の形ではaddrレジスタ、rom.vの出力レジスタ、romdataレジスタの3段を経てデータが届いていたので、readMemの2つのnoActionはそのための待ちでした。ROMの読み出しが1段になった後も、noActionはそのまま残してサイクル数を変えていません。ROMのreqはFSMの状態からだけ呼ばれるので、always_enabledは付けず、呼ばれた周期だけBRAMのENが立ちます。
sound.vはmkSoundFSM0から3を直接4つ置く形になり、sndch.vとrom.vは消えました。
mkSoundFSM0 u_ch0 (
.CLK(snd_fsmclk), .RST_N(snd_xrst), .sync_lrclk(LRCLK), .sound_incode(scode),
.empty_flag(fifo_empty), .fifo_ren(ren0), .sdout(ch_out0), .soundon(ch_on0)
);
動作は、旧sndch.vと新mkSoundFSMnを4チャネルとも同じ乱数のROM内容、同じコマンド列、同じlrclkで並走させて確かめました。SND_FSMCLKで300万サイクル、コマンド852件、再生中の周期がチャネル合計で488万あり、fifo_ren、soundon、sdoutの3出力が全周期一致しました。



