From 1bc8eda38dc6188b80422e5b2c061859d1360855 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Sun, 26 Jan 2014 01:25:12 -0800 Subject: [PATCH] ACIA interrupt handling --- samples/README | 10 ++- samples/echo_irq.rom | Bin 0 -> 16384 bytes samples/echo_irq/.gitignore | 4 + samples/echo_irq/Makefile | 13 +++ samples/echo_irq/echo_irq.asm | 48 +++++++++++ samples/echo_irq/symon.config | 16 ++++ samples/{echo.asm => echo_poll.asm} | 0 samples/{echo.prg => echo_poll.prg} | Bin src/main/java/com/loomcom/symon/Cpu.java | 18 ++-- .../java/com/loomcom/symon/devices/Acia.java | 38 +++++++-- src/test/java/com/loomcom/symon/AciaTest.java | 78 +++++++++++++++++- src/test/java/com/loomcom/symon/CpuTest.java | 44 ++++++++++ 12 files changed, 244 insertions(+), 25 deletions(-) create mode 100644 samples/echo_irq.rom create mode 100644 samples/echo_irq/.gitignore create mode 100644 samples/echo_irq/Makefile create mode 100644 samples/echo_irq/echo_irq.asm create mode 100644 samples/echo_irq/symon.config rename samples/{echo.asm => echo_poll.asm} (100%) rename samples/{echo.prg => echo_poll.prg} (100%) diff --git a/samples/README b/samples/README index 733079d..6681b8c 100644 --- a/samples/README +++ b/samples/README @@ -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. diff --git a/samples/echo_irq.rom b/samples/echo_irq.rom new file mode 100644 index 0000000000000000000000000000000000000000..7a47f657f3d6170f243f07544a9453472f289546 GIT binary patch literal 16384 zcmeIup%H^X5CFg@C?+NXMF(_&j7R|~A*G!8LY^6Vc#WbIYy<;U;C4&cZ|U+Q%8=VA z=e%ym=UJB&@1=?Hwf}!P#gs5$z<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r v3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#rZW`!os(rlx<&YBh literal 0 HcmV?d00001 diff --git a/samples/echo_irq/.gitignore b/samples/echo_irq/.gitignore new file mode 100644 index 0000000..f7e9b90 --- /dev/null +++ b/samples/echo_irq/.gitignore @@ -0,0 +1,4 @@ +*.rom +*.lst +*.map +*.o diff --git a/samples/echo_irq/Makefile b/samples/echo_irq/Makefile new file mode 100644 index 0000000..2b8302e --- /dev/null +++ b/samples/echo_irq/Makefile @@ -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 diff --git a/samples/echo_irq/echo_irq.asm b/samples/echo_irq/echo_irq.asm new file mode 100644 index 0000000..e25af6e --- /dev/null +++ b/samples/echo_irq/echo_irq.asm @@ -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 diff --git a/samples/echo_irq/symon.config b/samples/echo_irq/symon.config new file mode 100644 index 0000000..6d5ae20 --- /dev/null +++ b/samples/echo_irq/symon.config @@ -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; +} + diff --git a/samples/echo.asm b/samples/echo_poll.asm similarity index 100% rename from samples/echo.asm rename to samples/echo_poll.asm diff --git a/samples/echo.prg b/samples/echo_poll.prg similarity index 100% rename from samples/echo.prg rename to samples/echo_poll.prg diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index a5d72d6..de6d705 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -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(); diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java index 95e624a..8606c0c 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia.java +++ b/src/main/java/com/loomcom/symon/devices/Acia.java @@ -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; } } diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java index 2319ca5..bcb4784 100644 --- a/src/test/java/com/loomcom/symon/AciaTest.java +++ b/src/test/java/com/loomcom/symon/AciaTest.java @@ -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); diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index 1332adf..a44e92d 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -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);