From: "Alexandru E. Ungur" Date: 2006-07-31T03:36:12+09:00 Subject: [Solution] [QUIZ] Chip-8 (#88) --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 = 4 # bytes EOX = '0000' # End Of eXecutable VF = 15 # Shorcut to the carry reg. @CODE = '0' * 1024 # The program @REG = Array.new(16, 0) # The Registers @CA = 0 # Current Address @CO = 0 # Current Opcode class << self def jmp(addr) # Jump at addr @CODE[@CA, OPCODE_LENGTH] = '1' << ("%03X" % addr) @CA += OPCODE_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 = vx.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '3' << ivx << ("%02X" % kk) @CA += OPCODE_LENGTH end def snc(vx, kk) # Skip if NOT equal with Constant ivx = vx.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '4' << ivx << ("%02X" % kk) @CA += OPCODE_LENGTH end def seq(vx, vy) # Skip next open if VX = VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '5' << ivx << ivy << '0' @CA += OPCODE_LENGTH end def sne(vx, vy) # Skip next opcode if VX != VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '9' << ivx << ivy << '0' @CA += OPCODE_LENGTH end def mov(vx, kk) # VX = KK ivx = vx.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '6' << ivx << ("%02X" % kk) @CA += OPCODE_LENGTH end def inc(vx, kk) # VX = VX + KK ivx = vx.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '7' << ivx << ("%02X" % kk) @CA += OPCODE_LENGTH end def movr(vx, vy) # VX = VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '0' @CA += OPCODE_LENGTH end def _or(vx, vy) # VX = VX OR VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '1' @CA += OPCODE_LENGTH end def _and(vx, vy) # VX = VX AND VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '2' @CA += OPCODE_LENGTH end def _xor(vx, vy) # VX = VX XOR VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '3' @CA += OPCODE_LENGTH end def sum(vx, vy) # VX = VX + VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '4' @CA += OPCODE_LENGTH end def sub(vx, vy) # VX = VX - VY ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '5' @CA += OPCODE_LENGTH end def shr(vx, vy) # VX >> 1 ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '6' @CA += OPCODE_LENGTH end def subr(vx, vy) # VX = VY - VX ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << '7' @CA += OPCODE_LENGTH end def shl(vx, vy) # VX << 1 ivx, ivy = vx.to_s.delete('V'), vy.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = '8' << ivx << ivy << 'E' @CA += OPCODE_LENGTH end def jmp0(nnn) # Jump to nnn + V0 @CODE[@CA, OPCODE_LENGTH] = 'B' << ("%03X" % nnn) @CA += OPCODE_LENGTH end def rndc(vx, kk) # Random AND Constant ivx = vx.to_s.delete('V') @CODE[@CA, OPCODE_LENGTH] = 'C' << ivx << ("%02X" % kk) @CA += OPCODE_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 += 1 end opcode << b.chr end end def store fname, fext = File.basename(ARGV[0]).split(/\./); filename = "#{fname}.bin" fh = File.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 = 4 # bytes EOX = '0000' # End Of eXecutable VF = 15 # Shorcut to the carry reg. def initialize(code) @CODE = code # The program @REG = Array.new(16, 0) # The Registers @IP = 0 # Instruction Pointer @CO = 0 # 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 = nnn - OPCODE_LENGTH when '2'; ip = @IP; @IP = nnn; run; @IP = ip when '3'; @IP += OPCODE_LENGTH if @REG[vx] == kk when '4'; @IP += OPCODE_LENGTH if @REG[vx] != kk when '5'; @IP += OPCODE_LENGTH if @REG[vx] == @REG[vy] when '6'; @REG[vx] = kk when '7'; @REG[vx] += kk 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] = sum % 256 @REG[VF] = 1 else @REG[vx] = sum @REG[VF] = 0 end when '5' diff = @REG[vx] - @REG[vy] if diff < 0 @REG[vx] = 256 - @REG[vy] @REG[VF] = 0 else @REG[vx] = diff @REG[VF] = 1 end when '6' bin = "%b" % @REG[vx] @REG[VF] = bin[-1, 1].to_i @REG[vx] = @REG[vx] >> 1 when '7' diff = @REG[vy] - @REG[vx] if diff < 0 @REG[vx] = 256 - @REG[vx] @REG[VF] = 0 else @REG[vx] = diff @REG[VF] = 1 end when 'E' bin = "%08b" % @REG[vx] @REG[VF] = bin[0, 1].to_i @REG[vx] = (@REG[vx] << 1) % 256 end when '9'; @IP += OPCODE_LENGTH if @REG[vx] != @REG[vy] when 'A'; @IP = nnn - OPCODE_LENGTH when 'B'; @IP = nnn + @REG[0] - OPCODE_LENGTH when 'C'; @REG[vx] = rand(256) & kk when '0'; return if @CO == EOX end @IP += OPCODE_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 = Processor.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--