One machine, three courses
This combined project brings How computers work, Assembly language and Operating systems together. You'll build a complete little computer system: an assembler that produces real machine words, a CPU that decodes and runs them, a decoder written in RISC-V, and a kernel that time-slices two programs on your CPU.
- Day 1: the assembler (machine code)
- Day 2: the CPU (fetch, decode, execute)
- Day 3: a decoder in RISC-V assembly
- Day 4: a kernel running two programs
The encoding
// The toy ISA: 16-bit words. op (4 bits) | r (2) | s (2) | n (8)
const OPS = { HALT: 0, LOAD: 1, ADD: 2, SUB: 3, JMP: 4, JZ: 5, JNZ: 6, OUT: 7, DEC: 8, INC: 9, SYS: 10 };
const REGS = { A: 0, B: 1, C: 2, D: 3 };
function assemble(source) {
const lines = [];
const labels = {};
source.split("\n").forEach((raw, i) => {
const text = raw.split(";")[0].trim();
if (!text) return;
if (text.endsWith(":")) {
labels[text.slice(0, -1)] = lines.length; // a label names the next word's address
return;
}
lines.push({ parts: text.split(/[\s,]+/), line: i + 1 });
});
return lines.map(({ parts: [op, x, y], line }) => {
const name = op.toUpperCase();
const code = OPS[name];
if (code === undefined) throw new Error(`Line ${line}: unknown instruction ${op}`);
const reg = (t) => {
if (!(t in REGS)) throw new Error(`Line ${line}: bad register ${t}`);
return REGS[t];
};
const num = (t) => (t in labels ? labels[t] : Number(t)) & 255;
let word = code << 12;
if (["LOAD", "JZ", "JNZ"].includes(name)) word |= (reg(x) << 10) | num(y);
else if (["ADD", "SUB"].includes(name)) word |= (reg(x) << 10) | (reg(y) << 8);
else if (["OUT", "DEC", "INC"].includes(name)) word |= reg(x) << 10;
else if (["JMP", "SYS"].includes(name)) word |= num(x);
return word;
});
}LOAD A 3 → op 1, r 0, n 3 → 0x1003. ADD C B → op 2, r 2, s 1 → 0x2900. JNZ A loop (loop = 1) → 0x6001.