Skip to content

Commit e49245f

Browse files
authored
Add Bitplane Selection (#35)
* Add bitplane selection command. * Update unit tests to cover unknown opcode exception.
1 parent 2e64371 commit e49245f

File tree

2 files changed

+116
-50
lines changed

2 files changed

+116
-50
lines changed

chip8/cpu.py

Lines changed: 96 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class UnknownOpCodeException(Exception):
3636
A class to raise unknown op code exceptions.
3737
"""
3838
def __init__(self, op_code):
39-
Exception.__init__(self, f"Unknown op-code: {op_code:X}")
39+
Exception.__init__(self, f"Unknown op-code: {op_code:04X}")
4040

4141

4242
class Chip8CPU:
@@ -94,6 +94,8 @@ def __init__(
9494
self.pitch = 64
9595
self.playback_rate = 4000
9696

97+
self.bitplane = 1
98+
9799
self.shift_quirks = shift_quirks
98100
self.index_quirks = index_quirks
99101
self.jump_quirks = jump_quirks
@@ -125,6 +127,16 @@ def __init__(
125127
0xF: self.misc_routines, # see subfunctions below
126128
}
127129

130+
self.clear_routines = {
131+
0xE0: self.clear_screen, # 00E0 - CLS
132+
0xEE: self.return_from_subroutine, # 00EE - RTS
133+
0xFB: self.scroll_right, # 00FB - SCRR
134+
0xFC: self.scroll_left, # 00FC - SCRL
135+
0xFD: self.exit, # 00FD - EXIT
136+
0xFE: self.disable_extended_mode, # 00FE - SET NORMAL
137+
0xFF: self.enable_extended_mode, # 00FF - SET EXTENDED
138+
}
139+
128140
# This set of operations is invoked when the operand loaded into the
129141
# CPU starts with 8 (e.g. operand 8nn0 would call
130142
# self.move_reg_into_reg)
@@ -144,6 +156,7 @@ def __init__(
144156
# CPU starts with F (e.g. operand Fn07 would call
145157
# self.move_delay_timer_into_reg)
146158
self.misc_routine_lookup = {
159+
0x01: self.set_bitplane, # Fn01 - BITPLANE n
147160
0x07: self.move_delay_timer_into_reg, # Ft07 - LOAD Vt, DELAY
148161
0x0A: self.wait_for_keypress, # Ft0A - KEYD Vt
149162
0x15: self.move_reg_into_delay_timer, # Fs15 - LOAD DELAY, Vs
@@ -249,50 +262,29 @@ def misc_routines(self):
249262

250263
def clear_return(self):
251264
"""
252-
Opcodes starting with a 0 are one of the following instructions:
253-
254-
0nnn - Jump to machine code function (ignored)
255-
00Cn - Scroll n pixels down
256-
00E0 - Clear the display
257-
00EE - Return from subroutine
258-
00FB - Scroll 4 pixels right
259-
00FC - Scroll 4 pixels left
260-
00FD - Exit
261-
00FE - Disable extended mode
262-
00FF - Enable extended mode
265+
Opcodes starting with a 0 usually correspond to screen clearing or scrolling
266+
routines, or emulator exit routines.
263267
"""
264268
operation = self.operand & 0x00FF
265269
sub_operation = operation & 0x00F0
266270
if sub_operation == 0x00C0:
267271
num_lines = self.operand & 0x000F
268272
self.screen.scroll_down(num_lines)
269273
self.last_op = f"Scroll Down {num_lines:01X}"
274+
else:
275+
try:
276+
self.clear_routines[operation]()
277+
except KeyError:
278+
raise UnknownOpCodeException(self.operand)
270279

271-
if operation == 0x00E0:
272-
self.screen.clear_screen()
273-
self.last_op = "CLS"
274-
275-
if operation == 0x00EE:
276-
self.return_from_subroutine()
277-
278-
if operation == 0x00FB:
279-
self.screen.scroll_right()
280-
self.last_op = "Scroll Right"
281-
282-
if operation == 0x00FC:
283-
self.screen.scroll_left()
284-
self.last_op = "Scroll Left"
285-
286-
if operation == 0x00FD:
287-
self.running = False
288-
289-
if operation == 0x00FE:
290-
self.disable_extended_mode()
291-
self.last_op = "Set Normal Mode"
280+
def clear_screen(self):
281+
"""
282+
00E0 - CLS
292283
293-
if operation == 0x00FF:
294-
self.enable_extended_mode()
295-
self.last_op = "Set Extended Mode"
284+
Clears the screen
285+
"""
286+
self.screen.clear_screen()
287+
self.last_op = "CLS"
296288

297289
def return_from_subroutine(self):
298290
"""
@@ -307,6 +299,53 @@ def return_from_subroutine(self):
307299
self.pc += self.memory[self.sp]
308300
self.last_op = "RTS"
309301

302+
def scroll_right(self):
303+
"""
304+
00FB - SCRR
305+
306+
Scrolls the screen right by 4 pixels.
307+
"""
308+
self.screen.scroll_right()
309+
self.last_op = "Scroll Right"
310+
311+
def scroll_left(self):
312+
"""
313+
00FC - SCRL
314+
315+
Scrolls the screen left by 4 pixels.
316+
"""
317+
self.screen.scroll_left()
318+
self.last_op = "Scroll Left"
319+
320+
def exit(self):
321+
"""
322+
00FD - EXIT
323+
324+
Exits the emulator.
325+
"""
326+
self.running = False
327+
self.last_op = "EXIT"
328+
329+
def disable_extended_mode(self):
330+
"""
331+
00FE - SET NORMAL
332+
333+
Disables extended mode.
334+
"""
335+
self.screen.set_normal()
336+
self.mode = MODE_NORMAL
337+
self.last_op = "Set Normal Mode"
338+
339+
def enable_extended_mode(self):
340+
"""
341+
00FF - SET EXTENDED
342+
343+
Set extended mode.
344+
"""
345+
self.screen.set_extended()
346+
self.mode = MODE_EXTENDED
347+
self.last_op = "Set Extended Mode"
348+
310349
def jump_to_address(self):
311350
"""
312351
1nnn - JUMP nnn
@@ -778,6 +817,26 @@ def draw_extended(self, x_pos, y_pos):
778817
self.v[0xF] += 1
779818
self.screen.update()
780819

820+
def set_bitplane(self):
821+
"""
822+
Fn01 - BITPLANE n
823+
824+
Selects the active bitplane for screen drawing operations. Bitplane
825+
selection is as follows:
826+
827+
0 - no bitplane selected
828+
1 - first bitplane selected
829+
2 - second bitplane selected
830+
3 - first and second bitplane selected
831+
832+
The bitplane selection values is as follows:
833+
834+
Bits: 15-12 11-8 7-4 3-0
835+
F n 0 1
836+
"""
837+
self.bitplane = (self.operand & 0x0F00) >> 8
838+
self.last_op = f"BITPLANE {self.bitplane:01X}"
839+
781840
def move_delay_timer_into_reg(self):
782841
"""
783842
Fx07 - LOAD Vx, DELAY
@@ -1031,6 +1090,7 @@ def reset(self):
10311090
self.rpl = [0] * NUM_REGISTERS
10321091
self.pitch = 64
10331092
self.playback_rate = 4000
1093+
self.bitplane = 1
10341094

10351095
def load_rom(self, filename, offset=PROGRAM_COUNTER_START):
10361096
"""
@@ -1054,18 +1114,5 @@ def decrement_timers(self):
10541114
self.delay -= 1 if self.delay > 0 else 0
10551115
self.sound -= 1 if self.delay > 0 else 0
10561116

1057-
def enable_extended_mode(self):
1058-
"""
1059-
Set extended mode.
1060-
"""
1061-
self.screen.set_extended()
1062-
self.mode = MODE_EXTENDED
1063-
1064-
def disable_extended_mode(self):
1065-
"""
1066-
Disables extended mode.
1067-
"""
1068-
self.screen.set_normal()
1069-
self.mode = MODE_NORMAL
10701117

10711118
# E N D O F F I L E ########################################################

test/test_chip8cpu.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ def setUp(self):
3434
self.cpu = Chip8CPU(self.screen)
3535
self.cpu_spy = mock.Mock(wraps=self.cpu)
3636

37+
def test_clear_return_with_unknown_opcode(self):
38+
with self.assertRaises(UnknownOpCodeException) as context:
39+
self.cpu.execute_instruction(operand=0x00FA)
40+
self.assertEqual("Unknown op-code: 00FA", str(context.exception))
41+
42+
def test_bitplane_1_init(self):
43+
self.assertEqual(1, self.cpu.bitplane)
44+
45+
def test_set_bitplane(self):
46+
self.cpu.operand = 0xF201
47+
self.cpu.set_bitplane()
48+
self.assertEqual(2, self.cpu.bitplane)
49+
50+
def test_set_bitplane_integration(self):
51+
self.cpu.memory[0x0200] = 0xF2
52+
self.cpu.memory[0x0201] = 0x01
53+
self.cpu.execute_instruction()
54+
self.assertEqual(2, self.cpu.bitplane)
55+
3756
def test_memory_size_default_64k(self):
3857
self.assertEqual(65536, len(self.cpu.memory))
3958

@@ -733,7 +752,7 @@ def test_misc_routines_raises_exception_on_unknown_op_codes(self):
733752
self.cpu.operand = 0x0
734753
with self.assertRaises(UnknownOpCodeException) as context:
735754
self.cpu.misc_routines()
736-
self.assertEqual("Unknown op-code: 0", str(context.exception))
755+
self.assertEqual("Unknown op-code: 0000", str(context.exception))
737756

738757
def test_scroll_down_called(self):
739758
self.cpu.operand = 0x00C4

0 commit comments

Comments
 (0)