--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

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

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 :)

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' :)
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


--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cc8.rb"

#!/usr/bin/ruby
OPCODE_LENGTH             # bytes
EOX  0000'                # End Of eXecutable
VF  5                     # Shorcut to the carry reg.

@CODE  0' * 1024          # The program
@REG  rray.new(16, 0)     # The Registers
@CA                       # Current Address
@CO                       # Current Opcode

class << self
  def jmp(addr)   # Jump at addr
    @CODE[@CA, OPCODE_LENGTH]  1' << ("%03X" % addr)
    @CA + PCODE_LENGTH
  end
  def call(addr)  # Call code at addr
    @CODE[@CA, OPCODE_LENGTH]  2' << ("%03X" % addr)
  end
  def sec(vx, kk) # Skip if Equal with Constant
    ivx  x.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  3' << ivx  << ("%02X" % kk)
    @CA + PCODE_LENGTH
  end
  def snc(vx, kk) # Skip if NOT equal with Constant
    ivx  x.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  4' << ivx  << ("%02X" % kk)
    @CA + PCODE_LENGTH
  end
  def seq(vx, vy) # Skip next open if VX  Y
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  5' << ivx << ivy << '0'
    @CA + PCODE_LENGTH
  end
  def sne(vx, vy) # Skip next opcode if VX ! Y
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  9' << ivx << ivy << '0'
    @CA + PCODE_LENGTH
  end
  def mov(vx, kk) # VX  K
    ivx  x.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  6' << ivx  << ("%02X" % kk)
    @CA + PCODE_LENGTH
  end
  def inc(vx, kk) # VX  X + KK
    ivx  x.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  7' << ivx  << ("%02X" % kk)
    @CA + PCODE_LENGTH
  end
  def movr(vx, vy) # VX  Y
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '0'
    @CA + PCODE_LENGTH
  end
  def _or(vx, vy) # VX  X OR VY
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '1'
    @CA + PCODE_LENGTH
  end
  def _and(vx, vy) # VX  X AND VY
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '2'
    @CA + PCODE_LENGTH
  end
  def _xor(vx, vy) # VX  X XOR VY
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '3'
    @CA + PCODE_LENGTH
  end
  def sum(vx, vy) # VX  X + VY
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '4'
    @CA + PCODE_LENGTH
  end
  def sub(vx, vy) # VX  X - VY
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '5'
    @CA + PCODE_LENGTH
  end
  def shr(vx, vy) # VX >> 1
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '6'
    @CA + PCODE_LENGTH
  end
  def subr(vx, vy) # VX  Y - VX
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << '7'
    @CA + PCODE_LENGTH
  end
  def shl(vx, vy) # VX << 1
    ivx, ivy  x.to_s.delete('V'), vy.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  8' << ivx << ivy << 'E'
    @CA + PCODE_LENGTH
  end
  def jmp0(nnn) # Jump to nnn + V0
    @CODE[@CA, OPCODE_LENGTH]  B' << ("%03X" % nnn)
    @CA + PCODE_LENGTH
  end
  def rndc(vx, kk) # Random AND Constant
    ivx  x.to_s.delete('V')
    @CODE[@CA, OPCODE_LENGTH]  C' << ivx  << ("%02X" % kk)
    @CA + PCODE_LENGTH
  end
  def ret
    @CODE  CODE[0, @CA + OPCODE_LENGTH + (OPCODE_LENGTH / 2)]
  end
  def dump
    opcode, i  ', 1
    @CODE.each_byte do |b|
      if opcode.length 4
        print "#{opcode} "; opcode  '
        puts if i % 20 0
        i + 
      end
      opcode << b.chr
    end
  end
  def store
    fname, fext  ile.basename(ARGV[0]).split(/\./); filename  #{fname}.bin"
    fh  ile.open(filename, 'wb')
    byte  '
    @CODE.each_byte do |b| 
      if byte.length 2 # hex digits
        fh.write(byte.hex.chr); byte  '
      end
      byte << b.chr
    end
    fh.close
  end
end

eval(File.open(ARGV[0]).read)

--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="chip8.rb"

#!/usr/bin/ruby

class Processor
  OPCODE_LENGTH           # bytes
  EOX  0000'              # End Of eXecutable
  VF  5                   # Shorcut to the carry reg.

  def initialize(code)
    @CODE  ode            # The program
    @REG  rray.new(16, 0) # The Registers
    @IP                   # Instruction Pointer
    @CO                   # Current Opcode
  end

  def dump
    puts "Opcode: #@CO"
    0.upto(VF) {|i| puts "V%X:" % i << "%08b %02X" % [@REG[i], @REG[i]]}; puts 
  end

  def run
    @CO  CODE[@IP, OPCODE_LENGTH]
    vx, vy   CO[1,1].hex.to_i, @CO[2,1].hex.to_i
    kk, nnn  CO[2,2].hex.to_i, @CO[1,3].hex.to_i
    case @CO[0,1]
    when '1'; @IP  nn - OPCODE_LENGTH
    when '2'; ip  IP; @IP  nn; run; @IP  p
    when '3'; @IP + PCODE_LENGTH if @REG[vx] kk
    when '4'; @IP + PCODE_LENGTH if @REG[vx] ! k
    when '5'; @IP + PCODE_LENGTH if @REG[vx] @REG[vy]
    when '6'; @REG[vx]  k
    when '7'; @REG[vx] + k
    when '8' 
      case @CO[3,1]
      when '0'; @REG[vx]   REG[vy]
      when '1'; @REG[vx] | REG[vy]
      when '2'; @REG[vx] & REG[vy]
      when '3'; @REG[vx] ^ REG[vy]
      when '4'
        sum  REG[vx] + @REG[vy]
        if sum > 255
          @REG[vx]  um % 256
          @REG[VF]  
        else
          @REG[vx]  um
          @REG[VF]  
        end
      when '5'
        diff  REG[vx] - @REG[vy]
        if diff < 0
          @REG[vx]  56 - @REG[vy]
          @REG[VF]  
        else
          @REG[vx]  iff
          @REG[VF]  
        end
      when '6'
        bin  %b" % @REG[vx]
        @REG[VF]  in[-1, 1].to_i
        @REG[vx]  REG[vx] >> 1
      when '7'
        diff  REG[vy] - @REG[vx]
        if diff < 0
          @REG[vx]  56 - @REG[vx]
          @REG[VF]  
        else
          @REG[vx]  iff
          @REG[VF]  
        end
      when 'E'
        bin  %08b" % @REG[vx]
        @REG[VF]  in[0, 1].to_i
        @REG[vx]  @REG[vx] << 1) % 256
      end
    when '9'; @IP + PCODE_LENGTH if @REG[vx] ! REG[vy]
    when 'A'; @IP  nn - OPCODE_LENGTH
    when 'B'; @IP  nn + @REG[0] - OPCODE_LENGTH
    when 'C'; @REG[vx]  and(256) & kk
    when '0'; return if @CO EOX
    end
    @IP + PCODE_LENGTH
    run # again
  end
end

if $PROGRAM_NAME __FILE__
  code  '
  filetorun  ARGV[0].nil?) ? 'Chip8Test' : ARGV[0]
  File.open(filetorun).each_byte {|b| code << "%02X"%b}
  P  rocessor.new(code)
  P.run
  P.dump
end

--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cc8-quickref.txt"

CHIP-8 Pseudo Assembler 
   - Quick Reference

  jmp  addr     # Jump at addr
  call addr     # Call code at addr
  sec  vx, kk   # Skip if equal with constant
  snc  vx, kk   # Skip if NOT equal with constant
  seq  vx, vy   # Skip if equal
  sne  vx, vy   # Skip if NOT equal with constant
  mov  vx, kk   # Set register
  inc  vx, kk   # Increment register
  movr vx, vy   # Copy register
  _or  vx, vy   # Copy register /w AND
  _and vx, vy   # Copy register w/ OR
  _xor vx, vy   # Copy register w/ XOR
  sum  vx, vy   # Sum up registers
  sub  vx, vy   # Substract registers
  shr  vx, vy   # Substract registers
  subr vx, vy   # Substract registers, reversed
  shl  vx, vy   # Substract registers
  jmp0 nnn      # Jump to nnn + V0
  rndc vx, kk   # Random AND constant

At the end of the program you can tell the compiler to either dump the
opcodes to the screen, or 'compile' it into a binary file. Or both.
Before that however you must use:

  ret           # output '0000' the exit opcode

and only after that you can use:
  dump          # Dump the opcodes on screen
  store         # Dump the opcodes into a binary file


--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="AnotherTest.c8"

mov  :V1, 0x10
mov  :V2, 0x13
mov  :V3, 0x31
mov  :V4, 0x21
mov  :V5, 0x22
mov  :V6, 0x25
sec  :V2, 7
snc  :V8, 0x25
seq  :V2, :V7
sne  :VA, :VD
_and :V4, :V2
inc  :V4, 0x11
movr :V2, :V4
mov  :V4, 0x33
_or  :V1, :V4
rndc :V9, 0x71
sec  :V2, 7
seq  :V2, :V7
shl  :V8, :V0
shr  :V9, :V0
snc  :V8, 0x25
sne  :VA, :VD
subr :V2, :V1
shr  :V9, :V0
shl  :V8, :V0
subr :V2, :V1
inc  :V4, 0x11
sub  :V2, :V3
sum  :V2, :V1
_xor :V5, :V5
mov  :V4, 0x33
inc  :V4, 0x11
rndc :V9, 0x71
movr :V2, :V4
_or  :V1, :V4
_and :V4, :V2
_xor :V5, :V5
sum  :V2, :V1
sub  :V2, :V3
shr  :V9, :V0
shl  :V8, :V0
subr :V2, :V1
inc  :V4, 0x11
rndc :V9, 0x71
ret

dump
store

--Q68bSM7Ycu6FN28Q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="Chip8Test.c8"

# .CODE
mov  :V1, 0x77
mov  :V2, 0x45
inc  :V1, 0x01
movr :V3, :V2
_or  :V1, :V2
_and :V1, :V2
_xor :V2, :V3
sum  :V1, :V3
sub  :V2, :V3
shr  :V1, :V0
subr :V3, :V2
shl  :V3, :V0
mov  :V4, 0xFF
rndc :V4, 0x11
sec  :V2, 0xBB
jmp 0         
ret           
# .COMPILER
dump
store

--Q68bSM7Ycu6FN28Q--