The Adventure Begins 

The enthralling tale of Dreamdrive64 finds its origin in the PicoCart64 project. In the sun-soaked summer of 2022, I embarked on a thrilling new chapter, fueled by an abundance of free time and an unwavering passion for custom hardware projects. This venture promised an exciting opportunity to contribute, learn, and grow.

As autumn leaves began to fall in 2022, I emerged as the sole active member of the project. However, I was far from lonely, as the project's lively Discord community brimmed with inspiring individuals eager to share their knowledge and expertise. I am eternally grateful for their infinite patience and invaluable experience.

Driven by an aspiration to forge a unique creation, I decided to infuse the project with a dash of personal branding. Embracing its open-source roots, I envisioned this as the first genuine product that I could proudly build and sell. Thus, the Dreamdrive64 was born, ingeniously crafted upon the foundation of PicoCart64's "v2" hardware.

Project Background 

The PicoCart64 was a straightforward PCB that allowed users to solder a Raspberry Pico (or clone) and flash a small ROM onto it. While functional, its capabilities were limited, supporting only one small ROM at a time.

In contrast, the "v2" hardware featured a bank of eight PSRAM chips, providing 64MB of non-persistent storage for loading ROMs from an SD card. The design also included two RP2040 chips responsible for communication with the N64, the SD card, and the PSRAM. An ESP32 was integrated into the board, offering the potential for innovative wireless features or simply empowering developers to get creative.

The v2 branch was quite basic. Both RP2040 chips were booted from the same flash chip, and a rudimentary test setup enabled reading from the SD card, writing/reading to the PSRAM in SPI mode, and loading code from flash into RAM. While the foundation was set, significant work remained to transform it into a fully-fledged ROM cart.


The psram and flash, are connected via the rp2040's qspi lines. These lines are typically only used to communicate with flash. A demux is included on the board to address the individual psram chips. The two rp2040's we will call MCU1 and MCU2. The following is the broad overview of the connections:




I wrote a menu using the open source n64 sdk, libdragon, that contains a simple file explorer to browse the contents of the SD card and load a selected rom file. 

The Dreamdrive64 has it's own address space with which a rom can interact with. In my case I added the ability for the rom to pass through filesystem commands to read from the sd card. 

These requests flow: ROM->N64->MCU1->Serial comms->MCU2->SD card->disk_read->MCU2->serial comms->MCU1->N64->ROM



The most significant and frustrating challenge throughout the project was establishing communication between the SSI hardware (QSPI) and the PSRAM. A rudimentary 1-bit communication setup allowed data exchange between the MCUs and the PSRAM, but it lacked the speed required to efficiently service the ROM data to the N64's bus.

Although the N64's bus speed can be "patched" to run slower, no commercial ROMs ever utilized this feature, resulting in some games failing to run without the "stock" bus speed.

Achieving "quad mode," or 4-bit SPI transfers with the SSI hardware, was crucial. Inspired by the Pico-SDK's boot2 files and after much trial and error, I managed to read data from the PSRAM chips using the RP2040's XIP address space. This approach allowed me to make read requests via a pointer address, relying on hardware rather than the software method employed in the 1-bit mode setup for 8-bit reads and writes.

To enable "quad mode," the PSRAM chips must receive a 0x35 command, sent via the SSI hardware configured in the 1-bit software mode mentioned earlier. The SSI hardware configurations suggest that serial commands, serial addresses, and quad data should be possible without issuing a 0x35 quad mode enable command to toggle the state, but I encountered difficulties in getting it to work properly. 

This ssi config is used after issuing the quad mode enable command:

void qspi_init_qspi() {
    ssi->ssienr = 0;
    ssi->baudr = QSPI_QUAD_MODE_CLK_DIVIDER; // Depends on the rp2040 clock speed 
    ssi->ctrlr0 =
            (SSI_CTRLR0_SPI_FRF_VALUE_QUAD << SSI_CTRLR0_SPI_FRF_LSB) |  // Quad SPI serial frames
            (31 << SSI_CTRLR0_DFS_32_LSB) |                             // 32 clocks per data frame
            (SSI_CTRLR0_TMOD_VALUE_EEPROM_READ << SSI_CTRLR0_TMOD_LSB); // Send instr + addr, receive data
    ssi->spi_ctrlr0 =
            (0xEB << SSI_SPI_CTRLR0_XIP_CMD_LSB) |  // Fast Read command
            (6u << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | // 6 wait cycles for EB command
            (6u << SSI_SPI_CTRLR0_ADDR_L_LSB) |    // 24-bit addressing 
            (SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A  // Command and address both in quad format
                    << SSI_SPI_CTRLR0_TRANS_TYPE_LSB);

    ssi->rx_sample_dly = 2;
    ssi->ssienr = 1;

The `rx_sample_dly` needs to be modified slightly based on the clock speed of the rp2040s. Experimentally clocks under 170MHz don't need any adjustment (1 is fine), above 170 and below 266, 2 is a good sample delay, and above 266, 3 or 4 are needed.