The Acorn Electron FPGA core has reached a major milestone. Cassette loading is now functional which means GAMES!
Although schematics have been made available recently for the Electron’s ULA, I was already partially into implementing the cassette interface so decided to keep going with a best guess.
I’ll return to the ULA chip again once the schematics have been analysed and any differences between how people thought the ULA worked come to light. Hopefully that will include answers to how the CAS RC pin is used by the ULA and clear up confusion regarding writes to the FE06 counter impacting loading.
In a real Electron, data is loaded/saved to cassette tape via four ULA pins and a small amount of interfacing hardware.
- CAS IN - Reading data from cassette
- CAS OUT - Writing data to cassette
- CAS RC - Unknown purpose, docs hint at high-tone related
- CAS MO - Controls a relay to enable/disable the cassette player motor
The data on a tape is encoded using the Kansas City Standard an example of the waveform from the game Elite is below:
The highlighted section shows two higher frequency pulses followed by three longer frequency pulses followed by 6 more short pulses and so on.
Two short pulses represent a “1” bit and a single longer pulse represents a “0”. Hence the shaded section contains “100011101” (and part of the next 1 bit).
The ULA has to perform this same conversion from wave to bits in order to work with the data. How it does that is a mystery. What is known is how the signal is converted via interfacing hardware before it enters CAS IN of the ULA (and likewise for output via CAS OUT). That interface logic process the waveform from a sine wave into a square wave allowing the ULA to only concern itself with two discrete logic levels.
The trace makes it apparent that the period of the square wave frequency still varies between 1 and 0 bits just as the sine wave did. Detecting a 0 or 1 thus becomes a case of counting the number of clock cycles the CAS IN pin is high for. If the signal high period is 1200Hz interpret as a 0, whilst two pulses with a 2400Hz period are a 1.
Beyond that it’s following information in the advanced user guide which states that at least ten 1 bits (twenty 2400Hz pulses) must be received (high tone) after which a start bit “0” will occur followed by 8 data bits and a stop bit “1”.
From a ULA point of view, there’s really not that much more to it and a simple state machine handles interpreting the incoming pulses and if hooking up a real cassette tape was all you wanted, just breadboard the external cassette interface hardware and job done.
But what about loading from the replay’s SD card?
Virtual Cassette Interface
Loading from SD card presents an interesting dilema. Data coming from the SD card is already in a format the ULA could use eg a stream of 0s and 1s. The Replay Lib includes a generic fileio protocol to transfer that bitstream 16 bits at a time to the FPGA but how can the ULA use it?
The data is not in a suitable format to clock in via the CAS IN signal on the ULA as that’s expecting a frequency encoded signal. Adding a new signal that allows passing in a full byte of data is an option but that would mean the ULA now needs to know if you’re reading from a real cassette or virtual one and deal with each in a different way.
I really want the ULA to not care whether you use a real cassette or virtual. In order to achieve that I added a small pulse translator module which takes the bitstream the ARM feeds into the FPGA and turns it into a square wave with a frequency of 1200Hz and (two) 2400Hz depending on whether it’s a 0 or 1 and feed that into the ULA which wil reverse the process..
The ULA is now ignorant of where the data is coming from.
There is a small wrinkle to this which is when it comes to implementing save support.
As the above oscilloscope trace shows (also provided by Dave), the real Electron ULA generates a pseudo sine wave for the CAS OUT signal.
If the ULA generates that, the pulse translator module would not be able to handle converting that back to 0s and 1s without an ADC. So here a compromise is needed. The ULA will need to output a square wave on one pin for the virtual interface and a sine wave on CAS OUT for a real cassette to use.
This is not all that bad as it’s trivial to make all the logic levels of a pseduo sine wave be 1 (other than 0) to get a square wave. So I still feel for the most part the ULA continues to have a “does not care” attitude to where the data is coming from or going to.
OSD / UEF
The Replay’s on screen display (OSD) could do with a few tweaks to better support working as a virtual cassette player (not surprising as it wasn’t intended to support this). In the mean time I’ve added options to set PLAY on/off, RECORD on/off and FFWD/RWND on/off. Only PLAY is implemented at the moment.
One feature I could provide is a counter showing the current tape position which will be important if multiple games or data is stored on the same tape and you want to start loading from a later point.
As for the file format that the OSD allows you to mount as a tape image, that’s currently a bit of an inconvenient compromise. Electron tapes are commonly converted to the UEF format for use with emulators.
The Acorn core does not support the UEF format at this time and works only with raw data. A small python script will convert a UEF to a suitable raw bitstream format which can then be used with the core and loaded from SD card.
This is a little inconvenient due to needing to convert any UEF you wish to use and a step I’d like to remove by adding UEF support to the core or firmware at a later date (for loading at least).
With a tape “inserted” in the OSD and CHAIN"" issued at the Acorn prompt it’s not long before I’m greeted with a loading screen and an incredible sense of accomplishment.
Nearly five minutes later the game has finally loaded (I don’t miss loading from tape one bit) Mission complete! Elite has successfully loaded and is now possible to play, although there’s no save support or sound yet.
I tried a handful of games such as Elite, Repton 1, Monsters, Hopper, Jet Set Willy and Sphinx Adventure. All loaded and appeared to play correctly although my testing was not exhaustive, i expect to find bugs once I try a wider range of games, especially those that take advantage of quirks in the Electron’s hardware.
The core has finally reached a point I consider it useful enough to release. Both source code and a bitstream file are available in the FPGA Replay public SVN repo. I will be pushing source code to github a little later.