#88 Chip-8

Hi,

Here is my solution to the current quiz. It has a few flaws, and it
uses a method call for each statement (which is probably quite slow).
The nice thing is that you can dump a program and view it in an
assembler-like way.
The implementation is quite simple, I don’t think i need to explain it
in detail. I hope i haven’t screwed up somewhere :slight_smile:

Tom

Tom R. [email protected] writes:

Hi,

Here is my solution to the current quiz. It has a few flaws, and it
uses a method call for each statement (which is probably quite slow).
The nice thing is that you can dump a program and view it in an
assembler-like way.
The implementation is quite simple, I don’t think i need to explain it
in detail. I hope i haven’t screwed up somewhere :slight_smile:

Well, I just read my code again, and noticed that I did screw up. An
instruction is
2 byte, so
def jmp addr
@pc=addr/4-1 #each instruction has 4 byte. -1, because @pc gets
incremented in main loop
end
should be
def jmp addr
@pc=addr/2-1 #each instruction has 2 byte. -1, because @pc gets
incremented in main loop
end

I hope next time I’ll notice such things earlier :slight_smile:

Tom

On Jul 30, 2006, at 12:17 PM, Tom R. wrote:

Hi,

Here is my solution to the current quiz. It has a few flaws, and it
uses a method call for each statement (which is probably quite slow).
The nice thing is that you can dump a program and view it in an
assembler-like way.
The implementation is quite simple, I don’t think i need to explain it
in detail. I hope i haven’t screwed up somewhere :slight_smile:

Sure looks good to me. Here’s one trivial suggestion though:

print “Program read: \n”
print “============= \n”
emu.dump_prog
print “\nRunning… \n\n”
emu.run
print “Done! Registers: \n”
print “================ \n”
emu.dump

puts() will add a newline character, if it’s not already at the end
of the String you pass to it, so you could lose a lot of \n’s above
if you like.

James Edward G. II

James Edward G. II [email protected] writes:

Sure looks good to me. Here’s one trivial suggestion though:

What bugs me a little bit is that the jmp-code isn’t quite right. If i
read the spec right, it would be possible to jump to any byte in the
source-file, even if it’s in the middle of an opcode. With my solution
this is not possible. But I realized that after I finished it, and I
didn’t want to rewrite it.

print “Program read: \n”
you like.
Thanks, and thanks that you manage this whole quiz-thing and do the
summaries and everything.

James Edward G. II

Tom

There have been much better solutions, but for the sake of
completeness, here’s what I came up with while considering the quiz.

James Edward G. II

#!/usr/local/bin/ruby -w

require “enumerator”
require “forwardable”

module Kernel
module_function

def NNN(digits)
Integer(“0x#{digits.map { |d| d.to_s(16) }.join}”)
end
alias_method :KK, :NNN
end

class Chip8
extend Forwardable

MAX_REGISTER = 0b1111_1111
DEFAULT_HANDLER = [ Array.new,
lambda { |em, op| raise “Unknown Op: #
{op.inspect}.”} ]

def self.handlers
@@handlers ||= Array.new
end

def self.handle(*pattern, &handler)
handlers << [pattern, handler]
end

handle(1) { |em, op| em.head = NNN(op[-3…-1]) }
handle(3) { |em, op| em.skip if em[op[1]] == KK(op[-2…-1]) }
handle(6) { |em, op| em[op[1]] = KK(op[-2…-1]) }
handle(7) { |em, op| em[op[1]] += KK(op[-2…-1]) }
handle(8, nil, nil, 0) { |em, op| em[op[1]] = em[op[2]] }
handle(8, nil, nil, 1) { |em, op| em[op[1]] |= em[op[2]] }
handle(8, nil, nil, 2) { |em, op| em[op[1]] &= em[op[2]] }
handle(8, nil, nil, 3) { |em, op| em[op[1]] ^= em[op[2]] }
handle(8, nil, nil, 4) do |em, op|
em[op[1]] += em[op[2]]
em[op[1]], em[15] = em[op[1]] - MAX_REGISTER, 1 if em[op[1]] >
MAX_REGISTER
end
handle(8, nil, nil, 5) do |em, op|
em[op[1]] -= em[op[2]]
em[op[1]], em[15] = MAX_REGISTER + 1 + em[op[1]], 1 if em[op[1]]
< 0
end
handle(8, nil, nil, 6) do |em, op|
em[15], em[op[1]] = em[op[1]][0], em[op[1]] >> 1
end
handle(8, nil, nil, 7) do |em, op|
em[op[1]] = em[op[2]] - em[op[1]]
em[op[1]], em[15] = MAX_REGISTER + 1 + em[op[1]], 1 if em[op[1]]
< 0
end
handle(8, nil, 0, 14) do |em, op|
em[15], em[op[1]] = em[op[1]][7], em[op[1]] << 1
end
handle(12) { |em, op| em[op[1]] = rand(MAX_REGISTER) & KK(op
[-2…-1]) }
handle(0, 0, 0, 0) { exit }

def initialize(file)
@addresses = File.read(file).scan(/…/m)
@registers = Hash.new

 @head = 0

end

attr_accessor :head

def_delegators :@registers, :[], :[]=

def run
while op_code = read_one_op_code
find_handler(op_code).call(self, op_code)
trim_registers
end
end

def read_one_op_code
if @head >= @addresses.size
nil
else
@head += 1
@addresses[@head - 1].unpack(“HXhHXh”).map { |nib| Integer(“0x#
{nib}”) }
end
end
alias_method :skip, :read_one_op_code

def to_s
@registers.inject(String.new) do |output, (key, value)|
output + “V#{key.to_s(16).upcase}:%08b\n” % value
end
end

private

def find_handler(op_code)
(self.class.handlers + [DEFAULT_HANDLER]).find do |pattern,
handler|
pattern.enum_for(:each_with_index).all? do |match, index|
match.nil? or match == op_code[index]
end
end.last
end

def trim_registers
@registers.each { |name, bits| @registers[name] = MAX_REGISTER &
bits }
end
end

if FILE == $PROGRAM_NAME
emulator = Chip8.new(“Chip8Test”)
at_exit { puts emulator }
emulator.run
end