diff --git a/Makefile b/Makefile index e976cf4..c7c8b9d 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,15 @@ cli: src/cli.c gcc -o build/cli.exe src/cli.c $(CFLAGS) web: src/web.c - rm -rf build/web mkdir -p build/web emcc -o build/web/sim8086.js src/web.c --no-entry -sEXPORTED_RUNTIME_METHODS=cwrap,AsciiToString -sEXPORTED_FUNCTIONS=_free,_malloc $(CFLAGS) cp -r src/web/* build/web -serve-web: +serve-web: web cd build/web && python -m http.server +watch-web: + live-server build/web --wait=250 + clean: - rm -r build \ No newline at end of file + rm -r build diff --git a/src/web.c b/src/web.c index 0c07caa..bfa2e06 100644 --- a/src/web.c +++ b/src/web.c @@ -1,4 +1,5 @@ #include +#include #define SIM8086_EMCC #include "sim8086/prelude.h" @@ -8,29 +9,44 @@ EXPORT struct memory memory_state; EXPORT struct cpu_state cpu_state; -EXPORT struct instruction current_instruction; + +EXPORT void step() { + struct instruction inst; + enum decode_error err = decode_instruction(&memory_state, &cpu_state.ip, &inst); + if (err == DECODE_OK) { + execute_instruction(&memory_state, &cpu_state, &inst); + } +} + +EXPORT void reset_cpu() { + memset(&cpu_state, 0, sizeof(cpu_state)); +} /* -------------------- Decoder ----------------------- */ -EXPORT int decode_inst_at_ip() { - return decode_instruction(&memory_state, &cpu_state.ip, ¤t_instruction); -} - -EXPORT void get_inst_str(char *buff, size_t buff_size) { - instruction_to_str(buff, buff_size, ¤t_instruction); +EXPORT u16 decode_inst_at(u16 addr, char *buff, size_t buff_size) { + struct instruction inst; + u16 after_addr = addr; + enum decode_error err = decode_instruction(&memory_state, &after_addr, &inst); + if (err == DECODE_OK) { + instruction_to_str(buff, buff_size, &inst); + return after_addr - addr; + } else { + return 0; + } } /* -------------------- Memory ----------------------- */ -EXPORT int load_to_memory_state(u8 *assembly, u16 assembly_size, u16 start) { - return load_mem_from_buff(&memory_state, assembly, assembly_size, start); +EXPORT int set_memory_state(u8 *buffer, u16 buffer_size, u16 start) { + return load_mem_from_buff(&memory_state, buffer, buffer_size, start); } EXPORT u8 *get_memory_state_base() { return memory_state.mem; } -EXPORT size_t get_memory_state_size(u8 *assembly, u16 assembly_size, u16 start) { +EXPORT size_t get_memory_state_size() { return MEMORY_SIZE; } @@ -63,4 +79,4 @@ EXPORT bool cpu_get_zero_flag() EXPORT bool cpu_get_sign_flag() { return cpu_state.flags.sign; -} \ No newline at end of file +} diff --git a/src/web/api.js b/src/web/api.js new file mode 100644 index 0000000..e02a7bd --- /dev/null +++ b/src/web/api.js @@ -0,0 +1,38 @@ + +const registers = {} +for (const reg of ["ax", "bx", "cx", "dx", "sp", "bp", "si", "di", "ip"]) { + registers[reg] = { + set: Module.cwrap(`cpu_set_${reg}`, null, ["number"]), + get: Module.cwrap(`cpu_get_${reg}`, "number", []) + } +} + +const decodeInstAt = Module.cwrap("decode_inst_at", null, ["number", "number", "number"]) +function getCurrentInstructionAt(address) { + let text = undefined + const inst = Module._malloc(64) + const size = decodeInstAt(address, inst, 64) + if (size > 0) { + text = Module.AsciiToString(inst) + } + Module._free(inst) + return [text, size] +} + +const setMemoryState = Module.cwrap("set_memory_state", null, ["array", "number", "number"]) +function setMemoryAt(address, value) { + setMemoryBufferAt(address, [value]) +} + +function setMemoryBufferAt(address, buffer) { + setMemoryState(buffer, buffer.length, address) +} + +const getMemoryBaseAddress = Module.cwrap("get_memory_state_base", null, []) +const getMemorySize = Module.cwrap("get_memory_state_size", null, []) +function getMemory() { + return new Uint8Array(wasmMemory.buffer, getMemoryBaseAddress(), getMemorySize()) +} + +const resetCPU = Module.cwrap("reset_cpu", null, []) +const stepCPU = Module.cwrap("step", null, []) diff --git a/src/web/assembly-view.js b/src/web/assembly-view.js new file mode 100644 index 0000000..26c4774 --- /dev/null +++ b/src/web/assembly-view.js @@ -0,0 +1,38 @@ +class AssemblyViewElement extends HTMLElement { + assemblySize = 0 + startAddress = 0 + + connectedCallback() { + this.classList.add("flex") + this.addressColumn = document.createElement("div") + this.addressColumn.classList.add("line_numbers") + this.assemblyColumn = document.createElement("div") + this.assemblyColumn.classList.add("assembly_code") + this.appendChild(this.addressColumn) + this.appendChild(this.assemblyColumn) + } + + render() { + const lineNumbers = [] + const assemblyLines = [] + + let currentAddress = this.startAddress + for (var i = 0; i < 1024; i++) { + const assemblyDiv = document.createElement("div") + let [assemblyStr, assemblySize] = getCurrentInstructionAt(currentAddress) + assemblyDiv.textContent = assemblyStr + assemblyLines.push(assemblyDiv) + + const addressDiv = document.createElement("div") + addressDiv.textContent = "0x" + currentAddress.toString(16).padStart(4, "0") + lineNumbers.push(addressDiv) + + currentAddress += assemblySize + if (currentAddress >= this.assemblySize) break; + } + this.addressColumn.replaceChildren(...lineNumbers) + this.assemblyColumn.replaceChildren(...assemblyLines) + } +} + +customElements.define("assembly-view", AssemblyViewElement) diff --git a/src/web/index.html b/src/web/index.html index 9c5f7d3..b99cd1e 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -17,209 +17,179 @@ } } + + register-field { + display: inline-block; + background: #AAA; + display: flex; + gap: 0.25rem; + } + register-field .name, + register-field .value { + display: inline-block; + background: #BBB; + } + register-field .value { + text-align: center; + padding: 0.25ch 1ch; + flex-grow: 1; + } + register-field .value--byte { + width: 8ch; + } + register-field .name { + text-align: right; + min-width: 4ch + } + +

sim8086.wasm

-
-
a
-
-
- -
-
-
- - - - - - - - - - -
-
-
-
+
+
+ + + + + +
+
+ +
+ + + + + + + + + + +
-
+ + + - \ No newline at end of file + diff --git a/src/web/register-field.js b/src/web/register-field.js new file mode 100644 index 0000000..d7385b2 --- /dev/null +++ b/src/web/register-field.js @@ -0,0 +1,132 @@ +class RegisterInputElement extends HTMLInputElement { + /** @type {"low"|"high"|"full"} */ + position = "full" + reg = "" + /** @type {"hex"|"decimal"|"binary"} */ + format = "decimal" + validFormatSymbols = { + hex: "0123456789abcdef", + decimal: "0123456789", + binary: "01" + } + + onChange(e) { + let parsed = parseInt(this.value, this.validFormatSymbols[this.format].length) + if (parsed === NaN) { + parsed = 0 + } + + this.setInputValue(parsed) + this.render() + } + + onKeyPress(e) { + if (!this.validFormatSymbols[this.format].includes(e.key.toLocaleLowerCase())) { + e.preventDefault() + } + } + + connectedCallback() { + this.reg = this.getAttribute("reg").toLocaleLowerCase() + this.position = this.getAttribute("position") + this.classList.add("value") + if (this.position !== "full") { + this.classList.add("value--byte") + } + this.addEventListener("change", this.onChange) + this.addEventListener("keypress", this.onKeyPress) + } + + getInputValue() { + const value = registers[this.reg].get() + switch (this.position) { + case "full": return value & 0xFFFF + case "low": return value & 0xFF + case "high": return (value >> 8) & 0xFF + } + } + + setInputValue(value) { + let current = registers[this.reg].get() + let newValue + switch (this.position) { + case "full": + newValue = value + break; + case "low": + newValue = (current & 0xFF00) | (value & 0xFF) + break; + case "high": + newValue = (current & 0x00FF) | ((value & 0xFF) << 8) + break; + } + registers[this.reg].set(newValue) + } + + render() { + const value = this.getInputValue() + if (this.format == "hex") { + this.value = value.toString(16).padStart(this.position == "full" ? 4 : 2, "0") + } else if (this.format == "decimal") { + this.value = value + } else if (this.format == "binary") { + this.value = value.toString(2).padStart(this.position == "full" ? 16 : 8, "0") + } + } +} + +class RegisterElement extends HTMLElement { + reg = "" + readonly = false + hasHighLow = false + + /** @type {RegisterInputElement} */ + valueElement = undefined + /** @type {RegisterInputElement} */ + highByteElement = undefined + /** @type {RegisterInputElement} */ + lowByteElement = undefined + + constructor() { + super() + this.readonly = this.getAttribute("readonly") !== null + this.reg = this.getAttribute("reg").toLocaleLowerCase() + this.hasHighLow = this.getAttribute("has-low-high") !== null + + const nameElement = this.appendChild(document.createElement("span")) + nameElement.classList.add("name") + nameElement.innerText = this.reg.toUpperCase() + + if (this.hasHighLow) { + const highByteElem = document.createElement("input", { is: "register-input" }) + highByteElem.setAttribute("reg", this.reg) + highByteElem.setAttribute("position", "high") + if (this.readonly) highByteElem.setAttribute("readonly", "") + this.highByteElement = this.appendChild(highByteElem) + + const lowByteElem = document.createElement("input", { is: "register-input" }) + lowByteElem.setAttribute("reg", this.reg) + lowByteElem.setAttribute("position", "low") + if (this.readonly) lowByteElem.setAttribute("readonly", "") + this.lowByteElement = this.appendChild(lowByteElem) + } else { + const elem = document.createElement("input", { is: "register-input" }) + elem.setAttribute("reg", this.reg) + elem.setAttribute("position", "full") + if (this.readonly) elem.setAttribute("readonly", "") + this.valueElement = this.appendChild(elem) + } + } + + render() { + if (this.hasHighLow) { + this.lowByteElement.render() + this.highByteElement.render() + } else { + this.valueElement.render() + } + } +} + +customElements.define("register-input", RegisterInputElement, { extends: "input" }) +customElements.define("register-field", RegisterElement)