From c1caf8c6b4b4ab4bfee2049813ea222b3e3ff963 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Sun, 14 Oct 2012 17:56:19 -0700 Subject: [PATCH] Timing and UI enhancements. This change introduces simulated baud rates in the ACIA. Baud rate is controlled just as in the real 6551, by writing to the ACIA's control register. Baud rates between 50 and 19,200 baud are selectable. A baud rate of 0 has special meaning, and turns off all simulated baud rate delays (on a real 6551, this means to use an external clock instead of the internal baud rate generator) A busy-wait loop between steps in the simulator control program has also been added. This uses the high-resolution 'System.nanoTime()' call to wait a specific number of nanoseconds before continuing. Symon now waits at least 1uS between clock cycles, trying to approach a real 1MHz 6502 in performance. It is far from perfect, but it's better than it was. Also refactored the status panel to use a BoxLayout, and DRY-up some of the code. --- samples/echo.asm | 4 +- samples/echo.prg | Bin 36 -> 36 bytes samples/hello.asm | 23 ++- samples/hello.prg | Bin 34 -> 52 bytes src/main/java/com/loomcom/symon/Cpu.java | 15 +- .../java/com/loomcom/symon/Simulator.java | 36 ++++- .../java/com/loomcom/symon/devices/Acia.java | 146 ++++++++++++++++-- .../com/loomcom/symon/ui/StatusPanel.java | 103 ++++++------ src/test/java/com/loomcom/symon/AciaTest.java | 73 +++++---- 9 files changed, 272 insertions(+), 128 deletions(-) diff --git a/samples/echo.asm b/samples/echo.asm index 759e27a..3ab97e0 100644 --- a/samples/echo.asm +++ b/samples/echo.asm @@ -13,8 +13,8 @@ start: cli lda #$09 sta iocmd ; Set command status - lda #$16 - sta ioctrl ; 0 stop bits, 8 bit word, 300 baud + lda #$1a + sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud ;; Load a character from the keyboard and store it into ;; the accumulator diff --git a/samples/echo.prg b/samples/echo.prg index e0190cdbb7b206ae1a2a001acc33328e99775692..8ed8ad58bccaa8de667897d1a3d0c2fb1ce71be8 100644 GIT binary patch delta 14 VcmY#U;fPqt*~@fbrPM?Y82}x31R?+c delta 14 VcmY#U;fPqt*~@fbrPxFc82}w;1RekY diff --git a/samples/hello.asm b/samples/hello.asm index a1738cc..4916ba7 100644 --- a/samples/hello.asm +++ b/samples/hello.asm @@ -13,20 +13,19 @@ start: cli lda #$09 sta iocmd ; Set command status - lda #$16 - sta ioctrl ; 0 stop bits, 8 bit word, 300 baud - ldx #$00 ; Initialize index + lda #$1a + sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud -loop: lda string,x - beq start ; If A is 0, loop back and start again +init: ldx #$00 ; Initialize index -write: lda iostatus - and #$10 ; Load ACIA status. Is output buffer empty? - beq write ; If not, loop back and try again, - lda string,x - sta iobase ; Otherwise, write to output. +loop: lda iostatus + and #$10 ; Is the tx register empty? + beq loop ; No, wait for it to empty + lda string,x ; Otherwise, load the string pointer + beq init ; If the char is 0, re-init + sta iobase ; Otherwise, transmit - inx - jmp loop ; Repeat. + inx ; Increment string pointer. + jmp loop ; Repeat write. string: .byte "Hello, 6502 world! ", 0 diff --git a/samples/hello.prg b/samples/hello.prg index a4f561a135a7f2f9d38a3e4f18e8f1757f6faede..dc26662d6c068e4786d0f25ae10863acf927d356 100644 GIT binary patch literal 52 zcma!8$=S 0) { + // This is a pretty rough approximation based on 8 bits per character, + // and 1/baudRate per bit. + return (long)((1.0 / baudRate) * 1000000000 * 8); + } else { + return 0; + } + } + + /** + * @return The simulated baud rate in bps. + */ + public int getBaudRate() { + return baudRate; + } + + /** + * Set the baud rate of the simulated ACIA. + * + * @param rate The baud rate in bps. 0 means no simulated baud rate delay. + */ + public void setBaudRate(int rate) { + this.baudRate = rate; + } + + /** + * @return The contents of the status register. + */ + public int statusReg() { + // TODO: Overrun, Parity Error, Framing Error, DTR, DSR, and Interrupt flags. + int stat = 0; + if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) { + stat |= 0x08; + } + if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { + stat |= 0x10; + } + return stat; + } + @Override public String toString() { return "ACIA@" + String.format("%04X", baseAddress); } public synchronized int rxRead() { + lastRxRead = System.nanoTime(); rxFull = false; return rxChar; } @@ -103,12 +226,13 @@ public class Acia extends Device { } public synchronized int txRead() { - txFull = false; + txEmpty = true; return txChar; } public synchronized void txWrite(int data) { - txFull = true; + lastTxWrite = System.nanoTime(); + txEmpty = false; txChar = data; } @@ -116,7 +240,7 @@ public class Acia extends Device { * @return true if there is character data in the TX register. */ public boolean hasTxChar() { - return txFull; + return !txEmpty; } /** @@ -128,7 +252,7 @@ public class Acia extends Device { private synchronized void reset() { txChar = 0; - txFull = false; + txEmpty = true; rxChar = 0; rxFull = false; } diff --git a/src/main/java/com/loomcom/symon/ui/StatusPanel.java b/src/main/java/com/loomcom/symon/ui/StatusPanel.java index e28bd95..b0defe7 100644 --- a/src/main/java/com/loomcom/symon/ui/StatusPanel.java +++ b/src/main/java/com/loomcom/symon/ui/StatusPanel.java @@ -6,7 +6,7 @@ package com.loomcom.symon.ui; import com.loomcom.symon.Cpu; import javax.swing.*; -import javax.swing.border.Border; +import javax.swing.border.*; import java.awt.*; public class StatusPanel extends JPanel { @@ -36,6 +36,7 @@ public class StatusPanel extends JPanel { private JTextField opcodeField; private JTextField pcField; + private JTextField spField; private JTextField aField; private JTextField xField; private JTextField yField; @@ -43,26 +44,29 @@ public class StatusPanel extends JPanel { private final JLabel opcodeLabel; private final JLabel pcLabel; + private final JLabel spLabel; private final JLabel aLabel; private final JLabel xLabel; private final JLabel yLabel; // private final JLabel stepCountLabel; - private static final int WIDTH = 140; - private static final int HEIGHT = 200; + private static final int EMPTY_BORDER = 5; + private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 4, 0, 0); + private static final Font LABEL_FONT = new Font("sansserif", Font.PLAIN, 11); public StatusPanel() { super(); - Dimension dimensions = new Dimension(WIDTH, HEIGHT); + Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER, + EMPTY_BORDER, EMPTY_BORDER); + Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED); - setMinimumSize(dimensions); - setPreferredSize(dimensions); - setMaximumSize(dimensions); + setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder)); - setLayout(new GridLayout(13,1)); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); JPanel statusFlagsPanel = new JPanel(); + statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT); carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png")); carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png")); @@ -88,50 +92,6 @@ public class StatusPanel extends JPanel { overflowFlagLabel = new JLabel(overflowOff, JLabel.CENTER); negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER); - Border emptyBorder = BorderFactory.createEmptyBorder(8, 6, 0, 6); - - // Create and add register and address labels - opcodeLabel = new JLabel("Instruction"); - opcodeLabel.setBorder(emptyBorder); - pcLabel = new JLabel("Program Counter"); - pcLabel.setBorder(emptyBorder); - aLabel = new JLabel("Accumulator"); - aLabel.setBorder(emptyBorder); - xLabel = new JLabel("X Register"); - xLabel.setBorder(emptyBorder); - yLabel = new JLabel("Y Register"); - yLabel.setBorder(emptyBorder); -// stepCountLabel = new JLabel("Steps"); -// stepCountLabel.setBorder(emptyBorder); - - opcodeField = new JTextField(""); - pcField = new JTextField(""); - aField = new JTextField(""); - xField = new JTextField(""); - yField = new JTextField(""); -// stepCountField = new JTextField(""); - - opcodeField.setEditable(false); - pcField.setEditable(false); - aField.setEditable(false); - xField.setEditable(false); - yField.setEditable(false); -// stepCountField.setEditable(false); - - opcodeLabel.setMinimumSize(new Dimension(100, 20)); - pcLabel.setMinimumSize(new Dimension(80, 20)); - aLabel.setMinimumSize(new Dimension(60, 20)); - xLabel.setMinimumSize(new Dimension(60, 20)); - yLabel.setMinimumSize(new Dimension(60, 20)); -// stepCountLabel.setMinimumSize(new Dimension(120, 20)); - - opcodeLabel.setPreferredSize(new Dimension(100, 20)); - pcLabel.setPreferredSize(new Dimension(80, 20)); - aLabel.setPreferredSize(new Dimension(60, 20)); - xLabel.setPreferredSize(new Dimension(60, 20)); - yLabel.setPreferredSize(new Dimension(60, 20)); -// stepCountLabel.setPreferredSize(new Dimension(120, 20)); - statusFlagsPanel.add(negativeFlagLabel); statusFlagsPanel.add(overflowFlagLabel); statusFlagsPanel.add(breakFlagLabel); @@ -140,11 +100,30 @@ public class StatusPanel extends JPanel { statusFlagsPanel.add(zeroFlagLabel); statusFlagsPanel.add(carryFlagLabel); + // Create and add register and address labels + opcodeLabel = makeLabel("Instruction"); + pcLabel = makeLabel("Program Counter"); + spLabel = makeLabel("Stack Pointer"); + aLabel = makeLabel("Accumulator"); + xLabel = makeLabel("X Register"); + yLabel = makeLabel("Y Register"); +// stepCountLabel = new JLabel("Steps"); + + opcodeField = makeTextField(); + pcField = makeTextField(); + spField = makeTextField(); + aField = makeTextField(); + xField = makeTextField(); + yField = makeTextField(); +// stepCountField = new JTextField(""); + add(statusFlagsPanel); add(opcodeLabel); add(opcodeField); add(pcLabel); add(pcField); + add(spLabel); + add(spField); add(aLabel); add(aField); add(xLabel); @@ -153,8 +132,6 @@ public class StatusPanel extends JPanel { add(yField); // add(stepCountLabel); // add(stepCountField); - - setBorder(BorderFactory.createBevelBorder(3)); } /** @@ -177,7 +154,8 @@ public class StatusPanel extends JPanel { // Update the register and address displays opcodeField.setText(cpu.getOpcodeStatus()); pcField.setText(cpu.getProgramCounterStatus()); - aField.setText(cpu.getARegisterStatus()); + spField.setText(cpu.getStackPointerStatus()); + aField.setText(cpu.getAccumulatorStatus()); xField.setText(cpu.getXRegisterStatus()); yField.setText(cpu.getYRegisterStatus()); // stepCountField.setText(Long.toString(cpu.getStepCounter())); @@ -242,4 +220,19 @@ public class StatusPanel extends JPanel { return imageIcon; } + private JLabel makeLabel(String text) { + JLabel label = new JLabel(text); + label.setAlignmentX(LEFT_ALIGNMENT); + label.setBorder(LABEL_BORDER); + label.setFont(LABEL_FONT); + return label; + } + + private JTextField makeTextField() { + JTextField textField = new JTextField(""); + textField.setAlignmentX(LEFT_ALIGNMENT); + textField.setEditable(false); + return textField; + } + } diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java index 72b6425..2319ca5 100644 --- a/src/test/java/com/loomcom/symon/AciaTest.java +++ b/src/test/java/com/loomcom/symon/AciaTest.java @@ -8,55 +8,54 @@ import com.loomcom.symon.exceptions.FifoUnderrunException; import static org.junit.Assert.*; public class AciaTest { + @Test + public void newAciaShouldHaveTxEmptyStatus() throws Exception { + Acia acia = new Acia(0x000); - @Test - public void newAciaShouldHaveTxEmptyStatus() throws Exception { - Acia acia = new Acia(0x000); + assertEquals(0x10, acia.read(0x0001)); + } - assertEquals(0x10, acia.read(0x0001)); - } + @Test + public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception { + Acia acia = new Acia(0x000); - @Test - public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception { - Acia acia = new Acia(0x000); + acia.txWrite('a'); + assertEquals(0x00, acia.read(0x0001)); + } - acia.txWrite('a'); - assertEquals(0x00, acia.read(0x0001)); - } + @Test + public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception { + Acia acia = new Acia(0x000); - @Test - public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception { - Acia acia = new Acia(0x000); + acia.rxWrite('a'); + assertEquals(0x18, acia.read(0x0001)); + } - acia.rxWrite('a'); - assertEquals(0x18, acia.read(0x0001)); - } + @Test + public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData() + throws Exception { + Acia acia = new Acia(0x000); - @Test - public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData() - throws Exception { - Acia acia = new Acia(0x000); + acia.rxWrite('a'); + acia.txWrite('b'); - acia.rxWrite('a'); - acia.txWrite('b'); + assertEquals(0x08, acia.read(0x0001)); + } - assertEquals(0x08, acia.read(0x0001)); - } + @Test + public void readingBuffersShouldResetStatus() + throws Exception { + Acia acia = new Acia(0x0000); - @Test - public void readingBuffersShouldResetStatus() - throws Exception { - Acia acia = new Acia(0x0000); + acia.rxWrite('a'); + acia.txWrite('b'); - acia.rxWrite('a'); - acia.txWrite('b'); - assertEquals(0x08, acia.read(0x0001)); + assertEquals(0x08, acia.read(0x0001)); - assertEquals('a', acia.rxRead()); - assertEquals('b', acia.txRead()); - - assertEquals(0x10, acia.read(0x0001)); - } + assertEquals('a', acia.rxRead()); + assertEquals('b', acia.txRead()); + assertEquals(0x10, acia.read(0x0001)); + } }