Acorn Electron Gremlins
Someone not only spilt water on my implementation but clearly fed it after midnight because the gremlins are out in force.
Be ready to snigger, point and mock, because this blog post is all about bugs, glorious bugs.
Bugs glorious bugs…
With the ROM, RAM and CPU wired up and enough of the ULA implemented to provide the CPU with a clock and RAM access, the stage is set for a first boot up. I guess the post title gives away how well that went.
In theory the CPU should attempt to read the RESET vector address which is the two bytes stored in ROM at 0xFFFC and 0xFFFD.
0xFFFC after removing the 15th bit (rom uses 14..0) is 0x7FFC. Looking in the combined 32kB basic+os rom file at that location shows the CPU should receive a RESET vector address of D2D8.

CPU Reset Vector Read Issue
Circled in green in the simulator image above shows the CPU does indeed receive D2D8 back from ROM however there’s a problem circled in red.
After reading D8D2 the CPU then reads A9 from that location followed by pushing d8d2 to the stack and 0x20. At this point it appears to read the IRQ vector FFFE/FFFF of DAE7 and starts executing from there. Not what I was expecting which was for it to start executing code at the RESET vector address.
It turns out I made a mistake and had set the CPU’s n_irq line using the Master IRQ.
isr_status(ISR_MASTER_IRQ) <= (isr_status(ISR_FRAME_END) and isr_en(ISR_FRAME_END)) or
(isr_status(ISR_RTC) and isr_en(ISR_RTC)) or
(isr_status(ISR_TX_EMPTY) and isr_en(ISR_TX_EMPTY)) or
(isr_status(ISR_RX_FULL) and isr_en(ISR_RX_FULL)) or
(isr_status(ISR_HIGH_TONE) and isr_en(ISR_HIGH_TONE));
o_n_irq <= isr_status(ISR_MASTER_IRQ);
The Master IRQ is high anytime an IRQ is flagged such as Power on Reset (POR), Cassette High Tone detected…
n_irq however should have been set to not Master IRQ as it is active low.
o_n_irq <= not isr_status(ISR_MASTER_IRQ);
One bug down, too many to go…
Memory Clear Failure
Having setup the video to output a blue fill by default and a green fill colour if the CPU attempted to put the ULA into mode 6. I was presented with a blue fill colour, yet another bug (or bugs).
Stepping through with the simulator and decoding each instruction the CPU read/executed was needed. For this I made use of several sites including 6502 Instruction Set Decoded and 6502 Opcodes.
With this and the simulator address/data output, I determined the CPU was executing the following sequence of instructions after coming out of reset.
Addr | Data | R/nW | Rom En | Instruction | Bytes | Cycles | Comments |
---|---|---|---|---|---|---|---|
0100 | UU | 1 | 0 | ||||
FFFC | D2 | 1 | 1 | Reset Vector | |||
FFFD | D8 | 1 | 1 | Reset Vector | |||
D8D2 | A9 | 1 | 1 | LDA #immediate | 2 | ||
D8D3 | 40 | 1 | 1 | ||||
D8D4 | 8D | 1 | 1 | STA absolute | 3 | ||
D8D5 | 00 | 1 | 1 | lsb | |||
D8D6 | 0D | 1 | 1 | msb | |||
0D08 | 40 | 0 | 0 | ||||
D8D7 | 78 | 1 | 1 | SEI | 1 | Set Interrupt disable | |
D8D8 | D8 | 1 | 1 | CLD | 1 | Clear decimal | |
D8D9 | A2 | 1 | 1 | LXD #immediate | 2 | ||
D8DA | FF | 1 | 1 | ||||
D8DB | 9A | 1 | 1 | TXS | 2 | Transfer X to Stack pointer | |
D8DC | E8 | 1 | 1 | INX | 1 | Increment X (FF→00) | |
D8DD | 8E | 1 | 1 | STX absolute | 3 | ||
D8DE | 00 | 1 | 1 | ||||
D8DF | FE | 1 | 1 | ||||
FE00 | 00 | 0 | 0 | Clear ISR_EN | |||
D8E0 | 8E | 1 | 1 | STX absolute | 3 | ||
D8E1 | 8D | 1 | 1 | lsb | |||
D8E2 | 02 | 1 | 1 | msb | |||
028D | 00 | 0 | 0 | ||||
D8E3 | A9 | 1 | 1 | LDA #immediate | 2 | ||
D8E4 | F8 | 1 | 1 | ||||
D8E5 | 8D | 1 | 1 | STA absolute | 3 | ||
D8E6 | 05 | 1 | 1 | lsb | |||
D8E7 | FE | 1 | 1 | msb | |||
FE05 | F8 | 0 | 0 | ISR_PAGING 00001000, Keyboard page 8. | |||
D8E8 | AD | 1 | 1 | LDA absolute | 3 | ||
D8E9 | 00 | 1 | 1 | lsb | |||
D8EA | FE | 1 | 1 | msb | |||
FE00 | 02 | 1 | 0 | Read isr_en also clearing POR after read | |||
D8EB | 29 | 1 | 1 | AND #immediate | 2 | ||
D8EC | 02 | 1 | 1 | ||||
D8ED | 49 | 1 | 1 | EOR #immediate | 2 | ||
D8EE | 02 | 1 | 1 | ||||
D8EF | 48 | 1 | 1 | PHA | 3 | push accumulator | |
D8F0 | F0 | 1 | 1 | ||||
01FF | 00 | 0 | 0 | (result of 2 EOR 2 = 0 ie POR was set) | |||
D8F0 | F0 | 1 | 1 | BEQ | 2 | ||
D8F1 | 09 | 1 | 1 | ||||
D8F2 | AD | 1 | 1 | LDA absolute | |||
D8F3 | 58 | 1 | 1 | lsb | |||
D8F4 | 02 | 1 | 1 | msb | |||
0258 | UU | 1 | 0 | Why read here? BEQ failed to jump! | |||
D8F5 | 41 | 1 | 1 | ||||
D8F6 | C9 | 1 | 1 | ||||
D8F7 | 01 | 1 | 1 | ||||
D8F8 | D0 | 1 | 1 | ||||
D8F9 | 18 | 1 | 1 | ||||
D8FA | 4A | 1 | 1 | ||||
D8FB | A2 | 1 | 1 | LXD #immediate | 2 | Routing to clear memory If POR flag was set (This is where BEQ should have jumped to) | |
D8FC | 04 | 1 | 1 | ||||
D8FD | 86 | 1 | 1 | STX zero page | 2 | ||
D8FE | 01 | 1 | 1 | ||||
0001 | 04 | 0 | 0 | ||||
D8FF | 85 | 1 | 1 | STA zero page | 2 | ||
D900 | 00 | 1 | 1 | ||||
0000 | 00 | 0 | 0 | ||||
D901 | A8 | 1 | 1 | TAY | 1 | Transfer A to Y | |
D902 | 91 | 1 | 1 | STA indirect Y | 2 | ||
D903 | 00 | 1 | 1 | ||||
0000 | 00 | 1 | 0 |
Bolded in the listing above are the three relevant addresses.
A few instructions after the 0xFE00 register read (which also clears the POR, power on reset, flag) there’s a BEQ (branch if equal) at D8F0 that is based on whether the POR flag previously read is set or not.
When it’s set the CPU will jump to a memory clearing routine. However as the listing and sim shows it does not branch and continues on to attempt a read of address 0x0258 which returns UU, isim speak for uninitialised data.
The simulator trace below shows why this is occuring. When reading the 0xFE00 register the ULA clears the POR flag on the next cycle, but since the CPU is operating at 1MHz rather than 16MHz, it’s several more cycles before the CPU actually receives the read data by which point the register has been cleared to 0x0 rather than the expected 0x02 value (POR flag set). You can just about make out the 0x02 transition to 0x0 near the left of the red circled region.

This flag shouldn’t be cleared until the next 1MHz CPU cycle. As it happens I was considering this issue during initial development as the following TODO comment indicated.
-- Register access
if (i_addr(15 downto 8) = x"FE") then
if (i_n_w = '1') then
if (i_addr(3 downto 0) = x"0") then
-- TODO: Will this be read without a one clock reset delay?
isr_status(ISR_POWER_ON_RESET) <= '0';
elsif (i_addr(3 downto 0) = x"4") then
isr_status(ISR_RX_FULL) <= '0';
end if;
...
I guess the answer to that was, no it will not and just one ULA clock delay would also not be sufficient, it needs to delay the clear until the next CPU clock.

With a delayed clear in place, 0x02 is returned and used in the AND and EOR operations resulting in 00 in the accumulator, Z flag set and 0 being pushed to the stack. The BEQ now branches 9 addresses forward to D8FB which is the memory clearing logic and memory does indeed appear to be clearing in the simulator.
Memory clearing seemed a little odd as not every location in RAM was zero’d out. After stepping through the routine, the following instructions were executed:
Addr | Data | R/nW | Rom En | Instruction | Bytes | Cycles | Comments |
---|---|---|---|---|---|---|---|
D902 | 91 | 1 | 1 | ||||
D902 | 91 | 1 | 1 | STA indirect Y | 2 | 6 | |
D903 | 00 | 1 | 1 | ||||
0000 | 00 | 1 | 0 | ||||
0001 | 13 | 1 | 0 | ||||
13FF | 00 | 0 | 0 | ||||
D904 | C8 | 1 | 1 | Increment Y | 1 | 2 | |
D905 | D0 | 1 | 1 | BNE… | 2 | 3-4+2 | |
D906 | FB | 1 | 1 | ||||
D907 | C8 | 1 | 1 | Increment Y | 1 | 2 | |
D908 | E6 | 1 | 1 | Zero Page Inc | 2 | 5 | Increment memory at 01 |
D909 | 01 | 1 | 1 | ||||
0001 | 13 | 1 | 0 | Reads 13, Writes 13 then 14 into 0001 | |||
13 | 0 | 0 | |||||
14 | 0 | 0 | |||||
D90A | 10 | 1 | 1 | BPL | 2 | 3-4/2 | Branch on Plus |
D90B | f6 | 1 | 1 | ||||
D902 | 91 | 1 | 1 | STA indirect Y | 2 | 6 | LOOPED |
It looks like the memory skipping is deliberate. Memory clearing starts 0x0400 bytes in and skips one byte every 256, zeroing out the rest of RAM between 0x0400 and 0x8000. Why, I’m not sure.
A small victory and confirmation some parts are starting to work, but more bugs remain.
Use the source
With more bugs to solve and tired of working out each opcode from the iSim trace I decided to DDG to see if anyone had disassembled the acorn firmware and found a link to this partially commented dissassembly
Between the simulator and the above disassembly I was able to step through and resolve the remaining issues with the implementation. A few conflicts between the CPU and ULA ram during read/writes and assorted other minor issues.
In the end, the screen finally displayed a green fill, signifying that the POR flag had been correctly cleared and the CPU had been able to execute far enough to request the ULA switch to video mode 6.
The first real sign of progress and indication that this just might work.