Files
EMARS/src/main/java/mars/tools/BitmapDisplay.kt
T
2022-11-13 00:44:00 -05:00

610 lines
23 KiB
Kotlin

package mars.tools
import mars.Globals
import mars.detectRadix
import mars.mips.hardware.*
import mars.toHex
import mars.util.Binary
import java.awt.*
import java.awt.BorderLayout.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.util.*
import javax.swing.*
import javax.swing.border.EmptyBorder
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
/*
Copyright (c) 2010-2011, 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)
*/
/**
* Bitmapp 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, verison 1.0, 23 December 2010.
*/
class BitmapDisplay : AbstractMarsToolAndApplication
{
// Major GUI components
private lateinit var uiBaseAddressSelector: JComboBox<String>
private lateinit var uiKeyboardCheckbox: JCheckBox
private lateinit var canvas: JPanel
private lateinit var results: JPanel
// Some GUI settings
private val emptyBorder = EmptyBorder(4, 4, 4, 4)
private val backgroundColor = Color.WHITE
// Values for display canvas. Note their initialization uses the identifiers just above.
private var unitWidth: Int = 2
private var unitHeight: Int = 2
private var displayWidth: Int = 512
private var displayHeight: Int = 256
private val displayDimension get() = Dimension(displayWidth, displayHeight)
private val baseAddresses = intArrayOf(Memory.dataSegmentBaseAddress, Memory.globalPointer, Memory.dataBaseAddress,
Memory.heapBaseAddress, Memory.memoryMapBaseAddress)
private val baseAddress get() = baseAddresses[uiBaseAddressSelector.selectedIndex]
// Keyboard++
private var keyboardAddr: UInt = 0xFFFF0010u
private var oldKeyboardAddr: UInt = 0xFFFF0000u
private var oldKeyboardLastPressed: Char = ' '
private var keyboardAttached = false
private lateinit var uiKeyboard: JTextField
private lateinit var grid: Grid
val pooledKeyEvents = hashMapOf<UInt, ArrayList<KeyEvent>>(
0x00u to ArrayList(), 0x10u to ArrayList(), 0x20u to ArrayList()
)
val downKeys = HashSet<Int>()
/**
* Simple constructor, likely used to run a stand-alone bitmap display tool.
*
* @param title String containing title for title bar
* @param heading String containing text for heading shown in upper part of window.
*/
constructor(title: String, heading: String) : super(title, heading)
constructor() : super("Azalea's Bitmap Display++", HEADING)
override fun getName() = HEADING
/**
* Override the inherited method, which registers us as an Observer over the static data segment (starting address
* 0x10010000) only. This version will register us as observer over the the memory range as selected by the base
* address combo box and capacity of the visualization display (number of visualization elements times the number of
* memory words each one represents). It does so by calling the inherited 2-parameter overload of this method. 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.
*/
override fun addAsObserver()
{
var highAddress: Int = baseAddress + grid.rows * grid.cols * Memory.WORD_LENGTH_BYTES
// Special case: baseAddress<0 means we're in kernel memory (0x80000000 and up) and most likely
// in memory map address space (0xffff0000 and up). In this case, we need to make sure the high address
// does not drop off the high end of 32 bit address space. Highest allowable word address is 0xfffffffc,
// which is interpreted in Java int as -4.
if (baseAddress < 0 && highAddress > -4)
{
highAddress = -4
}
addAsObserver(baseAddress, highAddress)
addAsObserver(keyboardAddr.toInt(), (keyboardAddr + 0x20u).toInt())
}
/**
* Method that constructs the main display area. It is organized vertically into two major components: the display
* configuration which an be modified using combo boxes, and the visualization display which is updated as the
* attached MIPS program executes.
*
* @return the GUI component containing these two areas
*/
override fun buildMainDisplayArea(): JComponent
{
results = JPanel()
results.add(buildOrganizationArea())
results.add(buildVisualizationArea())
return results
}
//////////////////////////////////////////////////////////////////////////////////////
// Rest of the protected methods. These override do-nothing methods inherited from
// the abstract superclass.
//////////////////////////////////////////////////////////////////////////////////////
/**
* Update display when connected MIPS program accesses (data) memory.
*
* @param memory the attached memory
* @param ac information provided by memory in MemoryAccessNotice object
*/
override fun processMIPSUpdate(memory: Observable, ac: AccessNotice)
{
if (ac !is MemoryAccessNotice || ac.accessType != AccessNotice.WRITE) return
val addr = ac.address.toUInt()
// For the keyboard++
if (addr in keyboardAddr..keyboardAddr + 0x30u)
{
// Offset 0x01 or 0x11 or 0x21 bytes are for telling the keyboard that the events are received
var offset = addr - keyboardAddr
// Clear the segment
if (offset == 0x01u || offset == 0x11u)
{
if (Globals.memory.getByte(addr.toInt()) != 1) return
println("Offset written: ${offset.toHex(2)}")
// Clear bytes if 1 is written to the 0x01 offset
offset -= 1u
val range = offset..offset + 0x0Fu
synchronized(Globals.memoryAndRegistersLock)
{
range.map { keyboardAddr + it }.forEach { Globals.memory.setByte(it.toInt(), 0) }
}
pooledKeyEvents[offset]!!.clear()
println("[Keyboard++] Keyboard segment range $range cleared")
}
return
}
// For the display
val value = ac.value
val offset = ((addr - baseAddress.toUInt()) / 4u).toInt()
try
{
grid.setElement(offset / grid.cols, offset % grid.cols, value)
}
catch (e: IndexOutOfBoundsException)
{
// If address is out of range for display, do nothing.
}
if (offset == 0) canvas.repaint()
}
/**
* Event on key press
*
* @param offset Event type / offset
* @param e Event
*/
fun keyEvent(offset: UInt, e: KeyEvent)
{
if (!keyboardAttached) return
println("[Keyboard++] ${e.id} '${e.keyChar}' ${e.keyCode}")
val queue = pooledKeyEvents[offset]!!
// Old keyboard compatibility
synchronized(Globals.memoryAndRegistersLock)
{
if (e.id == KeyEvent.KEY_PRESSED)
{
oldKeyboardLastPressed = e.keyChar
Globals.memory.setWord(oldKeyboardAddr.toInt(), 1)
Globals.memory.setWord((oldKeyboardAddr + 4u).toInt(), e.keyChar.code)
}
if (e.id == KeyEvent.KEY_RELEASED && e.keyChar == oldKeyboardLastPressed)
{
oldKeyboardLastPressed = 0.toChar()
Globals.memory.setWord(oldKeyboardAddr.toInt(), 0)
Globals.memory.setWord((oldKeyboardAddr + 4u).toInt(), 0)
}
}
// Check for more than 7 key events queued
if (queue.size == 7) return
// Add to queue
queue.add(e)
// Add to memory
var addr = keyboardAddr + offset
println("[Keyboard++] Address ${addr.toHex(8)} set to ${queue.size} | ${addr.toHex(8)} set to ${e.keyCode} (${e.keyChar.toHex()})")
synchronized(Globals.memoryAndRegistersLock)
{
// Change 0x0: Number of events
Globals.memory.setByte(addr.toInt(), queue.size)
// Set the keycode
addr += queue.size.toUInt() * 2u
Globals.memory.setHalf(addr.toInt(), e.keyCode)
}
}
/**
* Initialize all JComboBox choice structures not already initialized at declaration. Overrides inherited method
* that does nothing.
*/
override fun initializePreGUI()
{
grid = Grid(0, 0)
}
/**
* The only post-GUI initialization is to create the initial Grid object based on the default settings of the
* various combo boxes. Overrides inherited method that does nothing.
*/
override fun initializePostGUI()
{
grid = createNewGrid()
}
/**
* Method to reset counters and display when the Reset button selected. Overrides inherited method that does
* nothing.
*/
override fun reset()
{
grid.reset()
canvas.repaint()
}
/**
* Overrides default method, to provide a Help button for this tool/app.
*/
override fun getHelpComponent(): JComponent
{
val helpContent = """
Use this program to simulate a basic bitmap display where
each memory word in a specified address space corresponds to
one display pixel in row-major order starting at the upper left
corner of the display. This tool may be run either from the
MARS Tools menu or as a stand-alone application.
You can easily learn to use this small program by playing with
it! Each rectangular unit on the display represents one memory
word in a contiguous address space starting with the specified
base address. The value stored in that word will be interpreted
as a 24-bit RGB color value with the red component in bits 16-23,
the green component in bits 8-15, and the blue component in bits 0-7.
Each time a memory word within the display address space is written
by the MIPS program, its position in the display will be rendered
in the color that its value represents.
Version 1.0 is very basic and was constructed from the Memory
Reference Visualization tool's code. Feel free to improve it and
send me your code for consideration in the next MARS release.
Contact Pete Sanderson at psanderson@otterbein.edu with
questions or comments.
""".trimIndent()
val help = JButton("Help")
help.addActionListener { JOptionPane.showMessageDialog(theWindow, helpContent) }
return help
}
//////////////////////////////////////////////////////////////////////////////////////
// Private methods defined to support the above.
//////////////////////////////////////////////////////////////////////////////////////
// UI components and layout for left half of GUI, where settings are specified.
private fun buildOrganizationArea(): JComponent
{
val uiUnitWidthSelector = JComboBox(arrayOf(1, 2, 4, 8, 16, 32)).apply {
isEditable = false
background = backgroundColor
selectedIndex = 1
toolTipText = "Width in pixels of rectangle representing memory word"
addActionListener {
unitWidth = getInt()
grid = createNewGrid()
reset()
}
}
val uiUnitHeightSelector = JComboBox(arrayOf(1, 2, 4, 8, 16, 32)).apply {
isEditable = false
background = backgroundColor
selectedIndex = 1
toolTipText = "Height in pixels of rectangle representing memory word"
addActionListener {
unitHeight = getInt()
grid = createNewGrid()
reset()
}
}
val uiWidthSelector = JComboBox(arrayOf(64, 128, 256, 512, 1024)).apply {
isEditable = false
background = backgroundColor
selectedIndex = 3
toolTipText = "Total width in pixels of display area"
addActionListener {
displayWidth = getInt()
canvas.preferredSize = displayDimension
canvas.size = displayDimension
grid = createNewGrid()
reset()
}
}
val uiHeightSelector = JComboBox(arrayOf(64, 128, 256, 512, 1024)).apply {
isEditable = false
background = backgroundColor
selectedIndex = 2
toolTipText = "Total height in pixels of display area"
addActionListener {
displayHeight = getInt()
canvas.preferredSize = displayDimension
canvas.size = displayDimension
grid = createNewGrid()
reset()
}
}
val descriptions = arrayOf("global data", "\$gp", "static data", "heap", "memory map")
val displayBaseAddressChoices = baseAddresses
.mapIndexed { i, it -> "${Binary.intToHexString(it)} (${descriptions[i]})" }.toTypedArray()
uiBaseAddressSelector = JComboBox(displayBaseAddressChoices).apply {
isEditable = false
background = backgroundColor
selectedIndex = 1
toolTipText = "Base address for display area (upper left corner)"
addActionListener {
// If display base address is changed while connected to MIPS (this can only occur
// when being used as a MarsTool), we have to delete ourselves as an observer and re-register.
if (connectButton != null && connectButton.isConnected)
{
deleteAsObserver()
addAsObserver()
}
grid = createNewGrid()
reset()
}
}
val uiKeyboardAddress = JTextField(keyboardAddr.toHex(8)).apply {
addActionListener {
text.toUIntOrNull(text.detectRadix())?.let {
keyboardAddr = it
uiKeyboardCheckbox.isSelected = false
}
}
}
val uiKeyboardCheckbox = JCheckBox("Attach Keyboard++", keyboardAttached).apply {
addActionListener {
keyboardAttached = isSelected
if (isSelected)
{
deleteAsObserver()
addAsObserver()
}
pooledKeyEvents.values.forEach { it.clear() }
}
}
println("Bitmap Display++ Initialized")
// Register key listener
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher {
if (!(keyboardAttached && uiKeyboard.hasFocus())) return@addKeyEventDispatcher false
when (it.id)
{
KeyEvent.KEY_PRESSED ->
{
// Keep track of down keys to avoid the system's key repeat
if (!downKeys.contains(it.keyCode))
{
keyEvent(0x00u, it)
downKeys.add(it.keyCode)
}
}
KeyEvent.KEY_RELEASED ->
{
keyEvent(0x10u, it)
downKeys.remove(it.keyCode)
}
}
true
}
uiKeyboard = JTextField(2).apply {
class KL : KeyListener
{
override fun keyTyped(e: KeyEvent) {
uiKeyboard.text = ""
}
override fun keyPressed(e: KeyEvent) {
uiKeyboard.text = ""
// keyEvent(0x0u, e)
}
override fun keyReleased(e: KeyEvent) {
uiKeyboard.text = ""
// keyEvent(0x10u, e)
}
}
addKeyListener(KL())
}
// Lay 'em out in the grid...
return JPanel(GridLayout(8, 1)).apply {
add(getPanelWithBorderLayout().apply {
add(JLabel("Unit Width in Pixels "), WEST)
add(uiUnitWidthSelector, EAST)
})
add(getPanelWithBorderLayout().apply {
add(JLabel("Unit Height in Pixels "), WEST)
add(uiUnitHeightSelector, EAST)
})
add(getPanelWithBorderLayout().apply {
add(JLabel("Display Width in Pixels "), WEST)
add(uiWidthSelector, EAST)
})
add(getPanelWithBorderLayout().apply {
add(JLabel("Display Height in Pixels "), WEST)
add(uiHeightSelector, EAST)
})
add(getPanelWithBorderLayout().apply {
add(JLabel("Base address for display "), WEST)
add(uiBaseAddressSelector, EAST)
})
add(getPanelWithBorderLayout().apply {
add(uiKeyboardCheckbox, WEST)
add(uiKeyboardAddress, EAST)
})
add(getPanelWithBorderLayout().apply {
add(JLabel("To use keyboard, put your cursor here >"), WEST)
add(uiKeyboard)
})
}
}
// UI components and layout for right half of GUI, the visualization display area.
private fun buildVisualizationArea(): JComponent
{
canvas = GraphicsPanel()
canvas.preferredSize = displayDimension
canvas.toolTipText = "Bitmap display area"
return canvas
}
private fun <T> JComboBox<T>.getInt() = selectedItem!!.toString().toInt()
// Use this for consistent results.
private fun getPanelWithBorderLayout(): JPanel = JPanel(BorderLayout(2, 2)).apply { border = emptyBorder }
// Method to determine grid dimensions based on current control settings.
// Each grid element corresponds to one visualization unit.
private fun createNewGrid(): Grid
{
val rows = displayHeight / unitHeight
val columns = displayWidth / unitWidth
return Grid(rows, columns)
}
//////////////////////////////////////////////////////////////////////////////////////
// Specialized inner classes for modeling and animation.
//////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Class that represents the panel for visualizing and animating memory reference
// patterns.
private inner class GraphicsPanel : JPanel()
{
// override default paint method to assure display updated correctly every time
// the panel is repainted.
override fun paint(g: Graphics) = paintGrid(g, grid)
// Paint the color codes.
private fun paintGrid(g: Graphics, grid: Grid)
{
var upperLeftX = 0
var upperLeftY = 0
for (i in 0 until grid.rows)
{
for (j in 0 until grid.cols)
{
g.color = grid.getElementFast(i, j)
g.fillRect(upperLeftX, upperLeftY, unitWidth, unitHeight)
upperLeftX += unitWidth // faster than multiplying
}
// get ready for next row...
upperLeftX = 0
upperLeftY += unitHeight // faster than multiplying
}
}
}
////////////////////////////////////////////////////////////////////////
// Represents grid of colors
private class Grid(rows: Int, columns: Int)
{
var grid: Array<Array<Color?>>
var rows: Int
var cols: Int
init
{
grid = Array(rows) { arrayOfNulls(columns) }
this.rows = rows
this.cols = columns
reset()
}
// Returns value in given grid element; null if row or column is out of range.
private fun getElement(row: Int, column: Int): Color?
{
return if (row in 0..rows && column >= 0 && column <= cols) grid[row][column] else null
}
// Returns value in given grid element without doing any row/column index checking.
// Is faster than getElement but will throw array index out of bounds exception if
// parameter values are outside the bounds of the grid.
fun getElementFast(row: Int, column: Int): Color?
{
return grid[row][column]
}
// Set the grid element.
fun setElement(row: Int, column: Int, color: Int)
{
grid[row][column] = Color(color)
}
// Set the grid element.
private fun setElement(row: Int, column: Int, color: Color)
{
grid[row][column] = color
}
// Just set all grid elements to black.
fun reset()
{
for (i in 0 until rows)
{
for (j in 0 until cols)
{
grid[i][j] = Color.BLACK
}
}
}
}
companion object
{
private const val VERSION = "Version 1.0"
private const val HEADING = "Bitmap Display++"
/**
* Main provided for pure stand-alone use. Recommended stand-alone use is to write a driver program that
* instantiates a Bitmap 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.
*/
@JvmStatic
fun main(args: Array<String>)
{
BitmapDisplay("Bitmap Display stand-alone, " + VERSION, HEADING).go()
}
}
}