The Currah uSpeech
The Currah uSpeech (or Currah Micro Speech) was a HW peripheral for the ZX Spectrum from 1983. It was a speech device which allowed to speak words from a BASIC program. A few commercial programs/games did support it as well. It connected to the HF- and audio-output of the spectrum and mixed it’s own (speech) audio and the spectrum sound onto the HF signal. So the audio could be heard from the TV speaker.
My aim is to gain enough knowledge about the Currah uSpeech and its functionality to use it in an ZX Spectrum assembler-code game.
The best source of written information about the uSpeech HW that I could find is here:
http://problemkaputt.de/zxdocs.htm and here https://k1.spdns.de/Vintage/Sinclair/82/Peripherals/Currah uSpeech/
And of course the Currah uSpeech_Manual itself.
But there were still a lot of unclarities left. Therefore I took a real uSpeech HW (thanks to Kio) and wrote a test program to unriddle its last mysteries.
Table of Contents
Some Reverse Engineering (Rockfall)
The Hardware
The uSpeech has 4 connectors/cables: UHF-in, UHF-out, a line lead used for in or output and the edge connector.
The normal setup for the uSpeech was to attach it to the Spectrum and then connect the UHF-in to the UHF-out of the ZX Spectrum, the UHF-out to the TV and the line lead (as input) to the MIC output of the ZX Spectrum. The speech signal (together with the audio output of the ZX Spectrum) was added to the UHF-output signal of the ZX Spectrum and fed into the TV.
Normal setup: Spectrum Audio and UHF out are fed through the uSpeech which adds speech and the Spectrum audio to the UHF out.
The other setup is not so well known although it is documented in the Currah uSpeech_Manual: UHF-in and UHF-out are not used, the line lead is used as output and connected to an audio amplifier. In this case the speech signal is output to the line lead. I used this setup to record speech output in some of my tests.
Alternate setup: The Spectrum stays connected to the TV. The audio out of the uSpeech can be connected to an amplifier or tape deck.
Strange about this setup is that there exist mods to modify the uSpeech HW to allow line out functionality (see here and also here.). This wouldn’t be necessary with the HW that I used. The HW in the link is definitely a different revision, maybe it didn’t came with line out functionality.
Opening the device (it’s just clipped, there are no screws) we find the main components of the uSpeech HW:
- 2k uSpeech ROM
- SP0256-AL2 (Narrator Spech Processor)
- ULA
The pin 12 (ser out) of the SP0256 has been removed. Maybe there is an error on that PCB revision.
For completeness here is the back side:
I also measured the oscillator frequency. Unfortunately the frequency dropped when I reached the Osc out pin (28). So, Kio did some simulation, the resulting frequency should be about 3,05MHz.
The API
The uSpeech comes with a 2k ROM. It’s main purpose is to provide speech output for the Spectrum Basic. But it also offers an API to use from machine code.
Both ways are described in the Currah uSpeech_Manual and provide a way to pass complete sentences to the uSpeech which are turned into allophones and provided to the uSpeech HW by the ROM code.
However, the machine code API is very intrusive and restrict assembler programs (games) very much in the use of the interrupt.
A third, undocumented, API is described later.
The System Behaviour
In very brief the general behaviour of the uSpeech HW:
The uSpeech comes with a 2k ROM. It is enabled with an access to address 0038h. The next access will disable the ROM again.
The idea behind this was that the normal ZX Spectrum interrupt routine passed address 0038h which made the uSpeech ROM available. After the uSpeech interrupt routine was finished it again jumped to 00038h which disabled the ROM so that the normal ZX Spectrum ROM was visible. I.e. the Spectrum interrupt routine could continue normally and finish.
The uSpeech ROM code establishes the BASIC and a machine code interface.
From BASIC you could e.g. assign the s$ (LET s$=”something”) and the uSpeech immeditely begins to talk. It converts words or a complete sentence into allophones and sends them to the SP0256 for output. Intonation is different for upper and lower case characters.
The uSpeech ROM also observes data in the RAM for a new sentence/word. This can be used as API to a machine code program. Here, the intonation is different if bit 6 of the data is set or not.
Some Reverse Engineering
‘The Rockfall case’: when doing a disassembly of the rockfall game (Ian Collier) one can easily find code that does an ‘in a,(38h)’ and then reads a value from ROM to see if the ROM has been toggled. However, I was not able to see that code executing, it seemed like dead code. On the other hand I also tried Rockfall II. There is a very similar routine used: After the port operation you can see that a read is done on 0039h of the ROM. Followed by a comparison with F1h, which translates to POP AF. The POP AF is the instruction used in the Currah ROM. In the normal Spectrum you would find E5h (PUSH HL).
And I could verify that this indeed was executed.
It is strange that Rockfall uses an IO operation for uSpeech detection. A simple memory read seems much easier.
The Tests
There were a few questions that I wanted to answer by tests:
- Intonation: what does intonation mean. Is it volume or pitch? If it is pitch, what frequency is it.
- Is the ROM toggled only by an opcode fetch or also by a normal read operation or even an IO operation on address 0038h?
- Is address 1000h (and 3000/1h) only writable/readable if the Currah ROM is on?
While testing I found some other interesting behaviour an extended the tests:
- The values of the other bits when reading 1000h.
- Mirroring of the Currah ROM.
- Is the Spectrum ROM available (above 0800h) while the Currah ROM is enabled?
- Mirroring of the addresses 1000h and 3000/1h.
Here is the test program I used:
Source code + tap file: https://github.com/maziac/currah_uspeech_tests
Explanations:
General:
When reading from address 1000h (or mirrors) the contents of the byte is written to the screen. Only changed values are written. This leads to a vertical bar. The rightmost bit is bit 0 which is known to be the busy bit (set when SP0256 is busy). The meaning of the other bits is unknown.
out 38h:
An
ld bc,0038h
out (c),a
is executed. Afterwards it is checked at address 0039h+i*0800h with i in [0;7] if value 0f1h is found which is the value of the uSpeech ROM at address 0039h. If found the block is marked as red on screen.
in 38h:
Same as before but a
ld bc,0038h
in a,(c)
is used.
mem write 38h:
Same as before but a
ld (0038h),a
is used.
mem read 38h:
Same as before but a
ld a,(0038h)
is used.
mem holes test:
This test is used to see if any Spectrum ROM is accessible if the uSpeech ROM has been activated. It reads a byte from the ROM, then switches the ROM and reads the same address and compares both values. If equal this is indicated by a red square. The addresses used for comparison are 0001h+i*0800h with i in [0;7].
/AA/ with 3000h:
Writes to address 3000h for low intonation and afterwards write /AA/ (18h) to address 1000h. This is done in a loop, i.e. the address 1000h is read and the next /AA/ is written when the busy bit (bit 0) is reset to 0. This test does not access the 0039h address. So it can be used in conjunction with the tests above (e.g. “mem write 38h”) to verify the using address 1000h is only working the same time that the uSpeech ROM has been enabled.
/AA/ with 3001h:
Same as above but with high intonation (address 3001h).
<h4>/AA/ with bit 6:
Writes /AA/ to address 1000h with bit 6 set (18h | 40h = 58h). Doesn’t writes to 3000h or 3001h at all. The purpose of this test is to show that bit 6 has no influence on the intonation when writing to address 1000h.
/AA/ without bit 6:
Same as above but without setting bit 6.
/AA/ at 1XXXh:
Turns the uSpeech ROM on. Writes /AA/ to address 1XXXh, i.e. other addresses than 1000h to see if there are mirrors of 1000h. Everytime you execute this test another address is used. The used addresss is printed at the bottom part of the screen. The tested addresses are: 1000h, 1001h, 1002h, 1004h, 1008h, 1010h, 1020h, 1040h, 1080h, 1100h, 1200h, 1400h, 1800h. I.e. every address line is tested once.
/AA/ with 3XXXh even:
Turns the uSpeech ROM on. Writes /AA/ to address 1000h/3XXX(even), i.e. it tests for mirrors of 3000h. Everytime you execute this test another address is used. The used addresss is printed at the bottom part of the screen. The tested addresses are all mainly even: 3000h, 3001h, 3002h, 3004h, 3008h, 3010h, 3020h, 3040h, 3080h, 3100h, 3200h, 3400h, 3800h. (Note: 3001h is included for validation of the algorithm.) I.e. every address line is tested once.
/AA/ with 3XXXh odd:
Turns the uSpeech ROM on. Writes /AA/ to address 1000h/3XXX(odd), i.e. it tests for mirrors of 3001h. Everytime you execute this test another address is used. The used addresss is printed at the bottom part of the screen. The tested addresses are all mainly odd: 3000h, 3001h, 3003h, 3007h, 300fh, 301fh, 303fh, 307fh, 30ffh, 31ffh, 33ffh, 37ffh, 3fffh. (Note: 3000h is included for validation of the algorithm.) I.e. every address line is tested once.
/AA/ at 2000h:
Turns the uSpeech ROM on. Writes /AA/ to address 2000h and reads from 2000h (bit 0) repeatedly. Used to check ifthere are mirrors at 2000h.
/AA/ at 1000h in/out:
Turns the uSpeech ROM on. Uses in/output. Outputs /AA/ to address 1000h and reads with ‘in’ from 1000h.
/AA/ altern. 3000/1h write:
Turns the uSpeech ROM on. This test is to measure the time the oscillator requires to switch from one to the other frequency. An /AA/ is written to 1000h/3000h for a short while then an /AA/ is written to 1000h/3001h. This is done in a loop until another key is pressed.
/AA/ altern. 3000/1h read:
Same as before but uses a read of address 3000/1h instead of a write. Used to check if a read does work as well.
/AA/ altern. 3000/1h out:
Same as before but uses an ‘out (c),a’ of address 3000/1h instead of a write. Used to check if an ‘out’ does work as well.
/AA/ altern. 3000/1h in:
Same as before but uses an ‘in a,(c)’ of address 3000/1h instead of a write. Used to check if an ‘in’ does work as well.
All allophones (5-63):
Speaks all allophones from 5 to 63 each followed by a pause.
busy flag (/AA/ only once):
Turns the uSpeech ROM on. Writes /AA/ to address 1000h once and observes the busy flag (reads address 1000h). You see a horizontal line with the contents of the busy flag (1/0). Additional (as usual) the contents of the complete byte is written as a vertical bar on the left, but only for changed values.
busy flag (/SH/ only once):
Same as above but with allophone /SH/.
busy flag (/SH/ only once):
Same as above but does not stop until a key is pressed. Intention was to take some pictures with an oscilloscope.
Findings
ROM toggling with 0038h: Apart from the opcode fetch, the following assembler code is all valid to toggle the ROM:
ld bc,0038h
; in-operation
in a,(c) ; out-operation. contents of a does not matter.
out (c),a
; another in (or out) operation
xor a
in a,(38h)
; memory read
ld a,(0038h)
; memory write. contents of a does not matter.
ld (0038h),a
I.e. every operation on 0038h, let it be an IO or memory operation, an opcode fetch, a read or write, will enable/disable the ROM.
In the test pressing one of the keys 0-3 will all toggle the ROM. This can be seen in the upper left corner. Red means that the uSpeech ROM is present (red arrow). The number below indicates the memory area. Multiply the number with 0800h to get the start address. I.e. each block represents 0800h = 2k bytes.
In this video I press the keys repeatedly which results in enabling/disabling the ROM:
1000h toggling: Here we see that address 1000h can be used to output speech only if it is enabled by accessing address 0038h before. It is enabled together with the uSpeech ROM which can be seen by the red rectangle in the upper part.
Please note: the decreasing volume is due to the recording, in reality the volume is constant.
1000h mirroring: All addresses 1XXXh are valid for reading the status bits or writing the allophones. In binary 0001XXXX XXXXXXXX. I tested the following addresses: 1000h, 1001h, 1002h, 1004h, 1008h, 1010h, 1020h, 1040h, 1080h, 1100h, 1200h, 1400h, 1800h. At address 2000h there is no mirroring.
1000h accesses: Instead of a read/write to this register one can use in/out instead.
1000h reading, other bits: The picture shows in the lower half the contents of the bits read from address 1000h after an allophone was written. Bit 0 (busy) is shown at the bottom, above are bits 1 to 7. At the left side you see a short marker separated by 2 pixels which indicates logical 0.
The bits are not floating, there seems to be some deterministic behaviour. The bits 1 to 7 seem to be behave very similar but looking into more detail not all of them contain the same value.
Bits 0, 1 and 5 contain different values. Bits 2, 3 and 4 might be equal and bit 6 and 7 might be equal.
3001h/3001h mirroring: Very similar behaviour here. All addresses are mirrored. It is only important if the address is even or odd. Mirrors for 3000h: In binary 0011XXXX XXXXXXX0. Mirrors for 3001h: In binary 0011XXXX XXXXXXX1. I tested for 3000h: 3002h, 3004h, 3008h, 3010h, 3020h, 3040h, 3080h, 3100h, 3200h, 3400h, 3800h. And for 3001h: 3003h, 3007h, 300Fh, 301Fh, 303Fh, 307Fh, 30FFh, 31FFh, 33FFh, 37FFh, 3FFFh.
3001h/3001h accesses: A write or an out to this address does work whereas a read or in operation does not.
Intonation/Bit 6: In my tests setting the bit 6 didn’t have any effect on intonation when writing to address 1000h. The bit is only important if the machine code API from the manual is used. In this case setting bit 6 will lead to writing to memory location 3001h before the allophone is written. This leads to the different intonation. The uSpeech ROM code responsible for this is (address=0184h):
; a contains the allophone and bit 6 for intonation
ld de,3000h
bit 6,a
jr z,l1
; use 3001h if bit 6 is set
inc de
l1:
ld (de),a
ld (1000h),a
According my measurements writing to 3000h or 3001h just changes the frequency. The volume does not change. The frequency for 3001h is about 7% (x1.07) higher than that of 3000h. The frequency does not instantly change but it takes about 0.05 - 0.5 secs until the new frequency is settled. It is not necessary to write to 3001h/3001h before a write to 1000h is done. Simply the last set frequency will stay.
Here is the audio
Allophone loop: One effect that I wasn’t aware off I found by accident (although it was already partly documented in http://problemkaputt.de/zxdocs.htm): Whenever an allophone is written and no new allophone is written afterwards the last allophone is repeated endlessly. The video shows this for 2 allophones, /AA/ and /SH/. The horizontal line at the bottom shows the value of the busy bit. I.e. it goes up after writing the allophone and goes done to 0 when finished. But we can also see that it regularly is set to 1 although nothing is written to 1000h anymore. The vertical bar shows the content of the 1000h address when read. It is a compressed view and only displayed whenever it changes. It seems that not the complete allophone is repeated but only the last part, i.e. you do not hear “sh sh sh sh” but “shhhhhhhh”.
Conclusion
My goal was to use the uSpeech output in an assembler game. So I was looking for the best way to access Currah uSpeech HW. The ways described in the manual were not suitable for me:
- Using the BASIC interface: does not make sense for an assembler program
- The described machine code API: This has several issues:
- Allophones can’t be input directly. The input is done as a sentence that is broken done into allophones by the uSpeech ROM.
- It uses memory at the end of the RAM which conflicts with custom interrupts routines,
So the approach is to control the uSpeech HW directly. I have no need for intonation so it’s enough to deal with addresses 0038h and 1000h.
To avoid a busy loop while waiting to send the next allophone to the SP256 the speech routine has to be served in an interrupt which polls the busy flag and (if not busy) writes the next allophone.
Pseudocode of the interrupt routine:
- Enable registers
- Check if SP0256 busy
- If not: write allophone
- Disable registers
Assemblercode:
...
; enable uSpeech
ld a,(0038h)
; wait until SP0256 is not busy anymore
loop:
ld a,(1000h)
bit 0,a
jr nz,loop
; write allophone
ld a, ... ; load with some allophone, e.g. 18h for /AA/
ld (1000h),a
; disable uSpeech
ld a,(0038h)
...
Note 1: The uSpeech HW is enabled here without any test if it is already enabled. If the interrupt routine is the only code that accesses address 0038h than no test is needed. (As this is a custom interrupt routine the normal interrupt routine which passes address 0038h is not executed. So no problem here.)
Note 2: If the uSpeech HW is not attached this code will work as well (of course no speech is output). Reading from address 1000h will lead to bit 0 being set or not. If set then nothing is written to 1000h ever. If it is not set the allophone is written to 1000h which is a ROM location and therefore has no effect.
Note 3: It is important to turn the uSpeech off after accessing it. Otherwise the ZX Spectrum ROM is not available. If you don’t need the ROM you could also turn the uSpeech on only once (outside of the interrupt routine) and leave it on. But normally you would like to use some ROM routine e.g. for printing so you need to toggle the access.
Note 4: If you need to test (for some reason) if the uSpeech HW is available you can use the following code:
is_uspeech_available:
di
; test if uSpeech is enabled
ld a,(0039)
cp 0f1h
jr z,is_enabled ; jump if uSpeech is available
; try to enable the uSpeech
ld a,(0038h)
; test again
ld a,(0039)
cp 0f1h
jr nz,is_not_enabled ; jump if uSpeech is not available
is_enabled:
; disable uSpeech
ld a,(0038h)
is_not_enabled:
ei
ret
This subroutine returns with Z-flag set if the uSpeech HW is attached. It works by reading an address value that differs in uSpeech and Spectrum ROM. In the example above the address 0039h is used. In the uSpeech ROM it contains 0f1h for ‘pop af’. The ZX Spectrum ROM contains 0e5h for ‘push hl’.