Reversing the PSR-70 firmware
Now that we know enough of the PSR-70 hardware, it is time to start studying the firmware. It resides in one 32 KB EPROM, which is inconveniently soldered directly on the circuit board without any socket. Luckily the board is single-sided, so it was quite an easy task to remove it using a proper desoldering station. After this I soldered a socket in place of the EPROM.
I have an old Stag EPROM programmer, so no problem in reading the EPROM. I programmed the contents in another EPROM and inserted it in the socket. The keyboard worked without problems with the copy, so the reading was successful. I have sometimes got incorrect reading results, so it is always worth checking.
Stag can dump the EPROM contents in intel-hex format, which I disassembled with yazd. This is quite nice Z80 disassembler, it produces good cross-references, which help analyzing the software. Hex and disassembly listings are in my Github.
Analyzing the disassembly
Target is not to understand the software as a whole, there are many not-so-interesting parts, like auto accompaniment related things, saving and loading to the tape etc. But essential parts for understanding how the sound generation ICs are controlled are the main pieces to concentrate on. The first goal is to try to understand the controlling principles of the OPQ (YM3806) chip. The RYP4 (YM2154) I’ll leave for later.
Generally the code looks like hand-written assembly, not produced by any compiler. It seems there has been several coders; many conventions are inconsistent, like how to pass parameters to a function. Also there are some clever optimizations, I doubt that any 80’s compiler would have been able to produce those.
The reset vector at address 0000H is always a good place to start following the code. In the very beginning there is normally the I/O initialization. Now that we have the memory and I/O maps drawn, it is quite easy to detect and understand the initializations. UART and 8255 are the easy ones, just compare to the datasheet. Yamaha’s custom chips are harder, OPQ has 256 registers, RYP4 has 128, and meanings of all of them are unknown so far.
The basic initializations for the 8255 and UART are these:
; 8255 control: mode 0, PA=in, PB=out PC=out
02F0: 3E 90 LD A,90h
02F2: D3 23 OUT (23h),A
init_uart:
0CA0: 06 01 LD B,01h
0CA2: CD 73 0D CALL delay1
0CA5: 21 B1 0C LD HL,uart_init_data ;(0CB1H)
0CA8: 06 05 LD B,05h
0CAA: 7E L0CAA: LD A,(HL)
0CAB: D3 10 OUT (UART_control),A
0CAD: 23 INC HL
0CAE: 10 FA DJNZ L0CAA
0CB0: C9 RET
uart_init_data:
0CB1: 00 DB 0
0CB2: 00 DB 0
0CB3: 00 DB 0
0CB4: 03 DB 03H ; master reset
0CB5: 95 DB 95H ; Rx int enable, Tx int disable,
; 8-N-1, clk/16
Another good starting point is the interrupt vector at 0038H which starts the one and only interrupt service routine, because Z80 has only one actual interrupt. There is also the NMI but hardware inspections showed that it is not used for anything. As already guessed from the hardware, the interrupt service does poll each interrupt capable IC to find out, which was causing the interrupt. A corresponding service function is called for UART, OPQ, RYP4 or IG14330. This causes quite high interrupt latency but obviously it does not matter in this case. Couple of things learned by examining the interrupt service functions:
- OPQ register 00H (address C000H) is a status byte which tells whether the OPQ chip is requesting an interrupt.
- By writing value 71H to OPQ register 03H the interrupt is acknowledged. Actions executed in the service routine look very much like a real-time interrupt. So this must be the real-time interrupt I saw with the scope, and it is coming from the OPQ chip. Not that big surprise, I have seen that also OPL3 chip contains a timer and interrupt capability. This saves a separate timer chip.
- Midi communication is interrupt based and there are separate ring buffers for transmit and receive. Interrupt service sends from transmit buffer and stores received bytes to receive buffer. There are separate functions used by rest of the firmware for writing to transmit buffer and reading from the receive buffer.
Especially interesting was a small function which is called from the interrupt service routine when writing acknowledgment to OPQ chip:
L033F: POP HL ; return address -> HL
POP DE ; param1 -> E
EX (SP),HL ; param2 -> L, restore return address
L0342: LD A,(0C000h) ; wait for OPQ reg #00 MSB to clear
RLA
JR C,L0342
LD H,0C0h ; write param2 to (OPQ reg #param1)
LD (HL),E
...
This function takes two one-byte parameters from the stack, param1 and param2 (Z80 PUSH/POP are always 16-bit, but this code ignores the high-order bytes). Param1 is written to memory address C0xxH, where xx = param2. Very obvious “write_to_OPQ” function, because in memory addresses C000H...C0FFH resides the OPQ chip. This also shows that every time you want to write something to OPQ, you must first wait the register 00 highest bit clearing; so it is some kind of busy bit of the OPQ chip.
Yazd cross reference shows that function L033F is called from about 60 places in the code. On the other hand, I cannot find any other writes to addresses C000H...C0FFH. So we can assume that all register writes to OPQ chip go via this one function. This is good news.
I tried to analyze which bytes are written when calling this function, but as it is called from so many places, it was impossible to follow, especially in which order the writes are done. This must be traced in some other way.
Enter Romulator
While it is possible to find out many things just by analyzing the original firmware, the behavior of the OPQ chip cannot be fully derived from it. We will need a possibility to run own test programs in the PSR-70 hardware, to do testing like “what if I change the value of this register, how does it affect the produced sound?”.
The firmware is in EPROM, but it would not be practical to program a new EPROM every time we need to change the program. For these cases, I have built several years ago a tool I call Romulator. It will be needed now.
Romulator is, as the name suggests, a ROM-emulator (the name also comes from the Finnish word “romu”, which means “junk”, because it is mostly needed when testing this kind of old junk). It has a connector which can be inserted into target system’s EPROM socket, and it emulates an EPROM in the socket. The contents of the emulated EPROM can be changed by downloading an intel-hex file through the Romulator’s serial port. It can also control the target systems reset signal, so downloading a new EPROM contents gets fluent: every time a download is executed, Romulator resets the target system and the processor starts happily executing the new program, thinking it just got a fresh EPROM.
Romulator is a heavily modified version of this project. I dropped the memory to 32 KB, changed the microcontroller from AVR to Atmel’s 8051, wrote a new firmware from scratch and made the board significantly smaller. The VHDL inside the Xilinx CPLD is only slightly modified.
Hello World
As always, the first program would be some kind of HelloWorld. In the embedded systems, the HelloWorld is normally a led blinker, but there are no suitable leds for that. Front panel does have about 100 leds, but their control goes through the unknown IG14330, so no dice. Serial port would be better.
The only serial port available in PSR-70 is the midi port using the HD63A50 UART. From the hardware analysis we know that the UART baud rate is the standard midi rate 31.25 kbps and it cannot be changed. This is a bit problematic speed for normal terminal programs. Also the whole midi out circuit is quite strange and I did not get it working properly, so I took the TxD out from the board as a TTL level serial signal, and connected an Arduino to do the baud rate conversion 31.25 ↔ 38.4 kbps. Arduino is running a very simple SoftwareSerial example sketch, passing everything through between the two serial ports.
After these arrangements, I wrote a very simple HelloWorld with Z80 assembler. No frills here: delays by busy loop, serial transmit by status polling. The code is in my Github. Compiler (or actually assembler) is yaza which is good enough for this, srecord converts the binary to intel-hex, to be downloaded to the Romulator.
This started working quite easily, thanks to the careful hardware analysis. Next, I tested the interrupt mechanism by changing the HelloWorld to interrupt based version. Here the UART send and timing are done using interrupts. The 10 ms real-time interrupt comes from the OPQ timer in a similar way as the original firmware. This worked also, proving many assumptions correct. This program is using the OPQ chip for the first time, now we are getting somewhere!
ROM2 revisited
While studying the hardware I was wondering the role of ROM2 and could not make out anything of it. Now that I have a possibility to make own programs to PSR-70, I could at least read its contents. Very probably it could be read with the Stag but it means it should be desoldered. It will be easier to write a program which reads it and dumps the contents from the serial port.
That’s what I did. The program is here and the results are here. I ran it also through the yazd, in case there were some executable code.
The disassembly in the beginning of the file does not make sense, so it must be data. But from the address 175BH starts code that seems reasonable. It contains a collection of various functions that are indeed called from the EPROM. It’s worth noting that ROM2 resides in the CPU addresses 8000H...BFFFH, so the addresses in the disassembly listing must be offset with 8000H. The functions are called through the jump table in ROM2 addresses 175BH...18DAH (CPU addresses 975BH...98DAH). The jump target addresses are already offset addresses.
175B: C3 AD A7 JP 0A7ADh
175E: C3 0E A8 L175E: JP 0A80Eh
1761: C3 13 A8 L1761: JP 0A813h
1764: C3 59 A8 L1764: JP 0A859h
1767: C3 91 A8 L1767: JP 0A891h
176A: C3 C9 A8 L176A: JP 0A8C9h
176D: C3 02 A9 L176D: JP 0A902h
...
So the firmware is not completely in the EPROM, parts of it are in ROM2 which is a mask ROM. Very strange combination. Maybe this PSR-70 unit is an early production version, and in later versions also the EPROM is replaced with mask ROM, don’t know.
The meaning of the data in the beginning of the ROM remains still a mystery.
Capturing the OPQ write operations
The main target is still to understand how the OPQ chip should be controlled and how each register is affecting to the sound. There are 256 register and each of them has 256 possible values, so writing a program to blindly poke random values to registers won’t lead to anything. The values written by the original firmware would be a good starting point, but as explained, it would be too tedious to dig them up from the disassembly.
Because it looks like all write operations to the chip are done using one function, it might be possible to capture those by monitoring the function calls and the call parameters. The result could then be dumped out from the serial port. If all this could be done so that the original firmware would continue working, it would be a great opportunity to learn how the OPQ is controlled while the keyboard is played, sounds are changed from the front panel etc. But this proved to be not so simple.
First attempt was to make a modified version of the original firmware:
- The firmware is writing continuously midi Active Sensing message to the UART. I want to use the serial port for my own purposes, so the original serial transmit buffer write function must be modified to dummy, not doing anything.
- Modified the OPQ write function so that it first dumps the call parameters (two bytes, register number and register value) to the serial buffer and then returns to execute the original OPQ write.
Very soon this proved not to be the winning strategy. The serial transmit buffer is made for handling midi messages, so it is quite small (128 bytes). Each OPQ register write will write two bytes to buffer, and the calls are coming fast, so you won’t get over 64 register writes stored in any case. Simple actions, like pressing or releasing a key on the keyboard produce only about 20 register writes, but changing sound writes all 256 registers, so no hope capturing these.
The only way is to get all the data buffered at once and then dump it from the buffer. The register writes are bursts, so normally there is plenty of time to get the data sent after the burst. I finally found a contiguous 1500 byte area in RAM where there are no variables, according to yazd cross reference. It might be a buffer for own accompaniments or something like that, where the firmware is not normally writing anything. I blatantly took it to my own use.
The
SoftwareSerial library in the receiving Arduino had also problems
when the data was pouring in with the full 31.25 kbps speed. Now that
we have no hurry in dumping the data, I changed the sending so that
only one register write is sent for each 10 ms real-time
interrupt. A full 256 register set write will take about 2.5 secs but
that’s no problem in practice. It is a joy to watch how the data dump flows out when pressing a key or a button in the control panel. Now it is possible to really see, what the firmware is doing while the instrument is used. This
is how the dumps look like:
Now we are getting plenty of data, and the next thing is to try to interpret it. In the next post we will start analyzing the dumps and try to understand the OPQ functionality.
Comments
Post a Comment