Skip to content

Commit

Permalink
Limit instructions per second (#50)
Browse files Browse the repository at this point in the history
* Add tick counter for execution speed control

* Update README table of contents
  • Loading branch information
craigthomas authored Jan 16, 2025
1 parent 460ac4f commit 5e414e9
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 17 deletions.
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ An Octo compatible XO Chip, Super Chip, and Chip 8 emulator.
4. [Running](#running)
1. [Running a ROM](#running-a-rom)
2. [Screen Scale](#screen-scale)
3. [Execution Delay](#execution-delay)
3. [Instructions Per Second](#instructions-per-second)
4. [Quirks Modes](#quirks-modes)
1. [Shift Quirks](#shift-quirks)
2. [Index Quirks](#index-quirks)
Expand Down Expand Up @@ -207,18 +207,14 @@ scale is 64 x 32):
The command above will scale the window so that it is 10 times the normal
size.
### Execution Delay
### Instructions Per Second
You may also wish to experiment with the `--delay` switch, which instructs
the emulator to add a delay to every operation that is executed. For example,
The `--ticks` switch will limit the number of instructions per second that the
emulator is allowed to run. By default, the value is set to 1,000. Minimum values
are 200. Use this switch to adjust the running time of ROMs that execute too quickly.
For simplicity, each instruction is assumed to take the same amount of time.
python yac8e.py /path/to/rom/filename --delay 10
The command above will add a 10 ms delay to every opcode that is executed.
This is useful for very fast computers (note that it is difficult to find
information regarding opcode execution times, as such, I have not attempted
any fancy timing mechanisms to ensure that instructions are executed in a
set amount of time).
python yac8e.py /path/to/rom/filename --ticks 2000
### Quirks Modes
Expand Down
16 changes: 15 additions & 1 deletion chip8/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ def __init__(
jump_quirks=False,
clip_quirks=False,
logic_quirks=False,
mem_size="64K"
mem_size="64K",
max_ticks=1000,
):
"""
Initialize the Chip8 CPU. The only required parameter is a screen
Expand All @@ -98,6 +99,7 @@ def __init__(
:param clip_quirks: enables screen clipping quirks
:param logic_quirks: enables logic quirks
:param mem_size: sets the maximum memory available "4K" or "64K"
:param max_ticks: sets the maximum allowable operations per second
"""
self.last_pc = 0x0000
self.last_op = "None"
Expand All @@ -109,6 +111,11 @@ def __init__(
self.index = 0
self.rpl = [0] * NUM_REGISTERS

self.tick_counter = 0
self.max_ticks = max_ticks
if self.max_ticks < 200:
self.max_ticks = 200

self.pitch = 64
self.playback_rate = 4000
self.audio_pattern_buffer = [0] * 16
Expand Down Expand Up @@ -226,6 +233,9 @@ def execute_instruction(self, operand=None):
:param operand: the operand to execute
:return: returns the operand executed
"""
if self.tick_counter > self.max_ticks:
return None

self.last_pc = self.pc
if operand:
self.operand = operand
Expand All @@ -236,6 +246,7 @@ def execute_instruction(self, operand=None):
self.pc += 2
operation = (self.operand & 0xF000) >> 12
self.operation_lookup[operation]()
self.tick_counter += 1
return self.operand

def execute_logical_instruction(self):
Expand Down Expand Up @@ -1249,6 +1260,7 @@ def reset(self):
self.sound_playing = False
self.sound_waveform = None
self.bitplane = 1
self.tick_counter = 0

def load_rom(self, filename, offset=PROGRAM_COUNTER_START):
"""
Expand All @@ -1269,6 +1281,8 @@ def decrement_timers(self):
"""
Decrement both the sound and delay timer.
"""
self.tick_counter = 0

self.delay -= 1 if self.delay > 0 else 0
self.sound -= 1 if self.delay > 0 else 0
if self.sound > 0 and not self.sound_playing:
Expand Down
4 changes: 2 additions & 2 deletions chip8/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def main_loop(args):
:param args: the parsed command-line arguments
"""
delay_timer_event = 24
max_ticks = int(args.ticks / 60)

screen = Chip8Screen(
scale_factor=args.scale,
Expand All @@ -39,6 +40,7 @@ def main_loop(args):
clip_quirks=args.clip_quirks,
logic_quirks=args.logic_quirks,
mem_size=args.mem_size,
max_ticks=max_ticks
)
cpu.load_rom(FONT_FILE, 0)
cpu.load_rom(args.rom)
Expand All @@ -47,8 +49,6 @@ def main_loop(args):
pygame.time.set_timer(delay_timer_event, 17)

while cpu.running:
pygame.time.wait(args.op_delay)

if not cpu.awaiting_keypress:
cpu.execute_instruction()

Expand Down
5 changes: 2 additions & 3 deletions yac8e.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ def parse_arguments():
"--scale", help="the scale factor to apply to the display "
"(default is 5)", type=int, default=5, dest="scale")
parser.add_argument(
"--delay", help="sets the CPU operation to take at least "
"the specified number of milliseconds to execute (default is 1)",
type=int, default=1, dest="op_delay")
"--ticks", help="how many instructions per second are allowed",
type=int, default=1000, dest="ticks")
parser.add_argument(
"--shift_quirks", help="Enable shift quirks",
action="store_true", dest="shift_quirks"
Expand Down

0 comments on commit 5e414e9

Please sign in to comment.