Skip to content

Commit d73204c

Browse files
authored
Add bitplane tests. (#40)
Fix problems with incorrect opcode for register subset store / read. Fix problem with scrolling left on bitplane 3. Update README to include new colors. Add unit tests to cover bitplane 0 edge cases.
1 parent bed04f1 commit d73204c

File tree

6 files changed

+566
-202
lines changed

6 files changed

+566
-202
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ Color values are specified by using HTML hex values such as `AABBCC` without the
314314
leading `#`. There are currently 4 color values that can be set:
315315
316316
* `--color_0` specifies the background color. This defaults to `000000`.
317-
* `--color_1` specifies bitplane 1 color. This defaults to `666666`.
318-
* `--color_2` specifies bitplane 2 color. This defaults to `BBBBBB`.
317+
* `--color_1` specifies bitplane 1 color. This defaults to `FF33CC`.
318+
* `--color_2` specifies bitplane 2 color. This defaults to `33CCFF`.
319319
* `--color_3` specifies bitplane 1 and 2 overlap color. This defaults to `FFFFFF`.
320320
321321
For Chip8 and SuperChip 8 programs, only the background color `color_0` (for pixels

chip8/cpu.py

Lines changed: 95 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def __init__(
114114
0x2: self.jump_to_subroutine, # 2nnn - CALL nnn
115115
0x3: self.skip_if_reg_equal_val, # 3snn - SKE Vs, nn
116116
0x4: self.skip_if_reg_not_equal_val, # 4snn - SKNE Vs, nn
117-
0x5: self.skip_if_reg_equal_reg, # 5st0 - SKE Vs, Vt
117+
0x5: self.save_skip_routines, # see subfunctions below
118118
0x6: self.move_value_to_reg, # 6snn - LOAD Vs, nn
119119
0x7: self.add_value_to_reg, # 7snn - ADD Vs, nn
120120
0x8: self.execute_logical_instruction, # see subfunctions below
@@ -127,6 +127,12 @@ def __init__(
127127
0xF: self.misc_routines, # see subfunctions below
128128
}
129129

130+
self.save_skip_lookup = {
131+
0x0: self.skip_if_reg_equal_reg, # 5xy0 - SKE Vx, Vy
132+
0x2: self.store_subset_regs_in_memory, # 5xy2 - STORSUB x, y
133+
0x3: self.read_subset_regs_in_memory, # 5xy3 - LOADSUB x, y
134+
}
135+
130136
self.clear_routines = {
131137
0xE0: self.clear_screen, # 00E0 - CLS
132138
0xEE: self.return_from_subroutine, # 00EE - RTS
@@ -257,21 +263,25 @@ def keyboard_routines(self):
257263
if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00:
258264
self.pc += 2
259265

266+
def save_skip_routines(self):
267+
"""
268+
Will execute either a register save or skip routine.
269+
"""
270+
operation = self.operand & 0x000F
271+
try:
272+
self.save_skip_lookup[operation]()
273+
except KeyError:
274+
raise UnknownOpCodeException(self.operand)
275+
260276
def misc_routines(self):
261277
"""
262278
Will execute one of the routines specified in misc_routines.
263279
"""
264280
operation = self.operand & 0x00FF
265-
sub_operation = self.operand & 0x000F
266-
if sub_operation == 0x2:
267-
self.store_subset_regs_in_memory()
268-
elif sub_operation == 0x3:
269-
self.read_subset_regs_in_memory()
270-
else:
271-
try:
272-
self.misc_routine_lookup[operation]()
273-
except KeyError:
274-
raise UnknownOpCodeException(self.operand)
281+
try:
282+
self.misc_routine_lookup[operation]()
283+
except KeyError:
284+
raise UnknownOpCodeException(self.operand)
275285

276286
def clear_return(self):
277287
"""
@@ -282,7 +292,7 @@ def clear_return(self):
282292
sub_operation = operation & 0x00F0
283293
if sub_operation == 0x00C0:
284294
num_lines = self.operand & 0x000F
285-
self.screen.scroll_down(num_lines)
295+
self.screen.scroll_down(num_lines, self.bitplane)
286296
self.last_op = f"Scroll Down {num_lines:01X}"
287297
else:
288298
try:
@@ -296,7 +306,7 @@ def clear_screen(self):
296306
297307
Clears the screen
298308
"""
299-
self.screen.clear_screen()
309+
self.screen.clear_screen(self.bitplane)
300310
self.last_op = "CLS"
301311

302312
def return_from_subroutine(self):
@@ -318,7 +328,7 @@ def scroll_right(self):
318328
319329
Scrolls the screen right by 4 pixels.
320330
"""
321-
self.screen.scroll_right()
331+
self.screen.scroll_right(self.bitplane)
322332
self.last_op = "Scroll Right"
323333

324334
def scroll_left(self):
@@ -327,7 +337,7 @@ def scroll_left(self):
327337
328338
Scrolls the screen left by 4 pixels.
329339
"""
330-
self.screen.scroll_left()
340+
self.screen.scroll_left(self.bitplane)
331341
self.last_op = "Scroll Left"
332342

333343
def exit(self):
@@ -450,6 +460,58 @@ def skip_if_reg_equal_reg(self):
450460
self.pc += 2
451461
self.last_op = f"SKE V{x:01X}, V{y:01X}"
452462

463+
def store_subset_regs_in_memory(self):
464+
"""
465+
5xy2 - STORSUB [I], Vx, Vy
466+
467+
Store a subset of registers from x to y in memory starting at index.
468+
The x and y calculation is as follows:
469+
470+
Bits: 15-12 11-8 7-4 3-0
471+
F x y 2
472+
473+
If x is larger than y, then they will be stored in reverse order.
474+
"""
475+
x = (self.operand & 0x0F00) >> 8
476+
y = (self.operand & 0x00F0) >> 4
477+
pointer = 0
478+
if y >= x:
479+
for z in range(x, y+1):
480+
self.memory[self.index + pointer] = self.v[z]
481+
pointer += 1
482+
else:
483+
for z in range(x, y-1, -1):
484+
self.memory[self.index + pointer] = self.v[z]
485+
pointer += 1
486+
487+
self.last_op = f"STORSUB [I], {x:01X}, {y:01X}"
488+
489+
def read_subset_regs_in_memory(self):
490+
"""
491+
5xy3 - LOADSUB [I], Vx, Vy
492+
493+
Load a subset of registers from x to y in memory starting at index.
494+
The x and y calculation is as follows:
495+
496+
Bits: 15-12 11-8 7-4 3-0
497+
F x y 2
498+
499+
If x is larger than y, then they will be loaded in reverse order.
500+
"""
501+
x = (self.operand & 0x0F00) >> 8
502+
y = (self.operand & 0x00F0) >> 4
503+
pointer = 0
504+
if y >= x:
505+
for z in range(x, y+1):
506+
self.v[z] = self.memory[self.index + pointer]
507+
pointer += 1
508+
else:
509+
for z in range(x, y-1, -1):
510+
self.v[z] = self.memory[self.index + pointer]
511+
pointer += 1
512+
513+
self.last_op = f"LOADSUB [I], {x:01X}, {y:01X}"
514+
453515
def move_value_to_reg(self):
454516
"""
455517
6xnn - LOAD Vx, nn
@@ -781,19 +843,28 @@ def draw_sprite(self):
781843
self.v[0xF] = 0
782844

783845
if num_bytes == 0:
784-
self.draw_extended(x_pos, y_pos)
846+
if self.bitplane == 3:
847+
self.draw_extended(x_pos, y_pos, 1)
848+
self.draw_extended(x_pos, y_pos, 2)
849+
else:
850+
self.draw_extended(x_pos, y_pos, self.bitplane)
785851
self.last_op = f"DRAWEX"
786852
else:
787-
self.draw_normal(x_pos, y_pos, num_bytes)
853+
if self.bitplane == 3:
854+
self.draw_normal(x_pos, y_pos, num_bytes, 1)
855+
self.draw_normal(x_pos, y_pos, num_bytes, 2)
856+
else:
857+
self.draw_normal(x_pos, y_pos, num_bytes, self.bitplane)
788858
self.last_op = f"DRAW V{x_source:01X}, V{y_source:01X}"
789859

790-
def draw_normal(self, x_pos, y_pos, num_bytes):
860+
def draw_normal(self, x_pos, y_pos, num_bytes, bitplane):
791861
"""
792862
Draws a sprite on the screen while in NORMAL mode.
793863
794864
:param x_pos: the X position of the sprite
795865
:param y_pos: the Y position of the sprite
796866
:param num_bytes: the number of bytes to draw
867+
:param bitplane: the bitplane to draw to
797868
"""
798869
for y_index in range(num_bytes):
799870
color_byte = self.memory[self.index + y_index]
@@ -806,13 +877,13 @@ def draw_normal(self, x_pos, y_pos, num_bytes):
806877
if not self.clip_quirks or (self.clip_quirks and x_coord < self.screen.get_width()):
807878
x_coord = x_coord % self.screen.get_width()
808879
turned_on = (color_byte & mask) > 0
809-
current_on = self.screen.get_pixel(x_coord, y_coord)
880+
current_on = self.screen.get_pixel(x_coord, y_coord, bitplane)
810881
self.v[0xF] |= 1 if turned_on and current_on else 0
811-
self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on)
882+
self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on, bitplane)
812883
mask = mask >> 1
813884
self.screen.update()
814885

815-
def draw_extended(self, x_pos, y_pos):
886+
def draw_extended(self, x_pos, y_pos, bitplane):
816887
"""
817888
Draws a sprite on the screen while in EXTENDED mode. Sprites in this
818889
mode are assumed to be 16x16 pixels. This means that two bytes will
@@ -821,6 +892,7 @@ def draw_extended(self, x_pos, y_pos):
821892
822893
:param x_pos: the X position of the sprite
823894
:param y_pos: the Y position of the sprite
895+
:param bitplane: the bitplane to draw to
824896
"""
825897
for y_index in range(16):
826898
for x_byte in range(2):
@@ -834,9 +906,9 @@ def draw_extended(self, x_pos, y_pos):
834906
if not self.clip_quirks or (self.clip_quirks and x_coord < self.screen.get_width()):
835907
x_coord = x_coord % self.screen.get_width()
836908
turned_on = (color_byte & mask) > 0
837-
current_on = self.screen.get_pixel(x_coord, y_coord)
909+
current_on = self.screen.get_pixel(x_coord, y_coord, bitplane)
838910
self.v[0xF] += 1 if turned_on and current_on else 0
839-
self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on)
911+
self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on, bitplane)
840912
mask = mask >> 1
841913
else:
842914
self.v[0xF] += 1
@@ -873,58 +945,6 @@ def set_bitplane(self):
873945
self.bitplane = (self.operand & 0x0F00) >> 8
874946
self.last_op = f"BITPLANE {self.bitplane:01X}"
875947

876-
def store_subset_regs_in_memory(self):
877-
"""
878-
Fxy2 - STORSUB [I], Vx, Vy
879-
880-
Store a subset of registers from x to y in memory starting at index.
881-
The x and y calculation is as follows:
882-
883-
Bits: 15-12 11-8 7-4 3-0
884-
F x y 2
885-
886-
If x is larger than y, then they will be stored in reverse order.
887-
"""
888-
x = (self.operand & 0x0F00) >> 8
889-
y = (self.operand & 0x00F0) >> 4
890-
pointer = 0
891-
if y >= x:
892-
for z in range(x, y+1):
893-
self.memory[self.index + pointer] = self.v[z]
894-
pointer += 1
895-
else:
896-
for z in range(x, y-1, -1):
897-
self.memory[self.index + pointer] = self.v[z]
898-
pointer += 1
899-
900-
self.last_op = f"STORSUB [I], {x:01X}, {y:01X}"
901-
902-
def read_subset_regs_in_memory(self):
903-
"""
904-
Fxy3 - LOADSUB [I], Vx, Vy
905-
906-
Load a subset of registers from x to y in memory starting at index.
907-
The x and y calculation is as follows:
908-
909-
Bits: 15-12 11-8 7-4 3-0
910-
F x y 2
911-
912-
If x is larger than y, then they will be loaded in reverse order.
913-
"""
914-
x = (self.operand & 0x0F00) >> 8
915-
y = (self.operand & 0x00F0) >> 4
916-
pointer = 0
917-
if y >= x:
918-
for z in range(x, y+1):
919-
self.v[z] = self.memory[self.index + pointer]
920-
pointer += 1
921-
else:
922-
for z in range(x, y-1, -1):
923-
self.v[z] = self.memory[self.index + pointer]
924-
pointer += 1
925-
926-
self.last_op = f"LOADSUB [I], {x:01X}, {y:01X}"
927-
928948
def move_delay_timer_into_reg(self):
929949
"""
930950
Fx07 - LOAD Vx, DELAY

0 commit comments

Comments
 (0)