Skip to content

Commit 803ead1

Browse files
timboettigerclaude
andcommitted
savestate incr.5: PPU registers
Serialize the SPPU register state (640-bit vector) and aggregate it into the register pipeline (now 1088 bits: CPU [191:0] + SMP [447:192] + PPU [1087:448]); ss_spike REG_BYTES 56->136 (reg_off widened to 8 bits); savestate_size 0x40090. - PPU.vhd: SS_DO/SS_DI/SS_LOAD ports; 640-bit save concatenation; load branches placed LAST in the owning processes (so they override the normal body while paused): the big register-write process (~80 regs incl. mode/BG/Mode7/window/ color/VRAM/OAM regs + write latches) and the HV-counter process (H/V/FIELD/ IN_HBL/IN_VBL). Save concat counted to exactly 640 bits; load slices mirror it. - SNES.vhd: ppu_ss_do; SS_REG_DO <= ppu & smp & cpu; wire SPPU instance. NOT yet: PPU internal RAMs (CGRAM palette, OAM/HOAM sprites) -- need port-B taps; and DSP. So restore still won't be visually correct, but the PPU register state is captured. CI-fit only; functional restore needs the RAMs + DSP + hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 670cc5c commit 803ead1

6 files changed

Lines changed: 158 additions & 21 deletions

File tree

‎rtl/PPU.vhd‎

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ entity SPPU is
5454
HDE : out std_logic;
5555
VDE : out std_logic;
5656

57-
BG_EN : in std_logic_vector(4 downto 0)
57+
BG_EN : in std_logic_vector(4 downto 0);
58+
59+
-- Savestate: PPU register vector (640b). Layout is defined by the SS_DO
60+
-- concatenation in the architecture; the load branches in the owning
61+
-- processes slice SS_DI identically. SS_LOAD pulses while the core is paused.
62+
SS_DO : out std_logic_vector(639 downto 0);
63+
SS_DI : in std_logic_vector(639 downto 0) := (others => '0');
64+
SS_LOAD : in std_logic := '0'
5865
);
5966
end SPPU;
6067

@@ -277,6 +284,34 @@ signal HIRES : std_logic;
277284

278285
begin
279286

287+
-- Savestate save vector (640b). The load branches (in the owning processes)
288+
-- slice SS_DI in this exact same order; CGRAM_Lsb loads in the CGRAM process and
289+
-- H_CNT/V_CNT/FIELD/IN_HBL/IN_VBL load in the HV-counter process.
290+
SS_DO <= "00" & x"000000000000000" & -- [639:578] pad (62b)
291+
CGRAM_Lsb & -- [577:570]
292+
IN_VBL & IN_HBL & FIELD & -- [569:567]
293+
std_logic_vector(V_CNT) & std_logic_vector(H_CNT) & -- [566:549]
294+
EXTLATCHr & OAM_PRIO & OAM_PRIO_INDEX & OAM_ADDR & -- [548:530]
295+
OAM_latch & BGHOFS_latch & BGOFS_latch & M7_latch & -- [529:503]
296+
F_LATCH & OPVCT_latch & OPHCT_latch & -- [502:500]
297+
VRAMDATA_Prefetch & std_logic_vector(VMADD_INC) & -- [499:476]
298+
SUBCOLBD & OPVCT & OPHCT & -- [475:443]
299+
CGADSUB & CGWSEL & TSW & TMW & -- [442:411]
300+
WOBJLOG & WBGLOG & WOBJSEL & W34SEL & W12SEL & -- [410:371]
301+
WH3 & WH2 & WH1 & WH0 & -- [370:339]
302+
M7Y & M7X & M7VOFS & M7HOFS & -- [338:287]
303+
M7D & M7C & M7B & M7A & M7SEL & -- [286:215]
304+
BG_VOFS(3) & BG_VOFS(2) & BG_VOFS(1) & BG_VOFS(0) & -- [214:175]
305+
BG_HOFS(3) & BG_HOFS(2) & BG_HOFS(1) & BG_HOFS(0) & -- [174:135]
306+
VMADD & VMAIN_ADDRTRANS & VMAIN_ADDRINC & CGADD & -- [134:107]
307+
BG_NBA(3) & BG_NBA(2) & BG_NBA(1) & BG_NBA(0) & -- [106:91]
308+
BG_SC_SIZE(3) & BG_SC_SIZE(2) & BG_SC_SIZE(1) & BG_SC_SIZE(0) & -- [90:83]
309+
BG_SC_ADDR(3) & BG_SC_ADDR(2) & BG_SC_ADDR(1) & BG_SC_ADDR(0) & -- [82:59]
310+
MOSAIC_SIZE & BG_MOSAIC_EN & BG_SIZE & BG3PRIO & BG_MODE & -- [58:43]
311+
M7EXTBG & PSEUDOHIRES & OVERSCAN & OBJINTERLACE & BGINTERLACE & -- [42:38]
312+
TS & TM & OAMADD & -- [37:13]
313+
OBJSIZE & OBJNAME & OBJADDR & MB & FORCE_BLANK; -- [12:0]
314+
280315
process( RST_N, CLK )
281316
variable DOT_CYCLES: unsigned(2 downto 0);
282317
begin
@@ -693,6 +728,93 @@ begin
693728
F_LATCH <= '1';
694729
end if;
695730
end if;
731+
732+
-- Savestate restore (last in the process so it overrides; core paused).
733+
-- Slices mirror the SS_DO concatenation exactly. H/V counters and the
734+
-- HV-latch state load in their own process.
735+
if SS_LOAD = '1' then
736+
FORCE_BLANK <= SS_DI(0);
737+
MB <= SS_DI(4 downto 1);
738+
OBJADDR <= SS_DI(7 downto 5);
739+
OBJNAME <= SS_DI(9 downto 8);
740+
OBJSIZE <= SS_DI(12 downto 10);
741+
OAMADD <= SS_DI(21 downto 13);
742+
TM <= SS_DI(29 downto 22);
743+
TS <= SS_DI(37 downto 30);
744+
BGINTERLACE <= SS_DI(38);
745+
OBJINTERLACE <= SS_DI(39);
746+
OVERSCAN <= SS_DI(40);
747+
PSEUDOHIRES <= SS_DI(41);
748+
M7EXTBG <= SS_DI(42);
749+
BG_MODE <= SS_DI(45 downto 43);
750+
BG3PRIO <= SS_DI(46);
751+
BG_SIZE <= SS_DI(50 downto 47);
752+
BG_MOSAIC_EN <= SS_DI(54 downto 51);
753+
MOSAIC_SIZE <= SS_DI(58 downto 55);
754+
BG_SC_ADDR(0) <= SS_DI(64 downto 59);
755+
BG_SC_ADDR(1) <= SS_DI(70 downto 65);
756+
BG_SC_ADDR(2) <= SS_DI(76 downto 71);
757+
BG_SC_ADDR(3) <= SS_DI(82 downto 77);
758+
BG_SC_SIZE(0) <= SS_DI(84 downto 83);
759+
BG_SC_SIZE(1) <= SS_DI(86 downto 85);
760+
BG_SC_SIZE(2) <= SS_DI(88 downto 87);
761+
BG_SC_SIZE(3) <= SS_DI(90 downto 89);
762+
BG_NBA(0) <= SS_DI(94 downto 91);
763+
BG_NBA(1) <= SS_DI(98 downto 95);
764+
BG_NBA(2) <= SS_DI(102 downto 99);
765+
BG_NBA(3) <= SS_DI(106 downto 103);
766+
CGADD <= SS_DI(115 downto 107);
767+
VMAIN_ADDRINC <= SS_DI(116);
768+
VMAIN_ADDRTRANS <= SS_DI(118 downto 117);
769+
VMADD <= SS_DI(134 downto 119);
770+
BG_HOFS(0) <= SS_DI(144 downto 135);
771+
BG_HOFS(1) <= SS_DI(154 downto 145);
772+
BG_HOFS(2) <= SS_DI(164 downto 155);
773+
BG_HOFS(3) <= SS_DI(174 downto 165);
774+
BG_VOFS(0) <= SS_DI(184 downto 175);
775+
BG_VOFS(1) <= SS_DI(194 downto 185);
776+
BG_VOFS(2) <= SS_DI(204 downto 195);
777+
BG_VOFS(3) <= SS_DI(214 downto 205);
778+
M7SEL <= SS_DI(222 downto 215);
779+
M7A <= SS_DI(238 downto 223);
780+
M7B <= SS_DI(254 downto 239);
781+
M7C <= SS_DI(270 downto 255);
782+
M7D <= SS_DI(286 downto 271);
783+
M7HOFS <= SS_DI(299 downto 287);
784+
M7VOFS <= SS_DI(312 downto 300);
785+
M7X <= SS_DI(325 downto 313);
786+
M7Y <= SS_DI(338 downto 326);
787+
WH0 <= SS_DI(346 downto 339);
788+
WH1 <= SS_DI(354 downto 347);
789+
WH2 <= SS_DI(362 downto 355);
790+
WH3 <= SS_DI(370 downto 363);
791+
W12SEL <= SS_DI(378 downto 371);
792+
W34SEL <= SS_DI(386 downto 379);
793+
WOBJSEL <= SS_DI(394 downto 387);
794+
WBGLOG <= SS_DI(402 downto 395);
795+
WOBJLOG <= SS_DI(410 downto 403);
796+
TMW <= SS_DI(418 downto 411);
797+
TSW <= SS_DI(426 downto 419);
798+
CGWSEL <= SS_DI(434 downto 427);
799+
CGADSUB <= SS_DI(442 downto 435);
800+
OPHCT <= SS_DI(451 downto 443);
801+
OPVCT <= SS_DI(460 downto 452);
802+
SUBCOLBD <= SS_DI(475 downto 461);
803+
VMADD_INC <= unsigned(SS_DI(483 downto 476));
804+
VRAMDATA_Prefetch <= SS_DI(499 downto 484);
805+
OPHCT_latch <= SS_DI(500);
806+
OPVCT_latch <= SS_DI(501);
807+
F_LATCH <= SS_DI(502);
808+
M7_latch <= SS_DI(510 downto 503);
809+
BGOFS_latch <= SS_DI(518 downto 511);
810+
BGHOFS_latch <= SS_DI(521 downto 519);
811+
OAM_latch <= SS_DI(529 downto 522);
812+
OAM_ADDR <= SS_DI(539 downto 530);
813+
OAM_PRIO_INDEX <= SS_DI(546 downto 540);
814+
OAM_PRIO <= SS_DI(547);
815+
EXTLATCHr <= SS_DI(548);
816+
CGRAM_Lsb <= SS_DI(577 downto 570);
817+
end if;
696818
end if;
697819
end process;
698820

@@ -895,6 +1017,15 @@ begin
8951017
if V_CNT = VSYNC_LINE+3 then VSYNC <= '0'; end if;
8961018
end if;
8971019
end if;
1020+
1021+
-- Savestate restore (last -> overrides; core paused). Slices mirror SS_DO.
1022+
if SS_LOAD = '1' then
1023+
H_CNT <= unsigned(SS_DI(557 downto 549));
1024+
V_CNT <= unsigned(SS_DI(566 downto 558));
1025+
FIELD <= SS_DI(567);
1026+
IN_HBL <= SS_DI(568);
1027+
IN_VBL <= SS_DI(569);
1028+
end if;
8981029
end if;
8991030
end process;
9001031

‎rtl/SNES.vhd‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ entity SNES is
105105
AUDIO_R : out std_logic_vector(15 downto 0);
106106

107107
-- Savestate: aggregated chip register vector (to ss_spike via main.v).
108-
-- Layout: 65C816 [191:0] + SMP/SPC700 [447:192].
109-
SS_REG_DO : out std_logic_vector(447 downto 0);
110-
SS_REG_DI : in std_logic_vector(447 downto 0);
108+
-- Layout: 65C816 [191:0] + SMP/SPC700 [447:192] + PPU [1087:448].
109+
SS_REG_DO : out std_logic_vector(1087 downto 0);
110+
SS_REG_DI : in std_logic_vector(1087 downto 0);
111111
SS_REG_LOAD : in std_logic
112112
);
113113
end SNES;
@@ -157,6 +157,7 @@ architecture rtl of SNES is
157157
-- Savestate: per-chip register vectors, aggregated into SS_REG_DO
158158
signal cpu_ss_do : std_logic_vector(191 downto 0);
159159
signal smp_ss_do : std_logic_vector(255 downto 0);
160+
signal ppu_ss_do : std_logic_vector(639 downto 0);
160161

161162
signal APU_RAM_A : std_logic_vector(15 downto 0);
162163
signal APU_RAM_DO : std_logic_vector(7 downto 0);
@@ -187,8 +188,8 @@ architecture rtl of SNES is
187188

188189
begin
189190

190-
-- Savestate: aggregate the chip register vectors (65C816 low, SMP/SPC700 high)
191-
SS_REG_DO <= smp_ss_do & cpu_ss_do;
191+
-- Savestate: aggregate the chip register vectors (65C816 low .. PPU high)
192+
SS_REG_DO <= ppu_ss_do & smp_ss_do & cpu_ss_do;
192193

193194
-- Game Genie
194195
GAMEGENIE : component CODES
@@ -352,7 +353,11 @@ begin
352353
HSYNC => HSYNC,
353354
VSYNC => VSYNC,
354355

355-
BG_EN => DBG_BG_EN
356+
BG_EN => DBG_BG_EN,
357+
358+
SS_DO => ppu_ss_do,
359+
SS_DI => SS_REG_DI(1087 downto 448),
360+
SS_LOAD => SS_REG_LOAD
356361
);
357362

358363

‎rtl/main.v‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ module main #(
1717
input SS_PAUSE,
1818

1919
// Savestate: aggregated chip register vector pass-through (SNES.vhd <-> ss_spike)
20-
output [447:0] SS_REG_DO,
21-
input [447:0] SS_REG_DI,
22-
input SS_REG_LOAD,
20+
output [1087:0] SS_REG_DO,
21+
input [1087:0] SS_REG_DI,
22+
input SS_REG_LOAD,
2323

2424
input MCLK,
2525
input ACLK,

‎rtl/mister_top/SNES.sv‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -804,9 +804,9 @@ module MAIN_SNES (
804804
// ss_spike. clk_sys domain, no CDC. SNES.vhd aggregates 65C816 [191:0] +
805805
// SMP/SPC700 [447:192]. cpu_ss_reg_do = state out, ss_reg_di = state to load,
806806
// ss_reg_load = latch pulse.
807-
wire [447:0] cpu_ss_reg_do;
808-
wire [447:0] ss_reg_di;
809-
wire ss_reg_load;
807+
wire [1087:0] cpu_ss_reg_do;
808+
wire [1087:0] ss_reg_di;
809+
wire ss_reg_load;
810810

811811
psram #(
812812
.CLOCK_SPEED(85.9)

‎rtl/mister_top/ss_spike.sv‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
module ss_spike #(
2828
// Register region: REG_BYTES bytes (must be a multiple of 8 so the flat space
2929
// stays 8-byte aligned). Holds the aggregated chip register vector from
30-
// SNES.vhd: 65C816 [191:0] + SMP/SPC700 [447:192]. 56 bytes = 448 bits.
31-
parameter integer REG_BYTES = 56,
30+
// SNES.vhd: 65C816 [191:0] + SMP/SPC700 [447:192] + PPU [1087:448].
31+
// 136 bytes = 1088 bits.
32+
parameter integer REG_BYTES = 136,
3233
parameter integer REG_BITS = REG_BYTES * 8
3334
) (
3435
input wire clk, // clk_sys_21_48
@@ -122,7 +123,7 @@ module ss_spike #(
122123
wire in_aram = (gbyte >= WRAM_END) && (gbyte < REG_START); // else WRAM
123124
wire [16:0] local_addr = in_aram ? (gbyte[16:0] - WRAM_END[16:0])
124125
: (gbyte[16:0] - VRAM_END[16:0]);
125-
wire [5:0] reg_off = gbyte[5:0]; // 0..REG_BYTES-1 (REG_START low bits are 0)
126+
wire [7:0] reg_off = gbyte[7:0]; // 0..REG_BYTES-1 (REG_START low bits are 0)
126127

127128
always @(posedge clk) begin
128129
prev_save <= ss_save;
@@ -239,7 +240,7 @@ module ss_spike #(
239240
// chip register byte: write into ss_reg_di; after the last byte pulse
240241
// ss_reg_load so the chips latch the whole vector (core still paused).
241242
ss_reg_di[reg_off*8 +: 8] <= wbuf[byte_idx*8 +: 8];
242-
if (reg_off == 6'(REG_BYTES - 1)) ss_reg_load <= 1'b1;
243+
if (reg_off == 8'(REG_BYTES - 1)) ss_reg_load <= 1'b1;
243244
st <= L_NEXT;
244245
end else begin
245246
dma_req <= 1'b1;

‎target/pocket/core_top.sv‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,12 @@ module core_top (
463463

464464
// Savestate enabled. Streams over APF bridge region 0x5xxxxxxx because
465465
// 0x4xxxxxxx is already the "MK3 Cheats" slot. Size = 1 header word + RAM
466-
// payload (VRAM 64K + WRAM 128K + ARAM 64K = 0x40000 bytes) + 56 bytes of chip
467-
// registers (65C816 + SMP/SPC700) = 0x40038 bytes => 32776 words => 0x40040.
468-
// PPU/DSP registers still to come.
466+
// payload (0x40000 bytes) + 136 bytes of chip registers (65C816 + SMP/SPC700 +
467+
// PPU) = 0x40088 bytes => 32786 words => 0x40090. PPU internal RAMs + DSP still
468+
// to come.
469469
wire savestate_supported = 1;
470470
wire [31:0] savestate_addr = 32'h50000000;
471-
wire [31:0] savestate_size = 32'h00040040;
471+
wire [31:0] savestate_size = 32'h00040090;
472472
wire [31:0] savestate_maxloadsize = savestate_size + 32'h1000;
473473

474474
wire savestate_start;

0 commit comments

Comments
 (0)