From 3cc4fd3038fd0e03ff50bd5839e3093fff02948c Mon Sep 17 00:00:00 2001 From: Nicolai Brand Date: Sat, 2 Aug 2025 12:32:46 +0200 Subject: [PATCH] remove le, and add stpc --- spec/spec.md => spec.md | 18 ++++++++-------- spec/idea.md | 46 ----------------------------------------- web_impl/dist/isa.js | 38 +++++++++++++++++----------------- web_impl/dist/vm.js | 6 +++--- web_impl/spec.html | 18 ++++++++-------- web_impl/src/isa.ts | 26 +++++++++++------------ web_impl/src/vm.ts | 6 +++--- 7 files changed, 56 insertions(+), 102 deletions(-) rename spec/spec.md => spec.md (88%) delete mode 100644 spec/idea.md diff --git a/spec/spec.md b/spec.md similarity index 88% rename from spec/spec.md rename to spec.md index 51be61f..f60b118 100644 --- a/spec/spec.md +++ b/spec.md @@ -47,15 +47,15 @@ When an immediate is used as the operand it takes up the entire four bit space o | add | 0001 | Reg Reg | r0 = op1 + op2 | | sub | 0010 | Reg Reg | r0 = op1 - op2 | | ge | 0011 | Reg Reg | r0 = op1 > op2 (1 if true else 0) | -| le | 0100 | Reg Reg | r0 = op1 < op2 (1 if true else 0) | -| inc | 0101 | Reg | op1 = op1 + 1 | -| dec | 0110 | Reg | op1 = op1 - 1 | -| shift | 0111 | Imm | r0 = r0 << imm with some nuance | -| ali | 1000 | Imm | r0 = r0 & imm | -| li | 1001 | Imm | r0 = imm | -| mv | 1010 | Reg Reg | op1 = op2 | -| load | 1011 | Reg Reg | op1 = mem$op2$ | -| store | 1100 | Reg Reg | mem$op2$ = op1 | +| inc | 0100 | Reg | op1 = op1 + 1 | +| dec | 0101 | Reg | op1 = op1 - 1 | +| shift | 0110 | Imm | r0 = r0 << imm with some nuance | +| ali | 0111 | Imm | r0 = r0 & imm | +| li | 1000 | Imm | r0 = imm | +| mv | 1001 | Reg Reg | op1 = op2 | +| load | 1010 | Reg Reg | op1 = mem\[op2\] | +| store | 1011 | Reg Reg | mem\[op2\] = op1 | +| stpc | 1100 | Reg | mem\[op1\] = pc + 2 | | jmp | 1101 | Imm | pc = imm | | jiz | 1110 | Reg Reg | if op2 = 0 then pc = pc + op1 | | jaiz | 1111 | Reg Reg | if op2 = 0 then pc = op1 | diff --git a/spec/idea.md b/spec/idea.md deleted file mode 100644 index 3968e76..0000000 --- a/spec/idea.md +++ /dev/null @@ -1,46 +0,0 @@ -... some ideas ... - - -The natural word size of the Lynx 8-bit machine will be ... 8 bits! The immediate issue with an 8-bit computer is addressing memory. With 8 bits we can address 2**8 or 256 unique addresses ... Ouch ! 8-bit machines from the ancient times solves this by having a 16-bit address bus with an accompanying a 16-bit program counter. That's cheating! We are 8-bit purist and such crap won't fly here. - -For the Lynx 8-bit machine, everything which can be 8-bit will be 8-bit - even the address bus. That means we're stuck with with only being able to address 256 bytes of memory. To make this slighlty more bearable the Lynx 8-bit machine will not be a Von Neumann machine, but rather a Harvard machine. That means there will be two seperate addressable 256 byte long memory segments. One will hold the instructions. Another will hold data. Its not a lot, but hopefully enough for a couple of toy programs. - -If we are able to pack each instructions into a single byte then the maximum length of a single program will be 256 instructions. In some sense this proposes an interesting constraint that will be fun to work around. - -So how many registers should the Lynx 8-bit machine support? If we are to pack each instruction into a byte then we don't have a lot to work with. If we allocate 4 of our 8 bits to registers, we can only represent 2**4 or 16 unique instructions. That may be just about enough for a minimal set of instructions. - -That leaves us with 4 bits to specify the registers. If we want to be able to specify two operands then thats 2 bits for each register. That leaves us with a maximum of 4 registers. For a simple machine like this, that may be enough. However, while we now have specified the two operands, we have not specifed the output register. Well, we don't really have space for that. Instead, one of the 4 registers will function as an implicit accumulator register where the output is always stored. - -Let's call our 4 registers r0, r1, r2, r3. r0 will be the special accumulator register. We must also have a program counter register, which we call pc, which tells us where in the instruction stream to fetch from. - -As for the instructions, we must be careful as we are pressed for space. Our set of 16 instructions need to cover the basics. Data movement. Arithmetic. Control flow. - -TODO: Incredibly clean semantics. - -labels immediates - -idea: variable-length opcodes. Means we can store immediates better. - -Data movemnt: -1. load r1, r2 # load value at r2 into r1 -2. store r1, r2 # store value in r1 to r2 - -Arithmetic -1. add r1, r2 -2. sub r1, r2 -3. and r1, r2 -4. or r1, r2 -5. not r1, r2 - -Control flow: -1. jmp r1 # uncondtional -2. jiz r1, r2 # jump if r2 is zero - -3. halt - - -Okay, we need some instructions that take immediates. -Ideas: -- Pack immediate in the available 4 bits -- Instructions that take immediate take up 2 bytes. Problem: need seperate add, addi, sub, subi, etc. We could extend this idea to go full variable-length encoding and use more bits for the opcode. Idk. -- if both register operands are 0b00 (r0) and 0b00 (r0), then should load the next byte as an immediate? That would mean we don't need a seperate add and addi. diff --git a/web_impl/dist/isa.js b/web_impl/dist/isa.js index 3a02ad7..ab8f86c 100644 --- a/web_impl/dist/isa.js +++ b/web_impl/dist/isa.js @@ -7,15 +7,15 @@ var InstructionKind; InstructionKind[InstructionKind["Inc"] = 3] = "Inc"; InstructionKind[InstructionKind["Dec"] = 4] = "Dec"; InstructionKind[InstructionKind["Ge"] = 5] = "Ge"; - InstructionKind[InstructionKind["Le"] = 6] = "Le"; // 4 bit immdiate. If the most significant bit is 1 then right shift, else left shift. // Shift amount is the remainding 3 bits. - InstructionKind[InstructionKind["Shift"] = 7] = "Shift"; - InstructionKind[InstructionKind["Ali"] = 8] = "Ali"; - InstructionKind[InstructionKind["Li"] = 9] = "Li"; - InstructionKind[InstructionKind["Mv"] = 10] = "Mv"; - InstructionKind[InstructionKind["Load"] = 11] = "Load"; - InstructionKind[InstructionKind["Store"] = 12] = "Store"; + InstructionKind[InstructionKind["Shift"] = 6] = "Shift"; + InstructionKind[InstructionKind["Ali"] = 7] = "Ali"; + InstructionKind[InstructionKind["Li"] = 8] = "Li"; + InstructionKind[InstructionKind["Mv"] = 9] = "Mv"; + InstructionKind[InstructionKind["Load"] = 10] = "Load"; + InstructionKind[InstructionKind["Store"] = 11] = "Store"; + InstructionKind[InstructionKind["Stpc"] = 12] = "Stpc"; InstructionKind[InstructionKind["Jmp"] = 13] = "Jmp"; InstructionKind[InstructionKind["Jiz"] = 14] = "Jiz"; InstructionKind[InstructionKind["Jaiz"] = 15] = "Jaiz"; @@ -73,52 +73,52 @@ const InstructionInfo = { encoding: 5, is_directive: false, }, - [InstructionKind.Le]: { - kind: InstructionKind.Le, - name: "le", - operand_kind: OperandKind.RegReg, - encoding: 6, - is_directive: false, - }, [InstructionKind.Shift]: { kind: InstructionKind.Shift, name: "shift", operand_kind: OperandKind.Imm, - encoding: 7, + encoding: 6, is_directive: false, }, [InstructionKind.Ali]: { kind: InstructionKind.Ali, name: "ali", operand_kind: OperandKind.Imm, - encoding: 8, + encoding: 7, is_directive: false, }, [InstructionKind.Li]: { kind: InstructionKind.Li, name: "li", operand_kind: OperandKind.Imm, - encoding: 9, + encoding: 8, is_directive: false, }, [InstructionKind.Mv]: { kind: InstructionKind.Mv, name: "mv", operand_kind: OperandKind.RegReg, - encoding: 10, + encoding: 9, is_directive: false, }, [InstructionKind.Load]: { kind: InstructionKind.Load, name: "load", operand_kind: OperandKind.RegReg, - encoding: 11, + encoding: 10, is_directive: false, }, [InstructionKind.Store]: { kind: InstructionKind.Store, name: "store", operand_kind: OperandKind.RegReg, + encoding: 11, + is_directive: false, + }, + [InstructionKind.Stpc]: { + kind: InstructionKind.Stpc, + name: "stpc", + operand_kind: OperandKind.Reg, encoding: 12, is_directive: false, }, diff --git a/web_impl/dist/vm.js b/web_impl/dist/vm.js index 65691ca..b4ba1d8 100644 --- a/web_impl/dist/vm.js +++ b/web_impl/dist/vm.js @@ -121,9 +121,6 @@ function executeNextInstruction(lynxMachine) { case InstructionInfo[InstructionKind.Ge].encoding: lynxMachine.r0 = Number(lynxMachine[reg1] > lynxMachine[reg2]); break; - case InstructionInfo[InstructionKind.Le].encoding: - lynxMachine.r0 = Number(lynxMachine[reg1] < lynxMachine[reg2]); - break; case InstructionInfo[InstructionKind.Shift].encoding: const signBit = (imm & 0b1000) >> 3; const shiftAmount = imm & 0b0111; @@ -151,6 +148,9 @@ function executeNextInstruction(lynxMachine) { case InstructionInfo[InstructionKind.Store].encoding: lynxMachine.data[lynxMachine[reg2]] = lynxMachine[reg1]; break; + case InstructionInfo[InstructionKind.Stpc].encoding: + lynxMachine.data[lynxMachine[reg1]] = lynxMachine.pc + 2; + break; // Jumps case InstructionInfo[InstructionKind.Jmp].encoding: // Interpret imm as 4-bit signed (-8 to 7) diff --git a/web_impl/spec.html b/web_impl/spec.html index f1d486e..b346225 100644 --- a/web_impl/spec.html +++ b/web_impl/spec.html @@ -69,15 +69,15 @@

The Lynx 8-bit Machine Specification

| add | 0001 | Reg Reg | r0 = op1 + op2 | | sub | 0010 | Reg Reg | r0 = op1 - op2 | | ge | 0011 | Reg Reg | r0 = op1 > op2 (1 if true else 0) | -| le | 0100 | Reg Reg | r0 = op1 < op2 (1 if true else 0) | -| inc | 0101 | Reg | op1 = op1 + 1 | -| dec | 0110 | Reg | op1 = op1 - 1 | -| shift | 0111 | Imm | r0 = r0 << imm with some nuance | -| ali | 1000 | Imm | r0 = r0 & imm | -| li | 1001 | Imm | r0 = imm | -| mv | 1010 | Reg Reg | op1 = op2 | -| load | 1011 | Reg Reg | op1 = mem$op2$ | -| store | 1100 | Reg Reg | mem$op2$ = op1 | +| inc | 0100 | Reg | op1 = op1 + 1 | +| dec | 0101 | Reg | op1 = op1 - 1 | +| shift | 0110 | Imm | r0 = r0 << imm with some nuance | +| ali | 0111 | Imm | r0 = r0 & imm | +| li | 1000 | Imm | r0 = imm | +| mv | 1001 | Reg Reg | op1 = op2 | +| load | 1010 | Reg Reg | op1 = mem\[op2\] | +| store | 1011 | Reg Reg | mem\[op2\] = op1 | +| stpc | 1100 | Reg | mem\[op1\] = pc + 2 | | jmp | 1101 | Imm | pc = imm | | jiz | 1110 | Reg Reg | if op2 = 0 then pc = pc + op1 | | jaiz | 1111 | Reg Reg | if op2 = 0 then pc = op1 | diff --git a/web_impl/src/isa.ts b/web_impl/src/isa.ts index d54680c..0d4af8a 100644 --- a/web_impl/src/isa.ts +++ b/web_impl/src/isa.ts @@ -6,7 +6,6 @@ enum InstructionKind { Inc, Dec, Ge, - Le, // 4 bit immdiate. If the most significant bit is 1 then right shift, else left shift. // Shift amount is the remainding 3 bits. Shift, @@ -16,6 +15,7 @@ enum InstructionKind { Mv, // dest, src Load, // dest, src Store, // dest, src + Stpc, // store pc + 2 Jmp, // 4bit jump Jiz, @@ -85,32 +85,25 @@ const InstructionInfo: Record = { encoding: 5, is_directive: false, }, - [InstructionKind.Le]: { - kind: InstructionKind.Le, - name: "le", - operand_kind: OperandKind.RegReg, - encoding: 6, - is_directive: false, - }, [InstructionKind.Shift]: { kind: InstructionKind.Shift, name: "shift", operand_kind: OperandKind.Imm, - encoding: 7, + encoding: 6, is_directive: false, }, [InstructionKind.Ali]: { kind: InstructionKind.Ali, name: "ali", operand_kind: OperandKind.Imm, - encoding: 8, + encoding: 7, is_directive: false, }, [InstructionKind.Li]: { kind: InstructionKind.Li, name: "li", operand_kind: OperandKind.Imm, - encoding: 9, + encoding: 8, is_directive: false, }, @@ -118,20 +111,27 @@ const InstructionInfo: Record = { kind: InstructionKind.Mv, name: "mv", operand_kind: OperandKind.RegReg, - encoding: 10, + encoding: 9, is_directive: false, }, [InstructionKind.Load]: { kind: InstructionKind.Load, name: "load", operand_kind: OperandKind.RegReg, - encoding: 11, + encoding: 10, is_directive: false, }, [InstructionKind.Store]: { kind: InstructionKind.Store, name: "store", operand_kind: OperandKind.RegReg, + encoding: 11, + is_directive: false, + }, + [InstructionKind.Stpc]: { + kind: InstructionKind.Stpc, + name: "stpc", + operand_kind: OperandKind.Reg, encoding: 12, is_directive: false, }, diff --git a/web_impl/src/vm.ts b/web_impl/src/vm.ts index 67eab0e..1b3d78a 100644 --- a/web_impl/src/vm.ts +++ b/web_impl/src/vm.ts @@ -150,9 +150,6 @@ function executeNextInstruction(lynxMachine: LynxMachine): boolean { case InstructionInfo[InstructionKind.Ge].encoding: lynxMachine.r0 = Number(lynxMachine[reg1] > lynxMachine[reg2]); break; - case InstructionInfo[InstructionKind.Le].encoding: - lynxMachine.r0 = Number(lynxMachine[reg1] < lynxMachine[reg2]); - break; case InstructionInfo[InstructionKind.Shift].encoding: const signBit = (imm & 0b1000) >> 3; const shiftAmount = imm & 0b0111; @@ -179,6 +176,9 @@ function executeNextInstruction(lynxMachine: LynxMachine): boolean { case InstructionInfo[InstructionKind.Store].encoding: lynxMachine.data[lynxMachine[reg2]] = lynxMachine[reg1]; break; + case InstructionInfo[InstructionKind.Stpc].encoding: + lynxMachine.data[lynxMachine[reg1]] = lynxMachine.pc + 2; + break; // Jumps case InstructionInfo[InstructionKind.Jmp].encoding: // Interpret imm as 4-bit signed (-8 to 7)