A project log for Novasaur CP/M TTL Retrocomputer

Retrocomputer built from TTL logic running CP/M with no CPU or ALU

Alastair HewittAlastair Hewitt 06/04/2020 at 21:060 Comments

The 8080 development has been extended to support most of the additional features found in the 8085. This includes four of the five hardware interrupts. This is possible because the lower nibble of the Ei register is available via the expansion header. A buffer controlled by the ~EOE signal can be used to read the interrupts as shown.

Software Interrupt

The 8080 provides eight software interrupts via the RST 0 through RST 7 instructions. These will jump to the vector n * 8, where n is the number after the RST. The virtual CPU executes these interrupts using the code rst.nsa followed by restart.nsa. The first single-cycle RST instruction will increment the program counter, load the temp register with the lower 8-bits of the reset vector, and then rewrite the instruction to RESTART. The two-cycle restart instruction will push the program counter on to the stack, clear the upper program counter byte, and then copy the reset vector from the temp register to the lower program counter byte. The instruction at the new program counter address is then fetched and the instruction cache updated.

The original Micro-Soft Altair BASIC 3.2 used the RST instructions to call common subroutines. This was primarily to reduce program size since this is a single byte instruction rather than the typical 3-byte CALL which must specify the 16-bit subroutine address.

Hardware Interrupt

The 8080 provides one hardware interrupt via the INTR input. This is enabled via the EI instruction and disabled via the DI instruction. The 8085 adds an additional four interrupts: RST5.5, RST6.5, RST7.5, and TRAP. The TRAP interrupt is non-maskable and used to initiate things like a power shutdown. A client of the hardware abstraction layer (HAL) would not have this level of privilege, so this interrupt is not implemented.

The first three interrupts act like the RST instructions, except they are offset by an additional 4 bytes, so RST5.5 is the vector 5.5 * 8. The INTR interrupt will read the value of the I0 register and use this as a vector into the zero page. This could be set according to some external state to control the program flow.

An interrupt can only take effect during the fetch cycle, but the current fetch cycle does not have enough cycles to handle the interrupt mask. The solution is to treat the interrupts as another feature of the HAL and control them via the feature flag IMODE.

The IMODE flag is set at the beginning of the virtual process cycle if the interrupts are enabled. The fetch cycle checks the IMODE flag and if set will fork from the standard fetch operation. In this case the IMODE flag is reset and the INTR1 instruction is written to the instruction cache. This instruction is executed in the next machine cycle and applies a mask to the Ei register to see if any of the unmasked interrupts are set. If an interrupt is set then the associated vector is written to the temp register and the RESTART instruction is rewritten to the cache. If the interrupts are clear then the fetch cycle is completed and next instruction written to the cache.

This way the interrupts are checked once per virtual process cycle at the expense of one virtual machine cycle. Most applications would not use the interrupts and the IMODE flag would not be set. In this case there would be no impact on the virtual CPU performance.


The SIM instruction sets an interrupt mask based on the contents of the accumulator. The lower 3 bits (0-2) will mask the RST5.5RST6.5, and RST7.5 interrupts respectively (when set high). Bit 3 represents the interrupt enable state, so will have the same effect as calling DI if set low and EI if set high. There is no additional mask for INTR, so it is always active when the interrupts are enabled.

The RIM instruction reads the current interrupt mask and stores it in the lower nibble of the accumulator. Meanwhile the current value of the interrupts is stored in the upper nibble. This provides access to the state of the interrupt lines without having to enable the interrupts.

There is a hidden feature where the upper nibble of the accumulator will mask the rest of the Ei register as if it were more interrupt lines. This is where the serial inputs are located and in effect these lines could be used to trigger additional interrupts. Typically the upper nibble of the mask would be set to 0000, but setting any of the bits to 1 will unmask that line and cause an interrupt when the respective serial input is high. Note: this mask state is the opposite of the standard mask bit. These interrupts map to RST1.5RST2.5RST3.5, and RST4.5.

The interrupts are prioritized according to the number, where RST7.5 is the highest. If more than one interrupt line is set, then the highest priority will take effect. The INTR interrupt is the lowest priority, coming after RST1.5.


It's also worth noting the operation of the HLT instruction here. This effectively stops the CPU and waits until there's an interrupt before continuing execution. The halt instruction will increment the program counter, but then rewrite the INTR2 instruction to the cache. This is almost identical to INTR1 described above but does not fetch the next instruction if the interrupts are clear. In this case the INTR2 instruction is returned and the cycle repeats, leaving the CPU in an infinite loop until an interrupt is detected.

This way the interrupts are checked once per virtual machine cycle in the halted state. Execution will resume from the instruction after the HLT when the interrupt service routine ends with the RET instruction.