|
19 |
俺の勉強会#8 LT予告 |
Posts Issued in October, 2026
|
16 |
PICによるLEDドライバボードの設計 (9) |
Studio 500 3D Pop Up Wall Artの修理の続きです。

図1137.1 Studio 500
Claudeの能力が上がってきており、自分でEagleも操作できるので、思い切って論理図とレイアウトを設計して貰いました。前回は
なおAIの部品配置は配線が省略され適当なので、部品間の配線は人手で接続しています。
このように一部人手でしたが、部品間の配線もやってもらいました。つまり全てがClaudeの設計です。

図1137.2 点滅回路図
次にレイアウト図を示します。前回は配置はAIを使わずに人手で設計しましたが、今度はClaudeにEagleを操作して貰います。つまりレイアウトも全てがClaudeの設計です。
やってもらった印象ではあまり速くありませんでした。AIだと目にもとまらぬ速さで操作しそうですが、意外にEagleを実行してその画面を取得して分析してるようで、かなり遅いです。
さらに、前回は
出来上がりをClaudeにみせたところ、背面GNDと前面GNDの間のVIAが少なく、前面GNDパッド位置でVIAを打ち、背面の大きなGNDプレーンにコンタクトすべきとのことで、VIAを追加しました。
ところが今回は貫通VIAが無かったので、その旨を伝えて修正して貰いました。また、PCBサイズは26 × 27 mmとなりました。全般的に配線長が短くなって良い配置のように見えます。

図1137.3 点滅回路レイアウト図
このようにAIがEDAツールまで使いこなすようになったため、設計手法は大きく変わるように思います。
|
5 |
メモリを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は配線とクロック生成だけになりました。
|
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出力が全周期一致しました。
|
3 |
メモリをBSVで書く (3) |
2点目 VRAMをmkVramにする

図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個でしたから、物量は同じです。
|
2 |
メモリをBSVで書く (2) |
書き換え後は、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のように与えます。
|
1 |
メモリをBSVで書く |
教科書版の手書き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 パターン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)
);

