First commit

This commit is contained in:
Juhani Krekelä 2018-09-20 23:54:51 +03:00
commit 7c353f02de
4 changed files with 850 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.swp

116
CC0 Normal file
View File

@ -0,0 +1,116 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

47
README.md Normal file
View File

@ -0,0 +1,47 @@
Name
----
"Sipsi" is Finnish for a potato chip.
Usage
-----
python3 sipsi-8.py path/to/chip-8/file
Input
-----
Hardcoded (sorry)
1 2 3 4
Q W E R
A S D F
Z X C V
which maps to
1 2 3 C
4 5 6 D
7 8 9 E
A 0 B F
Sound
-----
Not yet implemented
Games
-----
Get some [here](http://www.pong-story.com/chip8/)
Code quality
------------
Pretty bad
Emulation speed
---------------
Instructions are run 500 times a second
Requirements
------------
* pyglet (`pip3 install pyglet`)
License
-------
Creative Commons Zero 1.0

686
sipsi-8.py Normal file
View File

@ -0,0 +1,686 @@
import random
import sys
import pyglet
# TODO: Don't hardcode keyboard input
keypad_keys = [
pyglet.window.key._1, pyglet.window.key._2, pyglet.window.key._3, pyglet.window.key._4,
pyglet.window.key.Q, pyglet.window.key.W, pyglet.window.key.E, pyglet.window.key.R,
pyglet.window.key.A, pyglet.window.key.S, pyglet.window.key.D, pyglet.window.key.F,
pyglet.window.key.Z, pyglet.window.key.X, pyglet.window.key.C, pyglet.window.key.V,
]
# OSCOM Nano keys are arranged like
# 123C
# 456D
# 789E
# A0BF
# according to http://hobbylabs.org/oscom_nano.htm
# However, at the moment the indices of the keys are
# 0123
# 4567
# 89ab
# cdef
# This rearranges them so that the index and the hex digit match
keypad_keys = [keypad_keys[i] for i in [0xd, 0, 1, 2, 4, 5, 6, 8, 9, 0xa, 0xc, 0xe, 3, 7, 0xb, 0xf]]
def key_pressed(symbol):
global keys_pressed, keypress_arrived
for i in range(16):
if symbol == keypad_keys[i]:
keys_pressed[i] = True
keypress_arrived = True
break
def key_released(symbol):
global keys_pressed
for i in range(16):
if symbol == keypad_keys[i]:
keys_pressed[i] = False
break
def draw_screen():
to_draw = []
for y in range(32):
for x in range(64):
if screen[y * 64 + x]:
to_draw.append((x, y))
screen_points = []
for x, y in to_draw:
# pyglet has 0,0 at bottom left, so reverse the y
y = 31 - y
# TODO: Don't hardcode pixel size
# Lol, what is a winding order
screen_points.append(x * 10)
screen_points.append(y * 10)
screen_points.append(x * 10 + 10)
screen_points.append(y * 10)
screen_points.append(x * 10 + 10)
screen_points.append(y * 10 + 10)
screen_points.append(x * 10)
screen_points.append(y * 10 + 10)
if len(screen_points) > 0:
pyglet.graphics.draw(len(screen_points) // 2,
pyglet.gl.GL_QUADS,
('v2i', screen_points)
)
def step():
global data_registers, ip, stack, i_register
global ram, font_start
global screen
global delay_timer, sound_timer
global keys_pressed, waiting_for_keypress, keypress_arrived
# Don't execute any code in a waitstate, as it'd only end up
# busylooping
if waiting_for_keypress and not keypress_arrived: return
high_byte = ram[ip]
ip = (ip + 1) & 0xfff
low_byte = ram[ip]
ip = (ip + 1) & 0xfff
#print(hex(high_byte), hex(low_byte), hex(ip), [hex(i) for i in data_registers], [hex(i) for i in stack], hex(i_register), delay_timer)#debg
#input()#debg
# Instruction info gotten from http://mattmik.com/files/chip8/mastering/chip8.html
# 00E0 clearscreen
if high_byte == 0x00 and low_byte == 0xE0:
screen = [False] * 64 * 32
# 00EE ret
elif high_byte == 0x00 and low_byte == 0xEE:
ip = stack.pop()
# 0nnn call_machine
elif high_byte >> 4 == 0:
print("%03x: Can't call machine language!" % (ip - 2))
sys.exit(1)
# 1nnn jmp nnn
elif high_byte >> 4 == 1:
ip = ((high_byte & 0xf) << 8) | low_byte
# 2nnn call nnn
elif high_byte >> 4 == 2:
stack.append(ip)
ip = ((high_byte & 0xf) << 8) | low_byte
# 3xnn skipeq vx, nn
elif high_byte >> 4 == 3:
if data_registers[high_byte & 0xf] == low_byte:
ip = (ip + 2) & 0xfff
# 4xnn skipneq vx, nn
elif high_byte >> 4 == 4:
if data_registers[high_byte & 0xf] != low_byte:
ip = (ip + 2) & 0xfff
# 5xy0 skipeq vx, vy
elif high_byte >> 4 == 5 and low_byte & 0xf == 0:
if data_registers[high_byte & 0xf] == data_registers[low_byte >> 4]:
ip = (ip + 2) & 0xfff
# 6xnn mov vx, nn
elif high_byte >> 4 == 6:
data_registers[high_byte & 0xf] = low_byte
# 7xnn add vx, nn (no flags)
elif high_byte >> 4 == 7:
old_value = data_registers[high_byte & 0xf]
data_registers[high_byte & 0xf] = (old_value + low_byte) & 0xff
# 8xy0 mov vx, vy
elif high_byte >> 4 == 8 and low_byte & 0xf == 0:
data_registers[high_byte & 0xf] = data_registers[low_byte >> 4]
# 8xy1 or vx, vy
elif high_byte >> 4 == 8 and low_byte & 0xf == 1:
old_value = data_registers[high_byte & 0xf]
result = old_value | data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = result
# 8xy2 and vx, vy
elif high_byte >> 4 == 8 and low_byte & 0xf == 2:
old_value = data_registers[high_byte & 0xf]
result = old_value & data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = result
# 8xy3 xor vx, vy
elif high_byte >> 4 == 8 and low_byte & 0xf == 3:
old_value = data_registers[high_byte & 0xf]
result = old_value ^ data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = result
# 8xy4 add vx, vy (carry flag)
elif high_byte >> 4 == 8 and low_byte & 0xf == 4:
old_value = data_registers[high_byte & 0xf]
result = old_value + data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = result & 0xff
if result > 255:
data_registers[0xf] = 1
else:
data_registers[0xf] = 0
# 8xy5 sub vx, vy (carry flag)
elif high_byte >> 4 == 8 and low_byte & 0xf == 5:
old_value = data_registers[high_byte & 0xf]
result = old_value - data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = result & 0xff
if result < 0:
data_registers[0xf] = 0
else:
data_registers[0xf] = 1
# 8xy6 shr vx, vy, 1 (LSB to VF)
elif high_byte >> 4 == 8 and low_byte & 0xf == 6:
other_value = data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = other_value >> 1
data_registers[0xf] = other_value & 0x1
# 8xy7 sub vx, vy, vx (carry flag)
elif high_byte >> 4 == 8 and low_byte & 0xf == 7:
old_value = data_registers[high_byte & 0xf]
result = data_registers[low_byte >> 4] - old_value
data_registers[high_byte & 0xf] = result & 0xff
if result < 0:
data_registers[0xf] = 0
else:
data_registers[0xf] = 1
# 8xyE shl vx, vy, 1 (MSB to VF)
elif high_byte >> 4 == 8 and low_byte & 0xf == 0xE:
other_value = data_registers[low_byte >> 4]
data_registers[high_byte & 0xf] = (other_value << 1) & 0xff
data_registers[0xf] = other_value >> 7
# 9xy0 skipneq vx, vy
elif high_byte >> 4 == 9 and low_byte & 0xf == 0:
if data_registers[high_byte & 0xf] != data_registers[low_byte >> 4]:
ip = (ip + 2) & 0xfff
# Annn mov i, nnn
elif high_byte >> 4 == 0xA:
i_register = ((high_byte & 0xf) << 8) | low_byte
# Bnnn jmp nnn + v0
elif high_byte >> 4 == 0xB:
ip = ((high_byte & 0xf) << 8) | low_byte
ip = (ip + data_registers[0]) & 0xfff
# Cxnn maskedrandom vx, nn
elif high_byte >> 4 == 0xC:
result = random.randint(0, 255)
data_registers[high_byte & 0xf] = result & low_byte
# Dxyn draw vx, vy, n
elif high_byte >> 4 == 0xD:
# TODO: OSCOM Nano manual (page 38) says "<The screen
# behaves as if the top was connected to the bottom and
# the sides to each other.>" Investigate how this wrapping
# should be implemented
x_start = data_registers[high_byte & 0xf]
y_start = data_registers[low_byte >> 4]
any_unset = False
# Sprite will be 8 pixels wide and n tall
for dy in range(low_byte & 0xf):
# Load this line's graphics
line = ram[(i_register + dy) & 0xfff]
y = y_start + dy
# Screen is 32 lines tall
if y >= 32:
# TODO: Figure how y >= 32 works
continue
for dx in range(8):
x = x_start + dx
# Screen is 64 columns wide
if x >= 64:
# TODO: Figure how x >= 64 works
continue
# Pixels are stored MSB-left
pixel = (line >> (7 - dx)) & 1
screen_index = y * 64 + x
if pixel:
# Check if we are unsetting a pixel
if screen[screen_index]:
any_unset = True
# XOR the pixel
screen[screen_index] = not screen[screen_index]
if any_unset:
data_registers[0xf] = 1
else:
data_registers[0xf] = 0
# Ex9E skipkey vx
elif high_byte >> 4 == 0xE and low_byte == 0x9E:
if keys_pressed[data_registers[high_byte & 0xf]]:
ip = (ip + 2) & 0xfff
# ExA1 skipnkey vx
elif high_byte >> 4 == 0xE and low_byte == 0xA1:
if not keys_pressed[data_registers[high_byte & 0xf]]:
ip = (ip + 2) & 0xfff
# Fx07 getdelay vx
elif high_byte >> 4 == 0xF and low_byte == 0x07:
data_registers[high_byte & 0xf] = delay_timer
# Fx0A getkey vx
elif high_byte >> 4 == 0xF and low_byte == 0x0A:
if not waiting_for_keypress:
# Wait for a key to be pressed and then re-execute
# this instruction
waiting_for_keypress = True
keypress_arrived = False
ip = (ip - 2) & 0xfff
if keypress_arrived:
# A key was pressed
for i in range(16):
if keys_pressed[i]:
# Set vx to first key we found
any_pressed = True
data_registers[high_byte & 0xf] = i
break
waiting_for_keypress = False
# Fx15 setdelay vx
elif high_byte >> 4 == 0xF and low_byte == 0x15:
delay_timer = data_registers[high_byte & 0xf]
# Fx18 setsound vx
elif high_byte >> 4 == 0xF and low_byte == 0x18:
sound_timer = data_registers[high_byte & 0xf]
# Fx1E add i, vx
elif high_byte >> 4 == 0xF and low_byte == 0x1E:
i_register = (i_register + data_registers[high_byte & 0xf]) & 0xfff
# Fx29 mov i, digit(vx)
elif high_byte >> 4 == 0xF and low_byte == 0x29:
value = data_registers[high_byte & 0xf]
if 0 <= value <= 0xf:
# Each digit is 5 lines tall = 5 bytes long
i_register = font_start + value * 5
else:
print('%03x: Bad font character' % (ip-2))
sys.exit(1)
# Fx33 mov [i … i+2], bcd(vx)
elif high_byte >> 4 == 0xF and low_byte == 0x33:
value = data_registers[high_byte & 0xf]
highdigit = value // 100
middigit = value // 10 % 10
lowdigit = value % 10
ram[i_register] = highdigit
ram[(i_register + 1) & 0xfff] = middigit
ram[(i_register + 2) & 0xfff] = lowdigit
# Fx55 mov [i … i+x], v0 … vx (updates i)
elif high_byte >> 4 == 0xF and low_byte == 0x55:
for register in range((high_byte & 0xf) + 1):
ram[i_register] = data_registers[register]
i_register = (i_register + 1) & 0xfff
# Fx65 mov v0 … vx, [i … i+x] (updates i)
elif high_byte >> 4 == 0xF and low_byte == 0x65:
for register in range((high_byte & 0xf) + 1):
data_registers[register] = ram[i_register]
i_register = (i_register + 1) & 0xfff
# Fallback
else:
print('%03x: Unrecognized!' % (ip-2))
def tick_timers():
global delay_timer, sound_timer
# Tick the delay timer down until it reaches 0
if delay_timer > 0:
# The timer should tick down at 60Hz
delay_timer -= 1
# TODO: Do sth about the sound timer
def advance_interpreter(dt):
global cpu_speed
global cpu_cycles_to_go
# Each tic (60Hz) we need to run cpu_clock / 60Hz cycles
cpu_cycles_to_go += cpu_clock / 60
tick_timers()
while cpu_cycles_to_go >= 1:
step()
cpu_cycles_to_go -= 1
def initialize_ram():
global ram, font_start
ram = [0]*(1<<12)
# Font data is from http://mattmik.com/files/chip8/mastering/chip8.html
# and http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#2.4
font = [
0xf0, 0x90, 0x90, 0x90, 0xf0, # 0
0x20, 0x60, 0x20, 0x20, 0x70, # 1
0xf0, 0x10, 0xf0, 0x80, 0xf0, # 2
0xf0, 0x10, 0xf0, 0x10, 0xf0, # 3
0x90, 0x90, 0xf0, 0x10, 0x10, # 4
0xf0, 0x80, 0xf0, 0x10, 0xf0, # 5
0xf0, 0x80, 0xf0, 0x90, 0xf0, # 6
0xf0, 0x10, 0x20, 0x40, 0x40, # 7
0xf0, 0x90, 0xf0, 0x90, 0xf0, # 8
0xf0, 0x90, 0xf0, 0x10, 0xf0, # 9
0xf0, 0x90, 0xf0, 0x90, 0x90, # A
0xe0, 0x90, 0xe0, 0x90, 0xe0, # B
0xf0, 0x80, 0x80, 0x80, 0xf0, # C
0xe0, 0x90, 0x90, 0x90, 0xe0, # D
0xf0, 0x80, 0xf0, 0x80, 0xf0, # E
0xf0, 0x80, 0xf0, 0x80, 0x80, # F
]
# Unsure where the font should go. Both http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
# and http://stevelosh.com/blog/2016/12/chip8-graphics/?m=1#fonts
# say it goes at 0x50, but most resources don't have its location
# specified and I have run into implementations that have it at
# start of memory (http://craigthomas.ca/blog/2017/10/15/writing-a-chip-8-emulator-built-in-font-set-part-4/)
# and even a resource stating it's at high memory (http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8.shtml)
# The location honestly shouldn't matter, but I'm putting it at
# 0x50
font_start = 0x50
ram[font_start:font_start + len(font)] = font
test_program = [
## Arithmetic test
#0x65, 0x42, # 0x42 → v5 | v5: 42
#0x82, 0x50, # v5 → v2 | v2: 42, v5: 42
#0x72, 0x69, # v2 + 0x69 → v2 | v2: ab, v5: 42
#0x85, 0x24, # v5 + v2 → v5 | v2: ab, v5: ed, vf: 00
#0x85, 0x24, # v5 + v2 → v5 | v2: ab, v5: 98, vf: 01
#0x85, 0x25, # v5 - v2 → v5 | v2: ab, v5: ed, vf: 00
#0x82, 0x57, # v5 - v2 → v2 | v2: 42, v5: ed, vf: 01
#0x63, 0x01, # 0x01 → v3 | v2: 42, v3: 01, v5: ed, vf: 01
#0x85, 0x22, # v5 & v2 → v5 | v2: 42, v3: 01, v5: 40, vf: 01
#0x83, 0x51, # v3 | v5 → v3 | v2: 42, v3: 41, v5: 40, vf: 01
#0x82, 0x33, # v2 ^ v3 → v2 | v2: 3, v3: 41, v5: 40, vf: 01
#0x82, 0x56, # v5 >> 1 → v2 | v2: 20, v3: 41, v5: 40, vf: 00
#0x85, 0x36, # v3 >> 1 → v5 | v2: 20, v3: 41, v5: 20, vf: 01
#0x64, 0x70, # 0x70 → v4 | v2: 20, v3: 41, v4: 70, v5: 20, vf: 01
#0x84, 0x4E, # v4 << 1 → v4 | v2: 20, v3: 41, v4: e0, v5: 20, vf: 00
#0x84, 0x4E, # v4 << 1 → v4 | v2: 20, v3: 41, v4: c0, v5: 20, vf: 01
## RNG test
#0xC0, 0xff, # RNG → v0
#0xC0, 0xff, # RNG → v0
#0xC0, 0xff, # RNG → v0
#0xC0, 0xff, # RNG → v0
#0xC0, 0x06, # RNG & 0x00001110 → v0
#0xC0, 0x06, # RNG & 0x00001110 → v0
#0xC0, 0x06, # RNG & 0x00001110 → v0
#0xC0, 0x06, # RNG & 0x00001110 → v0
## Flow control test
#0x60, 0x00, # mov v0, 0
#0x22, 0x06, # call 0x206
#0x12, 0x02, # jmp 0x202
#0x70, 0x01, # add v0, 1
#0x00, 0xee, # ret
## Skip test
#0x61, 0xf0, # mov v1, 0xf0
#0x62, 0xf1, # mov v2, 0xf1
#0x83, 0x10, # mov v3, v1
#0x31, 0xf0, # skipeq v3, 0xf0
#0x00, 0x00, # ill
#0x31, 0x00, # skipeq v3, 0
#0x6e, 0x01, # mov ve, 1
#0x51, 0x30, # skipeq v1, v3
#0x00, 0x00, # ill
#0x42, 0xf0, # skipneq v2, 0xf0
#0x00, 0x00, # ill
#0x91, 0x20, # skipneq v1, v2
#0x00, 0x00, # ill
#0x6d, 0x01, # mov vd, 1
## Timer test
#0xF0, 0x07, # getdelay v0
#0x40, 0x00, # skipneq v0, 0
#0x22, 0x08, # call 0x208
#0x12, 0x00, # jmp 0x200
#0x60, 0xff, # mov v0, 0xff
#0xf0, 0x15, # setdelay v0
#0x00, 0xee, # ret
## Input wait test
#0xf1, 0x0a, # getkey v1
## Input skip test
#0x61, 0x00, # mov v1, 0
#0x62, 0x0a, # mov v2, 0xa
#0x63, 0x0b, # mov v3, 0xb
#0xe2, 0xa1, # skipnkey v2
#0x71, 0x01, # add v1, 1
#0xe3, 0x9e, # skipkey v3
#0x12, 0x06, # jmp 0x206
## Input wait test the second
#0xa1, 0x23, # mov i, 0x123
#0xf0, 0x0a, # getkey v0
#0xf0, 0x1e, # add i, v0
#0x12, 0x02, # jmp 0x202
## Drawing program / drawing test
## From the OSCOM Nano manual
#0x6a, 0x01, # mov va, 1
#0x60, 0x10, # mov v0, 0x10
#0x61, 0x20, # mov v1, 0x20
#0xa2, 0x50, # mov i, 0x250
#0xf2, 0x0a, # getkey v2
#0x42, 0x01, # skipneq v2, 1
#0x22, 0x2e, # call 0x22e
#0x42, 0x02, # skipneq v2, 2
#0x80, 0xa5, # sub v0, va
#0x42, 0x03, # skipneq v2, 3
#0x22, 0x34, # call 0x234
#0x42, 0x04, # skipneq v2, 4
#0x81, 0xa5, # sub v1, va
#0x42, 0x06, # skipneq v2, 6
#0x81, 0xa4, # add v1, va
#0x42, 0x07, # skipneq v2, 7
#0x22, 0x3a, # call 0x23a
#0x42, 0x08, # skipneq v2, 8
#0x80, 0xa4, # add v0, va
#0x42, 0x09, # skipneq v2, 9
#0x22, 0x40, # call 0x240
#0xD1, 0x01, # draw v0, v1, 1
#0x12, 0x08, # jmp 0x208
## 22e
#0x81, 0xa5, # sub v1, va
#0x80, 0xa5, # sub v0, va
#0x00, 0xee, # ret
## 234
#0x80, 0xa5, # sub v0, va
#0x81, 0xa4, # add v1, va
#0x00, 0xee, # ret
## 23a
#0x80, 0xa4, # add v0, va
#0x81, 0xa5, # sub v1, va
#0x00, 0xee, # ret
## 240
#0x80, 0xa4, # add v0, va
#0x81, 0xa4, # add v1, va
#0x00, 0xee, # ret
## This was missing from the original
## Actually, never mind, it was just separately
## 246
#0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
## 250
#0x80,
## Font test
#0x60, 0x0a, # mov v0, 0x0a
#0xf1, 0x0a, # getkey v1
#0x00, 0xe0, # clearscreen
#0xf1, 0x29, # mov i, digit(v1)
#0xd0, 0x05, # draw v0, v0, 5
#0x12, 0x02, # jmp 0x202
## Base converter
## From the OSCOM Nano manual
## Modified to run several times and display input
#0x6a, 0x00, # mov va, 0
#0x6b, 0x00, # mov vb, 0
#0xf0, 0x0a, # getkey v0
#0x82, 0x00, # mov v2, v0
#0x00, 0xe0, # clearscreen
#0xf0, 0x29, # mov i, digit(v0)
#0xda, 0xb5, # draw va, vb, 5
#0x7a, 0x06, # add va, 6
#0xf1, 0x0a, # getkey v1
#0xf1, 0x29, # mov i, digit(v1)
#0xda, 0xb5, # draw va, vb, 5
#0x7b, 0x07, # add vb, 7
#0x6a, 0x00, # mov va, 0
#0x63, 0x00, # mov v3, 0
## 21c
#0x80, 0x24, # add v0, v2
#0x73, 0x01, # add v3, 1
#0x33, 0x0f, # skipeq v3, 0xf
#0x12, 0x1c, # jmp 0x21c
#0x80, 0x14, # add v0, v1
#0xA2, 0x50, # mov i, 0x250
#0xf0, 0x33, # mov [i … i+2], bcd(v0)
#0xf2, 0x65, # mov v0 … v2, [i … i+2]
#0xf0, 0x29, # mov i, digit(v0)
#0xda, 0xb5, # draw va, vb, 5
#0x7a, 0x06, # add va, 6
#0xf1, 0x29, # mov i, digit(v1)
#0xda, 0xb5, # draw va, vb, 5
#0x7a, 0x06, # add va, 6
#0xf2, 0x29, # mov i, digit(v2)
#0xda, 0xb5, # draw va, vb, 5
#0x12, 0x00, # jmp 0x200
]
#ram[0x200:len(test_program) + 0x200] = test_program
def initialize_screen():
global screen
screen = [False] * 64 * 32
def initialize_timers():
global delay_timer, sound_timer
# Tick timers down at 60Hz
delay_timer = 0
sound_timer = 0
def initialize_keyboard():
global keys_pressed, waiting_for_keypress, keypress_arrived
keys_pressed = [False]*16
waiting_for_keypress = False
keypress_arrived = False
def initialize_cpu():
global cpu_clock, data_registers, ip, stack, i_register
data_registers = [0] * 16
# According to the OSCOM Nano manual the program is loaded here
ip = 0x200
# It doesn't seem to be specified where this is stored, so put it
# in its own "address space"
stack = []
i_register = 0
# TODO: Don't hardcode the update speed
# Run the interpreter at 500Hz
# This was picked from https://github.com/AfBu/haxe-CHIP-8-emulator/wiki/(Super)CHIP-8-Secrets
cpu_clock = 500
def load_program(f):
global ram
# ram is an array of numbers, not a bytestring
program = [i for i in f.read()]
# Load at 0x200
ram[0x200: 0x200 + len(program)] = program
def main():
global window
global cpu_cycles_to_go
# Don't hardcode the size
window = pyglet.window.Window(640, 320, resizable = True)
# Hook up our screen drawing routine
@window.event
def on_draw():
# Clear the screen
window.clear()
# Draw things
draw_screen()
# Handle keyboard input
@window.event
def on_key_press(symbol, modifiers):
key_pressed(symbol)
@window.event
def on_key_release(symbol, modifiers):
key_released(symbol)
initialize_ram()
initialize_screen()
initialize_timers()
initialize_keyboard()
initialize_cpu()
with open(sys.argv[1], 'rb') as f:
load_program(f)
cpu_cycles_to_go = 0
# Start the emulation
pyglet.clock.schedule_interval(advance_interpreter, 1/60)
pyglet.app.run()
if __name__ == '__main__':
main()