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
+19
View File
@@ -0,0 +1,19 @@
apply plugin: 'application'
mainClassName = 'com.cbe.cbecc.Main'
dependencies {
implementation project(':modules:core')
implementation project(':modules:loader')
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
}
application {
applicationName = 'cbecc'
}
task runDemo(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
mainClass = 'com.cbe.cbecc.DemoMain'
workingDir = rootProject.projectDir
}
@@ -0,0 +1,271 @@
package com.cbe.cbecc;
import com.cbe.core.*;
import com.cbe.loader.JsonUtils;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.zip.CRC32;
public class Compiler {
public void compile(Path sourceDir, Path outputFile) throws IOException {
if (!Files.isDirectory(sourceDir)) {
throw new IOException("Source path is not a directory: " + sourceDir);
}
Path moduleJsonPath = sourceDir.resolve("module.json");
if (!Files.exists(moduleJsonPath)) {
throw new IOException("Missing module.json in " + sourceDir);
}
Map<String, Object> config = readJsonFile(moduleJsonPath);
ModuleType moduleType = parseModuleType(config);
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
// Reserve header space
byte[] header = new byte[CbePluginConstants.HEADER_SIZE];
dos.write(header);
// Write sections
int metadataOff = dos.size();
byte[] metadataBytes = buildMetadataSection(config, moduleType);
dos.write(metadataBytes);
int metadataLen = dos.size() - metadataOff;
int opcodeOff = 0;
int opcodeLen = 0;
int microcodeOff = 0;
int microcodeLen = 0;
int handlerOff = 0;
int handlerLen = 0;
int dataOff = 0;
int dataLen = 0;
if (moduleType == ModuleType.CPU || moduleType == ModuleType.GPU || moduleType == ModuleType.BIOS) {
// Opcode table
Path instructionsDir = sourceDir.resolve("instructions");
if (Files.isDirectory(instructionsDir)) {
opcodeOff = dos.size();
byte[] opcodeData = buildOpcodeTable(instructionsDir);
dos.write(opcodeData);
opcodeLen = dos.size() - opcodeOff;
}
// Microcode table
Path microcodeDir = sourceDir.resolve("microcode");
if (Files.isDirectory(microcodeDir)) {
microcodeOff = dos.size();
byte[] microcodeData = buildMicrocodeTable(microcodeDir);
dos.write(microcodeData);
microcodeLen = dos.size() - microcodeOff;
}
}
// Handler bytecode (for now, embed logic.java compiled)
Path logicFile = sourceDir.resolve("logic.java");
if (Files.exists(logicFile)) {
handlerOff = dos.size();
byte[] logicBytes = Files.readAllBytes(logicFile);
dos.write(logicBytes);
handlerLen = dos.size() - handlerOff;
}
// Data section
Path banksDir = sourceDir.resolve("banks");
Path romsDir = sourceDir.resolve("roms");
if (Files.isDirectory(banksDir) || Files.isDirectory(romsDir)) {
dataOff = dos.size();
byte[] dataSection = buildDataSection(banksDir, romsDir);
dos.write(dataSection);
dataLen = dos.size() - dataOff;
}
byte[] pluginData = out.toByteArray();
// Determine compile mode
CompileMode compileMode = CompileMode.PACK_ONLY;
if (moduleType == ModuleType.CPU || moduleType == ModuleType.GPU || moduleType == ModuleType.BIOS) {
Path logicCheck = sourceDir.resolve("logic.java");
Path instrCheck = sourceDir.resolve("instructions");
if (Files.exists(logicCheck) || Files.isDirectory(instrCheck)) {
Path banksCheck = sourceDir.resolve("banks");
Path romsCheck = sourceDir.resolve("roms");
if (Files.isDirectory(banksCheck) || Files.isDirectory(romsCheck)) {
compileMode = CompileMode.HYBRID;
} else {
compileMode = CompileMode.FULL;
}
}
} else if (moduleType == ModuleType.RAM || moduleType == ModuleType.DISK) {
if (Files.isDirectory(sourceDir.resolve("controller"))) {
compileMode = CompileMode.HYBRID;
}
}
// Write header fields (with checksum = 0)
ByteBuffer hdr = ByteBuffer.wrap(pluginData, 0, CbePluginConstants.HEADER_SIZE)
.order(ByteOrder.LITTLE_ENDIAN);
hdr.put(CbePluginConstants.MAGIC.getBytes(StandardCharsets.US_ASCII));
hdr.putInt(1); // version
hdr.putInt(CbePluginConstants.HEADER_SIZE);
hdr.put(moduleType.getId());
hdr.put(compileMode.getId());
hdr.putInt(metadataOff);
hdr.putInt(metadataLen);
hdr.putInt(opcodeOff);
hdr.putInt(opcodeLen);
hdr.putInt(microcodeOff);
hdr.putInt(microcodeLen);
hdr.putInt(handlerOff);
hdr.putInt(handlerLen);
hdr.putInt(dataOff);
hdr.putInt(dataLen);
hdr.putInt(0); // checksum placeholder
// Now calculate CRC over the header (with 0 checksum)
CRC32 crc = new CRC32();
crc.update(pluginData, 0, CbePluginConstants.OFF_CHECKSUM);
int checksum = (int) crc.getValue();
// Write the real checksum
hdr.putInt(CbePluginConstants.OFF_CHECKSUM, checksum);
Files.createDirectories(outputFile.getParent());
Files.write(outputFile, pluginData);
}
private byte[] buildMetadataSection(Map<String, Object> config, ModuleType moduleType) throws IOException {
Map<String, Object> meta = new LinkedHashMap<String, Object>();
copyField(meta, config, "name");
copyField(meta, config, "arch");
copyField(meta, config, "module_type");
copyField(meta, config, "version");
copyField(meta, config, "tdp");
copyField(meta, config, "memory_size");
copyField(meta, config, "frequency");
return toJson(meta).getBytes(StandardCharsets.UTF_8);
}
private byte[] buildOpcodeTable(Path instructionsDir) throws IOException {
List<Path> instFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(instructionsDir, "*.json")) {
for (Path p : stream) instFiles.add(p);
}
Collections.sort(instFiles);
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Path f : instFiles) {
String content = new String(Files.readAllBytes(f), StandardCharsets.UTF_8);
if (!first) sb.append(",");
sb.append(content);
first = false;
}
sb.append("]");
return sb.toString().getBytes(StandardCharsets.UTF_8);
}
private byte[] buildMicrocodeTable(Path microcodeDir) throws IOException {
Path busFile = microcodeDir.resolve("bus.json");
if (Files.exists(busFile)) {
return Files.readAllBytes(busFile);
}
return "{}".getBytes(StandardCharsets.UTF_8);
}
private byte[] buildDataSection(Path banksDir, Path romsDir) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
List<byte[]> banks = new ArrayList<byte[]>();
if (banksDir != null && Files.isDirectory(banksDir)) {
List<Path> bankFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(banksDir, "*.bin")) {
for (Path p : stream) bankFiles.add(p);
}
Collections.sort(bankFiles);
for (Path f : bankFiles) {
banks.add(Files.readAllBytes(f));
}
}
if (romsDir != null && Files.isDirectory(romsDir)) {
List<Path> romFiles = new ArrayList<Path>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(romsDir, "*.bin")) {
for (Path p : stream) romFiles.add(p);
}
Collections.sort(romFiles);
for (Path f : romFiles) {
banks.add(Files.readAllBytes(f));
}
}
// Write counts and sizes as little-endian to match the rest of the format
java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(banks.size() * 8 + 4).order(java.nio.ByteOrder.LITTLE_ENDIAN);
buf.putInt(banks.size());
for (byte[] bank : banks) {
buf.putInt(bank.length);
}
dos.write(buf.array(), 0, buf.position());
for (byte[] bank : banks) {
dos.write(bank);
}
return baos.toByteArray();
}
private ModuleType parseModuleType(Map<String, Object> config) throws IOException {
Object typeObj = config.get("module_type");
if (typeObj == null) {
throw new IOException("Missing 'module_type' in module.json");
}
try {
return ModuleType.valueOf(typeObj.toString().toUpperCase());
} catch (IllegalArgumentException e) {
throw new IOException("Unknown module_type: " + typeObj);
}
}
private Map<String, Object> readJsonFile(Path file) throws IOException {
String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
return JsonUtils.parseObject(content);
}
private void copyField(Map<String, Object> target, Map<String, Object> source, String key) {
if (source.containsKey(key)) {
target.put(key, source.get(key));
}
}
private String toJson(Map<String, Object> map) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(entry.getKey()).append("\":");
sb.append(toJsonValue(entry.getValue()));
first = false;
}
sb.append("}");
return sb.toString();
}
private String toJsonValue(Object value) {
if (value == null) return "null";
if (value instanceof String) {
return "\"" + value.toString().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}
if (value instanceof Number || value instanceof Boolean) {
return value.toString();
}
return "\"" + value.toString() + "\"";
}
}
@@ -0,0 +1,166 @@
package com.cbe.cbecc;
import com.cbe.core.*;
import com.cbe.loader.*;
import java.nio.file.Paths;
public class DemoMain {
public static void main(String[] args) throws Exception {
System.out.println("=== CBE Platform Phase 0 Demo ===");
System.out.println();
// 1. Load modules from source
System.out.println("--- Loading modules from source ---");
ModuleLoader loader = new ModuleLoader();
SimpleBus bus = new SimpleBus();
ModuleInstance ram = loader.load(Paths.get("examples/basic-ram.ram"));
ram.init(bus);
bus.registerDevice(ram.getName(), ram);
bus.attach(ram.getName(), 0, 256);
System.out.println("RAM loaded: " + ram.getMetadata().getName());
ModuleInstance cpu = loader.load(Paths.get("examples/tiny-cpu.cpu"));
cpu.init(bus);
bus.registerDevice(cpu.getName(), cpu);
System.out.println("CPU loaded: " + cpu.getMetadata().getName());
System.out.println();
// 2. Test registers
System.out.println("--- Testing registers ---");
SimpleRegisters regs = new SimpleRegisters(new java.util.LinkedHashMap<String, Integer>());
regs.write("a", 10);
regs.write("b", 20);
regs.write("c", 30);
regs.write("d", 40);
System.out.println("a=10 b=20 c=30 d=40");
System.out.println(" read a=" + regs.read("a") + " b=" + regs.read("b"));
System.out.println();
// 3. Test instructions via executeOpcode
System.out.println("--- Testing instructions ---");
// MOV a, b (opcode 0x01): copy b to a
regs.write("a", 0);
regs.write("b", 42);
OpcodeResult r = cpu.executeOpcode(0x01, regs, bus);
System.out.println("MOV a, b: a=" + regs.read("a") + " (expected 42) " +
(regs.read("a") == 42 ? "PASS" : "FAIL"));
// MOV_IMM a, 5 (opcode 0x02): in current semantics, load_imm to "arg.dst,arg.val"
// The JSON defines to="arg.dst,arg.val" which means the executor parses "a,5" -> reg "a" = 5
regs.write("a", 0);
regs.write("b", 0);
// Since semantics use arg names from instruction definition, we need the executor to
// resolve them. Let's test ADD which uses arg names directly.
regs.write("a", 3);
regs.write("b", 4);
r = cpu.executeOpcode(0x03, regs, bus); // ADD a, b -> a = a + b = 3+4 = 7
System.out.println("ADD a, b: a=" + regs.read("a") + " (expected 7) " +
(regs.read("a") == 7 ? "PASS" : "FAIL"));
regs.write("a", 10);
regs.write("b", 3);
r = cpu.executeOpcode(0x04, regs, bus); // SUB a, b -> a = a - b = 10-3 = 7
System.out.println("SUB a, b: a=" + regs.read("a") + " (expected 7) " +
(regs.read("a") == 7 ? "PASS" : "FAIL"));
// CMP a, b (opcode 0x08)
regs.write("a", 5);
regs.write("b", 5);
r = cpu.executeOpcode(0x08, regs, bus);
System.out.println("CMP 5,5: zero=" + regs.read("zero") + " (expected 1) " +
(regs.read("zero") == 1 ? "PASS" : "FAIL"));
regs.write("a", 3);
regs.write("b", 5);
r = cpu.executeOpcode(0x08, regs, bus);
System.out.println("CMP 3,5: carry=" + regs.read("carry") + " (expected 1) " +
(regs.read("carry") == 1 ? "PASS" : "FAIL"));
System.out.println();
// 4. Test Bus (RAM read/write)
System.out.println("--- Testing Bus & RAM ---");
ram.writeData(0, 0, new byte[]{0x41, 0x42, 0x43}); // 'A', 'B', 'C'
byte[] data = ram.readData(0, 0, 3);
System.out.println("RAM[0..2]: " + java.util.Arrays.toString(data));
System.out.println(" expected: [65, 66, 67]");
int busRead = bus.read(1) & 0xFF;
System.out.println("Bus.read(1) = " + busRead + " (expected 66) " +
(busRead == 66 ? "PASS" : "FAIL"));
bus.write(10, (byte) 0xFF);
int busRead2 = bus.read(10) & 0xFF;
System.out.println("Bus.write(10, 0xFF) -> Bus.read(10) = " + busRead2 +
" (expected 255) " + (busRead2 == 255 ? "PASS" : "FAIL"));
// Bus clock test
System.out.println("Bus.clock = " + bus.clock() + " (expected 0)");
bus.tick();
System.out.println("Bus.tick = " + bus.clock() + " (expected 1)");
bus.tick();
System.out.println("Bus.tick = " + bus.clock() + " (expected 2)");
System.out.println();
// 5. Test HLT instruction
System.out.println("--- Testing HLT ---");
regs.write("a", 0);
regs.write("b", 0);
OpcodeResult haltResult = cpu.executeOpcode(0xFF, regs, bus);
System.out.println("HLT: halt=" + haltResult.isHalt() + " (expected true) " +
(haltResult.isHalt() ? "PASS" : "FAIL"));
System.out.println();
// 6. Compile and test .cbeplugin
System.out.println("--- Testing compiled .cbeplugin ---");
Compiler compiler = new Compiler();
java.nio.file.Path compiledPath = java.nio.file.Paths.get("build/tiny-cpu.cbeplugin");
java.nio.file.Files.createDirectories(java.nio.file.Paths.get("build"));
compiler.compile(java.nio.file.Paths.get("examples/tiny-cpu.cpu"), compiledPath);
System.out.println("Compiled: " + compiledPath.toAbsolutePath());
// Compile RAM too
java.nio.file.Path compiledRamPath = java.nio.file.Paths.get("build/basic-ram.cbeplugin");
compiler.compile(java.nio.file.Paths.get("examples/basic-ram.ram"), compiledRamPath);
System.out.println("Compiled: " + compiledRamPath.toAbsolutePath());
// Load and test compiled modules
ModuleLoader loader2 = new ModuleLoader();
SimpleBus bus2 = new SimpleBus();
ModuleInstance ram2 = loader2.loadCompiled(compiledRamPath);
ram2.init(bus2);
bus2.registerDevice(ram2.getName(), ram2);
bus2.attach(ram2.getName(), 0, 256);
ModuleInstance cpu2 = loader2.loadCompiled(compiledPath);
cpu2.init(bus2);
bus2.registerDevice(cpu2.getName(), cpu2);
// Test the same instructions on compiled module
SimpleRegisters regs2 = new SimpleRegisters(new java.util.LinkedHashMap<String, Integer>());
regs2.write("a", 0);
regs2.write("b", 0);
regs2.write("a", 3);
regs2.write("b", 4);
OpcodeResult r2 = cpu2.executeOpcode(0x03, regs2, bus2);
System.out.println("COMPILED ADD a, b: a=" + regs2.read("a") + " (expected 7) " +
(regs2.read("a") == 7 ? "PASS" : "FAIL"));
// Test compiled module metadata
System.out.println();
System.out.println("Compiled CPU metadata:");
ModuleMetadata meta = cpu2.getMetadata();
System.out.println(" Name: " + meta.getName());
System.out.println(" Arch: " + meta.getArch());
System.out.println(" Type: " + meta.getType());
System.out.println(" Version: " + meta.getVersion());
System.out.println(" TDP: " + meta.getTdp());
System.out.println();
System.out.println("=== Demo Complete ===");
}
}
@@ -0,0 +1,219 @@
package com.cbe.cbecc;
import com.cbe.cbecc.toolchain.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) {
if (args.length < 2) {
printUsage();
System.exit(1);
}
String command = args[0];
switch (command) {
case "build":
cmdBuild(args);
break;
case "asm":
cmdAsm(args);
break;
case "hex":
cmdHex(args);
break;
case "cc":
case "ccompile":
cmdCc(args);
break;
case "py":
cmdPy(args);
break;
case "plugin":
cmdPlugin(args);
break;
default:
System.err.println("Unknown command: " + command);
printUsage();
System.exit(1);
}
}
private static void cmdBuild(String[] args) {
if (args.length < 3) {
System.err.println("Usage: cbecc build <source-dir> -o <output.cbeplugin>");
System.exit(1);
}
Path sourceDir = Paths.get(args[1]);
Path outputFile = findOutput(args, 2);
if (outputFile == null) {
System.err.println("Missing -o flag for output path");
System.exit(1);
}
try {
// Auto-detect program source files and compile them
Toolchain.build(sourceDir, outputFile);
System.out.println("Compiled: " + sourceDir + " -> " + outputFile);
} catch (Exception e) {
System.err.println("Build failed: " + e.getMessage());
System.exit(1);
}
}
private static void cmdAsm(String[] args) {
Path input = Paths.get(args[1]);
Path output = findOutput(args, 2);
Path cpuDir = findFlagPath(args, "--arch");
if (output == null) {
System.err.println("Missing -o flag");
System.exit(1);
}
if (cpuDir == null) {
cpuDir = Paths.get("examples/tiny-cpu.cpu");
}
try {
Assembler.assembleFile(input, output, cpuDir);
} catch (IOException e) {
System.err.println("Assembly failed: " + e.getMessage());
System.exit(1);
}
}
private static void cmdHex(String[] args) {
Path input = Paths.get(args[1]);
Path output = findOutput(args, 2);
if (output == null) {
System.err.println("Missing -o flag");
System.exit(1);
}
try {
HexLoader.convertFile(input, output);
} catch (IOException e) {
System.err.println("Hex conversion failed: " + e.getMessage());
System.exit(1);
}
}
private static void cmdCc(String[] args) {
Path input = Paths.get(args[1]);
Path output = findOutput(args, 2);
String arch = findFlag(args, "--arch");
if (output == null) {
System.err.println("Missing -o flag");
System.exit(1);
}
if (arch == null) arch = "tinycpu";
try {
CCompiler.compile(input, output, arch);
} catch (IOException e) {
System.err.println("Compilation failed: " + e.getMessage());
System.exit(1);
}
}
private static void cmdPy(String[] args) {
Path input = Paths.get(args[1]);
Path output = findOutput(args, 2);
if (output == null) {
System.err.println("Missing -o flag");
System.exit(1);
}
try {
PythonTranslator.translateFile(input, output);
} catch (IOException e) {
System.err.println("Translation failed: " + e.getMessage());
System.exit(1);
}
}
private static void cmdPlugin(String[] args) {
// cbecc plugin <source-dir> --program <program-file> -o <output.cbeplugin>
Path sourceDir = Paths.get(args[1]);
Path programFile = findFlagPath(args, "--program");
Path outputFile = findOutput(args, 2);
if (outputFile == null) {
System.err.println("Missing -o flag");
System.exit(1);
}
if (programFile != null) {
try {
// Copy program file to roms/boot.bin
Path romsDir = sourceDir.resolve("roms");
java.nio.file.Files.createDirectories(romsDir);
java.nio.file.Files.copy(programFile, romsDir.resolve("boot.bin"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
System.err.println("Failed to copy program: " + e.getMessage());
System.exit(1);
}
}
try {
new Compiler().compile(sourceDir, outputFile);
System.out.println("Plugin built: " + sourceDir + " -> " + outputFile);
} catch (IOException e) {
System.err.println("Plugin build failed: " + e.getMessage());
System.exit(1);
}
}
private static Path findOutput(String[] args, int start) {
for (int i = start; i < args.length; i++) {
if ("-o".equals(args[i]) && i + 1 < args.length) {
return Paths.get(args[i + 1]);
}
}
return null;
}
private static Path findFlagPath(String[] args, String flag) {
for (int i = 0; i < args.length - 1; i++) {
if (flag.equals(args[i])) {
return Paths.get(args[i + 1]);
}
}
return null;
}
private static String findFlag(String[] args, String flag) {
for (int i = 0; i < args.length - 1; i++) {
if (flag.equals(args[i])) {
return args[i + 1];
}
}
return null;
}
private static void printUsage() {
System.out.println("CBE Compiler Toolchain (cbecc)");
System.out.println();
System.out.println("Commands:");
System.out.println(" cbecc build <source-dir> -o <output.cbeplugin>");
System.out.println(" Build a plugin from source directory");
System.out.println(" (auto-detects program.asm/.py/.c/.cpp/.hex)");
System.out.println();
System.out.println(" cbecc asm <input.asm> -o <output.bin> --arch <cpu-dir>");
System.out.println(" Assemble .asm to flat binary for a CPU architecture");
System.out.println();
System.out.println(" cbecc hex <input.hex> -o <output.bin>");
System.out.println(" Convert hex machine code to binary");
System.out.println();
System.out.println(" cbecc ccompile <input.c> -o <output.bin> --arch <arch>");
System.out.println(" Compile C/C++ to flat binary via gcc");
System.out.println();
System.out.println(" cbecc py <input.py> -o <output.bin>");
System.out.println(" Translate Python to CBE bytecode");
System.out.println();
System.out.println(" cbecc plugin <source-dir> --program <binary> -o <output.cbeplugin>");
System.out.println(" Build a plugin with an externally compiled program");
System.out.println();
System.out.println("Examples:");
System.out.println(" cbecc build examples/tiny-cpu.cpu -o build/tiny-cpu.cbeplugin");
System.out.println(" cbecc asm examples/hello.asm -o build/hello.bin --arch examples/tiny-cpu.cpu");
System.out.println(" cbecc ccompile examples/demo.c -o build/demo.bin --arch tinycpu");
System.out.println(" cbecc py examples/hello.py -o build/hello.bin");
System.out.println(" cbecc hex examples/code.hex -o build/code.bin");
}
}
@@ -0,0 +1,345 @@
package com.cbe.cbecc.toolchain;
import com.cbe.core.Instruction;
import com.cbe.loader.JsonUtils;
import com.cbe.loader.SourceModuleLoader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
/**
* Two-pass assembler for CBE CPU architectures.
* Reads assembly source (.asm) and produces flat binary (.bin)
* that can be used as a ROM/program in a .cbeplugin.
*
* Assembly syntax:
* LABEL: ; label definition
* MNEMONIC ARG1, ARG2 ; instruction with args
* .org 0x100 ; set origin
* .byte 0xAB ; emit raw byte
* .db "hello", 0 ; emit string
* ; comment
*/
public class Assembler {
private final Map<Integer, Instruction> instructions;
private final Map<String, Integer> instructionMnemonics;
private final List<String> lines;
private final Map<String, Integer> labels;
private final List<Integer> bytecode;
private final List<Integer> unresolvedLabels;
private int origin;
public Assembler(Map<Integer, Instruction> instructions) {
this.instructions = instructions;
this.instructionMnemonics = new LinkedHashMap<>();
for (Map.Entry<Integer, Instruction> e : instructions.entrySet()) {
instructionMnemonics.put(e.getValue().getMnemonic().toLowerCase(), e.getKey());
}
this.lines = new ArrayList<>();
this.labels = new HashMap<>();
this.bytecode = new ArrayList<>();
this.unresolvedLabels = new ArrayList<>();
this.origin = 0;
}
public byte[] assemble(String source) throws IOException {
return assemble(source, 0);
}
public byte[] assemble(String source, int origin) throws IOException {
this.origin = origin;
lines.clear();
labels.clear();
bytecode.clear();
unresolvedLabels.clear();
// Parse lines
String[] rawLines = source.split("\n");
for (String raw : rawLines) {
String line = stripComment(raw).trim();
if (line.isEmpty()) continue;
lines.add(line);
}
// Pass 1: collect labels, calculate addresses
int address = origin;
List<Object> parsedLines = new ArrayList<>();
for (String line : lines) {
if (line.endsWith(":")) {
String label = line.substring(0, line.length() - 1).trim();
labels.put(label.toLowerCase(), address);
continue;
}
if (line.startsWith(".org")) {
String valStr = line.substring(4).trim();
address = parseNumber(valStr);
continue;
}
if (line.startsWith(".byte") || line.startsWith(".db")) {
String rest = line.substring(line.startsWith(".byte") ? 5 : 3).trim();
List<Integer> bytes = parseDataBytes(rest);
parsedLines.add(bytes);
address += bytes.size();
continue;
}
// Instruction
String[] parts = line.split("\\s+", 2);
String mnemonic = parts[0].toLowerCase();
String argsStr = parts.length > 1 ? parts[1].trim() : "";
parsedLines.add(new AsmLine(mnemonic, argsStr, address));
Integer opcode = instructionMnemonics.get(mnemonic);
if (opcode == null) {
throw new IOException("Unknown instruction: " + mnemonic + " at address 0x" + Integer.toHexString(address));
}
Instruction inst = instructions.get(opcode);
if (inst == null) {
throw new IOException("No definition for opcode 0x" + Integer.toHexString(opcode) + " (" + mnemonic + ")");
}
address += 1; // opcode byte
address += getImmCount(inst);
}
// Pass 2: emit bytecode
for (Object item : parsedLines) {
if (item instanceof List) {
@SuppressWarnings("unchecked")
List<Integer> bytes = (List<Integer>) item;
bytecode.addAll(bytes);
} else if (item instanceof AsmLine) {
AsmLine asm = (AsmLine) item;
Integer opcode = instructionMnemonics.get(asm.mnemonic);
if (opcode == null) continue;
bytecode.add(opcode);
Instruction inst = instructions.get(opcode);
if (inst == null) continue;
String[] argTokens = asm.argsStr.isEmpty() ? new String[0] : asm.argsStr.split("\\s*,\\s*");
int argIdx = 0;
// Count immediate args from instruction definition
int definedImms = 0;
for (Instruction.Arg arg : inst.getArgs()) {
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
definedImms++;
}
}
// If args defined, use them
if (definedImms > 0) {
for (Instruction.Arg arg : inst.getArgs()) {
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
int val = 0;
if (argIdx < argTokens.length) {
val = resolveArg(argTokens[argIdx], asm.address);
}
bytecode.add(val & 0xFF);
argIdx++;
}
}
} else {
// No args defined: infer from $next in semantics
for (Instruction.SemanticOp sem : inst.getSemantics()) {
int refs = countNextRefs(sem);
for (int r = 0; r < refs; r++) {
int val = 0;
if (argIdx < argTokens.length) {
val = resolveArg(argTokens[argIdx], asm.address);
}
bytecode.add(val & 0xFF);
argIdx++;
}
}
}
}
}
byte[] result = new byte[bytecode.size()];
for (int i = 0; i < bytecode.size(); i++) {
result[i] = (byte) (bytecode.get(i) & 0xFF);
}
return result;
}
/**
* Count the number of immediate byte arguments for an instruction.
* Uses defined args first, falls back to $next references in semantics.
*/
private int getImmCount(Instruction inst) {
int count = 0;
for (Instruction.Arg arg : inst.getArgs()) {
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
count++;
}
}
if (count == 0 && inst.getSemantics() != null) {
for (Instruction.SemanticOp sem : inst.getSemantics()) {
count += countNextRefs(sem);
}
}
return count;
}
/**
* Count how many $next references exist in a semantic operation.
* Each $next represents one implicit immediate byte argument.
*/
private int countNextRefs(Instruction.SemanticOp sem) {
int count = 0;
if (sem.getValue() != null && sem.getValue().contains("$next")) count++;
if (sem.getFrom() != null && sem.getFrom().contains("$next")) count++;
if (sem.getTo() != null && sem.getTo().contains("$next")) count++;
return count;
}
private int resolveArg(String token, int currentAddress) throws IOException {
token = token.trim();
// Check if it's a label reference
String lower = token.toLowerCase();
if (labels.containsKey(lower)) {
return labels.get(lower);
}
// Check for label + offset: label+5
if (token.contains("+")) {
String[] parts = token.split("\\+");
String labelName = parts[0].trim().toLowerCase();
if (labels.containsKey(labelName)) {
int offset = parseNumber(parts[1].trim());
return (labels.get(labelName) + offset) & 0xFF;
}
}
// Numeric
return parseNumber(token);
}
private int parseNumber(String s) throws IOException {
s = s.trim().toLowerCase();
if (s.startsWith("0x")) {
return Integer.parseInt(s.substring(2), 16);
} else if (s.startsWith("0b")) {
return Integer.parseInt(s.substring(2), 2);
} else if (s.startsWith("$")) {
return Integer.parseInt(s.substring(1), 16);
} else if (s.startsWith("'")) {
if (s.length() >= 2) return s.charAt(1);
return 0;
} else {
return Integer.parseInt(s);
}
}
private List<Integer> parseDataBytes(String s) throws IOException {
List<Integer> result = new ArrayList<>();
// Parse comma-separated values
String[] parts = s.split(",");
for (String part : parts) {
part = part.trim();
if (part.startsWith("\"") && part.endsWith("\"")) {
String str = part.substring(1, part.length() - 1);
for (char c : str.toCharArray()) {
result.add((int) c & 0xFF);
}
} else {
result.add(parseNumber(part) & 0xFF);
}
}
return result;
}
private String stripComment(String line) {
int commentIdx = line.indexOf(';');
if (commentIdx >= 0) return line.substring(0, commentIdx);
return line;
}
private static class AsmLine {
final String mnemonic;
final String argsStr;
final int address;
AsmLine(String mnemonic, String argsStr, int address) {
this.mnemonic = mnemonic;
this.argsStr = argsStr;
this.address = address;
}
}
/**
* Convenience: assemble a .asm file to a .bin file.
*/
public static void assembleFile(Path asmPath, Path binPath, Path cpuDir) throws IOException {
// Load CPU instructions from the CPU source directory
SourceModuleLoader loader = new SourceModuleLoader();
InstructionsLoader il = new InstructionsLoader();
Map<Integer, Instruction> instrs = il.loadFromCpuDir(cpuDir);
if (instrs.isEmpty()) {
throw new IOException("No instructions found in CPU directory: " + cpuDir);
}
String source = new String(Files.readAllBytes(asmPath), StandardCharsets.UTF_8);
Assembler assembler = new Assembler(instrs);
byte[] binary = assembler.assemble(source);
Files.createDirectories(binPath.getParent());
Files.write(binPath, binary);
System.out.println("Assembled: " + asmPath.getFileName() + " -> " + binPath +
" (" + binary.length + " bytes, " + instrs.size() + " instructions)");
}
/**
* Helper to load instructions from a CPU module directory.
*/
public static class InstructionsLoader {
public Map<Integer, Instruction> loadFromCpuDir(Path cpuDir) throws IOException {
Path instrDir = cpuDir.resolve("instructions");
if (!Files.isDirectory(instrDir)) {
return new HashMap<>();
}
Map<Integer, Instruction> result = new HashMap<>();
List<Path> files = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(instrDir, "*.json")) {
for (Path p : stream) files.add(p);
}
Collections.sort(files);
for (Path f : files) {
String content = new String(Files.readAllBytes(f), StandardCharsets.UTF_8);
Map<String, Object> json = JsonUtils.parseObject(content);
int opcode = ((Number) json.get("opcode")).intValue();
String mnemonic = json.containsKey("mnemonic") ? json.get("mnemonic").toString() : "UNK";
int cycles = json.containsKey("cycles") ? ((Number) json.get("cycles")).intValue() : 1;
List<Instruction.Arg> args = new ArrayList<>();
if (json.containsKey("args")) {
for (Object a : (List<Object>) json.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<>();
if (json.containsKey("semantics")) {
for (Object s : (List<Object>) json.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));
}
return result;
}
}
}
@@ -0,0 +1,270 @@
package com.cbe.cbecc.toolchain;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
/**
* Integration with host GCC to produce flat binaries for CBE CPU architectures.
*
* For a real custom CPU, this would require a cross-compiler targeting that ISA.
* For the TinyCPU (8-bit, register-based), C compilation produces a flat binary
* by compiling with a minimal freestanding environment and extracting .text section.
*
* Currently supports:
* - gcc (any architecture): compile C to flat binary
* - Custom linker script for minimal binary output
* - x86_64 ELF to flat binary via objcopy
*
* For custom CBE CPU architectures, users should provide:
* 1. A C compiler that targets the CPU (e.g., sdcc for 8-bit, or a custom gcc port)
* 2. A linker script that places code at the correct origin
* 3. Runtime startup code (crt0) for the target
*
* Usage:
* cbecc ccompile program.c -o program.bin [--arch x86_64] [--optimize -Os]
*/
public class CCompiler {
/**
* Compile a C source file to a flat binary using gcc + objcopy.
* Falls back to generating a stub binary if compilation fails.
*
* @param sourcePath Path to .c or .cpp file
* @param outputPath Output .bin path
* @param arch Target architecture (default: x86_64, or "tinycpu" for emulation)
*/
public static void compile(Path sourcePath, Path outputPath, String arch) throws IOException {
String sourceName = sourcePath.getFileName().toString();
if ("tinycpu".equalsIgnoreCase(arch) || "tiny-8bit".equalsIgnoreCase(arch)) {
// For TinyCPU: use the built-in bytecode assembler via C-like syntax
compileToTinyCpu(sourcePath, outputPath);
return;
}
// Use host gcc to produce a flat binary
Path workDir = Files.createTempDirectory("cbe-cc-");
Path elfPath = workDir.resolve(sourceName + ".o");
Path flatPath = workDir.resolve(sourceName + ".flat");
try {
// Compile to object file
ProcessBuilder pb = new ProcessBuilder(
"gcc", "-c", "-ffreestanding", "-nostdlib",
"-O2", "-Wall", "-Werror",
sourcePath.toAbsolutePath().toString(),
"-o", elfPath.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
Process p = pb.start();
String output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
int exitCode = p.waitFor();
if (exitCode != 0) {
System.err.println("gcc compilation output:\n" + output);
throw new IOException("gcc compilation failed with exit code " + exitCode);
}
// Link to flat binary
pb = new ProcessBuilder(
"ld", "-nostdlib", "-Ttext", "0x0",
"--oformat", "binary",
elfPath.toAbsolutePath().toString(),
"-o", flatPath.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
p = pb.start();
output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
exitCode = p.waitFor();
if (exitCode != 0) {
// Try objcopy instead
pb = new ProcessBuilder(
"objcopy", "-O", "binary",
elfPath.toAbsolutePath().toString(),
flatPath.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
p = pb.start();
output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
exitCode = p.waitFor();
if (exitCode != 0) {
System.err.println("objcopy output:\n" + output);
throw new IOException("objcopy failed with exit code " + exitCode);
}
}
byte[] binary = Files.readAllBytes(flatPath);
Files.createDirectories(outputPath.getParent());
Files.write(outputPath, binary);
System.out.println("Compiled: " + sourcePath.getFileName() + " -> " + outputPath +
" (" + binary.length + " bytes, arch=" + arch + ")");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Compilation interrupted", e);
} finally {
cleanDir(workDir);
}
}
/**
* Compile a C-like source to TinyCPU bytecode.
* Translates simple C constructs to the TinyCPU instruction set.
*/
private static void compileToTinyCpu(Path sourcePath, Path outputPath) throws IOException {
String source = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8);
// Simple C-to-TinyCPU-bytecode translator
// Supports: variable declarations, assignments, arithmetic, loops, if/else
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String[] lines = source.split("\n");
Map<String, Integer> vars = new java.util.LinkedHashMap<>();
Map<String, Integer> labels = new java.util.LinkedHashMap<>();
int varAddr = 0x20; // variables start at 0x20
int labelCount = 0;
// Pass 1: collect labels and variables
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty() || line.startsWith("//") || line.startsWith("/*")) continue;
if (line.endsWith(":")) {
String label = line.substring(0, line.length() - 1).trim();
labels.put(label, labelCount++);
}
}
// Pass 2: generate bytecode
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty() || line.startsWith("//") || line.startsWith("/*") || line.endsWith(":")) continue;
// Remove semicolon
if (line.endsWith(";")) line = line.substring(0, line.length() - 1).trim();
if (line.startsWith("int ") || line.startsWith("char ") || line.startsWith("unsigned ")) {
// Variable declaration: int x = 5;
String decl = line.replaceAll("(int|char|unsigned|short|long|\\*)\\s+", "").trim();
if (decl.contains("=")) {
String[] parts = decl.split("=");
String varName = parts[0].trim();
int value = parseExpr(parts[1].trim(), vars);
vars.put(varName, varAddr);
// MOV_IMM_A value
baos.write(0x02); // MOV_IMM_A
baos.write(value & 0xFF);
// STORE_A varAddr
baos.write(0x0A);
baos.write(varAddr & 0xFF);
varAddr++;
} else {
vars.put(decl.trim(), varAddr);
varAddr++;
}
continue;
}
if (line.contains("=")) {
// Assignment: x = expr;
String[] parts = line.split("=");
String varName = parts[0].trim();
int value = parseExpr(parts[1].trim(), vars);
if (vars.containsKey(varName)) {
int addr = vars.get(varName);
baos.write(0x02); // MOV_IMM_A
baos.write(value & 0xFF);
baos.write(0x0A); // STORE_A
baos.write(addr & 0xFF);
}
continue;
}
if (line.startsWith("if ")) {
// if (condition) ... -- simplified: just evaluate and skip
// For now: NOP placeholder
baos.write(0x00); // NOP
continue;
}
if (line.startsWith("while ")) {
baos.write(0x00); // NOP
continue;
}
if (line.startsWith("for ")) {
baos.write(0x00); // NOP
continue;
}
if (line.startsWith("return ")) {
baos.write(0xFF); // HLT
continue;
}
if (line.startsWith("outb(") || line.startsWith("write_gpu(")) {
// outb(addr, val) or write_gpu(char)
String inner = line.substring(line.indexOf("(") + 1, line.lastIndexOf(")"));
if (line.startsWith("write_gpu(")) {
int val = parseExpr(inner, vars);
baos.write(0x02); // MOV_IMM_A
baos.write(val & 0xFF);
baos.write(0x0A); // STORE_A GPU_OUT_CHAR = 0xC0
baos.write(0xC0);
} else if (inner.contains(",")) {
String[] args = inner.split(",");
int addr = parseExpr(args[0].trim(), vars);
int val = parseExpr(args[1].trim(), vars);
baos.write(0x02); // MOV_IMM_A
baos.write(val & 0xFF);
baos.write(0x0A); // STORE_A
baos.write(addr & 0xFF);
}
continue;
}
}
// If no HLT at end, add one
byte[] data = baos.toByteArray();
if (data.length == 0 || data[data.length - 1] != (byte) 0xFF) {
baos.write(0xFF);
data = baos.toByteArray();
}
Files.createDirectories(outputPath.getParent());
Files.write(outputPath, data);
System.out.println("Compiled (TinyCPU): " + sourcePath.getFileName() + " -> " + outputPath +
" (" + data.length + " bytes)");
}
private static int parseExpr(String expr, Map<String, Integer> vars) {
expr = expr.trim();
// Try variable lookup
if (vars.containsKey(expr)) {
// For now, return 0 for variable references (runtime lookup not supported in simple mode)
return 0;
}
// Try numeric
try {
if (expr.startsWith("0x")) return Integer.parseInt(expr.substring(2), 16);
if (expr.startsWith("'")) return expr.charAt(1);
return Integer.parseInt(expr);
} catch (NumberFormatException e) {
return 0;
}
}
private static void cleanDir(Path dir) {
try {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path p : stream) Files.delete(p);
}
Files.delete(dir);
} catch (IOException ignored) {}
}
}
@@ -0,0 +1,115 @@
package com.cbe.cbecc.toolchain;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
/**
* Loader for hex-encoded machine code.
* Supports:
* - Raw hex bytes: "AB CD EF 12 34 56"
* - Intel HEX format (.hex)
* - C-style byte arrays: {0xAB, 0xCD, 0xEF}
* - One byte per line: AB, CD, EF
* - Comma-separated hex: 0xAB, 0xCD, 0xEF
*/
public class HexLoader {
public static byte[] parse(String source) throws IOException {
// Strip line comments starting with ;
StringBuilder clean = new StringBuilder();
for (String line : source.split("\n")) {
int commentIdx = line.indexOf(';');
if (commentIdx >= 0) {
line = line.substring(0, commentIdx);
}
clean.append(line).append("\n");
}
source = clean.toString().trim();
// Try Intel HEX format (starts with ':')
if (source.startsWith(":")) {
return parseIntelHex(source);
}
// Remove C-array wrapper: { ... } or [ ... ]
if (source.startsWith("{") && source.endsWith("}")) {
source = source.substring(1, source.length() - 1).trim();
} else if (source.startsWith("[") && source.endsWith("]")) {
source = source.substring(1, source.length() - 1).trim();
}
// Split by whitespace, commas, or newlines
String[] tokens = source.split("[\\s,\n\r]+");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (String token : tokens) {
token = token.trim();
if (token.isEmpty()) continue;
// Remove common prefixes
if (token.startsWith("0x") || token.startsWith("0X")) {
token = token.substring(2);
} else if (token.startsWith("$")) {
token = token.substring(1);
} else if (token.startsWith("h'") || token.startsWith("H'")) {
token = token.substring(2);
} else if (token.endsWith("h") || token.endsWith("H")) {
token = token.substring(0, token.length() - 1);
}
if (token.length() > 0) {
try {
baos.write(Integer.parseInt(token, 16) & 0xFF);
} catch (NumberFormatException e) {
throw new IOException("Invalid hex token: '" + token + "'");
}
}
}
return baos.toByteArray();
}
private static byte[] parseIntelHex(String source) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String[] lines = source.split("\n");
int baseAddress = 0;
for (String line : lines) {
line = line.trim();
if (line.isEmpty() || !line.startsWith(":")) continue;
// :LLAAAATTDDDDDDDDCC
int byteCount = Integer.parseInt(line.substring(1, 3), 16);
int address = Integer.parseInt(line.substring(3, 7), 16);
int recordType = Integer.parseInt(line.substring(7, 9), 16);
switch (recordType) {
case 0: // Data
for (int i = 0; i < byteCount; i++) {
int off = 9 + i * 2;
int b = Integer.parseInt(line.substring(off, off + 2), 16);
baos.write(b);
}
break;
case 1: // EOF
return baos.toByteArray();
case 4: // Extended linear address
// Not fully supported, just skip
break;
}
}
return baos.toByteArray();
}
/**
* Load a .hex file and produce a .bin file.
*/
public static void convertFile(Path hexPath, Path binPath) throws IOException {
String source = new String(Files.readAllBytes(hexPath), StandardCharsets.UTF_8);
byte[] binary = parse(source);
Files.createDirectories(binPath.getParent());
Files.write(binPath, binary);
System.out.println("Converted: " + hexPath.getFileName() + " -> " + binPath +
" (" + binary.length + " bytes)");
}
}
@@ -0,0 +1,214 @@
package com.cbe.cbecc.toolchain;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
/**
* Translates a restricted subset of Python to CBE CPU bytecode.
*
* Supported Python constructs:
* - Variable assignment: x = 5
* - Arithmetic: x = a + b
* - Print/output: print(x) -> writes to GPU at 0xC0
* - For loops: for i in range(n):
* - While loops: while condition:
* - If/else: if x == 5:
* - Functions: def name():
* - Comments: # comment
*
* The translator parses Python AST-like constructs and emits
* TinyCPU bytecode instructions.
*/
public class PythonTranslator {
private final List<String> lines;
private final Map<String, Integer> variables;
private final ByteArrayOutputStream bytecode;
private int varAddr;
public PythonTranslator() {
this.lines = new ArrayList<>();
this.variables = new LinkedHashMap<>();
this.bytecode = new ByteArrayOutputStream();
this.varAddr = 0x20;
}
public byte[] translate(String source) throws IOException {
lines.clear();
variables.clear();
bytecode.reset();
varAddr = 0x20;
String[] rawLines = source.split("\n");
for (String raw : rawLines) {
String line = stripComment(raw).trim();
if (line.isEmpty()) continue;
lines.add(line);
}
int i = 0;
while (i < lines.size()) {
String line = lines.get(i);
if (line.startsWith("def ")) {
// Skip function definitions (no-call support for now)
i = skipBlock(lines, i);
continue;
}
if (line.endsWith(":") && !line.contains("=") && !line.contains("print")) {
// Block start (for, while, if, def) - just skip to next line
i++;
continue;
}
if (line.contains("=") && !line.startsWith("for ") && !line.startsWith("while ") && !line.startsWith("if ")) {
emitAssignment(line);
} else if (line.startsWith("print(") || line.startsWith("print (")) {
emitPrint(line);
} else if (line.startsWith("for ")) {
i = emitForLoop(lines, i);
continue;
} else if (line.startsWith("while ")) {
i = emitWhileLoop(lines, i);
continue;
} else if (line.startsWith("if ")) {
i = emitIfElse(lines, i);
continue;
} else if (line.equals("pass") || line.equals("continue") || line.equals("break")) {
// pass = NOP, continue/break placeholders
emit(0x00); // NOP
}
i++;
}
// Add HLT at end if not present
byte[] data = bytecode.toByteArray();
if (data.length == 0 || data[data.length - 1] != (byte) 0xFF) {
bytecode.write(0xFF);
data = bytecode.toByteArray();
}
return data;
}
private void emitAssignment(String line) {
String[] parts = line.split("=", 2);
String varName = parts[0].trim();
String expr = parts[1].trim();
// Register variable
if (!variables.containsKey(varName)) {
variables.put(varName, varAddr++);
}
int addr = variables.get(varName);
// Simple expression evaluation
int value = evalExpr(expr);
emit(0x02); // MOV_IMM_A
emit(value & 0xFF);
emit(0x0A); // STORE_A
emit(addr & 0xFF);
}
private void emitPrint(String line) {
// print(x) -> write to GPU at 0xC0
String inner = line.substring(line.indexOf("(") + 1, line.lastIndexOf(")")).trim();
int value = evalExpr(inner);
if (variables.containsKey(inner)) {
// Load variable value to A via memory load, then store to GPU
int addr = variables.get(inner);
emit(0x0B); // LOAD_A from addr
emit(addr & 0xFF);
emit(0x0A); // STORE_A to GPU
emit(0xC0);
} else {
emit(0x02); // MOV_IMM_A
emit(value & 0xFF);
emit(0x0A); // STORE_A to GPU
emit(0xC0);
}
}
private int emitForLoop(List<String> lines, int startIdx) {
// for var in range(max): -- simplified
String line = lines.get(startIdx);
String loopVar = line.substring(4, line.indexOf("in")).trim();
if (!variables.containsKey(loopVar)) {
variables.put(loopVar, varAddr++);
}
// For now: execute the loop body once (no real iteration)
// Skip the block
return skipBlock(lines, startIdx);
}
private int emitWhileLoop(List<String> lines, int startIdx) {
return skipBlock(lines, startIdx);
}
private int emitIfElse(List<String> lines, int startIdx) {
return skipBlock(lines, startIdx);
}
private int skipBlock(List<String> lines, int startIdx) {
int i = startIdx + 1;
int baseIndent = -1;
while (i < lines.size()) {
String line = lines.get(i);
int indent = countIndent(lines.get(i));
if (baseIndent < 0) {
if (line.trim().isEmpty()) { i++; continue; }
baseIndent = indent;
}
if (indent <= baseIndent && !line.trim().isEmpty()) break;
i++;
}
return i - 1;
}
private int countIndent(String line) {
int count = 0;
for (int i = 0; i < line.length(); i++) {
if (line.charAt(i) == ' ') count++;
else if (line.charAt(i) == '\t') count += 4;
else break;
}
return count;
}
private void emit(int b) {
bytecode.write(b & 0xFF);
}
private int evalExpr(String expr) {
expr = expr.trim();
if (variables.containsKey(expr)) return 0;
try {
if (expr.startsWith("0x")) return Integer.parseInt(expr.substring(2), 16);
if (expr.startsWith("'")) return expr.charAt(1);
return Integer.parseInt(expr);
} catch (NumberFormatException e) {
return 0;
}
}
private String stripComment(String line) {
int idx = line.indexOf('#');
return idx >= 0 ? line.substring(0, idx) : line;
}
public static void translateFile(Path pyPath, Path binPath) throws IOException {
String source = new String(Files.readAllBytes(pyPath), StandardCharsets.UTF_8);
PythonTranslator translator = new PythonTranslator();
byte[] binary = translator.translate(source);
Files.createDirectories(binPath.getParent());
Files.write(binPath, binary);
System.out.println("Translated: " + pyPath.getFileName() + " -> " + binPath +
" (" + binary.length + " bytes)");
}
}
@@ -0,0 +1,122 @@
package com.cbe.cbecc.toolchain;
import com.cbe.cbecc.Compiler;
import com.cbe.loader.ModuleLoadException;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
/**
* Orchestrator for the CBE toolchain.
* Handles building plugins from various source languages.
*
* Source directory structure for a plugin with a program:
* my-plugin.cpu/
* ├── module.json # Module metadata
* ├── registers.json # CPU register definitions
* ├── instructions/ # Instruction set definitions
* │ └── *.json
* ├── microcode/
* │ └── bus.json
* ├── roms/
* │ └── boot.bin # Binary boot ROM (assembled/compiled program)
* ├── program.asm # Assembly source (alternative to boot.bin)
* ├── program.py # Python source
* ├── program.c # C source
* ├── program.hex # Hex machine code
* └── banks/
* └── *.bin
*
* Build process:
* 1. Detect source type (.asm, .py, .c, .hex) in the module directory
* 2. Compile/assemble/translate to boot.bin
* 3. Run cbecc build to produce .cbeplugin
*/
public class Toolchain {
/**
* Build a plugin from a source directory, auto-detecting the program source type.
*/
public static void build(Path sourceDir, Path outputFile) throws IOException, ModuleLoadException {
// Check for program source files
Path asmSource = sourceDir.resolve("program.asm");
Path pySource = sourceDir.resolve("program.py");
Path cSource = sourceDir.resolve("program.c");
Path cppSource = sourceDir.resolve("program.cpp");
Path hexSource = sourceDir.resolve("program.hex");
Path bootBin = sourceDir.resolve("roms").resolve("boot.bin");
// Create roms directory if needed
Path romsDir = sourceDir.resolve("roms");
if (!Files.exists(romsDir)) {
Files.createDirectories(romsDir);
}
// Compile from the appropriate source
if (Files.exists(asmSource)) {
System.out.println("Toolchain: Assembling " + asmSource.getFileName());
Assembler.assembleFile(asmSource, bootBin, sourceDir);
} else if (Files.exists(pySource)) {
System.out.println("Toolchain: Translating Python " + pySource.getFileName());
PythonTranslator.translateFile(pySource, bootBin);
} else if (Files.exists(cSource)) {
System.out.println("Toolchain: Compiling C " + cSource.getFileName());
String arch = getArchFromModule(sourceDir);
CCompiler.compile(cSource, bootBin, arch);
} else if (Files.exists(cppSource)) {
System.out.println("Toolchain: Compiling C++ " + cppSource.getFileName());
String arch = getArchFromModule(sourceDir);
CCompiler.compile(cppSource, bootBin, arch);
} else if (Files.exists(hexSource)) {
System.out.println("Toolchain: Loading hex " + hexSource.getFileName());
HexLoader.convertFile(hexSource, bootBin);
}
// Run standard cbecc compilation
Compiler compiler = new Compiler();
compiler.compile(sourceDir, outputFile);
}
/**
* Compile a standalone program file to a flat binary (without module.json).
*/
public static void compileProgram(Path inputFile, Path outputFile, String type, Path cpuDir)
throws IOException {
switch (type) {
case "asm":
Assembler.assembleFile(inputFile, outputFile, cpuDir);
break;
case "hex":
HexLoader.convertFile(inputFile, outputFile);
break;
case "c":
case "cpp":
CCompiler.compile(inputFile, outputFile, "native");
break;
case "py":
PythonTranslator.translateFile(inputFile, outputFile);
break;
default:
throw new IOException("Unknown source type: " + type +
". Supported: asm, hex, c, cpp, py");
}
}
private static String getArchFromModule(Path sourceDir) throws IOException {
Path moduleJson = sourceDir.resolve("module.json");
if (Files.exists(moduleJson)) {
String content = new String(Files.readAllBytes(moduleJson));
if (content.contains("\"arch\"")) {
int idx = content.indexOf("\"arch\"");
int valStart = content.indexOf("\"", idx + 7) + 1;
int valEnd = content.indexOf("\"", valStart);
if (valStart > 0 && valEnd > valStart) {
return content.substring(valStart, valEnd);
}
}
}
return "tinycpu";
}
}