Skip to content

Instantly share code, notes, and snippets.

@rpazyaquian
Created May 10, 2014 22:44
Show Gist options
  • Save rpazyaquian/bf6359bcf6d82da086e2 to your computer and use it in GitHub Desktop.
Save rpazyaquian/bf6359bcf6d82da086e2 to your computer and use it in GitHub Desktop.
# CHIP-8 emulator, work in progress
# RAM array: 4 kilobytes (4096 unsigned 8-bit integers). First 512 elements are taken up by the interpreter, 0x200 to 0xFFF are free.
ram = bytearray(4096)
# Variable registers: 16 8-bit variable registers. Referred to as VX, where X is a hexadecimal digit (V0, VF, etc.)
v = bytearray(16) # VX = v[x], where x is a number between 0 and 15 (corresponding to 0 through F)
# I is a variable holding an address of RAM. It's an integer, and corresponds to an element in the RAM bytearray.
i = 0
# This is a CHIP-8 program:
program = 'a21e c201 3201 a21a d014 7004 3040 1200 6000 7104 3120 1200 1218 8040 2010 2040 8010'
# This is a typical opcode:
opcode = '1200' # Jump to address 0x200.
# NOTE: Think about how opcodes will be input. Obviously, it's not a string! It should be a 16 bit integer.
# Should I try and change an integer to a hex representation then, and split it based on that?
# I should not start working with strings, here. That misses the point.
# Look up bit shifting and bit masking.
# How this should work:
# 1. initialize a 4096 large bit array.
# 2. Read the input program two bytes at a time.
# 3. These two bytes must be compared against a list of pre-defined opcodes. This must be performed with bitmasking (right?).
# 4.
def input_to_int(byte1, byte2):
return ((byte1 << 8) | byte2)
def int_to_command(int):
# eg: If the input is 1234, then the bit representation is:
# 0001 0010 0011 0100
# So, for example, to find what the first nibble is, shift right 12 places (results in 0001).
# Then, & it with 1111 to look for anything that is 1 AND 1.
# This will return only the value of that section.
return (((int >> 12) & 0xF), ((int >> 8) & 0x0F), ((int >> 4) & 0x00F), ((int >> 0) & 0x000F))
# This will output a tuple of the value of the first nibble, second nibble, etc.
# Actually, I should do the splitting on a case by case basis. I think.
# So I really just need to define each byte, and each nib, and NN + NNN can just be the last two and last three nibs.
# I wish Python had cases.
# The following is taken from http://devernay.free.fr/hacks/chip8/chip8def.htm:
def command_to_opcode(command): # command should be a tuple from int_to_command()
if command[0] == 0:
if ((command[1] == 0 and command[2] == 15) and command[3] == 0):
return 'Clear the screen.'
elif ((command[1] == 0 and command[2] == 15) and command[3] == 15):
return 'Return from subroutine.'
else:
return '0{0}{1}{2} - Call RCA-1802 program from 0x{0}{1}{2}. (this is the original interpreter)'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1])
elif command[0] == 1:
return '1{0}{1}{2} - Jumps to address 0x{0}{1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1]) # figure out how to concatenate integers!
elif command[0] == 2:
return '2{0}{1}{2} - Calls subroutine at 0x{0}{1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1]) # figure out how to concatenate integers!
# (isn't there something about masking that can do concat? ORing, maybe?)
elif command[0] == 3:
return '3{0}{1}{2} - Skips the next instruction if V{0} equals {1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1]) # concatenate ints!
elif command[0] == 4:
return '4{0}{1}{2} - Skips the next instruction if V{0} doesn\'t equal {1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1]) # concatenate!
elif (command[0] == 5 and command[3] == 0):
return '5{0}{1}0 - Skips the next instruction if V{0} equals V{1}.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[0] == 6:
return '6{0}{1}{2} - Sets V{0} to {1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1])
elif command[0] == 7:
return '7{0}{1}{2} - Adds {1}{2} to V{0}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1])
elif command[0] == 8:
if command[3] == 0:
return '8{0}{1}0 - Sets V{0} to the value of V{1}.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 1:
return '8{0}{1}1 - Sets V{0} to (V{0} or V{1}).'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 2:
return '8{0}{1}2 - Sets V{0} to (V{0} and V{1}).'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 3:
return '8{0}{1}3 - Sets V{1} to (V{0} xor V{1}).'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 4:
return '8{0}{1}4 - Adds V{0} to V{1}. VF is set to 1 when there\'s a carry, and to 0 when there isn\'t.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 5:
return '8{0}{1}5 - V{1} is subtracted from V{0}. VF is set to 0 when there\'s a borrow, and 1 when there isn\'t'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 6:
return '8{0}{1}6 - Shifts V{0} right by one. VF is set to the value of the least significant bit of V{0} before the shift.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 7:
return '8{0}{1}7 - Sets V{0} to (V{1} minus V{0}). VF is set to 0 when there\'s a borrow, and 1 when there isn\'t.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[3] == 15:
return '8{0}{1}E - Shifts V{0} left by one. VF is set to the value of the most significant bit of V{0} before the shift.'.format(hex(command[1])[-1], hex(command[2])[-1])
else:
return "invalid opcode"
elif (command[0] == 9 and command[4] == 0):
return '9{0}{1}0 - Skips the next instruction if V{0} doesn\'t equal V{1}.'.format(hex(command[1])[-1], hex(command[2])[-1])
elif command[0] == 10:
return 'A{0}{1}{2} - Set I to address 0x{0}{1}{2}.'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1])
elif command[0] == 11:
return 'B{0}{1}{2} - Jumps to the address (0x{0}{1}{2} plus V0).'.format(hex(command[1])[-1], hex(command[2])[-1], hex(command[3])[-1]) # hooray, mixed addition!
elif command[0] == 12:
return 'CRXX - set vr to a random number less than or equal to XX'
elif command[0] == 13:
return 'DXYN - Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded (with the most significant bit of each byte displayed on the left) starting from memory location I; I value doesn\'t change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn\'t happen.'
elif command[0] == 14:
if (command[2] == 9 and command[3] == 15):
return 'EK9E - skip next if key # stored in register V(K) is pressed'
elif (command[2] == 10 and command[3] == 1):
return 'EK9E - skip next if key # stored in register V(K) is not pressed'
else:
return 'invalid opcode'
elif command[0] == 15:
if (command[2] == 0 and command[3] == 7):
return 'FX07 - Set VX to value of delay timer'
elif (command[2] == 0 and command[3] == 10):
return 'FX0A - A key press is awaited, and then stored in VX.'
elif (command[2] == 1 and command[3] == 5):
return 'FX15 - Set delay timer to VX.'
elif (command[2] == 1 and command[3] == 8):
return 'FX18 - Set sound timer to VX.'
elif (command[2] == 1 and command[3] == 15):
return 'FX1E - Add VX to I.'
elif (command[2] == 2 and command[3] == 9):
return 'FX29 - Set I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4x5 font.'
elif (command[2] == 3 and command[3] == 3):
return 'FX33 - Store the Binary-coded decimal representation of VX, with the most significant of three digits at the address in I, the middle digit at I plus 1, and the least significant digit at I plus 2. (In other words, take the decimal representation of VX, place the hundreds digit in memory at location in I, the tens digit at location I+1, and the ones digit at location I+2.)'
# that last one looks like a huge pain in the ass...
elif (command[2] == 5 and command[3] == 5):
return 'FX55 - Store V0 to VX in memory starting at address I.'
elif (command[2] == 6 and command[3] == 5):
return 'FX65 - Fill V0 to VX with values from memory starting at address I.'
else:
return 'invalid opcode'
else:
return 'invalid opcode'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment