Created
May 10, 2014 22:44
-
-
Save rpazyaquian/bf6359bcf6d82da086e2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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