diff --git a/.gitignore b/.gitignore index 7e20958..36fb924 100644 --- a/.gitignore +++ b/.gitignore @@ -4,36 +4,6 @@ out addons/out /.project *.class -tests/linker/factorial/factdemo.obj -tests/linker/factorial/linfac2_demo.obj -tests/linker/factorial/linfac_demo.obj -tests/linker/factorial/outfac.obj -tests/linker/multi/out.obj -tests/linker/nametest/extd.log -tests/linker/nametest/extd.lst -tests/linker/nametest/first.log -tests/linker/nametest/first.lst -tests/linker/nametest/nmtest.obj -tests/linker/nametest/test.log -tests/linker/nametest/test.lst -tests/linker/one/out.obj -tests/linker/partial/prtial.obj -tests/linker/stack/demostack.obj -tests/linker/factorial/ending.log -tests/linker/factorial/ending.lst -tests/linker/factorial/fact.log -tests/linker/factorial/fact.lst -tests/linker/factorial/linked.obj -tests/linker/factorial/main.log -tests/linker/factorial/main.lst -tests/linker/factorial/print.log -tests/linker/factorial/print.lst -tests/linker/factorial/print2.asm -tests/linker/factorial/print2.log -tests/linker/factorial/print2.lst -tests/linker/factorial/prt2fc.obj -tests/linker/factorial/stack.log -tests/linker/factorial/stack.lst -tests/linker/one2/linked.obj -tests/linker/one2/prog.log -tests/linker/one2/prog.lst +tests/**/*.log +tests/**/*.lst +tests/**/*.obj diff --git a/src/sic/asm/parsing/OperandParser.java b/src/sic/asm/parsing/OperandParser.java index fcef247..0b9ea94 100644 --- a/src/sic/asm/parsing/OperandParser.java +++ b/src/sic/asm/parsing/OperandParser.java @@ -97,7 +97,7 @@ private Command parseF1(Location loc, String label, Mnemonic mnemonic) { } private Command parseF2n(Location loc, String label, Mnemonic mnemonic) throws AsmError { - int n = parser.readInt(0, 15); + int n = parser.readInt(0, 255); return new InstructionF2n(loc, label, mnemonic, n); } diff --git a/src/sic/ast/instructions/InstructionF2n.java b/src/sic/ast/instructions/InstructionF2n.java index 5a5d2c8..a34556d 100644 --- a/src/sic/ast/instructions/InstructionF2n.java +++ b/src/sic/ast/instructions/InstructionF2n.java @@ -25,7 +25,8 @@ public String operandToString() { @Override public void emitRawCode(byte[] data, int loc) { - emitRawCode(data, loc, number, 0); + // The original "specification" is a bit unclear about the size of number. + emitRawCode(data, loc, (number>>4)&0xf, number&0xf); } @Override diff --git a/src/sic/common/Opcode.java b/src/sic/common/Opcode.java index 31ef50b..2008832 100644 --- a/src/sic/common/Opcode.java +++ b/src/sic/common/Opcode.java @@ -104,6 +104,41 @@ public class Opcode { "TD", null, "STSW", "SSK", "SIO", "HIO", "TIO", null }; + public static boolean isF1(int opcode) { + switch (opcode) { + case FLOAT, FIX, NORM, SIO, HIO, TIO: + return true; + } + return false; + } + + public static boolean isF2(int opcode) { + switch (opcode) { + case ADDR, SUBR, MULR, DIVR, COMPR, SHIFTL, SHIFTR, RMO, CLEAR, TIXR, SVC: + return true; + } + return false; + } + + public static boolean isF34(int opcode) { + switch (opcode) { + case STA, STX, STL, STCH, STB, STS, STF, STT, STSW, JEQ, JGT, JLT, J, + RSUB, JSUB, LDA, LDX, LDL, LDCH, LDB, LDS, LDF, LDT, ADD, SUB, MUL, + DIV, AND, OR, COMP, TIX, RD, WD, TD, ADDF, SUBF, MULF, DIVF, COMPF, + LPS, STI, SSK: + return true; + } + return false; + } + + public static boolean isPrivileged(int opcode) { + switch (opcode) { + case HIO, LPS, RD, SIO, SSK, STI, STSW, TD, TIO, WD: + return true; + } + return false; + } + public static String getName(int opcode) { // 0 <= opcode <= 256 return opcodeToNames[opcode >> 2]; diff --git a/src/sic/disasm/Disassembler.java b/src/sic/disasm/Disassembler.java index db216ab..001a9d4 100644 --- a/src/sic/disasm/Disassembler.java +++ b/src/sic/disasm/Disassembler.java @@ -63,7 +63,7 @@ public void prev(int count) { } private int fetchAddr; - protected int fetch() { + protected int fetchByte() { if (fetchAddr < 0) return 0; if (fetchAddr > SICXE.MAX_ADDR) return 0; return machine.memory.getByteRaw(fetchAddr++); @@ -71,7 +71,7 @@ protected int fetch() { public Instruction disassemble(int addr) { this.fetchAddr = addr; - int opcode = fetch(); + int opcode = fetchByte(); String name = Opcode.getName(opcode & 0xFC); if (name == null) return null; Mnemonic mnemonic = mnemonics.get(name); @@ -81,24 +81,24 @@ public Instruction disassemble(int addr) { case F1: return new InstructionF1(loc, "", mnemonic); case F2n: - return new InstructionF2n(loc, "", mnemonic, fetch() >> 4); + return new InstructionF2n(loc, "", mnemonic, fetchByte() >> 4); case F2r: - return new InstructionF2r(loc, "", mnemonic, fetch() >> 4); + return new InstructionF2r(loc, "", mnemonic, fetchByte() >> 4); case F2rn: - b1 = fetch(); + b1 = fetchByte(); return new InstructionF2rn(loc, "", mnemonic, (b1 & 0xF0) >> 4, (b1 & 0x0F) + 1); case F2rr: - b1 = fetch(); + b1 = fetchByte(); return new InstructionF2rr(loc, "", mnemonic, (b1 & 0xF0) >> 4, b1 & 0x0F); case F3: - fetch(); fetch(); // should be zero? + fetchByte(); fetchByte(); // should be zero? return new InstructionF3(loc, "", mnemonic); case F3m: case F4m: - b1 = fetch(); b2 = fetch(); + b1 = fetchByte(); b2 = fetchByte(); Flags flags = new Flags(opcode, b1); if (flags.isExtended()) { - int operand = flags.operandF4(b1, b2, fetch()); + int operand = flags.operandF4(b1, b2, fetchByte()); mnemonic = mnemonics.get("+" + name); return new InstructionF4m(loc, "", mnemonic, flags, operand, null); } @@ -138,4 +138,4 @@ public int getNextPCLocation() { return getLocationAfter(machine.registers.getPC()); } -} \ No newline at end of file +} diff --git a/src/sic/sim/Executor.java b/src/sic/sim/Executor.java index 83f9771..45d2cde 100644 --- a/src/sic/sim/Executor.java +++ b/src/sic/sim/Executor.java @@ -4,6 +4,7 @@ import sic.sim.breakpoints.DataBreakpointException; import sic.sim.breakpoints.DataBreakpoints; import sic.sim.vm.Machine; +import sic.sim.vm.Interrupt; import java.awt.event.ActionListener; import java.util.Timer; @@ -67,7 +68,7 @@ private void timerTickUntil(Predicate stopPredicate) { int oldPC = machine.registers.getPC(); try { - machine.execute(); + machine.step(); if (!dataBreakpoints.isEnabled()) { // Enable data breakpoints in case they got disabled because they were triggered. @@ -83,7 +84,9 @@ private void timerTickUntil(Predicate stopPredicate) { hasChanged = true; // check if the same instruction: halt J halt - if (oldPC == machine.registers.getPC()) { + if (oldPC == machine.registers.getPC() + && !machine.registers.intEnabled(Interrupt.IntClass.TIMER) + && !machine.registers.intEnabled(Interrupt.IntClass.IO)) { stop(); if (printStats) { System.out.printf("Instructions executed: %d\n", machine.getInstructionCount()); @@ -137,7 +140,7 @@ public void step() { dataBreakpoints.disable(); try { - machine.execute(); + machine.step(); } catch (DataBreakpointException ex) { // Shouldn't be triggered when breakpoints are disabled } @@ -170,7 +173,7 @@ public void runToAddress(int stopAddress) { * Step out of the current sub procedure */ public void stepOut() { - Integer addressAfterLastJSUB = machine.getAddressBelowLastJSUB(); + Integer addressAfterLastJSUB = machine.getReturnAddress(); if (addressAfterLastJSUB == null) return; runUntil(machine -> machine.registers.getPC() == addressAfterLastJSUB); } diff --git a/src/sic/sim/views/CPUView.java b/src/sic/sim/views/CPUView.java index 5ddfcbe..98ad7da 100644 --- a/src/sic/sim/views/CPUView.java +++ b/src/sic/sim/views/CPUView.java @@ -6,6 +6,7 @@ import sic.disasm.Disassembler; import sic.sim.Executor; import sic.sim.Colors; +import sic.sim.vm.Interrupt; import sic.sim.vm.Machine; import sic.sim.vm.Registers; @@ -43,10 +44,12 @@ public class CPUView { private JTextField regFF; private JTextField regPC; private JTextField txtInstruction; + private JTextField timer; private JButton btnStep; private JButton btnStartStop; public JPanel mainPanel; private JLabel lblInfo; + private JLabel lblInfoSW; public CPUView(final Executor executor, final Disassembler disassembler) { this.executor = executor; @@ -121,9 +124,33 @@ public void actionPerformed(ActionEvent evt) { updateView(); } }); + timer.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + registers.setTimer(Integer.parseInt(timer.getText())); + updateView(); + } + }); + regF.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + registers.setF(SICXE.bitsToFloat(Long.parseLong(regF.getText(), 16))); + updateView(); + } + }); + regFF.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + registers.setF(Double.parseDouble(regFF.getText())); + updateView(); + } + }); updateView(); } + private void updateTimer(int val) { + String str = String.format("%d", val); + timer.setForeground(registers.intEnabled(Interrupt.IntClass.TIMER) ? Colors.fg : Colors.selectionInactiveFg); + timer.setText(str); + } + private void updateRegWord(JTextField txt, int val) { String str = Conversion.wordToHex(val); if (txt.getText().equals(str)) @@ -162,218 +189,265 @@ public void updateView() { updateRegWord(regSW, registers.getSW()); updateRegFloat(registers.getF()); updateRegWord(regPC, registers.getPC()); + updateTimer(registers.getTimer()); // btnStartStop.setText(executor.isRunning() ? "Stop" : "Start"); // Command cmd = disassembler.disassemble(registers.getPC()); txtInstruction.setText(cmd == null ? "" : cmd.toString()); lblInfo.setText(cmd == null ? "" : "" + cmd.explain() + ""); + lblInfoSW.setText(cmd == null ? "" : "" + registers.explainSW() + ""); } { -// GUI initializer generated by IntelliJ IDEA GUI Designer -// >>> IMPORTANT!! <<< -// DO NOT EDIT OR ADD ANY CODE HERE! - $$$setupUI$$$(); + setupUI(); } - /** - * Method generated by IntelliJ IDEA GUI Designer - * >>> IMPORTANT!! <<< - * DO NOT edit this method OR call it in your code! - * - * @noinspection ALL - */ - private void $$$setupUI$$$() { + private void setupUI() { + GridBagConstraints gbc; + mainPanel = new JPanel(); - mainPanel.setLayout(new BorderLayout(0, 0)); + mainPanel.setLayout(new GridBagLayout()); mainPanel.setEnabled(true); mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLoweredBevelBorder(), "CPU", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); - final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridBagLayout()); - panel1.setAlignmentX(0.5f); - panel1.setAutoscrolls(false); - panel1.setEnabled(true); - mainPanel.add(panel1, BorderLayout.CENTER); - final JLabel label1 = new JLabel(); - label1.setText("X"); - GridBagConstraints gbc; + + // register A + final JLabel labelA = new JLabel(); + labelA.setText("A"); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(labelA, gbc); + + regA = new JTextField(); + regA.setColumns(8); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + mainPanel.add(regA, gbc); + + // register x + final JLabel labelX = new JLabel(); + labelX.setText("X"); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label1, gbc); + mainPanel.add(labelX, gbc); + regX = new JTextField(); regX.setColumns(8); - regX.setText("000002"); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regX, gbc); - final JLabel label2 = new JLabel(); - label2.setText("L"); + mainPanel.add(regX, gbc); + + // register L + final JLabel labelL = new JLabel(); + labelL.setText("L"); gbc = new GridBagConstraints(); gbc.gridx = 4; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label2, gbc); + mainPanel.add(labelL, gbc); + regL = new JTextField(); regL.setColumns(8); - regL.setText("000003"); gbc = new GridBagConstraints(); gbc.gridx = 5; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regL, gbc); - regS = new JTextField(); - regS.setText("000004"); + mainPanel.add(regL, gbc); + + // register S + final JLabel labelS = new JLabel(); + labelS.setText("S"); gbc = new GridBagConstraints(); - gbc.gridx = 1; + gbc.gridx = 0; gbc.gridy = 1; - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regS, gbc); - regA = new JTextField(); - regA.setColumns(8); - regA.setText("000001"); + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(labelS, gbc); + + regS = new JTextField(); gbc = new GridBagConstraints(); gbc.gridx = 1; - gbc.gridy = 0; + gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regA, gbc); - final JLabel label3 = new JLabel(); - label3.setText("S"); - gbc = new GridBagConstraints(); - gbc.gridx = 0; - gbc.gridy = 1; - gbc.anchor = GridBagConstraints.EAST; - panel1.add(label3, gbc); - final JLabel label4 = new JLabel(); - label4.setText("A"); - gbc = new GridBagConstraints(); - gbc.gridx = 0; - gbc.gridy = 0; - gbc.anchor = GridBagConstraints.EAST; - panel1.add(label4, gbc); - final JLabel label5 = new JLabel(); - label5.setText("T"); + mainPanel.add(regS, gbc); + + // register T + final JLabel labelT = new JLabel(); + labelT.setText("T"); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label5, gbc); + mainPanel.add(labelT, gbc); + regT = new JTextField(); - regT.setText("000005"); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regT, gbc); - final JLabel label6 = new JLabel(); - label6.setText("B"); + mainPanel.add(regT, gbc); + + // register B + final JLabel labelB = new JLabel(); + labelB.setText("B"); gbc = new GridBagConstraints(); gbc.gridx = 4; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label6, gbc); + mainPanel.add(labelB, gbc); + regB = new JTextField(); - regB.setText("000006"); gbc = new GridBagConstraints(); gbc.gridx = 5; gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regB, gbc); - final JLabel label7 = new JLabel(); - label7.setText("SW"); + mainPanel.add(regB, gbc); + + // timer + final JLabel labelTimer = new JLabel(); + labelTimer.setText("TIM"); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label7, gbc); - regSW = new JTextField(); - regSW.setText("000007"); + mainPanel.add(labelTimer, gbc); + + timer = new JTextField(); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 2; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regSW, gbc); - final JLabel label8 = new JLabel(); - label8.setText("F"); + mainPanel.add(timer, gbc); + + // register F + final JLabel labelF = new JLabel(); + labelF.setText("F"); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 2; gbc.anchor = GridBagConstraints.WEST; - panel1.add(label8, gbc); + mainPanel.add(labelF, gbc); + regF = new JTextField(); regF.setColumns(8); - regF.setText("000000000008"); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 2; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regF, gbc); + mainPanel.add(regF, gbc); + regFF = new JTextField(); - regFF.setText("0.0"); gbc = new GridBagConstraints(); gbc.gridx = 4; gbc.gridy = 2; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regFF, gbc); - final JLabel label9 = new JLabel(); - label9.setText("PC"); + mainPanel.add(regFF, gbc); + + // register SW + final JLabel labelSW = new JLabel(); + labelSW.setText("SW"); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 3; gbc.anchor = GridBagConstraints.EAST; - panel1.add(label9, gbc); - regPC = new JTextField(); - regPC.setText("000009"); + mainPanel.add(labelSW, gbc); + + regSW = new JTextField(); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 3; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(regPC, gbc); + mainPanel.add(regSW, gbc); + + // SW explanation + lblInfoSW = new JLabel(); + Font lblInfoFont = this.$$$getFont$$$("Courier", -1, 12, lblInfoSW.getFont()); + if (lblInfoFont != null) lblInfoSW.setFont(lblInfoFont); + lblInfoSW.putClientProperty("html.disable", Boolean.FALSE); + lblInfoSW.setToolTipText("MASK shows which interrupts are enabled:
" + +"SVC, PROGRAM, TIMER or IO
" + +"CC: 1 (LT), 2 (GT) or 0 (EQ)
" + +"IDLE: Idle or Running
" + +"MODE: User or Supervisor"); + gbc = new GridBagConstraints(); + gbc.gridx = 3; + gbc.gridy = 3; + gbc.gridwidth = 3; + gbc.gridheight = 1; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(8, 0, 0, 0); + mainPanel.add(lblInfoSW, gbc); + + // register PC + final JLabel labelPC = new JLabel(); + labelPC.setText("PC"); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(labelPC, gbc); + regPC = new JTextField(); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + mainPanel.add(regPC, gbc); + + // disassembled instruction txtInstruction = new JTextField(); - txtInstruction.setText("LDA 0"); txtInstruction.putClientProperty("html.disable", Boolean.FALSE); gbc = new GridBagConstraints(); gbc.gridx = 2; - gbc.gridy = 3; + gbc.gridy = 4; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(txtInstruction, gbc); + mainPanel.add(txtInstruction, gbc); + + // start/stop button btnStartStop = new JButton(); btnStartStop.setText("Start"); btnStartStop.setMnemonic('S'); btnStartStop.setDisplayedMnemonicIndex(0); gbc = new GridBagConstraints(); gbc.gridx = 1; - gbc.gridy = 4; + gbc.gridy = 5; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(btnStartStop, gbc); + mainPanel.add(btnStartStop, gbc); + + // step button btnStep = new JButton(); btnStep.setText("Step"); btnStep.setMnemonic('T'); btnStep.setDisplayedMnemonicIndex(1); gbc = new GridBagConstraints(); gbc.gridx = 1; - gbc.gridy = 5; + gbc.gridy = 6; gbc.fill = GridBagConstraints.HORIZONTAL; - panel1.add(btnStep, gbc); + mainPanel.add(btnStep, gbc); + + // instruction explanation lblInfo = new JLabel(); - Font lblInfoFont = this.$$$getFont$$$("Courier", -1, 12, lblInfo.getFont()); + lblInfoFont = this.$$$getFont$$$("Courier", -1, 12, lblInfo.getFont()); if (lblInfoFont != null) lblInfo.setFont(lblInfoFont); lblInfo.setPreferredSize(new Dimension(280, 120)); lblInfo.setText(""); @@ -382,27 +456,33 @@ public void updateView() { lblInfo.putClientProperty("html.disable", Boolean.FALSE); gbc = new GridBagConstraints(); gbc.gridx = 3; - gbc.gridy = 4; + gbc.gridy = 5; gbc.gridwidth = 3; gbc.gridheight = 3; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(8, 0, 0, 0); - panel1.add(lblInfo, gbc); + mainPanel.add(lblInfo, gbc); + + + // spacer final JPanel spacer1 = new JPanel(); gbc = new GridBagConstraints(); gbc.gridx = 1; - gbc.gridy = 6; + gbc.gridy = 7; gbc.fill = GridBagConstraints.VERTICAL; - panel1.add(spacer1, gbc); - label1.setLabelFor(regX); - label2.setLabelFor(regL); - label3.setLabelFor(regS); - label4.setLabelFor(regA); - label5.setLabelFor(regT); - label6.setLabelFor(regB); - label7.setLabelFor(regSW); - label8.setLabelFor(regF); - label9.setLabelFor(regPC); + mainPanel.add(spacer1, gbc); + + // labels + labelX.setLabelFor(regX); + labelL.setLabelFor(regL); + labelS.setLabelFor(regS); + labelA.setLabelFor(regA); + labelT.setLabelFor(regT); + labelB.setLabelFor(regB); + labelSW.setLabelFor(regSW); + labelF.setLabelFor(regF); + labelPC.setLabelFor(regPC); + labelTimer.setLabelFor(timer); } /** diff --git a/src/sic/sim/views/components/treetable/JTreeTable.java b/src/sic/sim/views/components/treetable/JTreeTable.java index 3e01baa..43f1c39 100644 --- a/src/sic/sim/views/components/treetable/JTreeTable.java +++ b/src/sic/sim/views/components/treetable/JTreeTable.java @@ -268,7 +268,7 @@ public boolean isCellEditable(EventObject e) { if (getColumnClass(counter) == TreeTableModel.class) { MouseEvent me = (MouseEvent)e; MouseEvent newME = new MouseEvent(tree, me.getID(), - me.getWhen(), me.getModifiers(), + me.getWhen(), me.getModifiersEx(), me.getX() - getCellRect(0, counter, true).x, me.getY(), me.getClickCount(), me.isPopupTrigger()); diff --git a/src/sic/sim/vm/Interrupt.java b/src/sic/sim/vm/Interrupt.java new file mode 100644 index 0000000..c80d979 --- /dev/null +++ b/src/sic/sim/vm/Interrupt.java @@ -0,0 +1,86 @@ +package sic.sim.vm; + +import sic.sim.breakpoints.ReadDataBreakpointException; +import sic.sim.breakpoints.WriteDataBreakpointException; + +public class Interrupt { + public enum IntClass { + SVC(8), + PROGRAM(4), + TIMER(2), + IO(1); + + public final int value; + + IntClass(int value) { + this.value = value; + } + } + + public enum ProgramIntCode { + ILLEGAL_INSTRUCTION(0x00), + PRIVILEGED_INSTRUCTION(0x01), + ADDR_OUT_OF_RANGE(0x02), + MEM_PROTECT(0x03), + OVERFLOW(0x04), + PAGE_FAULT(0x10), + SEG_FAULT(0x11), + SEG_PROTECTION_VIOLATION(0x12), + SEG_LEN_EXCEEDED(0x13); + + public final int value; + + ProgramIntCode(int value) { + this.value = value; + } + } + + public IntClass intClass; + private int intCode = 0; + + public Interrupt(IntClass intClass) { + this.intClass = intClass; + } + + public Interrupt(IntClass intClass, int intCode) { + this.intClass = intClass; + this.intCode = intCode; + } + + public Interrupt(IntClass intClass, ProgramIntCode intCode) { + this.intClass = intClass; + this.intCode = intCode.value; + } + + public int getWorkArea() { + switch (intClass) { + case SVC: + return 0x100; + case PROGRAM: + return 0x130; + case TIMER: + return 0x160; + case IO: + return 0x190; + } + return -1; + } + + public void trigger(Registers registers, Memory memory) throws ReadDataBreakpointException, WriteDataBreakpointException { + int addr = getWorkArea(); + + memory.setWordRaw(addr+6, registers.getSW()); + memory.setWordRaw(addr+9, registers.getPC()); + memory.setWordRaw(addr+12, registers.getA()); + memory.setWordRaw(addr+15, registers.getX()); + memory.setWordRaw(addr+18, registers.getL()); + memory.setWordRaw(addr+21, registers.getB()); + memory.setWordRaw(addr+24, registers.getS()); + memory.setWordRaw(addr+27, registers.getT()); + memory.setFloatRaw(addr+30, registers.getF()); + + registers.setSW(memory.getWordRaw(addr)); + registers.setPC(memory.getWordRaw(addr+3)); + registers.setIntCode(intCode); + } +} diff --git a/src/sic/sim/vm/Machine.java b/src/sic/sim/vm/Machine.java index 2d923dd..8f2e8d8 100644 --- a/src/sic/sim/vm/Machine.java +++ b/src/sic/sim/vm/Machine.java @@ -11,41 +11,29 @@ * @author jure */ public class Machine { - + // ************ Constants public static final int MAX_ADDRESS = (1 << 20) - 1; // 1048576 - 1 public static final int MAX_DEVICE = 255; // ************ Machine parts + public final Registers registers = new Registers(); + public final Memory memory = new Memory(MAX_ADDRESS+1); + public final Devices devices = new Devices(MAX_DEVICE+1); - public final Registers registers; - public final Memory memory; - public final Devices devices; - - // ************ Statistics - - private int instructionCount; - private MemorySpan lastExecAddr; - private MemorySpan lastExecRead; - private MemorySpan lastExecWrite; - - - private Stack addressBelowJSUB = new Stack<>(); - - private boolean indirectX = false; + private Interrupt svcInt = null; + private Interrupt programInt = null; + private Interrupt timerInt = null; + private Interrupt ioInt = null; - // ************ Constructor + private Stack callStack = new Stack<>(); - public Machine() { - this.registers = new Registers(); - this.memory = new Memory(MAX_ADDRESS+1); - this.devices = new Devices(MAX_DEVICE+1); - this.lastExecRead = new MemorySpan(); - this.lastExecWrite = new MemorySpan(); - this.lastExecAddr = new MemorySpan(); - } + // ************ Statistics + private int instructionCount = 0; + private MemorySpan lastExecAddr = new MemorySpan(); + private MemorySpan lastExecRead = new MemorySpan(); + private MemorySpan lastExecWrite = new MemorySpan(); // ************ getters/setters - public int getInstructionCount() { return instructionCount; } @@ -78,275 +66,477 @@ public void clearLastExecReadWrite() { lastExecAddr.clear(); } + /** + * Get the address on the top of the call stack, so we can step out. + * @return null if no item on stack - no JSUB encountered, otherwise last address. + */ + public Integer getReturnAddress() { + if (callStack.isEmpty()) { + return null; + } + return callStack.peek(); + } + // ********** Execution ********************* + // ********** Instruction types ********************* + private abstract class Instruction { + protected int opcode; - private void notImplemented(String mnemonic) { - Logger.fmterr("Instruction '%s' not implemented!", mnemonic); - } + abstract public void execute() throws DataBreakpointException; - private void invalidOpcode(int opcode) { - Logger.fmterr("Invalid opcode '%d'.", opcode); + public boolean isPrivileged() { + return Opcode.isPrivileged(opcode); + } } - private void invalidAddressing() { - Logger.err("Invalid addressing."); + private class InstructionF1 extends Instruction { + public InstructionF1(int opcode) { + this.opcode = opcode; + } + + @Override + public void execute() throws DataBreakpointException { + switch (opcode) { + case Opcode.FLOAT: + registers.setF((double) registers.getAs()); break; + case Opcode.FIX: + registers.setA((int) registers.getF()); break; + case Opcode.NORM: + notImplemented("NORM"); break; + case Opcode.SIO: + notImplemented("SIO"); break; + case Opcode.HIO: + notImplemented("HIO"); break; + case Opcode.TIO: + notImplemented("TIO"); break; + } + } } - private boolean execF1(int opcode) { - // Format 1: no operand - switch (opcode) { - case Opcode.FLOAT: registers.setF((double) registers.getAs()); break; - case Opcode.FIX: registers.setA((int) registers.getF()); break; - case Opcode.NORM: notImplemented("NORM"); break; - case Opcode.SIO: notImplemented("SIO"); break; - case Opcode.HIO: notImplemented("HIO"); break; - case Opcode.TIO: notImplemented("TIO"); break; - default: return false; + private class InstructionF2 extends Instruction { + private int operand; + + public InstructionF2(int opcode, int operand) { + this.opcode = opcode; + this.operand = operand; + } + + @Override + public void execute() throws DataBreakpointException { + // Format 2: OP o1, o2 - two 4-bit operands + int o1 = (operand & 0xF0) >> 4; + int o2 = (operand & 0x0F); + switch (opcode) { + case Opcode.ADDR: + registers.set(o2, registers.get(o2) + registers.get(o1)); break; + case Opcode.SUBR: + registers.set(o2, registers.get(o2) - registers.get(o1)); break; + case Opcode.MULR: + registers.set(o2, registers.get(o2) * registers.get(o1)); break; + case Opcode.DIVR: + int divisor = registers.get(o1); + if (divisor == 0) { + divisionByZero(); + } else { + registers.set(o2, registers.gets(o2) / divisor); + } + break; + case Opcode.COMPR: + registers.setCC(registers.gets(o1) - registers.gets(o2)); break; + case Opcode.SHIFTL: + registers.set(o1, registers.get(o1) << (o2 + 1) | registers.get(o1) >> (24 - o2 - 1)); break; + case Opcode.SHIFTR: + registers.set(o1, registers.gets(o1) >> (o2 + 1)); break; + case Opcode.RMO: + registers.set(o2, registers.get(o1)); break; + case Opcode.CLEAR: + registers.set(o1, 0); break; + case Opcode.TIXR: + registers.setX(registers.getX()+1); + registers.setCC(registers.getXs() - registers.gets(o1)); + break; + case Opcode.SVC: + if (!registers.intEnabled(Interrupt.IntClass.SVC)) { + Logger.fmterr("SVC is disabled"); + break; + } + svcInt = new Interrupt(Interrupt.IntClass.SVC, operand); + break; + } } - return true; } - private boolean execF2(int opcode, int operand) { - // Format 2: OP o1, o2 - two 4-bit operands - int o1 = (operand & 0xF0) >> 4; - int o2 = (operand & 0x0F); - switch (opcode) { - case Opcode.ADDR: registers.set(o2, registers.get(o2) + registers.get(o1)); break; - case Opcode.SUBR: registers.set(o2, registers.get(o2) - registers.get(o1)); break; - case Opcode.MULR: registers.set(o2, registers.get(o2) * registers.get(o1)); break; - case Opcode.DIVR: - int divisor = registers.get(o1); + private class InstructionSICF3F4 extends Instruction { + private Flags flags; + private int operand; + + public InstructionSICF3F4(int opcode, Flags flags, int operand) { + this.opcode = opcode; + this.flags = flags; + this.operand = operand; + } + + @Override + public void execute() throws DataBreakpointException { + // Formats: SIC, F3, F4 + switch (opcode) { + // ***** immediate addressing not possible ***** + // stores + case Opcode.STA: + storeWord(flags, operand, registers.getA()); break; + case Opcode.STX: + storeWord(flags, operand, registers.getX()); break; + case Opcode.STL: + storeWord(flags, operand, registers.getL()); break; + case Opcode.STCH: + storeByte(flags, operand, registers.getA()); break; + case Opcode.STB: + storeWord(flags, operand, registers.getB()); break; + case Opcode.STS: + storeWord(flags, operand, registers.getS()); break; + case Opcode.STF: + storeFloat(flags, operand, registers.getF()); break; + case Opcode.STT: + storeWord(flags, operand, registers.getT()); break; + case Opcode.STSW: + storeWord(flags, operand, registers.getSW()); break; + + // jumps + case Opcode.JEQ: + if (registers.ccIsEqual()) { + registers.setPC(resolveAddr(flags, operand)); + }; + break; + case Opcode.JGT: + if (registers.ccIsGreater()) { + registers.setPC(resolveAddr(flags, operand)); + }; + break; + case Opcode.JLT: + if (registers.ccIsLower()) { + registers.setPC(resolveAddr(flags, operand)); + }; + break; + case Opcode.J: + registers.setPC(resolveAddr(flags, operand)); break; + case Opcode.RSUB: + registers.setPC(registers.getL()); + callStack.pop(); + break; + case Opcode.JSUB: + registers.setL(registers.getPC()); + callStack.push(registers.getPC()); + registers.setPC(resolveAddr(flags, operand)); + break; + // ***** immediate addressing possible ***** + + // loads + case Opcode.LDA: + registers.setA(loadWord(flags, operand)); break; + case Opcode.LDX: + registers.setX(loadWord(flags, operand)); break; + case Opcode.LDL: + registers.setL(loadWord(flags, operand)); break; + case Opcode.LDCH: + registers.setALo(loadByte(flags, operand)); break; + case Opcode.LDB: + registers.setB(loadWord(flags, operand)); break; + case Opcode.LDS: + registers.setS(loadWord(flags, operand)); break; + case Opcode.LDF: + registers.setF(loadFloat(flags, operand)); break; + case Opcode.LDT: + registers.setT(loadWord(flags, operand)); break; + + // arithmetic + case Opcode.ADD: + registers.setA(registers.getA() + loadWord(flags, operand)); break; + case Opcode.SUB: + registers.setA(registers.getA() - loadWord(flags, operand)); break; + case Opcode.MUL: + registers.setA(registers.getA() * loadWord(flags, operand)); break; + case Opcode.DIV: + int divisor = SICXE.swordToInt(loadWord(flags, operand)); if (divisor == 0) { - System.out.println("division by zero"); + divisionByZero(); } else { - registers.set(o2, registers.gets(o2) / divisor); + registers.setA(registers.getAs() / divisor); } break; - case Opcode.COMPR: registers.setSWAfterCompare(registers.gets(o1) - registers.gets(o2)); break; - case Opcode.SHIFTL: registers.set(o1, registers.get(o1) << (o2 + 1) | registers.get(o1) >> (24 - o2 - 1)); break; - case Opcode.SHIFTR: registers.set(o1, registers.gets(o1) >> (o2 + 1)); break; - case Opcode.RMO: registers.set(o2, registers.get(o1)); break; - case Opcode.CLEAR: registers.set(o1, 0); break; - case Opcode.TIXR: registers.setX(registers.getX()+1); - registers.setSWAfterCompare(registers.getXs() - registers.gets(o1)); - break; - case Opcode.SVC: notImplemented("SVC"); break; - default: return false; + case Opcode.AND: + registers.setA(registers.getA() & loadWord(flags, operand)); break; + case Opcode.OR: + registers.setA(registers.getA() | loadWord(flags, operand)); break; + case Opcode.COMP: + registers.setCC(registers.getAs() - SICXE.swordToInt(loadWord(flags, operand))); break; + case Opcode.TIX: + registers.setX(registers.getX() + 1); + registers.setCC(registers.getXs() - SICXE.swordToInt(loadWord(flags, operand))); + break; + + // input/output + case Opcode.RD: + registers.setALo(devices.read(loadByte(flags, operand))); break; + case Opcode.WD: + devices.write(loadByte(flags, operand), registers.getALo()); break; + case Opcode.TD: + registers.setCC(devices.test(loadByte(flags, operand)) ? -1 : 0); break; + + // floating point arithmetic + case Opcode.ADDF: + registers.setF(registers.getF() + loadFloat(flags, operand)); break; + case Opcode.SUBF: + registers.setF(registers.getF() - loadFloat(flags, operand)); break; + case Opcode.MULF: + registers.setF(registers.getF() * loadFloat(flags, operand)); break; + case Opcode.DIVF: + registers.setF(registers.getF() / loadFloat(flags, operand)); break; + case Opcode.COMPF: + double sub = registers.getF() - loadFloat(flags, operand); + registers.setCC(sub > 0 ? 1 : (sub < 0 ? -1 : 0)); + break; + + // others + case Opcode.LPS: + lps(operand); break; + case Opcode.STI: + registers.setTimer(loadWord(flags, operand)); break; + case Opcode.SSK: + notImplemented("SSK"); break; + } } - return true; - } - // load + private int loadWord(Flags flags, int operand) throws ReadDataBreakpointException { + if (flags.isImmediate()) return operand; + int addr = resolveAddr(flags, operand); + setLastExecRead(addr, 3); + return memory.getWord(addr); + } - - private int loadWord(Flags flags, int operand) throws ReadDataBreakpointException { - if (flags.isImmediate()) return operand; - int addr = resolveAddr(flags, operand); - setLastExecRead(addr, 3); - return memory.getWord(addr); - } + private int loadByte(Flags flags, int operand) throws ReadDataBreakpointException { + if (flags.isImmediate()) return operand; + int addr = resolveAddr(flags, operand); + setLastExecRead(addr, 1); + return memory.getByte(addr); + } - private int loadByte(Flags flags, int operand) throws ReadDataBreakpointException { - if (flags.isImmediate()) return operand; - int addr = resolveAddr(flags, operand); - setLastExecRead(addr, 1); - return memory.getByte(addr); - } + private double loadFloat(Flags flags, int operand) throws ReadDataBreakpointException { + if (flags.isImmediate()) return operand; + int addr = resolveAddr(flags, operand); + setLastExecRead(addr, 6); + return memory.getFloat(addr); + } - private double loadFloat(Flags flags, int operand) throws ReadDataBreakpointException { - if (flags.isImmediate()) return operand; - int addr = resolveAddr(flags, operand); - setLastExecRead(addr, 6); - return memory.getFloat(addr); - } + private void storeWord(Flags flags, int operand, int word) throws WriteDataBreakpointException { + int addr = resolveAddr(flags, operand); + setLastExecWrite(addr, 3); + memory.setWord(addr, word); + } + + private void storeByte(Flags flags, int operand, int _byte) throws WriteDataBreakpointException { + int addr = resolveAddr(flags, operand); + setLastExecWrite(addr, 1); + memory.setByte(addr, _byte); + } + + private void storeFloat(Flags flags, int operand, double _float) throws WriteDataBreakpointException { + int addr = resolveAddr(flags, operand); + setLastExecWrite(addr, 6); + memory.setFloat(addr, _float); + } + + // use of TA for store: addr / addr of addr + private int resolveAddr(Flags flags, int addr) { + if (flags.isIndirect()) { + addr = memory.getWordRaw(addr); + // SicTools extension + if (flags.isIndexed()) + addr += registers.getXs(); + } + return addr; + } + + private void lps(int addr) throws ReadDataBreakpointException { + registers.setSW(memory.getWordRaw(addr+6)); + registers.setPC(memory.getWordRaw(addr+9)); + registers.setA(memory.getWordRaw(addr+12)); + registers.setX(memory.getWordRaw(addr+15)); + registers.setL(memory.getWordRaw(addr+18)); + registers.setB(memory.getWordRaw(addr+21)); + registers.setS(memory.getWordRaw(addr+24)); + registers.setT(memory.getWordRaw(addr+27)); + registers.setF(memory.getFloatRaw(addr+30)); + } - // use of TA for store: addr / addr of addr - private int resolveAddr(Flags flags, int addr) { - if (flags.isIndirect()) { - addr = memory.getWordRaw(addr); - if (indirectX) - addr += registers.getXs(); - } - return addr; } - private void storeWord(Flags flags, int operand, int word) throws WriteDataBreakpointException { - int addr = resolveAddr(flags, operand); - setLastExecWrite(addr, 3); - memory.setWord(addr, word); + private class InvalidInstruction extends Instruction { + public InvalidInstruction(int opcode) { + this.opcode = opcode; + } + + @Override + public void execute() throws DataBreakpointException { + if ( registers.intEnabled(Interrupt.IntClass.PROGRAM)) { + programInt = new Interrupt(Interrupt.IntClass.PROGRAM, + Interrupt.ProgramIntCode.ILLEGAL_INSTRUCTION); + } else { + Logger.fmterr("Invalid opcode '%d'.", opcode); + } + } + + @Override + public boolean isPrivileged() { + return false; + } } - private void storeByte(Flags flags, int operand, int _byte) throws WriteDataBreakpointException { - int addr = resolveAddr(flags, operand); - setLastExecWrite(addr, 1); - memory.setByte(addr, _byte); + // ********** Utility functions for fetch and execute ***************** + private void notImplemented(String mnemonic) { + Logger.fmterr("Instruction '%s' not implemented!", mnemonic); } - private void storeFloat(Flags flags, int operand, double _float) throws WriteDataBreakpointException { - int addr = resolveAddr(flags, operand); - setLastExecWrite(addr, 6); - memory.setFloat(addr, _float); + private void invalidAddressing() { + if ( registers.intEnabled(Interrupt.IntClass.PROGRAM)) { + programInt = new Interrupt(Interrupt.IntClass.PROGRAM, + Interrupt.ProgramIntCode.ILLEGAL_INSTRUCTION); + } else { + Logger.err("Invalid addressing."); + } } - private boolean execSICF3F4(int opcode, Flags flags, int operand) throws DataBreakpointException { - // Formats: SIC, F3, F4 - switch (opcode) { - // ***** immediate addressing not possible ***** - // stores - case Opcode.STA: storeWord(flags, operand, registers.getA()); break; - case Opcode.STX: storeWord(flags, operand, registers.getX()); break; - case Opcode.STL: storeWord(flags, operand, registers.getL()); break; - case Opcode.STCH: storeByte(flags, operand, registers.getA()); break; - case Opcode.STB: storeWord(flags, operand, registers.getB()); break; - case Opcode.STS: storeWord(flags, operand, registers.getS()); break; - case Opcode.STF: storeFloat(flags, operand, registers.getF()); break; - case Opcode.STT: storeWord(flags, operand, registers.getT()); break; - case Opcode.STSW: storeWord(flags, operand, registers.getSW()); break; - // jumps - case Opcode.JEQ: if (registers.isEqual()) registers.setPC(resolveAddr(flags, operand)); break; - case Opcode.JGT: if (registers.isGreater()) registers.setPC(resolveAddr(flags, operand)); break; - case Opcode.JLT: if (registers.isLower()) registers.setPC(resolveAddr(flags, operand)); break; - case Opcode.J: registers.setPC(resolveAddr(flags, operand)); break; - case Opcode.RSUB: registers.setPC(registers.getL()); popJSUB(); break; - case Opcode.JSUB: registers.setL(registers.getPC()); pushJSUB(); registers.setPC(resolveAddr(flags, operand)); break; - // ***** immediate addressing possible ***** - // loads - case Opcode.LDA: registers.setA(loadWord(flags, operand)); break; - case Opcode.LDX: registers.setX(loadWord(flags, operand)); break; - case Opcode.LDL: registers.setL(loadWord(flags, operand)); break; - case Opcode.LDCH: registers.setALo(loadByte(flags, operand)); break; - case Opcode.LDB: registers.setB(loadWord(flags, operand)); break; - case Opcode.LDS: registers.setS(loadWord(flags, operand)); break; - case Opcode.LDF: registers.setF(loadFloat(flags, operand)); break; - case Opcode.LDT: registers.setT(loadWord(flags, operand)); break; - // arithmetic - case Opcode.ADD: registers.setA(registers.getA() + loadWord(flags, operand)); break; - case Opcode.SUB: registers.setA(registers.getA() - loadWord(flags, operand)); break; - case Opcode.MUL: registers.setA(registers.getA() * loadWord(flags, operand)); break; - case Opcode.DIV: - int divisor = SICXE.swordToInt(loadWord(flags, operand)); - if (divisor == 0) { - System.out.println("division by zero"); - } else { - registers.setA(registers.getAs() / divisor); - } - break; - case Opcode.AND: registers.setA(registers.getA() & loadWord(flags, operand)); break; - case Opcode.OR: registers.setA(registers.getA() | loadWord(flags, operand)); break; - case Opcode.COMP: registers.setSWAfterCompare(registers.getAs() - SICXE.swordToInt(loadWord(flags, operand))); break; - case Opcode.TIX: registers.setX(registers.getX() + 1); - registers.setSWAfterCompare(registers.getXs() - SICXE.swordToInt(loadWord(flags, operand))); break; - // input/output - case Opcode.RD: registers.setALo(devices.read(loadByte(flags, operand))); break; - case Opcode.WD: devices.write(loadByte(flags, operand), registers.getALo()); break; - case Opcode.TD: registers.setSWAfterCompare(devices.test(loadByte(flags, operand)) ? -1 : 0); break; - // floating point arithmetic - case Opcode.ADDF: registers.setF(registers.getF() + loadFloat(flags, operand)); break; - case Opcode.SUBF: registers.setF(registers.getF() - loadFloat(flags, operand)); break; - case Opcode.MULF: registers.setF(registers.getF() * loadFloat(flags, operand)); break; - case Opcode.DIVF: registers.setF(registers.getF() / loadFloat(flags, operand)); break; - case Opcode.COMPF: double sub = registers.getF() - loadFloat(flags, operand); - registers.setSWAfterCompare(sub > 0 ? 1 : (sub < 0 ? -1 : 0)); - break; - // others - case Opcode.LPS: notImplemented("LPS"); break; - case Opcode.STI: notImplemented("STI"); break; - case Opcode.SSK: notImplemented("SSK"); break; - default: return false; + private void divisionByZero() { + if ( registers.intEnabled(Interrupt.IntClass.PROGRAM)) { + programInt = new Interrupt(Interrupt.IntClass.PROGRAM, + Interrupt.ProgramIntCode.ILLEGAL_INSTRUCTION); + } else { + Logger.err("division by zero"); } - return true; } - public int fetch() { + public int fetchByte() { int b = memory.getByteRaw(registers.getPC()); registers.incPC(); return b; } - public void execute() throws DataBreakpointException { - indirectX = false; - instructionCount++; - lastExecRead.clear(); - lastExecWrite.clear(); - lastExecAddr.setStartAddress(registers.getPC()); - lastExecAddr.setSpanLength(0); - // fetch first byte - int opcode = fetch(); - // try format 1 - if (execF1(opcode)) { - lastExecAddr.setSpanLength(1); - return; + public void triggerInterrupts() throws DataBreakpointException { + // Setting interrupt to null clears it. + if (svcInt != null) { + svcInt.trigger(registers, memory); + svcInt = null; + } else if (programInt != null) { + programInt.trigger(registers, memory); + programInt = null; + } else if (timerInt != null) { + timerInt.trigger(registers, memory); + timerInt = null; + } else if (ioInt != null) { + // TODO: IO interrupts. + // We should probably support multiple simultaneous IO interrupts. + // Should they also get cleared by the "HW" device? + ioInt.trigger(registers, memory); + ioInt = null; } - // fetch one more byte - int op = fetch(); - // try format 2 - if (execF2(opcode, op)) { - lastExecAddr.setSpanLength(2); - return; - } - // otherwise it is format SIC, F3 or F4 - Flags flags = new Flags(opcode, op); - int instructionSize = 0; - // operand depends on instruction format - int operand; - // check if standard SIC - if (flags.isSic()) { - operand = flags.operandSic(op, fetch()); - instructionSize = 3; - // check if F4 (extended) - } else if (flags.isExtended()) { - operand = flags.operandF4(op, fetch(), fetch()); - if (flags.isRelative()) invalidAddressing(); - instructionSize = 4; - // otherwise it is F3 - } else { - instructionSize = 3; - operand = flags.operandF3(op, fetch()); - if (flags.isPCRelative()) - operand = flags.operandPCRelative(operand) + registers.getPC(); - else if (flags.isBaseRelative()) - operand += registers.getB(); - else if (!flags.isAbsolute()) - invalidAddressing(); // both PC and base at the same time - } - // SIC, F3, F4 -- all support indexed addressing, but only when simple TA calculation used - if (flags.isIndexed()) - if (flags.isSimple()) operand += registers.getXs(); - else if(flags.isIndirect()) indirectX = true; - else invalidAddressing(); - // try to execute - if (execSICF3F4(opcode & 0xFC, flags, operand)) { - lastExecAddr.setSpanLength(instructionSize); - return; - } - invalidOpcode(opcode); } + // Each clock cycle performs two steps: + // 1. fetch and decode + // 2. execute + public Instruction fetchDecode() { + int instructionSize = 0; + Instruction instruction = null; + int opcode = 0; - // ********** Step over functionality ***************** + instructionCount++; + clearLastExecReadWrite(); - /** - * Push the address bellow current JSUB to the stack, so we can step out of procedure later. - */ - private void pushJSUB() { - this.addressBelowJSUB.push(this.registers.getPC()); - } + lastExecAddr.setStartAddress(registers.getPC()); + lastExecAddr.setSpanLength(0); - /** - * Pop the last address bellow current JSUB from the stack, since we got out of current function. - * (to be called with RSUB) - */ - private void popJSUB() { - this.addressBelowJSUB.pop(); + // Fetch the first byte. + opcode = fetchByte(); + if (Opcode.isF1(opcode)) { + instruction = new InstructionF1(opcode); + instructionSize = 1; + } else if (Opcode.isF2(opcode)) { + int op = fetchByte(); + instruction = new InstructionF2(opcode, op); + instructionSize = 2; + } else if (Opcode.isF34(opcode & 0xFC)) { + int op = fetchByte(); + Flags flags = new Flags(opcode, op); + int operand; + if (flags.isSic()) { + // ****** Standard SIC ******* + instructionSize = 3; + operand = flags.operandSic(op, fetchByte()); + } else if (flags.isExtended()) { + // ****** F4 (extended) ****** + instructionSize = 4; + operand = flags.operandF4(op, fetchByte(), fetchByte()); + + if (flags.isRelative()) { + invalidAddressing(); + } + } else { + // ****** F3 ***************** + instructionSize = 3; + operand = flags.operandF3(op, fetchByte()); + + // Handle relative addressing. + if (flags.isPCRelative()) { + operand = flags.operandPCRelative(operand) + registers.getPC(); + } else if (flags.isBaseRelative()) { + operand += registers.getB(); + } else if (!flags.isAbsolute()) { + // both PC and base at the same time + invalidAddressing(); + } + } + + // Handle indexed addressing. + if (flags.isIndexed()) { + // SIC, F3, F4 -- all support indexed addressing, but only when + // simple TA calculation used + // Indirect indexed addressing is an extension of SicTools to + // simplify complex programs. + if (flags.isSimple()) { + operand += registers.getXs(); + } else if (flags.isIndirect()) { + // Method resolveAddr will add X after the first resolution. + // I hope empty elif clause is more readable than + // negation of this condition for invalid addressing. + } else { + invalidAddressing(); + } + } + instruction = new InstructionSICF3F4(opcode & 0xFC, flags, operand); + } else { + instruction = new InvalidInstruction(opcode); + } + lastExecAddr.setSpanLength(instructionSize); + return instruction; } - /** - * Get the address below the last JSUB was executed, so we can step out. - * @return null if no item on stack - no JSUB encountered, otherwise last address. - */ - public Integer getAddressBelowLastJSUB() { - if (this.addressBelowJSUB.isEmpty()) return null; - else return this.addressBelowJSUB.peek(); + public void step() throws DataBreakpointException { + if (!registers.isIdle()) { + Instruction instruction = fetchDecode(); + if (instruction.isPrivileged() && !registers.isSupervisor()) { + if (registers.intEnabled(Interrupt.IntClass.PROGRAM)) { + programInt = new Interrupt(Interrupt.IntClass.PROGRAM, + Interrupt.ProgramIntCode.PRIVILEGED_INSTRUCTION); + } else { + Logger.err("skipping execution of privileged instruction"); + } + } else { + instruction.execute(); + } + } + registers.tickTimer(); + if (registers.getTimer() <= 0 && registers.intEnabled(Interrupt.IntClass.TIMER) && timerInt == null) { + timerInt = new Interrupt(Interrupt.IntClass.TIMER); + } + triggerInterrupts(); } - } diff --git a/src/sic/sim/vm/Registers.java b/src/sic/sim/vm/Registers.java index 751b521..195900d 100644 --- a/src/sic/sim/vm/Registers.java +++ b/src/sic/sim/vm/Registers.java @@ -17,8 +17,25 @@ public class Registers { private int S, T, B; // SIC/XE 48-bit float register private double F; - // condition code of status word register - private int CC; // TODO: full status word support + + private int timer = 0; + + private class StatusWord { + // 0 = user, 1 = supervisor + public int MODE = 1; + // 0 = running, 1 = idle + public int IDLE = 0; + // process identifier + public int ID = 0; + // condition code + public int CC = 0; + // interrupt mask + public int MASK = 0; + // interrupt code + public int INT_CODE = 0; + } + + private StatusWord SW = new StatusWord(); // ***** getters/setters ******************** // get ... unsigned @@ -34,6 +51,7 @@ public void setPC(int val) { public void incPC() { if (++PC > Machine.MAX_ADDRESS) { + // TODO: interrupt? Logger.fmterr("PC register overflow."); PC = 0; } @@ -128,31 +146,105 @@ public void setF(double val) { } public int getSW() { - if (CC == 0) return 0; - else if (CC < 0) return 0x40; - else return 0x80; + int value = 0; + value |= SW.MODE; + value |= SW.IDLE<<1; + value |= SW.ID<<2; + int cc = 0; + if (SW.CC > 0) { + cc = 0x1; + } else if (SW.CC < 0) { + cc = 0x2; + } + value |= cc<<6; + value |= SW.MASK<<8; + value |= SW.INT_CODE<<16; + return value; + } + + public boolean ccIsLower() { + return SW.CC < 0; + } + + public boolean ccIsEqual() { + return SW.CC == 0; + } + + public boolean ccIsGreater() { + return SW.CC > 0; + } + + public boolean isSupervisor() { + return SW.MODE == 1; } - public boolean isLower() { - return CC < 0; + public boolean isIdle() { + return SW.IDLE == 1; } - public boolean isEqual() { - return CC == 0; + public int processID() { + return SW.ID; } - public boolean isGreater() { - return CC > 0; + public void setIntCode(int value) { + SW.INT_CODE = value & 0xff; } public void setSW(int value) { - if ((value & 0x40) == 0x40) CC = -1; - else if ((value & 0x80) == 0x80) CC = 1; - else CC = 0; + SW.MODE = value&0x1; + SW.IDLE = (value>>1)&0x1; + SW.ID = (value>>2)&0xf; + int cc = (value>>6)&0x3; + if (cc == 0x1) { + SW.CC = -1; + } else if (cc == 0x2) { + SW.CC = 1; + } else { + SW.CC = 0; + } + SW.MASK = (value>>8)&0xf; + SW.INT_CODE = (value>>16)&0xff; + } + + public String explainSW() { + int cc = 0; + if (SW.CC > 0) { + cc = 0x1; + } else if (SW.CC < 0) { + cc = 0x2; + } + // I hate java + String mask = String.format("%s%s%s%s", + intEnabled(Interrupt.IntClass.SVC) ? "S" : "-", + intEnabled(Interrupt.IntClass.PROGRAM) ? "P" : "-", + intEnabled(Interrupt.IntClass.TIMER) ? "T" : "-", + intEnabled(Interrupt.IntClass.IO) ? "I" : "-"); + + return String.format("ICODE: 0x%02x MASK: %s CC: %d
" + + "ID: %d IDLE: %s MODE: %s", + SW.INT_CODE, mask, cc, + SW.ID, SW.IDLE == 0 ? "R" : "I", SW.MODE == 1 ? "color: red;" : "", + SW.MODE == 1 ? "S" : "U"); + } + + public void setCC(int compare) { + SW.CC = compare; + } + + public boolean intEnabled(Interrupt.IntClass c) { + return (SW.MASK & c.value) > 0; + } + + public int getTimer() { + return timer; + } + + public void setTimer(int timer) { + this.timer = timer; } - public void setSWAfterCompare(int compare) { - CC = compare; + public void tickTimer() { + timer--; } // ***** getter/setter by register index **** @@ -223,7 +315,8 @@ public void reset() { A = X = L = 0; B = S = T = 0; F = 0; - CC = 0; + timer = 0; + SW = new StatusWord(); } public Registers() { diff --git a/tests/idle.asm b/tests/idle.asm new file mode 100644 index 0000000..a34202c --- /dev/null +++ b/tests/idle.asm @@ -0,0 +1,40 @@ +idle START 0 + . this should get executed but it won't because processor is + . in idle state. PC won't change, nothing will be ouput. + . You can verify in CPU view that A is 0x41 ('A') and instruction is + . WD #1. + . If you set the SW (on line 28 of this file) to 0x201, then the As will be printed. +start WD #1 + J start + + ORG 0x160 +timer_int WORD 0x000001 +timer_haddr RESW 1 + RESW 10 + +timer_handler + . Write "X\n" + LDCH #0x58 + WD #1 + LDCH #0x0a + WD #1 + STI time + LPS #timer_int + +time WORD 40 + + +init_regs RESW 2 + . software will set the SW to the following value: + WORD 0x000202 . enable TIMER interrupts, IDLE state, user mode + WORD 0 . The start address of the program (in that case nothing will be run). + WORD 0x41 + RESW 8 . init all registers to 0 + + + . Initialization: +entry LDA #timer_handler + STA timer_haddr . set the value of timer_haddr to #timer_handler. + LPS #init_regs + + END entry diff --git a/tests/interrupts.asm b/tests/interrupts.asm new file mode 100644 index 0000000..dba379d --- /dev/null +++ b/tests/interrupts.asm @@ -0,0 +1,78 @@ +ints START 0 + + + ORG 0x100 + . first byte will be filled by the interrupt's value + . second nibble of the second byte is interrupt mask (0 = disable all interrupts) + . the third byte goes to supervisor mode +svc_int WORD 0x000001 +svc_haddr RESW 1 + RESW 10 . 9 registers, one of them 2 WORDS long + +svc_handler LDCH #0x53 + WD #1 + LDCH #0x56 + WD #1 + LDCH #0x43 + WD #1 + LDCH #0x20 + WD #1 + + . print code + LDCH #0x30 + WD #1 + LDCH #0x58 + WD #1 + + STSW sw + CLEAR A + LDCH sw + SHIFTR A,4 + AND #0xf + COMP #0xa + JLT add + ADD #0x7 +add ADD #0x30 + WD #1 + + LDCH sw + AND #0xf + COMP #0xa + JLT add2 + ADD #0x7 +add2 ADD #0x30 + WD #1 + LDCH #0x0a + WD #1 + . This SVC interrupt will not happen because all interrupts are masked. + SVC 13 + LPS #svc_int + + +init_regs RESW 2 + WORD 0x000801 . enable SVC, supervisor mode (because of WD) +init_addr RESW 1 + RESW 8 . init all registers to 0 + + +sw RESW 1 + + . switch to user mode, enable interrupts +entry LDA #svc_handler + STA svc_haddr + LDA #main + STA init_addr + LPS #init_regs + +. main function: print A, then request two interrupts and print A (to show that +. the register stays the same) and then B +main LDCH #0x41 + WD #1 + SVC 0x1a + SVC 0x2f + WD #1 + LDCH #0x42 + WD #1 +halt J halt + + END entry diff --git a/tests/interrupts_privileged.asm b/tests/interrupts_privileged.asm new file mode 100644 index 0000000..d4108ef --- /dev/null +++ b/tests/interrupts_privileged.asm @@ -0,0 +1,39 @@ +ints START 0 + + + ORG 0x130 + . first byte will be filled by the interrupt's value + . second nibble of the second byte is interrupt mask (don't disable + . any interrupt here, to demonstrate successful execution of TD) + . the third byte goes to supervisor mode +prog_int WORD 0x000f01 +prog_haddr RESW 1 + RESW 10 . 9 registers, one of them 2 WORDS long + +prog_handler + TD #1 . This TD will not trigger an interrupt. + LPS #prog_int + +rand WORD 1 + + +init_regs RESW 2 + WORD 0x000400 . enable PROG, user mode +init_addr RESW 1 + RESW 8 . init all registers to 0 + + +sw RESW 1 + + . switch to user mode, enable interrupts +entry LDA #prog_handler + STA prog_haddr + LDA #program + STA init_addr + STI #40 + LPS #init_regs + +program TD #1 . This TD will trigger an interrupt. + J program + + END entry diff --git a/tests/interrupts_timer.asm b/tests/interrupts_timer.asm new file mode 100644 index 0000000..e4df6ec --- /dev/null +++ b/tests/interrupts_timer.asm @@ -0,0 +1,58 @@ +. Run with rand.jar addon. +. You can play with the program: +. Stop the execution. If SW is equal to 0x000201, set it to 1 and resume execution. +. This will disable interrupts and only As will be printed. Then stop it again +. and reset SW back to 0x201. Interrupts will be enabled again. + +ints START 0 +. Register work area for timer interrupts + ORG 0x160 + . first byte will be filled by the interrupt's value + . second nibble of the second byte is interrupt mask (0 = disable all interrupts) + . the third byte goes to supervisor mode +timer_int WORD 0x000001 . SW will be set to this after the interrupt has been triggered. +timer_haddr RESW 1 . The address of handler routine. + RESW 10 . 9 registers, one of them (F) 2 WORDS long + +timer_handler + . Write "\nTIM\n" + LDCH #0x0a + WD #1 + LDCH #0x54 + WD #1 + LDCH #0x49 + WD #1 + LDCH #0x4d + WD #1 + LDCH #0x0a + WD #1 + . Get a random number and set the timer to it. + CLEAR A + RD #3 + STA rand + STI rand + . Return to the program. + LPS #timer_int +rand RESW 1 + + +init_regs RESW 2 + . software will set the SW to the following value: + WORD 0x000201 . enable TIMER interrupts, supervisor mode (we need WD) +init_addr RESW 1 . The start address of the program. + RESW 8 . init all registers to 0 + + + . Initialization: +entry LDA #timer_handler + STA timer_haddr . set the value of timer_haddr to #timer_handler. + LDA #main + STA init_addr . set the value of init_addr to #main. + LPS #init_regs + +. main function: loop that prints the character A. +main LDCH #0x41 + WD #1 + J main + + END entry