Support for breakpoints

- Adds a new window that allows adding and deleting breakpoints.
  Will halt the simulator when a breakpoint is reached.
This commit is contained in:
Seth Morabito committed 2015-12-31 16:09:50 -08:00
1 parent 69e1985ec1
commit df88c54f90
5 files changed
+258 -46

No files matched your search

-7
View File
@@ -911,13 +911,6 @@ public class Cpu implements InstructionTable {
this.clockPeriodInNs = clockPeriodInNs; this.clockPeriodInNs = clockPeriodInNs;
} }
/**
* @return The simulated clock period, in nanoseconds
*/
public long getClockPeriodInNs() {
return clockPeriodInNs;
}
/** /**
* Return the current Cpu State. * Return the current Cpu State.
* *
+2 -2
View File
@@ -97,8 +97,8 @@ public class Main {
}); });
Simulator.MAIN_CMD cmd = simulator.waitForCommand(); Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MAIN_CMD.SELECTMACHINE)) { if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
machineClass = null; machineClass = null;
} else { } else {
break; break;
+74 -33
View File
@@ -24,10 +24,7 @@
package com.loomcom.symon; package com.loomcom.symon;
import com.loomcom.symon.devices.Memory; import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.FifoUnderrunException; import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.machines.Machine; import com.loomcom.symon.machines.Machine;
import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.*;
import com.loomcom.symon.ui.Console; import com.loomcom.symon.ui.Console;
@@ -39,6 +36,8 @@ import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.io.*; import java.io.*;
import java.util.SortedSet;
import java.util.TreeSet;
/** /**
* Symon Simulator Interface and Control. * Symon Simulator Interface and Control.
@@ -53,9 +52,9 @@ public class Simulator {
private final static Logger logger = LoggerFactory.getLogger(Simulator.class.getName()); private final static Logger logger = LoggerFactory.getLogger(Simulator.class.getName());
// UI constants // UI constants
private static final int DEFAULT_FONT_SIZE = 12; private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE); private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final int CONSOLE_BORDER_WIDTH = 10; private static final int CONSOLE_BORDER_WIDTH = 10;
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz. // Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
private static final long[] CLOCK_PERIODS = {0, 1000, 500, 333, 250, 200, 167, 143, 125}; private static final long[] CLOCK_PERIODS = {0, 1000, 500, 333, 250, 200, 167, 143, 125};
@@ -105,38 +104,48 @@ public class Simulator {
private final VideoWindow videoWindow; private final VideoWindow videoWindow;
private final BreakpointsWindow breakpointsWindow;
private SimulatorMenu menuBar; private SimulatorMenu menuBar;
private RunLoop runLoop; private RunLoop runLoop;
private Console console; private Console console;
private StatusPanel statusPane; private StatusPanel statusPane;
private JButton runStopButton; private JButton runStopButton;
private JButton stepButton; private JButton stepButton;
private JComboBox<String> stepCountBox; private JComboBox<String> stepCountBox;
private JFileChooser fileChooser; private JFileChooser fileChooser;
private PreferencesDialog preferences; private PreferencesDialog preferences;
private SortedSet<Integer> breakpoints;
private final Object commandMonitorObject = new Object(); private final Object commandMonitorObject = new Object();
private MAIN_CMD command = MAIN_CMD.NONE;
public enum MAIN_CMD { private MainCommand command = MainCommand.NONE;
public enum MainCommand {
NONE, NONE,
SELECTMACHINE SELECTMACHINE
} }
/** /**
* The list of step counts that will appear in the "Step" drop-down. * The list of step counts that will appear in the "Step" drop-down.
*/ */
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception { public Simulator(Class machineClass) throws Exception {
this.breakpoints = new TreeSet<>();
this.machine = (Machine) machineClass.getConstructors()[0].newInstance(); this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
// Initialize final fields in the constructor. // Initialize final fields in the constructor.
this.traceLog = new TraceLog(); this.traceLog = new TraceLog();
this.memoryWindow = new MemoryWindow(machine.getBus()); this.memoryWindow = new MemoryWindow(machine.getBus());
if(machine.getCrtc() != null) { this.breakpointsWindow = new BreakpointsWindow(breakpoints, mainWindow);
if (machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2); videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
} else { } else {
videoWindow = null; videoWindow = null;
@@ -235,9 +244,9 @@ public class Simulator {
console.requestFocus(); console.requestFocus();
handleReset(false); handleReset(false);
} }
public MAIN_CMD waitForCommand() { public MainCommand waitForCommand() {
synchronized(commandMonitorObject) { synchronized (commandMonitorObject) {
try { try {
commandMonitorObject.wait(); commandMonitorObject.wait();
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
@@ -246,7 +255,7 @@ public class Simulator {
} }
return command; return command;
} }
private void handleStart() { private void handleStart() {
// Shift focus to the console. // Shift focus to the console.
@@ -274,7 +283,7 @@ public class Simulator {
} }
try { try {
logger.info("Reset requested. Resetting CPU."); logger.debug("Reset requested. Resetting CPU.");
// Reset CPU // Reset CPU
machine.getCpu().reset(); machine.getCpu().reset();
// Clear the console. // Clear the console.
@@ -349,7 +358,6 @@ public class Simulator {
updateVisibleState(); updateVisibleState();
stepsSinceLastUpdate = 0; stepsSinceLastUpdate = 0;
} }
} }
/** /**
@@ -389,7 +397,7 @@ public class Simulator {
} }
public void run() { public void run() {
logger.info("Starting main run loop."); logger.debug("Starting main run loop.");
isRunning = true; isRunning = true;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@@ -426,12 +434,12 @@ public class Simulator {
} }
/** /**
* Returns true if the run loop should proceed to the next step.
*
* @return True if the run loop should proceed to the next step. * @return True if the run loop should proceed to the next step.
*/ */
private boolean shouldContinue() { private boolean shouldContinue() {
return isRunning && !(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00); return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
} }
} }
@@ -506,7 +514,7 @@ public class Simulator {
if (fileSize != machine.getRomSize()) { if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes."); throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
} }
// Load the new ROM image // Load the new ROM image
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile); Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
machine.setRom(rom); machine.setRom(rom);
@@ -551,7 +559,7 @@ public class Simulator {
preferences.getDialog().setVisible(true); preferences.getDialog().setVisible(true);
} }
} }
class SelectMachineAction extends AbstractAction { class SelectMachineAction extends AbstractAction {
public SelectMachineAction() { public SelectMachineAction() {
super("Switch emulated machine...", null); super("Switch emulated machine...", null);
@@ -560,19 +568,19 @@ public class Simulator {
} }
public void actionPerformed(ActionEvent actionEvent) { public void actionPerformed(ActionEvent actionEvent) {
if(runLoop != null) { if (runLoop != null) {
runLoop.requestStop(); runLoop.requestStop();
} }
memoryWindow.dispose(); memoryWindow.dispose();
traceLog.dispose(); traceLog.dispose();
if(videoWindow != null) { if (videoWindow != null) {
videoWindow.dispose(); videoWindow.dispose();
} }
mainWindow.dispose(); mainWindow.dispose();
command = MAIN_CMD.SELECTMACHINE; command = MainCommand.SELECTMACHINE;
synchronized(commandMonitorObject) { synchronized (commandMonitorObject) {
commandMonitorObject.notifyAll(); commandMonitorObject.notifyAll();
} }
} }
@@ -682,6 +690,23 @@ public class Simulator {
} }
} }
class ToggleBreakpointWindowAction extends AbstractAction {
public ToggleBreakpointWindowAction() {
super("Breakpoints...", null);
putValue(SHORT_DESCRIPTION, "Show or Hide Breakpoints");
}
public void actionPerformed(ActionEvent actionEvent) {
synchronized (breakpointsWindow) {
if (breakpointsWindow.isVisible()) {
breakpointsWindow.setVisible(false);
} else {
breakpointsWindow.setVisible(true);
}
}
}
}
class SimulatorMenu extends JMenuBar { class SimulatorMenu extends JMenuBar {
// Menu Items // Menu Items
private JMenuItem loadProgramItem; private JMenuItem loadProgramItem;
@@ -779,7 +804,7 @@ public class Simulator {
}); });
viewMenu.add(showMemoryTable); viewMenu.add(showMemoryTable);
if(videoWindow != null) { if (videoWindow != null) {
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction()); final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() { videoWindow.addWindowListener(new WindowAdapter() {
@Override @Override
@@ -795,7 +820,15 @@ public class Simulator {
/* /*
* Simulator Menu * Simulator Menu
*/ */
JMenu simulatorMenu = new JMenu("Simulator"); JMenu simulatorMenu = new JMenu("Simulator");
// "Select Machine..." item.
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
simulatorMenu.add(selectMachineItem);
// "Clock Speed" sub-menu
JMenu speedSubMenu = new JMenu("Clock Speed"); JMenu speedSubMenu = new JMenu("Clock Speed");
ButtonGroup speedGroup = new ButtonGroup(); ButtonGroup speedGroup = new ButtonGroup();
@@ -806,8 +839,16 @@ public class Simulator {
simulatorMenu.add(speedSubMenu); simulatorMenu.add(speedSubMenu);
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction()); // "Breakpoints"
simulatorMenu.add(selectMachineItem); final JCheckBoxMenuItem showBreakpoints = new JCheckBoxMenuItem(new ToggleBreakpointWindowAction());
// Un-check the menu item if the user closes the window directly
breakpointsWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showBreakpoints.setSelected(false);
}
});
simulatorMenu.add(showBreakpoints);
add(simulatorMenu); add(simulatorMenu);
} }
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.ui;
import com.loomcom.symon.util.HexUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.util.ArrayList;
import java.util.SortedSet;
/**
* Simple window to enter breakpoints.
*/
public class BreakpointsWindow extends JFrame {
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
private static final String EMPTY_STRING = "";
private JFrame mainWindow;
private SortedSet<Integer> breakpoints;
/**
* Simple ListModel to back the list of breakpoints.
*/
private class BreakpointsListModel extends AbstractListModel<String> {
private SortedSet<Integer> breakpoints;
public BreakpointsListModel(SortedSet<Integer> breakpoints) {
this.breakpoints = breakpoints;
}
@Override
public int getSize() {
return breakpoints.size();
}
@Override
public String getElementAt(int index) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
return "$" + HexUtil.wordToHex(values.get(index));
}
public void addElement(Integer breakpoint) {
breakpoints.add(breakpoint);
ArrayList<Integer> values = new ArrayList<>(breakpoints);
int index = values.indexOf(breakpoint);
fireIntervalAdded(this, index, index);
}
public void removeElement(int index) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
Integer breakpoint = values.get(index);
breakpoints.remove(breakpoint);
fireIntervalRemoved(this, index, index);
}
}
public BreakpointsWindow(SortedSet<Integer> breakpoints,
JFrame mainWindow) {
this.breakpoints = breakpoints;
this.mainWindow = mainWindow;
createUi();
}
private void createUi() {
setTitle("Breakpoints");
JPanel breakpointsPanel = new JPanel();
JPanel controlPanel = new JPanel();
breakpointsPanel.setLayout(new BorderLayout());
breakpointsPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
JButton addButton = new JButton("Add");
JButton removeButton = new JButton("Del");
removeButton.setEnabled(false);
JTextField addTextField = new JTextField(5);
BreakpointsListModel listModel = new BreakpointsListModel(breakpoints);
JList<String> breakpointsList = new JList<>(listModel);
breakpointsList.setFont(new Font("Monospace", Font.PLAIN, 14));
breakpointsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(breakpointsList);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
breakpointsList.addListSelectionListener(le -> {
int idx = breakpointsList.getSelectedIndex();
if (idx == -1) {
removeButton.setEnabled(false);
} else {
removeButton.setEnabled(true);
}
});
addButton.addActionListener(e -> {
int value = -1;
String newBreakpoint = addTextField.getText();
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = Integer.parseInt(addTextField.getText(), 16);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
listModel.addElement(value);
logger.debug("Added breakpoint ${}", HexUtil.wordToHex(value));
addTextField.setText(EMPTY_STRING);
});
removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex()));
controlPanel.add(addTextField);
controlPanel.add(addButton);
controlPanel.add(removeButton);
setLayout(new BorderLayout());
getContentPane().add(breakpointsPanel, BorderLayout.CENTER);
getContentPane().add(controlPanel, BorderLayout.SOUTH);
setMinimumSize(FRAME_SIZE);
setMaximumSize(FRAME_SIZE);
setPreferredSize(FRAME_SIZE);
setLocationRelativeTo(mainWindow);
setResizable(false);
pack();
}
}
@@ -26,6 +26,8 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Bus; import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil; import com.loomcom.symon.util.HexUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
@@ -37,8 +39,7 @@ import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.EventObject; import java.util.EventObject;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* This Frame displays the contents of a page of memory. The page number to be displayed * This Frame displays the contents of a page of memory. The page number to be displayed
@@ -46,6 +47,8 @@ import java.util.logging.Logger;
*/ */
public class MemoryWindow extends JFrame implements ActionListener { public class MemoryWindow extends JFrame implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
private MemoryTableModel memoryTableModel; private MemoryTableModel memoryTableModel;
private JTable memoryTable; private JTable memoryTable;
private JTextField pageNumberTextField; private JTextField pageNumberTextField;
@@ -209,9 +212,9 @@ public class MemoryWindow extends JFrame implements ActionListener {
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
// An invalid number was entered. Log the error, but otherwise // An invalid number was entered. Log the error, but otherwise
// take no action. // take no action.
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " + logger.warn("Can't parse page number {}", pageNumberInput);
pageNumberInput);
} }
updateControls(); updateControls();
} }
} }