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