mirror of
https://github.com/sethm/symon.git
synced 2024-06-09 01:29:30 +00:00
ACIA interrupt handling
This commit is contained in:
parent
76dcbd712a
commit
1bc8eda38d
|
@ -6,18 +6,22 @@ Sample Programs
|
||||||
When loaded at address $0300, this program will print "Hello, 6502 World!" in
|
When loaded at address $0300, this program will print "Hello, 6502 World!" in
|
||||||
infinite loop.
|
infinite loop.
|
||||||
|
|
||||||
2. echo.prg
|
2. echo_poll.prg
|
||||||
|
|
||||||
When loaded at address $0300, this program will echo back to the console
|
When loaded at address $0300, this program will echo back to the console
|
||||||
anything typed.
|
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/
|
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.
|
This is Lee Davison's Enhanced 6502 BASIC.
|
||||||
|
|
||||||
|
|
BIN
samples/echo_irq.rom
Normal file
BIN
samples/echo_irq.rom
Normal file
Binary file not shown.
4
samples/echo_irq/.gitignore
vendored
Normal file
4
samples/echo_irq/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.rom
|
||||||
|
*.lst
|
||||||
|
*.map
|
||||||
|
*.o
|
13
samples/echo_irq/Makefile
Normal file
13
samples/echo_irq/Makefile
Normal 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
|
48
samples/echo_irq/echo_irq.asm
Normal file
48
samples/echo_irq/echo_irq.asm
Normal 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
|
16
samples/echo_irq/symon.config
Normal file
16
samples/echo_irq/symon.config
Normal 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;
|
||||||
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class Cpu implements InstructionTable {
|
||||||
if (state.nmiAsserted) {
|
if (state.nmiAsserted) {
|
||||||
handleNmi();
|
handleNmi();
|
||||||
} else if (state.irqAsserted && !getIrqDisableFlag()) {
|
} else if (state.irqAsserted && !getIrqDisableFlag()) {
|
||||||
handleIrq();
|
handleIrq(state.pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch memory location for this instruction.
|
// Fetch memory location for this instruction.
|
||||||
|
@ -259,7 +259,7 @@ public class Cpu implements InstructionTable {
|
||||||
/** Single Byte Instructions; Implied and Relative **/
|
/** Single Byte Instructions; Implied and Relative **/
|
||||||
case 0x00: // BRK - Force Interrupt - Implied
|
case 0x00: // BRK - Force Interrupt - Implied
|
||||||
if (!getIrqDisableFlag()) {
|
if (!getIrqDisableFlag()) {
|
||||||
handleIrq();
|
handleIrq(state.pc + 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x08: // PHP - Push Processor Status - Implied
|
case 0x08: // PHP - Push Processor Status - Implied
|
||||||
|
@ -732,15 +732,13 @@ public class Cpu implements InstructionTable {
|
||||||
delayLoop(state.ir);
|
delayLoop(state.ir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIrq() throws MemoryAccessException {
|
private void handleIrq(int returnPc) throws MemoryAccessException {
|
||||||
handleInterrupt(IRQ_VECTOR_L, IRQ_VECTOR_H);
|
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H);
|
||||||
|
|
||||||
clearIrq();
|
clearIrq();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleNmi() throws MemoryAccessException {
|
private void handleNmi() throws MemoryAccessException {
|
||||||
handleInterrupt(NMI_VECTOR_L, NMI_VECTOR_H);
|
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H);
|
||||||
|
|
||||||
clearNmi();
|
clearNmi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,12 +747,12 @@ public class Cpu implements InstructionTable {
|
||||||
*
|
*
|
||||||
* @throws MemoryAccessException
|
* @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.
|
// Set the break flag before pushing.
|
||||||
setBreakFlag();
|
setBreakFlag();
|
||||||
// Push program counter + 1 onto the stack
|
// Push program counter + 1 onto the stack
|
||||||
stackPush((state.pc + 1 >> 8) & 0xff); // PC high byte
|
stackPush((returnPc >> 8) & 0xff); // PC high byte
|
||||||
stackPush(state.pc + 1 & 0xff); // PC low byte
|
stackPush(returnPc & 0xff); // PC low byte
|
||||||
stackPush(state.getStatusFlag());
|
stackPush(state.getStatusFlag());
|
||||||
// Set the Interrupt Disabled flag. RTI will clear it.
|
// Set the Interrupt Disabled flag. RTI will clear it.
|
||||||
setIrqDisableFlag();
|
setIrqDisableFlag();
|
||||||
|
|
|
@ -23,10 +23,8 @@
|
||||||
|
|
||||||
package com.loomcom.symon.devices;
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,6 +56,8 @@ public class Acia extends Device {
|
||||||
private int commandRegister;
|
private int commandRegister;
|
||||||
private int controlRegister;
|
private int controlRegister;
|
||||||
|
|
||||||
|
private boolean receiveIrqEnabled = false;
|
||||||
|
private boolean transmitIrqEnabled = false;
|
||||||
private boolean overrun = false;
|
private boolean overrun = false;
|
||||||
|
|
||||||
private long lastTxWrite = 0;
|
private long lastTxWrite = 0;
|
||||||
|
@ -105,7 +105,7 @@ public class Acia extends Device {
|
||||||
reset();
|
reset();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
commandRegister = data;
|
setCommandRegister(data);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
setControlRegister(data);
|
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.
|
* Set the control register and associated state.
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
public void setControlRegister(int data) {
|
private void setControlRegister(int data) {
|
||||||
this.controlRegister = data;
|
controlRegister = data;
|
||||||
|
|
||||||
// If the value of the data is 0, this is a request to reset,
|
// If the value of the data is 0, this is a request to reset,
|
||||||
// otherwise it's a control update.
|
// otherwise it's a control update.
|
||||||
|
@ -245,18 +255,28 @@ public class Acia extends Device {
|
||||||
|
|
||||||
public synchronized void rxWrite(int data) {
|
public synchronized void rxWrite(int data) {
|
||||||
rxFull = true;
|
rxFull = true;
|
||||||
|
|
||||||
|
if (receiveIrqEnabled) {
|
||||||
|
getBus().assertIrq();
|
||||||
|
}
|
||||||
|
|
||||||
rxChar = data;
|
rxChar = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int txRead() {
|
public synchronized int txRead() {
|
||||||
txEmpty = true;
|
txEmpty = true;
|
||||||
|
|
||||||
|
if (transmitIrqEnabled) {
|
||||||
|
getBus().assertIrq();
|
||||||
|
}
|
||||||
|
|
||||||
return txChar;
|
return txChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void txWrite(int data) {
|
public synchronized void txWrite(int data) {
|
||||||
lastTxWrite = System.nanoTime();
|
lastTxWrite = System.nanoTime();
|
||||||
txEmpty = false;
|
|
||||||
txChar = data;
|
txChar = data;
|
||||||
|
txEmpty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -278,6 +298,8 @@ public class Acia extends Device {
|
||||||
txEmpty = true;
|
txEmpty = true;
|
||||||
rxChar = 0;
|
rxChar = 0;
|
||||||
rxFull = false;
|
rxFull = false;
|
||||||
|
receiveIrqEnabled = false;
|
||||||
|
transmitIrqEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,83 @@
|
||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import org.junit.*;
|
|
||||||
|
|
||||||
import com.loomcom.symon.devices.Acia;
|
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 {
|
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
|
@Test
|
||||||
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
|
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
|
||||||
Acia acia = new Acia(0x000);
|
Acia acia = new Acia(0x000);
|
||||||
|
|
|
@ -418,6 +418,50 @@ public class CpuTest extends TestCase {
|
||||||
assertEquals(0x02, cpu.getAccumulator());
|
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 {
|
public void testNmi() throws Exception {
|
||||||
// Set the NMI vector to 0x1000
|
// Set the NMI vector to 0x1000
|
||||||
bus.write(0xfffb, 0x10);
|
bus.write(0xfffb, 0x10);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user