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


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


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



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

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


  • 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.

  • Finally, something one can listen to

    Isa Twospirit06/11/2018 at 14:14 0 comments

    Over the course of the weekend I burned a set of WaveROMs with rather generic waveforms, plugged them into the DW, and - we have sound.

    That sounded promising enough to convert some other waves into an appropriate format - Some of you might recognize the wave names from $somewhere:

    ...this would not have been possible without the excellent information found here

    And now - let's go find a useful EPROM :)

  • Hello, I'm new here...

    Isa Twospirit06/08/2018 at 11:00 0 comments

    ...and I'll be contributing what I can to make this project work.
    I stumbled across it while looking up mods that could be applied to my DW6000. Since it's almost due for inspection (y'know, them oulde caps and stuff), this seems to be the right time.
    After some messages going back and forth, Mateusz and I decided I could join in.
    And what will I be doing?
    For now, I'll focus on getting waveforms ready for the DW. As a start, I'll generate them using a little tool I created a while ago to feed a 8-Bit-ATMega-based MiniSynth (Yup, it's VB.Net. To keep things simples, audio output is performed using NAudio):Later on, I'll see to adapt waveforms stripped from other machines.

    Just in time, an EPROM programmer and a small batch of 27C256 EPROMs dropped in today, so in theory I could start burning.
    But not just now - first I'll need to open up my DW and replace the stock waveform EPROMs with sockets.

    Stay tuned!

  • RTFM

    mateusz.kolanski09/27/2017 at 10:57 0 comments

    Just as I thought, there must be a reason behind every piece of code. I don't know MIDI specification by heart, but I had a feeling, that it might be the reason. And (unfortunately) I was right.

    This is an excerpt from the official MIDI specification. The MSB destinguishes between status and data bytes. That's why one does not simply use 8 bits for patch data. Bummer. Using only 2 bits for bank switching doesn't sound good enough to me, so I will probably rewrite the code and use 2 subsequent bytes to carry 2 bits each. Pros? Moar banks. Cons? I won't be able to reuse as much code as currently. Back to square 2.

  • Hello, world!

    mateusz.kolanski09/26/2017 at 20:40 0 comments

    When you're developing something that lives only inside of an emulator, there's a big chance that the code, which seems to work just fine in a debugger, will fail to work on real hardware. That's exactely why I wanted to hook up a logic analyzer and see what my code is doing outside of a silicon chip. Unfortunately due to my non existing safety precautions I have released the magic smoke from the CPU and didn't have a chance to see my code on real hardware. You know the story.

    This time I have taken a slightly different approach. No darn logic analyzers. Real hardware only. This is what I ended up with:

    It's pretty simple: just an octal edge-triggered latch and some LEDs. The latches are connected to PB4..6 and the clock input goes to PC2. As you can see this time I have used some heavy duty tape, just in case :) OK, but will it work?

    Nothing happens so far. Maybe I should change the bank number?

    Nice, looks like a binary five! So it works. Kinda. I have cheated a little bit. You see, the code I wrote gets executed only if I explicitely change the bank number, but if the patch is loaded from the memory (e.g. on power on or patch change), nothing happens. I have to dig a little deeper and find another place to call my bank switching routine. But I think that I already earned a beer:)

    Oh, there's one more thing that can complicate a thing or two. This piece of code:

    	LXI	EA, 2680H   ; EA = $2680 (working patch)
    	LXI	H, 2000H    ; HL = $2000 (patch memory)
    .1AEEH:	LDAX	H	    ; A = (HL)
    	ANI	A, 7FH	    ; highest bit = 0
    	STAX	H	    ; save back to (HL)
    	INX	H	    ; HL++
    	DEQ	EA, H	    ; loop until EA==HL
    	JR	.1AEEH		

    For some strange reason the memory region which contains patches gets ANDed with 0x7F to set the highest bit to zero. If you look at the DW6000 bit map, you can see that no parameter uses the highest byte. Unfortunately my parameter violates this rule:

    Is there any reason behind it? Well, there are two ways to find out: a) analyze the source code or b) disable it and see what happens. Guess which path I will take:)

  • Back on track

    mateusz.kolanski09/19/2017 at 12:25 0 comments

    After over one month, DW6k is working again. Yay! Fortunately only the main CPU was broken and after quick (sorta) replacement, everything seems to work just fine. It took so long, because I have ordered the ICs from China and they stuck at customs.

    The replacement took longer than I expected, mostly because I wanted to install the IC in a machined socket (you know, just in case), but I couldn't fit all the pins at once, so after more than one hour long struggle, I have soldered the chip directly to the PCB.

    For safety's sake I have poured a ton of hot snot on all exposed mains connectors :)

    After replacing the fuse I have connected the board to the PSU, powered it on and... success! It blinks again.

    Another few moments later I have connected other boards and it looks like this lil one is tougher than I thought - works like back in the day.

    More to come soon...

  • We're not dead - errata

    mateusz.kolanski08/09/2017 at 10:50 0 comments

    Some time ago I told you, that we're not dead yet, but.. that's not entirely true. Long story short - I was sniffing the GPIOs with a logic analyzer to see how the new code works and suddenly I have shorted out mains to the chassis. Boom, flash, circuit breaker engaged, but it was too late. Logic analyzer is partially vaporized, my laptop doesn't start anymore (at least my hard drive seems to be intact) and the logic board of DW6k is dead as well... I don't know yet if the analog boards are fine, but first I will try to fix the digital one (mostly off the shelf low cost chips) and try to estimate the damage. upd7810 is fried, no clue about other logic chips. Fail of the week, huh?

  • History of 1-4

    mateusz.kolanski07/30/2017 at 14:27 0 comments

    So, it's been a long time (almost a year), but I finally got back to my little project and done some progress. If you know DW6000, or if your perception level is at least somewhere round 7, you will see that on the picture I posted yesterday I dialed in the new, non existant parameter 14. So, here's the story:

    Before I even started further reverse engineering, I got in touch with Alfred Arnold (creator of the assembler I'm using) and asked him a few questions, because I wanted to get rid of any hardcoded addresses in jump tables. At the beginning I wanted to write a macro which would convert any label into either high or low byte and replace it with a nice DB. Long story short - the answer was pretty simple - all I needed to do was to use DWs instead. I didn't think about it first, because it seemed too easy:)


    .TBL03:	TABLE
    	DB    29H
            DB    14H


    .TBL03:	TABLE
    	DW    .1429H    ; that's an autogenerated label pointing 
                            ; (originally) to 0x1429h

    Then I fired up my emulator again (I didn't change anything in it, it just works) and started with setting a watchpoint which would break the execution if anything tries to read the data from any of both of the tables (more on them later). That brought me somewhat closer to what I wanted to do - I ended up knowing more or less where's the code which reads the data from the tables and does the stuff with it.

    Next I just started to change parameter numbers and/or parameter values (incrementing or decrementing them) and just looking into memory window searching for some patterns. And I finally found something: two offsets which are always taking the parameter's value. Another watchpoint set and... bingo! With some backtracking I have finally found the code I was looking for.

    Now, a word about the data tables. There are two tables (related to synthesizer's parameters) and in order to modify the code I had to understand precisely what kind of data is stored in them. The first one is 144 bytes (48x3) long. Each entry represents a parameter number (11..16, 21..26 and so on). 

    The 1st byte holds the information about parameter offset (bits 3-7) relative to the beginning of each patch and its beginning bit (bits 0-2). That makes more sense if you just take a look at the DW6k's service manual (page 6, DW-6000 bit map).

    The high nibble (4-7) of the second byte is a numeric value (0-9) which multiplied by 4 (size of an entry in the second table) gives an offset to the value from the second table. Just a relative pointer to say so. The low nibble holds a value between 0 and 3 which selects an appropriate display subroutine:
    - 0: "normal" 2 digit (max) value (e.g. 0-31)
    - 1: "normal" 2 digit (max) value, incremented (e.g. 0-31 displayed as 1-32)
    - 2: value from 0 to 2 with translation -> 0=16, 1=8, 2=4 (octave selection)
    - 3: value from 0 to 4 with translation -> 0=1, 1=-3, 2=3, 3=4, 4=5 (interval selection)

    The last byte is kind of an index of each parameter with a small twist - "local" (per pach) parameters go from 0x00 to 0x21, "global" parameters (81-83) from 0xF0 to 0xF2 and invalid parameters (like our 14)  are marked with 0xFF.

    The 2nd table isn't so exciting - the 1st byte is the maximum parameter's value, the second one is the bit mask to be applied on the value (after bit shifting) to get the desired value (hope you know what I mean:)). The 3rd one - I have no idea whatsoever, but it hasn't been used in any code which looks interesting to me, so let's just skip it. The last byte is another bitmask used to get or set the value.

    Knowing all of that I took a look at the bit map again to find out where I could store my new parameter. Unfortunately there's no continuous 4 bit space, so I had to use only 3 bits and extend the memory by 8 banks (not 16 as I planned before). The second byte which holds the value of portamento time consumes...

    Read more »

  • A teaser

    mateusz.kolanski08/18/2016 at 21:11 0 comments

    I told you, we're not dead :)

    More to come soon!

  • We're not dead (yet...)

    mateusz.kolanski08/14/2016 at 20:35 0 comments

    It's been a long time, but I needed a break to stop going in circles, gain some distance, energy and new ideas to continue my struggle.

    As you probably now, I've been using a modified MAME/MESS driver written for a handheld console called Game Master. As I started to play with it, I just wanted to have a way to see the DW-6000 firmware running, but as you know, the more you have, the more you want.

    Yesterday I started to write my own MESS driver from scratch. It's not that complicated, once you know what are you doing. After a few hours I ended up with a working skeleton emulator with working (well, sort of) keyboard support and display.

    I'm not going to show you anything now, because the keyboard works (very unreliably and somewhat random) just because of my mistake in coding (what do you know, sometimes it's a good thing) and after I fixed it, it stopped working. As I stated before it's probably because of the debouncing code in DW6000's firmware and that's something I need to investigate and understand now.

    Apart from that I have a nice debugging tool which still needs some work to be fully useful, but right now it can do a thing or two.

    So here's the plan for the nearest future:

    • investigate how the debouncing routine works and 'fix' the keyboard support problem
    • write a function that will be triggered by a press of a numeric key (1-8) and then it will watch the memory for a write of that value - that should help me track down the memory address which holds the number of a parameter or value we want to select

    Keep the fingers crossed!

View all 20 project logs

Enjoy this project?



Similar Projects

Does this project spark your interest?

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