Skip to content

Commit

Permalink
added true C64 emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
irmen committed Sep 21, 2019
1 parent 7af0356 commit 66588e0
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 104 deletions.
28 changes: 19 additions & 9 deletions README.md
Expand Up @@ -5,7 +5,7 @@
... Or is it? ... Or is it?


Well, it emulates the character mode *with sprites* quite well and thus you can display pretty PETSCII pictures if you want. Well, it emulates the character mode *with sprites* quite well and thus you can display pretty PETSCII pictures if you want.
It's generally not possible to run actual C-64 software, but that's not the goal It's generally not possible to run actual C-64 software, but that's not the real goal
of this project. (Look at actual c-64 emulators such as Vice or CCS64 if you want that) of this project. (Look at actual c-64 emulators such as Vice or CCS64 if you want that)




Expand All @@ -19,15 +19,22 @@ you can only do the same things with it as what is supported
for the BASIC mode. So no fancy screen scrolling or rasterbars... for the BASIC mode. So no fancy screen scrolling or rasterbars...




## can load and use real C64 ROMs ## Can run the real BASIC and KERNAL roms! True C64 emulation!


If basic/kernal/chargen ROM files are supplied in the roms folder, If basic/kernal/chargen ROM files are supplied in the roms folder,
the simulator will load these and make them part of the memory of the machine as ROM areas. the simulator will load these and make them part of the memory of the machine as ROM areas.
You can actually call machine code routines from the roms! If you run the ``startreal64`` program, it will load the ``realemulator`` module
Such as the well-known reset routine, SYS 64738 . instead of the simulated one, and will *actually run the C64 BASIC ROM*.
The simulator will use this routine as well when the machine is reset!
(just a few slight kernel patches are made to make it compatible and to avoid ending You can type anything in BASIC that you could type on a real C64 too!
up in the actual ROM BASIC program loop - that doesn't work for now)
Unfortunately, the I/O is not emulated so it is not possible to load or save
your programs in this mode.

On my machine the current code runs at around 0.6 Mhz in regular Python
(around half the speed of a real c64),
and between 2 and 3 Mhz in Pypy (there is a lot more speed than this obtainable
with pypy, but that makes the c64's basic mode more or less unusable)




## BASIC V2 and the VIC-registers (video chip) ## BASIC V2 and the VIC-registers (video chip)
Expand Down Expand Up @@ -106,9 +113,12 @@ are included on the 'virtual disk' in this project)


PETSCII image: PETSCII image:


![Screenshot two](demo_screenshot2.png) ![Screenshot](demo_screenshot2.png)


Python mode: Python mode:


![Screenshot two](demo_screenshot3.png) ![Screenshot](demo_screenshot3.png)

Real emulation of a C64:


![Screenshot](demo_screenshot4.png)
Binary file added demo_screenshot4.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 29 additions & 11 deletions pyc64/emulator.py
Expand Up @@ -14,6 +14,7 @@
import tkinter import tkinter
import pkgutil import pkgutil
import threading import threading
import time
import queue import queue
import time import time
from collections import deque from collections import deque
Expand Down Expand Up @@ -127,8 +128,9 @@ def __init__(self, screen, title, roms_directory):
self.canvas.tag_raise(self.border3) self.canvas.tag_raise(self.border3)
self.border4 = self.canvas.create_rectangle(*b4, outline="", fill="#000") self.border4 = self.canvas.create_rectangle(*b4, outline="", fill="#000")
self.canvas.tag_raise(self.border4) self.canvas.tag_raise(self.border4)
self.bind("<KeyPress>", self.keypress) # self.bind("<KeyPress>", self.keypress)
self.bind("<KeyRelease>", self.keyrelease) self.bind("<KeyRelease>", self.keyrelease)
self.bind("<Key>", self.keypress)
self.canvas.pack() self.canvas.pack()


def start(self): def start(self):
Expand Down Expand Up @@ -234,6 +236,7 @@ def repaint(self):
if configure: if configure:
# reconfigure all changed properties in one go # reconfigure all changed properties in one go
self.canvas.itemconfigure(self.spritebitmaps[snum], **configure) self.canvas.itemconfigure(self.spritebitmaps[snum], **configure)
self.update_idletasks()
self.refreshtick.set() self.refreshtick.set()


def smoothscroll(self, xs, ys): def smoothscroll(self, xs, ys):
Expand Down Expand Up @@ -319,17 +322,20 @@ class C64EmulatorWindow(EmulatorWindowBase):
"use 'gopy' to enter Python mode\n\n\n\n" \ "use 'gopy' to enter Python mode\n\n\n\n" \
"(install the py64 library to be able to execute 6502 machine code)" "(install the py64 library to be able to execute 6502 machine code)"


def __init__(self, screen, title, roms_directory): def __init__(self, screen, title, roms_directory, run_real_roms):
super().__init__(screen, title, roms_directory) super().__init__(screen, title, roms_directory)
self.screen.memory[0x00fb] = EmulatorWindowBase.update_rate self.screen.memory[0x00fb] = EmulatorWindowBase.update_rate
self.hertztick = threading.Event() self.hertztick = threading.Event()
self.interpret_thread = None self.interpret_thread = None
self.interpreter = None self.interpreter = None
self.real_cpu_running = None
self.run_real_roms = run_real_roms


def start(self): def start(self):
super().start() super().start()
self._cyclic_herztick() if not self.run_real_roms:
self._cyclic_blink_cursor() self._cyclic_herztick()
self._cyclic_blink_cursor()
self.reset_machine() self.reset_machine()


def _cyclic_herztick(self): def _cyclic_herztick(self):
Expand Down Expand Up @@ -618,11 +624,19 @@ def _border_positions(self):
def reset_machine(self): def reset_machine(self):
super().reset_machine() super().reset_machine()
self.screen.memory[0x00fb] = EmulatorWindowBase.update_rate self.screen.memory[0x00fb] = EmulatorWindowBase.update_rate
self.switch_interpreter("basic") if not self.run_real_roms:
self.switch_interpreter("basic")
if self.screen.using_roms: if self.screen.using_roms:
print("using actual ROM reset routine (sys 64738)") reset = self.screen.memory.getword(0xfffc)
do_sys(self.screen, 64738, self.interpret_thread._microsleep, use_rom_routines=True) print("using actual ROM reset routine at", reset)
self.interpreter.write_prompt("\n\n\n\n\n") if self.run_real_roms:
if self.real_cpu_running is None:
threading.Thread(target=self.run_rom_code, args=(reset,), daemon=True).start()
else:
self.real_cpu_running.reset()
else:
do_sys(self.screen, reset, self.interpret_thread._microsleep, use_rom_routines=True)
self.interpreter.write_prompt("\n\n\n\n\n")




class InterpretThread(threading.Thread): class InterpretThread(threading.Thread):
Expand Down Expand Up @@ -748,13 +762,17 @@ def runstop(self):
self.keybuffer.clear() self.keybuffer.clear()




def start(): def start(run_real_roms):
rom_directory = "roms" rom_directory = "roms"
screen = ScreenAndMemory(columns=C64EmulatorWindow.columns, screen = ScreenAndMemory(columns=C64EmulatorWindow.columns,
rows=C64EmulatorWindow.rows, rows=C64EmulatorWindow.rows,
sprites=C64EmulatorWindow.sprites, sprites=C64EmulatorWindow.sprites,
rom_directory=rom_directory) rom_directory=rom_directory,
emu = C64EmulatorWindow(screen, "Commodore-64 simulator in pure Python!", rom_directory) run_real_roms=run_real_roms)
if run_real_roms:
emu = RealC64EmulatorWindow(screen, "Commodore-64 emulator in pure Python! - running actual roms", rom_directory)
else:
emu = C64EmulatorWindow(screen, "Commodore-64 simulator in pure Python!", rom_directory, False)
emu.start() emu.start()
emu.mainloop() emu.mainloop()


Expand Down

0 comments on commit 66588e0

Please sign in to comment.