The Memory Map Reveals Itself
The firmware's chip-select writes told us everything about the hardware layout — we just had to listen to the I/O traces.
Chip-Selects Tell the Story
After getting the CPU to boot and execute the first instructions — the MOVE #$2700,SR that kicked off the Resurrection story — we turned to the emulator's I/O logging output. Every bus access to an unmapped address was captured. And the firmware's very first act after masking interrupts was configuring the 68302's chip-select registers at FC6830 and above.
Each chip-select write is a declaration: "there is a device at this address, with this size, and these access properties." The firmware was literally drawing us a map of its own hardware.
[I/O W]FC6830 <- 0068 ; CS0 base = 0x000000 (boot ROM, initial)[I/O W]FC6832 <- F830 ; CS0 option: 256KB, read-only[I/O W]FC6834 <- 0040 ; CS1 base = 0x400000[I/O W]FC6836 <- 7C30 ; CS1 option: 64KB region[I/O W]FC6838 <- 0000 ; CS2 base = 0x000000 (RAM)[I/O W]FC683A <- 1830 ; CS2 option: 512KB, read/write[I/O W]FC683C <- 00FC ; CS3 base = 0xFC0000 (68302 SIM)[I/O W]FC683E <- 7E30 ; CS3 option: 8KB regionFour chip-select registers, four memory regions, and already a complete picture of the base hardware layout. But the interesting part was what came next.
The ROM Remap Trick
On a 68000, address 0x000000 is sacred — it holds the reset vectors (initial SSP and PC). The ROM must be there at power-on, or the CPU has nothing to execute. But after boot, you want RAM at address 0 so the vector table can be modified at runtime (to install interrupt handlers, trap vectors, etc.). Every 68k system faces this chicken-and-egg problem, and the ASR-10 solves it with an elegant trick.
The firmware copies a tiny 14-byte stub — just 7 words of 68000 machine code — to the 68302's internal RAM at FC6200. This RAM is always accessible regardless of chip-select configuration, because it's part of the 68302 itself. Then the firmware jumps to the stub.
; Executing from 68302 internal RAM at FC6200[FC6200] MOVE.W #$F800, ($FC6830) ; CS0 base = 0xF80000[FC6206] MOVE.W #$7830, ($FC6832) ; CS0 option: 256KB, R/O[FC620C]JMP $F80210.L; jump to ROM at new address; ROM has moved: 0x000000 -> 0xF80000; But with 24-bit bus masking: 0xF80000 = 0xFF80000 externally; Address 0x000000 is now free for RAM The stub reconfigures CS0 to move the ROM from 0x000000 to 0xF80000. On the 68302's 24-bit address bus, this maps to the physical range 0xF80000–0xFBFFFF. The stub then jumps to the same code it was just executing, but at the new ROM address. From this point on, RAM occupies address 0 and the vector table is writable.
This is why the stub must live in internal RAM — it's the only memory that doesn't move when the chip-selects are reconfigured. If the stub were in ROM, it would be remapping the memory out from under its own feet.
The 24-bit address bus is a key detail. The 68302 only drives 24 address lines (A0–A23), so bit 24 and above are ignored in hardware. Address 0xF80000 and 0xFF80000 refer to the same physical location. In Ghidra, references to 0xFFF8xxxx and 0x00F8xxxx are the same ROM bytes.
ROM Mirroring
With the ROM at 0xF80000, we expected it to occupy exactly 0xF80000–0xFBFFFF (256 KB). But the I/O trace showed successful reads from 0xFF8xxx — well outside that range. What was going on?
The answer: partial address decoding. The chip-select logic doesn't decode all 24 address bits. The CS0 option register configures a 256 KB window, which means only address bits A0–A17 are used to select bytes within the ROM. The upper bits (A18–A23) are only partially matched, causing the ROM to appear at multiple mirror addresses in the bus space.
Code at 0xFF8xxx was actually reading ROM data from the same physical chip as 0xF8xxxx. Our emulator had to account for this — any address where the low 18 bits matched a ROM offset and the upper bits matched the chip-select mask was a valid ROM access. Getting this wrong produced hard-to-diagnose crashes where firmware reads returned garbage instead of instructions.
Four Chips on the Bus
With the ROM and RAM sorted out, the emulator's I/O logger began capturing accesses to the peripheral address space at 0xFC0000 and above. By analyzing the access patterns — which addresses were read, which were written, what values were used, and what the firmware did with the results — we identified four separate chips on the bus.
[I/O W]FC2000 <- 0020 ; DOC: write to voice register[I/O W]FC2002 <- 4000 ; DOC: voice frequency[I/O R]FC2061 -> ???? ; DOC: read voice status[I/O W]FC3080 <- 0001 ; DSP: GPR read select[I/O W]FC30A0 <- 0000 ; DSP: GPR write[I/O W]FC30C0 <- FFFF ; DSP: instruction write[I/O W]FC30E0 <- 0000 ; DSP: write both[I/O R]FC4813 -> ???? ; DUART: read SRB (status reg B)[I/O W]FC4817 <- 00E7 ; DUART: write THRB (TX byte)[I/O W]FC6830 <- F800 ; SIM: chip-select CS0 base[I/O R]FC6200 -> ???? ; SIM: internal SRAM readThe four chips, identified from their address ranges and access patterns:
FC20xx= Ensoniq ES5506 DOC (Digital Oscillator Chip) — A 32-voice wavetable synthesizer. We initially mistook this for the 68302's built-in SCC (Serial Communication Controller), which also lives in theFC2xxxrange. The access pattern gave it away: writes to voice frequency, volume, and start/end address registers, in a loop that iterates exactly 32 times. This is the chip that generates the raw audio waveforms from sample data in RAM.FC30xx= Ensoniq ES5510 DSP — The effects processor. The firmware uploads microcode through a specific protocol: write to offset0x80(read-select GPR),0xA0(write GPR),0xC0(write instruction),0xE0(write both). We watched it upload all 160 instruction words of the DSP program.FC48xx= Motorola 68681 DUART — A dual UART for serial communication. Channel B handles the front panel (LCD display and buttons). The tell: odd-byte addressing (FC4813,FC4817) — the 8-bit DUART sits on the low byte of the 16-bit bus.FC6xxx= 68302 SIM (System Integration Module) — The microcontroller's own internal registers: chip-selects, timers, DMA, and 2 KB of internal SRAM atFC6200.
The Mystery Peripheral
After the chip-selects were configured and the main peripherals initialized, the I/O log showed a curious sequence of writes to an address we hadn't seen before:
[I/O W]408000 <- 1111; write test pattern 1[I/O R]408000 -> 1111; read back — match?[I/O W]408000 <- 2222; write test pattern 2[I/O R]408000 -> 2222; read back — match?[I/O W]408000 <- 3333; write test pattern 3[I/O R]408000 -> 3333; read back — match?; Same register accessed at 3 aliased addresses:[I/O W]808000 <- 1111 ; same physical register[I/O W]C08000 <- 1111 ; same physical register; 0x408000 = 0x808000 = 0xC08000 (partial address decoding) Write 0x1111, read back. Write 0x2222, read back. Write 0x3333, read back. This is a textbook RAM test pattern. The firmware was probing whether memory existed at this address. And the same register appeared at three aliased addresses — 0x408000, 0x808000, and 0xC08000 — due to partial address decoding, just like the ROM mirrors.
The nature of this peripheral remained unclear at this stage. It could be sample RAM, a secondary I/O device, or expansion memory. But the firmware's own test routine told us exactly how to respond: echo back whatever was written. For now, that was enough to pass the test and let boot continue.
The Complete Map
After analyzing the full boot trace, we reconstructed the ASR-10's complete memory map. Every entry was discovered empirically — not from a schematic, not from documentation, but from the firmware's own initialization code telling us where its hardware lives.
| Address Range | Device | Size |
|---|---|---|
| 000000–07FFFF | Main RAM | 512 KB |
| 408000 | Peripheral (RAM-testable, aliased) | -- |
| FC20xx | ES5506 DOC — 32-voice wavetable synth | 256 bytes |
| FC30xx | ES5510 DSP — effects processor | 256 bytes |
| FC48xx | 68681 DUART — front panel serial | 32 regs (odd bytes) |
| FC6000–FC6FFF | 68302 SIM — config, timers, SRAM | 4 KB |
| F80000–FBFFFF | Boot ROM (remapped from 0x000000) | 256 KB |
This map became the foundation for everything that followed. Each chip needed a mock implementation that responded correctly to the firmware's initialization protocol. Getting any address wrong — even a single bit in a status register — would send the firmware into an infinite retry loop.
The beauty of emulation-driven reverse engineering is that you don't need the schematic. You don't need the service manual. You don't even need to own the hardware. The firmware is the documentation — every chip-select write is a declaration of what exists, every I/O read is a question you can learn to answer. Let the code tell you what it expects, and build the world around it.
Next: with the memory map established, the firmware expected each chip to respond. The ES5510 wanted its microcode acknowledged, the DUART needed status bits set just right, and a single wrong bit kept the boot loop spinning for days. Read on in Mocking the Silicon.
