Part 1 – an overview, and some details of the On-Screen Display
Both the OneChipMSX and PC Engine cores on this site make use the ZPUFlex processor to provide a bootstrap, control and OSD module. Let’s take a closer look at this control module:
The control module needs to provide the following services:
- Load a ROM from SD card (holding the host core off the SD card during the process, if necessary)
- Provide an On-Screen Display, toggled by the F12 key. The on-screen display must be generated in a form that can be merged with the host core’s video output.
- Prevent keystrokes reaching the host core while the OSD is displayed
- Allow various options to be set, and the settings to be read by the host core
- Perform any high-level peripheral translation (keyboard-based gamepad emulation for the PC Engine core, mouse emulation for the OneChipMSX)
The control module is almost a complete system-on-chip in its own right – it has a CPU, program ROM/RAM, interrupt controller, keyboard controller, SPI controlller and video output – all within about 1600 logic elements. Block RAM usage is also fairly reasonable – the OneChipMSX’s control ROM is 8kb in size. I’d succeeded in keeping the PCEngine’s ROM to the same size up until I added support for long filenames, at which point the required buffer size became too large and I had to increase its size to 16kb. 768 bytes of this is buffer space, though, so if block RAM were tight, I could give the control module access to SDRAM for the buffers, which would free up more than enough code space to shrink the ROM again.
The OSD has a 1024 byte character ROM (8 bytes for each of 128 characters) and a 512 byte text buffer, which uses a modified form of 7-bit ASCII. Character 32-127 are standard, but there are some special symbols in characters 0-31 which are used by the OSD to render progress bars, sliders, arrows, checkmarks, etc.
The On-screen display requires access to the video sync pulses of the host core’s video output. The hardware counts the duration of both the high and low portions of each sync signal, and the firmware uses those counts to figure out the timing of the OSD window. The OSD output takes the form of two signals: ‘window’ and ‘pixel’, which the toplevel merges with the host core’s video signal, creating a translucent blue box with white writing within.
The actual merging of video signals is done with an OSD_Overlay component, which also provides optional scanline emulation. The relevant code looks like this:
if osd_window_in='1' then red_out<=osd_pixel_in & osd_pixel_in & red_in(5 downto 0); green_out<=osd_pixel_in & osd_pixel_in & green_in(5 downto 0); blue_out<=osd_pixel_in & '1' & blue_in(5 downto 0); elsif scanline='1' and scanline_ena='1' then red_out<='0' & red_in(7 downto 1); green_out<='0' & green_in(7 downto 1); blue_out<='0' & blue_in(7 downto 1); else red_out<=red_in; green_out<=green_in; blue_out<=blue_in; end if;
Scanline emulation works by darkening alternative video rows, which is done simply by shifting each colour value 1 bit to the right, halving its intensity. When not drawing a darkened scanline, bits 5 downto 0 are passed straight through, and when the OSD window is enabled, bits 7 and 6 of each colour value are replaced by a ‘1’ if the OSD is currently outputting a pixel, and ‘0’ if not. The exception to this is blue bit 6, which is always ‘1’ while the OSD window is enabled, giving the characteristic translucent blue background.
Next time I’ll look in depth at address decoding and how the firmware controls the hardware.