How do I make my chip-8 emulator update at the intended rate?

Godot Version

4.3 Stable

Question

I’m making an emulator/interpreter for an old console called the chip-8 In Godot 4.3 stable, It’s going really good so far, I basically just load an external ROM into a virtual RAM (8BitPackedArray) and execute it’s each operations code like this for example

0x3000: # 1st nibble is '3':
		# 3XNN: Skips the next instruction if VX equals NN.
		if registers[opcode >> 8 & 0x0F] == opcode & 0x00FF:
			program_counter += 2

My current issue is that I wanna run the emulator at the same speed as the OG console, I first tried having a for loop in the _ready() function that executes all the opcodes, But it ended up running everything before the game even starts.

So next I tried executing the opcodes in the _process() function, And it worked but it was very slow compared to how the fast the console is meant to run, So I wanted to increase how many times _process() would run every second but I couldn’t figure out how, So I came up with a funny solution; have a for loop run 10 times in _process(), This should make _process() run 10 times faster, And yeah it worked but this solution seems very dangerous ? I mean How is this gonna run consistently ? Is it gonna run at the same rate on my PC and also stronger/weaker PCs ? And also when there’s movement happening the frame rate dropped to 45 ?

func _process(delta: float) -> void:
		for i: int in range(10):
			execute_opcode()
			program_counter += 2
		queue_redraw()

So next I tried using _physics_process(), It’s meant to run more consistently and I managed to change how often it’s run by increasing the “physics tics per second” option, I tried increasing from 60 to 1000 but my game is still not running fast enough.

# Even if 'physics tics per second' is 1000: It's still too slow.
func _physics_process(delta: float) -> void:
		execute_opcode()
		program_counter += 2
		queue_redraw()

Next I tried returning the “physics tics per second” back to 60, But I added a for loop that would run 5 times, But it resulted in the game being very choppy and slow:

# Very choppy and slow
func _physics_process(delta: float) -> void:
	for i: int in range(5):
		execute_opcode()
		program_counter += 2
	queue_redraw()

So yeah I guess I’m confused and don’t know how to solve this issue.

Hi,

I’ve written a complete programable retro computer system in Godot and after going down the same path as you I moved to the following:

Create a while loop and yield time back to the system at set intervals, you can control the yield frequency without locking up the entire system with no trouble.

func execution_scheduler() -> void:
	runable = true # start the engine
	while runable: # and is_inside_tree(): # execution loop / runable flag
		if yield_frequency < yield_max: # Engine Performace Register
			if not get_tree().paused:
				execute() # executes a single instruction
			yield_frequency += 1
			continue

		screen_refresh(false) # Refresh the screen
		await get_tree().process_frame # ALERT Never remove from here
		yield_frequency = 0

	if is_inside_tree(): get_tree().paused = false # Just to make sure
	screen_refresh(false) # Final screen refresh
	execution_stopped() # Progam finished

Call your execute methods from within this loop. The yield_frequency controls the emulation speed. And the await process frame yields time back to the system.

For my purposes I get about 1.5 million emulated instructions per-second so more than enough for a retro computer. In fact I never run the system at max speed which is about 30000 instruction between yield phases.

At some point in your emulated program you set runnable to false to exit.

Kindly

Ryn

2 Likes

Hi.
_process and _physics_process or any timer is heavily dependent on engine progression of the scene tree, this doesnt seem to b a good choice. An independent thread should be running on full speed all the time independent from the main timing. You can than use the time get_ticks_usec() function to idle (while loop) the code operation.
Also a thread should not interfere with the scene tree timing, blocking graphics update.

Don’t know if this would work but that would be my thoughts to this topic.

1 Like

Thank you so much for taking the time to answer my question, I’m gonna try using your approach later today, I’ll keep you updated.

Thanks a lot for the reply ! Yeah I figured that we probably need our own independent thread, I’m gonna look into get_ticks_usec() later today and I’ll keep you updated.

No problem.

It’s a surprisingly easy method. I tried threads as suggested but found that Godot lacks the granular thread control to make this work easily and you’d also need to allow for thread safe calls to update the screen and controls which can be troublesome.

Kindly

Ryn

1 Like

This is where mutexes and semaphores come into play to make it threadsafe. But as a rule of thumb you should not use references, better use copies of data that get send by a signal to not run into accessing live processed data.

1 Like

Hi

Thanks, yes, I was aware of that and I did go down that route initially and it worked fine but I found the yielded loop gave better, easier, and more consistent performance for emulating a Retro system.

I’m wasn’t trying to build a super computer so separating the engine onto a seperate thread really wasn’t needed. Instead I separated all the sections into seperate scenes so the runtime engine scene only does that one thing.

Kindly

Ryn

Someone on stack exchange game me a very similar solution to yours but their code was simpler so I used it instead.

var time_accumulator = 0.0

func _process(delta: float) -> void:
    time_accumulator += delta
    while time_accumulator >= (1.0 / instruction_rate):
        execute_opcode()
        program_counter += 2  # Each Chip-8 instruction is 2 bytes
        time_accumulator -= (1.0 / instruction_rate)
    
    queue_redraw()

Anyway thanks a ton for the help.

1 Like

Glad you found a solution.

Kindly

Ryn