Porting a Core, DeMiSTified – Part 6 – 2021-03-12
When I created a ZPU-based control module for the MSX and PC-Engine cores way back when, I created a menu system which used a tree of static menu structures, in which each elements could be one of several types.
I supported function callbacks, checkmarks, cycles and even sliders. It worked fairly well and the footprint was reasonably small – but it doesn’t lend itself to building a menu dynamically.
In order to build an arbitrary menu from a MiST config string using this system I would need to support memory allocation, which means managing a memory pool, and if the menu ever needs to be re-generated, potentially dealing with fragmentation. Doing any of this without bursting through the self-imposed 12k limit is unlikely to happen!
So instead I’m borrowing a trick I used from the file-selector in my previous project – where I had one page of the static menu structure declared and “filled in” on the fly. As you scroll down the list, the file selector will simply re-scan the directory and discard the first ‘n’ entries. This has two serious limitations; firstly, sorting the files isn’t possible, and secondly, it could potentially slow down as you approach the end of a large directory.
I can’t think of a good way to deal with sorting directory entries that won’t require way more RAM than we have available, or be intolerably slow – however one way to approach it is simply to collect the files together on a PC before copying them to SD card. If you copy them en-masse to a new directory, then the file selector will see them in the same order in which they were copied. For this reason I’m content to ignore the file sorting problem for now – I can revisit this at a later stage if firmware space allows.
For dealing with dynamic menus created from MiST config strings, I’m using a similar approach. Every time the menu is displayed or scrolled, I fetch a fresh copy of the string from the core, and in displaying it I skip the first ‘n’ entries, to allow scrolling.
What I haven’t yet decided is how I’m going to approach fetching ROM-specific options when I come to port arcade cores. Since the menu-building code currently begins the SPI transfer of the config string, then fetches bytes one by one while building the actual menu, one option is to begin a file transfer, then allow the self-same code to transfer a sector of the file one byte at a time. The actual menu-building code could then be (almost) independent of where the config string’s coming from.
So having implemented a menu both for adjusting options and selecting files, we now need to perform the actual file upload.
The MiST framework has a well-defined interface for this, in the form of the data_io module. This has its own SPI “chip select” signal, and responds to three commands. One enables or disables data transfer, one sets the “index” of the ROM (derived from its file extension, and which menu item was used to load the ROM) – which allows the core to handle ROMs of different types and automatically remap them appropriately – and finally a command to perform the actual data transfer.
Sounds simple enough, yes?
When I said that the MiST framework has a well-defined interface for ROM transfer, I was only telling half the story; in truth it has *two* such interfaces!
In the interface I just described, the MiST board’s control CPU loads the data from SD card, one sector at a time, then passes that data to the FPGA core. Both the FPGA and the SD card are on the same SPI interface; the control CPU’s data-out is connected to data-in on both the SD card and the FPGA. Both the FPGA and the SD card have a data-out signal which is connected to the control CPU’s data-in.
This implies, of course, that the SD card’s data out is connected to the FPGA’s data out, since they both go to the same pin on the control CPU.
Some MiST cores exploit this fact, and employ a “direct” mode to speed up ROM loading (a trick which was inherited from the original Minimig’s disk support) – the FPGA pin that usually handles SPI data-out temporarily becomes data-in, which allows the FPGA to capture data directly from the SD card. The control CPU is still generating the SPI clock, but it doesn’t have to receive and then relay the data to the FPGA – instead it coordinates a direct transfer.
This is why some MiST cores define the SPI_DO pin as bidirectional – and this complicates matters. Because we’re moving the external CPU into the FPGA design itself, we need to replicate the logical interconnections between the SD card and the guest core – but bidirectional signals in an FPGA are only allowed if they connect to a toplevel pin.
What this means in practice is that if we want to adapt and wrap a MiST core which uses this bi-directional trick, we can’t leave the guest core completely unmodified – we will have to replace this bi-directional signal with a pair of signals, one for each direction.
In other words, we’re replacing:
input SPI_DI, // Data from the MCU
inout SPI_DO, // Data to the MCU, or from the SD card
input SPI_DI, // Data from the MCU
input SPI_SD_DI, // Data from the SD card
output SPI_DO, // Data to the MC
In the toplevel we connect SPI_DI to a signal called spi_toguest, SPI_DO goes to spi_fromguest, and SPI_SD_SI is connected to the MISO pin on the SD card, and internally replaces the connection to SPI_DO on the data_io module.
All that remains is to multiplex the control module’s spi input between spi_fromguest and the SD card’s MISO pin, depending upon the SD card’s chip select.
Unfortunately the need for this workaround to bidirectional signals means that (a) the board specific toplevels can’t be universal, and (b) the guest core requires modification.