CHIP-8 Ruby Emülatörü
class CHIP8 | |
attr_accessor :memory, :gpio, :display_buffer, :stack, | |
:key_inputs, :fonts, :opcode, :index, :pc, | |
:delay_timer, :sound_timer, :should_draw, | |
:instructions, :vx, :vy | |
def initialize | |
@memory = [0] * 4096 # 4096 Baytlık belleğimiz | |
@gpio = [0] * 16 # 16 tane genel amaçlı register'ı tutan bir dizi | |
@display_buffer = [0] * 2048 # 64 * 32 = 2048 piksellik ekranımız | |
@stack = [] # Alt yordamları tutmamızı sağlayacak olan yığın | |
@key_inputs = [0] * 16 # 16 girdili klavyemiz | |
@fonts = [ # Her biri 5 bayt olarak şekilde, 16 tane fontumuz | |
0xF0, 0x90, 0x90, 0x90, 0xF0, # 0 | |
0x20, 0x60, 0x20, 0x20, 0x70, # 1 | |
0xF0, 0x10, 0xF0, 0x80, 0xF0, # 2 | |
0xF0, 0x10, 0xF0, 0x10, 0xF0, # 3 | |
0x90, 0x90, 0xF0, 0x10, 0x10, # 4 | |
0xF0, 0x80, 0xF0, 0x10, 0xF0, # 5 | |
0xF0, 0x80, 0xF0, 0x90, 0xF0, # 6 | |
0xF0, 0x10, 0x20, 0x40, 0x40, # 7 | |
0xF0, 0x90, 0xF0, 0x90, 0xF0, # 8 | |
0xF0, 0x90, 0xF0, 0x10, 0xF0, # 9 | |
0xF0, 0x90, 0xF0, 0x90, 0x90, # A | |
0xE0, 0x90, 0xE0, 0x90, 0xE0, # B | |
0xF0, 0x80, 0x80, 0x80, 0xF0, # C | |
0xE0, 0x90, 0x90, 0x90, 0xE0, # D | |
0xF0, 0x80, 0xF0, 0x80, 0xF0, # E | |
0xF0, 0x80, 0xF0, 0x80, 0x80 # F | |
] | |
@opcode = 0 # Çalıştırılacak olan işlemin kodu | |
@index = 0 # Index register'ımız | |
@pc = 0x200 # Program sayacımız. ilk 512 baytı es geçiyoruz. | |
@should_draw = false # Görüntü çizilmesi için bir boolean değişkeni | |
@vx, @vy = 0, 0 # Register'ları tutan değişkenler | |
@delay_timer = 0 | |
@sound_timer = 0 | |
# Fontları belleğe yüklüyoruz | |
(0...80).each { |i| @memory[i] = @fonts[i] } | |
# Ve en baba kısım: Talimatları bir Hash'e koyarak | |
# haritalama yapıyoruz. | |
@instructions = { | |
0x0000 => :_0ZZZ, | |
0x00e0 => :_0ZZ0, | |
0x00ee => :_0ZZE, | |
0x1000 => :_1ZZZ, | |
0x2000 => :_2ZZZ, | |
0x3000 => :_3ZZZ, | |
0x4000 => :_4ZZZ, | |
0x5000 => :_5ZZZ, | |
0x6000 => :_6ZZZ, | |
0x7000 => :_7ZZZ, | |
0x8000 => :_8ZZZ, | |
0x8FF0 => :_8ZZ0, | |
0x8FF1 => :_8ZZ1, | |
0x8FF2 => :_8ZZ2, | |
0x8FF3 => :_8ZZ3, | |
0x8FF4 => :_8ZZ4, | |
0x8FF5 => :_8ZZ5, | |
0x8FF6 => :_8ZZ6, | |
0x8FF7 => :_8ZZ7, | |
0x8FFE => :_8ZZE, | |
0x9000 => :_9ZZZ, | |
0xA000 => :_AZZZ, | |
0xB000 => :_BZZZ, | |
0xC000 => :_CZZZ, | |
0xD000 => :_DZZZ, | |
0xE000 => :_EZZZ, | |
0xE00E => :_EZZE, | |
0xE001 => :_EZZ1, | |
0xF000 => :_FZZZ, | |
0xF007 => :_FZ07, | |
0xF00A => :_FZ0A, | |
0xF015 => :_FZ15, | |
0xF018 => :_FZ18, | |
0xF01E => :_FZ1E, | |
0xF029 => :_FZ29, | |
0xF033 => :_FZ33, | |
0xF055 => :_FZ55, | |
0xF065 => :_FZ65 | |
} | |
end | |
def load_rom(path) | |
rom_file = File.open(path, 'rb') { |i| i.read } | |
rom_file.split('').each_with_index do |byte, index| | |
@memory[index + 0x200] = byte.ord | |
end | |
end | |
def cycle | |
@opcode = (@memory[@pc] << 8) | @memory[@pc + 1] # İşlem kodunu alıyoruz | |
@pc += 2 # Program sayacını arttırıyoruz | |
@vx = (@opcode & 0x0F00) >> 8 # İşlem kodunu maskeleyip | |
@vy = (@opcode & 0x00F0) >> 4 # register'ları değişkenlere atıyoruz | |
ext_op = @opcode & 0xF000 # İşlem kodunu maskeleyip, talimatı alıyoruz | |
puts "Running instruction: #{@instructions[ext_op].to_s[1..-1]}" rescue nil | |
run_instruction ext_op # Ve talimatı çalıştırıyoruz | |
@delay_timer -= 1 if @delay_timer > 0 # Gecikme ve ses sayaçlarını | |
@sound_timer -= 1 if @sound_timer > 0 # azaltıyoruz | |
end | |
def test | |
while true | |
sleep 0.1 | |
cycle | |
end | |
end | |
private | |
def run_instruction(ext_op) | |
method(@instructions[ext_op]).call rescue puts "Unknown instruction: #{ext_op.to_s(16)}" | |
end | |
def get_key | |
(0...16).each { |i| return i if @key_inputs[i] == 1 } | |
return -1 | |
end | |
def _0ZZZ | |
ext_op = @opcode & 0xF0FF | |
run_instruction ext_op | |
end | |
def _0ZZ0 | |
@display_buffer = [0] * 64 * 32 | |
@should_draw = true | |
end | |
def _0ZZE | |
@pc = @stack.pop | |
end | |
def _1ZZZ | |
@pc = @opcode & 0x0FFF | |
end | |
def _2ZZZ | |
@stack.push @pc | |
@pc = @opcode & 0x0FFF | |
end | |
def _3ZZZ | |
@pc += 2 if @gpio[@vx] == (@opcode & 0xFF) | |
end | |
def _4ZZZ | |
@pc += 2 if @gpio[@vx] != (@opcode & 0xFF) | |
end | |
def _5ZZZ | |
@pc += 2 if @gpio[@vx] == @gpio[@vy] | |
end | |
def _6ZZZ | |
@gpio[@vx] = @opcode & 0xFF | |
end | |
def _7ZZZ | |
@gpio[@vx] += (@opcode % 0xFF) | |
end | |
def _8ZZZ | |
ext_op = @opcode & 0xF00F | |
ext_op += 0xFF0 | |
run_instruction ext_op | |
end | |
def _8ZZ0 | |
@gpio[@vx] = @gpio[@vy] & 0xFF | |
end | |
def _8ZZ1 | |
@gpio[@vx] |= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ2 | |
@gpio[@vx] &= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ3 | |
@gpio[@vx] ^= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ4 | |
@gpio[0xF] = @gpio[@vx] + @gpio[@vy] > 0xFF ? 1 : 0 | |
@gpio[@vx] += @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ5 | |
@gpio[0xF] = @gpio[@vx] < @gpio[@vy] ? 0 : 1 | |
@gpio[@vx] -= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ6 | |
@gpio[0xF] = @gpio[@vx] & 0x0001 | |
@gpio[@vx] >>= 1 | |
end | |
def _8ZZ7 | |
@gpio[0xF] = @gpio[@vx] > @gpio[@vy] ? 0 : 1 | |
@gpio[@vx] = @gpio[@vy] - @gpio[@vx] | |
end | |
def _8ZZE | |
@gpio[0xF] = (@gpio[@vx] & 0xF0) >> 7 | |
@gpio[@vx] <<= 1 | |
@gpio[@vx] &= 0xFF | |
end | |
def _9ZZZ | |
@pc += 2 if @gpio[@vx] != @gpio[@vy] | |
end | |
def _AZZZ | |
@index = @opcode & 0x0FFF | |
end | |
def _BZZZ | |
@pc = (@opcode & 0x0FFF) + @gpio[0] | |
end | |
def _CZZZ | |
r = (rand * 0xFF).to_i | |
@gpio[@vx] = r & (@opcode & 0xFF) | |
@gpio[@vx] &= 0xFF | |
end | |
def _DZZZ | |
@gpio[0xF] = 0 | |
x = @gpio[@vx] & 0xFF | |
y = @gpio[@vy] & 0xFF | |
height = @opcode & 0x000F | |
row = 0 | |
while row < height | |
current_row = @memory[row + @index] | |
pixel_offset = 0 | |
while pixel_offset < 8 | |
loc = x + pixel_offset + ((y + row) * 64) | |
pixel_offset += 1 | |
next if (y + row) >= 32 || (x + pixel_offset - 1) >= 64 | |
mask = 1 << 8 - pixel_offset | |
current_pixel = (current_row & mask) >> (8 - pixel_offset) | |
@display_buffer[loc] ^= current_pixel | |
@gpio[0xF] = @display_buffer[loc] == 0 ? 1 : 0 | |
end | |
row += 1 | |
end | |
@should_draw = true | |
end | |
def _EZZZ | |
ext_op = @opcode & 0xF00F | |
run_instruction ext_op | |
end | |
def _EZZE | |
key = @gpio[@vx] & 0xF | |
@pc += 2 if @key_inputs[key] == 1 | |
end | |
def _EZZ1 | |
key = @gpio[@vx] & 0xF | |
@pc += 2 if @key_inputs[key] == 1 | |
end | |
def _FZZZ | |
ext_op = @opcode & 0xF0FF | |
run_instruction ext_op | |
end | |
def _FZ07 | |
@gpio[@vx] = @delay_timer | |
end | |
def _FZ0A | |
ret = get_key | |
if ret >= 0 | |
@gpio[@vx] = ret | |
else | |
@pc -= 2 | |
end | |
end | |
def _FZ15 | |
@delay_timer = @gpio[@vx] | |
end | |
def _FZ18 | |
@sound_timer = @gpio[@vx] | |
end | |
def _FZ1E | |
@index += @gpio[@vx] | |
if @index > 0x0FFF | |
@gpio[0xF] = 1 | |
@index &= 0xFFF | |
else | |
@gpio[0xF] = 0 | |
end | |
end | |
def _FZ29 | |
@index = (5 * (@gpio[@vx])) & 0x0FFF | |
end | |
def _FZ33 | |
@memory[@index] = @gpio[@vx] / 100 | |
@memory[@index + 1] = (@gpio[@vx] % 100) / 10 | |
@memory[@index + 2] = @gpio[@vx] % 10 | |
end | |
def _FZ55 | |
(0..@vx).each { |i| @memory[@index + i] = @gpio[i] } | |
@index += @vx + 1 | |
end | |
def _FZ65 | |
(0..@vx).each { |i| @gpio[i] = @memory[@index + 1] } | |
@index += @vx + 1 | |
end | |
end | |
emulator = CHIP8.new | |
emulator.load_rom('PONG') | |
emulator.test |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment