Marmmodore-1K: Fantasy and Tiny 8 bit computer
Writing a tiny fantasy 8-bit computer in 1 KB of JavaScript
Marmmodore-1K or Marmmodore-1000 is a fantasy 8 bit computer developed for the JS1K contest of 2016. It’s inspired by the MOS 6502 chip and the Commodore 64. It comes with a very primitive assembler and it has very limited set of instructions. 17 to be exact.
You can try out the code here DEMO.
SPECS
1) 1000 Bytes of RAM
2) It uses the last 512 bytes of RAM as frame buffer and stack.
3) The frame buffer starts at address $02E8 (744) and ends at $03E7 (999). The stack goes from $01E8 (488) to $02E7 (743).
4) Keyboard input is stored at address $01E7 (487).
5) 17 CPU instructions.
INSTRUCTION SET
#AD
ADd
Pops the 2 last elements
on the stack and adds them. Then
stores the vale on the accumulator.
#SB
SuBtract
Pops the 2 last elements on the
stack and subracts them. Then
stores the vale on the accumulator.
#PH
PusH
Pushes the accumulator into the stack
#PP
PoP
Pops the last element of the stack
in the accumulator.
#MA $FFFF
Memory to Accumulator
Loads value from memory into the
accumulator using 16 bit address
#SA $FFFF
Store Accumulator
Stores accumulator value into memory
using 16 bit address
#LA $FF
Load Accumulator
Loads 8 bit value into accumulator
#IN
INcrement
Increments the accumulator
#DE
DEcrement
Decrements the accumulator
#JP $FFFF
JumP
Equivalent to GOTO or JUMP. It'll set
program counter to the 16 bit address
argument.
#SO $FFFF
Store accumulator with stack Offset
This is an indexed store. It's like
"ma" instruction but it uses the las
element on the stack as an offset.
It's something like
mov a, $FFFF + stack.pop()
#CP $FF
ComPare
Compares the accumulator with 8 bit
value and pushes the result into
the stack
#JE $FF
Jump if Equal
Branches if last element on the
stack is 0
#JN $FF
Jump if Not equal
Branches if last element on the
stack is not 0
#JL $FFFF
Jump if Lower than
Branches if last element on the
stack is lower than 0
#JG $FFFF
Jump if Greater than
Branches if last element on the
stack is greater than 0
#BR
BReak
Breaks the execution of the program.
EXAMPLE
1) This will print 256 colors in the screen.
IN
PH
SO $02E8
JP $0000
2) This will get the keyboard input and store it on the frame buffer. It’ll do it for each pixel.
MA $0100
IN
SA $0100
PH
MA $01e7
SO $02e8
MA $0100
CP $ff
JN $00
JP $0000
The JS1K code
Here you can see the extreme compression of the code. I used a mix of Google’s Closure Compiler and RegPack to do this “crushing of the code”.
This code is exactly 1024 Bytes or 1KB which is the main limit of the JS1K contest.
The uncompressed code looks like this. It’s much more clear with all the comments.
C = b.appendChild(d.createElement('textarea'))
u = b.appendChild(d.createElement('a'))
a = a.width = a.height = 256;
u.innerHTML = "RUN";
// Sample program
C.value='IN\nPH\nSO $02E8\nJP $00';
u.onclick = function (w) {
function k() {
return d[++d[1001]+488]
}
p = C.value.toLowerCase(), m = [], n = 0;
// Assembler and
// code generation
for (clearTimeout(w); n < p.length;) {
l = p[n++];
// Scan hex values
if ("$" == l) {
for (h = ""; (l = p[n++]) && l.match(/[a-f0-9]/);)h += l;
h = parseInt(h, 16);
a > h ? m.push(h) : m.push(h & 255, h / a | 0)
}
// Scan mnemonics
if (l && l.match(/[a-v]/))
for (o = 0; 34 > o; o += 2)if (l + p[n] == "adsbphppmasalaindejpsocpjejnjljgbr".substr(o, 2))
m.push(o / 2 | 0),n++,o=a
}
// 1000 Bytes + 2 Bytes for Accumulator
// and Stack Pointer Registers.
d = new Uint8Array(1002), e = 0, x = m.length, t = 0, y = setTimeout;
onkeydown = function (e) {
d[487] = e.keyCode
};
onkeyup = function () {
d[487] = 0
};
d[1001]--;
// Burn object code into RAM
for (i = 0; i < x;)d[i] = m[i++];
// CPU Emulation
// Fetch - Decode - Execute
w = y(function E() {
for (h = a; h-- && !t;) {
g = m[e++];
// ADd
0 == g && (f = k() + k(), d[1E3] = f);
// SuBtract
1 == g && (f = k() - k(), d[1E3] = f);
// PusH
2 == g && (f = d[1E3], d[488 + d[1001]--] = f);
// PoP
3 == g && (f = k(), d[1E3] = f);
// Memory to Accumulator
4 == g && (f = d[++e] << 8 | d[e++ - 1], d[1E3] = d[f]);
// Store Accumulator
5 == g && (f = d[++e] << 8 | d[e++ - 1], d[f] = d[1E3]);
// Load Accumulator
6 == g && (f = d[e++], d[1E3] = f);
// INcrement
7 == g && ++d[1E3];
// DEcrement
8 == g && --d[1E3];
// JumP
9 == g && (e = d[++e] << 8 | d[e++ - 1]);
// Store accumulator with stack Offset
10 == g && (f = d[++e] << 8 | d[e++ - 1], d[f + k()] = d[1E3]);
// ComPare
11 == g && (f = d[1E3] - d[e++], d[488 + d[1001]--] = f);
// Jump if Equal
12 == g && (k() ? e += 1 : e = d[e++]);
// Jump if Not equal
13 == g && (k() ? e = d[e++] : e += 1);
// Jump if Lower than
14 == g && (k() > 0 ? e = d[e++] : e += 1);
// Jump if Greater than
15 == g && (k() < 0 ? e = d[e++] : e += 1);
// BReak
t = 16 == g
}
// Flush frame buffer
// into canvas.
for (i = 0; a > i;)A = ",", v = d[744 + i], c.fillStyle = "rgb(" + 32 * (v >> 5) + A + 32 * ((v & 28) >> 2) + A + 64 * (v & 3) + ")", c.fillRect(i % 16 * 16, 16 * (i++ / 16 | 0), 16, 16);
e < x && !t && (w = y(E, 0))
}, 0)
};
u.onclick();
C.rows = 9;