Refine FX-BMP implementation

- Add battery status readout
- Narrow address range to E800_0000 .. EBFF_FFFF
- Enable BMP only if it's mounted
- Configure BMP size according to mounted image size
This commit is contained in:
David Hunter
2026-01-24 18:44:06 -08:00
parent 554b19a237
commit 6f6d075d8a
8 changed files with 189 additions and 42 deletions

View File

@@ -14,6 +14,7 @@ set_global_assignment -name SYSTEMVERILOG_FILE rtl/v810/v810.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/mach.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/ram.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/sdram.v
set_global_assignment -name SYSTEMVERILOG_FILE rtl/fx_bmp.sv
set_global_assignment -name SYSTEMVERILOG_FILE rtl/dpram.sv
# set_global_assignment -name VHDL_FILE rtl/dpram.vhd
set_global_assignment -name SYSTEMVERILOG_FILE rtl/memif_sdram.sv

67
rtl/fx_bmp.sv Normal file
View File

@@ -0,0 +1,67 @@
// A FX-BMP with battery-backed SRAM, connected to the Memory Card Port
//
// Copyright (c) 2026 David Hunter
//
// This program is GPL licensed. See COPYING for the full license.
module fx_bmp
(
// Emulation configuration
input CFG_EN,
input [2:0] CFG_SIZE, // 0=128KB, 1=256KB, 2=512KB, .. 6=8MB
// Memory Card port interface
input [26:1] MCP_A, // A24 is omitted
input [7:0] MCP_DI,
output [7:0] MCP_DO,
input MCP_CSn, // aka /CartSel
input MCP_RDn,
input MCP_WRn,
output MCP_READYn,
// Memory interface
output [22:0] RAM_A,
output [7:0] RAM_DI,
input [7:0] RAM_DO,
output RAM_CEn,
output RAM_WEn,
input RAM_READYn
);
// Control size by masking (zeroing) RAM address bits above 128KB.
wire [22:0] ram_a_mask;
always @* begin
ram_a_mask[16:0] = '1;
case (CFG_SIZE)
3'd0: ram_a_mask[22:17] = 'b000_000; // 128KB
3'd1: ram_a_mask[22:17] = 'b000_001; // 256KB
3'd2: ram_a_mask[22:17] = 'b000_011; // 512KB
3'd3: ram_a_mask[22:17] = 'b000_111; // 1MB
3'd4: ram_a_mask[22:17] = 'b001_111; // 2MB
3'd5: ram_a_mask[22:17] = 'b011_111; // 4MB
default:ram_a_mask[22:17] = 'b111_111; // 8MB
endcase
end
// BMP address range is E800_0000 .. EBFF_FFFF -- half the port's addressable range.
wire bmp_sel = ~(~CFG_EN | MCP_CSn | MCP_A[26]);
// BMP memory is split down the middle:
// E800_0000 + (0000_0000 .. 01FF_FFFE) is SRAM
// E800_0000 + (0200_0000 .. 03FF_FFFE) is battery status
wire sram_sel = bmp_sel & ~MCP_A[25];
wire bat_sel = bmp_sel & MCP_A[25];
// The battery is always "good".
wire bat_good = '1;
assign RAM_A = MCP_A[23:1] & ram_a_mask;
assign RAM_DI = MCP_DI;
assign RAM_CEn = ~sram_sel;
assign RAM_WEn = RAM_CEn | MCP_WRn;
assign MCP_DO = {8{~bmp_sel}} | (bat_sel ? {7'h7F, bat_good} : RAM_DO);
assign MCP_READYn = MCP_CSn | (sram_sel & RAM_READYn);
endmodule

View File

@@ -30,7 +30,7 @@ module fx_ga
output ROM_CEn,
output RAM_CEn,
output SRAM_CEn,
output BMP_CEn,
output MCP_CSn,
output IO_CEn,
output FX_GA_CSn,
@@ -45,7 +45,7 @@ module fx_ga
input ROM_READYn,
input RAM_READYn,
input SRAM_READYn,
input BMP_READYn,
input MCP_READYn,
// Device control
output WRn,
@@ -77,8 +77,8 @@ module fx_ga
logic unk_cen;
logic io_readyn;
assign READYn = unk_cen & ROM_READYn & RAM_READYn & SRAM_READYn & BMP_READYn & io_readyn;
assign SZRQn = ~unk_cen | (ROM_CEn & IO_CEn & SRAM_CEn & BMP_CEn);
assign READYn = unk_cen & ROM_READYn & RAM_READYn & SRAM_READYn & MCP_READYn & io_readyn;
assign SZRQn = ~unk_cen | (ROM_CEn & IO_CEn & SRAM_CEn & MCP_CSn);
//////////////////////////////////////////////////////////////////////
// Address decoder
@@ -90,10 +90,10 @@ assign A1_16 = A[1] | (~&BEn[3:2] & &BEn[1:0]);
assign ROM_CEn = ~(~MRQn & (A[31:28] == 4'hF)); // F000_0000 .. FFFF_FFFF
assign RAM_CEn = ~(~MRQn & (A[31:24] == 8'h00)); // 0000_0000 .. 00FF_FFFF
assign SRAM_CEn = ~(~MRQn & (A[31:27] == 5'b1110_0)); // E000_0000 .. E7FF_FFFF
assign BMP_CEn = ~(~MRQn & (A[31:27] == 5'b1110_1)); // E800_0000 .. EFFF_FFFF
assign MCP_CSn = ~(~MRQn & (A[31:27] == 5'b1110_1)); // E800_0000 .. EFFF_FFFF
assign IO_CEn = ~((MRQn | (A[31:28] == 4'h8)) & (~BCYSTn | ~DAn)
& (ST == 2'b10));
assign unk_cen = ~(RAM_CEn & ROM_CEn & SRAM_CEn & BMP_CEn & IO_CEn);
assign unk_cen = ~(RAM_CEn & ROM_CEn & SRAM_CEn & MCP_CSn & IO_CEn);
assign FX_GA_CSn = ~(~IO_CEn & (A[30:12] == 19'h00000)) |
~(PSG_CSn & VPU_CSn & VCE_CSn & VDC0_CSn &

View File

@@ -36,12 +36,13 @@ module mach
output SRAM_WEn,
input SRAM_READYn,
output [22:0] BMP_A,
output [7:0] BMP_DI,
input [7:0] BMP_DO,
output BMP_CEn,
output BMP_WEn,
input BMP_READYn,
output [26:1] MCP_A,
output [7:0] MCP_DI,
input [7:0] MCP_DO,
output MCP_CSn,
output MCP_RDn,
output MCP_WRn,
input MCP_READYn,
input hmi_t HMI,
@@ -85,9 +86,9 @@ logic sram_cen;
wire [7:0] sram_do;
logic sram_readyn;
logic bmp_cen;
wire [7:0] bmp_do;
logic bmp_readyn;
logic mcp_csn;
wire [7:0] mcp_do;
logic mcp_readyn;
logic a1_16;
logic [31:0] mem16_a;
@@ -206,7 +207,7 @@ fx_ga ga
.ROM_CEn(rom_cen),
.RAM_CEn(ram_cen),
.SRAM_CEn(sram_cen),
.BMP_CEn(bmp_cen),
.MCP_CSn(mcp_csn),
.IO_CEn(io_cen),
.FX_GA_CSn(ga_csn),
@@ -220,7 +221,7 @@ fx_ga ga
.ROM_READYn(rom_readyn),
.RAM_READYn(ram_readyn),
.SRAM_READYn(sram_readyn),
.BMP_READYn(bmp_readyn),
.MCP_READYn(mcp_readyn),
.WRn(ga_wrn),
.RDn(ga_rdn),
@@ -451,8 +452,8 @@ always @* begin
cpu_d_i = ram_do;
else if (~sram_cen)
cpu_d_i = {24'b0, sram_do};
else if (~bmp_cen)
cpu_d_i = {24'b0, bmp_do};
else if (~mcp_csn)
cpu_d_i = {24'b0, mcp_do};
else if (~io_cen)
cpu_d_i = {16'b0, io_do};
else
@@ -483,8 +484,8 @@ assign ram_readyn = ram_cen | RAM_READYn;
assign sram_do = SRAM_DO;
assign sram_readyn = sram_cen | SRAM_READYn;
assign bmp_do = BMP_DO;
assign bmp_readyn = bmp_cen | BMP_READYn;
assign mcp_do = MCP_DO;
assign mcp_readyn = mcp_csn | MCP_READYn;
assign mem16_a = {cpu_a[31:2], a1_16, 1'b0};
@@ -507,10 +508,11 @@ assign SRAM_A = mem16_a[15:1];
assign SRAM_DI = cpu_d_o[7:0];
assign SRAM_WEn = sram_cen | cpu_rw;
assign BMP_CEn = bmp_cen;
assign BMP_A = mem16_a[23:1];
assign BMP_DI = cpu_d_o[7:0];
assign BMP_WEn = bmp_cen | cpu_rw;
assign MCP_CSn = mcp_csn;
assign MCP_A = mem16_a[26:1];
assign MCP_DI = cpu_d_o[7:0];
assign MCP_RDn = mcp_csn | ~cpu_rw;
assign MCP_WRn = mcp_csn | cpu_rw;
//////////////////////////////////////////////////////////////////////
// SCSI interface

View File

@@ -159,6 +159,15 @@ wire sram_cen;
wire sram_wen;
wire sram_readyn;
wire [26:1] mcp_a;
wire [7:0] mcp_di, mcp_do;
wire mcp_csn;
wire mcp_rdn;
wire mcp_wrn;
wire mcp_readyn;
wire bmp_cfg_en;
logic [2:0] bmp_cfg_size;
wire [22:0] bmp_a;
wire [7:0] bmp_di, bmp_do;
wire bmp_cen;
@@ -207,12 +216,13 @@ mach mach
.SRAM_WEn(sram_wen),
.SRAM_READYn(sram_readyn),
.BMP_A(bmp_a),
.BMP_DI(bmp_di),
.BMP_DO(bmp_do),
.BMP_CEn(bmp_cen),
.BMP_WEn(bmp_wen),
.BMP_READYn(bmp_readyn),
.MCP_A(mcp_a),
.MCP_DI(mcp_di),
.MCP_DO(mcp_do),
.MCP_CSn(mcp_csn),
.MCP_RDn(mcp_rdn),
.MCP_WRn(mcp_wrn),
.MCP_READYn(mcp_readyn),
.HMI(HMI),
@@ -276,6 +286,27 @@ memif_sdram memif_sdram
.SDRAM_DOUT(sdram_dout)
);
fx_bmp bmp
(
.CFG_EN(bmp_cfg_en),
.CFG_SIZE(bmp_cfg_size),
.MCP_A(mcp_a),
.MCP_DI(mcp_di),
.MCP_DO(mcp_do),
.MCP_CSn(mcp_csn),
.MCP_RDn(mcp_rdn),
.MCP_WRn(mcp_wrn),
.MCP_READYn(mcp_readyn),
.RAM_A(bmp_a),
.RAM_DI(bmp_di),
.RAM_DO(bmp_do),
.RAM_CEn(bmp_cen),
.RAM_WEn(bmp_wen),
.RAM_READYn(bmp_readyn)
);
//////////////////////////////////////////////////////////////////////
// ROM loader
@@ -318,6 +349,26 @@ always @(posedge clk_sys) begin
end
end
//////////////////////////////////////////////////////////////////////
// FX-BMP -> Memory Cord Port
wire [31:0] bmp_sd_blk_cnt = bk_sd_blk_cnt[1];
assign bmp_cfg_en = bk_mounted[1];
// Configure the BMP size to match the mounted image size.
always @* begin
casez ({|bmp_sd_blk_cnt[31:14], bmp_sd_blk_cnt[13:8]})
7'b1??_????: bmp_cfg_size = 3'd6; // 8MB
7'b01?_????: bmp_cfg_size = 3'd5; // 4MB
7'b001_????: bmp_cfg_size = 3'd4; // 2MB
7'b000_1???: bmp_cfg_size = 3'd3; // 1MB
7'b000_01??: bmp_cfg_size = 3'd2; // 512KB
7'b000_001?: bmp_cfg_size = 3'd1; // 256KB
default: bmp_cfg_size = 3'd0; // 128KB
endcase
end
//////////////////////////////////////////////////////////////////////
// Backup RAM transfer
@@ -347,7 +398,7 @@ logic sd_vd; // volume select
logic sd_ack_d;
always @(posedge clk_sys) begin
if (img_mounted) begin
if (img_mounted != 0) begin
bk_mounted[img_mounted[1]] <= |img_size;
bk_sd_blk_cnt[img_mounted[1]] <= img_size[9+:32];
end

View File

@@ -8,6 +8,7 @@ dpram.sv
../pcfx_top.sv
../memif_sdram.sv
../sdram.v
../fx_bmp.sv
../mach.sv
../huc6261.sv

View File

@@ -346,7 +346,7 @@ integer code;
sd_size[vd] = $ftell(fin);
sd_vd = vd;
-> mount_sd;
repeat (2) @(posedge clk_sys) ; // wait for mount completion
repeat (3) @(posedge clk_sys) ; // wait for mount completion
end
endtask
@@ -397,7 +397,7 @@ always @(negedge vs) begin
$display("%t: Frame %03d A=%x", $time, frame, pcfx_top.mach.cpu_a);
$sformat(fname, "frames/render-%03d", frame);
pice = 0;
if (frame >= 237) begin
if (frame >= 220) begin
fpic = $fopen({fname, ".hex"}, "w");
end
frame = frame + 1;
@@ -424,6 +424,8 @@ end
//////////////////////////////////////////////////////////////////////
event running;
initial #0 begin
#10 ; // wait for sdram init.
@@ -443,9 +445,12 @@ initial #0 begin
$display("RAMs loaded.");
end
`endif
-> running;
end
initial begin
@(running) ;
repeat (4) #(1000e3) ;
//#(500e3) ;
@@ -465,6 +470,7 @@ initial begin
end
initial if (1) begin
@(running) ;
#(216e3);
repeat (4) begin

View File

@@ -1,15 +1,15 @@
[*]
[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI
[*] Mon Jan 19 23:30:13 2026
[*] Sun Jan 25 01:51:43 2026
[*]
[dumpfile] "/Users/dhunter/src/mister/PCFX_MiSTer/rtl/tb/pcfx_top_tb.verilator.fst"
[dumpfile_mtime] "Mon Jan 19 23:29:06 2026"
[dumpfile_size] 9107446
[dumpfile_mtime] "Sun Jan 25 01:48:02 2026"
[dumpfile_size] 18901994
[savefile] "/Users/dhunter/src/mister/PCFX_MiSTer/rtl/tb/pcfx_top_tb.verilator.gtkw"
[timestart] 1230000
[timestart] 0
[size] 1334 601
[pos] -1 -1
*-24.251604 50041100 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
*-25.045605 50097180 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
[markername] AA
[markername] BB
[markername] CC
@@ -37,7 +37,6 @@
[markername] YY
[markername] ZZ
[treeopen] pcfx_top_tb.
[treeopen] pcfx_top_tb.pcfx_top.
[treeopen] pcfx_top_tb.pcfx_top.mach.cpu.
[treeopen] pcfx_top_tb.pcfx_top.mach.vdc1.SAT.
[sst_width] 255
@@ -48,6 +47,7 @@
-pcfx_top
@28
pcfx_top_tb.pcfx_top.img_mounted[1:0]
pcfx_top_tb.pcfx_top.bk_mounted[1:0]
pcfx_top_tb.pcfx_top.bk_load
pcfx_top_tb.pcfx_top.bk_save
pcfx_top_tb.pcfx_top.sd_vd
@@ -62,7 +62,6 @@ pcfx_top_tb.pcfx_top.sd_buff_addr[7:0]
pcfx_top_tb.pcfx_top.sd_buff_dout[15:0]
@28
pcfx_top_tb.pcfx_top.bk_loading
@29
pcfx_top_tb.pcfx_top.bk_saving
@100000028
pcfx_top_tb.pcfx_top.bk_state[3:0]
@@ -107,6 +106,26 @@ pcfx_top_tb.pcfx_top.memif_sdram.wact
pcfx_top_tb.pcfx_top.memif_sdram.mem_rdy
@1000200
-memif_sdram
@800200
-fx_bmp
@28
pcfx_top_tb.pcfx_top.bmp.CFG_EN
@24
pcfx_top_tb.pcfx_top.bmp.CFG_SIZE[2:0]
@29
pcfx_top_tb.pcfx_top.bmp.MCP_CSn
@22
pcfx_top_tb.pcfx_top.bmp.MCP_A[26:1]
@28
pcfx_top_tb.pcfx_top.bmp.MCP_READYn
@22
pcfx_top_tb.pcfx_top.bmp.MCP_DO[7:0]
@28
pcfx_top_tb.pcfx_top.bmp.bat_sel
pcfx_top_tb.pcfx_top.bmp.RAM_CEn
pcfx_top_tb.pcfx_top.bmp.RAM_READYn
@1000200
-fx_bmp
@28
pcfx_top_tb.pcfx_top.mach.ERROR
@22
@@ -123,7 +142,7 @@ pcfx_top_tb.pcfx_top.mach.mem16_a[31:0]
pcfx_top_tb.pcfx_top.mach.ram_cen
pcfx_top_tb.pcfx_top.mach.rom_cen
pcfx_top_tb.pcfx_top.mach.sram_cen
pcfx_top_tb.pcfx_top.mach.bmp_cen
pcfx_top_tb.pcfx_top.mach.mcp_csn
pcfx_top_tb.pcfx_top.mach.io_cen
pcfx_top_tb.pcfx_top.mach.ga_rdn
pcfx_top_tb.pcfx_top.mach.ga_wrn