1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-01 08:41:32 +00:00

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.
This commit is contained in:
Seth Morabito 2012-10-14 17:56:19 -07:00
parent 6c0ff5a5b2
commit c1caf8c6b4
9 changed files with 272 additions and 128 deletions

View File

@ -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

Binary file not shown.

View File

@ -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

Binary file not shown.

View File

@ -1260,7 +1260,7 @@ public class Cpu implements InstructionTable {
return String.format("$%04X", addr);
}
public String getARegisterStatus() {
public String getAccumulatorStatus() {
return String.format("$%02X", a);
}
@ -1276,6 +1276,10 @@ public class Cpu implements InstructionTable {
return String.format("$%04X", pc);
}
public String getStackPointerStatus() {
return String.format("$%02X", sp);
}
/**
* Returns a string representing the CPU state.
*/
@ -1284,10 +1288,11 @@ public class Cpu implements InstructionTable {
StringBuffer sb = new StringBuffer(String.format("$%04X", addr) +
" ");
sb.append(String.format("%-14s", opcode));
sb.append("A=" + String.format("$%02X", a) + " ");
sb.append("X=" + String.format("$%02X", x) + " ");
sb.append("Y=" + String.format("$%02X", y) + " ");
sb.append("PC=" + String.format("$%04X", pc) + " ");
sb.append("A=" + getAccumulatorStatus() + " ");
sb.append("X=" + getXRegisterStatus() + " ");
sb.append("Y=" + getYRegisterStatus() + " ");
sb.append("PC=" + getProgramCounterStatus() + " ");
sb.append("SP=" + getStackPointerStatus() + " ");
sb.append("P=" + getProcessorStatusString());
return sb.toString();
}

View File

@ -33,12 +33,21 @@ public class Simulator implements ActionListener, Observer {
private static final int ROM_BASE = 0xe000;
private static final int ROM_SIZE = 0x2000; // 8 KB
public static final int ACIA_BASE = 0xc000;
private static final int ACIA_BASE = 0xc000;
// The delay in microseconds between steps.
private static final int DELAY_BETWEEN_STEPS_NS = 1000;
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
// to refresh the view on every simualted clock cycle. Instead, we will only refresh the view after this
// to refresh the view on every simulated clock cycle. Instead, we will only refresh the view after this
// number of steps when running normally.
private static final int MAX_STEPS_BETWEEN_UPDATES = 15000;
//
// Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 10000
// should give us a status update every 10 ms.
//
// TODO: Work around the event dispatch thread with custom painting code instead of relying on Swing.
//
private static final int MAX_STEPS_BETWEEN_UPDATES = 10000;
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
@ -131,13 +140,16 @@ public class Simulator implements ActionListener, Observer {
buttonContainer.add(stepButton);
buttonContainer.add(resetButton);
// Left side - console and buttons
// Left side - console
controlsContainer.add(console, BorderLayout.PAGE_START);
controlsContainer.add(buttonContainer, BorderLayout.PAGE_END);
mainWindow.getContentPane().add(controlsContainer, BorderLayout.LINE_START);
// Right side - status pane
mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END);
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(this);
stepButton.addActionListener(this);
resetButton.addActionListener(this);
@ -325,6 +337,8 @@ public class Simulator implements ActionListener, Observer {
*/
private void step() throws MemoryAccessException {
delayLoop();
cpu.step();
// Read from the ACIA and immediately update the console if there's
@ -419,6 +433,16 @@ public class Simulator implements ActionListener, Observer {
}
}
/*
* Perform a busy-loop for DELAY_BETWEEN_STEPS_NS nanoseconds
*/
private void delayLoop() {
long startTime = System.nanoTime();
long stopTime = startTime + DELAY_BETWEEN_STEPS_NS;
// Busy loop
while (System.nanoTime() < stopTime) { ; }
}
/**
* The main run thread.
*/

View File

@ -2,6 +2,9 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
@ -34,14 +37,19 @@ public class Acia extends Device {
private boolean overrun = false;
private long lastTxWrite = 0;
private long lastRxRead = 0;
private int baudRate = 0;
private long baudRateDelay = 0;
/**
* Read/Write buffers
*/
private int rxChar = 0;
private int txChar = 0;
private boolean rxFull = false;
private boolean txFull = false;
private boolean rxFull = false;
private boolean txEmpty = true;
public Acia(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA");
@ -54,10 +62,7 @@ public class Acia extends Device {
case DATA_REG:
return rxRead();
case STAT_REG:
// TODO: Overrun, Parity Error, Framing Error,
// DTR, DSR, and Interrupt flags.
return ((rxFull ? 0x08 : 0x00) |
(txFull ? 0x00 : 0x10));
return statusReg();
case CMND_REG:
return commandRegister;
case CTRL_REG:
@ -80,19 +85,137 @@ public class Acia extends Device {
commandRegister = data;
break;
case 3:
controlRegister = data;
setControlRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
/**
* Set the control register and associated state.
*
* @param data
*/
public void setControlRegister(int data) {
this.controlRegister = data;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
baudRate = 0;
break;
case 1:
baudRate = 50;
break;
case 2:
baudRate = 75;
break;
case 3:
baudRate = 110; // Real rate is actually 109.92
break;
case 4:
baudRate = 135; // Real rate is actually 134.58
break;
case 5:
baudRate = 150;
break;
case 6:
baudRate = 300;
break;
case 7:
baudRate = 600;
break;
case 8:
baudRate = 1200;
break;
case 9:
baudRate = 1800;
break;
case 10:
baudRate = 2400;
break;
case 11:
baudRate = 3600;
break;
case 12:
baudRate = 4800;
break;
case 13:
baudRate = 7200;
break;
case 14:
baudRate = 9600;
break;
case 15:
baudRate = 19200;
break;
}
// Recalculate the baud rate delay.
baudRateDelay = calculateBaudRateDelay();
}
}
/*
* Calculate the delay in nanoseconds between successive read/write operations, based on the
* configured baud rate.
*/
private long calculateBaudRateDelay() {
if (baudRate > 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;
}

View File

@ -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;
}
}

View File

@ -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));
}
}