Skip to content

Magic Desk Plus cartridge support (#727) — simulated only, never run - #844

Draft
enver-haase wants to merge 5 commits into
GideonZ:masterfrom
enver-haase:feat/magic-desk-plus
Draft

enver-haase wants to merge 5 commits into
GideonZ:masterfrom
enver-haase:feat/magic-desk-plus

Conversation

@enver-haase

Copy link
Copy Markdown
Contributor

Implements Magic Desk Plus, requested in #727 for Murder on the Mississippi Remastered.

This has never run. Not on hardware, not as a bitstream, not against the game. I have no synthesis toolchain here and my C64U is bricked, so what follows is code plus a simulation, and nothing else. Somebody has to load Murder on the Mississippi Remastered on a real machine before any of this is believed. I would rather say that at the top than bury it.

What the format actually is

The cartridge is a Magic Desk with three things added: DE00 gains bit 6 as a bank bit, so it selects 128 banks of 8K instead of 64; DE01 becomes a 256-byte page register; DE03 becomes a control register whose bit 0 picks the SRAM half and bit 5 switches between 128K of battery-backed SRAM and an 8K or 32K EEPROM. Both are reached through a 256-byte window at DF00, readable and writable, which stays served when the ROM is switched off — the file system the format ships with relies on that.

Magic Desk and Magic Desk Plus are the same CRT hardware type, 19. The VICE patch that comes with the format extends magicdesk.c rather than adding a cartridge, and magicdesk_crt_attach() derives nothing from the file but a bank mask taken from the highest bank present. What turns the SRAM and EEPROM on there is the user supplying an image for them. So there is nothing in the header to detect, and this follows the same idea: a cart that brought its non-volatile memory with it is a Plus.

Where the memory lives

In the region the REU and GeoRAM already share, not in an area of its own. That was the second attempt: 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 exactly there, so an area of its own would have made this a feature one platform could not have. The shared region exists everywhere. The cost is that this cart and the REU cannot both be on, which is what GeoRAM already does and what def->prohibit already handles.

The store travels in the CRT as a chunk at DF00 — the address of the window — holding the EEPROM first and the SRAM behind it, in the order the logic addresses them. This layout is a proposal. The format defines no CRT representation for its non-volatile memory, so if @crystalct has one in mind, say so and I will follow it instead.

Red, then green

fpga/cart_slot/vhdl_sim/tb_magic_desk_plus.vhd drives the registers as the machine does and checks the address the logic produces, under GHDL:

result
against this branch 20 of 20 checks pass
against the unchanged all_carts_v5.vhd 17 fail

The three that pass either way are the power-up defaults, where the expected value is zero and an unimplemented cart is zero anyway.

It also pinned down something the sources do not state: cart_variant is sampled only while the cartridge is in reset, together with cart_logic_d. My first testbench changed the variant without one and got the old value. That is now a documented step in the test.

For @GideonZ, one thing I cannot answer

rom_addr(g_max_cart_bits-1 downto 13) bounds the bank width, and the software lays banks at a 16K stride (bank_multiplier = 16 * 1024), so 128 banks need g_max_cart_bits >= 21. The U2+ top levels pass 22; the default in slot_server_v4.vhd is 20. What the U64 and C64U pass I cannot see — fpga/fpga_top/ holds only ultimate_fpga, cyclone4_test, ecp5_test and video_fpga, so those top levels are not in this repository. At 20, this cart is limited to 64 banks and a 1 MB image will not work. Worth checking before anyone builds a bitstream.

What is verified, and it is not much

  • ghdl -a on the changed all_carts_v5.vhd: clean, exactly as the unchanged file is.
  • The testbench above, red and green.
  • make u64ii_no_esp in ghcr.io/gideonz/riscv from a tree with every output/ and result/ under target/ removed first: exit 0, no error, no new warning from c64_crt.cc.

What is not verified

Everything else. No bitstream was built, so the cartridge logic has never been synthesised, let alone run. No device has seen this. The game has not been tried. The 10 ms an EEPROM write physically takes is not modelled — a write through the window completes immediately, which is more permissive than the real part, so software that respects the delay will work and software that tests for it will not see it. The EEPROM size is fixed at the 32K variant because the file does not say which part was fitted.

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#727.
@enver-haase

Copy link
Copy Markdown
Contributor Author

Corrected the EEPROM handling: VICE does say which part is fitted, and I had claimed it does not.

magicdesk_eeprom_open_image() takes the size from the image file and accepts exactly two:

  if (size != 8192 && size != 32768) {
    log_message(LOG_DEFAULT,
                "MAGICDESK: Invalid EEPROM image size (must be 8K or 32K).");
    return -1;
  }

and the page mask follows from it, in both io2 handlers: page_mask = (magicdesk_eeprom.size == 8192) ? 0x1f : 0x7f. Those are the two masks this branch already implements in the cartridge logic, so only the software side was wrong.

It now does the same. The store travels as chunks at DF00 and the size says which is which — 8K or 32K is the EEPROM, 128K is the SRAM, and the three cannot be confused. A size that is none of them is refused rather than loaded. The variant, and with it the mask, follows the EEPROM image size instead of being fixed.

One consequence worth naming: a cart that brings only SRAM now gets the 8K mask, not the 32K one I had picked. That is what VICE creates when it has to make an EEPROM image from nothing, so it is the more faithful default.

Rebuilt: make u64ii_no_esp from a cleaned tree, exit 0, no new warning. The cartridge logic and its testbench are untouched by this — the masks were right there already.

Everything in the opening paragraph still stands: none of this has run on hardware, and Murder on the Mississippi Remastered has not been tried.

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#727.
@enver-haase

Copy link
Copy Markdown
Contributor Author

Adversarial pass over my own branch. Three findings, one of them fatal. Two are fixed; the third turned out not to be mine to fix.

Fatal: a 128K chunk cannot exist in a CRT

The size field of a CHIP header is 16 bits — uint16_t size = get_word(chip_header + CRTCHP_SIZE). My MDPLUS_SRAM_SIZE was 0x20000, so the comparison could never be true, and save_crt would have written fo->write(cc->ram_location, size, ...) with a truncated length. 64K does not fit either.

Neither the simulation nor the build could have caught this: the testbench exercises the cartridge logic, not the file parser, and a constant that never matches compiles happily.

Fixed by using the bank field, which a DF00 chunk does not otherwise need: bank 0 is the EEPROM at 8K or 32K, banks 1 to 4 are the SRAM in quarters of 32K, in address order. That also removes an ambiguity I had built in, where 32K would have meant both "EEPROM" and "a quarter of the SRAM". A chunk of any other shape is now refused instead of loaded.

Real: the prohibition missed the DF00 page

I had CART_PROHIBIT_DEXX | CART_REU. But

#define CART_PROHIBIT_DFXX (CART_ACIA_DF | CART_REU | CART_MAXREU | CART_UCI | CART_SAMPLER)

so the UCI at DF1C — inside the window — the sampler, an ACIA at DF00 and CART_MAXREU were all left enabled. This cart owns DE00..DE03 and the whole DF00 page. Fixed: CART_PROHIBIT_IO.

Not a defect: saving is explicit here, by design

I was going to add an automatic write-back, on the grounds that "battery-backed" means the player should not lose their notebook. Checking first was the right call: nothing on this device persists cartridge state by itself. save_crt() has exactly one caller, the "Save Cartridge" menu action, sitting beside "Save REU" and "Save MP3". EasyFlash's flash writes and GMod2's EEPROM go the same way.

So this is a house convention, not a gap, and Magic Desk Plus follows it rather than becoming the one cart that saves on its own. The consequence should be said plainly, because this cart is unusual in how much it depends on it: the reason Murder on the Mississippi Remastered uses this format at all is that it writes notes and progress continuously. On real hardware a battery keeps them. Here they live in the shared memory until the player picks Save Cartridge, and a power cycle without that loses them.

If that trade should come out differently for this cart, it is a UX call rather than a technical one, and it is yours.

Unchanged

The cartridge logic and its testbench are untouched by all three: the address map, the register decode and the 0x1F/0x7F masks were right from the start. REU_MEMORY_BASE, __reu_ram_start and g_georam_base are all 0x1000000, so the software and the gateware do mean the same memory.

One minor note carried over from #822: the store is filled with a plain memcpy through a cached address, while the AX88772 driver reaches its DMA buffers through BIT31. No effect today, since no shipping target defines DATACACHE.

Rebuilt after both fixes: make u64ii_no_esp from a cleaned tree, exit 0, no new warning.

Still true, and still the most important line here: none of this has run. No bitstream, no device, and the game has not been tried.

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.
Gideon added both spellings of the net in 8806a43, so the LPF line
this branch added is now upstream's. Keep upstream's version of the
file, which carries N_268_i alongside N_268.
@chrisgleissner chrisgleissner added 3.16 Targets 3.16 release enhancement labels Sep 9, 2026
@chrisgleissner
chrisgleissner changed the base branch from test-merge to master September 13, 2026 12:42

@chrisgleissner chrisgleissner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Changes Requested

Thank you for the detailed implementation and GHDL simulation testbench (tb_magic_desk_plus.vhd), @enver-haase.

This PR is not yet ready for merge. As noted in the description, this implementation has only been run in VHDL simulation and has not been verified on target firmware/hardware or against real cartridges/games.

Before this PR can be considered for merge, it must be enriched with an automated end-to-end Python test suite in tests/e2e/io/c64/ (and registered in run-tests and tests/README.md), following the established pattern of other recent cartridge fixes in this repository.

Recent Cartridge E2E Test References

Please consult these recent cartridge E2E tests for reference on how synthetic CRT generation and host-side 6502 test routines are structured:

  1. tests/e2e/io/c64/c64gs_cartridge_test.py (commit 7d9bf4bd):
    Generates a synthetic 64-bank Type 15 CRT, uploads it via device.runners.upload("run_crt", ...), copies a 6502 test routine to $C000, and verifies bank switching via IO1 accesses with marker validation in screen RAM ($0400).
  2. tests/e2e/io/c64/comal80_cartridge_test.py (PR #899 / commit c7464da2):
    Builds a synthetic Comal 80 CRT to test bank selection and cartridge-off bit handling.
  3. tests/e2e/io/c64/ocean_cartridge_test.py (commit 2db13b9f):
    Tests Ocean 16K bank switching using synthetic CRT generation.
  4. tests/e2e/io/c64/ultimax_cartridge_test.py (PR #882 / commit fd756470):
    Verifies Ultimax CRT loading and VIC stream output.

Required Changes Before Merge

  1. Add E2E Test Suite (tests/e2e/io/c64/magicdesk_plus_cartridge_test.py):

    • Construct a synthetic Type 19 Magic Desk Plus CRT containing ROM bank chunks, an EEPROM chunk (bank 0 at $DF00), and SRAM chunks (banks 1..4 at $DF00).
    • Run a 6502 test routine from RAM ($C000) to validate:
      • DE00: Bank selection across bits 0..6 (128 banks) and bit 7 ROM disable (exrom_n).
      • DE01 & DF00 window: Page selection via DE01 and window read/write data correctness at $DF00..$DFFF.
      • DE03: EEPROM select (bit 5 = 0) vs SRAM select (bit 5 = 1) and SRAM portion select (bit 0).
      • Window persistence: Confirming that $DF00..$DFFF remains served and writable even when the ROM is switched off (DE00 bit 7 set).
    • Register the new test suite in run-tests and tests/README.md.
  2. Verify FPGA Generics:

    • Double-check that top-level FPGA generics on target platforms set g_max_cart_bits >= 21 so that all 128 banks of 8K are addressable.

Please update the PR with the requested E2E tests once verified on target hardware/firmware.

@enver-haase

enver-haase commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Please update the PR with the requested E2E tests once verified on target hardware/firmware.

Is this not for @GideonZ to provide the (testable) gateware?

@crystalct

Copy link
Copy Markdown

In the latest update, the Vice Team introduced support for the Magic Desk Plus but strangely decided to assign it a new, additional ID—different from 19. The new ID is 87.

@GideonZ

GideonZ commented Sep 14, 2026

Copy link
Copy Markdown
Owner

I assigned TwoMegabyter to 87.. hehe

@GideonZ

GideonZ commented Sep 14, 2026

Copy link
Copy Markdown
Owner

I saw the FPGA changes. They are quite significant. And, I am wondering why the cartridge RAM area was not used. I don't think cartridges should use the REU ram. Did you have a special reason for using REU ram? @enver-haase

@chrisgleissner

chrisgleissner commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

@enver-haase I was referring to your initial statement: “This has never run. Not on hardware, not as a bitstream, not against the game.”

We normally require tests before merging a PR, ideally E2E tests. If testing depends on Gideon providing a bitstream, could you make that dependency explicit in the PR and clarify which tests remain outstanding and who would run them? It would also help to distinguish what the existing simulation tests establish from what still needs hardware verification. Keeping the PR in draft until that verification is complete would make its status clearer.

By the way, were you able to get your C64U repaired? You opened several other PRs a few weeks ago with similar notes about missing hardware testing. Once your setup is working and the necessary builds are available, could you revisit those PRs, test them on your hardware, and update them with the results and any fixes needed? It's always a good idea for us firmware devs to test on our own rig first and publish red/green results.

@enver-haase

Copy link
Copy Markdown
Contributor Author

@GideonZ — fair question, and the reasoning belongs here in the thread rather than
only in the commit message. It comes down to size.

Magic Desk Plus carries 128K of battery-backed SRAM plus an 8K or 32K EEPROM, so
160K in the largest case. The cartridge RAM area is 64K on every target:

target cart RAM cart ROM
u2 0x0EF00000x0F00000 (64K) 0x0F000000x1000000
u64 0x00EF00000x00F00000 (64K) 0x03C000000x04000000
u64ii 0x00EF00000x00F00000 (64K) 0x03C000000x04000000

So the SRAM does not fit in the cartridge RAM area, not even at half its size. The
obvious place to grow into is the megabyte directly above it, 0x0F000000x1000000,
which is free on u64 and u64ii and sits right below REU_MEMORY_BASE. On the u2 that
same address is where the cartridge ROM starts. Taking it would make Magic Desk Plus a
U64-family cartridge.

That is what sent me to the memory the REU and GeoRAM share: it is the only region of
that size present on all targets, and the price — this cart and the REU cannot be on at
the same time — is one the code already knows how to charge, via the prohibit mechanism
GeoRAM uses.

I share your instinct that cartridges should not be reaching into REU memory. Two ways
out, and the choice is yours, not mine:

  1. Give the cart its own area above the cartridge RAM on u64/u64ii and declare Magic
    Desk Plus unsupported on the u2. Cheap change — the base address moves, the prohibit
    flag drops, everything else stays.
  2. Keep the shared region and accept the mutual exclusion, as GeoRAM does today.

Tell me which and I will move the branch to it.

One more thing worth deciding while this is open: @crystalct points out VICE gave Magic
Desk Plus its own CRT id 87 rather than extending 19, and you have assigned 87 to
TwoMegabyter here. As it stands this PR detects a Plus the way the VICE patch does — a
type 19 image that brought non-volatile memory with it — so a CRT written by the new VICE
will not be recognised at all. Whether the loader should also accept 87, and how that
squares with TwoMegabyter, is a call I cannot make.


@chrisgleissner — yes, the C64U is working again as of today, and I have set this PR to
draft as you asked. Let me be precise about what stands behind it and what does not.

Verified, and only this: tb_magic_desk_plus drives the registers the way the machine
does and checks the addresses the slot logic produces — 20 checks pass against this branch,
17 of them fail against the unchanged file. make u64ii_no_esp builds clean. That is
simulation of the cartridge logic plus a compile, nothing more.

Not verified, and not verifiable by me: anything on real hardware. There is no synthesis
toolchain here, so the gateware change cannot become a bitstream without @GideonZ. Until
that bitstream exists there is no firmware to flash and no device behaviour to measure,
which makes a red/green E2E for this PR impossible rather than merely outstanding. Once a
bitstream exists I will run Murder on the Mississippi Remastered against it on my own
machine and post what it does, pass or fail.

@enver-haase
enver-haase marked this pull request as draft September 16, 2026 01:13
@chrisgleissner

Copy link
Copy Markdown
Collaborator

@enver-haase Congrats on fixing the C64U! Did you end up soldering your own JTAG adapter based on an FT232H and did you use the unbrick tool in this repo?

# Conflicts:
#	software/io/c64/c64_crt.cc
@crystalct

Copy link
Copy Markdown

Ultimately, it is Team Vice that defines the CRT IDs, and everyone else using that format falls into line; otherwise, they would create ambiguities and CRTs that are incompatible depending on the platform used. When the TwoMegabyter type becomes "official," it will get its own ID—specifically, the first one available at that time.

@enver-haase

Copy link
Copy Markdown
Contributor Author

@chrisgleissner — bought, not built: an FT232H breakout of the kind recovery/u64ii/README.md suggests. The only soldering was the JTAG pin headers, which that same README lists under "Hardware needed" — so this was the documented path rather than an improvisation.

And yes, the recovery tooling in recovery/u64ii/ is what brought it back.

The machine has been in steady use since: everything measured in #843 and #915 today was measured on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.16 Targets 3.16 release enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants