-
-
Save bmerrill42/25c7a44cdb4f4ca1d8a2506c74cc7a24 to your computer and use it in GitHub Desktop.
chip8 emu
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
require 'curses' | |
require 'io/console' | |
class Chip | |
FONTSET = [ | |
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 | |
] | |
def initialize | |
# initialize registers and memory | |
@memory = Array.new(4096, 0) | |
@v = Array.new(16, 0) | |
@i = 0 | |
@pc = 0x200 | |
@gfx = Array.new(64*32, 0) | |
@delay_timer = 0 | |
@sound_timer = 0 | |
@opcode = 0 | |
@stack = Array.new(16, 0) | |
@sp = -1 | |
# @key = Array.new(16,0) | |
@memory[0..(FONTSET.length - 1)] = FONTSET[0..(FONTSET.length - 1)] | |
@window = Curses::Window.new(35, 64, 0, 0) | |
@drawflag = 0 | |
end | |
def loadgame(rom) | |
buffer = File.binread(rom) | |
buffer.each_byte.with_index do |val, index| | |
@memory[0x200 + index] = val.ord | |
end | |
end | |
def nread_key | |
key = (STDIN.read_nonblock(1) rescue nil) | |
case key | |
when "1" | |
return 1 | |
when "2" | |
return 2 | |
when "3" | |
return 3 | |
when "4" | |
return 0xC | |
when "q" | |
return 4 | |
when "w" | |
return 5 | |
when "e" | |
return 6 | |
when "r" | |
return 0xD | |
when "a" | |
return 7 | |
when "s" | |
return 8 | |
when "d" | |
return 9 | |
when "f" | |
return 0xE | |
when "z" | |
return 0xA | |
when "x" | |
return 0 | |
when "c" | |
return 0xB | |
when "v" | |
return 0xF | |
end | |
end | |
def read_key | |
key = STDIN.readchar | |
case key | |
when "1" | |
return 1 | |
when "2" | |
return 2 | |
when "3" | |
return 3 | |
when "4" | |
return 0xC | |
when "q" | |
return 4 | |
when "w" | |
return 5 | |
when "e" | |
return 6 | |
when "r" | |
return 0xD | |
when "a" | |
return 7 | |
when "s" | |
return 8 | |
when "d" | |
return 9 | |
when "f" | |
return 0xE | |
when "z" | |
return 0xA | |
when "x" | |
return 0 | |
when "c" | |
return 0xB | |
when "v" | |
return 0xF | |
end | |
end | |
def emulate_cycle | |
# fetch opcode | |
@opcode = (@memory[@pc].ord << 8) | @memory[@pc + 1].ord | |
# decode opcode | |
nnn = @opcode & 0x0FFF | |
kk = @opcode & 0x00FF | |
n = @opcode & 0x000F | |
x = (@opcode & 0x0F00)>> 8 | |
y = (@opcode & 0x00F0)>> 4 | |
case @opcode & 0xF000 | |
when 0x0000 | |
case @opcode & 0x000F | |
when 0x0000 | |
@gfx.clear | |
@gfx = Array.new(64*32, 0) | |
@pc += 2 | |
when 0x000E | |
@pc = @stack[@sp] | |
@sp -=1 | |
@pc += 2 | |
end | |
when 0x1000 | |
@pc = nnn | |
when 0x2000 | |
@sp += 1 | |
@stack[@sp] = @pc | |
@pc = nnn | |
when 0x3000 | |
if @v[x] == kk | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
when 0x4000 | |
if @v[x] != kk | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
when 0x5000 | |
if @v[x] == @v[y] | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
when 0x6000 | |
@v[x] = kk | |
@pc += 2 | |
when 0x7000 | |
@v[x] = @v[x] + kk | |
@v[x] &= 0xFF | |
@pc += 2 | |
when 0x8000 | |
case n | |
when 0x0000 | |
@v[x] = @v[y] | |
@pc += 2 | |
when 0x0001 | |
@v[x] |= @v[y] | |
@pc += 2 | |
when 0x0002 | |
@v[x] &= @v[y] | |
@pc += 2 | |
when 0x0003 | |
@v[x] ^= @v[y] | |
@pc += 2 | |
when 0x0004 | |
@v[x] += @v[y] | |
if @v[x] > 0xFF | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@v[x] &= 0xFF | |
@pc += 2 | |
when 0x0005 | |
if @v[x] >= @v[y] | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@v[x] -= @v[y] | |
@v[x] &= 0xFF | |
@pc += 2 | |
when 0x0006 | |
if @v[x] & 0x01 == 1 | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@v[x] = (@v[x] >> 1) & 0xFF | |
@pc += 2 | |
when 0x0007 | |
if @v[y] > @v[x] | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@v[x] = (@v[y] - @v[x]) | |
@v[x] &= 0xFF | |
@pc += 2 | |
when 0x000E | |
if ((@v[x] & 0x80) >> 7) == 1 | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@v[x] = (@v[x] << 1) & 0xFF | |
@pc += 2 | |
end | |
when 0x9000 | |
if @v[x] != @v[y] | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
when 0xA000 | |
@i = nnn | |
@pc += 2 | |
when 0xB000 | |
@pc = nnn + @v[0] | |
when 0xC000 | |
@v[x] = rand(0xFF) & kk | |
@pc += 2 | |
when 0xD000 | |
# sprite display | |
for yline in 0..(n - 1) | |
pixel = @memory[@i + yline] | |
for xline in 0..7 | |
if (pixel & (0x80 >> xline)) != 0 | |
if (@gfx[@v[x] + xline + ((@v[y] + yline) * 64)] == 1) | |
@v[0xF] = 1 | |
else | |
@v[0xF] = 0 | |
end | |
@gfx[@v[x] + xline + ((@v[y] + yline) * 64)] ^= 1 | |
end | |
end | |
end | |
@drawflag = 1 | |
@pc += 2 | |
when 0xE000 | |
case kk | |
when 0x009E | |
if nread_key == @v[x] | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
# self.debug | |
# sleep(1) | |
when 0x00A1 | |
if nread_key != @v[x] | |
@pc += 4 | |
else | |
@pc += 2 | |
end | |
# self.debug | |
# sleep(1) | |
end | |
when 0xF000 | |
case kk | |
when 0x07 | |
@v[x] = @delay_timer | |
@pc += 2 | |
when 0x0A | |
@v[x] = read_key | |
@pc += 2 | |
when 0x15 | |
@delay_timer = @v[x] | |
@pc += 2 | |
when 0x18 | |
@sound_timer = @v[x] | |
@pc += 2 | |
when 0x1E | |
@i = @i + @v[x] | |
@pc += 2 | |
when 0x29 | |
@i = @v[x] * 5 | |
@pc += 2 | |
when 0x33 | |
@memory[@i] = @v[x] / 100 | |
@memory[@i + 1] = (@v[x] / 10) % 10 | |
@memory[@i + 2] = (@v[x] % 100) % 10 | |
@pc += 2 | |
when 0x55 | |
for idx in 0..x | |
@memory[@i] = @v[idx] | |
@i += 1 | |
end | |
@pc += 2 | |
when 0x65 | |
for idx in 0..x | |
@v[idx] = @memory[@i] | |
@i += 1 | |
end | |
@pc += 2 | |
end | |
else | |
puts "unknown opcode #{@opcode.to_s(16)}" | |
@pc += 2 | |
end | |
# @window.getch | |
# STDIN.readchar | |
end | |
def tick_timers | |
if @delay_timer > 0 | |
@delay_timer -= 1 | |
end | |
if @sound_timer > 0 | |
@sound_timer -= 1 | |
end | |
end | |
def drawflag | |
return @drawflag | |
end | |
def draw_graphics | |
@window.clear | |
@gfx.each.with_index do |pix, index| | |
x = index % 64 | |
y = index / 64 #/ | |
if pix != 0 | |
@window.setpos(y, x) | |
@window << "X" | |
end | |
end | |
@window.setpos(0, 0) | |
@window.refresh | |
@drawflag = 0 | |
end | |
def debug | |
@window.setpos(32, 0) | |
@window.addstr(" ") | |
@window.setpos(33, 0) | |
@window.addstr(" ") | |
@window.setpos(34, 0) | |
@window.addstr(" ") | |
@window.setpos(32, 0) | |
@window.addstr("opcode: #{@opcode.to_s(16)} PC: #{@pc.to_s(16)} I: #{@i.to_s(16)} *I: #{@memory[@i].to_s(16)}") | |
@window.setpos(33, 0) | |
@window.addstr("reg: #{@v[0].to_s(16)} #{@v[1].to_s(16)} #{@v[2].to_s(16)} #{@v[3].to_s(16)} #{@v[4].to_s(16)} #{@v[5].to_s(16)} #{@v[6].to_s(16)} #{@v[7].to_s(16)} #{@v[8].to_s(16)} #{@v[9].to_s(16)} #{@v[10].to_s(16)} #{@v[11].to_s(16)} #{@v[12].to_s(16)} #{@v[13].to_s(16)} #{@v[14].to_s(16)} #{@v[0xF]}") | |
@window.setpos(34, 0) | |
@window.addstr("SP: #{@sp.to_s(16)} stack: #{@stack[0].to_s(16)} #{@stack[1].to_s(16)} #{@stack[2].to_s(16)} #{@stack[3].to_s(16)} #{@stack[4].to_s(16)} #{@stack[5].to_s(16)} #{@stack[6].to_s(16)} #{@stack[7].to_s(16)} #{@stack[8].to_s(16)} #{@stack[9].to_s(16)} #{@stack[10].to_s(16)} #{@stack[11].to_s(16)} #{@stack[12].to_s(16)} #{@stack[13].to_s(16)} #{@stack[14].to_s(16)} #{@stack[15].to_s(16)}") | |
@window.setpos(0, 0) | |
@window.refresh | |
end | |
end | |
begin | |
Curses.init_screen | |
Curses.noecho | |
chip8 = Chip.new | |
chip8.loadgame(ARGV[0]) | |
loop do | |
chip8.emulate_cycle | |
chip8.tick_timers | |
if chip8.drawflag != 0 | |
chip8.draw_graphics | |
end | |
chip8.debug | |
sleep(0.001) | |
end | |
ensure | |
Curses.close_screen | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment