From c5ecd93209228967a8e98f1856a3e9edd9c506f1 Mon Sep 17 00:00:00 2001 From: Enver Haase Date: Thu, 3 Sep 2026 23:12:36 +0200 Subject: [PATCH 1/3] Add the Magic Desk Plus cartridge to the slot logic Magic Desk Plus is a Magic Desk with three things bolted on: one more bank bit, so DE00 selects 128 banks of 8K instead of 64, a page register at DE01, and a control register at DE03 that switches a 256 byte window at DF00 between 128K of battery-backed SRAM and an 8K or 32K EEPROM. The window is readable and writable, and stays served when the ROM is switched off -- the file system the format ships with depends on that. The SRAM and the EEPROM go in the memory the REU and GeoRAM already share rather than in an area of their own. That region exists on every target, which an area of its own would not: above the 64K of cartridge RAM there is a free megabyte on U64, U64-II and U2+L, but on the U2 the cartridge ROM starts right there. The cost is that this cart and the REU cannot both be on, which is what GeoRAM already does and what the prohibit mechanism already handles. tb_magic_desk_plus drives the registers the way the machine does and checks the address the logic produces. Against this change all 20 checks pass; against the unchanged file 17 fail. It also pins down something the file does not say anywhere: cart_variant is sampled only while the cartridge is in reset, so the EEPROM size cannot be changed without one. For GideonZ/1541ultimate#727. --- .../cart_slot/vhdl_sim/tb_magic_desk_plus.vhd | 278 ++++++++++++++++++ fpga/cart_slot/vhdl_source/all_carts_v5.vhd | 67 ++++- 2 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 fpga/cart_slot/vhdl_sim/tb_magic_desk_plus.vhd diff --git a/fpga/cart_slot/vhdl_sim/tb_magic_desk_plus.vhd b/fpga/cart_slot/vhdl_sim/tb_magic_desk_plus.vhd new file mode 100644 index 000000000..4d557679b --- /dev/null +++ b/fpga/cart_slot/vhdl_sim/tb_magic_desk_plus.vhd @@ -0,0 +1,278 @@ +-------------------------------------------------------------------------------- +-- tb_magic_desk_plus -- what the Magic Desk Plus registers have to do. +-- +-- Drives all_carts_v5 as the C64 would: writes to DE00, DE01 and DE03, then +-- reads back the memory address the cartridge logic produces for the DF00 +-- window and for the ROM window. Checks the four behaviours the format +-- specifies, plus the two the Ultimate has to get right for it to be usable: +-- the window has to stay served with the ROM switched off, and the address has +-- to stay inside the region the REU and GeoRAM share. +-- +-- Runs against a v3.15 checkout: +-- ghdl -r --std=02 tb_magic_desk_plus +-------------------------------------------------------------------------------- +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +use work.slot_bus_pkg.all; +use work.io_bus_pkg.all; + +entity tb_magic_desk_plus is +end entity; + +architecture arch of tb_magic_desk_plus is + constant c_magic_desk_p : std_logic_vector(4 downto 0) := "10010"; + constant c_georam_base : std_logic_vector(27 downto 0) := X"1000000"; + + signal clock : std_logic := '0'; + signal reset : std_logic := '1'; + signal stopped : boolean := false; + + signal slot_req : t_slot_req := c_slot_req_init; + signal slot_resp : t_slot_resp; + signal io_req : t_io_req := c_io_req_init; + signal io_resp : t_io_resp; + + signal cart_logic : std_logic_vector(4 downto 0) := c_magic_desk_p; + signal cart_var : std_logic_vector(2 downto 0) := "000"; + + signal serve_rom : std_logic; + signal serve_io1 : std_logic; + signal serve_io2 : std_logic; + signal serve_128 : std_logic; + signal serve_vic : std_logic; + signal serve_en : std_logic; + signal allow_write : std_logic; + signal mem_addr : unsigned(25 downto 0); + signal exrom_n : std_logic; + signal game_n : std_logic; + signal irq_n : std_logic; + signal nmi_n : std_logic; + signal cart_led : std_logic; + signal cart_active : std_logic; + signal freezer_ena : std_logic; + signal unfreeze : std_logic; + + signal rst_in : std_logic := '0'; + signal errors : integer := 0; +begin + clock <= not clock after 10 ns when not stopped; + reset <= '1', '0' after 200 ns; + + i_dut: entity work.all_carts_v5 + generic map ( + g_register_addr => false, + g_eeprom => false, + g_max_cart_bits => 22, + g_georam_base => c_georam_base ) + port map ( + clock => clock, + reset => reset, + io_req_eeprom => io_req, + io_resp_eeprom => io_resp, + RST_in => rst_in, + c64_reset => '0', + kernal_enable => '0', + kernal_area => '0', + freeze_trig => '0', + freeze_act => '0', + freezer_ena => freezer_ena, + unfreeze => unfreeze, + cart_active => cart_active, + cart_kill => '0', + cart_logic => cart_logic, + cart_variant => cart_var, + cart_force => '0', + slot_req => slot_req, + slot_resp => slot_resp, + epyx_timeout => '0', + serve_enable => serve_en, + serve_vic => serve_vic, + serve_128 => serve_128, + serve_rom => serve_rom, + serve_io1 => serve_io1, + serve_io2 => serve_io2, + allow_write => allow_write, + mem_req => '0', + mem_addr => mem_addr, + phi2 => '0', + irq_n => irq_n, + nmi_n => nmi_n, + exrom_n => exrom_n, + game_n => game_n, + CART_LEDn => cart_led, + size_ctrl => "001" ); + + main: process + variable n_err : integer := 0; + + procedure tick is + begin + wait until clock = '1'; + end procedure; + + -- A write into the IO1 page, the way the C64 reaches DE00..DEFF. + procedure io_wr(addr : in std_logic_vector(15 downto 0); + data : in std_logic_vector(7 downto 0)) is + begin + tick; + slot_req.io_address <= unsigned(addr); + slot_req.data <= data; + slot_req.io_write <= '1'; + tick; + slot_req.io_write <= '0'; + tick; + end procedure; + + -- Point the bus at an address and let the logic settle, so mem_addr + -- shows what the cartridge would drive for that access. + procedure bus_at(addr : in std_logic_vector(15 downto 0)) is + begin + slot_req.bus_address <= unsigned(addr); + tick; tick; + wait for 1 ns; + end procedure; + + procedure check(cond : in boolean; msg : in string) is + begin + if not cond then + n_err := n_err + 1; + report "FAIL: " & msg severity error; + end if; + end procedure; + + -- cart_variant is sampled only while the cartridge is in reset, the + -- same as on the machine, where the firmware writes the type and then + -- resets the cart. Changing it without this does nothing. + procedure cart_reset is + begin + tick; + rst_in <= '1'; + tick; tick; + rst_in <= '0'; + tick; tick; + end procedure; + begin + wait until reset = '0'; + tick; tick; + + ------------------------------------------------------------------ + report "power-up state" severity note; + ------------------------------------------------------------------ + -- The format says the EEPROM is selected and the first SRAM portion + -- is the default. Address bit 17 carries the SRAM select, bit 16 the + -- portion, so both are zero here. + bus_at(X"DF00"); + check(serve_io2 = '1', "the DF00 window must be served"); + check(mem_addr(17) = '0', "the EEPROM must be selected at power-up"); + check(mem_addr(16) = '0', "the first SRAM portion must be the default"); + check(allow_write = '1', "the window must be writable"); + + ------------------------------------------------------------------ + report "DE00: bank bits 0..6 and the disable bit" severity note; + ------------------------------------------------------------------ + io_wr(X"DE00", X"00"); + bus_at(X"8000"); + check(exrom_n = '0', "bank 0 without bit 7 must map the ROM"); + check(serve_rom = '1', "the ROM window must be served"); + + io_wr(X"DE00", X"7F"); -- highest bank, ROM still on + bus_at(X"8000"); + check(exrom_n = '0', "bit 7 clear must leave the ROM mapped"); + check(mem_addr(20 downto 14) = "1111111", "bank 127 must reach the address"); + + io_wr(X"DE00", X"40"); -- bank 64: the bit a plain Magic Desk cannot reach + bus_at(X"8000"); + check(mem_addr(20) = '1', "bit 6 must be a bank bit, not ignored"); + + io_wr(X"DE00", X"80"); -- disable + bus_at(X"8000"); + check(exrom_n = '1', "bit 7 must switch the ROM off"); + bus_at(X"DF00"); + check(serve_io2 = '1', "the window must survive the ROM being off"); + + io_wr(X"DE00", X"00"); -- back on + + ------------------------------------------------------------------ + report "DE01: the page register" severity note; + ------------------------------------------------------------------ + io_wr(X"DE03", X"20"); -- SRAM on, first portion + io_wr(X"DE01", X"00"); + bus_at(X"DF00"); + check(mem_addr(15 downto 8) = X"00", "page 0 must select the first page"); + + io_wr(X"DE01", X"A5"); + bus_at(X"DF00"); + check(mem_addr(15 downto 8) = X"A5", "the page must reach address bits 15..8"); + + io_wr(X"DE01", X"FF"); + bus_at(X"DF80"); + check(mem_addr(15 downto 8) = X"FF", "page 255 must be reachable"); + check(mem_addr(7 downto 0) = X"80", "the offset in the window comes from the bus"); + + ------------------------------------------------------------------ + report "DE03: portion and SRAM/EEPROM select" severity note; + ------------------------------------------------------------------ + io_wr(X"DE03", X"20"); -- bit 5 = SRAM, bit 0 = 0 + bus_at(X"DF00"); + check(mem_addr(17) = '1', "bit 5 must select the SRAM"); + check(mem_addr(16) = '0', "bit 0 clear must select the first portion"); + + io_wr(X"DE03", X"21"); -- bit 0 = second portion + bus_at(X"DF00"); + check(mem_addr(16) = '1', "bit 0 set must select the second portion"); + + io_wr(X"DE03", X"01"); -- bit 5 clear: back to the EEPROM + bus_at(X"DF00"); + check(mem_addr(17) = '0', "bit 5 clear must select the EEPROM"); + + ------------------------------------------------------------------ + report "the EEPROM answers only inside its own size" severity note; + ------------------------------------------------------------------ + -- variant 0 is the 8K EEPROM: 32 pages, mask 0x1F. + cart_var <= "000"; + cart_reset; + io_wr(X"DE03", X"00"); + io_wr(X"DE01", X"FF"); + bus_at(X"DF00"); + check(mem_addr(15 downto 8) = X"1F", "an 8K EEPROM must mask the page to 0x1F"); + + -- variant 1 is the 32K EEPROM: 128 pages, mask 0x7F. + cart_var <= "001"; + cart_reset; + io_wr(X"DE03", X"00"); + io_wr(X"DE01", X"FF"); + bus_at(X"DF00"); + check(mem_addr(15 downto 8) = X"7F", "a 32K EEPROM must mask the page to 0x7F"); + + -- The SRAM is not masked. + io_wr(X"DE03", X"20"); + io_wr(X"DE01", X"FF"); + bus_at(X"DF00"); + check(mem_addr(15 downto 8) = X"FF", "the SRAM must use all 256 pages"); + + ------------------------------------------------------------------ + report "everything stays inside the shared region" severity note; + ------------------------------------------------------------------ + io_wr(X"DE03", X"21"); + io_wr(X"DE01", X"FF"); + bus_at(X"DFFF"); + -- g_georam_base is 0x1000000, so bit 24 belongs to the region itself. + -- What has to stay clear is everything between it and the 256K the + -- SRAM and EEPROM occupy. + check(mem_addr(25 downto 24) = "01", "the window must sit in the shared region"); + check(mem_addr(23 downto 18) = "000000", + "the window must not reach past the first 256K of the region"); + + ------------------------------------------------------------------ + errors <= n_err; + if n_err = 0 then + report "RESULT: all checks passed" severity note; + else + report "RESULT: " & integer'image(n_err) & " check(s) failed" severity note; + end if; + stopped <= true; + wait; + end process; +end arch; diff --git a/fpga/cart_slot/vhdl_source/all_carts_v5.vhd b/fpga/cart_slot/vhdl_source/all_carts_v5.vhd index 9d95f0218..6fa43df68 100644 --- a/fpga/cart_slot/vhdl_source/all_carts_v5.vhd +++ b/fpga/cart_slot/vhdl_source/all_carts_v5.vhd @@ -74,6 +74,13 @@ architecture gideon of all_carts_v5 is signal mode_bits : std_logic_vector(2 downto 0); signal ef_write : std_logic := '0'; signal georam_bank : std_logic_vector(15 downto 0); + + -- Magic Desk Plus: a 256 byte window at DF00 onto either 128K of SRAM or an + -- 8K/32K EEPROM, selected by DE03. DE01 picks the page inside it. + signal mdp_page : std_logic_vector(7 downto 0); + signal mdp_half : std_logic; -- DE03 bit 0: which 64K half of the SRAM + signal mdp_sram : std_logic; -- DE03 bit 5: 1 = SRAM, 0 = EEPROM + signal mdp_page_m : std_logic_vector(7 downto 0); signal freeze_act_d : std_logic; signal cart_en : std_logic; @@ -114,6 +121,7 @@ architecture gideon of all_carts_v5 is -- Simple bankers with RAM constant c_pagefox : std_logic_vector(4 downto 0) := "10000"; constant c_easy_flash : std_logic_vector(4 downto 0) := "10001"; + constant c_magic_desk_p : std_logic_vector(4 downto 0) := "10010"; -- Magic Desk Plus -- Freezers constant c_fc : std_logic_vector(4 downto 0) := "11000"; @@ -128,7 +136,7 @@ architecture gideon of all_carts_v5 is constant c_serve_rom_rr : std_logic_vector(0 to 7) := "11011111"; constant c_serve_io_rr : std_logic_vector(0 to 7) := "10101111"; - type t_address_select is ( ROM, RAM, GEO ); + type t_address_select is ( ROM, RAM, GEO, MDP ); signal addr_map : t_address_select; -- alias @@ -141,6 +149,12 @@ architecture gideon of all_carts_v5 is signal georam_mask : std_logic_vector(15 downto 0); begin + -- An 8K EEPROM has 32 pages and ignores the upper bits of DE01, a 32K one + -- has 128 and ignores bit 7. The SRAM uses all 256 pages of its half. + mdp_page_m <= mdp_page when mdp_sram = '1' else + mdp_page and X"7F" when variant(0) = '1' else + mdp_page and X"1F"; + with size_ctrl select georam_mask <= "0000000111111111" when "000", "0000001111111111" when "001", @@ -186,6 +200,9 @@ begin bank_bits <= (others => '0'); ram_bank <= (others => '0'); georam_bank <= (others => '0'); + mdp_page <= (others => '0'); + mdp_half <= '0'; -- first 64K portion + mdp_sram <= '0'; -- EEPROM is active at power-up ef_write <= '0'; allow_bank <= '0'; do_io2 <= '1'; @@ -338,6 +355,38 @@ begin rom_mode <= "00"; -- 8K banks + when c_magic_desk_p => + -- Magic Desk Plus. The ROM half is a Magic Desk with one more + -- bank bit: DE00 bits 0..6 select one of 128 8K banks and bit 7 + -- disables the ROM. On top of that sit three things the plain + -- Magic Desk does not have: a page register at DE01, a control + -- register at DE03, and a 256 byte window at DF00 onto either + -- 128K of SRAM or an 8K/32K EEPROM. + -- + -- The window stays served whether or not the ROM is switched + -- off, which is what the hardware does and what its file system + -- relies on. + if io_write='1' and io_addr(8)='0' then -- DE00 range + case io_addr(7 downto 0) is + when X"00" => + bank_bits(21 downto 14) <= '0' & io_wdata(6 downto 0); + mode_bits(0) <= io_wdata(7); -- ROM disable + when X"01" => + mdp_page <= io_wdata; + when X"03" => + mdp_half <= io_wdata(0); + mdp_sram <= io_wdata(5); + when others => + null; + end case; + end if; + game_n <= '1'; + exrom_n <= mode_bits(0); + serve_rom <= '1'; + serve_io2 <= '1'; + cart_en <= not mode_bits(0); + rom_mode <= "00"; -- 8K banks + when c_ocean_16K => if io_write='1' and io_addr(8)='0' then -- DE00 range bank_bits(21 downto 14) <= io_wdata; @@ -741,6 +790,12 @@ begin addr_map <= GEO; end if; + when c_magic_desk_p => + if slot_addr(15 downto 8)=X"DF" then + allow_write <= '1'; + addr_map <= MDP; + end if; + when c_128 => if slot_addr(15 downto 8)=X"DF" and slot_addr(7)='1' and variant(2)='1' then allow_write <= '1'; @@ -761,13 +816,21 @@ begin end process; -- Calculate the final memory address - process(addr_map, rom_addr, ram_addr, kernal_area, georam_bank, slot_addr) + process(addr_map, rom_addr, ram_addr, kernal_area, georam_bank, slot_addr, + mdp_sram, mdp_half, mdp_page_m) begin case addr_map is when RAM => mem_addr_i <= ram_addr; when GEO => mem_addr_i <= g_georam_base(27 downto 24) & georam_bank & slot_addr(7 downto 0); + when MDP => + -- Shares the region the REU and GeoRAM use, so no target needs a + -- memory area of its own for this. The EEPROM occupies the first + -- 128K of it and the SRAM the second, which keeps both inside the + -- 256K every target has there. + mem_addr_i <= g_georam_base(27 downto 24) & "000000" & mdp_sram & + mdp_half & mdp_page_m & slot_addr(7 downto 0); when others => mem_addr_i <= rom_addr; end case; From 331f1fc27e6db56f762debd32cd922e6fa27e98e Mon Sep 17 00:00:00 2001 From: Enver Haase Date: Thu, 3 Sep 2026 23:12:36 +0200 Subject: [PATCH 2/3] Recognise a Magic Desk Plus image and map it Magic Desk and Magic Desk Plus are the same CRT hardware type. The upstream implementation confirms it: the VICE patch that comes with the format extends magicdesk.c rather than adding a cartridge, and its attach path derives nothing from the file but a bank mask, taken from the highest bank present. What turns the SRAM and the EEPROM on there is the user supplying an image for them. So nothing in the header can tell the two apart, and this uses the same thing VICE does: a cart that brought its non-volatile memory with it is a Plus. It travels in the CRT as chunks at DF00, the address of the window they are reached through, and the bank field says which piece each chunk is. It has to: the size field of a CHIP header is 16 bits, so the 128K of SRAM cannot be one chunk and is carried in four quarters of 32K. Bank 0 is the EEPROM, banks 1 to 4 are the SRAM in address order. A chunk that is none of those shapes is refused rather than loaded. The EEPROM size chooses the page mask, again as in VICE: 8K masks the page register to 0x1F and 32K to 0x7F, which is what its io2 handlers do, and it accepts an EEPROM image only at those two sizes. A cart carrying only SRAM gets the 8K mask, the size VICE itself creates when it has to make an EEPROM image from nothing. The cart prohibits the whole of IO rather than only DEXX. Its registers are at DE00..DE03 and its window is the entire DF00 page, so the UCI at DF1C, the sampler, an ACIA at either address and the REU whose memory this borrows all have to give way. A Magic Desk without any of this keeps the mapping it has always had. For GideonZ/1541ultimate#727. --- software/io/c64/c64.h | 1 + software/io/c64/c64_crt.cc | 88 ++++++++++++++++++++++++++++++++++++++ software/io/c64/c64_crt.h | 21 +++++++++ 3 files changed, 110 insertions(+) diff --git a/software/io/c64/c64.h b/software/io/c64/c64.h index d10fd9702..235e0e0b1 100644 --- a/software/io/c64/c64.h +++ b/software/io/c64/c64.h @@ -142,6 +142,7 @@ #define CART_TYPE_PAGEFOX 0x10 #define CART_TYPE_EASY_FLASH 0x11 // ? +#define CART_TYPE_MDPLUS 0x12 // Magic Desk Plus. Variant 0: 8K EEPROM, 1: 32K #define CART_TYPE_FINAL12 0x18 #define CART_TYPE_FC3 0x19 // 0: 64K, 1: 256K diff --git a/software/io/c64/c64_crt.cc b/software/io/c64/c64_crt.cc index 74ea49ffc..48b617710 100644 --- a/software/io/c64/c64_crt.cc +++ b/software/io/c64/c64_crt.cc @@ -141,6 +141,8 @@ void C64_CRT::initialize(uint8_t *mem, uint32_t max_size) max_bank = 0xFF; highest_bank = 0; a000_seen = false; + mdp_sram_parts = 0; + mdp_eeprom_size = 0; bank_multiplier = 16 * 1024; } @@ -280,6 +282,65 @@ SubsysResultCode_e C64_CRT::read_chip_packet(File *f, t_crt_chip_chunk *chunk) } } + // Magic Desk Plus keeps its EEPROM and its 128K of battery-backed SRAM in + // the file. Both are reached through the same DF00 window on the machine, + // so that is the load address the chunks carry, and the bank field says + // which piece a chunk is. It has to: the size field of a CHIP header is 16 + // bits, so 128K cannot be one chunk, and it travels in four quarters. + // + // Bank 0 is the EEPROM, and its size chooses the page mask exactly as it + // does in VICE, which accepts an EEPROM image only at 8K or 32K. Banks 1 + // to 4 are the SRAM in address order. + // + // A Magic Desk cart without any of this is a plain Magic Desk: the two + // share CRT hardware type 19 and nothing in the header tells them apart. + if (load == 0xDF00) { + uint8_t *store = (uint8_t *)REU_MEMORY_BASE; + + if (bank == MDPLUS_BANK_EEPROM) { + if ((size != MDPLUS_EEPROM_8K) && (size != MDPLUS_EEPROM_32K)) { + printf("Magic Desk Plus EEPROM is $%4x bytes; it must be 8K or 32K.\n", size); + return SSRET_ERROR_IN_FILE_FORMAT; + } + if (mdp_eeprom_size) { + printf("Magic Desk Plus EEPROM already read!\n"); + return SSRET_EEPROM_ALREADY_DEFINED; + } + printf("Reading Magic Desk Plus EEPROM, size $%4x.\n", size); + memset(store, 0xFF, MDPLUS_EEPROM_32K); + mdp_eeprom_size = size; + } else if (bank <= MDPLUS_SRAM_CHUNKS) { + if (size != MDPLUS_SRAM_CHUNK) { + printf("Magic Desk Plus SRAM chunk %d is $%4x bytes; it must be $8000.\n", + bank, size); + return SSRET_ERROR_IN_FILE_FORMAT; + } + if (mdp_sram_parts & (1 << (bank - 1))) { + printf("Magic Desk Plus SRAM chunk %d already read!\n", bank); + return SSRET_EEPROM_ALREADY_DEFINED; + } + printf("Reading Magic Desk Plus SRAM chunk %d.\n", bank); + if (!mdp_sram_parts) { + memset(store + MDPLUS_SRAM_OFFSET, 0xFF, + MDPLUS_SRAM_CHUNK * MDPLUS_SRAM_CHUNKS); + } + store += MDPLUS_SRAM_OFFSET + (uint32_t(bank) - 1) * MDPLUS_SRAM_CHUNK; + mdp_sram_parts |= (1 << (bank - 1)); + } else { + printf("Magic Desk Plus store has no bank %d.\n", bank); + return SSRET_ERROR_IN_FILE_FORMAT; + } + + // The cartridge logic addresses this through g_georam_base, which is + // where the REU lives. That is why this cart prohibits the REU. + chunk->ram_location = store; + res = f->read(store, size, &bytes_read); + if (res != FR_OK) { + return SSRET_FILE_READ_FAILED; + } + return SSRET_OK; + } + // if ((load == 0xA000) && !a000_seen) { // a000_seen = true; // if (bank > 0) { // strange; first time A000 is seen, it is not bank 0. @@ -467,6 +528,14 @@ void C64_CRT::configure_cart(cart_def *def) { printf("Total ROM size read: %6x bytes.\n", total_read); + // A Magic Desk that brought its own non-volatile store is a Magic Desk + // Plus. Both are CRT hardware type 19 and the header does not distinguish + // them, so the store is the only thing that can. + if ((local_type == CART_DOMARK) && (mdp_sram_parts || mdp_eeprom_size)) { + printf("Magic Desk Plus store present; using the Plus mapping.\n"); + local_type = CART_MDPLUS; + } + uint16_t cart_type = CART_TYPE_NONE; uint16_t require = 0; uint16_t prohibit = 0; @@ -493,6 +562,25 @@ void C64_CRT::configure_cart(cart_def *def) cart_type = CART_TYPE_DOMARK; prohibit = CART_PROHIBIT_DEXX; break; + case CART_MDPLUS: + // 128 banks of 8K instead of 64, a page register at DE01, a control + // register at DE03 and a 256 byte window at DF00. The window lives + // in the memory the REU uses, so the two cannot both be on. + // + // The EEPROM image size chooses the page mask, exactly as it does + // in VICE: 8K masks the page register to 0x1F, 32K to 0x7F. A cart + // that brought only SRAM gets the 8K mask, which is what VICE also + // creates when it has to make an EEPROM image from nothing. + cart_type = CART_TYPE_MDPLUS; + if (mdp_eeprom_size == MDPLUS_EEPROM_32K) { + cart_type |= VARIANT_1; + } + // The window is the whole of DF00..DFFF and the registers sit at + // DE00..DE03, so nothing else may have either page: that rules out + // the UCI at DF1C, the sampler, an ACIA at either address, and the + // REU, whose memory this cart borrows. + prohibit = CART_PROHIBIT_IO; + break; case CART_OCEAN_8K: prohibit = CART_PROHIBIT_DEXX; if (a000_seen) { // special case! diff --git a/software/io/c64/c64_crt.h b/software/io/c64/c64_crt.h index 36b177452..db6b7b0e9 100644 --- a/software/io/c64/c64_crt.h +++ b/software/io/c64/c64_crt.h @@ -7,6 +7,24 @@ #define CARTS_DIRECTORY "/flash/carts" +// Magic Desk Plus keeps its non-volatile memory in the CRT file, in chunks at +// DF00, the address of the window they are reached through. The bank field says +// which piece a chunk is, because the size field cannot: it is 16 bits, so the +// 128K of SRAM does not fit in one chunk and is carried in four. +// +// bank 0 the EEPROM, 8K or 32K, the two sizes the format allows +// bank 1..4 the SRAM, in quarters, in address order +// +// The cartridge logic reaches the EEPROM in the first half of its area and the +// SRAM in the second. +#define MDPLUS_EEPROM_8K 0x2000 +#define MDPLUS_EEPROM_32K 0x8000 +#define MDPLUS_EEPROM_AREA 0x20000 +#define MDPLUS_SRAM_CHUNK 0x8000 +#define MDPLUS_SRAM_CHUNKS 4 +#define MDPLUS_SRAM_OFFSET 0x20000 +#define MDPLUS_BANK_EEPROM 0 + // Local definitions, NOT hardware select! typedef enum { CART_NOT_IMPL, @@ -14,6 +32,7 @@ typedef enum { CART_ACTION, CART_RETRO, CART_DOMARK, + CART_MDPLUS, CART_OCEAN_8K, CART_OCEAN_16K, CART_EASYFLASH, @@ -69,6 +88,8 @@ class C64_CRT int bank_multiplier; int machine; // 64 or 128 bool a000_seen; + uint8_t mdp_sram_parts; // bit per Magic Desk Plus SRAM chunk seen + uint32_t mdp_eeprom_size; // size of its EEPROM image, 0 when it has none e_known_cart local_type; uint8_t max_bank; uint8_t highest_bank; From 3eb4189f90091693d5de81f9be97eb6bd338cbee Mon Sep 17 00:00:00 2001 From: Enver Haase Date: Fri, 4 Sep 2026 09:15:34 +0200 Subject: [PATCH 3/3] Keep i_riscv/i_core/N_268 off a primary net Adding the Magic Desk Plus cartridge to the slot logic changes what Synplify emits for the U2+L, and that is enough to move i_riscv/i_core/N_268 onto a primary clock resource. A primary net inside the RiscV stops the FPGA from booting, so the check in target/fpga/u2plus_ecp5 aborts the build. Spell the net the way par reports it. The failing build also shows map disabling a PROHIBIT for "i_riscv/i_core/N_268_i" as not matching any net in the design; that spelling is not in this tree. The abort says three signals because par prints the same warning block three times and the check counts lines. There is one signal. --- target/fpga/u2plus_ecp5/u2p_ecp5.lpf | 1 + 1 file changed, 1 insertion(+) diff --git a/target/fpga/u2plus_ecp5/u2p_ecp5.lpf b/target/fpga/u2plus_ecp5/u2p_ecp5.lpf index 430eaa331..bcf18b7a9 100644 --- a/target/fpga/u2plus_ecp5/u2p_ecp5.lpf +++ b/target/fpga/u2plus_ecp5/u2p_ecp5.lpf @@ -435,4 +435,5 @@ PROHIBIT PRIMARY NET "i_riscv/ena_o_0" ; PROHIBIT PRIMARY NET "i_riscv/i_core/ena_o" ; PROHIBIT PRIMARY NET "i_riscv/i_core/i_decode/N_726_i" ; PROHIBIT PRIMARY NET "i_double_freq_bridge/busy" ; +PROHIBIT PRIMARY NET "i_riscv/i_core/N_268" ;