In a FPGA Companion based setup the workload is distributed between the FPGA and a seperate MCU. The FPGA implements the hardware of the original device (e.g. the Atari ST) and the MCU mainly deals with modern peripherals like USB keyboards, mice and SD cards with their file systems (FAT, exFAT).
This setup requires that the FPGA and the MCU communicate permanently so the MCU can instruct the FPGA to display the on-screen-display (OSD), to send keyboard and mouse events into the core and to receive floppy disk read requests from the FPGA. And since the SD card is connected to the FPGA as well, the MCU needs to request SD card data from the FPGA. This is all done via a SPI bus.
This part of MiSTeryNano is a work and progress and the information below may be outdated. The basic principles should still be valid.
It's nowadays possible to use the Tang Nano 20k's internal BL616 MCU or an
externally connected MCU like a Raspberry Pi Pico or a M0S Dock.
Using the internal MCU has the advantage that no additional component
is required. Development usually takes place with an external MCU.
At time of project start it wasn't reasonable possible to make use of TN20k internal BL616 MPU due to both PCBA and firmware limitations. This had been resolved over time by a new TN20k assembly version 3921 and availability of the fused/encryped bl616_fpga_partner firmware.
The SPI bus between the MCU and the FPGA consists of five connections e.g. for a M0S Dock/BL616. For other MCUs see the READMEs in their respective subdirectories.
| M0S Dock <-> FPGA | TN20k int. MCU <-> FPGA | ||
|---|---|---|---|
| CSN | GPIO12 -> 56 | GPIO0 -> 86 | SPI select, active low |
| SCK | GPIO13 -> 54 | GPIO1 -> 13 | SPI clock, idle low |
| MOSI | GPIO11 -> 41 | GPIO3 -> 76 | SPI data from MCU to FPGA |
| MISO | GPIO10 <- 42 | GPIO2 <- 75 | SPI data from FPGA to MCU |
| IRQN | GPIO14 <- 51 | GPIO13 <- 69 | Interrupt from FPGA to MCU, active low |
Usually the SPI bus is run at 20Mhz.
The SPI master is the MCU and the SPI target is the FPGA. Thus, only the MCU initiates SPI communication. The SPI is operated in MODE1 with clock idle state being low and data being sampled on the falling edge of clock.
An interrupt signal (IRQ) is being used to allow the FPGA to notify the MCU of events. This avoids the need to constantly poll the FPGA from the MCU and the FPGA can simply raise the interrupt signal whenever it requires the cooperation of the MCU. Currently the following interrupt sources are being used:
| bit | name | usage |
|---|---|---|
| 0 | SYS | FPGA has been (re-)initialized (cold boot) or port data has been received |
| 1 | HID | DB9 joystick event detected by FPGA |
| 3 | SDC | FPGA requests SD card sector translation |
SPI communication is initiated by the MCU by driving CSN low.
The basic SPI communication runs between spi.c on the
MCU side and mcu_spi.v on FPGA side.
The first byte of each message identifies the target inside the FPGA the MCU wants to address. Currently implemented are:
| value | name | description | MCU implementation | FPGA implementation |
|---|---|---|---|---|
| 0 | SYS | Generic system control | sysctrl.c |
sysctrl.v |
| 1 | HID | Human Interface Devices, e.g. keyboard & mice | usb_host.c |
hid.v |
| 2 | OSD | On-Screen-Display | osd_u8g2.c |
osd_u8g2.v |
| 3 | SDC | SD Card | sdc.c |
sd_card.v |
| 4 | AUDIO | Audio output | TBD | TBD |
Any data after the first target byte is being sent to the target
inside the FPGA for further parsing. The communication inside the FPGA
between mcu_spi.v and the various targets is byte-wide using
four signals:
| signal | description |
|---|---|
strobe |
Indicate the arrival of a data byte for the target |
start |
Valid for the first byte of a message |
dout[8] |
Databyte from MCU to target |
din[8] |
Databyte from target to MCU |
The first byte transferred with start active (the second byte
within each SPI message) usually is a command byte for the target.
The SYS (system control) target currently supports these commands:
| value | name | description |
|---|---|---|
| 0 | SPI_SYS_STATUS |
Request status from FPGA |
| 1 | SPI_SYS_LEDS |
Send a LED status into the FPGA |
| 2 | SPI_SYS_RGB |
Send RGB color info into FPGA |
| 3 | SPI_SYS_BTN |
Request button state from FPGA |
| 4 | SPI_SYS_SET |
Set a variable in the FPGA |
| 5 | SPI_SYS_IRQ_CTRL |
Interrupt control |
| 6 | SPI_SYS_IRQ_SRC |
Request interrupt source |
| 7 | SPI_SYS_PORT |
Handle port IO (e.g rs232) |
| 8 | SPI_SYS_READ_CFG |
Read configuration |
The SPI_SYS_STATUS command currently just returns $5c and $42 in
the Atari ST core. This can be used to check if the FPGA has started
up and has the right core installed. Other cores may use different
bytes to allow the MCU to identify the type of the running core.
The SPI_SYS_LEDS command allows to control LEDs 4 and 5 on the Tang
Nano from the MCU. The other four LEDs are controlled by the core itself.
This may not always be wired up and some releases may use all LEDs by the
FPGA directly to signal certain operation states and events.
The SPI_SYS_RGB command can be used to send a 24 bit (three bytes)
RGB (red/green/blue) value into the core which it can e.g. use to drive
the on-board ws2812 led.
The SPI_SYS_BTN command will return the state of the two S0 and
S1 buttons on the Tang Nano 20k. This can e.g. be used to control the
MCU via those buttons. Bit 0 typically reports the reset button and
bit 1 reports a user button if preset which may e.g. used to open the
on-sreen-display.
The SPI_SYS_SET command has two data bytes. The first byte is
the ASCII ID's of a variable to be set and the second byte is a 8 bit
value. E.g. the Atari ST core supports the configuration of the
chipset between three values (ST, Mega ST and STE) via the OSD. This
setting is sent using the ID 'C' (for Chipset) with a value of 0 to
2. The use of these values is core specific.
With SPI_SYS_IRQ_CTRL the first data byte allows to acknowledge
the eight possible interrupt sources inside the FPGA. The second data
byte will return the pending interrupts. The eight interrupts sources
are currently mapped to the SPI targets, e.g. the SD card target 3 is
mapped to interrupt bit 3.
With SPI_SYS_IRQ_SRC command allows to request the system IRQ
source. The source is returned as a bit field with the second data
byte. Currently supported are bit 0 which indicates a FPGA cold boot
detection and bit 1 which indicates the port data is available. This
is currently being used whenever data has been received via the "AT
Wifi" emulation which allows to simulate a RS232 port over TCP/Telnet
on MCUs that support WiFi. But 2 is set if a FPGA button change has
been detected which could then be read (and acknowledged) using the
SPI_SYS_BTN command.
The SPI_SYS_PORT command handles port redirection for RS232
implementation or the like. The first payload bytes specifies a
subcommand. Currently three subcommands are implemented. These are
SPI_SYS_PORT_STATUS, SPI_SYS_PORT_PUT and
SPI_SYS_PORT_GET allowing the MCU to read the ports status
(e.g. free FIFO space or bitrates set by the core), to send bytes into
the core or to read bytes back from the core. The SPI_SYS_PORT
is currently being used to implement a TCP/Telnet emulation of a
serial port over Wifi using AT commands. This allows to e.g. dial into
BBS from the core.
The SPI_SYS_READ_CONFIG allows the FPGA Companion to read the
core specific configuration in XML format. This data mainly contains
the menu structure used by this partticular core and makes the core
development independent from the FPGA Companion. The config is
returned beginning with the second payload byte read via this
command. The core may return the configuation either as plain XML text
or in GZIP compressed format. A plain text XML must begin with the
< character and be terminated by a zero byte character. The
GZIP'd XML must begin with a $1f byte and will be parsed until the
decompression us successful or aborted by a decompression error. The
gzip'd hex file can be regenereted under Linux using like so:
$ gzip -n atarist.xml
$ xxd -c1 -p atarist.xml.gz > atarist_xml.hex
The advantage of the compressed version is that it only occupies about 25% of the uncompressed XML in the FPGAs internal ROM resources.
The HID target currently supports three commands:
| value | name | description |
|---|---|---|
| 0 | SPI_HID_STATUS |
Request status from FPGA |
| 1 | SPI_HID_KEYBOARD |
Send a keyboard data byte into the FPGA |
| 2 | SPI_HID_MOUSE |
Send a mouse data byte into the FPGA |
| 3 | SPI_HID_JOYSTICK |
Send a joystick data byte into the FPGA |
| 4 | SPI_HID_GET_DB9 |
Read the DB9 joystick status from the FPGA |
The SPI_HID_STATUS message is currently unused. It will be used to report the
HID requirements. This may e.g. include the keycode mapping required
by the core. This currently returns 0x5c and 0x42 in bytes 4 and 5.
The SPI_HID_KEYBOARD messages are core specific. Currently each message
consists of one byte only containing the press/release status and the
row and column of the key inside the keyboard matrix. Typically
the F12 key is not forwarded into the core. Instead the F12 key is
used to open and close the OSD. While the OSD is visible no keyboard
data is sent into the core. Instead the keyboard is being used to
control the OSD.
The SPI_HID_MOUSE messages contain three data bytes. The first
byte carries the state of the mouse buttons in bits 0 and 1. The
second and third bytes contain relative x and y movements.
The SPI_HID_JOYSTICK messages contains five data bytes, the
first addressing the joystick / gamepad and the second containing classic 8 bit
digital joystick data. The address byte is needed since unlike
keyboards and mice, the core needs to distinguish between multiple
joysticks. The upper four bits of the second byte contain up to four
fire buttons. These can simply be or'd together for standard DB9
joystick emulation.
The subsequent three bytes transfer Gamepad analog proportional position X, Y and 8 extra digital gamepad buttons.
The SPI_HID_GET_DB9 command allows the MCU to request the
state of the DB9 joystick port from the FPGA. This can be used
to e.g. control the OSD via a joystick connected to that port.
The OSD (on-screen-display) is a monochrome 128x64 frame buffer inside the FPGA that can be displayed centered on the main screen on request by the MCU. The 1024 bytes of video memory (128x64/8) are organized "vertically" as typically used with small OLED displays. This is to easy the use of the u8g2 library which is a graphics library for small monochrome displays and which is being used in MiSTeryNano.
The OSD target supports the following commands:
| value | name | description |
|---|---|---|
| 1 | SPI_OSD_ENABLE |
Show or hide the OSD |
| 2 | SPI_OSD_WRITE |
Send graphics data to the OSD |
The SPI_OSD_ENABLE command has one data byte. The lowest bit of this
indicates whether the OSD is to be shown (1) or hidden (0).
The SPI_OSD_WRITE command is followed by a byte containing an offset value
and 1 or more data bytes. The offset indicates which tile the graphics data
starts at. E.g. an offset of 100 indicates that writing should start at tile column
100 which is the 800th pixel column. Since the display is 128 pixels wide the
800th column is the 32th pixel column in tile row 6 (6*128+32=800). This is exactly
how the u8g2 library expects to address a display.
The SDC (SD card) target allows the MCU to use the SD card connected to the FPGA. It can either read whole sectors for its own purposed (e.g. letting the user browse the file system) or it can assist the FPGA core in accessing data inside files on the SD card. This is necessary since most cores expect to deal with single media (e.g. a single floppy disk). The MiSTeryNano instead stores these in media files (e.g. Atari ST .ST disk image files) which in turn reside inside the file system of the SD card. So these are basically core specific file systems stored inside a SD card file system. While the FPGA core deals with the core specific file system inside the image files it's up to the MCU to handle the SD cards own file system.
The SDC target supports the following commands:
| value | name | description |
|---|---|---|
| 1 | SPI_SDC_STATUS |
Read SD card status |
| 2 | SPI_SDC_CORE_RW |
Request the core to read or write a sector for it's own purpose |
| 3 | SPI_SDC_MCU_READ |
Request to read data for MCU usage |
| 4 | SPI_SDC_INSERTED |
Inform core about the selection of disk images |
| 5 | SPI_SDC_MCU_WRITE |
Request to write data on behalf of the MCU |
| 6 | SPI_SDC_DIRECT |
Inform core that image may be accessed directly |
| 7 | SPI_SDC_INS_LARGE |
Inform core about the selection of large disk images |
The SPI_SDC_STATUS command is used to poll the SD card status. The first
byte returned is a generic status byte indicating whether the card
could be initialized and the card type (SDv1, SDv2, SDHC). Bit 1 of
the status byte indicates whether the SD card is currenly busy reading
or writing data. The second byte contains core requests. Currently the
lower two bits are used by the core to request sector data. In the
Atari ST core bit 0 indicates a request for floppy A: and bit 1 for
floppy B:. The following four bytes contain the sector number the core
wants to read.
The SPI_SDC_CORE_RW command requests the core to read or write a sector
from or to SD card and use it for it's own purposes. The command is
followed by four bytes containing the sector number to be read or
written. This command is usually a sent in reply to bit 0 or 1 being
set in the reply toSPI_SDC_STATUS. The core has requested to
read or write a sector (e.g. sector 0 if it wants to read or write the
first sector on the floppy) and the MCU now instructs it to use a
certain sector on SD card. This will be the first sector of the disk
images on SD card which will be the first data sector in the image
file. No actual data is being returned to the MCU with this command.
In contrast SPI_SDC_MCU_READ requests the FPGA to return the contents
of a sector from the SD card is to be returned to the MCU. The following four
data bytes indicate again which sector is to be read. The FPGA will return
busy bytes (!=0) as long as the SD card is being read. Once data is ready, the
command returns a single 0 byte followed by 512 bytes of sector data. This
data is not visible to the core. Instead it used by the MCU to display the
contents of the SD card and to determine where data is stored inside the images
files stored on the SD card.
The SPI_SDC_INSERTED command and the following five data bytes
are used to inform the core about drive and the size of the selected
disk image. This is needed to translate between the track/sector/side
values typically used when reading from floppy disk into sector
offsets into the image file. A size of 0 indicates that no image is
currently selected. The core should then behave as if e.g. no floppy
disk is inserted.
Using SPI_SDC_MCU_WRITE the MCU can request a sector to be written to
SD card. The first four data bytes indicate which sector on the SD card
is to be written. The following 512 bytes are the data to be written.
Afterwards command will return bytes != 0 as long as the card is busy
writing.
The SPI_SDC_DIRECT command tells the core that the image
specified by the first data byte is continous on SD card (not
fragmented) and may be accessed by the core directly without sector
translation by the MCU. The following four data bytes contain the LBA
of the first sector of the selected image on SD card and may
optionally be used by the core to directly access the SD card.
The SPI_SDC_INS_LARGE command and the following nine data bytes
inform the core that a large image (>4GB) has been inserted. This
commands works otherwise like SPI_SDC_INSERTED. Cores not
able to deal with images that large should ignore this command and
thus prevent themselves from accidentially damaging the image during
a write.
The audio target has not been implemented, yet. It's purpose is to give the MCU the ability to output audio via the core. This may e.g. be used for accoustic feedback during menu usage or for the simulation of floppy disk sounds or the like.