Close
0%
0%

Project 72 - Korg DW-6000 wave memory expansion

An attempt to reverse engineer and modify Korg DW-6000s firmware in order to expand its wave memory.

Similar projects worth following
One day I stumbled upon Chris Strellis' Korg DW-6000 256 Waveforms Mod. What a great mod, I thought, but unfortunately Chris didn't want to share his overengineered (at least that's what I was thinking back then...) CPLD design. Furthermore, I like the factory look of my gear, so cutting holes is definitely a no-go. My DW-6000 was broken, so I spent a reasonable amount of time fixing it. Reading schematics gave me this crazy idea, that if I could drive the unused half of upd7810's port B, I could address 16x more waveform memory than the DW does just by soldering 4 wires from the CPU to the ROM chips. That sounds way more simple than using a CPLD chip.

Well, let's see if I manage to reverse-engineer this thing and squeeze some code in 72 remaining bytes...

Korg DW-6000 is a 6 voice polyphonic synthesizer from 1985. It's hybrid, because its tone generators are digital (sample based), but amplifiers and filters are analog. DW-6000 has been superseded by his bigger brother, DW-8000 which featured more waveforms (16 instead of 8), more voices (8 instead of 6), digital effects section (well, I think that the DW-6000's analog chorus is way better than DW-8000's digital delay, but you know, back then - c'mon - it's digital dude, and digital is better).

Internally, DW-6000 consists of 3 main boards:

  • KLM-653 - programmer / assigner board - (this is the board I'm working on)
  • KLM-654 - generator (here be voice ROMs)
  • KLM-655 - analog board

This is how it looks like on a block diagram:

DW-6000 block diagram

The idea (at least from electrical point of view) is fairly simple:

  • replace the stock ROMs (KLM-654, IC29 and IC30 - HN613256, 256 kb) with bigger ones (4 Mb) and fill them with some new waveforms
  • connect four unused address lines (A15-A18) to PB4-PB7 of IC1 (uPD7811, KLM-653)
  • modify DW-6000s firmware to implement bank switching mechanism - add a new parameter (14) with 4 bit resolution (1-16), which will tell the CPU to set a value on PB4-PB7

more paint masteryP.S. I have started this project some time ago and I didn't pay much attention to logging activities, that's why I'm adding a whole bunch of stuff right now and that's also why I'm missing some dates. Once I'm happy with my backlog, I will make this project public and start logging things as they appear.

Adobe Portable Document Format - 2.65 MB - 03/14/2016 at 06:06

Preview
Download

Adobe Portable Document Format - 3.16 MB - 03/12/2016 at 18:38

Preview
Download

Adobe Portable Document Format - 1.35 MB - 03/12/2016 at 18:38

Preview
Download

Nec_UCOM-87AD_family_8-Bit_Microcomputers_UPD78C1X_Users_manual.pdf

The ultimate source of information about uPD78(C)1x

Adobe Portable Document Format - 7.77 MB - 03/12/2016 at 18:38

Preview
Download

  • PCBs

    mateusz.kolanski12/14/2018 at 19:08 0 comments

    Some time ago I have designed and ordered a few adapter PCBs. Finally they have arrived!

    They are looking really good, I just messed up the silkscreen - JP1 should be labeled L321 instead of L123. And the whole PCB is kind of upside down, but I'm really happy how it turned out, not bad for a first PCB:) 

    This is how it looks after assembly:

    I have decided to put some LEDs (just in case). JP1 is not populated yet, they ran out of angled connectors in my local shop and I didn't want to wait the whole weekend. Fortunately I found an old floppy connector lying around and after some tweaking I got it installed.

    Looks nice!

  • ...fixed

    mateusz.kolanski11/19/2018 at 20:27 0 comments

    Looks like it helped, yay!

  • Debugging the hardware

    mateusz.kolanski11/19/2018 at 20:14 1 comment

    Hi there, since my last post I've been waiting for the new batch of memory chips. EEPROMS this time for convenience sake. I got them today, burned the new firmware (the one with bank switching routine surrounded with DI/EI), installed it, but unfortunately it's still glitching. I've (CAREFULLY) hooked up a logic analyzer and gave it a go. 

    No magic smoke this time :)

    I've found the reason why it sometimes fails. Look at this pic first:

    You can see, that D0 goes high, then after 6us CLK goes low and after 5us more it goes back high. This is when '374 does the switching (see transition @Q0 and Q1). So far, so good. But this is how it looks when it glitches:

    You can clearly see that the clock pulse is way longer than before and the rising edge misses whatever has been set ad D0..D2 inputs. Why? Well, normally I would say, it's an interrupt. But this code should be interrupt safe now!

    Yeeeah, should is a good word. Take a look at my 'smart' code again:

    .BSHND:	EQI	E, 22H		; ID == 22? (1-4 ,select bank)
    	JR	.NOBS		; nope, skip
    	LTI	D, 04H
    	JR	.MORE
    .LESS:	ANIW	82H, 3FH	; unset 6th bit @ $2682
    	JR	.BS		; that's all, we can set the bank number
    .MORE:	ORIW	82H, 40H	; set 6th bit @ $2682
    	ANIW	81H, 7FH	; unset 7th bit @ $2681
    	DI			; I wonder if it helps...
    .BS:	CALL	.BANKSWITCH	; call bank switch routine
    	EI
    	JR	.11E1H		; and skip KLM-654 transmission part

    It will clearly only work if you go through the .MORE branch. Going through .LESS causes the jump to .BS label which skips DI instruction! D'oh.... OK, let's move the .BS label to the previous line, build the binary again, burn it and test it. Se you later :)

  • It works!

    mateusz.kolanski11/12/2018 at 21:12 0 comments

    Yes, it does! Finally after too many hours spent on it I've installed everything inside of my DW6k, flipped the switch and... no magic smoke! But let's take a step back. 

    First I've desoldered the original ROMs and installed machined sockets.

    After making sure, that it still works, I've soldered some wires directly to the CPU (notice the battery:)) ...

    ... double checked and installed my ghetto adapter (since it works, I will make a nice PCB soon) with HC374 and two W49F020s full of waveforms ...

    ... looks nice, yet a little bit messy, "hardware debugger" on the right :) ...

    ... and it works! I've been playing a few minutes and the new waveforms sound really great. I'll post a few samples soon, maybe even a video to show you that I'm not making this out :)

    The only thing to address is the occasional glitch during bank switch, after that I'll order a small batch of PCBs, test it again and we're good with V1.0 :)

  • Another quickie

    mateusz.kolanski11/12/2018 at 17:46 0 comments

    I've added another feature to my emulator: now I can watch port B's upper half and the latch signal on port C. It looks like everything's fine, I couldn't reproduce the glitch I saw on the real hardware. But from the other hand, I'm emulating an isolated portion of the original hardware, so your mileage may vary. I suspect, that the bank change routine may be sensitive to interrupts, so once I get my 2864s, I'll try to disable interrupts before executing it.

    Other than that, I've assembled a memory adapter on a veroboard, but didn't have a chance to test it yet. I don't know if driving address lines connected in parallel it's something HC374 can do without hiccups, we'll see. First I need to prepare waveform banks, I will probably use Isa's wonderful piece of software for that purpose. 

  • Facepalm!

    mateusz.kolanski11/10/2018 at 20:30 0 comments

    Ok, it turns out that mame debugger can load/save memory from/to file. I've downloaded a sysex file containing 64 factory patches, cleaned it up and loaded it in debugger. I've edited first 8 patches again, setting bank number from 1 to 8. Save, restart, load - all was there. No idea why it didn't work on the hardware. Maybe I have saved patches 2-1 - 2-8 and loaded 1-1 - 1-8 instead? No clue. I have also added the NOP instruction between latch pulses - maybe that's why it sometimes doesn't work, who knows. At least I had one free byte for my disposal :)

    I got back to the synthesizer (of course with the old ROM, because I have nothing to write the firmware to), played with it a little bit only to confirm, that sometimes changing bank number from 2 to 3 doesn't work as it should - instead of 3 it sets 0. Other combinations seem to work fine, so I doubt it's a timing issue... Maybe I'll hook up my logic analyzer (be careful this time:>).

    Oh! I almost forgot! I know why the bank numbers were messed up after reboot - there's no SRAM battery on the mainboard! FACEPALM! But that solves one of my problems :)

  • Almost there

    mateusz.kolanski11/10/2018 at 16:53 0 comments

    Hi there, after some struggle I've managed to get even closer to the end. What I got now is an almost working version. Yes, this time I have burnt an EPROM (the last empty one I had - I don't have an eraser, but I've already ordered some EEPROMS) and tested in on real hardware.

    What works?
    • the new parameter 1-4 is unlocked and you can change its value from 1 to 8
    • LEDs connected to upd7810 show the correct binary value (i.e. port and latch are working fine)
    • you can save and recall the bank number to/from SRAM

    Looks nice, huh? So what's wrong?

    • sometimes even if the value is set properly (cycling from 1 to 8), it doesn't get send to the hardware (now I need to figure out if it's because of wrong value being read or set)
    • after restart, saved bank values are messed up

    I have an idea why the first problem occurs, it's probably because the bank switching handler is not interrupt safe. Even if the fix looks easy to implement, I have another problem - keep reading.

    As for the second part, I think I could modify my emulator so that the SRAM is not handled as an ordinary RAM but rather saved and loaded to a file. That would give me an opportunity to see how it behaves after reboot.

    Now, my biggest problem is the available memory, or lack of it. I managed to squeeze my code into the EPROM, but I had to sacrifice a few lines of code to do that. Some time ago I said, that my plan B would be to remove tape saving and loading routines, because that's someting nobody's gonna use, especially if there's MIDI available. So far I have removed the code that clears the value on displays before writing TAPE on them. Not a big deal, now it looks more like TAPE_1 or TAPE<whatever walue was set>. I think, I will remove saving to tape first, because even if there's a slightliest chance, that somebody has some old patches stored on tape or wave file, I really doubt that anybody would like to save anything back to tape...

    I may have another session with debugger this weekend. I'll keep you informed.

  • Stack Overflow!

    mateusz.kolanski11/07/2018 at 20:27 0 comments

    No, I'm not browsing SO looking for help. I'm experiencing it right now :) After I found the definitive places where I'm going to inject my code, I noticed that my .BANKSWITCH 'thingy' must become a callable subroutine. Blah blah blah... Wait a minute, I think I know why it fails. BRB

    [10 minutes later]

    Hi there. I started writing this post because - for some strange reason - my debugger decided to crash. Not once, not twice, but all the time. I didn't quite expect this kind of behaviour, but when I loaded vanilla ROM, it was rock stable again. OK, quick look into GIT and I quickly figured out that the only change was my dreadful subroutine I created yesterday. I needed a callable sub to be able to, well, call it from different places. The biggest limitation of the previous one was the arbitral jump into the place it should go back plus the fact, that it completely relied on values in registers. This is the old one:

    MOV	A, D		; bank number (0-7) stored in D, copy to A
    SLL	A		; store in high nibble
    SLL	A
    SLL	A
    SLL	A
    ;OFFIW 	82H, 40H	; check if $2682 bit 5 is set
    ;ORI	A, 40H		; yes, we have 'carry'
    MOV	B, A		; store in B
    DI			; enter critical section
    MOV	A, PB		; get current PB
    ANI	A, 0FH		; wipe high nibble
    ORA	A, B		; join together
    MOV	PB, A		; speak to hardware
    XRI	PC, 04H		; enable latch (PC2)
    NOP			; wait
    XRI	PC, 04H		; disable latch
    EI			; enable interrupts
    JMP	.11E1H		; go back (label is my guess:)

    The jump back was actually spot-on (I figured it out after some more cumbersome code analysis), but like I said it didn't fit at all in the other place. This is what I ended up with:

    LDAW	81H		; load word from $2681 to A
    SLR 	A		; shift right, now we have our value in b4-b5
    OFFIW	82H, 40H	; check if bit 6 @ $2682 is set
    ORI	A, 40H		; it is, so let's do the same with our data
    ANI	A, 70H		; clean-up
    MOV	B, A		; store in B
    DI			; enter critical section
    MOV	A, PB		; get current PB
    ANI	A, 0FH		; wipe high nibble
    ORA	A, B		; join together
    MOV	PB, A		; speak to hardware
    XRI	PC, 04H		; enable latch (PC2)
    NOP			; wait
    XRI	PC, 04H		; disable latch
    EI			; enable interrupts
    RET

    First off, I'm using absolute addresses instead of values from registers. They ain't gonna change, so I can hardcode them. And since the value I'm 'extracting' is already in bits b4-b5 and finally I'm writing it back to the upper half of PB, only one shift is necessary. Furthermore, if we store the high bit already on the 'right' position, all we need to do is to check if it's set and copy its state into our value. Neat. So far so good. But then MAME crashed a few times. I managed to go in step mode into my routine and then I saw that the value of the vector register (V) is not 0x26 but 0x27. I was pretty sure, that the context will be right, but it turned out that I was wrong. Maybe that's why it crashed, huh? OK, let's modify it one more time:

    PUSH    V
    MVI     V, 27H
    ...
    POP     V
    

    That should do the trick. I was only concerned about the stack size, cause it's pretty tiny, but I decided to give it another go instead. Nope, still the same. I set a breakpoint on my new code again and saw that each call of my sub causes the bottommost part of the memory (where the stack lives) to fill with some repeating pattern. Yup, stack overflow.

    That was the moment I decided to write a post, to share a laugh and maybe let my brain cool down a little bit. But after a few lines it struck me where the problem was.

    I have rewritten this code, because I needed to run it every time new patch is loaded. I found a suitable place inside of a piece of code that sends the patch data to KLM-654 (DWGS board). After each chunk of data, KLM-654 sends an ACK signal to say that it finished processing the previous one. It generates an interrupt and then the magic happens. After having sent the whole patch, the code branches and this is where my code is being called. I'm not going to show you the whole ISR, but here's something I have clearly missed:...

    Read more »

  • Quick update

    mateusz.kolanski11/04/2018 at 18:05 0 comments

    Hi there. I finally managed to find some time and motivation to get back to business. Quick recap: I knew that my new code worked to some extent, but it wasn't triggering automatically and furthermore, it violated MIDI specification.

    Let's analyze the second problem again (I know, I can repeat myself, but it's been a long time): in order to store more than two bits of data, I'm gonna need to split this variable into two memory addresses and do some masking and shifting. The handling of parameters is done in pretty flexible way - there's a generic handler that can use metadata provided with each parameter. This ease of use comes with some limitations though - of course it's not possible to span one parameter across multiple offsets and there's no 4-bit panel value handler too (like there's no parameter that takes values from 1 to 15, only 1-8, 0-31 and 0-63 plus some custom ones). From this point on I must consider possible routes:

    a) use only 2 bits available in one adress, expand waveform memory to 4 banks, changes needed: write a 2-bit value handler (quite easy, but some memory reorganization will be necessary)

    b) use the existing 3-bit handler, expand waveform memory to 8 banks, changes needed: store one bit somewhere, add custom parameter handler which will be able to read and write this bit in an adjacent address (probably hardcoded)

    c) a + b - use 4 bits of memory, changes needed: custom value handler and custom parameter/memory handler

    Should be doable in, well, finite time, but I'm a little bit concerned if I manage to squeeze it into available memory.

    Now let's get back to the first problem: my recent hack worked only on value change. That's nice, however setting the bank number once the patch is loaded should be a no-brainer. I spent a considerable amount of time trying to track down when this happens, but without any luck. Today I tried to do it once again: I fired up my emulator and set a watchpoint on the memory area occupied by the working patch (I've probably done that before) - that's basically your buffer, each time you recall a patch from the memory, it gets copied here and each modification is stored there as long as you keep the same patch number. Well, if it gets written, then it must be also read, right? Makes sense, but I couldn't capture it. I took a closer look at the schematic again. DW6000 consists of two processor boards connected by a parallel interface. But it's TCP-like, so instead of fireing and forgetting you need to wait for an ACK signal before you can send more data, right? The ACK signal coming from the DWGS board generates an interrupt which then gets handled by our lil board. OK, that might be a clue. Let's get back to the drawing board. Uhm, to the text editor and MAME source code I mean :) I have quickly appended following code to the write handler:

    if (offset == 0x4000)
    {
      logerror("[%.4X]: write to KLM-654, data=%.2X\n", m_maincpu->pc(), data);
      m_maincpu->set_input_line(UPD7810_INTF2, CLEAR_LINE);
      m_maincpu->set_input_line(UPD7810_INTF2, ASSERT_LINE);
    }

    It's a simple mock that reacts on each write to 0x6000 (that's right, but I'm using addresses relative to 0x2000 which is the beginning of external RAM) by generating an interrupt.

    And what do you know, it worked! Shortly I was able to see the whole transmission. It is basically a whole patch dump with a header (or message ID) after power on / patch change and another short message after each parameter change. 

    It seems that I need to put another hook before or after patch transmission, because KLM-653 doesn't give a shit about all the parameters. It only needs to know a few: assign mode and currently displayed parameter's value. And the others are read whenever they're being modified. That's it.

    More to come SOON.

  • Told ya, we're not dead!

    mateusz.kolanski06/12/2018 at 12:29 0 comments

    ...just procrastinating!

    But thanks to Isa I decided to get back to work. I haven't been updating this feed for some time, but that doesn't mean, that I've been doing nothing (well... sorta). 

    When I found out that my modification of the DW6k's bit map (aka patch structure) will corrupt SysEx messages, I knew that there's no turning back from some heavier firmware modifications. All I need to do is to write a function which assembles bigger values out of two bits available in almost each byte of patch data. Not quite a rocket science, just some shifts and masking (this is what assembly is all about:)), but must be precisely injected into the existing code. Doing it the old way (i.e. run the code in the emulator, set some break- and watchpoints, decipher what it does, comment, wash, rinse, repeat) is highly inefficient as it requires lots of patience and concentration. What I needed was a way to graphically represent the code including calls and conditionals. There's a brilliant program called IDA Pro, but unfortunately is way too expensive. OK, I thought - maybe I don't need it with its all bells and whistles. At one point I considered writing a piece of software which parses the source file and exports it to Graphviz format, but then I decided to spent comparable amount of time to do something better. Do you know radare2? It's an open source reverse engineering suite which supports multiple processor architectures. As for upd7810, if you can write a module for it it will run just fine:) And that's what I've been doing for some time. I've re-used some code from MAME (didn't have to type in all opcodes by hand), but I didn't manage to get it fully working (disassembly works fine, but the analyzer gets crazy on branching instructions). And that's what I'm gonna do now.

View all 29 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates