Close

Interrupts now work

A project log for TMS9900 compatible CPU core in VHDL

Retro challenge 2017/04 project to create a TMS9900 compatible CPU core. Again in a month... Failure could be an option...

erik-piehlErik Piehl 05/16/2017 at 20:590 Comments

One more feature done! Now the CPU core supports interrupts, leaving the major missing features to MPY and DIV instructions - and a whole bunch of debugging.

The CPU core now has 3 more I/O signals:

int_req : in STD_LOGIC;         -- interrupt request, active high
ic03     : in STD_LOGIC_VECTOR(3 downto 0);     -- interrupt priority for the request, 0001 is the highest (0000 is reset)
int_ack : out STD_LOGIC;

Of these on both my simulation framework and the actual FPGA implementation I set ic03 bit vector to "0001" so all interrupts occur at the highest level. This is how interrupts are hardwired on TI-99/4A.

int_ack is a signal that does not exist on the real TMS9900 CPU. In my case it is set to high while the CPU fetches the new workspace pointer from the interrupt vector table. This would allow external hardware to see that the CPU is vectoring to an interrupt, and also which interrupt it is. Maybe the actual CPU does something similar, but this was simple so I did it... At least useful for simulation runs.

It is amazing that there is almost no feature where the first implementation version wouldn't have several bugs: in my case when the CPU vectors to an interrupt it needs to modify its internal interrupt level (stored in 4 LSBs of the status register). I managed to implement two bugs in there first: initially I latched the interrupt priority code too early, so that when the CPU exited the interrupt service routine, it actually remained at a higher interrupt priority level than before the interrupt, because I altered interrupt priority before the previous level was stored to memory (as part of status register). So a flag bit was needed to modify the status register's interrupt priority field only after storing the previous contents to memory as part of the interrupt context switch.

The second problem was harder to debug, since it only occurred with FPGA run and not on simulation. When the CPU vectors to an interrupt, it must also adjust the current priority level not to the level that external hardware is requesting the interrupt for, but to a level below, as to block the same interrupt from firing over and over again. The following code does the right thing in the processing of the do_blwp3 state (this is part of the chain of states the CPU execution state machine marches through when entering the interrupt):

if set_int_priority then
  st(3 downto 0) <= std_logic_vector(unsigned(ic03) - 1);
  set_int_priority <= False;
end if;
So a four bit decrementer is required to compute the new interrupt priority.

Having interrupts working both in simulation and on the FPGA allowed me to also properly implement the IDLE instruction. This instruction simply waits for an interrupt. I added a new state to the CPU, which waits for an interrupt that has the same or higher priority that CPU currently has.

On the TI-99/4A hardware that my FPGA implements the interrupt originate from my TMS9918 video processor core on every frame, then pass through my TMS9901 core which further can mask the interrupt, and finally it goes to the CPU core. Thus on the TI-99/4A clone the IDLE instruction (assuming the video processor and I/O controller are properly set up) becomes a wait for next video frame operation.

Discussions