from argparse import ArgumentParser
from os.path import exists as f_exists
from os import remove as f_remove
# Adressmodi
IMP = 0 # Implied: z.B. BRK, INX
ACC = 1 # Accumulator: z.B. ASL A
IMM = 2 # Immediate: z.B. LDA #$A7
ZP = 3 # Zeropage: z.B. STA $60
ZPX = 4 # Zeropage X: z.B. LDA $60,x
ABS = 5 # Absolute: z.B. LDA $C000
ABSX = 6 # Absolute X: z.B. LDA $6000,x
ABSY = 7 # Absolute Y: z.B. STA $8000,y
INDY = 8 # Indirect Y: z.B. LDA ($61),Y
IND = 9 # Indirect: z.B. JMP ($8000)
REL = 10 # Relative: z.B. BNE
def hexfmt4(val):
""" 4bit-Zahl mit hex(val) umwandeln und das Ergebnis wie
folgt anpassen:
'0x5' -> '5'
"""
if val > 15:
raise SystemExit("hexfmt4 overflow: %d" % val)
s = hex(val)
s = s[2:]
return s.upper()
def binfmt8(val):
""" Zahl mit bin(val) umwandeln und das Ergebnis wie folgt
anpassen:
'0b1' -> '0000 0001'
'0x10000' -> '0001 0000'
"""
if val > 255:
raise SystemExit("binfmt8 overflow: %d" % val)
s = bin(val)
s = s[2:]
x = len(s)
while x < 8:
s = '0' + s
x += 1
s = s[:4] + ' ' + s[4:]
return s
def _comp_set_flags(self, reg, comp):
""" Setzt die Flags fuer die Vergleichsroutinen
Der Parameter reg enthhaelt den Wert des Registers,
comp den Vergleichswert (direkt bzw. aus dem Speicher).
"""
self.flag_z = (reg == comp)
self.flag_c = (reg >= comp)
self.flag_n = (reg < comp)
# HILFSROUTINE FUeR VERZWEIGUNGEN
def _branch_rel(self):
""" Fuehrt einen relativen Sprung aus """
branch = self.mem[self.pc]
# Zweier-Komplement bei Rueckwaerts-Spruengen
# in negative Zahl umwandeln
if branch > 127:
branch = branch ^ 255 # xor
branch += 1
branch = - branch
# Sprung gilt ab der Adresse des Folgebefehls
# (daher noch +1)
self.pc = self.pc + 1 + branch
# HILFSROUTINEN FUeR ADDITION UND SUBSTRAKTION
def _add(self, val):
s = self.reg_a + val
if self.flag_c:
s += 1
# Test auf Ueberlauf
self.flag_c = (s != s & 255)
self.reg_a = s & 255
self._a_set_nz_flags()
def _sub(self, val):
self.reg_a = self.reg_a - val
# Wenn das Carray-Flag vor der Substraktion nicht
# gesetzt wurde, wird noch der Wert 1 abgezogen
if not self.flag_c:
self.reg_a -= 1
# Carry-Flag setzen (bei positivem Resultat)
self.flag_c = (self.reg_a >= 0)
# Bei negativem Resultat Zweier-Komplement bilden
if self.reg_a < 0:
self.reg_a = abs(self.reg_a)
self.reg_a = self.reg_a ^ 255 # xor
self.reg_a += 1
self._a_set_nz_flags()
pass
# HILFSROUTINEN ZUM SETZEN STACKPOINTER
def _dec_sp(self):
self.sp -= 1
# Test auf Ueberlauf
if self.sp < 0:
self.sp = 0xff
def _inc_sp(self):
self.sp += 1
# Test auf Ueberlauf
if self.sp > 0xff:
self.sp = 0
def asl(self):
""" ASL A """
self.reg_a = self.reg_a << 1
# Test auf Ueberlauf
self.flag_c = (self.reg_a != self.reg_a & 255)
self.reg_a = self.reg_a & 255
self._a_set_nz_flags()
def asl_zp(self):
""" ASL aa """
adr = self.mem[self.pc]
res = self.mem[adr] << 1
# Test auf Ueberlauf
self.flag_c = (res != res & 255)
self.mem[adr] = res & 255
self._mem_set_nz_flags(adr)
def bcc(self):
""" BCC aa """
if self.flag_c == 0:
self._branch_rel()
else:
self.pc += 1
def bcs(self):
""" BCS aa """
if self.flag_c == 1:
self._branch_rel()
else:
self.pc += 1
def beq(self):
""" BEQ aa """
if self.flag_z == 1:
self._branch_rel()
else:
self.pc += 1
def bne(self):
""" BNE aa """
if self.flag_z == 0:
self._branch_rel()
else:
self.pc += 1
def bpl(self):
""" BPL aa """
if self.flag_n == 0:
self._branch_rel()
else:
self.pc += 1
def jsr_abs(self):
""" JSR aaaa """
adr = self.mem[self.pc+1] << 8 | self.mem[self.pc]
if not adr in self.jsr_alt:
# Programmzaehler+2 als Ruecksprungadresse auf den
# Stack legen (PC wurde schon um eins erhoeht,
# daher nur noch +1)
self.mem[self.stack + self.sp] = (self.pc+1) >> 8
self._dec_sp()
self.mem[self.stack + self.sp] = (self.pc+1) & 0xff
self._dec_sp()
self.pc = adr
else:
# Alternativen Handler aufrufen (Programmzaehler wurde
# schon um eins erhoeht, daher nur noch +2)
self.jsr_alt[adr]()
self.pc += 2
def rol(self):
""" ROL A """
self.reg_a = self.reg_a << 1
# Carry-Flag ins Bit 0 uebertragen
self.reg_a = self.reg_a | self.flag_c
# Test auf Ueberlauf
self.flag_c = (self.reg_a != self.reg_a & 255)
self.reg_a = self.reg_a & 255
self._a_set_nz_flags()
def rol_zp(self):
""" ROL aa """
adr = self.mem[self.pc]
res = self.mem[adr] << 1
# Carry-Flag ins Bit 0 uebertragen
res = res | self.flag_c
# Test auf Ueberlauf
self.flag_c = (res != res & 255)
self.mem[adr] = res & 255
self._mem_set_nz_flags(adr)
def ror(self):
""" ROR A """
self.reg_a = self.reg_a & 255
# Carry-Flag merken (fuer unten)
cs = self.flag_c
# Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
self.flag_c = (self.reg_a != self.reg_a & 254)
self.reg_a = self.reg_a >> 1
# Carry, falls es gesetzt war, ins Bit 7 uebertragen
if cs:
self.reg_a = self.reg_a | 128
self._a_set_nz_flags()
def ror_zp(self):
""" ROR aa """
adr = self.mem[self.pc]
val = self.mem[adr]
# Carry-Flag merken (fuer unten)
cs = self.flag_c
# Test auf vorhersehbaren Ueberlauf (Bit 0 gesetzt?)
self.flag_c = (val != val & 254)
self.mem[adr] = val >> 1
# Carry, falls es gesetzt war, ins Bit 7 uebertragen
if cs:
self.mem[adr] = self.mem[adr] | 128
self._mem_set_nz_flags(adr)
def load(self, filename):
""" Laedt das Programm an die angegebene Adresse, die
in den ersten beiden Bytes steht (cbm format),
und setzt den Start und das Ende fuer das Programm
"""
f = open(filename, 'rb')
data = f.read()
f.close()
# Startadresse (die ersten zwei Bytes) laden
slow = data[0]
shigh = data[1]
self.start = shigh << 8 | slow
# restliches Programm laden
idx = self.start
for b in data[2:]:
self.mem[idx] = b
idx += 1
self.end = idx - 1
def loadsym(self, filename):
f = open(filename, 'r')
data = f.readlines()
f.close()
for line in data:
parts = line.split(chr(9)) # tab sep
# [TAB]vscroll[TAB]= $c1b0[TAB]; ?
# -> ['', 'vscroll', '= $c1b0', '; ?']
adr = int(parts[2][3:],16)
label = parts[1]
self.addresses[adr] = label
self.labels[label] = adr
def disass(self):
""" Gibt das geladene Programm in Assembler aus """
adr = self.start
while adr <= self.end:
if (len(self.addresses) > 0) and (adr in self.addresses):
print("%s:" % self.addresses[adr])
try:
cmd = self.opcodes[self.mem[adr]]
except KeyError:
cmd = Opcode(None, '???', IMP, 0, False, None)
argl = self.mem[adr+1]
argh = self.mem[adr+2]
print(hexfmt16(adr >> 8, adr & 0xff), end='')
print(" ", end='')
arg_txt = ""
if cmd.amode in [IMM, ZP, ZPX, INDY]:
arg_txt = hexfmt8(argl)
elif cmd.amode in [ABS, ABSX, ABSY, IND]:
arg_txt = "%s%s" % (hexfmt8(argh), hexfmt8(argl))
elif cmd.amode == REL:
# Zweier-Komplement bei Rueckwaerts-Spruengen
# in negative Zahl umwandeln
if argl > 127:
branch = argl ^ 255 # xor
branch += 1
dest = adr + 2 - branch
else:
dest = adr + 2 + argl
arg_txt = hexfmt16(dest >> 8, dest & 0xff)
print(cmd.to_str(arg_txt), end='')
if (len(self.addresses) > 0) and (cmd.amode in [ZP, ABS, ABSX, ABSY, IND]): # (cmd.opc in [0x20, 0x4c])
try:
if cmd.amode == ZP: # spaeter: in [ZP, ZPX, ...]
label = self.addresses[argl]
else:
label = self.addresses[argh << 8 | argl]
print(" ; %s" % label, end='')
except KeyError:
pass
print("")
if cmd.opc in self.sep_lines:
print("------------------")
adr += cmd.bytes_
def run(self):
""" Startet das geladene Programm """
self.pc = self.start
while True:
opc = self.mem[self.pc]
try:
cmd = self.opcodes[opc]
except KeyError:
print("Illegal opcode: %s" % hexfmt8(opc))
self.registers()
raise SystemExit()
# Programmzaehler auf das erste Argument setzen
self.pc += 1
cmd.func()
# Programmzaehler weiter erhoehen, wenn es kein
# Sprungbefehl ist
if not cmd.changepc:
self.pc += cmd.bytes_ - 1 # -1 wegen der Erhoehung oben
self.cycles += self.opcodes[opc].cyc
# Bei BRK-Befehl abbrechen
if opc == 0:
break
# Bei Existenz einer Stop-Datei abbrechen
if f_exists(self.brk_file):
f_remove(self.brk_file)
break
# Interrupt
if not self.flag_i:
self._check_irq()
# Debug
def registers(self):
""" Druckt die Register und die Flags aus """
print("")
print(" PC AC XR YR SP NV-BDIZC")
print("------------------------------")
print("%s %s %s %s %s %d%d---%d%d%d" % (
hexfmt16(self.pc >> 8, self.pc & 0xff),
hexfmt8(self.reg_a), hexfmt8(self.reg_x),
hexfmt8(self.reg_y), hexfmt8(self.sp),
self.flag_n, 0, self.flag_i, self.flag_z, self.flag_c
))
def mempage(self, page):
""" Druckt die angegebene Speicherseite aus """
print("")
print("Page %s:" % hexfmt8(page))
print("--------")
for i in range(16):
print("%s: " % hexfmt8(i << 4), end='')
for j in range(16):
p = (i << 4) + j
print("%s " % hexfmt8(self.mem[(page << 8) + p]), end='')
print("")
print("")
def cmdtable(self):
""" Druckt eine Tabelle aller implementierten Befehle
sortiert nach den Opcodes aus (wie im Datenblatt von
WDC). Aus Platzgruenden wird die Tabelle in der Mitte
geteilt und die beiden Haelften nacheinander ausgedruckt.
"""
# linke und rechte Haelfte
for page in range(2):
# Spaltentitel (unteren 4 Bits) mit 0-7 bzw. 8-F
print(" ", end = '')
offset = page * 8
for i in range(8):
print("|%s" % hexfmt4(i+offset).center(7), end='')
print("")
# 16 Zeilen fuer die oberen 4 Bits
for row in range(16):
# Trennlinie
print("--", end = '')
print("+-------" * 8)
# Zeilentitel 00-F0
print("%sx" % hexfmt4(row), end='')
# Die Befehle werden in zwei Zeilen gedruckt
# (1. Zeile mit Befehlsnamen und 2. Zeile mit
# Addressierungsart). Daher werden die Texte
# zuerst in zwei Listen gesammelt und am Ende
# ausgedruckt
row1 = []
row2 = []
for col in range(8):
opc = row << 4 | (col+offset)
try:
cmd = self.opcodes[opc]
arg_txt = ""
if cmd.amode in [ZP, ZPX, INDY, REL]:
arg_txt = "aa"
elif cmd.amode in [ABS, ABSX, ABSY, IND]:
arg_txt = "aaaa"
txt = cmd.to_str(arg_txt)
# Befehlsnamen und Adressierungsart aufteilen
itms = txt.split(" ")
row1.append(itms[0])
if len(itms) > 1:
row2.append(itms[1])
else:
row2.append("")
except KeyError:
# kein Befehl fuer diese Opcode implementiert
row1.append("")
row2.append("")
# 1. Zeile der Befehle ausgeben
for itm in row1:
print("|%s" % itm.center(7), end='')
# 2. Zeile der Befehle ausgeben
print("")
print(" ", end='')
for itm in row2:
print("|%s" % itm.center(7), end='')
print("")
# Zwischenraum
print("")
print("")
def setirq(self, adr):
""" Aktiviert den Hardware-IRQ und setzt den Zeiger
bei $fffe
"""
self.flag_i = False
self.mem[0xfffe] = adr & 0xff
self.mem[0xffff] = adr >> 8
def addhandler(self, label, adr, handler):
""" Setzt einen Handler fuer eine bestimmte Adresse,
der dann von JSR aufgerufen wird.
"""
self.jsr_alt[adr] = handler
self.addresses[adr] = label
self.labels[label] = adr
def getargs(self):
""" Verarbeitet die Parameter aus dem Programmaufruf
"""
self.parser = ArgumentParser(
prog=self.progname,
description=self.progdesc
)
self.parser.add_argument("-r", "--reg", action='store_true',
help="print registers")
self.parser.add_argument("-c", "--cyc", action='store_true',
help="print cycles")
self.parser.add_argument("-m", "--mem", metavar="INT",
help="print memory page")
self.parser.add_argument("-f", "--file", metavar="FILE",
help="program file in cbm format")
self.parser.add_argument("-s", "--symtab", metavar="FILE",
help="symtable for program file")
self.parser.add_argument("-a", "--rega", metavar="INT",
help="start value for A-Register")
self.parser.add_argument("-x", "--regx", metavar="INT",
help="start value for X-Register")
self.parser.add_argument("-y", "--regy", metavar="INT",
help="start value for Y-Register")
self.parser.add_argument("--irq", metavar="INT",
help="set IRQ handler")
# self.parser.add_argument("--reset", metavar="INT",
# help="set hardware reset handler")
# self.parser.add_argument("--start", metavar="ADR", default=32768,
# help="start of program (default $8000)")
self.parser.add_argument("-d", "--disass", action='store_true',
help="disassemble the program")
self.parser.add_argument("--run", action='store_true',
help="run the program")
self.parser.add_argument("--jsr", action='store_true',
help="create jsr call for program")
self.parser.add_argument("--commands", action='store_true',
help="print command table")
def parseargs(self):
""" Ruft den Argument-Parser auf (in einer extra Methode,
damit abgeleitete Klassen noch Argumente hinzufuegen
koennen, da ein mehrmaliger Aufruf von parse_args nicht
funktioniert hat)
"""
self.args = self.parser.parse_args()
def executeargs(self):
""" Fuehrt die beim Programm uebergebenen Argumente aus
"""
if self.args.file:
self.load(self.args.file)
if self.args.symtab:
self.loadsym(self.args.symtab)
if self.args.commands:
self.cmdtable()
if self.args.disass:
self.disass()
if self.args.rega:
self.reg_a = int(self.args.rega)
if self.args.regx:
self.reg_x = int(self.args.regx)
if self.args.regy:
self.reg_y = int(self.args.regy)
if self.args.irq:
try:
adr = int(self.args.irq) # Dezimalwert
except ValueError:
adr = int(self.args.irq, 16) # Hex-Wert
self.setirq(adr)
if self.args.jsr:
start_prg = self.start
self.start = 0x0000
self.addcmd2('jsr aaaa', start_prg)
self.addcmd('brk')
if self.args.run:
self.run()
if self.args.reg:
self.registers()
if self.args.cyc:
print("Cycles: %d" % self.cycles)
if self.args.mem:
self.mempage(int(self.args.mem))
if __name__ == "__main__":
cpu = CPU6502()
cpu.getargs()
cpu.parseargs()
cpu.executeargs()