Posts Issued in October, 2026

メモリをBSVで書く (5)

posted by sakurai on October 5, 2026 #1136

メモリをBSVで書く (5)

4点目 運用

bsc付属のライブラリVerilogが3つ増えました。mkFIFOF1のFIFO1.v、mkBRAMCore1LoadのBRAM1Load.v、mkSyncBRAMCore2LoadのBRAM2Load.vで、bscはこれらを生成せず、インストール先のlib/Verilogにあるものをインスタンスします。教科書版のMakefileはこれをsrcに写して、生成Verilogと同じ扱いにします。

# bsc 付属の Verilog ライブラリ。mkBlitter が使う FIFO1 と BRAM1Load、
# mkVram が使う BRAM2Load はここから src に写す
BSC_VLIB     ?= $(abspath $(dir $(shell which $(BSC)))../lib/Verilog)
BSC_LIB_V    := FIFO1.v BRAM1Load.v BRAM2Load.v
$(addprefix $(RTL_DIR)/,$(BSC_LIB_V)): $(RTL_DIR)/%.v: $(BSC_VLIB)/%.v
	@mkdir -p $(RTL_DIR)
	cp $< $@

1つのソースから2つのモジュールを出す規則は、グループターゲットで3つの生成物を1回のレシピに結びます。Blitter.bsvからはmkBlitter.vとmkProm.vとBlitter.bo、SoundFSM.bsvからは変種ごとにmkSoundFSMn.vとmkSoundROMn.vです。

$(RTL_DIR)/mkBlitter.v $(RTL_DIR)/mkProm.v $(VOBJ_DIR)/Blitter.bo &: $(SRC_DIR)/Blitter.bsv
	@mkdir -p $(RTL_DIR) $(VOBJ_DIR)
	$(TIME) $(BSC) $(BSC_RT_OPTS) $(VLOG_FLAGS) $(PFLAGS) $(STEPFLAGS) \
	    -bdir $(VOBJ_DIR) -vdir $(RTL_DIR) -g mkBlitter -g mkProm $(SRC_DIR)/Blitter.bsv
$(RTL_DIR)/mkSoundFSM0.v $(RTL_DIR)/mkSoundROM0.v &: $(SRC_DIR)/SoundFSM.bsv $(SRC_DIR)/SoundCodes.vh
	$(call BSC_GEN_D,mkSoundFSM0 mkSoundROM0,$(SRC_DIR)/SoundFSM.bsv,FSM0)

make checkはライブラリ3つの所在も確認します。ライブラリが別の場所にあるときはmake BSC_VLIB=場所で指定します。Vivadoには、srcの他の.vと同様にこの3つと、新しく増えたmkProm.v、mkVram.v、mkSoundROM0.vから3を登録します。memの.memはライブラリのVerilogの中から同じ名前で読まれるので、登録は今までのままです。

手書きVerilogからはvram.v、rom.v、sndch.vが消え、master.vからROMのインスタンスが消えました。残るのはtop.v、clock.v、master.v、sound.vの4つで、clock.vのMMCMと分周以外は配線です。

Yosysで試すときは1つ注意があります。read_verilogに-deferを付けないと、BRAM1Load.vがパラメータの既定値である空のファイル名で$readmemhを評価して止まります。-deferを付けると、パラメータが確定してから本体を解釈します。

まとめ

比べる前は、ブリッタの起動をFIFOにした版で、ROMとVRAMは手書きVerilogです。後はこの記事の3点を入れた版です。LUTとFFはYosysのsynth_xilinxでフラットに合成した概算で、mkGameFSMの行はmkBlitterとmkPromを含みます。動作はVerilatorでmkGameFSMを1,200万サイクル回し、VRAM書き込みとサウンドコードの系列と時刻を比べました。

メモリのBSV化前後 前 後 備考
ソース Blitter.bsvとGameFSM.bsv[行] 1,870 1,883 mkPromを足し、ポート2つとレジスタ1つが消えた
Vram.bsv[行] 0 41 vram.vの34行が置き換わった
SoundFSM.bsv[行] 261 306 rom.vの26行とsndch.vの65行が置き換わった
生成Verilog[行] 32,863 32,911 mkBlitter.v、mkProm.v、mkGameFSM.vの合計
mkBlitter LUT 183 198 ROMを3個のBRAMに分ける選択論理
FF 160 149 p_addrの15ビットが消えた
RAMB36 0 3 mkProm 25728 x 4
mkGameFSM LUT 5,695 5,790 後はROMを含む
FF 2,168 2,157
mkVram RAMB36 8 8 前はvram.v
mkSoundFSMn ポート 2本 0本 rom_addressとrom_dataが消えた
動作 mkGameFSMの系列と時刻 一致 1,200万サイクル
サウンド4チャネルの出力 一致 300万サイクル、852コマンド

mkGameFSMのLUTが95増えていますが、前の値にはmaster.vにあったROMが入っていません。ROMの3個のBRAMを選ぶアドレスの比較と出力の選択がmkGameFSMの側に移った分で、設計全体では増えていません。FFの減りはp_addrの15ビットそのもので、読み出しデータのレジスタはBRAMの出力レジスタに置き換わりました。

メモリをBSVに移して得たものは、ブリッタのインタフェースからROMが消えたことです。ROMを読むのはブリッタだけなのに、アドレスとデータの2ポートがブリッタ、mkGameFSM、master.vの3階層を貫いていました。使う側の中に置けば、その経路は丸ごと要らなくなります。一方でFSMとROMは機能が別なので、ブリッタの中でもmkProm、サウンドFSMの中でもmkSoundROMnという別のモジュールにし、FSMはそれをメソッドで呼びます。パターンROMとサウンドROMが同じ形になりました。VRAMは2つのクロック域にまたがるのでトップに残りますが、クロックを引数で受けるBSVのモジュールになり、手書きVerilogは配線とクロック生成だけになりました。


左矢前のブログ 次のブログ右矢

メモリをBSVで書く (4)

posted by sakurai on October 4, 2026 #1135

3点目 サウンドROMをmkSoundROMにする

図1135.1
図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出力が全周期一致しました。


左矢前のブログ 次のブログ右矢

メモリをBSVで書く (3)

posted by sakurai on October 3, 2026 #1134

2点目 VRAMをmkVramにする

図1134.1
図1134.1 VRAM書き換え前後

VRAMは2ポート2クロックのTrue Dual Portです。ポートAはゲームFSMの2MHzで読み書き、ポートBはグラフィクスFSMの49.5MHzで読み出しだけです。手書きのvram.vはこうでした。

書き換え前

module vram #(
   parameter DEPTH = 65536,
   parameter FILE  = "vram.mem"
) (
   input  wire        clka,
   input  wire        wea,
   input  wire [15:0] addra,
   input  wire [3:0]  dina,
   output reg  [3:0]  douta,

   input  wire        clkb,
   input  wire [15:0] addrb,
   output reg  [3:0]  doutb
);

   reg [3:0] mem [0:DEPTH-1];
   initial $readmemh(FILE, mem);

   always @(posedge clka) begin
      if (wea) mem[addra] <= dina;
      douta <= mem[addra];
   end

   always @(posedge clkb) doutb <= mem[addrb];

endmodule

書き換え後のVram.bsvは次のとおりで、これが全文です。

package Vram;

// ================= VRAM =================
// 256x256、4bit/画素の画面メモリ。True Dual Port で、ポート A はゲーム FSM の
// クロックで読み書き、ポート B はグラフィクス FSM のピクセルクロックで読み出し。
// BRAMCore ライブラリの mkSyncBRAMCore2Load がそのまま実体で、初期値は
// vram.mem を $readmemh で読む。両ポートとも毎周期アクセスする always_enabled で、
// 生成 Verilog の BRAM2Load は EN が常に 1 になる。

import BRAMCore::*;

typedef Bit#(16) VAddr_t;
typedef Bit#(4)  Pattern_t;

interface Vram_ifc;
   // ポート A: ゲーム FSM 側。既定クロック
   (* always_ready, always_enabled *)
   method Action    a_req(Bool we, VAddr_t addr, Pattern_t d);
   (* always_ready *)
   method Pattern_t a_data;
   // ポート B: グラフィクス側。pixClk
   (* always_ready, always_enabled *)
   method Action    b_req(VAddr_t addr);
   (* always_ready *)
   method Pattern_t b_data;
endinterface

(* synthesize *)
module mkVram#(Clock pixClk, Reset pixRst)(Vram_ifc);
   Clock clkA <- exposeCurrentClock;
   Reset rstA <- exposeCurrentReset;
   BRAM_DUAL_PORT#(VAddr_t, Pattern_t) ram <-
      mkSyncBRAMCore2Load(65536, False, clkA, rstA, pixClk, pixRst, "vram.mem", False);

   method Action    a_req(Bool we, VAddr_t addr, Pattern_t d) = ram.a.put(we, addr, d);
   method Pattern_t a_data = ram.a.read;
   method Action    b_req(VAddr_t addr) = ram.b.put(False, addr, ?);
   method Pattern_t b_data = ram.b.read;
endmodule

endpackage

2つ目のクロックはモジュールの引数で受けます。mkVram#(Clock pixClk, Reset pixRst)と書くと、生成VerilogにCLK_pixClkとRST_N_pixRstの入力が増えます。自分のクロックはexposeCurrentClockで取り出し、mkSyncBRAMCore2Loadの2組のクロックとリセットに渡します。ポートBのメソッドb_reqとb_dataは、BRAMのポートBがpixClkで動いていることからbscがクロック域を推論するので、clocked_byを書く必要がありません。両ポートにalways_enabledを付けたのは、vram.vと同じく毎周期読む形にするためで、生成VerilogのBRAM2LoadはENAとENBが定数1になります。

トップの接続は、vram.vのパラメータ付きインスタンスがbsc生成モジュールの接続に変わるだけです。

書き換え前

   vram #(.DEPTH(65536), .FILE("vram.mem")) u_vram (
      .clka  (clk_2m),
      .wea   (vwe_a),
      .addra (vaddr_a),
      .dina  (vdout_a),
      .douta (vdin_a),
      .clkb  (C49_5MHz),
      .addrb (vaddr_b),
      .doutb (vram_qb)
   );

書き換え後

   mkVram u_vram (
      .CLK          (clk_2m),
      .RST_N        (locked),
      .CLK_pixClk   (C49_5MHz),
      .RST_N_pixRst (locked),
      .a_req_we     (vwe_a),
      .a_req_addr   (vaddr_a),
      .a_req_d      (vdout_a),
      .a_data       (vdin_a),
      .b_req_addr   (vaddr_b),
      .b_data       (vram_qb)
   );

1つだけ振る舞いの違いがあります。vram.vはポートAで書き込みと同じ周期に読んだ値が書き込み前の内容になる読み出し優先で、bscのBRAM2Load.vは書き込んだ値が出る書き込み優先です。ブリッタは読む状態と書く状態を分けていて、書く周期の読み出し値を使う箇所がありません。それを確かめるために、vram.vとmkVramを同じ乱数の読み書きで並走させました。ポートAは2MHz、ポートBは49.5MHzで、ポートAは書き込みの無い周期の読み出し値を20万周期分比べて不一致0、書き込みの周期だけが46,666回違いました。ポートBは、ポートAが同じ番地に書いている瞬間を除いた4,799,764回の読み出しが全て一致しました。同じ番地への同時アクセスはどちらのモデルでも未定義で、設計上も起きません。

Yosysでは、mkVramがRAMB36E1を8個使い、LUTとFFは0です。vram.vのVivadoでの実装も8個でしたから、物量は同じです。


左矢前のブログ 次のブログ右矢

メモリをBSVで書く (2)

posted by sakurai on October 2, 2026 #1133

書き換え後は、Blitter.bsvにmkPromというROMのモジュールを立て、ブリッタはそれを置いてreqとdataを呼ぶだけにします。mkPromの実体はBRAMCoreのmkBRAMCore1Loadで、引数は語数、出力レジスタの有無、ファイル名、ファイルが2進かどうかです。

import StmtFSM::*;
import FIFOF::*;
import BRAMCore::*;
// MemPort: 画面メモリ側の面。PROM は内蔵なので VRAM のポート A だけが出る
interface MemPort;
   (* always_ready *) method VAddr_t   vaddr;
   (* always_ready *) method Bool      vwe;
   (* always_ready *) method Pattern_t vdout;
   (* always_enabled *) method Action vdin(Pattern_t d);
endinterface
// ================= パターン ROM =================
// 25728 x 4 の同期 ROM。prom.mem を $readmemh で読む。req したアドレスの内容が
// 次の周期から data に出る。実体は BRAMCore ライブラリの mkBRAMCore1Load で、
// 生成 Verilog では bsc 付属の BRAM1Load.v がインスタンスされる。
// FSM とは機能が別なので、ブリッタの中に置く別モジュールにしてある。
// 語数は PROM_DEPTH で作り分けられる(テストベンチ用)。
`ifdef PROM_DEPTH
   Integer promDepth = `PROM_DEPTH;
`else
   Integer promDepth = 25728;
`endif

interface Prom_ifc;
   (* always_ready *) method Action    req(PAddr_t addr);
   (* always_ready *) method Pattern_t data;
endinterface

(* synthesize *)
module mkProm(Prom_ifc);
   BRAM_PORT#(PAddr_t, Pattern_t) rom <- mkBRAMCore1Load(promDepth, False, "prom.mem", False);
   method Action    req(PAddr_t addr) = rom.put(False, addr, ?);
   method Pattern_t data = rom.read;
endmodule
   Reg#(VAddr_t) v_addr <- mkRegU;
   Reg#(Bool) fvwe <- mkReg(False);
   Reg#(Bool) fobj  <- mkReg(False),
              fbase <- mkReg(False);
   Reg#(Pattern_t) vd <- mkRegU;
   Wire#(Pattern_t) vdata <- mkWire;

   // パターン ROM。機能は mkProm という別のモジュールで、ブリッタはそれを置いて
   // req と data を呼ぶだけ。階層図では mkBlitter の中に mkProm の箱として見える。
   Prom_ifc prom <- mkProm;
               action
                  prom.req({pack(ys)[7:0], pack(xs)[6:0]});
                  UInt#(8) y_addr = truncate(yd);
                  UInt#(8) x_addr = truncate(xd);
                  v_addr <= {pack(y_addr), pack(x_addr)};
               endaction
               noAction; // リード待ち(1サイクル)
               action
                  case (b.op)
                     BLIT_COPY:  vd <= prom.data;
                     BLIT_OR:    vd <= vdata | prom.data;
                     BLIT_ANDN:  vd <= vdata & ~prom.data;
                     BLIT_ERASE: vd <= 0;
                  endcase
                  xs <= xs + 1;
                  xd <= xd + 1;
               endaction
   interface MemPort mem;
      method VAddr_t   vaddr     = v_addr;
      method Bool      vwe       = fvwe;
      method Pattern_t vdout     = vd;
      method Action vdin(Pattern_t d); vdata   <= d; endmethod
   endinterface

アドレスを置くactionはレジスタへの代入からprom.reqに、ラッチのactionはワイヤからprom.dataに変わりました。reqしたアドレスの内容は次の周期からdataに出るので、間のnoActionを含めて1画素あたりのサイクル数は変わりません。消えたのはp_addrレジスタ、romdataワイヤ、MemPortの2ポート、mkGameFSMの同名の2ポート、master.vのROMインスタンスと2本の配線です。mkPromは(* synthesize *)付きなので生成物にmkProm.vが1つ増え、Vivadoの階層図ではmkBlitterの中にmkPromの箱が出ます。ブリッタのFSMとROMが別のモジュールであることが、字面でも階層図でも見えます。

生成されたmkProm.vの中では、bsc付属のBRAM1Load.vがFILENAMEにprom.mem、MEMSIZEに25728を与えてインスタンスされます。BRAM1Load.vの中が$readmemhなので、memの運用は今までと同じです。語数はpromDepthにあり、テストベンチのように別の語数で作りたいときは-D PROM_DEPTH=32768のように与えます。


左矢前のブログ 次のブログ右矢

メモリをBSVで書く

posted by sakurai on October 1, 2026 #1132

教科書版の手書きVerilogは、トップの配線、メモリ、クロック生成の3つでした。このうちメモリは、BSVの標準ライブラリBRAMCoreがそのまま当てはまる部分で、手書きのVerilogを残す理由がありません。パターンROM、VRAM、サウンドROMの3つをBSVのモジュールにしました。どれも初期値は今までどおりmemの$readmemhで読み、Vivadoの運用は変わりません。合成はYosysのsynth_xilinx、動作確認はVerilatorとiverilogで、前回までと同じ条件です。

置き場所を決める物差しは、機能が別なら別モジュール、というものです。FSMはROMを読む機能、ROMは内容を持つ機能で、2つは別のモジュールにします。そのうえで、ROMを読むのがそのFSMだけなら、ROMのモジュールはそのFSMの中に置き、FSMがメソッドで呼びます。パターンROMはブリッタの中に、サウンドROMはサウンドFSMの中に置きました。VRAMは2つのクロック域から共有されるので、独立のモジュールとしてトップに置いたままです。

1点目 パターンROMをブリッタの中に置く

図1132.1
図1132.1 パターンROM書き換え前後

パターンROMを読むのはブリッタだけです。それなのにROMはmaster.vにあり、ブリッタはアドレスをレジスタp_addrに置いて外に出し、読み出しデータをワイヤromdataで受けていました。ブリッタのMemPortにはprom_addrとpdinの2ポートがあり、mkGameFSMにも同じ名前のポートが素通しで並んでいました。

書き換え前

// MemPort: 画面メモリ側の面
interface MemPort;
   (* always_ready *) method PAddr_t   prom_addr;
   (* always_ready *) method VAddr_t   vaddr;
   (* always_ready *) method Bool      vwe;
   (* always_ready *) method Pattern_t vdout;
   (* always_enabled *) method Action pdin(Pattern_t d);
   (* always_enabled *) method Action vdin(Pattern_t d);
endinterface
   Reg#(PAddr_t) p_addr <- mkRegU;
   Reg#(VAddr_t) v_addr <- mkRegU;
   Reg#(Bool) fvwe <- mkReg(False);
   Reg#(Bool) fobj  <- mkReg(False),
              fbase <- mkReg(False);
   Reg#(Pattern_t) vd <- mkRegU;
   Wire#(Pattern_t) vdata <- mkWire,
                    romdata <- mkWire;
               action
                  p_addr <= {pack(ys)[7:0], pack(xs)[6:0]};
                  UInt#(8) y_addr = truncate(yd);
                  UInt#(8) x_addr = truncate(xd);
                  v_addr <= {pack(y_addr), pack(x_addr)};
               endaction
               noAction; // リード待ち(1サイクル)
               action
                  case (b.op)
                     BLIT_COPY:  vd <= romdata;
                     BLIT_OR:    vd <= vdata | romdata;
                     BLIT_ANDN:  vd <= vdata & ~romdata;
                     BLIT_ERASE: vd <= 0;
                  endcase
                  xs <= xs + 1;
                  xd <= xd + 1;
               endaction
   interface MemPort mem;
      method PAddr_t   prom_addr = p_addr;
      method VAddr_t   vaddr     = v_addr;
      method Bool      vwe       = fvwe;
      method Pattern_t vdout     = vd;
      method Action pdin(Pattern_t d); romdata <= d; endmethod
      method Action vdin(Pattern_t d); vdata   <= d; endmethod
   endinterface

master.vにはROMのインスタンスと、そこへ渡す2本の配線がありました。

   wire [14:0] prom_addr;
   wire [3:0]  prom_dout;

   mkGameFSM u_game (
      .CLK          (clk),
      .RST_N        (rst_n),
      .COUNT        (COUNT),
      .START_BUTTON (start_db),
      .LEFT_BUTTON  (left_db),
      .RIGHT_BUTTON (right_db),
      .FIRE_BUTTON  (fire_db),
      .tick         (tick),
      .empty        (fifo_empty),
      .fifo_wen     (fifo_wen),
      .SCODE        (scode),
      .prom_addr    (prom_addr),
      .pdin         (prom_dout),
      .vaddr        (vaddr),
      .vdout        (vdout),
      .vwe          (vwe),
      .vdin         (vdin),
      .Gun_Exp      (gun_exp)
   );

   rom #(.WIDTH(4), .AWIDTH(15), .DEPTH(25728), .FILE("prom.mem")) u_prom (
      .clk  (clk),
      .addr (prom_addr),
      .q    (prom_dout)
   );

左矢前のブログ 次のブログ右矢