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

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.