Skip to content

Commit 07fd36c

Browse files
committed
Set up mod support for running headless
1 parent 2a4b5be commit 07fd36c

File tree

6 files changed

+229
-30
lines changed

6 files changed

+229
-30
lines changed

mod/assets/scripts/main.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,85 @@ global.override.block(LogicBlock, {
107107
},
108108
});
109109

110+
let activeProcessors = [];
111+
112+
global.mlogv32 = {
113+
loadSchematic: (/** @type {string} */ arg) => {
114+
const [path, xRaw, yRaw, cpuXOffsetRaw, cpuYOffsetRaw, address, portRaw] = arg.split(" ");
115+
const x = parseFloat(xRaw);
116+
const y = parseFloat(yRaw);
117+
const cpuXOffset = parseFloat(cpuXOffsetRaw);
118+
const cpuYOffset = parseFloat(cpuYOffsetRaw);
119+
const port = parseInt(portRaw);
120+
if (
121+
!path.length ||
122+
isNaN(x) ||
123+
isNaN(y) ||
124+
isNaN(cpuXOffset) ||
125+
isNaN(cpuYOffset) ||
126+
isNaN(port) ||
127+
port < 0 ||
128+
port > 65535
129+
) {
130+
Log.err("Invalid arguments.");
131+
return;
132+
}
133+
134+
/** @type {Fi} */
135+
let file;
136+
if (path[0] == "/") {
137+
file = Core.files.absolute(path);
138+
} else {
139+
file = Core.files.local(path);
140+
}
141+
142+
Log.info("Loading schematic: " + file);
143+
const schem = Schematics.read(file);
144+
145+
Log.info("Placing schematic.");
146+
Schematics.place(
147+
schem,
148+
x + schem.width / 2,
149+
y + schem.height / 2,
150+
Vars.state.rules.defaultTeam,
151+
true
152+
);
153+
154+
const cpuX = x + cpuXOffset;
155+
const cpuY = cpuYOffset >= 0 ? y + cpuYOffset : y + schem.height + cpuYOffset;
156+
Log.info("CPU position: " + cpuX + "," + cpuY);
157+
158+
const build = Vars.world.build(cpuX, cpuY);
159+
if (build == null) {
160+
Log.err("CPU not found.");
161+
return;
162+
}
163+
164+
Log.info("Waiting for CPU to initialize...");
165+
166+
Time.runTask(60, () => {
167+
const processor = ProcessorAccess_Companion.of(build);
168+
if (processor == null) {
169+
Log.err("Invalid CPU: " + build);
170+
return;
171+
}
172+
173+
processor.startServer(address, port);
174+
activeProcessors.push(processor);
175+
});
176+
},
177+
178+
stopAll: () => {
179+
activeProcessors.forEach((processor) => processor.stopServer());
180+
Log.info(
181+
"Stopped " +
182+
activeProcessors.length +
183+
" processor" +
184+
(activeProcessors.length === 1 ? "" : "s") +
185+
"."
186+
);
187+
activeProcessors = [];
188+
},
189+
};
190+
110191
Log.info("Loaded mlogv32-utils scripts.");

mod/src/main/kotlin/gay/object/mlogv32/Mlogv32UtilsMod.kt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,68 @@ class Mlogv32UtilsMod : Mod() {
9595

9696
Log.info("Successfully dumped $bytes bytes from processor RAM to $file.")
9797
}
98+
99+
handler.register(
100+
"mlogv32.start",
101+
"<x> <y>",
102+
"Start the processor.",
103+
) { (x, y) ->
104+
val processor = getProcessor(x, y) ?: return@register
105+
processor.powerSwitch.configure(true)
106+
Log.info("Processor started.")
107+
}
108+
109+
handler.register(
110+
"mlogv32.pause",
111+
"<x> <y>",
112+
"Pause the processor.",
113+
) { (x, y) ->
114+
val processor = getProcessor(x, y) ?: return@register
115+
processor.singleStepSwitch.configure(true)
116+
processor.pauseSwitch.configure(true)
117+
Log.info("Processor paused.")
118+
}
119+
120+
handler.register(
121+
"mlogv32.step",
122+
"<x> <y>",
123+
"Step the processor by one instruction.",
124+
) { (x, y) ->
125+
val processor = getProcessor(x, y) ?: return@register
126+
processor.singleStepSwitch.configure(true)
127+
processor.pauseSwitch.configure(false)
128+
Log.info("Processor stepped.")
129+
}
130+
131+
handler.register(
132+
"mlogv32.unpause",
133+
"<x> <y>",
134+
"Unpause the processor.",
135+
) { (x, y) ->
136+
val processor = getProcessor(x, y) ?: return@register
137+
processor.singleStepSwitch.configure(false)
138+
processor.pauseSwitch.configure(false)
139+
Log.info("Processor unpaused.")
140+
}
141+
142+
handler.register(
143+
"mlogv32.stop",
144+
"<x> <y>",
145+
"Stop the processor.",
146+
) { (x, y) ->
147+
val processor = getProcessor(x, y) ?: return@register
148+
processor.powerSwitch.configure(false)
149+
Log.info("Processor stopped.")
150+
}
151+
152+
handler.register(
153+
"mlogv32.status",
154+
"<x> <y>",
155+
"View the processor's status.",
156+
) { (x, y) ->
157+
val processor = getProcessor(x, y) ?: return@register
158+
Log.info(processor.getStatus().toPrettyDebugString(indentWidth = 2))
159+
}
98160
}
99161
}
100162

@@ -125,3 +187,20 @@ private fun getProcessor(x: String, y: String): ProcessorAccess? {
125187

126188
return result
127189
}
190+
191+
// https://gist.github.com/mayankmkh/92084bdf2b59288d3e74c3735cccbf9f?permalink_comment_id=5355537#gistcomment-5355537
192+
private fun Any.toPrettyDebugString(indentWidth : Int = 4) = buildString {
193+
fun StringBuilder.indent(level : Int) = append("".padStart(level * indentWidth))
194+
var ignoreSpace = false
195+
var indentLevel = 0
196+
this@toPrettyDebugString.toString().onEach {
197+
when (it) {
198+
'(', '[', '{' -> appendLine(it).indent(++indentLevel)
199+
')', ']', '}' -> appendLine().indent(--indentLevel).append(it)
200+
',' -> appendLine(it).indent(indentLevel).also { ignoreSpace = true }
201+
' ' -> if (ignoreSpace) ignoreSpace = false else append(it)
202+
'=' -> append(" = ")
203+
else -> append(it)
204+
}
205+
}
206+
}

mod/src/main/kotlin/gay/object/mlogv32/ProcessorAccess.kt

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class ProcessorAccess(
118118
val client = serverSocket.accept()
119119

120120
if (runOnMainThread { build.isValid }) {
121+
Log.info("Client connected!")
121122
launch {
122123
handleClient(client)
123124
}
@@ -148,11 +149,36 @@ class ProcessorAccess(
148149
ProcessorAccess.stopServer()
149150
}
150151

151-
fun getCSR(address: Int): UInt =
152+
private fun getCSR(address: Int): UInt =
152153
csrs.executor.optionalVar(address + 1)
153154
?.numu()
154155
?: 0u
155156

157+
private fun getUIntVar(name: String) =
158+
build.executor.optionalVar(name)?.numu() ?: 0u
159+
160+
fun getStatus() = StatusResponse(
161+
running = powerSwitch.enabled,
162+
paused = pauseSwitch.enabled,
163+
state = (build.executor.optionalVar("state")?.obj() as? String) ?: "",
164+
errorOutput = errorOutput.message?.toString() ?: "",
165+
pc = build.executor.optionalVar("pc")?.numu(),
166+
instruction = build.executor.optionalVar("instruction")?.numu(),
167+
privilegeMode = build.executor.optionalVar("privilege_mode")?.numu(),
168+
registers = registers.memory.take(32).map { it.toUInt() },
169+
mscratch = getCSR(0x340),
170+
mtvec = getCSR(0x305),
171+
mepc = getCSR(0x341),
172+
mcause = getCSR(0x342),
173+
mtval = getCSR(0x343),
174+
mstatus = getUIntVar("csr_mstatus"),
175+
mip = getUIntVar("csr_mip"),
176+
mie = getUIntVar("csr_mie"),
177+
mcycle = (getCSR(0xB80).toULong() shl 32) or getCSR(0xB00).toULong(),
178+
minstret = (getCSR(0xB82).toULong() shl 32) or getUIntVar("csr_minstret").toULong(),
179+
mtime = (getUIntVar("csr_mtimeh").toULong() shl 32) or getUIntVar("csr_mtime").toULong(),
180+
)
181+
156182
private fun ramWordsSequence(startAddress: UInt) = sequence {
157183
require(startAddress in RAM_START..<ramEnd) { "Start address must be within RAM." }
158184
require(startAddress.mod(4u) == 0u) { "Start address must be aligned to 4 bytes." }
@@ -220,7 +246,6 @@ class ProcessorAccess(
220246

221247
private suspend fun handleClient(client: Socket) {
222248
client.use {
223-
Log.info("Client connected!")
224249
val rx = client.openReadChannel()
225250
val tx = client.openWriteChannel(true)
226251
while (true) {
@@ -334,9 +359,14 @@ sealed class Request {
334359
@SerialName("flash")
335360
data class FlashRequest(
336361
val path: String,
362+
val absolute: Boolean = true,
337363
) : Request() {
338364
override suspend fun handle(processor: ProcessorAccess, rx: ByteReadChannel, tx: ByteWriteChannel) = runOnMainThread {
339-
val file = Core.files.absolute(path)
365+
val file = if (absolute) {
366+
Core.files.absolute(path)
367+
} else {
368+
Core.files.local(path)
369+
}
340370
require(file.exists()) { "File not found." }
341371

342372
val bytes = processor.flashRom(file)
@@ -350,9 +380,14 @@ data class DumpRequest(
350380
val path: String,
351381
val address: UInt?,
352382
val bytes: Int?,
383+
val absolute: Boolean = true,
353384
) : Request() {
354385
override suspend fun handle(processor: ProcessorAccess, rx: ByteReadChannel, tx: ByteWriteChannel) = runOnMainThread {
355-
val file = Core.files.absolute(path)
386+
val file = if (absolute) {
387+
Core.files.absolute(path)
388+
} else {
389+
Core.files.local(path)
390+
}
356391
file.parent().mkdirs()
357392

358393
val address = address ?: ProcessorAccess.RAM_START
@@ -511,23 +546,7 @@ data object StopRequest : Request() {
511546
@SerialName("status")
512547
data object StatusRequest : Request() {
513548
override suspend fun handle(processor: ProcessorAccess, rx: ByteReadChannel, tx: ByteWriteChannel) = runOnMainThread {
514-
StatusResponse(
515-
running = processor.powerSwitch.enabled,
516-
paused = processor.pauseSwitch.enabled,
517-
errorOutput = processor.errorOutput.message?.toString() ?: "",
518-
pc = processor.build.executor.optionalVar("pc")?.numu(),
519-
instruction = processor.build.executor.optionalVar("instruction")?.numu(),
520-
privilegeMode = processor.build.executor.optionalVar("privilege_mode")?.numu(),
521-
registers = processor.registers.memory.take(32).map { it.toUInt() },
522-
mscratch = processor.getCSR(0x340),
523-
mtvec = processor.getCSR(0x305),
524-
mepc = processor.getCSR(0x341),
525-
mcause = processor.getCSR(0x342),
526-
mtval = processor.getCSR(0x343),
527-
mstatus = processor.build.executor.optionalVar("csr_mstatus")?.numu() ?: 0u,
528-
mip = processor.build.executor.optionalVar("csr_mip")?.numu() ?: 0u,
529-
mie = processor.build.executor.optionalVar("csr_mie")?.numu() ?: 0u,
530-
)
549+
processor.getStatus()
531550
}
532551
}
533552

@@ -543,6 +562,7 @@ data class SuccessResponse(val message: String) : Response()
543562
data class StatusResponse(
544563
val running: Boolean,
545564
val paused: Boolean,
565+
val state: String,
546566
val errorOutput: String,
547567
val pc: UInt?,
548568
val instruction: UInt?,
@@ -556,6 +576,9 @@ data class StatusResponse(
556576
val mstatus: UInt,
557577
val mip: UInt,
558578
val mie: UInt,
579+
val mcycle: ULong,
580+
val minstret: ULong,
581+
val mtime: ULong,
559582
) : Response()
560583

561584
@Serializable

python/src/mlogv32/preprocessor/app.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,16 @@ def build(
9797
yaml_path: Path,
9898
cpu_config_name: Annotated[str | None, Option("-c", "--config")] = None,
9999
width: Annotated[int, Option("-w", "--width")] = 16,
100-
height: Annotated[int, Option("-h", "--height", max=16)] = 16,
101-
size: Annotated[int | None, Option("-s", "--size", max=16)] = None,
100+
height: Annotated[int, Option("-h", "--height")] = 16,
101+
size: Annotated[int | None, Option("-s", "--size")] = None,
102102
output: Annotated[Path | None, Option("-o", "--output")] = None,
103103
bin_path: Annotated[Path | None, Option("--bin")] = None,
104104
include_all: Annotated[bool, Option("--all")] = False,
105105
include_cpu: Annotated[bool, Option("--cpu")] = False,
106106
include_peripherals: Annotated[bool, Option("--peripherals")] = False,
107107
include_memory: Annotated[bool, Option("--memory")] = False,
108108
include_debugger: Annotated[bool, Option("--debugger")] = False,
109+
include_keyboard: Annotated[bool, Option("--keyboard/--no-keyboard")] = True,
109110
):
110111
"""Generate a CPU schematic.
111112
@@ -144,6 +145,11 @@ def build(
144145
width = size
145146
height = size
146147

148+
if include_keyboard and height > 16:
149+
raise ValueError(
150+
f"Maximum height is 16 if keyboard is enabled, but got {height}"
151+
)
152+
147153
if include_all:
148154
include_cpu = True
149155
include_peripherals = True
@@ -410,7 +416,7 @@ def add_with_label(block: Block, **labels: Unpack[Labels]):
410416
add_peripheral(ram_schem, 12, 17)
411417
add_peripheral(ram_schem, 13, 17)
412418
add_with_label(
413-
Block(Content.SWITCH, 13, 18, True, 0),
419+
Block(Content.SWITCH, 13, 18, False, 0),
414420
right="UART0 -> DISPLAY",
415421
)
416422

0 commit comments

Comments
 (0)