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”.

_='C_textarea")u_a")a=a.width=a.height=256;u.innerHTMLRUN";INPHSO $02E8JP $00"kE^ Y{return ZB+488]}p=.toLowerCase(m=[];nFWclear(En<p;if("$"==lWh";()Kf0-9]/)h+=l;h=parseInt(h,a>h?Gh):Gh&255,h/a|0)}if(lKv]/))WoF34>o;o+=2)l+p[nadsbphppmasalaindejpsocpjejnjljgbr".substr(o,2)(Go/2|0@n,o=a)}d=new Uint8Array(1002eFx=mtFy=setdownkk.keyCode}up0};B--;WiFi<x;)Zim[iQ;E=y(^ r(Wh=a;h--!t;)g=m[eQ,0DY+YV1DY-YV2DqX3DYV4q=Zf]@5Zfq@6DV7#q,8#--q,9~ez@10Zf+Yq@11Dq-X12~Y?e+=1:e=@13~`14~0<`15~0>`t===g;WiFa>i;)A,",v=Z744+i]Stylergb("v>>5)+A(v&28)>>2)+A+64*(v&3)+")"Rect(i%*,*(i/|0@,e<x!t(E=y(r,0))},0)}Y;C.rows=9;~#(z=Ze]<<8|Ze-1]qZ1E3]`Y?e=:e+=1@_=b.appendChild(d.createElement("^functionZd[Yk()X,Z488+B--f@Wfor(V,q=f@Q]Kl.match(/[a-Gm.push(F=0;D~f=BZ1001]@),#==g;u.onclic16);.length;Z487++ZeQ~fz,C.valueTimeout,c.fill="&&]=;onkeyl=p[nQ){\\n=^(+32*(';for(i in g='#@BDFGKQVWXYZ^_`qz~')with(_.split(g[i]))_=join(pop());eval(_)

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;