diff --git a/locale/en-US.txt b/locale/en-US.txt index 97e0486..e53b626 100644 --- a/locale/en-US.txt +++ b/locale/en-US.txt @@ -10,6 +10,7 @@ app { debug { (menu) Debug console Console + cpu CPU memory Memory } @@ -36,6 +37,16 @@ console { title Console } +# CPU window +cpu { + float Float + hex Hex + last_pc Last PC + signed Signed + title CPU + unsigned Unsigned +} + # Memory window memory { title Memory diff --git a/makefile b/makefile index d5c9951..e9d12fe 100644 --- a/makefile +++ b/makefile @@ -69,7 +69,7 @@ core: desktop: clean_desktop @echo " Compiling Java desktop application" @javac -sourcepath src/desktop --release 10 -Xlint:unchecked \ - -h src/desktop/native -d . src/desktop/Main.java + -h src/desktop/vue -d . src/desktop/Main.java # Build all native modules .PHONY: native @@ -103,7 +103,7 @@ clean_desktop: # Delete everything but the .jar .PHONY: clean_most clean_most: clean_desktop - @rm -f src/desktop/native/vue_NativeVUE.h native/*.dll native/*.so + @rm -f src/desktop/vue/vue_NativeVUE.h native/*.dll native/*.so @@ -112,14 +112,14 @@ clean_most: clean_desktop ############################################################################### # JNI header file -src/desktop/native/vue_NativeVUE.h: src/desktop/vue/NativeVUE.java - @javac -h src/desktop/native -sourcepath src/desktop -d . \ +src/desktop/vue/vue_NativeVUE.h: src/desktop/vue/NativeVUE.java + @javac -h src/desktop/vue -sourcepath src/desktop -d . \ src/desktop/vue/NativeVUE.java @sleep 3 # linux_x86 .PHONY: lin32_pre -lin32_pre: src/desktop/native/vue_NativeVUE.h +lin32_pre: src/desktop/vue/vue_NativeVUE.h $(eval name = linux_x86) $(eval prefix = `uname -m`-linux-gnu-) $(eval include = -I$(include_linux) -I$(include_linux)/linux) @@ -130,7 +130,7 @@ lin32: lin32_pre native_common # linux_x86-64 .PHONY: lin64_pre -lin64_pre: src/desktop/native/vue_NativeVUE.h +lin64_pre: src/desktop/vue/vue_NativeVUE.h $(eval name = linux_x86-64) $(eval prefix = `uname -m`-linux-gnu-) $(eval include = -I$(include_linux) -I$(include_linux)/linux) @@ -141,7 +141,7 @@ lin64: lin64_pre native_common # windows_x86 .PHONY: win32_pre -win32_pre: src/desktop/native/vue_NativeVUE.h +win32_pre: src/desktop/vue/vue_NativeVUE.h $(eval name = windows_x86) $(eval prefix = i686-w64-mingw32-) $(eval include = -I$(include_windows) -I$(include_windows)/win32) @@ -151,7 +151,7 @@ win32: win32_pre native_common # windows_x86-64 .PHONY: win64_pre -win64_pre: src/desktop/native/vue_NativeVUE.h +win64_pre: src/desktop/vue/vue_NativeVUE.h $(eval name = windows_x86-64) $(eval prefix = x86_64-w64-mingw32-) $(eval include = -I$(include_windows) -I$(include_windows)/win32) @@ -165,4 +165,4 @@ native_common: @echo " Building native module $(name)" @$(prefix)gcc $(include) -Isrc/core/include $(gccargs) -s -shared -O2 \ -fno-strict-aliasing -Werror \ - -o native/$(name)$(ext) src/desktop/native/native.c src/core/vue.c + -o native/$(name)$(ext) src/desktop/vue/NativeVUE.c src/core/vue.c diff --git a/src/core/bus.c b/src/core/bus.c index 9ace5dd..d50aa2b 100644 --- a/src/core/bus.c +++ b/src/core/bus.c @@ -69,6 +69,13 @@ static int32_t busReadValue(VUE *vue, uint32_t address, int type) { return 0; /* Unreachable */ } +/* Perform a system reset */ +static void busReset(VUE *vue) { + int x; + for (x = 0; x < 0x10000; x++) + vue->bus.wram[x] = 0; +} + /* Write bytes to host memory */ static void busWriteBytes(uint8_t *dest, uint8_t *src, uint32_t address, uint32_t size, uint32_t length) { diff --git a/src/core/cpu.c b/src/core/cpu.c index f62ed58..7c26baf 100644 --- a/src/core/cpu.c +++ b/src/core/cpu.c @@ -90,6 +90,13 @@ static int32_t cpuSetSystemRegister(VUE *vue, int index, int32_t value, return vue->cpu.chcw_ice << 1; + case VUE_ECR: + if (debug) { + vue->cpu.ecr_fecc = value >> 16 & 0xFFFF; + vue->cpu.ecr_eicc = value & 0xFFFF; + } + return vue->cpu.ecr_fecc << 16 | vue->cpu.ecr_eicc; + case VUE_PSW : vue->cpu.psw_i = value >> 16 & 15; vue->cpu.psw_np = value >> 15 & 1; @@ -109,16 +116,16 @@ static int32_t cpuSetSystemRegister(VUE *vue, int index, int32_t value, return value & 0x000FF3FF; /* Remaining cases to encourage tableswitch */ - case 4: case 6: case 7: case 8: case 9: case 10: case 11: - case 12: case 13: case 14: case 15: case 16: case 17: case 18: - case 19: case 20: case 21: case 22: case 23: case 26: case 27: - case 28: case 30: + case 6: case 7: case 8: case 9: case 10: case 11: case 12: + case 13: case 14: case 15: case 16: case 17: case 18: case 19: + case 20: case 21: case 22: case 23: case 26: case 27: case 28: + case 30: return 0; } return 1; /* Unreachable */ } -/* System reset */ +/* Perform a system reset */ static void cpuReset(VUE *vue) { int x; @@ -127,15 +134,17 @@ static void cpuReset(VUE *vue) { vue->cpu.fetch = 0; vue->cpu.stage = CPU_FETCH; - /* Reset program counter */ - vue->cpu.pc = 0xFFFFFFF0; - /* Clear all registers (hardware only sets ECR, PC and PSW) */ for (x = 0; x < 32; x++) { vue->cpu.program[x] = 0; cpuSetSystemRegister(vue, x, 0, VUE_TRUE); } + /* Configure registers */ + vue->cpu.ecr_eicc = 0xFFF0; + vue->cpu.lastPC = 0xFFFFFFF0; + vue->cpu.pc = 0xFFFFFFF0; + vue->cpu.psw_np = 1; } /* Test a condition */ diff --git a/src/core/include/vue.h b/src/core/include/vue.h index 9cfbb84..aa5bfd8 100644 --- a/src/core/include/vue.h +++ b/src/core/include/vue.h @@ -43,6 +43,13 @@ extern "C" { #define VUE_PSW 5 #define VUE_TKCW 7 +/* Program register indexes */ +#define VUE_GP 4 +#define VUE_HP 2 +#define VUE_LP 31 +#define VUE_SP 3 +#define VUE_TP 5 + /***************************************************************************** @@ -68,6 +75,7 @@ typedef struct { struct { uint32_t cycles; /* Cycles until next stage */ int fetch; /* Fetch unit index */ + int32_t lastPC; /* Previous value of PC */ int stage; /* Current processing stage */ /* Program registers */ @@ -101,17 +109,17 @@ typedef struct { int8_t psw_z; /* Zero */ /* Cache Control Word */ - int32_t chcw_cec; /* Clear Entry Count */ - int32_t chcw_cen; /* Clear Entry Number */ - int8_t chcw_icc; /* Instruction Cache Clear */ - int8_t chcw_icd; /* Instruction Cache Dump */ - int8_t chcw_ice; /* Instruction Cache Enable */ - int8_t chcw_icr; /* Instruction Cache Restore */ - int32_t chcw_sa; /* Spill-Out Base Address */ + uint16_t chcw_cec; /* Clear Entry Count */ + uint16_t chcw_cen; /* Clear Entry Number */ + int8_t chcw_icc; /* Instruction Cache Clear */ + int8_t chcw_icd; /* Instruction Cache Dump */ + int8_t chcw_ice; /* Instruction Cache Enable */ + int8_t chcw_icr; /* Instruction Cache Restore */ + int32_t chcw_sa; /* Spill-Out Base Address */ /* Exception Cause Register */ - int8_t ecr_eicc; /* Exception/Interrupt Cause Code */ - int8_t ecr_fecc; /* Fatal Error Cause Code */ + uint16_t ecr_eicc; /* Exception/Interrupt Cause Code */ + uint16_t ecr_fecc; /* Fatal Error Cause Code */ } cpu; } VUE; @@ -125,6 +133,7 @@ typedef struct { VUEAPI int32_t vueGetRegister(VUE *vue, int index, vbool system); VUEAPI void vueInitialize (VUE *vue); VUEAPI vbool vueRead (VUE *vue, uint32_t address, uint8_t *dest, uint32_t length); +VUEAPI void vueReset (VUE *vue); VUEAPI int32_t vueSetRegister(VUE *vue, int index, vbool system, int32_t value); VUEAPI vbool vueSetROM (VUE *vue, uint8_t *rom, uint32_t size); VUEAPI vbool vueWrite (VUE *vue, uint32_t address, uint8_t *src, uint32_t length); diff --git a/src/core/vue.c b/src/core/vue.c index fbf8208..62ee09d 100644 --- a/src/core/vue.c +++ b/src/core/vue.c @@ -2,7 +2,6 @@ #include - /***************************************************************************** * Constants * *****************************************************************************/ @@ -27,7 +26,7 @@ static const int TYPE_SIZES[] = { 1, 1, 2, 2, 4 }; /* Retrieve the value of a register */ int32_t vueGetRegister(VUE *vue, int index, vbool system) { - return + return vue == NULL ? 0 : index == VUE_PC && system ? vue->cpu.pc : index < 0 || index > 31 ? 0 : system ? cpuGetSystemRegister(vue, index) : @@ -37,6 +36,8 @@ int32_t vueGetRegister(VUE *vue, int index, vbool system) { /* Prepare an emulation state context for use */ void vueInitialize(VUE *vue) { + if (vue == NULL) + return; vue->bus.rom = NULL; vue->bus.sram = NULL; } @@ -79,13 +80,21 @@ vbool vueRead(VUE *vue, uint32_t address, uint8_t *dest, uint32_t length) { return VUE_TRUE; } +/* Initialize all system components */ +void vueReset(VUE *vue) { + if (vue == NULL) + return; + busReset(vue); + cpuReset(vue); +} + /* Specify a value for a register */ int32_t vueSetRegister(VUE *vue, int index, vbool system, int32_t value) { - return + return vue == NULL ? 0 : index == VUE_PC && system ? vue->cpu.pc = value & 0xFFFFFFFE : index < 0 || index > 31 ? 0 : system ? cpuSetSystemRegister(vue, index, value, VUE_TRUE) : - index == 0 ? 0 : vue->cpu.program[index] + index == 0 ? 0 : (vue->cpu.program[index] = value) ; } diff --git a/src/desktop/app/CPU.java b/src/desktop/app/CPU.java new file mode 100644 index 0000000..7a4ad10 --- /dev/null +++ b/src/desktop/app/CPU.java @@ -0,0 +1,255 @@ +package app; + +// Java imports +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +// Project imports +import util.*; +import vue.*; + +// CPU window +class CPU extends ChildWindow { + + // Instance fields + private int expandWidth; // Width of expand buttons + private Font font; // Display font + private int fontHeight; // Font line height + private int fontWidth; // Font maximum character width + private boolean shown; // Window has been shown + private int systemHeight; // Initial height of system register list + + // UI components + private JPanel dasm; // Disassembler + private JPanel system; // System registers + private JPanel program; // Program registers + private ArrayList registers; // Register controls + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + CPU(MainWindow parent) { + super(parent, "cpu.title"); + var client = getContentPane(); + + // Configure instance fields + font = new Font(Util.fontFamily(new String[] + { "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14); + registers = new ArrayList(); + + dasm = new JPanel(null); + dasm.setBackground(SystemColor.window); + dasm.setFocusable(true); + + var inner = initRegisters(); + var outer = Util.splitPane(JSplitPane.HORIZONTAL_SPLIT); + outer.setLeftComponent(new JScrollPane(dasm, + JScrollPane.VERTICAL_SCROLLBAR_NEVER, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + outer.setRightComponent(inner); + outer.setResizeWeight(1); + + client.add(outer); + client.setPreferredSize(new Dimension(640, 480)); + setFont2(font); + pack(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Register Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Initialize program register list + private void initProgram() { + Dimension target = null; + + // Program register container + var lst = program = new JPanel(new GridBagLayout()) { + public Dimension getPreferredSize() { + var ret = super.getPreferredSize(); + if (!shown) + ret.height = 0; + return ret; + } + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (shown) + return; + shown = true; + system.revalidate(); + program.revalidate(); + } + }; + lst.setBackground(SystemColor.window); + lst.setFocusable(true); + lst.addMouseListener(Util.onMouse(e->lst.requestFocus(),null)); + + // Produce the list of program registers + for (int x = 0; x < 32; x++) { + String name = "r" + x; + switch (x) { + case VUE.GP: name = "gp"; break; + case VUE.HP: name = "hp"; break; + case VUE.LP: name = "lp"; break; + case VUE.SP: name = "sp"; break; + case VUE.TP: name = "tp"; break; + } + registers.add(new Register(parent,lst,name,x,Register.PROGRAM)); + } + endList(lst); + } + + // Initialize register lists + private JSplitPane initRegisters() { + initSystem(); + initProgram(); + + var ret = Util.splitPane(JSplitPane.VERTICAL_SPLIT); + ret.setTopComponent(new JScrollPane(system, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + )); + ret.setBottomComponent(new JScrollPane(program, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED + )); + return ret; + } + + // Initialize system register list + private void initSystem() { + + // System register container + var lst = system = new JPanel(new GridBagLayout()) { + public Dimension getPreferredSize() { + var ret = super.getPreferredSize(); + if (!shown) + ret.height = systemHeight; + return ret; + } + }; + system.setBackground(SystemColor.window); + system.setFocusable(true); + system.addMouseListener(Util.onMouse(e->system.requestFocus(), null)); + system.putClientProperty("shown", true); + + // Add the first two system registers and expand PSW + registers.add(new Register(parent, lst, "PC" , VUE.PC , VUE.PC )); + Register psw =new Register(parent, lst, "PSW" , VUE.PSW , VUE.PSW ); + psw.setExpanded(true); + registers.add(psw); + shown = true; + systemHeight = system.getPreferredSize().height + 6; + shown = false; + + // Add the remaining system registers + registers.add(new Register(parent, lst, "EIPC" , VUE.EIPC , VUE.PC )); + registers.add(new Register(parent, lst, "EIPSW", VUE.EIPSW, VUE.PSW )); + registers.add(new Register(parent, lst, "FEPC" , VUE.FEPC , VUE.PC )); + registers.add(new Register(parent, lst, "FEPSW", VUE.FEPSW, VUE.PSW )); + registers.add(new Register(parent, lst, "ECR" , VUE.ECR , VUE.ECR )); + registers.add(new Register(parent, lst, "ADTRE", VUE.ADTRE, VUE.PC )); + registers.add(new Register(parent, lst, "CHCW" , VUE.CHCW , VUE.CHCW)); + registers.add(new Register(parent, lst, "PIR" , VUE.PIR , VUE.PIR )); + registers.add(new Register(parent, lst, "TKCW" , VUE.TKCW , VUE.TKCW)); + registers.add(new Register(parent, lst, "29" , 29, VUE.PC )); + registers.add(new Register(parent, lst, "30" , 30, VUE.PC )); + registers.add(new Register(parent, lst, "31" , 31, VUE.PC )); + endList(lst); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Event Handlers // + /////////////////////////////////////////////////////////////////////////// + + // Client resize + private void onResize() { + //refreshDasm(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // Update the display + void refresh() { + + // The element is not ready + if (registers == null) + return; + + // Refresh registers + for (var reg : registers) + reg.refresh(); + } + + // Specify a new font + void setFont2(Font font) { + this.font = font; + + // Configure the maximum font dimensions + var fontMax = new JLabel("!"); + fontMax.setFont(font); + fontHeight = Math.max(1, fontMax.getPreferredSize().height); + fontWidth = -1; + for (int x = 0; x < 16; x++) { + fontMax.setText(Integer.toString(x, 16).toUpperCase()); + fontWidth = Math.max(fontWidth, fontMax.getPreferredSize().width); + } + + // Configure register list scrolling + for (int x = 0; x < 2; x++) { + Component ctrl = x == 0 ? system : program; + while (!(ctrl instanceof JScrollPane)) + ctrl = ctrl.getParent(); + ((JScrollPane) ctrl).getVerticalScrollBar() + .setUnitIncrement(fontHeight); + } + + // Determine the width of the register expand buttons + var expand = new JLabel("+"); + int width = expand.getPreferredSize().width; + expand.setText("-"); + width = Math.max(width, expand.getPreferredSize().width) + 4; + + // Configure registers + for (var reg : registers) { + reg.setExpandWidth(width); + reg.setFont(font, fontWidth, fontHeight); + } + + onResize(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Private Methods // + /////////////////////////////////////////////////////////////////////////// + + // Terminate a register list + private JPanel endList(JPanel list) { + var spacer = new JPanel(null); + spacer.setOpaque(false); + spacer.setPreferredSize(new Dimension(1, 1)); + var gbc = new GridBagConstraints(); + gbc.gridheight = GridBagConstraints.REMAINDER; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.weighty = 1; + list.add(spacer, gbc); + return spacer; + } + +} diff --git a/src/desktop/app/MainWindow.java b/src/desktop/app/MainWindow.java index 5882330..b0e6ab9 100644 --- a/src/desktop/app/MainWindow.java +++ b/src/desktop/app/MainWindow.java @@ -29,6 +29,7 @@ class MainWindow extends JFrame { // UI components private JPanel client; // Common client container private Console console; // Console window + private CPU cpu; // CPU window private JDesktopPane desktop; // Container for child windows private Memory memory; // Memory window private JPanel video; // Video output @@ -92,9 +93,11 @@ class MainWindow extends JFrame { desktop = new JDesktopPane(); desktop.setBackground(SystemColor.controlShadow); desktop.add(console = new Console(this)); + desktop.add(cpu = new CPU (this)); desktop.add(memory = new Memory (this)); // Display window + refreshDebug(); pack(); setLocationRelativeTo(null); setVisible(true); @@ -131,6 +134,11 @@ class MainWindow extends JFrame { mnuDebugMemory.addActionListener(e->memory.setVisible(true)); mnuDebug.add(mnuDebugMemory); + var mnuDebugCPU = new JMenuItem(); + loc.add(mnuDebugCPU, "app.debug.cpu"); + mnuDebugCPU.addActionListener(e->cpu.setVisible(true)); + mnuDebug.add(mnuDebugCPU); + return mnuDebug; } @@ -178,6 +186,7 @@ class MainWindow extends JFrame { // Refresh all debug views void refreshDebug() { + cpu .refresh(); memory.refresh(); } @@ -283,6 +292,7 @@ class MainWindow extends JFrame { var bytes = rom.toByteArray(); // Pause emulation vue.setROM(bytes, 0, bytes.length); + vue.reset(); refreshDebug(); // Resume emulation } diff --git a/src/desktop/app/Memory.java b/src/desktop/app/Memory.java index d8da350..00bd9c7 100644 --- a/src/desktop/app/Memory.java +++ b/src/desktop/app/Memory.java @@ -13,12 +13,13 @@ import util.*; class Memory extends ChildWindow { // Private fields - private int address; // Address of top row - private Font font; // Display font + private int address; // Address of top row + private Font font; // Display font + private int fontHeight; // Font line height + private int fontWidth; // Font maximum character width // UI components - private JPanel client; // Client area - private JLabel fontHeight; // Font height proxy + private JPanel client; // Client area private ArrayList rows; // Rows of text @@ -44,23 +45,25 @@ class Memory extends ChildWindow { super(parent, "memory.title"); // Configure instance fields - address = 0x00000000; - font = new Font(Util.fontFamily(new String[] + address = 0x00000000; + font = new Font(Util.fontFamily(new String[] { "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14); - fontHeight = new JLabel("."); - rows = new ArrayList(); + rows = new ArrayList(); // Configure client area client = new JPanel(null); client.addComponentListener(Util.onResize(e->onResize())); client.addKeyListener(Util.onKey(e->onKeyDown(e), null)); client.addMouseWheelListener(e->onWheel(e)); + client.setBackground(SystemColor.window); client.setFocusable(true); client.setPreferredSize(new Dimension(640, 480)); - client.setBackground(SystemColor.window); // Configure component - setContentPane(client); + var content = new JPanel(new BorderLayout()); + content.setBorder(new JScrollPane().getBorder()); + content.add(client, BorderLayout.CENTER); + setContentPane(content); setFont2(font); pack(); } @@ -79,11 +82,9 @@ class Memory extends ChildWindow { return; // Configure working variables - int height = client.getHeight(); - int lineHeight = fontHeight.getPreferredSize().height; - int count = (height + lineHeight - 1) / lineHeight; - var data = new byte[count * 16]; - var widths = new int[2]; + int height = client.getHeight(); + int count = (height + fontHeight - 1) / fontHeight; + var data = new byte[count * 16]; // Retrieve all visible bytes from the emulation context parent.vue.read(address, data, 0, data.length); @@ -91,26 +92,17 @@ class Memory extends ChildWindow { // Update visible rows for (int x = 0; x < count; x++) { Row row; - - // Retrieve the row if it exists, make a new one otherwise if (x < rows.size()) - row = rows.get(x); - else row = createRow(); - - // Configure the row - update(row, address + x * 16, data, x * 16); + row = rows.get(x); // Retrieve row from collection + else row = createRow(); // Produce a new row + update(row, x * fontHeight, address + x * 16, data, x * 16); setVisible(row, true); - measure(row, widths); } // Hide any rows that are not visible for (int x = count; x < rows.size(); x++) setVisible(rows.get(x), false); - // Position components - for (int x = 0; x < rows.size(); x++) - arrange(rows.get(x), x * lineHeight, widths, lineHeight); - // Finalize layout client.revalidate(); client.repaint(); @@ -119,12 +111,24 @@ class Memory extends ChildWindow { // Specify a new font void setFont2(Font font) { this.font = font; - fontHeight.setFont(font); + + // Configure the maximum font dimensions + var fontMax = new JLabel("!"); + fontMax.setFont(font); + fontHeight = Math.max(1, fontMax.getPreferredSize().height); + fontWidth = -1; + for (int x = 0; x < 16; x++) { + fontMax.setText(Integer.toString(x, 16).toUpperCase()); + fontWidth = Math.max(fontWidth, fontMax.getPreferredSize().width); + } + + // Configure rows for (var row : rows) { row.address.setFont(font); for (var label : row.bytes) label.setFont(font); } + onResize(); } @@ -192,27 +196,9 @@ class Memory extends ChildWindow { // Private Methods // /////////////////////////////////////////////////////////////////////////// - // Position the elements of a row - private void arrange(Row row, int y, int[] widths, int height) { - int spacing = (height + 1) / 2; - - // Size and position the address header - row.address.setSize(row.address.getPreferredSize()); - row.address.setLocation(0, y); - - // Size and position the byte labels - for (int z = 0, x = widths[0] + height; z < 16; z++) { - var label = row.bytes[z]; - label.setBounds(x, y, widths[1], height); - x += widths[1] + (z == 7 ? height : spacing); - } - - } - // Add a new row of output private Row createRow() { var row = new Row(); - row.address = // Address label row.address = new JLabel(); @@ -260,16 +246,24 @@ class Memory extends ChildWindow { // Determine how many rows of output are visible private int tall(boolean partial) { - int lineHeight = fontHeight.getPreferredSize().height; - return lineHeight == 0 ? 0 : - (client.getHeight() + (partial ? lineHeight + 1 : 0)) / lineHeight; + return (client.getHeight() + (partial?fontHeight-1:0)) / fontHeight; } // Update the text of a row - private void update(Row row, int address, byte[] data, int offset) { + private void update(Row row, int y, int address, byte[] data, int offset) { + + // Update address + row.address.setBounds(0, y, 8 * fontWidth, fontHeight); row.address.setText(String.format("%08X", address)); - for (var label : row.bytes) + + // Update bytes + for (int z = 0, x = 10 * fontWidth; z < 16; z++) { + var label = row.bytes[z]; + label.setBounds(x, y, 2 * fontWidth, fontHeight); label.setText(String.format("%02X", data[offset++] & 0xFF)); + x += fontWidth * (z == 7 ? 4 : 3); + } + } } diff --git a/src/desktop/app/Register.java b/src/desktop/app/Register.java new file mode 100644 index 0000000..2c40eb4 --- /dev/null +++ b/src/desktop/app/Register.java @@ -0,0 +1,419 @@ +package app; + +// Java imports +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +// Project imports +import util.*; +import vue.*; + +// Register list item +class Register { + + // Instance fields + boolean expanded; // The expanded area is being shown + Font font; // Hexadecimal font + int index; // Register index + int mode; // Display mode for program registers + MainWindow parent; // Containing window + int type; // Expansion controls type + int value; // Current register value + + // UI components + private JPanel expansion; // Expansion area + private JLabel btnExpand; // Expand button + private JLabel lblName; // Register name + private JPanel list; // Containing element + private JPanel spacer; // Expansion area spacer + private JTextField txtValue; // Register value + private ArrayList controls; // Expansion controls + + + + /////////////////////////////////////////////////////////////////////////// + // Constants // + /////////////////////////////////////////////////////////////////////////// + + // Modes + static final int FLOAT = 3; + static final int HEX = 0; + static final int SIGNED = 1; + static final int UNSIGNED = 2; + + // Types + static final int PROGRAM = -2; + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + Register(MainWindow parent, JPanel list, String name, int index, int type){ + + // Configure instance fields + controls = new ArrayList(); + this.index = index; + this.list = list; + this.mode = HEX; + this.parent = parent; + this.type = type; + + // Click handler for expand and name controls + MouseListener expand = type == VUE.PC ? null : Util.onMouse( + e->{ if (e.getButton() == 1) setExpanded(!expanded); }, null); + + // Expand button + btnExpand = new JLabel(expand != null ? "+" : " "); + btnExpand.setHorizontalAlignment(SwingConstants.CENTER); + if (expand != null) + btnExpand.addMouseListener(expand); + var gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTH; + gbc.fill = GridBagConstraints.HORIZONTAL; + list.add(btnExpand, gbc); + + // Name label + lblName = new JLabel(name); + if (expand != null) + lblName.addMouseListener(expand); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTH; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1; + list.add(lblName, gbc); + + // Value text box + txtValue = new JTextField(); + txtValue.setBorder(null); + txtValue.setOpaque(false); + txtValue.setText("00000000"); + gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.insets = new Insets(0, 4, 0, 0); + list.add(txtValue, gbc); + + // Value changed + txtValue.addActionListener(e->{ + String text = txtValue.getText(); + int val = value; + try { switch (mode) { + case HEX : val = (int) Long.parseLong(text, 16); break; + case SIGNED : // Fallthrough + case UNSIGNED: val = (int) Long.parseLong(text, 10); break; + case FLOAT : val = + Float.floatToIntBits(Float.parseFloat(text) ); break; + }} catch (Exception x) { } + setValue(val); + }); + + // Expansion controls + switch (type) { + case PROGRAM : initProgram(); break; + case VUE.CHCW: initCHCW (); break; + case VUE.ECR : initECR (); break; + case VUE.PIR : initPIR (); break; + case VUE.PSW : initPSW (); break; + case VUE.TKCW: initTKCW (); break; + default: return; + } + + // Expansion spacer + spacer = new JPanel(null); + spacer.setOpaque(false); + spacer.setPreferredSize(new Dimension(0, 0)); + spacer.setVisible(false); + list.add(spacer, new GridBagConstraints()); + + // Expansion area + expansion.setOpaque(false); + expansion.setVisible(false); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.insets = new Insets(0, 4, 0, 0); + list.add(expansion, gbc); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Expansion Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Expansion controls for CHCW + private void initCHCW() { + expansion = new JPanel(new GridBagLayout()); + addCheckBox("ICE", 1, false); endRow(); + } + + // Expansion controls for ECR + private void initECR() { + expansion = new JPanel(new GridBagLayout()); + addTextBox("EICC", 0, 16, false, true); endRow(); + addTextBox("FECC", 16, 16, false, true); endRow(); + } + + // Expansion controls for program registers + private void initProgram() { + expansion = new JPanel(new GridBagLayout()); + var group = new ButtonGroup(); + group.add(addRadioButton("cpu.hex" , HEX )); + group.add(addRadioButton("cpu.signed" , SIGNED )); + group.add(addRadioButton("cpu.unsigned", UNSIGNED)); + group.add(addRadioButton("cpu.float" , FLOAT )); + } + + // Expansion controls for PSW + private void initPSW() { + expansion = new JPanel(new GridBagLayout()); + addCheckBox("Z" , 0, false); + addCheckBox("FRO", 9, false); endRow(); + addCheckBox("S" , 1, false); + addCheckBox("FIV", 8, false); endRow(); + addCheckBox("OV" , 2, false); + addCheckBox("FZD", 7, false); endRow(); + addCheckBox("CY" , 3, false); + addCheckBox("FOV", 6, false); endRow(); + addCheckBox("EP" , 14, false); + addCheckBox("FUD", 5, false); endRow(); + addCheckBox("NP" , 15, false); + addCheckBox("FPR", 4, false); endRow(); + addCheckBox("AE" , 13, false); endRow(); + addCheckBox("ID" , 12, false); + addTextBox ("I" , 16, 4, false, false); endRow(); + } + + // Expansion controls for PIR + private void initPIR() { + expansion = new JPanel(new GridBagLayout()); + addTextBox("PT", 0, 16, true, true); endRow(); + } + + // Expansion controls for TKCW + private void initTKCW() { + expansion = new JPanel(new GridBagLayout()); + addCheckBox("OTM", 8, true); + addCheckBox("FVT", 5, true); endRow(); + addCheckBox("FIT", 7, true); + addCheckBox("FUT", 4, true); endRow(); + addCheckBox("FZT", 6, true); + addCheckBox("FPT", 3, true); endRow(); + addCheckBox("RDI", 2, true); + addTextBox ("RD", 0, 2, true, false); endRow(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // Refresh controls + void refresh() { + + // Value text box + value = parent.vue.getRegister(index, type != PROGRAM); + txtValue.setText( + type != PROGRAM || mode == HEX ? + String.format("%08X", value) : + mode == SIGNED ? Integer.toString(value) : + mode == UNSIGNED ? Long.toString(value & 0xFFFFFFFFL) : + Float.toString(Float.intBitsToFloat(value)) + ); + + // Expansion controls + for (var control : controls) { + + // Check box + if (control instanceof JCheckBox) { + var ctrl = (JCheckBox) control; + int bit = (Integer) ctrl.getClientProperty("bit"); + ctrl.setSelected((value & 1 << bit) != 0); + } + + // Text box + if (control instanceof JTextField) { + var ctrl = (JTextField) control; + int bit = (Integer) ctrl.getClientProperty("bit" ); + int digits = (Integer) ctrl.getClientProperty("digits"); + boolean hex = (Boolean) ctrl.getClientProperty("hex"); + int width = (Integer) ctrl.getClientProperty("width"); + int val = value >> bit & (1 << width) - 1; + ctrl.setText(!hex ? Integer.toString(val) : + String.format("%0" + digits + "X", val)); + } + + } + + } + + // Specify whether the expansion area is expanded + void setExpanded(boolean expanded) { + + // Error checking + if (type == VUE.PC) + return; + + // Update controls + this.expanded = expanded; + btnExpand.setText (expanded ? "-" : "+"); + expansion.setVisible(expanded); + spacer .setVisible(expanded); + list.revalidate(); + list.repaint(); + } + + // Specify the width of the expand button + void setExpandWidth(int width) { + var size = btnExpand.getPreferredSize(); + size.width = width; + btnExpand.setPreferredSize(size); + } + + // Specify a new font + void setFont(Font font, int fontWidth, int fontHeight) { + this.font = font; + + // Value text box + txtValue.setFont(font); + txtValue.setPreferredSize( + new Dimension(8 * fontWidth + 4, fontHeight)); + + // Expansion controls + for (var ctrl : controls) { + if (!(ctrl instanceof JTextField)) + continue; + if ((Boolean) ctrl.getClientProperty("hex")) + ((JTextField) ctrl).setFont(font); + int digits = (Integer) ctrl.getClientProperty("digits"); + var size = ctrl.getPreferredSize(); + size.width = digits * fontWidth + 4; + ctrl.setPreferredSize(size); + } + + } + + // Change the display mode of a program register + void setMode(int mode) { + this.mode = mode; + txtValue.setFont(mode == HEX ? font : null); + refresh(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Private Methods // + /////////////////////////////////////////////////////////////////////////// + + // Add a check box to the expansion area + private void addCheckBox(String name, int bit, boolean readOnly) { + int mask = 1 << bit; + + // Configure control + var ctrl = new JCheckBox(name); + ctrl.putClientProperty("bit", bit); + ctrl.setBorder(null); + ctrl.setEnabled(!readOnly); + ctrl.setFocusable(false); + ctrl.setOpaque(false); + controls.add(ctrl); + + // Event handler + ctrl.addItemListener(e->setValue( + e.getStateChange() == ItemEvent.SELECTED ? + value | mask : value & ~mask + )); + + // Configure expansion area + var gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.gridwidth = 2; + gbc.insets = new Insets(0, 4, 0, 0); + expansion.add(ctrl, gbc); + } + + // Add a radio button to the expansion area + private JRadioButton addRadioButton(String key, int mode) { + + // Configure control + var ctrl = new JRadioButton(); + parent.app.getLocalizer().add(ctrl, key); + ctrl.setBorder(null); + ctrl.setFocusable(false); + ctrl.setOpaque(false); + ctrl.setSelected(mode == HEX); + controls.add(ctrl); + + // Event handler + ctrl.addItemListener(e->{ + if (e.getStateChange() == ItemEvent.SELECTED) setMode(mode); }); + + // Configure expansion area + var gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.insets = new Insets(0, 4, 0, 0); + expansion.add(ctrl, gbc); + + return ctrl; + } + + // Add a text box to the expansion area + private void addTextBox(String name, int bit, int width, boolean readOnly, + boolean hex) { + int mask = (1 << width) - 1; + + // Configure control + var ctrl = new JTextField(); + ctrl.putClientProperty("bit", bit); + ctrl.putClientProperty("digits", + Integer.toString(mask, hex ? 16 : 10).length()); + ctrl.putClientProperty("hex", hex); + ctrl.putClientProperty("width", width); + ctrl.setBorder(null); + ctrl.setEnabled(!readOnly); + ctrl.setOpaque(false); + controls.add(ctrl); + + // Event handlers + ctrl.addActionListener(e->{ + int val = value >> bit & mask; + try { val = Integer.parseInt(ctrl.getText(), hex ? 16 : 10); } + catch (Exception x) { } + setValue(value & ~(mask << bit) | (val & mask) << bit); + }); + + // Configure expansion area + var label = new JLabel(name); + label.setEnabled(!readOnly); + var gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.insets = new Insets(0, 4, 0, 4); + expansion.add(label, gbc); + expansion.add(ctrl , gbc); + } + + // Terminate a row of expansion controls + private void endRow() { + var spacer = new JPanel(null); + spacer.setOpaque(false); + spacer.setPreferredSize(new Dimension(0, 0)); + var gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + expansion.add(spacer, gbc); + } + + // Update the register value + private void setValue(int value) { + list.requestFocus(); + parent.vue.setRegister(index, type != PROGRAM, value); + refresh(); + } + +} \ No newline at end of file diff --git a/src/desktop/util/Util.java b/src/desktop/util/Util.java index ccbaadc..cbf5aad 100644 --- a/src/desktop/util/Util.java +++ b/src/desktop/util/Util.java @@ -28,12 +28,13 @@ public final class Util { /////////////////////////////////////////////////////////////////////////// // Event listener interfaces - public interface OnClose { void call(WindowEvent e); } - public interface OnClose2 { void call(InternalFrameEvent e); } - public interface OnFocus { void call(FocusEvent e); } - public interface OnKey { void call(KeyEvent e); } - public interface OnMouse { void call(MouseEvent e); } - public interface OnResize { void call(ComponentEvent e); } + public interface OnClose { void call(WindowEvent e); } + public interface OnClose2 { void call(InternalFrameEvent e); } + public interface OnFocus { void call(FocusEvent e); } + public interface OnKey { void call(KeyEvent e); } + public interface OnMouse { void call(MouseEvent e); } + public interface OnResize { void call(ComponentEvent e); } + public interface OnVisible { void call(AncestorEvent e); } // Data class for byte-order marks private static class BOM { @@ -310,6 +311,17 @@ public final class Util { }; } + // Produce an AncestorListener using functional interfaces + public static AncestorListener onVisible(OnVisible show, OnVisible hide) { + return new AncestorListener() { + public void ancestorMoved (AncestorEvent e) { } + public void ancestorAdded (AncestorEvent e) + { if (show != null) show.call(e); } + public void ancestorRemoved(AncestorEvent e) + { if (hide != null) hide.call(e); } + }; + } + // Configure the Swing look-and-feel with the system theme public static boolean setSystemLAF() { try { diff --git a/src/desktop/vue/Bus.java b/src/desktop/vue/Bus.java index 12602d4..5033eb5 100644 --- a/src/desktop/vue/Bus.java +++ b/src/desktop/vue/Bus.java @@ -87,6 +87,11 @@ class Bus { return 0; // Unreachable } + // Perform a system reset + public void reset() { + Arrays.fill(wram, 0, 0x10000, (byte) 0); + } + // Write bytes to host memory void writeBytes(byte[] dest,byte[] src,int address,int offset,int length) { diff --git a/src/desktop/vue/CPU.java b/src/desktop/vue/CPU.java index 1234e5d..5250aad 100644 --- a/src/desktop/vue/CPU.java +++ b/src/desktop/vue/CPU.java @@ -9,6 +9,7 @@ class CPU { // Package fields int cycles; // Cycles until next stage int fetch; // Fetch unit index + int lastPC; // Previous value of PC int stage; // Current processing stage // Program registers @@ -113,7 +114,19 @@ class CPU { // Remaining cases to encourage tableswitch case 8: case 9: case 10: case 11: case 12: case 13: case 14: - case 15: case 16: case 17: case 18: case 19: case 20: case 21: + case 15: case 16: case 17: case 18: case 19: case 20: case 21: // Configure instance fields + cycles = 0; + fetch = 0; + stage = FETCH; + + // Reset program counter + pc = 0xFFFFFFF0; + + // Clear all registers (hardware only sets ECR, PC and PSW) + for (int x = 0; x < 32; x++) { + program[x] = 0; + setSystemRegister(x, 0, true); + } case 22: case 23: case 26: case 27: case 28: return 0; } @@ -128,15 +141,17 @@ class CPU { fetch = 0; stage = FETCH; - // Reset program counter - pc = 0xFFFFFFF0; - // Clear all registers (hardware only sets ECR, PC and PSW) for (int x = 0; x < 32; x++) { program[x] = 0; setSystemRegister(x, 0, true); } + // Configure registers + ecr_eicc = 0xFFF0; + lastPC = 0xFFFFFFF0; + pc = 0xFFFFFFF0; + psw_np = 1; } // Write a system register @@ -150,6 +165,13 @@ class CPU { case 29 : return sr29 = value; case 31 : return sr31 = debug || value >= 0 ? value : -value; + case VUE.ECR: + if (debug) { + ecr_fecc = value >> 16 & 0xFFFF; + ecr_eicc = value & 0xFFFF; + } + return ecr_fecc << 16 | ecr_eicc; + case VUE.CHCW : chcw_cen = value >> 20 & 0x00000FFF; chcw_cec = value >> 8 & 0x00000FFF; @@ -181,10 +203,10 @@ class CPU { return value & 0x000FF3FF; // Remaining cases to encourage tableswitch - case 4: case 6: case 7: case 8: case 9: case 10: case 11: - case 12: case 13: case 14: case 15: case 16: case 17: case 18: - case 19: case 20: case 21: case 22: case 23: case 26: case 27: - case 28: case 30: + case 6: case 7: case 8: case 9: case 10: case 11: case 12: + case 13: case 14: case 15: case 16: case 17: case 18: case 19: + case 20: case 21: case 22: case 23: case 26: case 27: case 28: + case 30: return 0; } return 1; // Unreachable diff --git a/src/desktop/vue/JavaVUE.java b/src/desktop/vue/JavaVUE.java index ba8ffd8..73fe217 100644 --- a/src/desktop/vue/JavaVUE.java +++ b/src/desktop/vue/JavaVUE.java @@ -26,6 +26,7 @@ class JavaVUE extends VUE { JavaVUE() { bus = new Bus(this); cpu = new CPU(this); + reset(); } @@ -108,13 +109,19 @@ class JavaVUE extends VUE { return true; } + // Initialize all system components + public void reset() { + bus.reset(); + cpu.reset(); + } + // Specify a register value public int setRegister(int index, boolean system, int value) { return index == VUE.PC && system ? cpu.pc = value & 0xFFFFFFFE : index < 0 || index > 31 ? 0 : system ? cpu.setSystemRegister(index, value, true) : - index == 0 ? 0 : cpu.program[index] + index == 0 ? 0 : (cpu.program[index] = value) ; } diff --git a/src/desktop/native/native.c b/src/desktop/vue/NativeVUE.c similarity index 94% rename from src/desktop/native/native.c rename to src/desktop/vue/NativeVUE.c index 5f43a72..e87f5cb 100644 --- a/src/desktop/native/native.c +++ b/src/desktop/vue/NativeVUE.c @@ -1,3 +1,5 @@ +// Native-backed emulation core implementation + #include #include #include @@ -21,7 +23,7 @@ static const int TYPE_SIZES[] = { 1, 1, 2, 2, 4 }; // Core context state type typedef struct { - VUE vue; + VUE vue; /* Context into the generic C library */ } CORE; @@ -59,6 +61,7 @@ JNIEXPORT void JNICALL Java_vue_NativeVUE_construct // Produce and initialize s new core context CORE *core = calloc(sizeof (CORE), 1); vueInitialize(&core->vue); + vueReset(&core->vue); // Encode the context handle into a byte array jbyteArray pointer = (*env)->NewByteArray(env, sizeof (void *)); @@ -83,7 +86,7 @@ JNIEXPORT void JNICALL Java_vue_NativeVUE_dispose JNIEXPORT jint JNICALL Java_vue_NativeVUE_getRegister (JNIEnv *env, jobject vue, jint index, jboolean system) { CORE *core = GetCore(env, vue); - return vueGetRegister(&core->vue, index, system); + return vueGetRegister(&core->vue, index, system);; } // Retrieve a copy of the ROM data @@ -130,6 +133,13 @@ JNIEXPORT jboolean JNICALL Java_vue_NativeVUE_read return JNI_TRUE; } +// Initialize all system components +JNIEXPORT void JNICALL Java_vue_NativeVUE_reset + (JNIEnv *env, jobject vue) { + CORE *core = GetCore(env, vue); + vueReset(&core->vue); +} + // Specify a register value JNIEXPORT jint JNICALL Java_vue_NativeVUE_setRegister (JNIEnv *env, jobject vue, jint index, jboolean system, jint value) { diff --git a/src/desktop/vue/NativeVUE.java b/src/desktop/vue/NativeVUE.java index 960ddc3..3bfe2a8 100644 --- a/src/desktop/vue/NativeVUE.java +++ b/src/desktop/vue/NativeVUE.java @@ -39,6 +39,9 @@ class NativeVUE extends VUE { public native boolean read(int address, byte[] dest, int offset, int length); + // Initialize all system components + public native void reset(); + // Specify a register value public native int setRegister(int index, boolean system, int value); diff --git a/src/desktop/vue/VUE.java b/src/desktop/vue/VUE.java index 4c3065d..ae52705 100644 --- a/src/desktop/vue/VUE.java +++ b/src/desktop/vue/VUE.java @@ -32,6 +32,13 @@ public abstract class VUE { public static final int PSW = 5; public static final int TKCW = 7; + // Program register indexes + public static final int GP = 4; + public static final int HP = 2; + public static final int LP = 31; + public static final int SP = 3; + public static final int TP = 5; + /////////////////////////////////////////////////////////////////////////// @@ -81,6 +88,9 @@ public abstract class VUE { public abstract boolean read(int address, byte[] dest, int offset, int length); + // Initialize all system components + public abstract void reset(); + // Specify a register value public abstract int setRegister(int index, boolean system, int value);