package mars.tools;
import mars.util.Binary;
import mars.venus.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import mars.Globals;
import mars.venus.RunSpeedPanel;
import mars.mips.hardware.*;
import mars.simulator.Exceptions;
import javax.swing.text.DefaultCaret;
/*
Copyright (c) 2003-2014, Pete Sanderson and Kenneth Vollmar
Developed by Pete Sanderson (psanderson@otterbein.edu)
and Kenneth Vollmar (kenvollmar@missouristate.edu)
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.
(MIT license, http://www.opensource.org/licenses/mit-license.html)
*/
/**
* Keyboard and Display Simulator. It can be run either as a stand-alone Java application having
* access to the mars package, or through MARS as an item in its Tools menu. It makes
* maximum use of methods inherited from its abstract superclass AbstractMarsToolAndApplication.
* Pete Sanderson
* Version 1.0, 24 July 2008.
* Version 1.1, 24 November 2008 corrects two omissions: (1) the tool failed to register as an observer
* of kernel text memory when counting instruction executions for transmitter ready bit
* reset delay, and (2) the tool failed to test the Status register's Exception Level bit before
* raising the exception that results in the interrupt (if the Exception Level bit is 1, that
* means an interrupt is being processed, so disable further interrupts).
*
* Version 1.2, August 2009, soft-codes the MMIO register locations for new memory configuration
* feature of MARS 3.7. Previously memory segment addresses were fixed and final. Now they
* can be modified dynamically so the tool has to get its values dynamically as well.
*
* Version 1.3, August 2011, corrects bug to enable Display window to scroll when needed.
*
* Version 1.4, August 2014, adds two features: (1) ASCII control character 12 (form feed) when
* transmitted will clear the Display window. (2) ASCII control character 7 (bell) when
* transmitted with properly coded (X,Y) values will reposition the cursor to the specified
* position of a virtual text-based terminal. X represents column, Y represents row.
*/
public class KeyboardAndDisplaySimulator extends AbstractMarsToolAndApplication {
private static String version = "Version 1.4";
private static String heading = "Keyboard and Display MMIO Simulator";
private static String displayPanelTitle, keyboardPanelTitle;
private static char VT_FILL = ' '; // fill character for virtual terminal (random access mode)
public static Dimension preferredTextAreaDimension = new Dimension(400,200);
private static Insets textAreaInsets = new Insets(4,4,4,4);
// Time delay to process Transmitter Data is simulated by counting instruction executions.
// After this many executions, the Transmitter Controller Ready bit set to 1.
private final TransmitterDelayTechnique[] delayTechniques = {
new FixedLengthDelay(),
new UniformlyDistributedDelay(),
new NormallyDistributedDelay()
};
public static int RECEIVER_CONTROL; // keyboard Ready in low-order bit
public static int RECEIVER_DATA; // keyboard character in low-order byte
public static int TRANSMITTER_CONTROL; // display Ready in low-order bit
public static int TRANSMITTER_DATA; // display character in low-order byte
// These are used to track instruction counts to simulate driver delay of Transmitter Data
private boolean countingInstructions;
private int instructionCount;
private int transmitDelayInstructionCountLimit;
private int currentDelayInstructionLimit;
// Should the transmitted character be displayed before the transmitter delay period?
// If not, hold onto it and print at the end of delay period.
private int intWithCharacterToDisplay;
private boolean displayAfterDelay = true;
// Whether or not display position is sequential (JTextArea append)
// or random access (row, column). Supports new random access feature. DPS 17-July-2014
private boolean displayRandomAccessMode = false;
private int rows, columns;
private DisplayResizeAdapter updateDisplayBorder;
private KeyboardAndDisplaySimulator simulator;
// Major GUI components
private JPanel keyboardAndDisplay;
private JScrollPane displayScrollPane;
private JTextArea display;
private JPanel displayPanel, displayOptions;
private JComboBox delayTechniqueChooser;
private DelayLengthPanel delayLengthPanel;
private JSlider delayLengthSlider;
private JCheckBox displayAfterDelayCheckBox;
private JPanel keyboardPanel;
private JScrollPane keyAccepterScrollPane;
private JTextArea keyEventAccepter;
private JButton fontButton;
private Font defaultFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
/**
* Simple constructor, likely used to run a stand-alone keyboard/display simulator.
* @param title String containing title for title bar
* @param heading String containing text for heading shown in upper part of window.
*/
public KeyboardAndDisplaySimulator(String title, String heading) {
super(title,heading);
simulator = this;
}
/**
* Simple constructor, likely used by the MARS Tools menu mechanism
*/
public KeyboardAndDisplaySimulator() {
super(heading + ", " + version, heading);
simulator = this;
}
/**
* Main provided for pure stand-alone use. Recommended stand-alone use is to write a
* driver program that instantiates a KeyboardAndDisplaySimulator object then invokes its go() method.
* "stand-alone" means it is not invoked from the MARS Tools menu. "Pure" means there
* is no driver program to invoke the application.
*/
public static void main(String[] args) {
new KeyboardAndDisplaySimulator(heading+" stand-alone, "+version,heading).go();
}
/**
* Required MarsTool method to return Tool name.
* @return Tool name. MARS will display this in menu item.
*/
public String getName() {
return heading;
}
// Set the MMIO addresses. Prior to MARS 3.7 these were final because
// MIPS address space was final as well. Now we will get MMIO base address
// each time to reflect possible change in memory configuration. DPS 6-Aug-09
protected void initializePreGUI() {
RECEIVER_CONTROL = Memory.memoryMapBaseAddress; //0xffff0000; // keyboard Ready in low-order bit
RECEIVER_DATA = Memory.memoryMapBaseAddress + 4; //0xffff0004; // keyboard character in low-order byte
TRANSMITTER_CONTROL = Memory.memoryMapBaseAddress + 8; //0xffff0008; // display Ready in low-order bit
TRANSMITTER_DATA = Memory.memoryMapBaseAddress + 12; //0xffff000c; // display character in low-order byte
displayPanelTitle = "DISPLAY: Store to Transmitter Data "+Binary.intToHexString(TRANSMITTER_DATA);
keyboardPanelTitle = "KEYBOARD: Characters typed here are stored to Receiver Data "+Binary.intToHexString(RECEIVER_DATA);
}
/**
* Override the inherited method, which registers us as an Observer over the static data segment
* (starting address 0x10010000) only.
*
* When user enters keystroke, set RECEIVER_CONTROL and RECEIVER_DATA using the action listener.
* When user loads word (lw) from RECEIVER_DATA (we are notified of the read), then clear RECEIVER_CONTROL.
* When user stores word (sw) to TRANSMITTER_DATA (we are notified of the write), then clear TRANSMITTER_CONTROL, read TRANSMITTER_DATA,
* echo the character to display, wait for delay period, then set TRANSMITTER_CONTROL.
*
* If you use the inherited GUI buttons, this method is invoked when you click "Connect" button on MarsTool or the
* "Assemble and Run" button on a Mars-based app.
*/
protected void addAsObserver() {
// Set transmitter Control ready bit to 1, means we're ready to accept display character.
updateMMIOControl(TRANSMITTER_CONTROL, readyBitSet(TRANSMITTER_CONTROL));
// We want to be an observer only of MIPS reads from RECEIVER_DATA and writes to TRANSMITTER_DATA.
// Use the Globals.memory.addObserver() methods instead of inherited method to achieve this.
addAsObserver(RECEIVER_DATA,RECEIVER_DATA);
addAsObserver(TRANSMITTER_DATA, TRANSMITTER_DATA);
// We want to be notified of each instruction execution, because instruction count is the
// basis for delay in re-setting (literally) the TRANSMITTER_CONTROL register. SPIM does
// this too. This simulates the time required for the display unit to process the
// TRANSMITTER_DATA.
addAsObserver(Memory.textBaseAddress, Memory.textLimitAddress);
addAsObserver(Memory.kernelTextBaseAddress, Memory.kernelTextLimitAddress);
}
/**
* Method that constructs the main display area. It is organized vertically
* into two major components: the display and the keyboard. The display itself
* is a JTextArea and it echoes characters placed into the low order byte of
* the Transmitter Data location, 0xffff000c. They keyboard is also a JTextArea
* places each typed character into the Receive Data location 0xffff0004.
* @return the GUI component containing these two areas
*/
protected JComponent buildMainDisplayArea() {
// Changed arrangement of the display and keyboard panels from GridLayout(2,1)
// to BorderLayout to hold a JSplitPane containing both panels. This permits user
// to apportion the relative sizes of the display and keyboard panels within
// the overall frame. Will be convenient for use with the new random-access
// display positioning feature. Previously, both the display and the keyboard
// text areas were equal in size and there was no way for the user to change that.
// DPS 17-July-2014
keyboardAndDisplay = new JPanel(new BorderLayout());
JSplitPane both = new JSplitPane(JSplitPane.VERTICAL_SPLIT, buildDisplay(),buildKeyboard());
both.setResizeWeight(0.5);
keyboardAndDisplay.add(both);
return keyboardAndDisplay;
}
//////////////////////////////////////////////////////////////////////////////////////
// Rest of the protected methods. These all override do-nothing methods inherited from
// the abstract superclass.
//////////////////////////////////////////////////////////////////////////////////////
/**
* Update display when connected MIPS program accesses (data) memory.
* @param memory the attached memory
* @param accessNotice information provided by memory in MemoryAccessNotice object
*/
protected void processMIPSUpdate(Observable memory, AccessNotice accessNotice) {
MemoryAccessNotice notice = (MemoryAccessNotice) accessNotice;
// If MIPS program has just read (loaded) the receiver (keyboard) data register,
// then clear the Ready bit to indicate there is no longer a keystroke available.
// If Ready bit was initially clear, they'll get the old keystroke -- serves 'em right
// for not checking!
if (notice.getAddress()==RECEIVER_DATA && notice.getAccessType()==AccessNotice.READ) {
updateMMIOControl(RECEIVER_CONTROL, readyBitCleared(RECEIVER_CONTROL));
}
// MIPS program has just written (stored) the transmitter (display) data register. If transmitter
// Ready bit is clear, device is not ready yet so ignore this event -- serves 'em right for not checking!
// If transmitter Ready bit is set, then clear it to indicate the display device is processing the character.
// Also start an intruction counter that will simulate the delay of the slower
// display device processing the character.
if (isReadyBitSet(TRANSMITTER_CONTROL) && notice.getAddress()==TRANSMITTER_DATA && notice.getAccessType()==AccessNotice.WRITE) {
updateMMIOControl(TRANSMITTER_CONTROL, readyBitCleared(TRANSMITTER_CONTROL));
intWithCharacterToDisplay = notice.getValue();
if (!displayAfterDelay) displayCharacter(intWithCharacterToDisplay);
this.countingInstructions = true;
this.instructionCount = 0;
this.transmitDelayInstructionCountLimit = generateDelay();
}
// We have been notified of a MIPS instruction execution.
// If we are in transmit delay period, increment instruction count and if limit
// has been reached, set the transmitter Ready flag to indicate the MIPS program
// can write another character to the transmitter data register. If the Interrupt-Enabled
// bit had been set by the MIPS program, generate an interrupt!
if ( this.countingInstructions &&
notice.getAccessType()==AccessNotice.READ &&
(Memory.inTextSegment(notice.getAddress()) || Memory.inKernelTextSegment(notice.getAddress()))) {
this.instructionCount++;
if (this.instructionCount >= this.transmitDelayInstructionCountLimit) {
if (displayAfterDelay) displayCharacter(intWithCharacterToDisplay);
this.countingInstructions = false;
int updatedTransmitterControl = readyBitSet(TRANSMITTER_CONTROL);
updateMMIOControl(TRANSMITTER_CONTROL, updatedTransmitterControl);
if (updatedTransmitterControl != 1
&& (Coprocessor0.getValue(Coprocessor0.STATUS) & 2)==0 // Added by Carl Hauser Nov 2008
&& (Coprocessor0.getValue(Coprocessor0.STATUS) & 1)==1) {
// interrupt-enabled bit is set in both Tranmitter Control and in
// Coprocessor0 Status register, and Interrupt Level Bit is 0, so trigger external interrupt.
mars.simulator.Simulator.externalInterruptingDevice = Exceptions.EXTERNAL_INTERRUPT_DISPLAY;
}
}
}
}
private static final char CLEAR_SCREEN = 12; // ASCII Form Feed
private static final char SET_CURSOR_X_Y = 7; // ASCII Bell (ding ding!)
// Method to display the character stored in the low-order byte of
// the parameter. We also recognize two non-printing characters:
// Decimal 12 (Ascii Form Feed) to clear the display
// Decimal 7 (Ascii Bell) to place the cursor at a specified (X,Y) position.
// of a virtual text terminal. The position is specified in the high
// order 24 bits of the transmitter word (X in 20-31, Y in 8-19).
// Thus the parameter is the entire word, not just the low-order byte.
// Once the latter is performed, the display mode changes to random
// access, which has repercussions for the implementation of character display.
private void displayCharacter(int intWithCharacterToDisplay) {
char characterToDisplay = (char) (intWithCharacterToDisplay & 0x000000FF);
if (characterToDisplay == CLEAR_SCREEN) {
initializeDisplay(displayRandomAccessMode);
}
else if (characterToDisplay == SET_CURSOR_X_Y) {
// First call will activate random access mode.
// We're using JTextArea, where caret has to be within text.
// So initialize text to all spaces to fill the JTextArea to its
// current capacity. Then set caret. Subsequent character
// displays will replace, not append, in the text.
if (!displayRandomAccessMode) {
displayRandomAccessMode = true;
initializeDisplay(displayRandomAccessMode);
}
// For SET_CURSOR_X_Y, we need data from the rest of the word.
// High order 3 bytes are split in half to store (X,Y) value.
// High 12 bits contain X value, next 12 bits contain Y value.
int x = (intWithCharacterToDisplay & 0xFFF00000) >>> 20;
int y = (intWithCharacterToDisplay & 0x000FFF00) >>> 8;
// If X or Y values are outside current range, set to range limit.
if (x<0) x=0;
if (x>=columns) x=columns-1;
if (y<0) y=0;
if (y>=rows) y=rows-1;
// display is a JTextArea whose character positioning in the text is linear.
// Converting (row,column) to linear position requires knowing how many columns
// are in each row. I add one because each row except the last ends with '\n' that
// does not count as a column but occupies a position in the text string.
// The values of rows and columns is set in initializeDisplay().
display.setCaretPosition( y*(columns+1) + x);
}
else {
if (displayRandomAccessMode) {
try {
int caretPosition = display.getCaretPosition();
// if caret is positioned at the end of a line (at the '\n'), skip over the '\n'
if ( (caretPosition+1) % (columns+1) == 0) {
caretPosition++;
display.setCaretPosition(caretPosition);
}
display.replaceRange(""+characterToDisplay, caretPosition, caretPosition+1);
}
catch (IllegalArgumentException e) {
// tried to write off the end of the defined grid.
display.setCaretPosition(display.getCaretPosition()-1);
display.replaceRange(""+characterToDisplay,display.getCaretPosition(),display.getCaretPosition()+1);
}
}
else {
display.append(""+characterToDisplay);
}
}
}
/**
* Initialization code to be executed after the GUI is configured. Overrides inherited default.
*/
protected void initializePostGUI() {
initializeTransmitDelaySimulator();
keyEventAccepter.requestFocusInWindow();
}
/**
* Method to reset counters and display when the Reset button selected.
* Overrides inherited method that does nothing.
*/
protected void reset() {
displayRandomAccessMode = false;
initializeTransmitDelaySimulator();
initializeDisplay(displayRandomAccessMode);
keyEventAccepter.setText("");
((TitledBorder)displayPanel.getBorder()).setTitle(displayPanelTitle);
displayPanel.repaint();
keyEventAccepter.requestFocusInWindow();
updateMMIOControl(TRANSMITTER_CONTROL, readyBitSet(TRANSMITTER_CONTROL));
}
// The display JTextArea (top half) is initialized either to the empty
// string, or to a string filled with lines of spaces. It will do the
// latter only if the MIPS program has sent the BELL character (Ascii 7) to
// the transmitter. This sets the caret (cursor) to a specific (x,y) position
// on a text-based virtual display. The lines of spaces is necessary because
// the caret can only be placed at a position within the current text string.
private void initializeDisplay(boolean randomAccess) {
String initialText = "";
if (randomAccess) {
Dimension textDimensions = getDisplayPanelTextDimensions();
columns = (int) textDimensions.getWidth();
rows = (int) textDimensions.getHeight();
repaintDisplayPanelBorder();
char[] charArray = new char[columns];
Arrays.fill(charArray, VT_FILL);
String row = new String(charArray);
StringBuffer str = new StringBuffer(row);
for (int i=1; i