mirror of
https://github.com/simon987/Much-Assembly-Required.git
synced 2025-04-19 18:46:43 +00:00
Interrupts WIP #230
This commit is contained in:
parent
51c0d4c3c7
commit
b1d7121b22
@ -45,6 +45,11 @@ public class CPU implements MongoSerializable {
|
|||||||
*/
|
*/
|
||||||
private int codeSectionOffset;
|
private int codeSectionOffset;
|
||||||
|
|
||||||
|
private int interruptVectorTableOffset;
|
||||||
|
private final int graceInstructionCount;
|
||||||
|
private int graceInstructionLeft;
|
||||||
|
private boolean isGracePeriod;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instruction pointer, always points to the next instruction
|
* Instruction pointer, always points to the next instruction
|
||||||
*/
|
*/
|
||||||
@ -57,14 +62,10 @@ public class CPU implements MongoSerializable {
|
|||||||
|
|
||||||
private int registerSetSize;
|
private int registerSetSize;
|
||||||
|
|
||||||
private static final char EXECUTION_COST_ADDR = 0x0050;
|
private static final char EXECUTION_COST_ADDR = 0x0150;
|
||||||
private static final char EXECUTED_INS_ADDR = 0x0051;
|
private static final char EXECUTED_INS_ADDR = 0x0151;
|
||||||
|
|
||||||
public CPU() {
|
|
||||||
instructionSet = new DefaultInstructionSet();
|
|
||||||
registerSet = new DefaultRegisterSet();
|
|
||||||
codeSectionOffset = GameServer.INSTANCE.getConfig().getInt("org_offset");
|
|
||||||
|
|
||||||
|
private void addInstructions() {
|
||||||
instructionSet.add(new JmpInstruction(this));
|
instructionSet.add(new JmpInstruction(this));
|
||||||
instructionSet.add(new JnzInstruction(this));
|
instructionSet.add(new JnzInstruction(this));
|
||||||
instructionSet.add(new JzInstruction(this));
|
instructionSet.add(new JzInstruction(this));
|
||||||
@ -91,7 +92,20 @@ public class CPU implements MongoSerializable {
|
|||||||
instructionSet.add(new PopfInstruction(this));
|
instructionSet.add(new PopfInstruction(this));
|
||||||
instructionSet.add(new JnaInstruction(this));
|
instructionSet.add(new JnaInstruction(this));
|
||||||
instructionSet.add(new JaInstruction(this));
|
instructionSet.add(new JaInstruction(this));
|
||||||
|
instructionSet.add(new IntInstruction(this));
|
||||||
|
instructionSet.add(new IretInstruction(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CPU() {
|
||||||
|
instructionSet = new DefaultInstructionSet();
|
||||||
|
registerSet = new DefaultRegisterSet();
|
||||||
|
IServerConfiguration config = GameServer.INSTANCE.getConfig();
|
||||||
|
codeSectionOffset = config.getInt("org_offset");
|
||||||
|
graceInstructionCount = config.getInt("grace_instruction_count");
|
||||||
|
|
||||||
|
interruptVectorTableOffset = config.getInt("ivt_offset");
|
||||||
|
|
||||||
|
addInstructions();
|
||||||
status = new Status();
|
status = new Status();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,41 +116,32 @@ public class CPU implements MongoSerializable {
|
|||||||
instructionSet = new DefaultInstructionSet();
|
instructionSet = new DefaultInstructionSet();
|
||||||
registerSet = new DefaultRegisterSet();
|
registerSet = new DefaultRegisterSet();
|
||||||
codeSectionOffset = config.getInt("org_offset");
|
codeSectionOffset = config.getInt("org_offset");
|
||||||
|
graceInstructionCount = config.getInt("grace_instruction_count");
|
||||||
|
|
||||||
instructionSet.add(new JmpInstruction(this));
|
interruptVectorTableOffset = config.getInt("ivt_offset");
|
||||||
instructionSet.add(new JnzInstruction(this));
|
|
||||||
instructionSet.add(new JzInstruction(this));
|
addInstructions();
|
||||||
instructionSet.add(new JgInstruction(this));
|
|
||||||
instructionSet.add(new JgeInstruction(this));
|
|
||||||
instructionSet.add(new JleInstruction(this));
|
|
||||||
instructionSet.add(new JlInstruction(this));
|
|
||||||
instructionSet.add(new PushInstruction(this));
|
|
||||||
instructionSet.add(new PopInstruction(this));
|
|
||||||
instructionSet.add(new CallInstruction(this));
|
|
||||||
instructionSet.add(new RetInstruction(this));
|
|
||||||
instructionSet.add(new MulInstruction(this));
|
|
||||||
instructionSet.add(new DivInstruction(this));
|
|
||||||
instructionSet.add(new JnsInstruction(this));
|
|
||||||
instructionSet.add(new JsInstruction(this));
|
|
||||||
instructionSet.add(new HwiInstruction(this));
|
|
||||||
instructionSet.add(new HwqInstruction(this));
|
|
||||||
instructionSet.add(new XchgInstruction(this));
|
|
||||||
instructionSet.add(new JcInstruction(this));
|
|
||||||
instructionSet.add(new JncInstruction(this));
|
|
||||||
instructionSet.add(new JnoInstruction(this));
|
|
||||||
instructionSet.add(new JoInstruction(this));
|
|
||||||
instructionSet.add(new PushfInstruction(this));
|
|
||||||
instructionSet.add(new PopfInstruction(this));
|
|
||||||
instructionSet.add(new JnaInstruction(this));
|
|
||||||
instructionSet.add(new JaInstruction(this));
|
|
||||||
|
|
||||||
status = new Status();
|
status = new Status();
|
||||||
memory = new Memory(config.getInt("memory_size"));
|
memory = new Memory(config.getInt("memory_size"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the IP to IVT + number and pushes flags then the old IP
|
||||||
|
*/
|
||||||
|
public void interrupt(int number) {
|
||||||
|
Instruction push = instructionSet.get(PushInstruction.OPCODE);
|
||||||
|
push.execute(status.toWord(), status);
|
||||||
|
push.execute(ip, status);
|
||||||
|
|
||||||
|
this.setIp((char) memory.get(interruptVectorTableOffset + number));
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
status.clear();
|
status.clear();
|
||||||
ip = codeSectionOffset;
|
ip = codeSectionOffset;
|
||||||
|
graceInstructionLeft = graceInstructionCount;
|
||||||
|
isGracePeriod = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int execute(int timeout) {
|
public int execute(int timeout) {
|
||||||
@ -151,17 +156,16 @@ public class CPU implements MongoSerializable {
|
|||||||
while (!status.isBreakFlag()) {
|
while (!status.isBreakFlag()) {
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
if (counter % 10000 == 0) {
|
if (isGracePeriod) {
|
||||||
if (System.currentTimeMillis() > (startTime + timeout)) {
|
if (graceInstructionLeft-- == 0) {
|
||||||
LogManager.LOGGER.fine("CPU Timeout " + this + " after " + counter + "instructions (" + timeout + "ms): " + (double) counter / ((double) timeout / 1000) / 1000000 + "MHz");
|
writeExecutionStats(timeout, counter);
|
||||||
|
|
||||||
//Write execution cost and instruction count to memory
|
|
||||||
memory.set(EXECUTION_COST_ADDR, timeout);
|
|
||||||
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
|
||||||
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
|
||||||
|
|
||||||
return timeout;
|
return timeout;
|
||||||
}
|
}
|
||||||
|
} else if (counter % 10000 == 0) {
|
||||||
|
if (System.currentTimeMillis() > (startTime + timeout)) {
|
||||||
|
interrupt(32);
|
||||||
|
isGracePeriod = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fetch instruction
|
//fetch instruction
|
||||||
@ -184,15 +188,17 @@ public class CPU implements MongoSerializable {
|
|||||||
|
|
||||||
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
|
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
|
||||||
|
|
||||||
|
writeExecutionStats(elapsed, counter);
|
||||||
//Write execution cost and instruction count to memory
|
|
||||||
memory.set(EXECUTION_COST_ADDR, elapsed);
|
|
||||||
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
|
||||||
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
|
||||||
|
|
||||||
return elapsed;
|
return elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeExecutionStats(int timeout, int counter) {
|
||||||
|
memory.set(EXECUTION_COST_ADDR, timeout);
|
||||||
|
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
||||||
|
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
||||||
|
}
|
||||||
|
|
||||||
public void executeInstruction(Instruction instruction, int source, int destination) {
|
public void executeInstruction(Instruction instruction, int source, int destination) {
|
||||||
|
|
||||||
//Execute the instruction
|
//Execute the instruction
|
||||||
|
@ -59,7 +59,6 @@ public abstract class Instruction {
|
|||||||
*/
|
*/
|
||||||
public Status execute(Target dst, int dstIndex, int src, Status status) {
|
public Status execute(Target dst, int dstIndex, int src, Status status) {
|
||||||
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ public class Status implements Cloneable {
|
|||||||
this.errorFlag = errorFlag;
|
this.errorFlag = errorFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public char toByte() {
|
public char toWord() {
|
||||||
char stat = 0;
|
char stat = 0;
|
||||||
stat = (char) (stat | ((signFlag ? 1 : 0) << 3));
|
stat = (char) (stat | ((signFlag ? 1 : 0) << 3));
|
||||||
stat = (char) (stat | ((zeroFlag ? 1 : 0) << 2));
|
stat = (char) (stat | ((zeroFlag ? 1 : 0) << 2));
|
||||||
@ -142,7 +142,7 @@ public class Status implements Cloneable {
|
|||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fromByte(char stat) {
|
public void fromWord(char stat) {
|
||||||
setSignFlag((stat & (1 << 3)) != 0);
|
setSignFlag((stat & (1 << 3)) != 0);
|
||||||
setZeroFlag((stat & (1 << 2)) != 0);
|
setZeroFlag((stat & (1 << 2)) != 0);
|
||||||
setCarryFlag((stat & (1 << 1)) != 0);
|
setCarryFlag((stat & (1 << 1)) != 0);
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.simon987.mar.server.assembly.instruction;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.assembly.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Software Interrupt
|
||||||
|
* Pushes the flags register and the IP to the stack then
|
||||||
|
* Sets the IP to the CPU [ivt_offset + src].
|
||||||
|
*/
|
||||||
|
public class IntInstruction extends Instruction {
|
||||||
|
|
||||||
|
public static final int OPCODE = 48;
|
||||||
|
private final CPU cpu;
|
||||||
|
|
||||||
|
public IntInstruction(CPU cpu) {
|
||||||
|
super("int", OPCODE);
|
||||||
|
this.cpu = cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Status execute(int src, Status status) {
|
||||||
|
cpu.interrupt(src);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operandsValid(Operand o1, Operand o2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operandValid(Operand o1) {
|
||||||
|
return o1.getType() == OperandType.IMMEDIATE16 && o1.getData() < 256;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.simon987.mar.server.assembly.instruction;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.assembly.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interrupt Return
|
||||||
|
*
|
||||||
|
* Pops the IP and status flag from the stack.
|
||||||
|
*/
|
||||||
|
public class IretInstruction extends Instruction {
|
||||||
|
|
||||||
|
public static final int OPCODE = 49;
|
||||||
|
private final CPU cpu;
|
||||||
|
|
||||||
|
public IretInstruction(CPU cpu) {
|
||||||
|
super("iret", OPCODE);
|
||||||
|
this.cpu = cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status execute(Status status) {
|
||||||
|
RegisterSet reg = cpu.getRegisterSet();
|
||||||
|
|
||||||
|
cpu.setIp((char) cpu.getMemory().get(reg.get(7))); //IP = (SP + 0)
|
||||||
|
status.fromWord((char) cpu.getMemory().get(reg.get(7) + 1)); //Status = (SP + 1)
|
||||||
|
reg.set(7, reg.get(7) + 2); //Increment SP (stack grows towards smaller)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean noOperandsValid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operandsValid(Operand o1, Operand o2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operandValid(Operand o1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ public class PopfInstruction extends Instruction {
|
|||||||
char flags = (char) cpu.getMemory().get(sp.getValue());
|
char flags = (char) cpu.getMemory().get(sp.getValue());
|
||||||
|
|
||||||
// Overwrite the CPU flags
|
// Overwrite the CPU flags
|
||||||
status.fromByte(flags);
|
status.fromWord(flags);
|
||||||
|
|
||||||
// Increment SP
|
// Increment SP
|
||||||
sp.setValue(sp.getValue() + 1);
|
sp.setValue(sp.getValue() + 1);
|
||||||
|
@ -28,7 +28,7 @@ public class PushfInstruction extends Instruction {
|
|||||||
sp.setValue(sp.getValue() - 1);
|
sp.setValue(sp.getValue() - 1);
|
||||||
|
|
||||||
// Push the current flags
|
// Push the current flags
|
||||||
cpu.getMemory().set(sp.getValue(), status.toByte());
|
cpu.getMemory().set(sp.getValue(), status.toWord());
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,9 @@ wg_createNewOnRequest=0
|
|||||||
|
|
||||||
#CPU
|
#CPU
|
||||||
tick_length=1000
|
tick_length=1000
|
||||||
org_offset=512
|
org_offset=4096
|
||||||
|
ivt_offset=0
|
||||||
|
grace_instruction_count=10000
|
||||||
stack_bottom=65536
|
stack_bottom=65536
|
||||||
memory_size=65536
|
memory_size=65536
|
||||||
user_timeout=100
|
user_timeout=100
|
||||||
|
@ -8,8 +8,6 @@ import net.simon987.mar.server.assembly.exception.CancelledException;
|
|||||||
|
|
||||||
public class TestHelper {
|
public class TestHelper {
|
||||||
|
|
||||||
private static final int TIMEOUT = 100;
|
|
||||||
|
|
||||||
public static Assembler getTestAsm() {
|
public static Assembler getTestAsm() {
|
||||||
IServerConfiguration config = getTestConfig();
|
IServerConfiguration config = getTestConfig();
|
||||||
CPU cpu = getTestCpu();
|
CPU cpu = getTestCpu();
|
||||||
@ -20,7 +18,9 @@ public class TestHelper {
|
|||||||
IServerConfiguration configuration = new FakeConfiguration();
|
IServerConfiguration configuration = new FakeConfiguration();
|
||||||
|
|
||||||
configuration.setInt("memory_size", 65536);
|
configuration.setInt("memory_size", 65536);
|
||||||
configuration.setInt("org_offset", 400);
|
configuration.setInt("org_offset", 4096);
|
||||||
|
configuration.setInt("ivt_offset", 0);
|
||||||
|
configuration.setInt("grace_instruction_count", 10000);
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +36,10 @@ public class TestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static TestExecutionResult executeCode(String code) {
|
public static TestExecutionResult executeCode(String code) {
|
||||||
|
return executeCode(code, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TestExecutionResult executeCode(String code, int timeout) {
|
||||||
AssemblyResult ar = getTestAsm().parse(code);
|
AssemblyResult ar = getTestAsm().parse(code);
|
||||||
CPU cpu = TestHelper.getTestCpu();
|
CPU cpu = TestHelper.getTestCpu();
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ public class TestHelper {
|
|||||||
cpu.setCodeSectionOffset(ar.getCodeSectionOffset());
|
cpu.setCodeSectionOffset(ar.getCodeSectionOffset());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
cpu.execute(TIMEOUT);
|
cpu.execute(timeout);
|
||||||
|
|
||||||
return new TestExecutionResult(cpu.getState(), host.callHistory, ar);
|
return new TestExecutionResult(cpu.getState(), host.callHistory, ar);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.simon987.mar.server.assembly.instruction;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.TestExecutionResult;
|
||||||
|
import net.simon987.mar.server.assembly.TestHelper;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class IntInstructionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void intSimple1() {
|
||||||
|
String code = "" +
|
||||||
|
"isr: \n" +
|
||||||
|
" MOV X, 0x1234 \n" +
|
||||||
|
" IRET \n" +
|
||||||
|
".text \n" +
|
||||||
|
"MOV [32], isr \n" +
|
||||||
|
"INT 32 \n" +
|
||||||
|
"MOV Y, 0x4567 \n" +
|
||||||
|
"brk \n";
|
||||||
|
|
||||||
|
TestExecutionResult res = TestHelper.executeCode(code);
|
||||||
|
|
||||||
|
assertEquals(0x1234, res.regValue("X"));
|
||||||
|
assertEquals(0x4567, res.regValue("Y"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void intExecutionLimit() {
|
||||||
|
String code = "" +
|
||||||
|
"isr: \n" +
|
||||||
|
" MOV X, 0x1234 \n" +
|
||||||
|
" BRK \n" +
|
||||||
|
".text \n" +
|
||||||
|
"MOV [32], isr \n" +
|
||||||
|
"loop: \n" +
|
||||||
|
"ADD A, 1 \n" +
|
||||||
|
"JMP loop \n" +
|
||||||
|
"MOV Y, 0x4567 \n" +
|
||||||
|
"brk \n";
|
||||||
|
|
||||||
|
TestExecutionResult res = TestHelper.executeCode(code, 10);
|
||||||
|
|
||||||
|
assertEquals(0x1234, res.regValue("X"));
|
||||||
|
assertEquals(0, res.regValue("Y"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user