Part 6 – resource sharing
So far we have the control module merging an on-screen display with the underlying host core’s video output, responding to keypresses and running a simple on-screen menu. The largest single addition now will be SD card access, which I will explore in the next part. In this part, however, I’m going to talk about resource sharing.
Some cores, like the test-pattern generator we’ve been using so far or the PC-Engine core, make no use of the keyboard or SD card, so giving sole access to the control module is no problem. If, on the other hand, the underlying core does make use of keyboard and SD card (such as the OneChipMSX core, for instance) we need to have some way of arbitrating for access to these resources.
The keyboard is fairly easy to handle. The PS/2 protocol uses two bidirectional data lines, which in the FPGA core are generally split into -in and -out signals, so we have clk_in, dat_in, clk_out, dat_out, and the toplevel will combine these into two tri-state pins.
To block key events from reaching the underlying core we simply have to feed it logic high on the clk_in and dat_in signals, while making sure that the toplevel receives logic high on the clk_out and dat_out signals. This leaves the bus tri-stated, so that key events can still reach the control module. Timing-wise there is the potential problem that we might divert the keyboard in the middle of a keystroke being received – but in practice, since pretty much everything the OSD does is in direct response to a keystroke, this doesn’t seem to be a problem.
To implement this, we simply create host_ps2k_* signals which we connect to the host core in place of the direct ps2k_* signals, then combine them like so:
-- Block keyboard signals from reaching the host when host_divert_keyboard is high. host_ps2k_dat_in <= ps2k_dat_in or host_divert_keyboard; host_ps2k_clk_in <= ps2k_clk_in or host_divert_keyboard; ps2k_dat_out <= host_ps2k_dat_out or host_divert_keyboard; ps2k_clk_out <= host_ps2k_clk_out or host_divert_keyboard;
where host_divert_keyboard is a signal which comes from the Control module.
The SD card is a bit trickier. I’m going to assume here that the host core uses simple SPI access to the SD card, so we have spi_miso, spi_mosi, spi_clk and spi_cs signals from the toplevel.
Again, instead of giving the host core direct access to these, we’re going to create host_spi_* signals, and also ctrl_spi_* signals, then multiplex between them, like so:
-- Multiplex SD card signals between the host and control module spi_clk <= ctrl_spi_clk when host_divert_sdcard='1' else host_spi_clk; spi_mosi <= ctrl_spi_mosi when host_divert_sdcard='1' else host_spi_mosi; spi_cs <= ctrl_spi_cs when host_divert_sdcard='1' else host_spi_cs; host_spi_miso <= '1' when host_divert_sdcard='1' else spi_miso;
Problem solved? Well… no.
While this takes care of the plumbing, there’s a semantic problem. The Control module doesn’t know what the host’s doing with the SD card, so if it asserts host_divert_sdcard while the host’s in the middle of a read, the host’s either going to hang, receive bad data or crash. Worse, if the host’s in the middle of a write, the card’s contents will be corrupted.
This is a very difficult problem to solve in a general way, but for the OneChipMSX core I took the following approach:
- At startup, the control module puts the host core in reset / boot mode
- The control module accesses the SD card, loads the firmware and passes it to the host core
- The control module allows the host core to start, and subsequently doesn’t touch the SD card.
- If the user saves a configuration through the OSD, the control module puts the host core in reset, re-initializes the card, saves the configuration data, then restarts the host core by releasing reset.
This approach ensures that neither the host nor the OneChipMSX core find themselves accessing the SD card in an unexpected state. The downside, of course, is the host core being reset by saving a configuration – but that’s preferable to a potentially subtle data corruption bug if the control module hijacks the card and leaves in an unexpected state for the host core. There’s still the chance for data corruption if a particularly determined user saves a configuration while the MSX core is writing to the SD card, but that’s hopefully a pretty rare corner case. It would also be possible to hedge against it by having the control module monitor SPI activity on the host side, but that’s beyond our current scope.
So where do the host_divert_keyboard and host_divert_sdcard signals come from? A new hardware register, defined at 0xFFFFFFEC. Three bits are defined in CtrlModule/Firmware/host.h
- HOST_CONTROL_RESET – bit 0
- HOST_CONTROL_DIVERT_KEYBOARD – bit 1
- HOST_CONTROL_DIVERT_SDCARD – bit 2
The new register is decoded like this:
when X"EC" => -- Host control mem_busy<='0'; host_reset_n<=not mem_write(0); host_divert_keyboard<=mem_write(1); host_divert_sdcard<=mem_write(2);
The github repo now contains a new tag – Step5 – in which the host core has been moved to a new module, making the plumbing in the virtual toplevel easier to read. The host core now responds to keypresses, which cause a vertical offset of the test pattern, and opening the OSD blocks them.