inital commit кек

This commit is contained in:
SashegDev
2026-06-04 03:12:17 +00:00
parent 82675f402d
commit f2888dea3a
190 changed files with 18421 additions and 21 deletions
+26
View File
@@ -0,0 +1,26 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.cbe"
compileSdk = 35
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
}
@@ -0,0 +1,13 @@
package com.cbe.core;
public interface Bus {
byte read(int address);
void write(int address, byte data);
int readWord(int address);
void writeWord(int address, int value);
long clock();
long tick();
void attach(String deviceName, int baseAddress, int size);
ModuleInstance getDevice(String name);
void reset();
}
@@ -0,0 +1,25 @@
package com.cbe.core;
public final class CbePluginConstants {
public static final String MAGIC = "CBE_PLUG";
public static final int HEADER_SIZE = 62;
public static final int OFF_MAGIC = 0;
public static final int OFF_VERSION = 8;
public static final int OFF_HEADER_SIZE = 12;
public static final int OFF_MODULE_TYPE = 16;
public static final int OFF_COMPILE_MODE = 17;
public static final int OFF_METADATA_OFF = 18;
public static final int OFF_METADATA_LEN = 22;
public static final int OFF_OPCODE_OFF = 26;
public static final int OFF_OPCODE_LEN = 30;
public static final int OFF_MICROCODE_OFF = 34;
public static final int OFF_MICROCODE_LEN = 38;
public static final int OFF_HANDLER_OFF = 42;
public static final int OFF_HANDLER_LEN = 46;
public static final int OFF_DATA_OFF = 50;
public static final int OFF_DATA_LEN = 54;
public static final int OFF_CHECKSUM = 58;
private CbePluginConstants() {}
}
@@ -0,0 +1,24 @@
package com.cbe.core;
public enum CompileMode {
FULL((byte) 0),
HYBRID((byte) 1),
PACK_ONLY((byte) 2);
private final byte id;
CompileMode(byte id) {
this.id = id;
}
public byte getId() {
return id;
}
public static CompileMode fromId(byte id) {
for (CompileMode m : values()) {
if (m.id == id) return m;
}
throw new IllegalArgumentException("Unknown CompileMode id: " + id);
}
}
@@ -0,0 +1,104 @@
package com.cbe.core;
import java.util.Collections;
import java.util.List;
public class Instruction {
private final int opcode;
private final String mnemonic;
private final List<Arg> args;
private final int cycles;
private final List<SemanticOp> semantics;
public Instruction(int opcode, String mnemonic, List<Arg> args, int cycles, List<SemanticOp> semantics) {
this.opcode = opcode;
this.mnemonic = mnemonic;
this.args = args != null ? Collections.unmodifiableList(args) : Collections.<Arg>emptyList();
this.cycles = cycles;
this.semantics = semantics != null ? Collections.unmodifiableList(semantics) : Collections.<SemanticOp>emptyList();
}
public int getOpcode() {
return opcode;
}
public String getMnemonic() {
return mnemonic;
}
public List<Arg> getArgs() {
return args;
}
public int getCycles() {
return cycles;
}
public List<SemanticOp> getSemantics() {
return semantics;
}
public static class Arg {
private final String name;
private final String type;
public Arg(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}
public static class SemanticOp {
private final String operation;
private final String from;
private final String to;
private final String value;
private final String condition;
private final String result;
public SemanticOp(String operation, String from, String to, String value, String condition) {
this(operation, from, to, value, condition, null);
}
public SemanticOp(String operation, String from, String to, String value, String condition, String result) {
this.operation = operation;
this.from = from;
this.to = to;
this.value = value;
this.condition = condition;
this.result = result;
}
public String getOperation() {
return operation;
}
public String getFrom() {
return from;
}
public String getTo() {
return to;
}
public String getValue() {
return value;
}
public String getCondition() {
return condition;
}
public String getResult() {
return result;
}
}
}
@@ -0,0 +1,386 @@
package com.cbe.core;
import java.util.Map;
public class InstructionExecutor {
private static final int MASK_8BIT = 0xFF;
// Pre-computed operation IDs for faster dispatch
private static final int OP_NOP = 0;
private static final int OP_COPY = 1;
private static final int OP_LOAD_IMM = 2;
private static final int OP_ADD = 3;
private static final int OP_SUB = 4;
private static final int OP_CMP = 5;
private static final int OP_STORE = 6;
private static final int OP_LOAD = 7;
private static final int OP_JMP = 8;
private static final int OP_JCC = 9;
private static final int OP_JMP_IMM = 10;
private static final int OP_JCC_IMM = 11;
private static final int OP_CALL = 12;
private static final int OP_RET = 13;
private static final int OP_PUSH = 14;
private static final int OP_POP = 15;
private static final int OP_INC = 16;
private static final java.util.HashMap<String, Integer> OP_IDS = new java.util.HashMap<String, Integer>();
static {
OP_IDS.put("nop", OP_NOP);
OP_IDS.put("copy", OP_COPY);
OP_IDS.put("load_imm", OP_LOAD_IMM);
OP_IDS.put("add", OP_ADD);
OP_IDS.put("sub", OP_SUB);
OP_IDS.put("cmp", OP_CMP);
OP_IDS.put("store", OP_STORE);
OP_IDS.put("load", OP_LOAD);
OP_IDS.put("jmp", OP_JMP);
OP_IDS.put("jcc", OP_JCC);
OP_IDS.put("jmp_imm", OP_JMP_IMM);
OP_IDS.put("jcc_imm", OP_JCC_IMM);
OP_IDS.put("call", OP_CALL);
OP_IDS.put("ret", OP_RET);
OP_IDS.put("push", OP_PUSH);
OP_IDS.put("pop", OP_POP);
OP_IDS.put("inc", OP_INC);
}
// Cached spec type: 0=reg, 1=mem[const], 2=arg.X, 3=imm.X, 4=mem[arg.X], 5=$next
private static int classifySpec(String spec) {
if (spec == null || spec.isEmpty()) return 0;
char c = spec.charAt(0);
if (c == 'a' && spec.startsWith("arg.")) return 2;
if (c == 'i' && spec.startsWith("imm.")) return 3;
if (c == 'm' && spec.startsWith("mem[")) {
if (spec.endsWith("]")) {
if (spec.length() > 5 && spec.charAt(4) == 'a' && spec.startsWith("mem[arg.")) return 4;
// Check if it's $next
if (spec.equals("mem[$next]")) return 5;
return 1; // mem[const]
}
}
if (c == '$' && spec.equals("$next")) return 5;
return 0; // reg or numeric literal
}
// Pre-computed spec info for quick operand resolution
private static class SpecInfo {
final int type; // 0=reg, 1=mem[const], 2=arg.X, 3=imm.X, 4=mem[arg.X], 5=$next
final String value; // register name, argument name, or numeric string
SpecInfo(String spec) {
this.type = classifySpec(spec);
switch (type) {
case 1: this.value = spec.substring(4, spec.length() - 1); break;
case 2: this.value = spec.substring(4); break;
case 3: this.value = spec.substring(4); break;
case 4: this.value = spec.substring(8, spec.length() - 1); break;
default: this.value = spec; break;
}
}
}
private final Map<Integer, Instruction> instructionMap;
// Per-instruction cached spec info
private final java.util.HashMap<Instruction, CachedSpecs> specCache;
private static class CachedSpecs {
final int[] opIds;
final SpecInfo[] fromSpecs;
final SpecInfo[] toSpecs;
final SpecInfo[] resultSpecs;
final String[] valueSpecs;
final String[] conditions;
CachedSpecs(Instruction inst) {
java.util.List<Instruction.SemanticOp> ops = inst.getSemantics();
int n = ops.size();
opIds = new int[n];
fromSpecs = new SpecInfo[n];
toSpecs = new SpecInfo[n];
resultSpecs = new SpecInfo[n];
valueSpecs = new String[n];
conditions = new String[n];
for (int i = 0; i < n; i++) {
Instruction.SemanticOp op = ops.get(i);
Integer id = OP_IDS.get(op.getOperation());
opIds[i] = id != null ? id : -1;
fromSpecs[i] = new SpecInfo(op.getFrom());
toSpecs[i] = new SpecInfo(op.getTo());
resultSpecs[i] = new SpecInfo(op.getResult());
valueSpecs[i] = op.getValue();
conditions[i] = op.getCondition();
}
}
}
public InstructionExecutor(Map<Integer, Instruction> instructionMap) {
this.instructionMap = instructionMap;
this.specCache = new java.util.HashMap<Instruction, CachedSpecs>();
}
public OpcodeResult execute(int opcode, Registers regs, Bus bus, byte[] memory) {
Instruction inst = instructionMap.get(opcode);
if (inst == null) {
throw new IllegalArgumentException("Unknown opcode: 0x" + Integer.toHexString(opcode & 0xFF));
}
// Always 8-bit for TinyCPU
int mask = MASK_8BIT;
CachedSpecs cached = specCache.get(inst);
if (cached == null) {
cached = new CachedSpecs(inst);
specCache.put(inst, cached);
}
for (int i = 0; i < cached.opIds.length; i++) {
executeCached(cached, i, regs, bus, memory, mask);
}
return OpcodeResult.ok(inst.getCycles());
}
private void executeCached(CachedSpecs cached, int idx, Registers regs, Bus bus, byte[] memory, int mask) {
switch (cached.opIds[idx]) {
case OP_NOP:
break;
case OP_COPY: {
int srcVal = resolveValueCached(cached.fromSpecs[idx], regs, memory);
writeCached(cached.toSpecs[idx], srcVal & mask, regs, memory);
break;
}
case OP_LOAD_IMM: {
SpecInfo to = cached.toSpecs[idx];
if (to.type != 0 || to.value == null) break;
int immValue;
String valSpec = cached.valueSpecs[idx];
if (valSpec != null && valSpec.equals("$next")) {
int nextAddr = regs.read("pc");
immValue = nextAddr < memory.length ? (memory[nextAddr] & 0xFF) : 0;
regs.write("pc", nextAddr + 1);
} else if (valSpec != null) {
immValue = Integer.parseInt(valSpec.trim());
} else {
immValue = 0;
}
regs.write(to.value, immValue & mask);
break;
}
case OP_ADD: {
int srcVal = resolveValueCached(cached.fromSpecs[idx], regs, memory);
int dstVal = resolveValueCached(cached.toSpecs[idx], regs, memory);
int result = srcVal + dstVal;
int masked = result & mask;
String dest = cached.resultSpecs[idx].value != null ? cached.resultSpecs[idx].value : cached.toSpecs[idx].value;
if (dest != null) regs.write(dest, masked);
regs.write("carry", (result != masked) ? 1 : 0);
regs.write("zero", (masked == 0) ? 1 : 0);
break;
}
case OP_SUB: {
int a = resolveValueCached(cached.fromSpecs[idx], regs, memory);
int b = resolveValueCached(cached.toSpecs[idx], regs, memory);
int result = a - b;
int masked = result & mask;
String dest = cached.resultSpecs[idx].value != null ? cached.resultSpecs[idx].value : cached.toSpecs[idx].value;
if (dest != null) regs.write(dest, masked);
regs.write("carry", (result < 0) ? 1 : 0);
regs.write("zero", (masked == 0) ? 1 : 0);
break;
}
case OP_CMP: {
int a = resolveValueCached(cached.fromSpecs[idx], regs, memory);
int b = resolveValueCached(cached.toSpecs[idx], regs, memory);
int result = a - b;
regs.write("zero", ((result & mask) == 0) ? 1 : 0);
regs.write("carry", (result < 0) ? 1 : 0);
break;
}
case OP_STORE: {
int val = resolveValueCached(cached.fromSpecs[idx], regs, memory);
int addr = resolveAddressCached(cached.toSpecs[idx], regs, memory);
if (addr >= 0 && addr < memory.length) {
memory[addr] = (byte) (val & 0xFF);
}
break;
}
case OP_LOAD: {
int addr = resolveAddressCached(cached.fromSpecs[idx], regs, memory);
int val = (addr >= 0 && addr < memory.length) ? (memory[addr] & 0xFF) : 0;
writeCached(cached.toSpecs[idx], val, regs, memory);
break;
}
case OP_JMP: {
int addr = resolveValueCached(cached.toSpecs[idx], regs, memory);
regs.write("pc", addr & mask);
break;
}
case OP_JCC: {
String cond = cached.conditions[idx];
if (cond != null) {
int comma = cond.indexOf(',');
if (comma > 0) {
String flagName = cond.substring(0, comma).trim();
int expected = Integer.parseInt(cond.substring(comma + 1).trim());
if (regs.read(flagName) == expected) {
int addr = resolveValueCached(cached.toSpecs[idx], regs, memory);
regs.write("pc", addr & mask);
}
}
}
break;
}
case OP_JMP_IMM: {
int nextAddr = regs.read("pc");
if (nextAddr < memory.length) {
regs.write("pc", memory[nextAddr] & 0xFF);
}
break;
}
case OP_JCC_IMM: {
String cond = cached.conditions[idx];
int nextAddr = regs.read("pc");
if (cond != null && nextAddr < memory.length) {
int comma = cond.indexOf(',');
if (comma > 0) {
String flagName = cond.substring(0, comma).trim();
int expected = Integer.parseInt(cond.substring(comma + 1).trim());
int immTarget = memory[nextAddr] & 0xFF;
if (regs.read(flagName) == expected) {
regs.write("pc", immTarget);
} else {
regs.write("pc", nextAddr + 1);
}
}
}
break;
}
case OP_CALL: {
int retAddr = regs.read("pc");
int sp = regs.read("sp");
if (sp > 0 && sp < memory.length) {
memory[sp - 1] = (byte) (retAddr & 0xFF);
regs.write("sp", sp - 1);
regs.write("pc", resolveValueCached(cached.toSpecs[idx], regs, memory) & mask);
}
break;
}
case OP_RET: {
int sp = regs.read("sp");
if (sp >= 0 && sp < memory.length) {
regs.write("sp", sp + 1);
regs.write("pc", memory[sp] & 0xFF);
}
break;
}
case OP_PUSH: {
int sp = regs.read("sp");
int val = resolveValueCached(cached.fromSpecs[idx], regs, memory);
if (sp > 0 && sp < memory.length) {
memory[sp - 1] = (byte) (val & 0xFF);
regs.write("sp", sp - 1);
}
break;
}
case OP_POP: {
int sp = regs.read("sp");
if (sp >= 0 && sp < memory.length) {
int val = memory[sp] & 0xFF;
regs.write("sp", sp + 1);
writeCached(cached.toSpecs[idx], val, regs, memory);
}
break;
}
case OP_INC: {
int val = resolveValueCached(cached.fromSpecs[idx], regs, memory);
writeCached(cached.toSpecs[idx], (val + 1) & mask, regs, memory);
break;
}
default:
throw new IllegalArgumentException("Unknown semantic operation: " + cached.opIds[idx]);
}
}
private static int resolveAddressCached(SpecInfo spec, Registers regs, byte[] memory) {
switch (spec.type) {
case 5: { // $next
int pc = regs.read("pc");
if (pc < memory.length) {
regs.write("pc", pc + 1);
return memory[pc] & 0xFF;
}
return 0;
}
case 1: // mem[const]
return Integer.parseInt(spec.value);
case 2: // arg.X
return regs.read(spec.value);
case 4: // mem[arg.X]
return regs.read(spec.value);
case 0: // register value as address
return regs.read(spec.value);
default:
return 0;
}
}
private static int resolveValueCached(SpecInfo spec, Registers regs, byte[] memory) {
switch (spec.type) {
case 0: { // register or numeric literal
String v = spec.value;
if (v == null || v.isEmpty()) return 0;
char c = v.charAt(0);
if (c >= '0' && c <= '9') {
try { return Integer.parseInt(v); } catch (NumberFormatException e) { return regs.read(v); }
}
if (c == '-') {
try { return Integer.parseInt(v); } catch (NumberFormatException e) { return regs.read(v); }
}
return regs.read(v);
}
case 1: // mem[const]
return memory[Integer.parseInt(spec.value)] & 0xFF;
case 2: // arg.X
return regs.read(spec.value);
case 3: // imm.X
return Integer.parseInt(spec.value);
case 4: { // mem[arg.X]
int addr = regs.read(spec.value);
return addr < memory.length ? (memory[addr] & 0xFF) : 0;
}
case 5: { // $next
int pc = regs.read("pc");
if (pc < memory.length) {
regs.write("pc", pc + 1);
return memory[pc] & 0xFF;
}
return 0;
}
default:
return 0;
}
}
private static void writeCached(SpecInfo spec, int value, Registers regs, byte[] memory) {
if (spec == null || spec.value == null) return;
switch (spec.type) {
case 0:
regs.write(spec.value, value);
break;
case 1: // mem[const]
memory[Integer.parseInt(spec.value)] = (byte) value;
break;
case 2: // arg.X
regs.write(spec.value, value);
break;
case 4: { // mem[arg.X]
int addr = regs.read(spec.value);
if (addr >= 0 && addr < memory.length) memory[addr] = (byte) value;
break;
}
}
}
}
@@ -0,0 +1,13 @@
package com.cbe.core;
public interface ModuleInstance {
ModuleMetadata getMetadata();
String getName();
void init(Bus bus);
void reset();
void destroy();
OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus);
byte[] readData(int bank, int offset, int size);
void writeData(int bank, int offset, byte[] data);
byte[] getMicrocode(String command);
}
@@ -0,0 +1,51 @@
package com.cbe.core;
import java.io.Serializable;
public class ModuleMetadata implements Serializable {
private static final long serialVersionUID = 2L;
private final String name;
private final String arch;
private final ModuleType type;
private final int version;
private final float tdp;
private final int maxFrequency; // Hz, 0 = unknown
public ModuleMetadata(String name, String arch, ModuleType type, int version, float tdp) {
this(name, arch, type, version, tdp, 0);
}
public ModuleMetadata(String name, String arch, ModuleType type, int version, float tdp, int maxFrequency) {
this.name = name;
this.arch = arch;
this.type = type;
this.version = version;
this.tdp = tdp;
this.maxFrequency = maxFrequency;
}
public String getName() {
return name;
}
public String getArch() {
return arch;
}
public ModuleType getType() {
return type;
}
public int getVersion() {
return version;
}
public float getTdp() {
return tdp;
}
public int getMaxFrequency() {
return maxFrequency;
}
}
@@ -0,0 +1,29 @@
package com.cbe.core;
public enum ModuleType {
CPU((byte) 0),
RAM((byte) 1),
DISK((byte) 2),
GPU((byte) 3),
BIOS((byte) 4),
KBD((byte) 5),
SND((byte) 6),
DATA_ONLY((byte) 7);
private final byte id;
ModuleType(byte id) {
this.id = id;
}
public byte getId() {
return id;
}
public static ModuleType fromId(byte id) {
for (ModuleType t : values()) {
if (t.id == id) return t;
}
throw new IllegalArgumentException("Unknown ModuleType id: " + id);
}
}
@@ -0,0 +1,27 @@
package com.cbe.core;
public class OpcodeResult {
private final boolean halt;
private final int cyclesConsumed;
public OpcodeResult(boolean halt, int cyclesConsumed) {
this.halt = halt;
this.cyclesConsumed = cyclesConsumed;
}
public static OpcodeResult ok(int cycles) {
return new OpcodeResult(false, cycles);
}
public static OpcodeResult halt(int cycles) {
return new OpcodeResult(true, cycles);
}
public boolean isHalt() {
return halt;
}
public int getCyclesConsumed() {
return cyclesConsumed;
}
}
@@ -0,0 +1,132 @@
package com.cbe.core;
/**
* POST (Power-On Self-Test) codes.
* Each code is a single byte that the system reports as it initializes.
*
* Layout convention:
* 0x0_ = power-on phase
* 0x1_ = CPU
* 0x2_ = memory
* 0x3_ = video
* 0x4_ = keyboard
* 0x5_ = sound
* 0x6_ = disk
* 0x7_ = BIOS
* 0x8_ = OS / boot
* 0xA_ = runtime
* 0xE_ = warning
* 0xF_ = fatal
*
* The engine writes the current POST code to the special memory address
* {@link #POST_CODE_ADDR}. The GUI reads from there for visualization.
*
* The COMPONENT_OK bitmask lives in the LED register at {@link #LED_STATUS_ADDR}.
*/
public enum PostCode {
POWER_ON(0x01),
CPU_DETECTED(0x10), CPU_OK(0x11), CPU_FAIL(0x12),
MEM_DETECTED(0x20), MEM_OK(0x21), MEM_FAIL(0x22),
VIDEO_DETECTED(0x30), VIDEO_OK(0x31), VIDEO_FAIL(0x32),
KBD_DETECTED(0x40), KBD_OK(0x41), KBD_FAIL(0x42),
SND_DETECTED(0x50), SND_OK(0x51), SND_FAIL(0x52),
DISK_DETECTED(0x60), DISK_OK(0x61), DISK_FAIL(0x62),
BIOS_LOADING(0x70), BIOS_OK(0x71), BIOS_FAIL(0x72),
BIOS_CPU(0x73), BIOS_RAM(0x74), BIOS_GPU(0x75),
BIOS_KBD(0x76), BIOS_SND(0x77), BIOS_DONE(0x78),
BOOT_LOAD(0x80), BOOT_OK(0x81),
RUNTIME(0xA0),
WARNING(0xE0),
FATAL(0xF0);
public static final int POST_CODE_ADDR = 0xFE; // current POST code
public static final int ERROR_CODE_ADDR = 0xFD; // last error code
public static final int LED_STATUS_ADDR = 0xFC; // bitmask of components OK
public static final int KBD_DATA_ADDR = 0xFB; // KBD: next key (or 0 if none)
public static final int KBD_STATUS_ADDR = 0xFA; // KBD: bit0=key available
public static final int SND_BEEP_ADDR = 0xF9; // SND: any write triggers beep
public static final int BIOS_TRIGGER = 0xF7; // write any value to invoke BIOS diagnostics
public static final int BIOS_INFO_BASE = 0xE0; // 16 bytes of BIOS info (0xE0..0xEF)
public static final int DEVICE_TABLE_BASE = 0xD0; // 16 bytes device table (0xD0..0xDF)
public static final int GPU_OUT_CHAR = 0xC0; // write byte to output char on GPU display
public static final int DISK_SECTOR_ADDR = 0xB0; // write sector LBA here to queue a disk read
public static final int DISK_READY_ADDR = 0xB1; // read: 1=disk I/O ready, 0=busy
public static final int DISK_DATA_BASE = 0xB2; // 256 bytes of disk sector data (0xB2..0xFF)
// LED bits for the visualizer
public static final int LED_PWR = 0x01;
public static final int LED_CPU = 0x02;
public static final int LED_MEM = 0x04;
public static final int LED_VID = 0x08;
public static final int LED_KBD = 0x10;
public static final int LED_SND = 0x20;
public static final int LED_DSK = 0x40;
public static final int LED_CLK = 0x80;
public final int code;
PostCode(int code) { this.code = code; }
public static int componentLed(int code) {
int hi = (code >> 4) & 0x0F;
if (hi == 0x7) {
// BIOS codes (0x70-0x7F): map to the component being tested
int lo = code & 0x0F;
switch (lo) {
case 0x3: return LED_CPU; // BIOS_CPU
case 0x4: return LED_MEM; // BIOS_RAM
case 0x5: return LED_VID; // BIOS_GPU
case 0x6: return LED_KBD; // BIOS_KBD
case 0x7: return LED_SND; // BIOS_SND
case 0x8: return LED_DSK; // BIOS_DONE / boot attempt
default: return 0;
}
}
switch (hi) {
case 0x0: return LED_PWR;
case 0x1: return LED_CPU;
case 0x2: return LED_MEM;
case 0x3: return LED_VID;
case 0x4: return LED_KBD;
case 0x5: return LED_SND;
case 0x6: return LED_DSK;
case 0x8: return LED_DSK; // BOOT - disk LED
case 0xA: return LED_CLK; // runtime
default: return 0;
}
}
public static String describe(int code) {
switch (code) {
case 0x00: return "Idle";
case 0x01: return "Power on";
case 0x10: return "CPU detected";
case 0x11: return "CPU OK";
case 0x12: return "CPU FAIL";
case 0x20: return "Memory detected";
case 0x21: return "Memory OK";
case 0x22: return "Memory FAIL";
case 0x30: return "Video detected";
case 0x31: return "Video OK";
case 0x32: return "Video FAIL";
case 0x40: return "Keyboard detected";
case 0x41: return "Keyboard OK";
case 0x42: return "Keyboard FAIL";
case 0x50: return "Sound detected";
case 0x51: return "Sound OK";
case 0x52: return "Sound FAIL";
case 0x60: return "Disk detected";
case 0x61: return "Disk OK";
case 0x62: return "Disk FAIL";
case 0x70: return "BIOS loading";
case 0x71: return "BIOS OK";
case 0x72: return "BIOS FAIL";
case 0x80: return "Boot loader";
case 0x81: return "Boot OK";
case 0xA0: return "Runtime";
case 0xE0: return "WARNING";
case 0xF0: return "FATAL";
default: return "Code 0x" + Integer.toHexString(code);
}
}
}
@@ -0,0 +1,8 @@
package com.cbe.core;
public interface Registers {
int read(String name);
void write(String name, int value);
java.util.Set<String> names();
void reset();
}
@@ -0,0 +1,66 @@
package com.cbe.loader;
import com.cbe.core.*;
import java.util.Arrays;
public abstract class AbstractModuleInstance implements ModuleInstance {
protected final ModuleMetadata metadata;
protected final byte[][] dataBanks;
public AbstractModuleInstance(ModuleMetadata metadata, int bankCount, int bankSize) {
this.metadata = metadata;
this.dataBanks = new byte[bankCount][bankSize];
}
public AbstractModuleInstance(ModuleMetadata metadata, byte[][] dataBanks) {
this.metadata = metadata;
this.dataBanks = dataBanks;
}
public AbstractModuleInstance(ModuleMetadata metadata) {
this.metadata = metadata;
this.dataBanks = new byte[0][];
}
@Override
public ModuleMetadata getMetadata() {
return metadata;
}
@Override
public String getName() {
return metadata.getName();
}
@Override
public void init(Bus bus) {
}
@Override
public void reset() {
}
@Override
public void destroy() {
}
@Override
public byte[] readData(int bank, int offset, int size) {
if (bank < 0 || bank >= dataBanks.length) return new byte[size];
byte[] bankData = dataBanks[bank];
if (bankData == null) return new byte[size];
if (offset >= bankData.length) return new byte[size];
int actualSize = Math.min(size, bankData.length - offset);
return Arrays.copyOfRange(bankData, offset, offset + actualSize);
}
@Override
public void writeData(int bank, int offset, byte[] data) {
if (bank < 0 || bank >= dataBanks.length) return;
byte[] bankData = dataBanks[bank];
if (bankData == null || offset >= bankData.length) return;
int actualSize = Math.min(data.length, bankData.length - offset);
System.arraycopy(data, 0, bankData, offset, actualSize);
}
}
@@ -0,0 +1,13 @@
package com.cbe.loader;
public class AudioBridge {
private static BeepHandler handler;
public static void setBeepHandler(BeepHandler h) {
handler = h;
}
public static void beep() {
if (handler != null) handler.beep();
}
}
@@ -0,0 +1,5 @@
package com.cbe.loader;
public interface BeepHandler {
void beep();
}
@@ -0,0 +1,568 @@
package com.cbe.loader;
import com.cbe.core.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.CRC32;
public class CompiledModuleLoader {
public ModuleInstance load(Path file) throws ModuleLoadException {
try {
byte[] raw = Files.readAllBytes(file);
return parse(raw);
} catch (IOException e) {
throw new ModuleLoadException("Failed to read .cbeplugin: " + file, e);
}
}
public ModuleInstance loadFromBytes(byte[] raw) throws ModuleLoadException {
return parse(raw);
}
private ModuleInstance parse(byte[] raw) throws ModuleLoadException {
if (raw.length < CbePluginConstants.HEADER_SIZE) {
throw new ModuleLoadException("File too small for valid .cbeplugin");
}
ByteBuffer buf = ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);
byte[] magicBytes = new byte[8];
buf.get(magicBytes);
String magic = new String(magicBytes, StandardCharsets.US_ASCII);
if (!magic.equals(CbePluginConstants.MAGIC)) {
throw new ModuleLoadException("Invalid magic: expected CBEPLUGIN, got " + magic);
}
int version = buf.getInt() & 0xFFFFFFFF;
int headerSize = buf.getInt() & 0xFFFFFFFF;
ModuleType moduleType = ModuleType.fromId(buf.get());
CompileMode compileMode = CompileMode.fromId(buf.get());
int metadataOff = buf.getInt();
int metadataLen = buf.getInt();
int opcodeOff = buf.getInt();
int opcodeLen = buf.getInt();
int microcodeOff = buf.getInt();
int microcodeLen = buf.getInt();
int handlerOff = buf.getInt();
int handlerLen = buf.getInt();
int dataOff = buf.getInt();
int dataLen = buf.getInt();
int storedChecksum = buf.getInt();
// Verify checksum (everything before checksum field)
CRC32 crc = new CRC32();
crc.update(raw, 0, CbePluginConstants.OFF_CHECKSUM);
if ((int) crc.getValue() != storedChecksum) {
throw new ModuleLoadException("Checksum mismatch");
}
// Parse metadata
ModuleMetadata metadata = parseMetadata(raw, metadataOff, metadataLen);
// Load opcode table
Map<Integer, Instruction> instructions = new HashMap<Integer, Instruction>();
if (opcodeOff > 0 && opcodeLen > 0) {
instructions = parseOpcodeTable(raw, opcodeOff, opcodeLen);
}
// Load microcodes
Map<String, byte[]> microcodes = new HashMap<String, byte[]>();
if (microcodeOff > 0 && microcodeLen > 0) {
microcodes = parseMicrocodeTable(raw, microcodeOff, microcodeLen);
}
// Load handler bytecode
byte[] handlerBytecode = new byte[0];
if (handlerOff > 0 && handlerLen > 0) {
handlerBytecode = Arrays.copyOfRange(raw, handlerOff, handlerOff + handlerLen);
}
// Load data banks
byte[][] dataBanks = new byte[0][];
if (dataOff > 0 && dataLen > 0) {
dataBanks = parseDataSection(raw, dataOff, dataLen);
}
switch (moduleType) {
case CPU:
return new CompiledCpuInstance(metadata, instructions, microcodes, handlerBytecode, dataBanks);
case RAM:
return new CompiledRamInstance(metadata, dataBanks, microcodes);
case GPU:
return new CompiledGpuInstance(metadata, dataBanks, microcodes);
case KBD:
return new CompiledKbdInstance(metadata);
case SND:
return new CompiledSndInstance(metadata);
case BIOS:
return new CompiledBiosInstance(metadata);
case DISK:
return new CompiledDiskInstance(metadata, dataBanks);
default:
throw new ModuleLoadException("Unsupported compiled module type: " + moduleType);
}
}
private ModuleMetadata parseMetadata(byte[] raw, int off, int len) {
if (len <= 0) {
return new ModuleMetadata("unknown", "generic", ModuleType.DATA_ONLY, 1, 0);
}
String json = new String(raw, off, len, StandardCharsets.UTF_8);
return metadataFromJson(json);
}
private ModuleMetadata metadataFromJson(String json) {
try {
Map<String, Object> map = JsonUtils.parseObject(json);
String name = map.containsKey("name") ? map.get("name").toString() : "unknown";
String arch = map.containsKey("arch") ? map.get("arch").toString() : "generic";
int version = map.containsKey("version") ? ((Number) map.get("version")).intValue() : 1;
float tdp = map.containsKey("tdp") ? ((Number) map.get("tdp")).floatValue() : 0;
int freq = map.containsKey("frequency") ? ((Number) map.get("frequency")).intValue() : 0;
ModuleType type = ModuleType.DATA_ONLY;
if (map.containsKey("module_type")) {
try {
type = ModuleType.valueOf(map.get("module_type").toString().toUpperCase());
} catch (IllegalArgumentException e) {
type = ModuleType.DATA_ONLY;
}
}
return new ModuleMetadata(name, arch, type, version, tdp, freq);
} catch (Exception e) {
return new ModuleMetadata("unknown", "generic", ModuleType.DATA_ONLY, 1, 0);
}
}
private Map<Integer, Instruction> parseOpcodeTable(byte[] raw, int off, int len) {
Map<Integer, Instruction> result = new HashMap<Integer, Instruction>();
String json = new String(raw, off, len, StandardCharsets.UTF_8);
try {
Object parsed = JsonUtils.parseValue(json);
if (parsed instanceof List) {
List<Object> list = (List<Object>) parsed;
for (Object item : list) {
if (item instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) item;
int opcode = ((Number) map.get("opcode")).intValue();
String mnemonic = map.containsKey("mnemonic") ? map.get("mnemonic").toString() : "UNK";
int cycles = map.containsKey("cycles") ? ((Number) map.get("cycles")).intValue() : 1;
List<Instruction.Arg> args = new ArrayList<Instruction.Arg>();
if (map.containsKey("args")) {
for (Object a : (List<Object>) map.get("args")) {
@SuppressWarnings("unchecked")
Map<String, Object> am = (Map<String, Object>) a;
args.add(new Instruction.Arg(
am.get("name").toString(),
am.get("type").toString()
));
}
}
List<Instruction.SemanticOp> semantics = new ArrayList<Instruction.SemanticOp>();
if (map.containsKey("semantics")) {
for (Object s : (List<Object>) map.get("semantics")) {
@SuppressWarnings("unchecked")
Map<String, Object> sm = (Map<String, Object>) s;
semantics.add(new Instruction.SemanticOp(
sm.containsKey("op") ? sm.get("op").toString() : null,
sm.containsKey("from") ? sm.get("from").toString() : null,
sm.containsKey("to") ? sm.get("to").toString() : null,
sm.containsKey("value") ? sm.get("value").toString() : null,
sm.containsKey("condition") ? sm.get("condition").toString() : null,
sm.containsKey("result") ? sm.get("result").toString() : null
));
}
}
result.put(opcode, new Instruction(opcode, mnemonic, args, cycles, semantics));
}
}
}
} catch (Exception e) {
// If parsing fails, return empty map
}
return result;
}
private Map<String, byte[]> parseMicrocodeTable(byte[] raw, int off, int len) {
Map<String, byte[]> result = new HashMap<String, byte[]>();
String json = new String(raw, off, len, StandardCharsets.UTF_8);
try {
Object parsed = JsonUtils.parseValue(json);
if (parsed instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) parsed;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof String) {
result.put(entry.getKey(), ((String) entry.getValue()).getBytes(StandardCharsets.UTF_8));
} else if (entry.getValue() instanceof List) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (Object v : (List<Object>) entry.getValue()) {
if (v instanceof Number) baos.write(((Number) v).byteValue());
}
result.put(entry.getKey(), baos.toByteArray());
}
}
}
} catch (Exception e) {
// ignore parse errors, return empty
}
return result;
}
private byte[][] parseDataSection(byte[] raw, int off, int len) {
// Simple format: [bank_count:4bytes][bank0_size:4bytes][bank0_data...][bank1_size:4bytes]...
if (len < 4) return new byte[0][];
ByteBuffer buf = ByteBuffer.wrap(raw, off, len).order(ByteOrder.LITTLE_ENDIAN);
int bankCount = buf.getInt();
if (bankCount <= 0 || bankCount > 256) return new byte[0][];
byte[][] banks = new byte[bankCount][];
for (int i = 0; i < bankCount; i++) {
if (buf.remaining() < 4) break;
int bankSize = buf.getInt();
if (bankSize <= 0 || bankSize > 1024 * 1024) break;
bankSize = Math.min(bankSize, buf.remaining());
banks[i] = new byte[bankSize];
buf.get(banks[i]);
}
return banks;
}
// ----- Compiled CPU Instance -----
public static class CompiledCpuInstance extends AbstractModuleInstance {
private final Map<Integer, Instruction> instructions;
private final InstructionExecutor executor;
private final Map<String, byte[]> microcodes;
private final byte[] workingMemory;
CompiledCpuInstance(ModuleMetadata metadata, Map<Integer, Instruction> instructions,
Map<String, byte[]> microcodes, byte[] handlerBytecode,
byte[][] dataBanks) {
super(metadata, dataBanks);
this.instructions = instructions;
this.executor = new InstructionExecutor(instructions);
this.microcodes = microcodes;
// Unified 64K address space: copy the program ROM at the start, zero-fill the rest.
// This prevents STORE/LOAD from corrupting the program ROM while still letting
// programs read constants from the ROM area via $next or mem[N] with N < romSize.
this.workingMemory = new byte[65536];
if (dataBanks.length > 0 && dataBanks[0] != null) {
int copyLen = Math.min(dataBanks[0].length, 65536);
System.arraycopy(dataBanks[0], 0, workingMemory, 0, copyLen);
}
}
public byte[] getWorkingMemory() { return workingMemory; }
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
if (opcode == 0xFF) {
return OpcodeResult.halt(1);
}
return executor.execute(opcode, regs, bus, workingMemory);
}
@Override
public byte[] getMicrocode(String command) {
byte[] mc = microcodes.get(command);
return mc != null ? mc.clone() : new byte[0];
}
public byte[] getRawDataBank(int index) {
if (index < 0 || index >= dataBanks.length) return null;
return dataBanks[index];
}
}
// ----- Compiled RAM Instance -----
private static class CompiledRamInstance extends AbstractModuleInstance {
private final Map<String, byte[]> microcodes;
CompiledRamInstance(ModuleMetadata metadata, byte[][] banks, Map<String, byte[]> microcodes) {
super(metadata, banks);
this.microcodes = microcodes;
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) {
byte[] mc = microcodes.get(command);
return mc != null ? mc.clone() : new byte[0];
}
}
public static class CompiledGpuInstance extends AbstractModuleInstance {
private final Map<String, byte[]> microcodes;
private int cursorX;
private int cursorY;
private boolean needsRedraw;
private int rows = 25;
private int cols = 80;
CompiledGpuInstance(ModuleMetadata metadata, byte[][] banks, Map<String, byte[]> microcodes) {
super(metadata, banks);
this.microcodes = microcodes;
this.cursorX = 0;
this.cursorY = 0;
this.needsRedraw = true;
if (banks.length > 0 && banks[0].length >= 2000) {
this.rows = 25;
this.cols = banks[0].length / 25;
}
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) {
byte[] mc = microcodes.get(command);
return mc != null ? mc.clone() : new byte[0];
}
public byte[] getVram() {
return dataBanks.length > 0 ? dataBanks[0] : new byte[0];
}
public int getRows() { return rows; }
public int getCols() { return cols; }
public int getCursorX() { return cursorX; }
public int getCursorY() { return cursorY; }
public boolean isDirty() { return needsRedraw; }
public void markClean() { needsRedraw = false; }
public void writeChar(char c) {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
int offset = cursorY * cols + cursorX;
if (offset >= vram.length) return;
vram[offset] = (byte) (c & 0x7F);
cursorX++;
if (cursorX >= cols) {
cursorX = 0;
cursorY++;
if (cursorY >= rows) scroll();
}
needsRedraw = true;
}
public void writeString(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\n') {
cursorX = 0;
cursorY++;
if (cursorY >= rows) scroll();
} else {
writeChar(c);
}
}
needsRedraw = true;
}
public void clear() {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
for (int i = 0; i < vram.length; i++) vram[i] = 0;
cursorX = 0;
cursorY = 0;
needsRedraw = true;
}
public void setCursor(int x, int y) {
this.cursorX = Math.max(0, Math.min(cols - 1, x));
this.cursorY = Math.max(0, Math.min(rows - 1, y));
needsRedraw = true;
}
private void scroll() {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
for (int row = 0; row < rows - 1; row++) {
for (int col = 0; col < cols; col++) {
vram[row * cols + col] = vram[(row + 1) * cols + col];
}
}
for (int col = 0; col < cols; col++) {
vram[(rows - 1) * cols + col] = 0;
}
cursorY = rows - 1;
needsRedraw = true;
}
public String readString() {
if (dataBanks.length == 0) return "";
byte[] vram = dataBanks[0];
StringBuilder sb = new StringBuilder();
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
int b = vram[row * cols + col] & 0xFF;
if (b == 0) sb.append(' ');
else sb.append((char) (b & 0x7F));
}
if (row < rows - 1) sb.append('\n');
}
return sb.toString();
}
}
// ========================================================================
// Compiled KBD - keyboard input device
// ========================================================================
public static class CompiledKbdInstance extends AbstractModuleInstance {
private final java.util.concurrent.LinkedBlockingQueue<Integer> keyBuffer;
CompiledKbdInstance(ModuleMetadata metadata) {
super(metadata);
this.keyBuffer = new java.util.concurrent.LinkedBlockingQueue<Integer>(32);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] readData(int bank, int offset, int size) {
byte[] result = new byte[size];
if (offset == com.cbe.core.PostCode.KBD_DATA_ADDR) {
// Peek: don't remove. CPU consumes by writing 0 to KBD_STATUS_ADDR.
Integer k = keyBuffer.peek();
result[0] = (byte) (k != null ? (k & 0xFF) : 0);
} else if (offset == com.cbe.core.PostCode.KBD_STATUS_ADDR) {
result[0] = (byte) (keyBuffer.isEmpty() ? 0 : 1);
}
return result;
}
@Override
public void writeData(int bank, int offset, byte[] data) {
// Writing 0 to KBD_STATUS_ADDR acknowledges the key (consume it).
if (offset == com.cbe.core.PostCode.KBD_STATUS_ADDR && data != null && data.length > 0
&& (data[0] & 0xFF) == 0) {
keyBuffer.poll();
}
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void pushKey(int keyCode) {
if (keyBuffer.remainingCapacity() == 0) keyBuffer.poll();
keyBuffer.offer(keyCode & 0xFF);
}
public void clear() { keyBuffer.clear(); }
public int available() { return keyBuffer.size(); }
/** Consume the next key (called by the Engine when the CPU writes 0 to KBD_STATUS_ADDR). */
public void acknowledge() { keyBuffer.poll(); }
}
// ========================================================================
// Compiled SND - sound/beep device
// ========================================================================
public static class CompiledSndInstance extends AbstractModuleInstance {
private long lastBeepNs = 0;
private int beepCount = 0;
CompiledSndInstance(ModuleMetadata metadata) {
super(metadata);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void beep() {
long now = System.nanoTime();
if (now - lastBeepNs < 100_000_000L) return;
lastBeepNs = now;
beepCount++;
AudioBridge.beep();
}
public int beepCount() { return beepCount; }
}
// ========================================================================
// Compiled DISK - provides sector storage
// ========================================================================
public static class CompiledDiskInstance extends AbstractModuleInstance {
private static final int SECTOR_SIZE = 512;
CompiledDiskInstance(ModuleMetadata metadata, byte[][] dataBanks) {
super(metadata, dataBanks);
}
public int getSectorSize() { return SECTOR_SIZE; }
public int getSectorCount() {
return dataBanks.length > 0 && dataBanks[0] != null ? dataBanks[0].length / SECTOR_SIZE : 0;
}
public byte[] getDiskData() {
return dataBanks.length > 0 ? dataBanks[0] : new byte[0];
}
/** Read one sector (0-indexed LBA). Returns sector bytes or 0-filled if out of range. */
public byte[] readSector(int lba) {
if (lba < 0 || dataBanks.length == 0 || dataBanks[0] == null) return new byte[SECTOR_SIZE];
long off = (long) lba * SECTOR_SIZE;
if (off >= dataBanks[0].length) return new byte[SECTOR_SIZE];
int end = (int) Math.min(off + SECTOR_SIZE, dataBanks[0].length);
return java.util.Arrays.copyOfRange(dataBanks[0], (int) off, end);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
}
// ========================================================================
// Compiled BIOS - boot ROM
// ========================================================================
public static class CompiledBiosInstance extends AbstractModuleInstance {
private String systemInfo = "CBE BIOS v0.1 (no scan yet)";
CompiledBiosInstance(ModuleMetadata metadata) {
super(metadata);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void setSystemInfo(String info) { this.systemInfo = info; }
public String getSystemInfo() { return systemInfo; }
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
package com.cbe.loader;
import java.util.*;
public final class JsonUtils {
private JsonUtils() {}
public static Map<String, Object> parseObject(String json) {
json = json.trim();
if (!json.startsWith("{")) return new LinkedHashMap<String, Object>();
json = json.substring(1, json.lastIndexOf('}')).trim();
if (json.isEmpty()) return new LinkedHashMap<String, Object>();
Map<String, Object> result = new LinkedHashMap<String, Object>();
List<String> pairs = splitPairs(json);
for (String pair : pairs) {
int colon = findColon(pair);
if (colon < 0) continue;
String key = pair.substring(0, colon).trim();
if (key.startsWith("\"") && key.endsWith("\"")) {
key = key.substring(1, key.length() - 1);
}
String valueStr = pair.substring(colon + 1).trim();
result.put(key, parseValue(valueStr));
}
return result;
}
public static List<Object> parseArray(String json) {
json = json.trim();
if (!json.startsWith("[")) return new ArrayList<Object>();
json = json.substring(1, json.lastIndexOf(']')).trim();
if (json.isEmpty()) return new ArrayList<Object>();
List<Object> result = new ArrayList<Object>();
List<String> items = splitPairs(json);
for (String item : items) {
result.add(parseValue(item.trim()));
}
return result;
}
public static Object parseValue(String json) {
json = json.trim();
if (json.startsWith("{")) return parseObject(json);
if (json.startsWith("[")) return parseArray(json);
if (json.startsWith("\"")) {
return json.substring(1, json.length() - 1);
}
if (json.equals("true")) return Boolean.TRUE;
if (json.equals("false")) return Boolean.FALSE;
if (json.equals("null")) return null;
try {
if (json.contains(".") || json.contains("e") || json.contains("E")) {
return Double.parseDouble(json);
}
return Integer.parseInt(json);
} catch (NumberFormatException e) {
return json;
}
}
private static List<String> splitPairs(String json) {
List<String> pairs = new ArrayList<String>();
int depth = 0;
int last = 0;
boolean inString = false;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '"' && (i == 0 || json.charAt(i - 1) != '\\')) {
inString = !inString;
}
if (!inString) {
if (c == '{' || c == '[') depth++;
if (c == '}' || c == ']') depth--;
if ((c == ',' || c == ';') && depth == 0) {
pairs.add(json.substring(last, i));
last = i + 1;
}
}
}
pairs.add(json.substring(last));
return pairs;
}
private static int findColon(String s) {
boolean inString = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '"') inString = !inString;
if (c == ':' && !inString) return i;
}
return -1;
}
}
@@ -0,0 +1,11 @@
package com.cbe.loader;
public class ModuleLoadException extends Exception {
public ModuleLoadException(String message) {
super(message);
}
public ModuleLoadException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,41 @@
package com.cbe.loader;
import com.cbe.core.ModuleInstance;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ModuleLoader {
private final SourceModuleLoader sourceLoader;
private final CompiledModuleLoader compiledLoader;
public ModuleLoader() {
this.sourceLoader = new SourceModuleLoader();
this.compiledLoader = new CompiledModuleLoader();
}
public ModuleInstance load(Path path) throws ModuleLoadException {
if (!Files.exists(path)) {
throw new ModuleLoadException("Path does not exist: " + path.toAbsolutePath());
}
if (Files.isDirectory(path)) {
return sourceLoader.load(path);
} else {
String fileName = path.getFileName().toString().toLowerCase();
if (fileName.endsWith(".cbeplugin")) {
return compiledLoader.load(path);
}
throw new ModuleLoadException("Unknown module file format: " + fileName);
}
}
public ModuleInstance loadSource(Path dir) throws ModuleLoadException {
return sourceLoader.load(dir);
}
public ModuleInstance loadCompiled(Path file) throws ModuleLoadException {
return compiledLoader.load(file);
}
}
@@ -0,0 +1,108 @@
package com.cbe.loader;
import com.cbe.core.Bus;
import com.cbe.core.ModuleInstance;
import java.util.LinkedHashMap;
import java.util.Map;
public class SimpleBus implements Bus {
private final Map<Integer, BusMapping> mappings;
private final Map<String, ModuleInstance> devices;
private long clockCycle;
public SimpleBus() {
this.mappings = new LinkedHashMap<Integer, BusMapping>();
this.devices = new LinkedHashMap<String, ModuleInstance>();
this.clockCycle = 0;
}
@Override
public byte read(int address) {
BusMapping mapping = findMapping(address);
if (mapping != null) {
int localAddr = address - mapping.baseAddress;
byte[] data = mapping.device.readData(0, localAddr, 1);
return data != null && data.length > 0 ? data[0] : 0;
}
return 0;
}
@Override
public void write(int address, byte data) {
BusMapping mapping = findMapping(address);
if (mapping != null) {
int localAddr = address - mapping.baseAddress;
mapping.device.writeData(0, localAddr, new byte[]{data});
}
}
@Override
public int readWord(int address) {
int low = read(address) & 0xFF;
int high = read(address + 1) & 0xFF;
return (high << 8) | low;
}
@Override
public void writeWord(int address, int value) {
write(address, (byte) (value & 0xFF));
write(address + 1, (byte) ((value >> 8) & 0xFF));
}
@Override
public long clock() {
return clockCycle;
}
@Override
public long tick() {
return ++clockCycle;
}
@Override
public void attach(String deviceName, int baseAddress, int size) {
ModuleInstance dev = devices.get(deviceName);
if (dev != null) {
mappings.put(baseAddress, new BusMapping(dev, baseAddress, size));
}
}
public void registerDevice(String name, ModuleInstance device) {
devices.put(name, device);
}
@Override
public ModuleInstance getDevice(String name) {
return devices.get(name);
}
@Override
public void reset() {
clockCycle = 0;
for (ModuleInstance dev : devices.values()) {
dev.reset();
}
}
private BusMapping findMapping(int address) {
for (BusMapping m : mappings.values()) {
if (address >= m.baseAddress && address < m.baseAddress + m.size) {
return m;
}
}
return null;
}
private static class BusMapping {
final ModuleInstance device;
final int baseAddress;
final int size;
BusMapping(ModuleInstance device, int baseAddress, int size) {
this.device = device;
this.baseAddress = baseAddress;
this.size = size;
}
}
}
@@ -0,0 +1,51 @@
package com.cbe.loader;
import com.cbe.core.Registers;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class SimpleRegisters implements Registers {
private final Map<String, Integer> registers;
private final Map<String, Integer> defaults;
public SimpleRegisters() {
this(new LinkedHashMap<String, Integer>());
}
public SimpleRegisters(Map<String, Integer> initialValues) {
this.registers = new LinkedHashMap<String, Integer>();
this.defaults = new LinkedHashMap<String, Integer>();
if (initialValues != null) {
for (Map.Entry<String, Integer> e : initialValues.entrySet()) {
int val = e.getValue() != null ? e.getValue() : 0;
this.registers.put(e.getKey(), val);
this.defaults.put(e.getKey(), val);
}
}
}
@Override
public int read(String name) {
Integer val = registers.get(name);
return val != null ? val : 0;
}
@Override
public void write(String name, int value) {
registers.put(name, value);
}
@Override
public Set<String> names() {
return Collections.unmodifiableSet(registers.keySet());
}
@Override
public void reset() {
registers.clear();
registers.putAll(defaults);
}
}
@@ -0,0 +1,723 @@
package com.cbe.loader;
import com.cbe.core.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
public class SourceModuleLoader {
public ModuleInstance load(Path dir) throws ModuleLoadException {
Path moduleJson = dir.resolve("module.json");
if (!Files.exists(moduleJson)) {
throw new ModuleLoadException("Missing module.json in " + dir.toAbsolutePath());
}
Map<String, Object> config = readJson(moduleJson);
ModuleType type = parseModuleType(config);
ModuleMetadata metadata = parseMetadata(config, type);
switch (type) {
case CPU:
return loadCpu(dir, metadata, config);
case RAM:
return loadRam(dir, metadata, config);
case GPU:
return loadGpu(dir, metadata, config);
case KBD:
return loadKbd(dir, metadata, config);
case SND:
return loadSnd(dir, metadata, config);
case BIOS:
return loadBios(dir, metadata, config);
case DISK:
return loadDisk(dir, metadata, config);
default:
throw new ModuleLoadException("Unsupported module type: " + type);
}
}
@SuppressWarnings("unchecked")
private ModuleMetadata parseMetadata(Map<String, Object> config, ModuleType type) {
String name = getString(config, "name", "unknown");
String arch = getString(config, "arch", "generic");
int version = getInt(config, "version", 1);
float tdp = getFloat(config, "tdp", 0.0f);
int freq = getInt(config, "frequency", 0);
return new ModuleMetadata(name, arch, type, version, tdp, freq);
}
private ModuleType parseModuleType(Map<String, Object> config) throws ModuleLoadException {
String typeStr = getString(config, "module_type", null);
if (typeStr == null) {
throw new ModuleLoadException("Missing 'module_type' in module.json");
}
try {
return ModuleType.valueOf(typeStr.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ModuleLoadException("Unknown module_type: " + typeStr);
}
}
private ModuleInstance loadCpu(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
Path registersFile = dir.resolve("registers.json");
if (!Files.exists(registersFile)) {
throw new ModuleLoadException("CPU module missing registers.json");
}
Map<String, Integer> regDefaults = parseRegisters(registersFile);
Path instructionsDir = dir.resolve("instructions");
Map<Integer, Instruction> instructions = new HashMap<Integer, Instruction>();
if (Files.isDirectory(instructionsDir)) {
List<Path> instFiles = listJsonFiles(instructionsDir);
for (Path f : instFiles) {
Instruction inst = parseInstruction(f);
instructions.put(inst.getOpcode(), inst);
}
}
Path microcodeDir = dir.resolve("microcode");
Map<String, byte[]> microcodes = new HashMap<String, byte[]>();
if (Files.isDirectory(microcodeDir)) {
loadMicrocodes(microcodeDir, microcodes);
}
Path romsDir = dir.resolve("roms");
List<byte[]> romBanks = new ArrayList<byte[]>();
if (Files.isDirectory(romsDir)) {
loadRoms(romsDir, romBanks);
}
int memorySize = getInt(config, "memory_size", 256);
return new CpuModuleInstance(metadata, regDefaults, instructions, microcodes, romBanks, memorySize);
}
private ModuleInstance loadRam(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
int bankSize = getInt(config, "bank_size", 256);
int bankCount = getInt(config, "bank_count", 1);
byte[][] banks = new byte[bankCount][bankSize];
Path banksDir = dir.resolve("banks");
if (Files.isDirectory(banksDir)) {
List<Path> bankFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(banksDir, "*.bin")) {
for (Path p : stream) bankFiles.add(p);
} catch (IOException e) {
throw new ModuleLoadException("Failed to list banks", e);
}
Collections.sort(bankFiles);
for (int i = 0; i < Math.min(bankFiles.size(), bankCount); i++) {
try {
byte[] data = Files.readAllBytes(bankFiles.get(i));
System.arraycopy(data, 0, banks[i], 0, Math.min(data.length, bankSize));
} catch (IOException e) {
throw new ModuleLoadException("Failed to read bank: " + bankFiles.get(i), e);
}
}
}
Path controllerDir = dir.resolve("controller");
Map<String, byte[]> microcodes = new HashMap<String, byte[]>();
if (Files.isDirectory(controllerDir)) {
Path mcDir = controllerDir.resolve("microcode");
if (Files.isDirectory(mcDir)) {
loadMicrocodes(mcDir, microcodes);
}
}
return new RamModuleInstance(metadata, banks, microcodes);
}
private ModuleInstance loadGpu(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
int vramSize = getInt(config, "vram_size", 2000);
int rows = getInt(config, "rows", 25);
int cols = getInt(config, "cols", 80);
byte[][] vram = new byte[1][vramSize];
Path banksDir = dir.resolve("banks");
if (Files.isDirectory(banksDir)) {
List<Path> bankFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(banksDir, "*.bin")) {
for (Path p : stream) bankFiles.add(p);
} catch (IOException e) {
throw new ModuleLoadException("Failed to list VRAM banks", e);
}
if (!bankFiles.isEmpty()) {
try {
byte[] data = Files.readAllBytes(bankFiles.get(0));
System.arraycopy(data, 0, vram[0], 0, Math.min(data.length, vramSize));
} catch (IOException e) {
throw new ModuleLoadException("Failed to read VRAM", e);
}
}
}
Path microcodeDir = dir.resolve("microcode");
Map<String, byte[]> microcodes = new HashMap<String, byte[]>();
if (Files.isDirectory(microcodeDir)) {
loadMicrocodes(microcodeDir, microcodes);
}
return new GpuModuleInstance(metadata, vram, microcodes, rows, cols);
}
private ModuleInstance loadKbd(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
int bufferSize = getInt(config, "buffer_size", 16);
return new KbdModuleInstance(metadata, bufferSize);
}
private ModuleInstance loadSnd(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
return new SndModuleInstance(metadata);
}
private ModuleInstance loadBios(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
return new BiosModuleInstance(metadata);
}
private ModuleInstance loadDisk(Path dir, ModuleMetadata metadata, Map<String, Object> config) throws ModuleLoadException {
int sectorSize = getInt(config, "sector_size", 512);
List<byte[]> banks = new ArrayList<byte[]>();
Path banksDir = dir.resolve("banks");
if (Files.isDirectory(banksDir)) {
loadRoms(banksDir, banks);
}
if (banks.isEmpty()) {
banks.add(new byte[sectorSize]);
}
return new DiskModuleInstance(metadata, banks.get(0));
}
@SuppressWarnings("unchecked")
private Map<String, Integer> parseRegisters(Path file) throws ModuleLoadException {
Map<String, Object> json = readJson(file);
Map<String, Integer> registers = new LinkedHashMap<String, Integer>();
for (Map.Entry<String, Object> entry : json.entrySet()) {
if (entry.getValue() instanceof Number) {
registers.put(entry.getKey(), ((Number) entry.getValue()).intValue());
} else {
registers.put(entry.getKey(), 0);
}
}
return registers;
}
@SuppressWarnings("unchecked")
private Instruction parseInstruction(Path file) throws ModuleLoadException {
Map<String, Object> json = readJson(file);
int opcode = getInt(json, "opcode", -1);
if (opcode < 0) {
throw new ModuleLoadException("Instruction missing opcode: " + file);
}
String mnemonic = getString(json, "mnemonic", "UNK");
List<Map<String, Object>> argsRaw = (List<Map<String, Object>>) json.get("args");
List<Instruction.Arg> args = new ArrayList<Instruction.Arg>();
if (argsRaw != null) {
for (Map<String, Object> a : argsRaw) {
args.add(new Instruction.Arg(
getString(a, "name", "?"),
getString(a, "type", "reg")
));
}
}
int cycles = getInt(json, "cycles", 1);
List<Map<String, Object>> semRaw = (List<Map<String, Object>>) json.get("semantics");
List<Instruction.SemanticOp> semantics = new ArrayList<Instruction.SemanticOp>();
if (semRaw != null) {
for (Map<String, Object> s : semRaw) {
semantics.add(new Instruction.SemanticOp(
getString(s, "op", null),
getString(s, "from", null),
getString(s, "to", null),
getString(s, "value", null),
getString(s, "condition", null),
getString(s, "result", null)
));
}
}
return new Instruction(opcode, mnemonic, args, cycles, semantics);
}
private void loadMicrocodes(Path dir, Map<String, byte[]> microcodes) throws ModuleLoadException {
Path busFile = dir.resolve("bus.json");
if (Files.exists(busFile)) {
Map<String, Object> json = readJson(busFile);
for (Map.Entry<String, Object> entry : json.entrySet()) {
if (entry.getValue() instanceof String) {
microcodes.put(entry.getKey(), ((String) entry.getValue()).getBytes(StandardCharsets.UTF_8));
} else if (entry.getValue() instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) entry.getValue();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (Object v : list) {
if (v instanceof Number) {
baos.write(((Number) v).byteValue());
}
}
microcodes.put(entry.getKey(), baos.toByteArray());
}
}
}
}
private void loadRoms(Path dir, List<byte[]> romBanks) throws ModuleLoadException {
try {
List<Path> romFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.bin")) {
for (Path p : stream) romFiles.add(p);
}
Collections.sort(romFiles);
for (Path f : romFiles) {
romBanks.add(Files.readAllBytes(f));
}
} catch (IOException e) {
throw new ModuleLoadException("Failed to load ROMs", e);
}
}
private List<Path> listJsonFiles(Path dir) throws ModuleLoadException {
List<Path> files = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.json")) {
for (Path p : stream) files.add(p);
} catch (IOException e) {
throw new ModuleLoadException("Failed to list JSON files in " + dir, e);
}
Collections.sort(files);
return files;
}
private Map<String, Object> readJson(Path file) throws ModuleLoadException {
try {
String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
return JsonUtils.parseObject(content);
} catch (IOException e) {
throw new ModuleLoadException("Failed to read " + file, e);
}
}
private String getString(Map<String, Object> map, String key, String def) {
Object v = map.get(key);
return v != null ? v.toString() : def;
}
private int getInt(Map<String, Object> map, String key, int def) {
Object v = map.get(key);
if (v instanceof Number) return ((Number) v).intValue();
if (v != null) {
try { return Integer.parseInt(v.toString()); } catch (NumberFormatException e) {}
}
return def;
}
private float getFloat(Map<String, Object> map, String key, float def) {
Object v = map.get(key);
if (v instanceof Number) return ((Number) v).floatValue();
return def;
}
// ----- CPU Module Instance -----
private static class CpuModuleInstance extends AbstractModuleInstance {
private final Map<String, Integer> regDefaults;
private final Map<Integer, Instruction> instructions;
private final InstructionExecutor executor;
private final Map<String, byte[]> microcodes;
private final int memorySize;
private SimpleRegisters registers;
private byte[] memory;
CpuModuleInstance(ModuleMetadata metadata, Map<String, Integer> regDefaults,
Map<Integer, Instruction> instructions,
Map<String, byte[]> microcodes,
List<byte[]> romBanks, int memorySize) {
super(metadata, 0, 0);
this.regDefaults = regDefaults;
this.instructions = instructions;
this.executor = new InstructionExecutor(instructions);
this.microcodes = microcodes;
this.memorySize = memorySize;
this.memory = new byte[memorySize];
this.registers = new SimpleRegisters(regDefaults);
// Load ROM banks into memory
int offset = 0;
for (byte[] rom : romBanks) {
int len = Math.min(rom.length, memory.length - offset);
System.arraycopy(rom, 0, memory, offset, len);
offset += len;
}
}
@Override
public void init(Bus bus) {
this.registers = new SimpleRegisters(regDefaults);
this.memory = new byte[memorySize];
}
@Override
public void reset() {
registers.reset();
memory = new byte[memorySize];
}
@Override
public void destroy() {
memory = null;
registers = null;
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
if (opcode == 0xFF) {
return OpcodeResult.halt(1);
}
return executor.execute(opcode, regs, bus, memory);
}
@Override
public byte[] readData(int bank, int offset, int size) {
if (bank != 0) return new byte[size];
int actualSize = Math.min(size, memory.length - offset);
if (actualSize <= 0) return new byte[size];
return Arrays.copyOfRange(memory, offset, offset + actualSize);
}
@Override
public void writeData(int bank, int offset, byte[] data) {
if (bank != 0) return;
int actualSize = Math.min(data.length, memory.length - offset);
if (actualSize <= 0) return;
System.arraycopy(data, 0, memory, offset, actualSize);
}
@Override
public byte[] getMicrocode(String command) {
return microcodes.get(command);
}
SimpleRegisters getRegisters() {
return registers;
}
byte[] getMemory() {
return memory;
}
Map<Integer, Instruction> getInstructions() {
return instructions;
}
}
// ----- RAM Module Instance -----
private static class RamModuleInstance extends AbstractModuleInstance {
private final Map<String, byte[]> microcodes;
RamModuleInstance(ModuleMetadata metadata, byte[][] banks, Map<String, byte[]> microcodes) {
super(metadata, banks);
this.microcodes = microcodes;
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) {
byte[] mc = microcodes.get(command);
return mc != null ? mc.clone() : new byte[0];
}
}
// ----- GPU Module Instance -----
public static class GpuModuleInstance extends AbstractModuleInstance {
private final Map<String, byte[]> microcodes;
private final int rows;
private final int cols;
private int cursorX;
private int cursorY;
private boolean needsRedraw;
private long lastRedrawCheck;
GpuModuleInstance(ModuleMetadata metadata, byte[][] vram, Map<String, byte[]> microcodes,
int rows, int cols) {
super(metadata, vram);
this.microcodes = microcodes;
this.rows = rows;
this.cols = cols;
this.cursorX = 0;
this.cursorY = 0;
this.needsRedraw = true;
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) {
byte[] mc = microcodes.get(command);
return mc != null ? mc.clone() : new byte[0];
}
public byte[] getVram() {
return dataBanks.length > 0 ? dataBanks[0] : new byte[0];
}
public int getRows() {
return rows;
}
public int getCols() {
return cols;
}
public int getCursorX() {
return cursorX;
}
public int getCursorY() {
return cursorY;
}
public boolean isDirty() {
return needsRedraw;
}
public void markClean() {
needsRedraw = false;
}
public void writeChar(char c) {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
int offset = cursorY * cols + cursorX;
if (offset >= vram.length) return;
vram[offset] = (byte) (c & 0x7F);
cursorX++;
if (cursorX >= cols) {
cursorX = 0;
cursorY++;
if (cursorY >= rows) {
scroll();
}
}
needsRedraw = true;
}
public void writeString(String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\n') {
cursorX = 0;
cursorY++;
if (cursorY >= rows) scroll();
} else {
writeChar(c);
}
}
needsRedraw = true;
}
public void clear() {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
for (int i = 0; i < vram.length; i++) vram[i] = 0;
cursorX = 0;
cursorY = 0;
needsRedraw = true;
}
public void setCursor(int x, int y) {
this.cursorX = Math.max(0, Math.min(cols - 1, x));
this.cursorY = Math.max(0, Math.min(rows - 1, y));
needsRedraw = true;
}
private void scroll() {
if (dataBanks.length == 0) return;
byte[] vram = dataBanks[0];
for (int row = 0; row < rows - 1; row++) {
for (int col = 0; col < cols; col++) {
vram[row * cols + col] = vram[(row + 1) * cols + col];
}
}
for (int col = 0; col < cols; col++) {
vram[(rows - 1) * cols + col] = 0;
}
cursorY = rows - 1;
needsRedraw = true;
}
public String readString() {
if (dataBanks.length == 0) return "";
byte[] vram = dataBanks[0];
StringBuilder sb = new StringBuilder();
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
int b = vram[row * cols + col] & 0xFF;
if (b == 0) {
sb.append(' ');
} else {
sb.append((char) (b & 0x7F));
}
}
if (row < rows - 1) sb.append('\n');
}
return sb.toString();
}
}
// ========================================================================
// KBD Module - keyboard input device
// Memory-mapped at PostCode.KBD_DATA_ADDR / KBD_STATUS_ADDR
// ========================================================================
public static class KbdModuleInstance extends AbstractModuleInstance {
private final java.util.concurrent.LinkedBlockingQueue<Integer> keyBuffer;
private final int bufferSize;
KbdModuleInstance(ModuleMetadata metadata, int bufferSize) {
super(metadata);
this.bufferSize = bufferSize;
this.keyBuffer = new java.util.concurrent.LinkedBlockingQueue<Integer>(bufferSize);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] readData(int bank, int offset, int size) {
byte[] result = new byte[size];
if (offset == com.cbe.core.PostCode.KBD_DATA_ADDR) {
// Peek: don't remove. CPU consumes by writing 0 to KBD_STATUS_ADDR
// (a simple "ack" handshake) or by reading from an internal pointer.
Integer k = keyBuffer.peek();
result[0] = (byte) (k != null ? (k & 0xFF) : 0);
} else if (offset == com.cbe.core.PostCode.KBD_STATUS_ADDR) {
result[0] = (byte) (keyBuffer.isEmpty() ? 0 : 1);
}
return result;
}
@Override
public void writeData(int bank, int offset, byte[] data) {
// Writing 0 to KBD_STATUS_ADDR acknowledges the key (consume it).
if (offset == com.cbe.core.PostCode.KBD_STATUS_ADDR && data != null && data.length > 0
&& (data[0] & 0xFF) == 0) {
keyBuffer.poll();
}
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void pushKey(int keyCode) {
if (keyBuffer.remainingCapacity() == 0) keyBuffer.poll();
keyBuffer.offer(keyCode & 0xFF);
}
public void clear() { keyBuffer.clear(); }
public int bufferSize() { return bufferSize; }
public int available() { return keyBuffer.size(); }
/** Consume the next key (called by the Engine when the CPU writes 0 to KBD_STATUS_ADDR). */
public void acknowledge() { keyBuffer.poll(); }
}
// ========================================================================
// SND Module - sound/beep output device
// Writing to PostCode.SND_BEEP_ADDR triggers a system beep
// ========================================================================
public static class SndModuleInstance extends AbstractModuleInstance {
private long lastBeepNs = 0;
private int beepCount = 0;
SndModuleInstance(ModuleMetadata metadata) {
super(metadata);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void beep() {
long now = System.nanoTime();
if (now - lastBeepNs < 100_000_000L) return;
lastBeepNs = now;
beepCount++;
AudioBridge.beep();
}
public int beepCount() { return beepCount; }
public void resetCount() { beepCount = 0; }
}
// ========================================================================
// BIOS Module - placeholder for boot ROM / POST sequence
// ========================================================================
public static class DiskModuleInstance extends AbstractModuleInstance {
private final byte[] diskData;
private static final int SECTOR_SIZE = 512;
DiskModuleInstance(ModuleMetadata metadata, byte[] diskData) {
super(metadata, new byte[][]{diskData});
this.diskData = diskData;
}
public int getSectorSize() { return SECTOR_SIZE; }
public int getSectorCount() { return diskData.length / SECTOR_SIZE; }
public byte[] getDiskData() { return diskData; }
/** Read one sector (0-indexed LBA). Returns sector bytes or 0-filled if out of range. */
public byte[] readSector(int lba) {
int off = lba * SECTOR_SIZE;
if (lba < 0 || off >= diskData.length) return new byte[SECTOR_SIZE];
return java.util.Arrays.copyOfRange(diskData, off, Math.min(off + SECTOR_SIZE, diskData.length));
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
}
public static class BiosModuleInstance extends AbstractModuleInstance {
private String systemInfo = "CBE BIOS v0.1 (no scan yet)";
BiosModuleInstance(ModuleMetadata metadata) {
super(metadata);
}
@Override
public OpcodeResult executeOpcode(int opcode, Registers regs, Bus bus) {
return OpcodeResult.ok(1);
}
@Override
public byte[] getMicrocode(String command) { return new byte[0]; }
public void setSystemInfo(String info) { this.systemInfo = info; }
public String getSystemInfo() { return systemInfo; }
}
}