package dcpu;
import static dcpu.DcpuConstants.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class Dcpu implements WorldPauser {
public long debug = 0;
public Registers regs;
public IOperand[] operands;
public IOperation[] operations;
public IAdvOperation[] advops;
public DcpuMemory memory;
public long cyclecnt;
public boolean ifFailed;
public Queue<Character> interrupts;
public boolean queue_interrupts;
public Queue<Runnable> cpuTodo;
public Set<CpuWatcher> watchers;
public SortedMap<Character, Hardware> hardware;
public boolean wfi;
private long cyclecntLim;
private boolean paused;
private Object pausedMonitor;
private long cpuid = 0;
long tally;
public final long id;
public Dcpu() {
synchronized(Dcpu.class) {
this.id = cpuid;
cpuid+=1;
}
regs = new Registers();
operands = new IOperand[0x40];
operations = new IOperation[0x20];
advops = new IAdvOperation[0x20];
memory = new DcpuMemory();
interrupts = new LinkedList<>();
cpuTodo = new LinkedList<>();
queue_interrupts = true;
watchers = new HashSet<>();
hardware = new TreeMap<>();
wfi = false;
fill_operand_extractors();
fill_operation_executors();
fill_adv_operation_executors();
pausedMonitor = new Object();
paused = false;
}
public void runInCpuThread(Runnable r) {
synchronized(cpuTodo) {
cpuTodo.add(r);
}
}
public void pause() {
synchronized(pausedMonitor) {
paused = true;
pausedMonitor.notifyAll();
}
}
public void unpause() {
synchronized(pausedMonitor) {
paused = false;
pausedMonitor.notifyAll();
}
}
public void waitForUnpause() {
synchronized(pausedMonitor) {
while (paused) try { pausedMonitor.wait(); } catch(InterruptedException e) {}
}
}
//run for at least cycles cp cycles, leaving the extra
//to shorten the next run
public void step_cycles(long cycles, WorldPauseHandler handler) {
cyclecntLim += cycles;
while (cyclecntLim - cyclecnt > cycles) {
synchronized(cpuTodo) {
while(!cpuTodo.isEmpty()) {
cpuTodo.poll().run();
}
}
synchronized(pausedMonitor) {
if (paused) {
handler.woldPaused(this);
}
}
step();
}
}
public void step() {
long cyclecntStart = cyclecnt;
if (ifFailed) {
Instruction inst = new Instruction(memory.get(regs.pc++));
long cyclecnt_freeze = cyclecnt;
if (inst.opcode()!=ADV) {
operands[inst.b()].fetch(inst.b(), false);
operands[inst.a()].fetch(inst.a(), true);
cyclecnt= cyclecnt_freeze+1;
} else {
operands[inst.a()].fetch(inst.a(), true);
cyclecnt= cyclecnt_freeze+1;
}
if(!IS_IF(inst.opcode())) {
ifFailed = false;
}
} else if (!queue_interrupts && !interrupts.isEmpty()) {
wfi = false;
if (regs.ia != 0) {
queue_interrupts = true;
memory.set(--regs.sp, regs.pc);
memory.set(--regs.sp, regs.gp[REG_A]);
regs.pc = regs.ia;
regs.gp[REG_A] = interrupts.poll();
} else {
interrupts.poll();
}
} else {
if (debug > 0) {
debug -=1;
System.out.printf("%04x(%04x)\n", (int)regs.pc, (int)memory.get(regs.pc));
}
if (wfi) {
cyclecnt+=1;
for (CpuWatcher watcher : watchers) {
watcher.cpu_changed(this, 1, true);
}
return;
}
Instruction inst = new Instruction(memory.get(regs.pc++));
if (inst.opcode() == ADV) {
char a_pc = regs.pc;
int afrag = inst.a();
IOperand target = operands[afrag];
char a = target.fetch(afrag, true);
IAdvOperation op = advops[inst.b()];
if (op != null) {
op.compute(a, target, afrag, a_pc);
} else {
cyclecnt+=16;
}
} else {
int afrag = inst.a();
char a = operands[afrag].fetch(afrag, true);
char b_pc = regs.pc;
int bfrag = inst.b();
IOperand target = operands[bfrag];
char b = target.fetch(bfrag, false);
IOperation op = operations[inst.opcode()];
if (op != null) {
op.compute(a, b, target, bfrag, b_pc);
} else {
cyclecnt+=16;
}
}
}
for (CpuWatcher watcher : watchers) {
watcher.cpu_changed(this, cyclecnt - cyclecntStart, false);
}
}
private void fill_adv_operation_executors() {
advops[JSR] = new IAdvOperation() {
public void compute(char a, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
memory.set(--regs.sp, regs.pc);
regs.pc = a;
cyclecnt+=3;
}
};
advops[WFI] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
cyclecnt+=1;
wfi = true;
}
};
advops[INT] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
cyclecnt+=4;
interrupts.add(a);
}
};
advops[IAG] = new IAdvOperation() {
public void compute(char a, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
cyclecnt+=1;
target.set(a_instruction_fragment, regs.ia, a_fetch_pc);
}
};
advops[IAS] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
cyclecnt+=1;
regs.ia = a;
}
};
advops[RFI] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
regs.gp[REG_A] = memory.get(regs.sp++);
regs.pc = memory.get(regs.sp++);
queue_interrupts = false;
cyclecnt+=3;
}
};
advops[IAQ] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
queue_interrupts = a!=0;
cyclecnt+=2;
}
};
advops[HWN] = new IAdvOperation() {
public void compute(char a, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
char num = next_hardware_id();
target.set(a_instruction_fragment, num, a_fetch_pc);
cyclecnt+=2;
}
};
advops[HWQ] = new IAdvOperation() {
public void compute(char a, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
if (hardware.containsKey(a)) {
hardware.get(a).query(Dcpu.this);
}
cyclecnt+=4;
}
};
advops[HWI] = new IAdvOperation() {
public void compute(char a, IOperand target, int a_instruction_fragment,
char a_fetch_pc) {
if (hardware.containsKey(a)) {
hardware.get(a).interrupted(Dcpu.this);
}
cyclecnt+=4;
}
};
}
private void fill_operation_executors() {
operations[SET] = new ExFixedOperation(1) {
public int compute(char a, char b) {
return a;
}
};
operations[ADD] = new IntOperation(2) {
public int compute(char a, char b) {
return a + b;
}
};
operations[SUB] = new IntOperation(2) {
public int compute(char a, char b) {
return ((int) b) - ((int) a);
}
};
operations[MUL] = new IntOperation(2) {
public int compute(char a, char b) {
return a * b;
}
};
operations[MLI] = new IntOperation(2) {
public int compute(char a, char b) {
return sign_extend(a) * sign_extend(b);
}
};
operations[DIV] = new IOperation() {
public void compute(char a, char b, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
cyclecnt+=3;
if (a==0) {
target.set(a_instruction_fragment, (char)(0), a_fetch_pc);
regs.ex = (char)0;
} else {
int result = (b<<16)/a;
regs.ex = (char)result;
result = b/a;
target.set(a_instruction_fragment, (char)(result), a_fetch_pc);
}
}
};
operations[DVI] = new IOperation() {
public void compute(char a, char b, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
int ai = sign_extend(a);
int bi = sign_extend(b);
cyclecnt+=3;
if (ai == 0) {
target.set(a_instruction_fragment, (char)(0), a_fetch_pc);
regs.ex = (char)0;
} else {
int result = (bi<<16)/ai;
regs.ex = (char)result;
result = bi/ai;
target.set(a_instruction_fragment, (char)(result), a_fetch_pc);
}
}
};
operations[MOD] = new ExFixedOperation(3) {
public int compute(char a, char b) {
if (a==0) return 0;
return b % a;
}
};
operations[MDI] = new ExFixedOperation(3) {
public int compute(char a, char b) {
if (a==0) return 0;
return sign_extend(b) % sign_extend(a);
}
};
operations[AND] = new ExFixedOperation(1) {
public int compute(char a, char b) {
return a & b;
}
};
operations[BOR] = new ExFixedOperation(1) {
public int compute(char a, char b) {
return a | b;
}
};
operations[XOR] = new ExFixedOperation(1) {
public int compute(char a, char b) {
return a ^ b;
}
};
operations[ASR] = new IOperation() {
public void compute(char a, char b, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
int result = sign_extend(b) << (16 - a);
regs.ex = (char)result;
result = result >> 16;
target.set(a_instruction_fragment, (char)result, a_fetch_pc);
cyclecnt+=1;
}
};
operations[SHR] = new IOperation() {
public void compute(char a, char b, IOperand target,
int a_instruction_fragment, char a_fetch_pc) {
int result = b << (16 - (int)a);
regs.ex = (char)result;
result = result >> 16;
target.set(a_instruction_fragment, (char)result, a_fetch_pc);
cyclecnt+=1;
}
};
operations[SHL] = new IntOperation(1) {
public int compute(char a, char b) {
return ((int) b) << a;
}
};
operations[IFB] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return (b & a) != 0;
}
};
operations[IFC] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return (b & a) == 0;
}
};
operations[IFE] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return b == a;
}
};
operations[IFN] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return b != a;
}
};
operations[IFG] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return b > a;
}
};
operations[IFA] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return sign_extend(b) > sign_extend(a);
}
};
operations[IFL] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return b < a;
}
};
operations[IFU] = new ConditionalOperation(2) {
public boolean compute(char a, char b) {
return sign_extend(b) < sign_extend(a);
}
};
operations[ADX] = new IntOperation(3) {
public int compute(char a, char b) {
return a + b + regs.ex;
}
};
operations[SBX] = new IntOperation(3) {
public int compute(char a, char b) {
return b - a + regs.ex;
}
};
operations[STI] = new IOperation() {
public void compute(char a, char b, IOperand target,
int b_instruction_fragment, char b_fetch_pc) {
target.set(b_instruction_fragment, a, b_fetch_pc);
regs.gp[REG_I]++;
regs.gp[REG_J]++;
cyclecnt+=2;
}
};
operations[STD] = new IOperation() {
public void compute(char a, char b, IOperand target,
int b_instruction_fragment, char b_fetch_pc) {
target.set(b_instruction_fragment, a, b_fetch_pc);
regs.gp[REG_I]--;
regs.gp[REG_J]--;
cyclecnt+=2;
}
};
}
private void fill_operand_extractors() {
IOperand regop = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
regs.gp[instruction_fragment] = value;
}
public char fetch(int instruction_fragment, boolean isA) {
return regs.gp[instruction_fragment];
}
};
for (int i = REG_A; i < GP_REG_COUNT; i++) {
operands[i] = regop;
}
IOperand indregop = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
memory.set(regs.gp[instruction_fragment-INDIRECT(REG_A)], value);
}
public char fetch(int instruction_fragment, boolean isA) {
return memory.get(regs.gp[instruction_fragment-INDIRECT(REG_A)]);
}
};
for (int i = INDIRECT(REG_A); i < INDIRECT(GP_REG_COUNT); i++) {
operands[i] = indregop;
}
IOperand indoffop = new IOperand() {
public char fetch(int instruction_fragment, boolean isA) {
cyclecnt += 1;
char offset = memory.get(regs.pc++);
return memory
.get((char) (offset + regs.gp[instruction_fragment-INDIRECT_OFFSET(REG_A)]));
}
public void set(int instruction_fragment, char value, char fetch_pc) {
char offset = memory.get(fetch_pc);
memory.set((char) (offset + regs.gp[instruction_fragment-INDIRECT_OFFSET(REG_A)]),
value);
}
};
for (int i = INDIRECT_OFFSET(REG_A); i < INDIRECT_OFFSET(GP_REG_COUNT); i++) {
operands[i] = indoffop;
}
operands[PUSHPOP] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
memory.set(--regs.sp, value);
}
public char fetch(int instruction_fragment, boolean isA) {
if (isA) {
return memory.get(regs.sp++);
} else {
return 0;
}
}
};
operands[PEEK] = new IOperand() {
public char fetch(int instruction_fragment, boolean isA) {
return memory.get(regs.sp);
}
public void set(int instruction_fragment, char value, char fetch_pc) {
memory.set(regs.sp, value);
}
};
operands[PICK] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
char offset = memory.get(fetch_pc);
memory.set((char) (offset + regs.sp), value);
}
public char fetch(int instruction_fragment, boolean isA) {
cyclecnt += 1;
char offset = memory.get(regs.pc++);
return memory.get((char) (offset + regs.sp));
}
};
operands[SP] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
regs.sp = value;
}
public char fetch(int instruction_fragment, boolean isA) {
return regs.sp;
}
};
operands[PC] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
regs.pc = value;
}
public char fetch(int instruction_fragment, boolean isA) {
return regs.pc;
}
};
operands[EX] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
regs.ex = value;
}
public char fetch(int instruction_fragment, boolean isA) {
return regs.ex;
}
};
operands[LIT_IND] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
memory.set(memory.get(fetch_pc), value);
}
public char fetch(int instruction_fragment, boolean isA) {
cyclecnt += 1;
return memory.get(memory.get(regs.pc++));
}
};
operands[LONG_LIT] = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
}
public char fetch(int instruction_fragment, boolean isA) {
cyclecnt += 1;
return memory.get(regs.pc++);
}
};
IOperand shortlit = new IOperand() {
public void set(int instruction_fragment, char value, char fetch_pc) {
//no way to hit this method.
}
public char fetch(int instruction_fragment, boolean isA) {
return (char) (instruction_fragment - 0x21);
}
};
for (int i = SHORT_LIT(SHORT_LIT_MIN); i < SHORT_LIT(SHORT_LIT_MAX) + 1; i++) {
operands[i] = shortlit;
}
}
public class Registers {
public char[] gp = new char[GP_REG_COUNT];
public char pc, sp, ex, ia;
}
public interface IOperand {
char fetch(int instruction_fragment, boolean isA);
void set(int instruction_fragment, char value, char fetch_pc);
}
public interface IOperation {
void compute(char a, char b, IOperand target,
int a_instruction_fragment, char a_fetch_pc);
}
public abstract class IntOperation implements IOperation {
private int cnt;
public IntOperation(int cnt) {
this.cnt = cnt;
}
public void compute(char a, char b, IOperand target,
int b_instruction_fragment, char b_fetch_pc) {
int result = compute(a, b);
regs.ex = (char) (result >> 16);
target.set(b_instruction_fragment, (char) result, b_fetch_pc);
cyclecnt += cnt;
}
public abstract int compute(char a, char b);
}
public abstract class ExFixedOperation implements IOperation {
private int cnt;
public ExFixedOperation(int cnt) {
this.cnt = cnt;
}
public void compute(char a, char b, IOperand target,
int b_instruction_fragment, char b_fetch_pc) {
int result = compute(a, b);
target.set(b_instruction_fragment, (char) (result), b_fetch_pc);
cyclecnt += cnt;
}
public abstract int compute(char a, char b);
}
public abstract class ConditionalOperation implements IOperation {
private int cnt;
public ConditionalOperation(int cnt) {
this.cnt = cnt;
}
public void compute(char a, char b, IOperand target,
int b_instruction_fragment, char b_fetch_pc) {
if (!compute(a, b)) {
ifFailed = true;
}
cyclecnt += cnt;
}
public abstract boolean compute(char a, char b);
}
public interface IAdvOperation {
void compute(char a, IOperand target, int a_instruction_fragment, char a_fetch_pc);
}
public static class Instruction {
private char value;
Instruction(char value) {
this.value = value;
}
int opcode() {
return (this.value >> 0) & 0x1f;
}
int b() {
return (this.value >> 5) & 0x1f;
}
int a() {
return (this.value >> 10) & 0x3f;
}
}
public void addWatcher(CpuWatcher w) {
watchers.add(w);
}
public void removeWatcher(CpuWatcher w) {
watchers.remove(w);
}
public char next_hardware_id() {
if (hardware.isEmpty()) {
return (char)1;
} else {
return (char)(hardware.lastKey()+1);
}
}
public void addHardware(char id, Hardware hw) {
hw.plugged_in(this, id);
hardware.put(id, hw);
}
public void reset() {
regs.ex = 0;
regs.ia = 0;
regs.pc = 0;
regs.sp = 0;
for (int i=0;i<regs.gp.length;i++) {
regs.gp[i] = 0;
}
ifFailed = false;
interrupts.clear();
queue_interrupts = true;
cpuTodo.clear();
cyclecntLim = cyclecnt = 0;
debug = 60;
}
public void queryResult(int vendor, int hardware, int version) {
regs.gp[REG_B] = (char) (hardware>>16);
regs.gp[REG_A] = (char) (hardware);
regs.gp[REG_C] = (char) (version);
regs.gp[REG_Y] = (char) (vendor>>16);
regs.gp[REG_X] = (char) (vendor);
}
}