Skip to content

Instantly share code, notes, and snippets.

@bmerrill42
Created March 6, 2018 00:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmerrill42/25c7a44cdb4f4ca1d8a2506c74cc7a24 to your computer and use it in GitHub Desktop.
Save bmerrill42/25c7a44cdb4f4ca1d8a2506c74cc7a24 to your computer and use it in GitHub Desktop.
chip8 emu
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