Message Boards Message Boards

GROUPS:

Emulating the CHIP-8 architecture in Mathematica.

Posted 8 years ago
3638 Views
|
0 Replies
|
12 Total Likes
|
The CHIP-8 is an old and simple computer architecture designed in the mid-70s. It's among the easiest to reproduce via software, and is a good starting project when learning emulation.

I've yet to see proper emulation inside of Mathematica, so I decided to write a simple example myself. It turned out to be much easier than I thought. I'd previously written an emulator using JavaScript, and in comparison, Mathematica made everything astonishingly easy.

We can start by declaring the emulator as a module function.
Chip8Emulator = Module[{BIOS = #, Pro = #2, InitChp8, Memory, Stk, Screen, RI, PC, Key,
   DelayTimer, SoundTimer, V, ReadOp, Cycle, s, t, sc, ba, KS, dis, wait},
Here I've decided to make the emulator a binary function, with the first input being stored as the BIOS, and the second the program proper. This is going to be designed to use the Import[# , "Binary"] function. I had the files on my computer as .ch8 files, and upon import, they were lists of numbers that will be fed into the Chip8Emulator function.  The rest of the variables will be explained as they come up. 

When the function is called, we want everything to be initialized and ready for use. 
 InitChp8 := (
    Memory = Table[0, {4096}];
    Stk = Table[0, {16}];
    Screen = Table[0, {2048}];
    
    RI = 0; PC = 16^^200; Key = 0;
    Do[V[j] = 0, {j, 0, 15}];
    
    DelayTimer = 0; SoundTimer = 0;
   
   Do[Memory[[i + 16^^50 - 1]] = BIOS[[i]], {i, Length@BIOS}];
   Do[Memory[[i + 16^^200 - 1]] = Pro[[i]], {i, Length@Pro}];
);

InitChp8;
The memory, stack, and screen are all set to arrays containing 0s. The registers, being RI (usually called "I", but that's already defined in Mathematica), Key, and the 16 V registers, are all set to 0. Same with the timers. PC is set to the position that the program will later be stored. Then the BIOS and program are loaded into Memory by copying them bit by bit to the correct location. The initialization is then performed once.
KS := If[wait == True,
   wait = False;
   V[s[Memory[[PC]] 16^2 + Memory[[PC + 1]] ]] = Key;
   RunScheduledTask[Cycle, 1/60]];
This is the keystroke function. This starts up the cycling (to be defined later) upon a key press if the program is waiting.

We now need a way to interpret the binary in the program.
 s = Function[x, BitShiftRight[BitAnd[3840, x], 8]];
 t = Function[x, BitShiftRight[BitAnd[240, x], 4]];
 sc = Function[{j, vx, i, vy}, 1 + Mod[j + vx + 64 (i + vy), 64 32]];
 ba = Function[x, BitAnd[x, 255]];
 
 ReadOp = Switch[#~BitShiftRight~12,
     0, Switch[#,
          16^^e0, Screen = Table[0, {2048}],
          16^^EE, PC = Stk[[1]]; Stk = Append[Rest@Stk, 0];],
    1, PC = BitAnd[#, 4095] - 2,
    2, Stk = Prepend[Most@Stk, PC]; PC = BitAnd[#, 4095] - 2,
    3, If[V[s@#] == ba@#, PC += 2],
    4, If[V[s@#] != ba@#, PC += 2],
    5, If[V[s@#] == V[t@#], PC += 2],
    6, V[s@#] = ba@#,
    7, V[s@#] = BitAnd[V[s@#] + BitAnd[#, 255], 255],
    8, Switch[Mod[#, 16],
         0, V[s@#] = V[t@#],
         1, V[s@#] = BitOr[V[s@#], V[t@#]],
         2, V[s@#] = BitAnd[V[s@#], V[t@#]],
         3, V[s@#] = BitXor[V[s@#], V[t@#]],
         4, V[s@#] = Mod[V[s@#] + V[t@#], 16^2]; If[V[s@#] < V[t@#], V[15] = 1, V[15] = 0],
         5, V[s@#] = Mod[V[s@#] - V[t@#], 16^2]; If[V[s@#] > V[t@#], V[15] = 0, V[15] = 1],
         6, V[15] = IntegerDigits[V[s@#], 2][[-1]]; V[s@#] = BitShiftRight@V[s@#],
         7, V[s@#] = Mod[V[t@#] - V[s@#], 16^2]; If[V[t@#] > V[s@#], V[15] = 0, V[15] = 1],
         14, V[15] = IntegerDigits[V[s@#], 2][[1]]; V[s@#] = BitShiftLeft@V[s@#]],
    9, If[V[s@#] != V[t@#], PC += 2],
    10, RI = BitAnd[#, 4095],
    11, PC = Mod[# + V[0], 16^3],
    12, V[s@#] = BitAnd[RandomInteger[{0, 16^2 - 1}], ba@#],
    13, Block[{vx = V[s@#], vy = V[t@#], l}, V[15] = 0;
     Do[l = sc[j, vx, i, vy];
          If[BitAnd[16^^80~BitShiftRight~j, Memory[[i + RI]]] != 0,
       If[Screen[[l]] == 1, V[15] = 1];
           Screen[[l]] = Screen[[l]]~BitXor~1;], {i, 0,
       BitAnd[#, 15] - 1}, {j, 0, 7}]],
    14, Switch[ba@#,
         16^^9e, If[V[s@#] == Key, PC += 2],
         16^^a1, If[V[s@#] != Key, PC += 2]],
    15, Switch[ba@#,
         16^^7, V[s@#] = DelayTimer,
         16^^a, RemoveScheduledTask@ScheduledTasks[]; wait = True,
         16^^15, DelayTimer = V[s@#],
         16^^18, SoundTimer = V[s@#],
         16^^1e, RI = Mod[RI + V[s@#], 16^3],
         16^^29, RI = 16^^50 + 5 Mod[V[s@#], 16],
         16^^33, Block[{i = IntegerDigits[V[s@#], 10, 3]},
            Memory[[RI]] = i[[3]]; Memory[[RI + 1]] = i[[2]]; Memory[[RI + 2]] = i[[1]]],
         16^^55, Block[{i}, For[i = 0, i <= s@#, i++, Memory[[i + RI]] = V[i]]],
         16^^65, Block[{i}, For[i = 0, i <= s@#, i++, V[i] = Memory[[i + RI]]]]
         ]] &;
The ReadOp function is fed a four digit (in base-16) number, and then runs an opcode for it. A detailed explanation of what each code is meant to do is given in the Wiki article linked to at the beginning.

It's worth noting that most emulators have the opcode reader and the opcode executors separate. For instance one would define an "opcodeD" function that would be called by ReadOp upon being feed "DXXX", but I found it more convenient to merge them into a single statement. An assembly language with less consistency, such as a Z80 variant,  might require the usual separation.
Cycle :=
  (ReadOp[Memory[[PC]] 16^2 + Memory[[PC + 1]] ];PC += 2;
   If[SoundTimer > 0, EmitSound[Play[Sin[2000 t], {t, 0, .1}]];
    SoundTimer--]; If[DelayTimer > 0, DelayTimer--];);
The cycle is what the computer will do every 1/60 seconds. It looks ahead two spaces in Memory, feeds that into ReadOp, increments the program counter, and updates the timers. 
 TableForm@{{Dynamic[
        TableForm@Image[Partition[Screen, 64], ImageSize -> Large]], {
        (Button[IntegerString[#, 16], Key = #; KS] & /@ {1, 2, 3, 12, 4, 5, 6, 13, 7, 8, 9, 14, 10, 0, 11, 15})~
        Partition~4~Grid~(Spacings -> {0, 0}), Button["Cycle", Cycle],
        Button["Start", RunScheduledTask[Cycle, 1/60]],
        Button["Stop", RemoveScheduledTask@ScheduledTasks[]],
        Button["Clear", InitChp8]},
       Dynamic@TableForm@Flatten@{"PC:   " <> IntegerString[PC, 16, 3],
           "Op:   " <>If[StringLength[dis = StringJoin@
                IntegerString[Memory[[PC ;; PC + 1]], 16]] == 4, dis, "0" <> dis],
          Array["V[" <> IntegerString[#, 16] <> "]: " <> IntegerString[V@#, 16, 2] &, 16, {0, 15}]},
      Dynamic@TableForm@Flatten@{"I:   " <> IntegerString[RI, 16, 3], "Stack:", IntegerString[Stk, 16, 3]}
      }}
   ] &;
And the rest of the code is mostly formatting the display and defining the controls. One thing I couldn't figure out how to do was keep a variable as a value only so long as a button is clicked. For instance, if 5 is pressed down, Key should be 5, and stop being 5 once the button is let go. Instead, Key stays whatever the last button pressed was.

The actual emulator can then the be called as;
Chip8Emulator[bios, spaceInvaders]
Which, after a bit of running may look like;
















And last, here are some binary files I imported for testing, including the BIOS. The controls for Space Invaders are 4 5 and 6, Pong uses 1 and 4, and Breakout uses 4 and 6.
  bios := {240, 144, 144, 144, 240, 32, 96, 32, 32, 112, 240, 16, 240,
     128, 240, 240, 16, 240, 16, 240, 144, 144, 240, 16, 16, 240, 128,
     240, 16, 240, 240, 128, 240, 144, 240, 240, 16, 32, 64, 64, 240,
     144, 240, 144, 240, 240, 144, 240, 16, 240, 240, 144, 240, 144,
     144, 224, 144, 224, 144, 224, 240, 128, 128, 128, 240, 224, 144,
     144, 144, 224, 240, 128, 240, 128, 240, 240, 128, 240, 128, 128};
  
  spaceInvaders := {18, 37, 83, 80, 65, 67, 69, 32, 73, 78, 86, 65, 68,
     69, 82, 83, 32, 48, 46, 57, 49, 32, 66, 121, 32, 68, 97, 118, 105,
    100, 32, 87, 73, 78, 84, 69, 82, 96, 0, 97, 0, 98, 8, 163, 221,
    208, 24, 113, 8, 242, 30, 49, 32, 18, 45, 112, 8, 97, 0, 48, 64,
    18, 45, 105, 5, 108, 21, 110, 0, 35, 145, 96, 10, 240, 21, 240, 7,
    48, 0, 18, 75, 35, 145, 126, 1, 18, 69, 102, 0, 104, 28, 105, 0,
    106, 4, 107, 10, 108, 4, 109, 60, 110, 15, 0, 224, 35, 117, 35, 81,
     253, 21, 96, 4, 224, 158, 18, 125, 35, 117, 56, 0, 120, 255, 35,
    117, 96, 6, 224, 158, 18, 139, 35, 117, 56, 57, 120, 1, 35, 117,
    54, 0, 18, 159, 96, 5, 224, 158, 18, 233, 102, 1, 101, 27, 132,
    128, 163, 217, 212, 81, 163, 217, 212, 81, 117, 255, 53, 255, 18,
    173, 102, 0, 18, 233, 212, 81, 63, 1, 18, 233, 212, 81, 102, 0,
    131, 64, 115, 3, 131, 181, 98, 248, 131, 34, 98, 8, 51, 0, 18, 201,
     35, 125, 130, 6, 67, 8, 18, 211, 51, 16, 18, 213, 35, 125, 130, 6,
     51, 24, 18, 221, 35, 125, 130, 6, 67, 32, 18, 231, 51, 40, 18,
    233, 35, 125, 62, 0, 19, 7, 121, 6, 73, 24, 105, 0, 106, 4, 107,
    10, 108, 4, 125, 244, 110, 15, 0, 224, 35, 81, 35, 117, 253, 21,
    18, 111, 247, 7, 55, 0, 18, 111, 253, 21, 35, 81, 139, 164, 59, 18,
     19, 27, 124, 2, 106, 252, 59, 2, 19, 35, 124, 2, 106, 4, 35, 81,
    60, 24, 18, 111, 0, 224, 164, 221, 96, 20, 97, 8, 98, 15, 208, 31,
    112, 8, 242, 30, 48, 44, 19, 51, 96, 255, 240, 21, 240, 7, 48, 0,
    19, 65, 240, 10, 0, 224, 167, 6, 254, 101, 18, 37, 163, 193, 249,
    30, 97, 8, 35, 105, 129, 6, 35, 105, 129, 6, 35, 105, 129, 6, 35,
    105, 123, 208, 0, 238, 128, 224, 128, 18, 48, 0, 219, 198, 123, 12,
     0, 238, 163, 217, 96, 28, 216, 4, 0, 238, 35, 81, 142, 35, 35, 81,
     96, 5, 240, 24, 240, 21, 240, 7, 48, 0, 19, 137, 0, 238, 106, 0,
    141, 224, 107, 4, 233, 161, 18, 87, 166, 12, 253, 30, 240, 101, 48,
     255, 19, 175, 106, 0, 107, 4, 109, 1, 110, 1, 19, 151, 165, 10,
    240, 30, 219, 198, 123, 8, 125, 1, 122, 1, 58, 7, 19, 151, 0, 238,
    60, 126, 255, 255, 153, 153, 126, 255, 255, 36, 36, 231, 126, 255,
    60, 60, 126, 219, 129, 66, 60, 126, 255, 219, 16, 56, 124, 254, 0,
    0, 127, 0, 63, 0, 127, 0, 0, 0, 1, 1, 1, 3, 3, 3, 3, 0, 0, 63, 32,
    32, 32, 32, 32, 32, 32, 32, 63, 8, 8, 255, 0, 0, 254, 0, 252, 0,
    254, 0, 0, 0, 126, 66, 66, 98, 98, 98, 98, 0, 0, 255, 0, 0, 0, 0,
    0, 0, 0, 0, 255, 0, 0, 255, 0, 125, 0, 65, 125, 5, 125, 125, 0, 0,
    194, 194, 198, 68, 108, 40, 56, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0,
    255, 0, 0, 255, 0, 247, 16, 20, 247, 247, 4, 4, 0, 0, 124, 68, 254,
     194, 194, 194, 194, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0,
    255, 0, 239, 32, 40, 232, 232, 47, 47, 0, 0, 249, 133, 197, 197,
    197, 197, 249, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255,
    0, 190, 0, 32, 48, 32, 190, 190, 0, 0, 247, 4, 231, 133, 133, 132,
    244, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 127,
    0, 63, 0, 127, 0, 0, 0, 239, 40, 239, 0, 224, 96, 111, 0, 0, 255,
    0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 0, 0, 254, 0, 252, 0, 254,
    0, 0, 0, 192, 0, 192, 192, 192, 192, 192, 0, 0, 252, 4, 4, 4, 4, 4,
     4, 4, 4, 252, 16, 16, 255, 249, 129, 185, 139, 154, 154, 250, 0,
    250, 138, 154, 154, 155, 153, 248, 230, 37, 37, 244, 52, 52, 52, 0,
     23, 20, 52, 55, 54, 38, 199, 223, 80, 80, 92, 216, 216, 223, 0,
    223, 17, 31, 18, 27, 25, 217, 124, 68, 254, 134, 134, 134, 252,
    132, 254, 130, 130, 254, 254, 128, 192, 192, 192, 254, 252, 130,
    194, 194, 194, 252, 254, 128, 248, 192, 192, 254, 254, 128, 240,
    192, 192, 192, 254, 128, 190, 134, 134, 254, 134, 134, 254, 134,
    134, 134, 16, 16, 16, 16, 16, 16, 24, 24, 24, 72, 72, 120, 156,
    144, 176, 192, 176, 156, 128, 128, 192, 192, 192, 254, 238, 146,
    146, 134, 134, 134, 254, 130, 134, 134, 134, 134, 124, 130, 134,
    134, 134, 124, 254, 130, 254, 192, 192, 192, 124, 130, 194, 202,
    196, 122, 254, 134, 254, 144, 156, 132, 254, 192, 254, 2, 2, 254,
    254, 16, 48, 48, 48, 48, 130, 130, 194, 194, 194, 254, 130, 130,
    130, 238, 56, 16, 134, 134, 150, 146, 146, 238, 130, 68, 56, 56,
    68, 130, 130, 130, 254, 48, 48, 48, 254, 2, 30, 240, 128, 254, 0,
    0, 0, 0, 6, 6, 0, 0, 0, 96, 96, 192, 0, 0, 0, 0, 0, 0, 24, 24, 24,
    24, 0, 24, 124, 198, 12, 24, 0, 24, 0, 0, 254, 254, 0, 0, 254, 130,
     134, 134, 134, 254, 8, 8, 8, 24, 24, 24, 254, 2, 254, 192, 192,
    254, 254, 2, 30, 6, 6, 254, 132, 196, 196, 254, 4, 4, 254, 128,
    254, 6, 6, 254, 192, 192, 192, 254, 130, 254, 254, 2, 2, 6, 6, 6,
    124, 68, 254, 134, 134, 254, 254, 130, 254, 6, 6, 6, 68, 254, 68,
    68, 254, 68, 168, 168, 168, 168, 168, 168, 168, 108, 90, 0, 12, 24,
     168, 48, 78, 126, 0, 18, 24, 102, 108, 168, 90, 102, 84, 36, 102,
    0, 72, 72, 24, 18, 168, 6, 144, 168, 18, 0, 126, 48, 18, 168, 132,
    48, 78, 114, 24, 102, 168, 168, 168, 168, 168, 168, 144, 84, 120,
    168, 72, 120, 108, 114, 168, 18, 24, 108, 114, 102, 84, 144, 168,
    114, 42, 24, 168, 48, 78, 126, 0, 18, 24, 102, 108, 168, 114, 84,
    168, 90, 102, 24, 126, 24, 78, 114, 168, 114, 42, 24, 48, 102, 168,
     48, 78, 126, 0, 108, 48, 84, 78, 156, 168, 168, 168, 168, 168,
    168, 168, 72, 84, 126, 24, 168, 144, 84, 120, 102, 168, 108, 42,
    48, 90, 168, 132, 48, 114, 42, 168, 216, 168, 0, 78, 18, 168, 228,
    162, 168, 0, 78, 18, 168, 108, 42, 84, 84, 114, 168, 132, 48, 114,
    42, 168, 222, 156, 168, 114, 42, 24, 168, 12, 84, 72, 90, 120, 114,
     24, 102, 168, 102, 24, 90, 84, 102, 114, 108, 168, 114, 42, 0,
    114, 168, 114, 42, 24, 168, 48, 78, 126, 0, 18, 24, 102, 108, 168,
    0, 102, 24, 168, 48, 78, 12, 102, 24, 0, 108, 48, 78, 36, 168, 114,
     42, 24, 48, 102, 168, 30, 84, 102, 12, 24, 156, 168, 36, 84, 84,
    18, 168, 66, 120, 12, 60, 168, 174, 168, 168, 168, 168, 168, 168,
    168, 255};
 
 pong := {106, 2, 107, 12, 108, 63, 109, 12, 162, 234, 218, 182, 220,
    214, 110, 0, 34, 212, 102, 3, 104, 2, 96, 96, 240, 21, 240, 7, 48,
    0, 18, 26, 199, 23, 119, 8, 105, 255, 162, 240, 214, 113, 162, 234,
     218, 182, 220, 214, 96, 1, 224, 161, 123, 254, 96, 4, 224, 161,
    123, 2, 96, 31, 139, 2, 218, 182, 141, 112, 192, 10, 125, 254, 64,
    0, 125, 2, 96, 0, 96, 31, 141, 2, 220, 214, 162, 240, 214, 113,
    134, 132, 135, 148, 96, 63, 134, 2, 97, 31, 135, 18, 70, 2, 18,
   120, 70, 63, 18, 130, 71, 31, 105, 255, 71, 0, 105, 1, 214, 113,
   18, 42, 104, 2, 99, 1, 128, 112, 128, 181, 18, 138, 104, 254, 99,
   10, 128, 112, 128, 213, 63, 1, 18, 162, 97, 2, 128, 21, 63, 1, 18,
   186, 128, 21, 63, 1, 18, 200, 128, 21, 63, 1, 18, 194, 96, 32, 240,
    24, 34, 212, 142, 52, 34, 212, 102, 62, 51, 1, 102, 3, 104, 254,
   51, 1, 104, 2, 18, 22, 121, 255, 73, 254, 105, 255, 18, 200, 121,
   1, 73, 2, 105, 1, 96, 4, 240, 24, 118, 1, 70, 64, 118, 254, 18,
   108, 162, 242, 254, 51, 242, 101, 241, 41, 100, 20, 101, 0, 212,
   85, 116, 21, 242, 41, 212, 85, 0, 238, 128, 128, 128, 128, 128,
   128, 128};

breakout := {110, 5, 101, 0, 107, 6, 106, 0, 163, 12, 218, 177, 122,
   4, 58, 64, 18, 8, 123, 2, 59, 18, 18, 6, 108, 32, 109, 31, 163, 16,
    220, 209, 34, 246, 96, 0, 97, 0, 163, 18, 208, 17, 112, 8, 163,
   14, 208, 17, 96, 64, 240, 21, 240, 7, 48, 0, 18, 52, 198, 15, 103,
   30, 104, 1, 105, 255, 163, 14, 214, 113, 163, 16, 220, 209, 96, 4,
   224, 161, 124, 254, 96, 6, 224, 161, 124, 2, 96, 63, 140, 2, 220,
   209, 163, 14, 214, 113, 134, 132, 135, 148, 96, 63, 134, 2, 97, 31,
    135, 18, 71, 31, 18, 172, 70, 0, 104, 1, 70, 63, 104, 255, 71, 0,
   105, 1, 214, 113, 63, 1, 18, 170, 71, 31, 18, 170, 96, 5, 128, 117,
    63, 0, 18, 170, 96, 1, 240, 24, 128, 96, 97, 252, 128, 18, 163,
   12, 208, 113, 96, 254, 137, 3, 34, 246, 117, 1, 34, 246, 69, 96,
   18, 222, 18, 70, 105, 255, 128, 96, 128, 197, 63, 1, 18, 202, 97,
   2, 128, 21, 63, 1, 18, 224, 128, 21, 63, 1, 18, 238, 128, 21, 63,
   1, 18, 232, 96, 32, 240, 24, 163, 14, 126, 255, 128, 224, 128, 4,
   97, 0, 208, 17, 62, 0, 18, 48, 18, 222, 120, 255, 72, 254, 104,
   255, 18, 238, 120, 1, 72, 2, 104, 1, 96, 4, 240, 24, 105, 255, 18,
   112, 163, 20, 245, 51, 242, 101, 241, 41, 99, 55, 100, 0, 211, 69,
   115, 5, 242, 41, 211, 69, 0, 238, 240, 0, 128, 0, 252, 0, 170};
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract