Chip-8 (#88)

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz
    until
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Ethan P.

CHIP-8 was an interpreted language used in the 1970’s for basic games
like Pong.
While it is technically an interpreted language, it defines many
components a
real computer has, such as registers and a processor. Today, like many
other
gaming consoles, it is kept alive by emulators allowing it to be played
on most
any modern computer. Emulation is where raw operation codes (the most
low-level
way of feeding instructions to a computer) are translated by another
computer,
giving a totally different system the ability to run programs not
designed for
it.

Your job is to make an emulator. It will only cover some basic parts of
it, but
all emulators have to start somewhere. I have written a simple program,
and will
give you a list of all the opcodes I used and what they are supposed to
do. We
won’t worry about graphics right now, just simple operations. Four
things need
to be done:

1. Make the emulator read the file. All opcodes are four digit hex 

numbers,
but those will need to be split.
2. Set up the registers and other assorted things so they can be
modified.
3. Interpret the function of the opcode in Ruby.
4. Dump all the registers so you can check and make sure everything
went well.

Like I said, all opcodes are four digit long hex numbers. So you read
four hex
digits worth of data, process it, then move on to the next four digits.
There
are 16 registers (areas where data can be stored). They are named V0
through VF,
and are all eight bits wide. VF is usually used as a carry for math
operations.

Here is the list of opcodes you will need to interpret:

NNN is an address
KK is an 8 bit constant
X and Y are two 4 bits constants

0000 This is nothing. If you see this,  it should mean that you are at 

the end
of the file and have nothing left to read, so you therefore need
to exit
gracefully.

1NNN Jump to the address NNN of the file

3XKK Skip next instruction if VX == KK

6XKK VX = KK

7XKK VX = VX + KK (Note: The documentation does not mention this, but I 

would
assume that VF would act as a carry if needed.
The
included program will not need a carry for
this
function, so providing for one is purely
optional.)

8XY0 VX = VY

8XY1 VX = VX OR VY

8XY2 VX = VX AND VY

8XY3 VX = VX XOR VY

8XY4 VX = VX + VY, Sets VF to 1 if there is a carry, 0 if there isn't

8XY5 VX = VX - VY, VF is set to 0 if there is a borrow, 1 if there 

isn’t.

8X06 VX = VX SHIFT RIGHT 1 (VX=VX/2), VF is set to the value of the 

least
significant bit of VX before the
shift.

8XY7 VX = VY - VX, VF is set to 0 if there is a borrow, 1 if there 

isn’t.

8X0E VX = VX SHIFT LEFT 1 (VX=VX*2), VF is set to the value of the most
                                     significant bit of VX before the 

shift.

CXKK VX = Random number AND KK

Lets explain how to read these opcodes. Take opcode 1NNN for example.
Since the
1NNN starts with 1, we know that no matter what comes after it we will
be doing
a jump instruction. Same with the other codes. For the opcode 8XY2, we
know if
an opcode starts with 8 and ends in 2 it will be the operation “VX = VX
AND VY”.

The variables NNN, KK, X and Y are a little different. They can
represent any
hex number (0-F), and also are used to note how big of chunks to read
them in.
For NNN, you will want to read those as a three digit hex number. Lets
do an
example opcode (in hex): 1234. Since it starts with 1, we know it is a
“Jump to
address NNN” command. The three numbers after that (the NNN part), are
234, so
we know to jump to location 234 in the file (this is a hex number, not
decimal).
KK and X/Y are similar, just for different sized chunks.

While I’m on the subject, let’s talk about addresses in detail. Your
current
address in the file is how many bytes you are from the beginning of the
file. So
if I am at address 008 in the file, I am eight bytes away from the
beginning of
the file, and therefore the next byte I read will be byte nine. Say we
had the
opcode 100F, that means we would jump to after byte 16 (it is a hex
remember).

If your wondering how the carries work, here are some examples. First
off,
addition. Say we add these two numbers (opcode 8xy4):

11111111 <--This is VX
+
00000001 <--This is VY

Obviously this would equal 100000000, but that is too big for one
register. So
what we do is make VF equal 00000001, and then roll over to 00000000.
Sort of
like an old car odometer. When it hits 99,999 miles, it goes back to
00,000
miles, except we are keeping track of the fact that it rolled over.
Subtraction
is similar (opcode 8xy5):

00000000 <-- This is VX
-
00000001 <-- This is VY

Since the registers can only deal with positive numbers, we have to keep
it
positive. So what we do is “borrow”, making VX equal to 100000000. All
of a
sudden our subtraction works:

100000000 <--VX
-
00000001 <--VY
=
11111111

If we do this though, we have to make sure VF is zero to indicate that a
borrow
was needed. If a borrow was not needed, VF gets set to 00000001, since
you
didn’t need the extra.

Now for shifts. To sum it up, if you shift left, the far left number
gets taken
away, and a zero added to the far right. So 10101111 would become
01011110,
because we got rid of the far left number and added a zero to the far
right.
Shift right is the same except going the other way (get rid of far right
number,
add 0 to the left). As you can see, a number disappears when you shift.
VF is
set to the number that disappears (obviously it can only be a 1 or 0).

A full list of opcodes can be had at David W.'s page, under section
1.5:

http://www.pdc.kth.se/%7Elfo/chip8/CHIP8.htm

He also has a full explanation of everything, so if check there for more
details. If you want to write code for an opcode that I didn’t list, by
all
means go for it.

Be sure to start reading the attached program from the beginning. To my
understanding in a real Chip-8 game up to address 200 was reserved for
machine
code which we wont worry about.

The register values you should get are:

V1:01000101
V2:10111011
V3:11101100
V4:this number should be random, so do multiple runs to make sure it 

changes
VF:00000000

A note: If everything is working as it is supposed to, you should run
straight
through the file once. If you are looping, I would start with double
checking
all your math operations.

Good Luck!

On Jul 28, 2006, at 12:50 PM, Ruby Q. wrote:

by Ethan P.

CHIP-8 was an interpreted language used in the 1970’s for basic
games like Pong.

I know this quiz is a bit longer (to read) than we normally prefer,
it is not hard however and I encourage people to try it. I solved it
myself in just over 100 lines of code.

The length mainly comes from me pestering Ethan to clarify details,
which he did a great job of.

James Edward G. II

On Jul 28, 2006, at 12:50 PM, Ruby Q. wrote:

I have written a simple program

It was just pointed out to me that I didn’t attach the program. You
can find it at:

http://www.rubyquiz.com/Chip8Test

James Edward G. II

Ruby Q. [email protected] writes:

  1. Make the emulator read the file. All opcodes are four digit hex numbers,
    but those will need to be split.

What’s not specified here is that the opcodes are written to the file
as big-endian 16-bit numbers. That is, most significant digit first.
The file represents a memory dump of a simple chip-8 processor that
begins at memory address 0000.

  1. Set up the registers and other assorted things so they can be modified.

I’ll note that the spec. nowhere tells us what the initial, power-on
values of the registers are. My personal recommendation is that
people intialize the registers with random values, so that bad chip-8
programs would be caught. As an addition, it might be nice to accept
command line arguments to initialize the registers.

  1. Interpret the function of the opcode in Ruby.
  2. Dump all the registers so you can check and make sure everything went well.

I’d really like some more I/O than this. I’m tempted to add an
extension that does memory mapped IO, or, looking at the chip-8 page,
implement the graphics through ASCII-graphics.

While I’m on the subject, let’s talk about addresses in detail. Your
current address in the file is how many bytes you are from the
beginning of the file. So if I am at address 008 in the file, I am
eight bytes away from the beginning of the file, and therefore the
next byte I read will be byte nine. Say we had the opcode 100F, that
means we would jump to after byte 16 (it is a hex remember).

I’m not sure you mean this. I think you mean either “jump to before
the 16th byte” or meant to use opcode 1010.

Looking at your program, I see an easy way to make this explanation
easier:

Opcode 0100 will send you back to the beginning, so that the next
instruction read and executed is the first instruction of the file.
Opcode 0102 will send you back to the second instruction in the file
(remember that instructions are four hex digits - or two bytes -
long). Other addresses follow.

Be sure to start reading the attached program from the beginning. To
my understanding in a real Chip-8 game up to address 200 was
reserved for machine code which we wont worry about.

In case people are looking at this in an environment where they can’t
get online and grab the program, here’s a short ruby program that
recreates the file:

File.open(“Chip8Test”,“wb”) { |f|
x = “x”
%w{ 61 77 62 45 71 01 83 20 81 21 81 22 82 33 81 34
82 35 81 06 83 27 83 0e 64 ff c4 11 32 bb 10 00
00 00 }.each {|b| x[0] = b.hex; f.write(x)}
}

On 7/28/06, Daniel M. [email protected] wrote:

Looking at your program, I see an easy way to make this explanation
easier:

Opcode 0100 will send you back to the beginning, so that the next
instruction read and executed is the first instruction of the file.
Opcode 0102 will send you back to the second instruction in the file
(remember that instructions are four hex digits - or two bytes -
long). Other addresses follow.

I think you mean 1000 and 1002 in the description above. :slight_smile:

So, should an absolute jump to N make it so that the Nth (0-based
index) instruction is the next executed, or the (N+1)th? You’re
example above seems to put it both ways. 1000 sends you back to the
beginning, so the “0th” instruction (first instruction in the file) is
the next executed. But then 1002 should make it so that the “2nd”
instruction (third instruction in the file) is the next executed,
right? Maybe this is what you meant by “second” anyways…

Jacob F.

On 7/28/06, Jacob F. [email protected] wrote:

Opcode 0102 will send you back to the second instruction in the file
instruction (third instruction in the file) is the next executed,
right? Maybe this is what you meant by “second” anyways…

Jacob F.

Here is how it is supposed to work. Say we have a file that reads in hex:
1234ABCD5678
Opcode 1000 would send us to before 1234, so after jumping the first
instruction we would read is 1234. Opcode 1002 would send us to after
1234
and before ABCD (were moving forward 2 bytes, which equals 4 hex digits
worth of data). 1004 would send us to after ABCD and before 5678, and so
on.

From Daniel’s message
“I’ll note that the spec. nowhere tells us what the initial, power-on
values of the registers are. My personal recommendation is that
people intialize the registers with random values, so that bad chip-8
programs would be caught.”
Initial power on values for all registers should be zero. Initializing
with
random numbers sounds like a good idea, but I imagine it might cause the
Chip-8 program to output different numbers, so I would cautious in doing
so.

Hope that helps,
Ethan P.

On Jul 28, 2006, at 3:15 PM, Daniel M. wrote:

  1. Set up the registers and other assorted things so they can be
    modified.

I’ll note that the spec. nowhere tells us what the initial, power-on
values of the registers are. My personal recommendation is that
people intialize the registers with random values, so that bad chip-8
programs would be caught. As an addition, it might be nice to accept
command line arguments to initialize the registers.

I would think nils would be more likely to blow up with a problem in
the later math operations.

James Edward G. II

On 7/28/06, Jacob F. [email protected] wrote:

long). Other addresses follow.
hex:
decompile all the instructions before beginning execution. This makes
sense now. So, regarding the initial quiz description, 100F would send
us F (15) bytes forward from the start of the file, or only 7.5
instructions, and you’d have your instruction pointer in the middle of
an instruction. Is that valid? Or was it indeed a typo as Daniel
suggested?

Jacob F.

Well it would be vaild, but indeed you would end up right in the middle
of
an opcode (probably not where one wants to be), so it was a typo on my
part.
100F would send you 15 bytes forward, not 16. So it probably should have
read something to the effect of “Say we had the opcode 100F, that means
we
would jump to after byte 15, and the next instruction read would be
bytes 16
and 17”. My apologies for that.

Ethan P.

On 7/28/06, Ethan P. [email protected] wrote:

example above seems to put it both ways. 1000 sends you back to the
worth of data). 1004 would send us to after ABCD and before 5678, and so on.
Ah, gotcha. I was thinking in instruction addressing, rather than
memory addressing. That probably reflects the fact that I read and
decompile all the instructions before beginning execution. This makes
sense now. So, regarding the initial quiz description, 100F would send
us F (15) bytes forward from the start of the file, or only 7.5
instructions, and you’d have your instruction pointer in the middle of
an instruction. Is that valid? Or was it indeed a typo as Daniel
suggested?

Jacob F.

Hi,

This one was a lot of fun :slight_smile:
Not that the others weren’t but I must admit I forgot
how much I liked assembler… Thanks for reminding me :slight_smile:

Now, once the emulator was done, wasn’t too much fun to
watch the same program over and over, so I wrote a small
‘assembler’ to be able to create programs as well :slight_smile:

I rewrote the Chip8Test file as well in this pseudo
assembler, then ‘compiled’ it back, and the run it.
I also added a few more opcodes, almost all except those
used for the actual graphical part.

The files are:
chip8.rb - the emulator
cc8.rb - the ‘assembler’
Chip8Test.c8 - the original test program, in ‘source’ :slight_smile:
AnotherTest.c8 - another test program
cc8-quickref.txt - a quick reference to the ‘assembly’ lang
used in the .c8 tests files.

The way it works is:
ruby cc8.rb Chip8Test.c8 will create a Chip8Test.bin file
which can then be run with chip8.rb

Great quiz!!

Have a nice day everyone,
Alex

Hi,

here is my solution:

I initialize the registers with zeroes, because it simplifies unit-
testing.

A binary program is unpacked into an array of opcodes (16bit). The
program counter (named “current”) counts opcodes, not bytes, so
for a jump instruction the destination address has to be divided by two.

I also wrote a small “disassembler” which dumped the test program as:

000: [6177] V1 = 0x77
002: [6245] V2 = 0x45
004: [7101] V1 = V1 + 0x01
006: [8320] V3 = V2
008: [8121] V1 = V1 | V2
00A: [8122] V1 = V1 & V2
00C: [8233] V2 = V2 ^ V3
00E: [8134] V1 = V1 + V3
010: [8235] V2 = V2 - V3
012: [8106] V1 = V1 >> 1
014: [8327] V3 = V2 - V3
016: [830E] V3 = V3 << 1
018: [64FF] V4 = 0xFF
01A: [C411] V4 = rand() & 0x11
01C: [32BB] skip next if V2 == 0xBB
01E: [1000] goto 000
020: [0000] exit

(Almost looks like ruby code. We really need a “goto” in ruby :wink:

Regards,
Boris

chip8_emu.rb

class Chip8Emulator
attr_accessor :register

VF = 15 # index of the carry/borrow register

def initialize
@register = [0] * 16 # V0…VF
end

def exec(program)
opcodes = program.unpack(‘n*’)
current = 0 # current opcode index
loop do
opcode = opcodes[current]

   # these are needed often:
   x   = opcode >> 8 & 0x0F
   y   = opcode >> 4 & 0x0F
   kk  = opcode & 0xFF
   nnn = opcode & 0x0FFF

   case opcode >> 12 # first nibble
   when 0 then return
   when 1 then current = (nnn) / 2 and next
   when 3 then current += 1 if @register[x] == kk
   when 6 then @register[x] = kk
   when 7 then add(x, kk)
   when 8
     case opcode & 0x0F # last nibble
     when 0 then @register[x]  = @register[y]
     when 1 then @register[x] |= @register[y]
     when 2 then @register[x] &= @register[y]
     when 3 then @register[x] ^= @register[y]
     when 4 then add(x, @register[y])
     when 5 then subtract(x, x, y)
     when 6 then shift_right(x)
     when 7 then subtract(x, y, x)
     when 0xE then shift_left(x)
     else raise "Unknown opcode: " + opcode.to_s(16)
     end
   when 0xC then random(x, kk)
   else raise "Unknown opcode: " + opcode.to_s(16)
   end
   current += 1 # next opcode
 end

end

def add(reg, value)
result = @register[reg] + value
@register[reg] = result & 0xFF
@register[VF] = result >> 8 # carry
end

def subtract(reg, a, b)
result = @register[a] - @register[b]
@register[reg] = result & 0xFF
@register[VF] = - (result >> 8) # borrow
end

def shift_right(reg)
@register[VF] = @register[reg] & 0x01
@register[reg] >>= 1
end

def shift_left(reg)
@register[VF] = @register[reg] >> 7
@register[reg] = (@register[reg] << 1) & 0xFF
end

def random(reg, kk)
@register[reg] = rand(256) & kk
end

show all registers

def dump
0.upto(VF) do |reg|
printf(“V%1X:%08b\n”, reg, @register[reg])
end
end
end

if $0 == FILE
ARGV.each do |program|
emu = Chip8Emulator.new
emu.exec(File.read(program))
emu.dump
end
end

test_chip8_emu.rb

require ‘test/unit’
require ‘chip8_emu’

class Chip8EmulatorTest < Test::Unit::TestCase
def setup
@emu = Chip8Emulator.new
end

def test_init
assert_equal [0] * 16, @emu.register
end

def test_set_register
@emu.exec("\x60\x42" + “\x63\xFF” + “\x6F\x66” + “\0\0”)
assert_equal [66, 0, 0, 255] + [0]*11 + [102], @emu.register
end

def test_jump
@emu.exec("\x10\x04" + “\x00\x00” + “\x60\x42” + “\0\0”)
assert_equal [66] + [0]*15, @emu.register
end

def test_skip_next
@emu.exec("\x60\x42" + “\x30\x42” + “\x60\x43” + “\0\0”)
assert_equal [66] + [0]*15, @emu.register
end

def test_add_const
@emu.exec("\x60\xFF" + “\x70\x01” + “\0\0”)
assert_equal [0]*15 + [1], @emu.register
end

def test_copy
@emu.exec("\x60\x42" + “\x81\x00” + “\0\0”)
assert_equal [66]*2 + [0]*14, @emu.register
end

def test_or
@emu.exec("\x60\x03" + “\x61\x05” + “\x80\x11” + “\0\0”)
assert_equal [7, 5] + [0]*14, @emu.register
end

def test_and
@emu.exec("\x60\x03" + “\x61\x05” + “\x80\x12” + “\0\0”)
assert_equal [1, 5] + [0]*14, @emu.register
end

def test_xor
@emu.exec("\x60\x03" + “\x61\x05” + “\x80\x13” + “\0\0”)
assert_equal [6, 5] + [0]*14, @emu.register
end

def test_add
@emu.exec("\x60\x01" + “\x61\x01” + “\x80\x14” + “\0\0”)
assert_equal [2, 1] + [0]*14, @emu.register
end

def test_subtract
@emu.exec("\x60\x00" + “\x61\x01” + “\x80\x15” + “\0\0”)
assert_equal [255, 1] + [0]*13 + [1], @emu.register
end

def test_subtract2
@emu.exec("\x60\x01" + “\x61\x02” + “\x80\x17” + “\0\0”)
assert_equal [1, 2] + [0]*14, @emu.register
end

def test_shift_right
@emu.exec("\x60\xFF" + “\x80\x06” + “\0\0”)
assert_equal [0x7F] + [0]*14 + [1], @emu.register
end

def test_shift_left
@emu.exec("\x60\xFF" + “\x80\x0E” + “\0\0”)
assert_equal [0xFE] + [0]*14 + [1], @emu.register
end

def test_rand
srand 0
first_rand = rand(256)
srand 0
@emu.exec("\xC0\x0F" + “\0\0”)
assert_equal [first_rand & 0x0F] + [0]*15, @emu.register
end
end

chip8_asm.rb

class Chip8Disassembler
CODES = {
/0000/ => ‘exit’,
/1(…)/ => ‘goto \1’,
/3(.)(…)/ => ‘skip next if V\1 == 0x\2’,
/6(.)(…)/ => ‘V\1 = 0x\2’,
/7(.)(…)/ => ‘V\1 = V\1 + 0x\2’,
/8(.)(.)0/ => ‘V\1 = V\2’,
/8(.)(.)1/ => ‘V\1 = V\1 | V\2’,
/8(.)(.)2/ => ‘V\1 = V\1 & V\2’,
/8(.)(.)3/ => ‘V\1 = V\1 ^ V\2’,
/8(.)(.)4/ => ‘V\1 = V\1 + V\2’,
/8(.)(.)5/ => ‘V\1 = V\1 - V\2’,
/8(.)06/ => ‘V\1 = V\1 >> 1’,
/8(.)(.)7/ => ‘V\1 = V\2 - V\1’,
/8(.)0E/ => ‘V\1 = V\1 << 1’,
/C(.)(…)/ => ‘V\1 = rand() & 0x\2’,
}

def self.code2text hexcode
CODES.each do |re, subs|
if hexcode =~ re
return hexcode.sub(re, subs)
end
end
‘???’
end

def self.dump binary
opcodes = binary.unpack “n*”
opcodes.each_with_index do |code, waddr|
code_hex = “%04X” % code
printf("%03X: [%s] %s\n", waddr*2, code_hex, code2text
(code_hex));
end
end
end

binary = File.read(ARGV[0])
Chip8Disassembler.dump(binary)

require ‘logger’

class OpcodeGenerator
include Enumerable

OPCODE_SIZE = 2

def initialize(source_file)
@source_file = source_file
end

def each
while true
text = @source_file.read(OPCODE_SIZE)
return if text.nil?
yield text.unpack(‘S’)[0]
end
end

def seek(address)
@source_file.seek address
end

def skip_next
@source_file.seek(OPCODE_SIZE, IO::SEEK_CUR)
end

end

class Chip_8_Emulator

REGISTER_COUNT = 0x16,
REGISTER_PATTERN = /V([0-9A-F])/
REGISTER_MASK = 0xFF
CARRY_MASK = 0x100
LEFT_SHIFT_MASK = 0x80
RIGHT_SHIFT_MASK = 0x1
CARRY_REGISTER = 0xF

def initialize
@register = Array.new(REGISTER_COUNT)
end

def run(opcode_generator)
@register.fill 0
opcode_generator.each do |opcode|
case opcode & 0xF000
when 0x0000 # exit gracefully
$LOG.debug sprintf(“0x%04X Exit”, opcode)
break
when 0x1000 # Jump to NNNN
address = address_from_opcode opcode
$LOG.debug sprintf(“0x%04X Jump to 0x%04X”, opcode, address)
opcode_generator.seek address
when 0x3000 # Skip next instruction if VX == KK
x, k = register_and_constant_from_opcode opcode
$LOG.debug sprintf(“0x%04X Skip if V%X == 0x%04X”, opcode, x,
k)
if @register[x] == k then
opcode_generator.skip_next
end
when 0x6000 # VX = KK
x, k = register_and_constant_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = 0x%04X”, opcode, x, k)
@register[x] = k
when 0x7000 # VX = VX + KK
x, k = register_and_constant_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X + 0x%04X”, opcode, x, x,
k)
@register[x] = short_int_add(@register[x], k)
when 0x8000 # register operations
case opcode & 0x000F
when 0x0 # VX = VY
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X”, opcode, x, y)
@register[x] = @register[y]
when 0x1 # VX = VX OR VY
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X OR V%X”, opcode, x, x,
y)
@register[x] |= @register[y]
when 0x2 # VX = VX AND VY
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X AND V%X”, opcode, x, x,
y)
@register[x] &= @register[y]
when 0x3 # VX = VX AND VY
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X XOR V%X”, opcode, x, x,
y)
@register[x] ^= @register[y]
when 0x4 # VX = VX + VY
x, y = register_pair_from_opcode opcode
@register[x] = short_int_add(@register[x], @register[y])
when 0x5 # VX = VX - VY
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X - V%X”, opcode, x, x, y)
@register[x] = short_int_subtract(@register[x], @register[y])
when 0x6 # VX = VX shift right 1
x = register_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X shift right 1”, opcode,
x, x)
@register[x] = short_int_shift_right(@register[x])
when 0x7 # VX = VY - VX
x, y = register_pair_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X - V%X”, opcode, x, y, x)
@register[x] = short_int_subtract(@register[y], @register[x])
when 0xE # VX = VX shift left 1
x = register_from_opcode opcode
$LOG.debug sprintf(“0x%04X V%X = V%X shift left 1”, opcode,
x, x)
@register[x] = short_int_shift_left(@register[x])
else
raise RuntimeError, “Unknown register opcode
0x#{opcode.to_s(16)}”
end
register_index = (opcode & 0x0F00) >> 8
value = opcode & 0x00FF
$LOG.debug sprintf(“0x%04X V%X = V%X + 0x%04X”, opcode,
register_index, register_index, value)
when 0xC000 # VX = Random number AND KK
x, k = register_and_constant_from_opcode opcode
r = rand(0xFF)
$LOG.debug sprintf(“0x%04X V%X = random number 0x%04X AND
0x%04X”, opcode, x, r, k)
@register[x] = r & k
else
raise RuntimeError, “Unknown opcode 0x#{opcode.to_s(16)}”
end
end
end

def address_from_opcode(opcode)
opcode & 0x0FFF
end

def register_from_opcode(opcode)
(opcode & 0x0F00) >> 8
end

def register_and_constant_from_opcode(opcode)
x = (opcode & 0x0F00) >> 8
k = opcode & 0x00FF

[x, k]

end

def register_pair_from_opcode(opcode)
x = (opcode & 0x0F00) >> 8
y = (opcode & 0x00F0) >> 4

[x, y]

end

def short_int_add(a, b)
sum = a + b
@register[CARRY_REGISTER] = (sum & CARRY_MASK) >> 8
sum & REGISTER_MASK
end

def short_int_subtract(a, b)
difference = (a | CARRY_MASK) - b
@register[CARRY_REGISTER] = (difference & CARRY_MASK) >> 8
difference & REGISTER_MASK
end

def short_int_shift_left(a)
@register[CARRY_REGISTER] = a & LEFT_SHIFT_MASK
(a << 1) & REGISTER_MASK
end

def short_int_shift_right(a)
@register[CARRY_REGISTER] = a & RIGHT_SHIFT_MASK
a >> 1
end

def method_missing(method_id)
match_object = REGISTER_PATTERN.match(method_id.id2name)
if match_object.nil? : raise NoMethodError, method_id.inspect end

@register[match_object[1].hex]

end

end

if FILE == $0

$LOG = Logger.new STDOUT
$LOG.info “program starts”

require ‘test/unit’
require ‘stringio’

class TestOpcodeGenerator < Test::Unit::TestCase

TEST_DATA = [0x0001, 0x0002, 0x0003]

def test_generator
  opcodes = OpcodeGenerator.new StringIO.new(TEST_DATA.pack("S*"))
  opcodes.zip(TEST_DATA) {|opcode, test_element| assert_equal

test_element, opcode}
end

def test_seek
  opcodes = OpcodeGenerator.new StringIO.new(TEST_DATA.pack("S*"))
  opcodes.seek 2
  opcodes.zip(TEST_DATA[1,TEST_DATA.length-1]) {|opcode,

test_element| assert_equal test_element, opcode}
end

def test_skip_next
  opcodes = OpcodeGenerator.new StringIO.new(TEST_DATA.pack("S*"))
  opcodes.seek 2
  opcodes.skip_next
  opcodes.zip(TEST_DATA[2,TEST_DATA.length-2]) {|opcode,

test_element| assert_equal test_element, opcode}
end

end

class TestChip_8 < Test::Unit::TestCase

# dump of file Chip8Test.html
TEST_DATA =

[0x6177,0x6245,0x7101,0x8320,0x8121,0x8122,0x8233,0x8134,0x8235,0x8106,0x8327,0x830E,0x64FF,0xC411,0x32BB,0x1000,0x0000,].pack(‘S*’)

# V1:01000101
# V2:10111011
# V3:11101100
# V4:this number should be random, so do multiple runs to make sure

it changes
# VF:00000000
def test_emulator

  emulator = Chip_8_Emulator.new

  emulator.run OpcodeGenerator.new(StringIO.new(TEST_DATA))
  assert_equal 0b01000101, emulator.V1
  assert_equal 0b10111011, emulator.V2
  assert_equal 0b11101100, emulator.V3
  first_v4 = emulator.V4
  assert_equal 0b00000000, emulator.VF

  emulator.run OpcodeGenerator.new(StringIO.new(TEST_DATA))
  assert_equal 0b01000101, emulator.V1
  assert_equal 0b10111011, emulator.V2
  assert_equal 0b11101100, emulator.V3

note that this test fails sometimes because the domain isn’t very big

assert_not_equal first_v4, emulator.V4

  assert_equal 0b00000000, emulator.VF

end

end

end

I implemented pretty much all the opcodes and used Tk to display the
graphics as curses didn’t work on my machine.
It can run the games from the site James posted
(http://www.pdc.kth.se/~lfo/chip8/CHIP8/GAMES/).
I used one Win32 function (GetKeyState) as I didn’t know a compatible
way to check if a key is pressed.

Controls: q for quit, normal controls mapped just like on the site
with the explanation of the opcodes.

I also put the code on Pastie, as Gmail will probably mess up my code
(again): http://pastie.caboo.se/6634

require ‘enumerator’
require ‘tk’

require ‘Win32API’
keystate = Win32API.new(“user32”, “GetKeyState”, ‘I’, ‘I’)

virtual keycodes, mapping keys like

http://www.pdc.kth.se/~lfo/chip8/CHIP8.htm does
VK = {‘q’ => 81, 0 => 110, 1 => 103, 2 => 104, 3 => 105, 4 => 100, 5
=> 101, 6 => 102, 7 => 97, 8 => 98, 9 => 99, 0xA => 96, 0xB => 13, 0xC
=> 111, 0xD => 106, 0xE => 109, 0xF => 107}

$key_pressed = proc{|key|
keystate.call(VK[key]) & ~1 != 0 # higher bit set = key currently
pressed
}

class Integer
def lo; self & 0xF end # low nibble of byte
def hi; (self >> 4).lo end # high nibble of byte
def bcd_byte
[self / 100, (self/10) % 10, self % 10].map{|n| n.chr}.join
end
end

class Chip8Emulator
COLORS = [’#000000’,’#ffffff’]
FONT_TO_BIN = {’ ’ => 0,’#’ => 1}
FONT = ["#### # #### #### # # #### #### #### #### #### ## ###

### #### #### ",

      "#  #  ##     #    # #  # #    #       # #  # #  # #  # #  #

# # # # ",

      "#  #   #  ####  ### #### #### ####   #  #### #### #### ###

# # ### ### ",

      "#  #   #  #       #    #    # #  #  #   #  #    # #  # #  #

# # # # ",

      "####  ### #### ####    # #### ####  #   #### #### #  # ###

### #### # "

     ].map{|l| l.split('').enum_slice(5).to_a 

}.transpose.map{|lines|
lines.map{|line| (line.map{|c| FONT_TO_BIN[c] }.join +
‘000’).to_i(2).chr }.join
}.join # FONT is now the encoded font: 5 bytes for 0, 5
bytes for 1, etc total 80 bytes

def initialize(code)
@ip = 0x200
@mem = FONT + “\0” * (@ip - 80) + code + “\0” * 4096 # ensure
4kb mem + program at start
@regs = “\0” * 160
@I = 0
@stack = []
init_screen
end

def +(a,b)
a += b
@regs[0xF] = a > 0xFF ? 1 : 0
a & 0xFF
end

def -(a,b)
a -= b
@regs[0xF] = a < 0 ? 0 : 1
(a + 0x100) & 0xFF
end

def get_keypress
sleep 0.01 until k = (0…0xF).find{|k| $key_pressed.call(k) }
k
end

def init_screen
@screen = Array.new(32) {Array.new(64,0) }
@img_screen = TkPhotoImage.new(‘width’=>64,‘height’=>32)
@img_screen.put( Array.new(32) {Array.new(64,COLORS[0]) } )
update_screen
end

def draw_sprite(x,y,size)
x %= 64
y %= 32
@regs[0xF] = 0
img_update = Array.new(size){ Array.new(8) }
@mem[@I,size].split(’’).each_with_index{|b,dy|
ypos = (y+dy) % 32
(0…7).each{|dx|
chr = b[0][7-dx]
xpos = (x+dx) % 64
@regs[0xF] = 1 if(chr==1 && @screen[ypos][xpos]==1) # collision
col = @screen[ypos][xpos] ^= chr
img_update[dy][dx] = COLORS[col]
}
}
@img_screen.put(img_update, :to => [x,y] )
update_screen
end

def update_screen
$tkscreen.copy(@img_screen,:zoom=>10)
end

def fetch
@instr = @mem[@ip,2]
@ip += 2
end

def execute
x = @instr[0].lo
y = @instr[1].hi
opt = @instr[1].lo
kk = @instr[1]
nnn = @instr[0].lo << 8 | @instr[1]
case @instr[0].hi
when 0
case kk
when 0xE0 then init_screen
# 00E0 Erase the screen
when 0xEE then @ip = @stack.pop
# 00EE Return from a CHIP-8 sub-routine
else return nil
end
when 1 then @ip = nnn
# 1NNN Jump to the address NNN of the file
when 2
# 2NNN Call CHIP-8 sub-routine at NNN
@stack.push @ip
@ip = nnn
when 3 then @ip += 2 if @regs[x] == kk
# 3XKK Skip next instruction if VX == KK
when 4 then @ip += 2 if @regs[x] != kk
# 4XKK Skip next instruction if VX != KK
when 5 then @ip += 2 if @regs[x] == @regs[y]
# 5XY0 Skip next instruction if VX == VY
when 6 then @regs[x] = kk
# 6XKK VX = KK
when 7 then @regs[x] = self.+(@regs[x],kk)
# 7XKK VX = VX + KK
when 8
case opt
when 0 then @regs[x] = @regs[y]
# 8XY0 VX = VY
when 1 then @regs[x] |= @regs[y]
# 8XY1 VX = VX OR VY
when 2 then @regs[x] &= @regs[y]
# 8XY2 VX = VX AND VY
when 3 then @regs[x] ^= @regs[y]
# 8XY3 VX = VX XOR VY
when 4 then @regs[x] = self.+(@regs[x],@regs[y])
# 8XY4 VX = VX + VY
when 5 then @regs[x] = self.-(@regs[x],@regs[y])
# 8XY5 VX = VX - VY
when 6 then @regs[0xF], @regs[x] = @regs[x][0], @regs[x]

1 # 8X06 VX = VX SHIFT RIGHT 1 VF = least significant bit
when 7 then @regs[x] = self.-(@regs[y],@regs[x])

  # 8XY7 VX = VY - VX
      when 0xE then @regs[0xF], @regs[x] = @regs[x][7], @regs[x]

<< 1 # 8X0E VX = VX SHIFT LEFT 1, VF = most significant bit
else return nil
end
when 9 then @ip += 2 if @regs[x] != @regs[y]
# 9XY0 Skip next instruction if VX != VY
when 0xA then @I = nnn
# ANNN I = NNN
when 0xB then @ip = nnn + @regs[0]
# BNNN Jump to NNN + V0
when 0xC then @regs[x] = kk & rand(0xFF)
# CXKK VX = Random number AND KK
when 0xD then draw_sprite(@regs[x],@regs[y],opt)
# DXYN Draws a sprite at (VX,VY) starting at M(I). VF =
collision.
when 0xE
case kk
when 0x9E then @ip +=2 if $key_pressed.call @regs[x]
# EX9E Skip next instruction if key VX pressed
when 0xA1 then @ip +=2 unless $key_pressed.call @regs[x]
# EXA1 Skip next instruction if key VX not pressed
else return nil
end
when 0xF
case kk
when 0x07 then @regs[x] = @delay_timer
# FX07 VX = Delay timer
when 0x0A then @regs[x] = get_keypress
# FX0A Waits a keypress and stores it in VX
when 0x15 then @delay_timer = @regs[x]
# FX15 Delay timer = VX
when 0x18 then
# FX18 Sound timer = VX, not implemented as it doesn’t do
anything except beep
when 0x1E then @I += @regs[x]
# FX1E I = I + VX
when 0x29 then @I = 5 * @regs[x]
# FX29 I points to the 4 x 5 font sprite of hex char in VX (
font at start of mem, 5 bytes per char)
when 0x33 then @mem[@I,2] = @regs[x].bcd_byte
# FX33 Store BCD representation of VX in M(I)…M(I+2)
when 0x55 then @mem[@I,x+1] = @regs[0…x]
# FX55 Save V0…VX in memory starting at M(I)
when 0x65 then @regs[0…x] = @mem[@I,x+1]
# FX65 Load V0…VX from memory starting at M(I)
else return nil
end
else return nil
end
return true
end

def run
Thread.new {
@key_timer = @delay_timer = 0
loop{
sleep 1.0 / 60
@delay_timer -= 1 if @delay_timer > 0
@key_timer -= 1 if @key_timer > 0
@key_pressed = nil if @key_timer == 0
exit! if $key_pressed.call(‘q’)
}
}

loop {
  fetch
  break unless execute
}
puts "Halted at instruction %02X%02X " % [@instr[0],@instr[1]]

end

end

$tkscreen = TkPhotoImage.new(‘width’=>640,‘height’=>320)
TkLabel.new(nil, ‘image’ => $tkscreen ).pack
Thread.new{ Tk.mainloop }

Chip8Emulator.new( File.open(ARGV[0],‘rb’).read).run if ARGV[0]

My solution for managing binary numbers was to make a new class
BitArray.

It seems like I’m repeating myself and being awfully wordy in the main
loop.

Mitchell Koch

Heres my solution. I was more shooting for brevity of code than cool
stuff… Like previous posts, it uses instruction-level addressing,
so jumps are divided by two.

http://pastie.caboo.se/6639

On Jul 30, 2006, at 1:55 PM, Sander L. wrote:

I used one Win32 function (GetKeyState) as I didn’t know a compatible
way to check if a key is pressed.

I discuss this a little at the end of this summary:

http://www.rubyquiz.com/quiz5.html

You can also use HighLine for this.

Amazing solution. Thanks for sharing.

James Edward G. II

On 7/28/06, Ruby Q. [email protected] wrote:

Your job is to make an emulator…

My solution: It’s like a clone of my ICFP solution. Where’s the
codex.chip8?
-Adam


class Chip8
class Instruction
attr_reader :op,:nn,:x,:y,:kk
def parse val
@op = (val&0xF000)>>12
@nn = (val&0x0FFF)
@kk = (val&0x00FF)
@x = (val&0x0F00)>>8
@y = (val&0x00F0)>>4
end
end
def initialize
@program = []
@cp = 0
@v = Array.new(16){0}
end
def load filename
File.open(filename, “rb”){|f|
while !f.eof
@program << f.read(2).unpack(‘n*’)[0]
end
}
end
def run
halt = false
i = Instruction.new
while !halt
i.parse(@program[@cp])
@cp+=1
case i.op
when 0 then halt=true
when 1 then @cp = i.nn/2
when 3 then @cp+=1 if @v[i.x]==i.kk
when 6 then @v[i.x] = i.kk
when 7 then @v[0xF],@v[i.x] = (@v[i.x]+ i.kk).divmod 0x100
when 8
case i.nn&0xF
when 0 then @v[i.x] = @v[i.y]
when 1 then @v[i.x] |= @v[i.y]
when 2 then @v[i.x] &= @v[i.y]
when 3 then @v[i.x] ^= @v[i.y]
when 4 then @v[0xF],@v[i.x] = (@v[i.x][email protected][i.y]).divmod 0x100
when 5 then @v[0xF],@v[i.x] = (@v[i.x]-@v[i.y]).divmod 0x100
when 6 then @v[0xF][email protected][i.x][0]
@v[i.x]>>=1
when 7 then c,@v[i.x] = (@v[i.y]-@v[i.x]).divmod 0x100
@v[0xF]=1+c
when 0xE then @v[0xF][email protected][i.x][7]
@v[i.x]<<=1
end
when 0xC then @v[i.x] = rand(256)&i.kk
end
end
end
def dump base = 10
puts @v.map{|e|e.to_s(base)}.inspect
end
end

if FILE == $0
c = Chip8.new
c.load ARGV[0]||“chip8.test”
c.run
c.dump(2)
end

On Jul 31, 2006, at 2:43 PM, Sander L. wrote:

I installed highline and checked the docs as well as the source and
couldn’t find anything.
GetKeyState checks if a key is currently held down and not if it was
pressed, so get_character does not do what i want.

My apologies. I misunderstood.

James Edward G. II

On 7/31/06, James Edward G. II [email protected] wrote:

On Jul 30, 2006, at 1:55 PM, Sander L. wrote:

I used one Win32 function (GetKeyState) as I didn’t know a compatible
way to check if a key is pressed.
I discuss this a little at the end of this summary:
http://www.rubyquiz.com/quiz5.html
You can also use HighLine for this.

I installed highline and checked the docs as well as the source and
couldn’t find anything.
GetKeyState checks if a key is currently held down and not if it was
pressed, so get_character does not do what i want.

An earlier implementation used kbhit/getch, but that didn’t work at
all because games like breakout check key input like:
V0 = 4
skip unless key V0 pressed

V0 = 6
skip unless key V0 pressed

And the repeat rate of keys seems to be too slow for the “6” to
trigger even if you hold the key down.
One other thing I tried is saving a key as held down for 0.25s if it
was pressed, but that was far too unreliable. The two “@key_timer
lines of my code are a leftover from this which I forgot to remove.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs