ACIA interrupt handling

This commit is contained in:
Seth Morabito 2014-01-26 01:25:12 -08:00
parent 76dcbd712a
commit 1bc8eda38d
12 changed files with 244 additions and 25 deletions

View File

@ -6,18 +6,22 @@ Sample Programs
When loaded at address $0300, this program will print "Hello, 6502 World!" in
infinite loop.
2. echo.prg
2. echo_poll.prg
When loaded at address $0300, this program will echo back to the console
anything typed.
Both hello.prg and echo.prg were assembled with the Ophis assembler:
Both hello.prg and echo_poll.prg were assembled with the Ophis assembler:
https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
3. echo_irq.rom
3. ehbasic.rom
This is another echo program, and behaves identically to echo_poll.prg,
except it is interrupt-driven.
4. ehbasic.rom
This is Lee Davison's Enhanced 6502 BASIC.

BIN
samples/echo_irq.rom Normal file

Binary file not shown.

4
samples/echo_irq/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o

13
samples/echo_irq/Makefile Normal file
View File

@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: echo_irq
echo_irq: echo_irq.o
$(LD) -C symon.config -vm -m echo_irq.map -o echo_irq.rom echo_irq.o
echo_irq.o:
$(CA) --listing -o echo_irq.o echo_irq.asm
clean:
rm -f *.o *.rom *.map *.lst

View File

@ -0,0 +1,48 @@
;;
;; Read input from the keyboard, and echo to console.
;;
IOBASE = $8800
IOSTATUS = IOBASE + 1
IOCMD = IOBASE + 2
IOCTRL = IOBASE + 3
.segment "CODE"
.org $C000
START: CLI
LDA #$09
STA IOCMD ; Set command status
LDA #$1A
STA IOCTRL ; 0 stop bits, 8 bit word, 2400 baud
;;
;; Infinite idle loop, waiting for interrupt.
;;
IDLE: LDA #$FF
TAX
IDLE1: DEX
BNE IDLE1
JMP IDLE
;;
;; IRQ handler
;;
IRQ: LDA IOBASE ; Get the character in the ACIA.
PHA ; Save accumulator
ECHO1: LDA IOSTATUS ; Read the ACIA status
AND #$10 ; Is the tx register empty?
BEQ ECHO1 ; No, wait for it to empty
PLA ; Otherwise, load saved accumulator,
STA IOBASE ; write to output,
RTI ; and return
; system vectors
.segment "VECTORS"
.org $FFFA
.word IRQ ; NMI vector
.word START ; RESET vector
.word IRQ ; IRQ vector

View File

@ -0,0 +1,16 @@
MEMORY {
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3FFA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
SEGMENTS {
CODE: load = ROM1, type = ro;
DATA: load = ROM1, type = ro;
VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
}

View File

@ -165,7 +165,7 @@ public class Cpu implements InstructionTable {
if (state.nmiAsserted) {
handleNmi();
} else if (state.irqAsserted && !getIrqDisableFlag()) {
handleIrq();
handleIrq(state.pc);
}
// Fetch memory location for this instruction.
@ -259,7 +259,7 @@ public class Cpu implements InstructionTable {
/** Single Byte Instructions; Implied and Relative **/
case 0x00: // BRK - Force Interrupt - Implied
if (!getIrqDisableFlag()) {
handleIrq();
handleIrq(state.pc + 1);
}
break;
case 0x08: // PHP - Push Processor Status - Implied
@ -732,15 +732,13 @@ public class Cpu implements InstructionTable {
delayLoop(state.ir);
}
private void handleIrq() throws MemoryAccessException {
handleInterrupt(IRQ_VECTOR_L, IRQ_VECTOR_H);
private void handleIrq(int returnPc) throws MemoryAccessException {
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H);
clearIrq();
}
private void handleNmi() throws MemoryAccessException {
handleInterrupt(NMI_VECTOR_L, NMI_VECTOR_H);
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H);
clearNmi();
}
@ -749,12 +747,12 @@ public class Cpu implements InstructionTable {
*
* @throws MemoryAccessException
*/
private void handleInterrupt(int vectorLow, int vectorHigh) throws MemoryAccessException {
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh) throws MemoryAccessException {
// Set the break flag before pushing.
setBreakFlag();
// Push program counter + 1 onto the stack
stackPush((state.pc + 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc + 1 & 0xff); // PC low byte
stackPush((returnPc >> 8) & 0xff); // PC high byte
stackPush(returnPc & 0xff); // PC low byte
stackPush(state.getStatusFlag());
// Set the Interrupt Disabled flag. RTI will clear it.
setIrqDisableFlag();

View File

@ -23,10 +23,8 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
@ -58,6 +56,8 @@ public class Acia extends Device {
private int commandRegister;
private int controlRegister;
private boolean receiveIrqEnabled = false;
private boolean transmitIrqEnabled = false;
private boolean overrun = false;
private long lastTxWrite = 0;
@ -105,7 +105,7 @@ public class Acia extends Device {
reset();
break;
case 2:
commandRegister = data;
setCommandRegister(data);
break;
case 3:
setControlRegister(data);
@ -115,13 +115,23 @@ public class Acia extends Device {
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bit 1 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x02) == 0;
// Bits 2 & 3 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0;
}
/**
* Set the control register and associated state.
*
* @param data
*/
public void setControlRegister(int data) {
this.controlRegister = data;
private void setControlRegister(int data) {
controlRegister = data;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
@ -245,18 +255,28 @@ public class Acia extends Device {
public synchronized void rxWrite(int data) {
rxFull = true;
if (receiveIrqEnabled) {
getBus().assertIrq();
}
rxChar = data;
}
public synchronized int txRead() {
txEmpty = true;
if (transmitIrqEnabled) {
getBus().assertIrq();
}
return txChar;
}
public synchronized void txWrite(int data) {
lastTxWrite = System.nanoTime();
txEmpty = false;
txChar = data;
txEmpty = false;
}
/**
@ -278,6 +298,8 @@ public class Acia extends Device {
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
}
}

View File

@ -1,13 +1,83 @@
package com.loomcom.symon;
import org.junit.*;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest {
@Test
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(2, 0x00);
acia.rxWrite('a');
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
acia.rxWrite('a');
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(2, 0x06);
// Write data
acia.write(0, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
// Write data
acia.write(0, 'a');
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, never()).assertIrq();
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia(0x000);

View File

@ -418,6 +418,50 @@ public class CpuTest extends TestCase {
assertEquals(0x02, cpu.getAccumulator());
}
public void testIrqPushesCorrectReturnAddressOntoStack() throws Exception {
// Ensure the IRQs are enabled
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x10);
bus.write(0xfffe, 0x00);
// Create an IRQ handler at 0x1000 that just RTIs.
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xea, 0xea, 0x40); // NOP, NOP, RTI
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200 with three instruction sizes.
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$01
0x6d, 0x06, 0x02, // ADC $0207
0x00, // BRK
0x03); // $03 (data @ $0206)
cpu.step(); // CLC
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0201
assertEquals(0x0201, cpu.getProgramCounter());
cpu.step(); // LDA $#01
assertEquals(0x0203, cpu.getProgramCounter());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0203
assertEquals(0x0203, cpu.getProgramCounter());
}
public void testNmi() throws Exception {
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);