1
0
mirror of https://github.com/mnaberez/py65.git synced 2024-07-22 04:28:57 +00:00
Conflicts:
	src/py65/assembler.py
	src/py65/disassembler.py
	src/py65/tests/devices/test_mpu6502.py
This commit is contained in:
Mike Naberezny 2012-01-01 14:59:55 -08:00
commit a991a7862c
12 changed files with 997 additions and 552 deletions

235
examples/65Org16.boot.asm Normal file
View File

@ -0,0 +1,235 @@
.LIST
; bootrom for py65 monitor in 65Org16 mode
; based on
; Intel Hex format loader by Ross Archer (9 February 2001, freeware)
; from http: http://www.6502.org/source/monitors/intelhex/intelhex.htm
;
; use this monitor like this:
; PYTHONPATH=. python py65/monitor.py -m 65Org16
; load bootrom.bin fe00
; goto fe00
START = $FFFFFe00
; I/O is memory-mapped in py65:
PUTC = $f001
GETC = $f005 ; blocking input
; Note that Hex format for 65Org16 uses ';' not ':' as the start of record mark
; also note that some fields are now composed of 16-bit elements:
; previously:
; length offset type data checksum
; :/08/E008/00/08090A0B0C0D0E0F/xx
; now
; ;/10/E008/00/00080009000A000B000C000D000E000F/xx
; Zero-page storage
DPL = $00 ; data pointer (two bytes) used by PUTSTRI
DPH = $01 ; high of data pointer
RECLEN = $02 ; record length in bytes
START_LO = $03
START_HI = $04
RECTYPE = $05
CHKSUM = $06 ; record checksum accumulator
DLFAIL = $07 ; flag for download failure
TEMP = $08 ; save hex value
TMPHEX = $09 ; save another hex value
; where the RAM program MUST have its first instruction
ENTRY_POINT = $0200
.ORG START
sei ; disable interrupts
cld ; binary mode arithmetic
ldx #$1FFFF ; Set up the stack pointer
txs ; "
; Download Intel hex. The program you download MUST have its entry
; instruction (even if only a jump to somewhere else) at ENTRY_POINT.
HEXDNLD lda #0
sta START_HI ; store all programs in bank 0 (page 0) for now
sta DLFAIL ;Start by assuming no D/L failure
jsr PUTSTRI
.byte 13,10,13,10
.byte "Send 65Org16 code in"
.byte " variant Intel Hex format"
.byte " at 19200,n,8,1 ->"
.byte 13,10
.byte 0 ; Null-terminate unless you prefer to crash.
HDWRECS jsr GETSER ; Wait for start of record mark ';'
cmp #';'
bne HDWRECS ; not found yet
; Start of record marker has been found
lda #0
sta CHKSUM
jsr GETHEX ; Get the record length
sta RECLEN ; save it
jsr GET4HX ; Get the 16-bit offset
sta START_LO
jsr GETHEX ; Get the record type
sta RECTYPE ; & save it
bne HDER1 ; end-of-record
ldx RECLEN ; number of data bytes to write to memory
ldy #0 ; start offset at 0
HDLP1 jsr GET4HX ; Get the first/next/last data word
sta (START_LO),y ; Save it to RAM
iny ; update data pointer
dex ; decrement character count
dex ; ... twice
bne HDLP1
jsr GETHEX ; get the checksum
lda CHKSUM
bne HDDLF1 ; If failed, report it
; Another successful record has been processed
lda #'#' ; Character indicating record OK = '#'
sta PUTC ; write it out but don't wait for output
jmp HDWRECS ; get next record
HDDLF1 lda #'F' ; Character indicating record failure = 'F'
sta DLFAIL ; download failed if non-zero
sta PUTC ; write it to transmit buffer register
jmp HDWRECS ; wait for next record start
HDER1 cmp #1 ; Check for end-of-record type
beq HDER2
jsr PUTSTRI ; Warn user of unknown record type
.byte 13,10,13,10
.byte "Unknown record type $"
.byte 0 ; null-terminate unless you prefer to crash!
lda RECTYPE ; Get it
sta DLFAIL ; non-zero --> download has failed
jsr PUTHEX ; print it
lda #13 ; but we'll let it finish so as not to
jsr PUTSER ; falsely start a new d/l from existing
lda #10 ; file that may still be coming in for
jsr PUTSER ; quite some time yet.
jmp HDWRECS
; We've reached the end-of-record record
HDER2 jsr GETHEX ; get the checksum
lda CHKSUM ; Add previous checksum accumulator value
beq HDER3 ; checksum = 0 means we're OK!
jsr PUTSTRI ; Warn user of bad checksum
.byte 13,10,13,10
.byte "Bad record checksum!",13,10
.byte 0 ; Null-terminate or 6502 go bye-bye
jmp START
HDER3 lda DLFAIL
beq HDEROK
;A download failure has occurred
jsr PUTSTRI
.byte 13,10,13,10
.byte "Download Failed",13,10
.byte "Aborting!",13,10
.byte 0 ; null-terminate every string yada yada.
jmp START
HDEROK jsr PUTSTRI
.byte 13,10,13,10
.byte "Download Successful!",13,10
.byte "Jumping to location $"
.byte 0 ; by now, I figure you know what this is for. :)
lda #HI(ENTRY_POINT) ; Print the entry point in hex
jsr PUTHEX
lda #LO(ENTRY_POINT)
jsr PUTHEX
jsr PUTSTRI
.byte 13,10
.byte 0 ; stop lemming-like march of the program ctr. thru data
jmp ENTRY_POINT ; jump to canonical entry point
; For py65, the input routine will block until a character arrives
GETSER lda GETC
rts
; get four ascii chars, adding both octets into the checksum
GET4HX jsr GETHEX
asl a
asl a
asl a
asl a
asl a
asl a
asl a
asl a
sta TMPHEX
jsr GETHEX
ora TMPHEX
rts
; get two ascii chars, add into the checksum
GETHEX jsr GETSER
jsr MKNIBL ; Convert to 0..F numeric
asl a
asl a
asl a
asl a ; This is the upper nibble
and #$F0
sta TEMP
jsr GETSER
jsr MKNIBL
ora TEMP
sta TEMP
clc
adc CHKSUM ; Add in the checksum
and #$ff
sta CHKSUM ;
lda TEMP
rts ; return with the nibble received
; Convert the ASCII nibble to numeric value from 0-F:
MKNIBL cmp #'9'+1 ; See if it's 0-9 or 'A'..'F' (no lowercase yet)
bcc MKNNH ; If we borrowed, we lost the carry so 0..9
sbc #7+1 ; Subtract off extra 7 (sbc subtracts off one less)
; If we fall through, carry is set unlike direct entry at MKNNH
MKNNH sbc #'0'-1 ; subtract off '0' (if carry clear coming in)
and #$0F ; no upper nibble no matter what
rts ; and return the nibble
; Put byte in A as hexydecascii
PUTHEX pha ;
lsr a
lsr a
lsr a
lsr a
jsr PRNIBL
pla
PRNIBL and #$0F ; strip off the low nibble
cmp #$0A
bcc NOTHEX ; if it's 0-9, add '0' else also add 7
adc #6 ; Add 7 (6+carry=1), result will be carry clear
NOTHEX adc #'0' ; If carry clear, we're 0-9
; Write the character in A as ASCII:
PUTSER sta PUTC
rts
;Put the string following in-line until a NULL out to the console
PUTSTRI pla ; Get the low part of "return" address (data start address)
sta DPL
pla
sta DPH ; Get the high part of "return" address
; (data start address)
; Note: actually we're pointing one short
PSINB ldy #1
lda (DPL),y ; Get the next string character
inc DPL ; update the pointer
bne PSICHO ; if not, we're pointing to next character
inc DPH ; account for page crossing
PSICHO ora #0 ; Set flags according to contents of Accumulator
beq PSIX1 ; don't print the final NULL
jsr PUTSER ; write it out
jmp PSINB ; back around
PSIX1 inc DPL ;
bne PSIX2 ;
inc DPH ; account for page crossing
PSIX2 jmp (DPL) ; return to byte following final NULL
;
; Dummy interrupt handlers
GOIRQ
GONMI RTI
; vectors
.ORG $FFFFFFFA
NMIENT .word GONMI
RSTENT .word START
IRQENT .word GOIRQ
.end ; finally. das Ende.

BIN
examples/65Org16.boot.rom Normal file

Binary file not shown.

12
examples/README.txt Normal file
View File

@ -0,0 +1,12 @@
Some example code and usage:
For linux:
(no need to install or build - just unpack)
$ cd src
$ env PYTHONPATH=. python py65/monitor.py -m 65Org16 -r ../examples/65Org16.boot.rom
Then paste in a hex file, such as ../examples/swapcase.hex
(Type in some mixed-case input and it will echo it back with the upper and lowercase swapped)

28
examples/swapcase.asm Normal file
View File

@ -0,0 +1,28 @@
; trivial demo program: read input, swap case and write out
; assemble using Anton Treuenfels' HXA assembler
; with I6502.A macro file
; HXA_TW.EXE swapcase.asm
; (runs on Linux if you have WINE installed)
; which will make
; SWAPCASE.HEX
; where the first line should not be input to the hexloader
.hexfile
.cpu T_32_M16
.assume BIT32=1032, BIT32R=3210
.include "i6502.a"
; I/O is memory-mapped in py65:
PUTC = $f001
GETC = $f005 ; blocking input
; the py65 hexload boot ROM will only load to $0200
.ORG $200
another
lda GETC
eor #$20 ; swap upper and lower case as a demo
sta PUTC
jmp another

3
examples/swapcase.hex Normal file
View File

@ -0,0 +1,3 @@
;020000040000FA
;1202000000A5F005004900200085F001004C0200000025
;00000001FF

View File

@ -6,8 +6,8 @@ class Assembler:
Statement = re.compile(r'^([A-z]{3}\s+'
r'\(?\s*)([^,\s\)]+)(\s*[,xXyY\s]*\)?'
r'[,xXyY\s]*)$')
Addressing = [
Addressing8 = [
['zpi', re.compile(r'^\(\$00([0-9A-F]{2})\)$')], # "($0012)"
['zpx', re.compile(r'^\$00([0-9A-F]{2}),X$')], # "$0012,X"
['zpy', re.compile(r'^\$00([0-9A-F]{2}),Y$')], # "$0012,Y"
@ -24,7 +24,25 @@ class Assembler:
['acc', re.compile(r'^A$')], # "A"
['imm', re.compile(r'^#\$([0-9A-F]{2})$')] # "#$12"
]
Addressing16 = [
['zpi', re.compile(r'^\(\$0000([0-9A-F]{4})\)$')], # "($00001234)"
['zpx', re.compile(r'^\$0000([0-9A-F]{4}),X$')], # "$00001234,X"
['zpy', re.compile(r'^\$0000([0-9A-F]{4}),Y$')], # "$00001234,Y"
['zpg', re.compile(r'^\$0000([0-9A-F]{4})$')], # "$00001234"
['inx', re.compile(r'^\(\$0000([0-9A-F]{4}),X\)$')], # "($00001234,X)"
['iny', re.compile(r'^\(\$0000([0-9A-F]{4})\),Y$')], # "($00001234),Y"
['ind', re.compile(r'^\(\$([0-9A-F]{4})([0-9A-F]{4})\)$')], # "($12345678)"
['abx', re.compile(r'^\$([0-9A-F]{4})([0-9A-F]{4}),X$')], # "$12345678,X"
['aby', re.compile(r'^\$([0-9A-F]{4})([0-9A-F]{4}),Y$')], # "$12345678,Y"
['abs', re.compile(r'^\$([0-9A-F]{4})([0-9A-F]{4})$')], # "$12345678"
['rel', re.compile(r'^\$([0-9A-F]{4})([0-9A-F]{4})$')], # "$12345678"
['imp', re.compile(r'^$')], # ""
['acc', re.compile(r'^$')], # ""
['acc', re.compile(r'^A$')], # "A"
['imm', re.compile(r'^#\$([0-9A-F]{4})$')] # "#$1234"
]
Addressing = Addressing8
def __init__(self, mpu, address_parser=None):
""" If a configured AddressParser is passed, symbolic addresses
may be used in the assembly statements.
@ -35,13 +53,25 @@ class Assembler:
self._mpu = mpu
self._address_parser = address_parser
self.addrWidth = mpu.addrWidth
self.byteWidth = mpu.byteWidth
self.addrFmt = mpu.addrFmt
self.byteFmt = mpu.byteFmt
self.addrMask = mpu.addrMask
self.byteMask = mpu.byteMask
if self.byteWidth == 8:
self.Addressing = self.Addressing8
else:
self.Addressing = self.Addressing16
def assemble(self, statement, pc=0000):
""" Assemble the given assembly language statement. If the statement
uses relative addressing, the program counter (pc) must also be given.
The result is a list of bytes, or None if the assembly failed.
"""
opcode, operands = self.normalize_and_split(statement)
for mode, pattern in self.Addressing:
match = pattern.match(operands)
@ -57,8 +87,8 @@ class Assembler:
# relative branch
absolute = int(''.join(operands), 16)
relative = (absolute - pc) - 2
relative = relative & 0xFF
operands = [ ("%02x" % relative) ]
relative = relative & self.byteMask
operands = [ (self.byteFmt % relative) ]
elif len(operands) == 2:
# swap bytes
@ -70,7 +100,7 @@ class Assembler:
# assembly failed
return None
def normalize_and_split(self, statement):
""" Given an assembly language statement like "lda $c12,x", normalize
the statement by uppercasing it, removing unnecessary whitespace,
@ -86,19 +116,19 @@ class Assembler:
# target is an immediate number
if target.startswith('#'):
number = self._address_parser.number(target[1:])
if (number < 0x00) or (number > 0xFF):
if (number < 0x00) or (number > self.byteMask):
raise OverflowError
statement = '%s#$%02x' % (before, number)
statement = before + '#$' + self.byteFmt % number
# target is the accumulator
elif target in ('a', 'A'):
elif target in ('a', 'A'):
pass
# target is an address or label
else:
address = self._address_parser.number(target)
statement = '%s$%04x%s' % (before, address, after)
statement = before + '$' + self.addrFmt % address + after
# strip unnecessary whitespace
opcode = statement[:3].upper()
operand = ''.join(statement[3:].split()).upper().strip()

View File

@ -17,11 +17,19 @@ class MPU:
ZERO = 2
CARRY = 1
def __init__(self, memory=None, pc=0x0000, debug=False):
def __init__(self, memory=None, pc=0x0000, debug=False, byteWidth=8, addrWidth=16, addrFmt="%04x", byteFmt="%02x"):
# config
self.debug = debug
self.name = '6502'
self.byteWidth = byteWidth
self.byteMask = ((1<<byteWidth)-1)
self.addrWidth = addrWidth
self.addrMask = ((1<<addrWidth)-1)
self.addrHighMask = (self.byteMask<<byteWidth)
self.addrFmt=addrFmt
self.byteFmt=byteFmt
self.spBase = 1<<byteWidth
# vm status
self.excycles = 0
self.addcycles = False
@ -35,30 +43,32 @@ class MPU:
# init
self.reset()
def reprformat(self):
return ("%s PC AC XR YR SP NV-BDIZC\n" + \
"%s: %04x %02x %02x %02x %02x %s"
)
def __repr__(self):
flags = itoa(self.p, 2).rjust(8, '0')
flags = itoa(self.p, 2).rjust(self.byteWidth, '0')
indent = ' ' * (len(self.name) + 2)
out = "%s PC AC XR YR SP NV-BDIZC\n" + \
"%s: %04x %02x %02x %02x %02x %s"
return out % (indent, self.name,
return self.reprformat() % (indent, self.name,
self.pc, self.a, self.x, self.y, self.sp, flags)
def step(self):
instructCode = self.ImmediateByte()
self.pc +=1
self.pc &=0xffff
self.pc &=self.addrMask
self.excycles = 0
self.addcycles = self.extracycles[instructCode]
self.instruct[instructCode](self)
self.processorCycles += self.cycletime[instructCode]+self.excycles
self.pc &= 0xffff
self.pc &= self.addrMask
return self
def reset(self):
self.pc = self.start_pc
self.sp = 255
self.sp = self.byteMask
self.a = 0
self.x = 0
self.y = 0
@ -71,11 +81,11 @@ class MPU:
return self.memory[addr]
def WordAt(self, addr):
return self.ByteAt(addr) + (self.ByteAt(addr + 1) << 8)
return self.ByteAt(addr) + (self.ByteAt(addr + 1) << self.byteWidth)
def WrapAt(self, addr):
wrap = lambda x: (x & 0xff00) + ((x + 1) & 0xff)
return self.ByteAt(addr) + (self.ByteAt(wrap(addr)) << 8)
wrap = lambda x: (x & self.addrHighMask) + ((x + 1) & self.byteMask)
return self.ByteAt(addr) + (self.ByteAt(wrap(addr)) << self.byteWidth)
def ProgramCounter(self):
return self.pc
@ -89,23 +99,23 @@ class MPU:
return self.ByteAt(self.pc)
def ZeroPageXAddr(self):
return 255 & (self.x + self.ByteAt(self.pc))
return self.byteMask & (self.x + self.ByteAt(self.pc))
def ZeroPageYAddr(self):
return 255 & (self.y + self.ByteAt(self.pc))
return self.byteMask & (self.y + self.ByteAt(self.pc))
def IndirectXAddr(self):
return self.WrapAt( 255 & (self.ByteAt(self.pc) + self.x))
return self.WrapAt( self.byteMask & (self.ByteAt(self.pc) + self.x))
def IndirectYAddr(self):
if self.addcycles:
a1 = self.WrapAt(self.ByteAt(self.pc))
a2 = (a1+self.y) & 0xffff
if (a1 & 0xff00) != (a2 & 0xff00):
a2 = (a1+self.y) & self.addrMask
if (a1 & self.addrHighMask) != (a2 & self.addrHighMask):
self.excycles += 1
return a2
else:
return (self.WrapAt(self.ByteAt(self.pc))+self.y)&0xffff
return (self.WrapAt(self.ByteAt(self.pc))+self.y)&self.addrMask
def AbsoluteAddr(self):
return self.WordAt(self.pc)
@ -113,57 +123,57 @@ class MPU:
def AbsoluteXAddr(self):
if self.addcycles:
a1 = self.WordAt(self.pc)
a2 = (a1 + self.x) & 0xffff
if (a1 & 0xff00) != (a2 & 0xff00):
a2 = (a1 + self.x) & self.addrMask
if (a1 & self.addrHighMask) != (a2 & self.addrHighMask):
self.excycles += 1
return a2
else:
return (self.WordAt(self.pc)+self.x)&0xffff
return (self.WordAt(self.pc)+self.x)&self.addrMask
def AbsoluteYAddr(self):
if self.addcycles:
a1 = self.WordAt(self.pc)
a2 = (a1 + self.y) & 0xffff
if (a1 & 0xff00) != (a2 & 0xff00):
a2 = (a1 + self.y) & self.addrMask
if (a1 & self.addrHighMask) != (a2 & self.addrHighMask):
self.excycles += 1
return a2
else:
return (self.WordAt(self.pc)+self.y)&0xffff
return (self.WordAt(self.pc)+self.y)&self.addrMask
def BranchRelAddr(self):
self.excycles += 1
addr = self.ImmediateByte()
self.pc += 1
if addr & 128:
addr = self.pc - (addr ^ 0xFF) - 1
if addr & self.NEGATIVE:
addr = self.pc - (addr ^ self.byteMask) - 1
else:
addr = self.pc + addr
if (self.pc & 0xff00) != (addr & 0xff00):
if (self.pc & self.addrHighMask) != (addr & self.addrHighMask):
self.excycles += 1
self.pc = addr & 0xffff
self.pc = addr & self.addrMask
# stack
def stPush(self,z):
self.memory[self.sp+256] = z&255
self.memory[self.sp+self.spBase] = z&self.byteMask
self.sp -= 1
self.sp &= 255
self.sp &= self.byteMask
def stPop(self):
self.sp += 1
self.sp &= 255
return self.ByteAt(self.sp+256)
self.sp &= self.byteMask
return self.ByteAt(self.sp+self.spBase)
def stPushWord(self, z):
self.stPush((z>>8)&255)
self.stPush(z&255)
self.stPush((z>>self.byteWidth)&self.byteMask)
self.stPush(z&self.byteMask)
def stPopWord(self):
z = self.stPop()
z += 256*self.stPop()
z += self.stPop()<<self.byteWidth
return z
def FlagsNZ(self, value):
@ -188,12 +198,12 @@ class MPU:
self.p &= ~(self.CARRY + self.NEGATIVE + self.ZERO)
if tbyte & 128:
if tbyte & self.NEGATIVE:
self.p |= self.CARRY
tbyte = (tbyte << 1) & 0xFF
tbyte = (tbyte << 1) & self.byteMask
if tbyte:
self.p |= tbyte & 128
self.p |= tbyte & self.NEGATIVE
else:
self.p |= self.ZERO
@ -250,7 +260,7 @@ class MPU:
self.p &=~(self.ZERO+self.NEGATIVE+self.OVERFLOW)
if (self.a & tbyte) == 0:
self.p |= self.ZERO
self.p |= tbyte&(128+64)
self.p |= tbyte&(self.NEGATIVE+self.OVERFLOW)
def opROL(self, x):
if x is None:
@ -260,16 +270,16 @@ class MPU:
tbyte = self.ByteAt(addr)
if self.p & self.CARRY:
if tbyte & 128:
if tbyte & self.NEGATIVE:
pass
else:
self.p &= ~self.CARRY
tbyte = (tbyte << 1) | 1
else:
if tbyte & 128:
if tbyte & self.NEGATIVE:
self.p |= self.CARRY
tbyte = tbyte << 1
tbyte &= 0xFF
tbyte &= self.byteMask
self.FlagsNZ(tbyte)
if x is None:
@ -313,7 +323,7 @@ class MPU:
self.p |= aluresult & self.NEGATIVE
if decimalcarry == 1:
self.p |= self.CARRY
if ( ~(self.a ^ data) & (self.a ^ aluresult) ) & 0x80:
if ( ~(self.a ^ data) & (self.a ^ aluresult) ) & self.NEGATIVE:
self.p |= self.OVERFLOW
self.a = (nibble1 << 4) + nibble0
else:
@ -323,12 +333,12 @@ class MPU:
tmp = 0
result = data + self.a + tmp
self.p &= ~(self.CARRY+self.OVERFLOW+self.NEGATIVE+self.ZERO)
if ( ~(self.a ^ data) & (self.a ^ result) ) & 0x80:
if ( ~(self.a ^ data) & (self.a ^ result) ) & self.NEGATIVE:
self.p |= self.OVERFLOW
data = result
if data > 255:
if data > self.byteMask:
self.p |= self.CARRY
data &=255
data &=self.byteMask
if data == 0:
self.p |= self.ZERO
else:
@ -347,7 +357,7 @@ class MPU:
pass # {}
else:
self.p &=~ self.CARRY
tbyte=(tbyte>>1)|128
tbyte=(tbyte>>1)|self.NEGATIVE
else:
if tbyte & 1:
self.p |= self.CARRY
@ -395,10 +405,10 @@ class MPU:
adjust1 = 10 << 4
# the ALU outputs are not decimally adjusted
aluresult = self.a + (~data & 0xFF) + (self.p & self.CARRY)
if aluresult > 0xff:
aluresult = self.a + (~data & self.byteMask) + (self.p & self.CARRY)
if aluresult > self.byteMask:
decimalcarry = 1
aluresult &= 0xff
aluresult &= self.byteMask
# but the final result will be adjusted
nibble0 = (aluresult + adjust0) & 0xf
@ -411,7 +421,7 @@ class MPU:
self.p |= aluresult & self.NEGATIVE
if decimalcarry == 1:
self.p |= self.CARRY
if ( (self.a ^ data) & (self.a ^ aluresult) ) & 0x80:
if ( (self.a ^ data) & (self.a ^ aluresult) ) & self.NEGATIVE:
self.p |= self.OVERFLOW
self.a = (nibble1 << 4) + nibble0
else:
@ -420,14 +430,14 @@ class MPU:
else:
borrow = 1
result = self.a + (~data & 0xFF) + (self.p & self.CARRY)
result = self.a + (~data & self.byteMask) + (self.p & self.CARRY)
self.p &= ~(self.CARRY + self.ZERO + self.OVERFLOW + self.NEGATIVE)
if ( (self.a ^ data) & (self.a ^ result) ) & 0x80:
if ( (self.a ^ data) & (self.a ^ result) ) & self.NEGATIVE:
self.p |= self.OVERFLOW
data = result & 0xFF
data = result & self.byteMask
if data == 0:
self.p |= self.ZERO
if result & 0x100:
if result > self.byteMask:
self.p |= self.CARRY
self.p |= data & self.NEGATIVE
self.a = data
@ -440,7 +450,7 @@ class MPU:
tbyte = self.ByteAt(addr)
self.p &= ~(self.ZERO + self.NEGATIVE)
tbyte = (tbyte - 1) & 0xFF
tbyte = (tbyte - 1) & self.byteMask
if tbyte:
self.p |= tbyte & self.NEGATIVE
else:
@ -459,7 +469,7 @@ class MPU:
tbyte = self.ByteAt(addr)
self.p &= ~(self.ZERO + self.NEGATIVE)
tbyte = (tbyte + 1) & 0xFF
tbyte = (tbyte + 1) & self.byteMask
if tbyte:
self.p |= tbyte & self.NEGATIVE
else:
@ -499,7 +509,7 @@ class MPU:
@instruction(name="BRK", mode="imp", cycles=7)
def inst_0x00(self):
pc = (self.pc + 1) & 0xFFFF # The pc has already been increased one
pc = (self.pc + 1) & self.addrMask # The pc has already been increased one
self.stPushWord(pc)
self.p |= self.BREAK
@ -586,7 +596,7 @@ class MPU:
@instruction(name="JSR", mode="abs", cycles=6)
def inst_0x20(self):
self.stPushWord((self.pc+1)&0xffff)
self.stPushWord((self.pc+1)&self.addrMask)
self.pc=self.WordAt(self.pc)
@instruction(name="AND", mode="inx", cycles=6)
@ -870,7 +880,7 @@ class MPU:
@instruction(name="DEY", mode="imp", cycles=2)
def inst_0x88(self):
self.y -= 1
self.y&=255
self.y&=self.byteMask
self.FlagsNZ(self.y)
@instruction(name="TXA", mode="imp", cycles=2)
@ -1077,7 +1087,7 @@ class MPU:
@instruction(name="INY", mode="imp", cycles=2)
def inst_0xc8(self):
self.y += 1
self.y &= 255
self.y &= self.byteMask
self.FlagsNZ(self.y)
@instruction(name="CMP", mode="imm", cycles=2)
@ -1088,7 +1098,7 @@ class MPU:
@instruction(name="DEX", mode="imp", cycles=2)
def inst_0xca(self):
self.x -= 1
self.x &= 255
self.x &= self.byteMask
self.FlagsNZ(self.x)
@instruction(name="CPY", mode="abs", cycles=4)
@ -1172,7 +1182,7 @@ class MPU:
@instruction(name="INX", mode="imp", cycles=2)
def inst_0xe8(self):
self.x+=1
self.x&=255
self.x&=self.byteMask
self.FlagsNZ(self.x)
@instruction(name="SBC", mode="imm", cycles=2)
@ -1236,4 +1246,3 @@ class MPU:
def inst_0xfe(self):
self.opINCR(self.AbsoluteXAddr)
self.pc += 2

View File

@ -0,0 +1,41 @@
from py65.devices import mpu6502
from py65.utils.devices import make_instruction_decorator
# The 65Org16 is a derivative of the 6502 architecture
# - with 32-bit address space (by using 16-bit bytes)
# - with no specific support for 8-bit bytes
# - with BCD mode not supported
# - and otherwise all opcodes and addressing modes are like the NMOS 6502
# - sign bit is bit 15, overflow bit is bit 14
#
# One implementation can be found here: https://github.com/BigEd/verilog-6502/wiki
class MPU(mpu6502.MPU):
def __init__(self, byteWidth=16, addrWidth=32, addrFmt="%08x", byteFmt="%04x", *args, **kwargs):
mpu6502.MPU.__init__(self, byteWidth=byteWidth, addrWidth=addrWidth, addrFmt=addrFmt, byteFmt=byteFmt, *args, **kwargs)
self.name = '65Org16'
self.waiting = False
self.IrqTo = (1<<self.addrWidth)-2
self.ResetTo = (1<<self.addrWidth)-4
self.NMITo = (1<<self.addrWidth)-6
self.NEGATIVE = 1 << 15
self.OVERFLOW = 1 << 14
def step(self):
if self.waiting:
self.processorCycles += 1
else:
mpu6502.MPU.step(self)
return self
# Make copies of the lists
instruct = mpu6502.MPU.instruct[:]
cycletime = mpu6502.MPU.cycletime[:]
extracycles = mpu6502.MPU.extracycles[:]
disassemble = mpu6502.MPU.disassemble[:]
def reprformat(self):
return ("%s PC AC XR YR SP NV---------BDIZC\n" + \
"%s: %08x %04x %04x %04x %04x %s"
)

View File

@ -8,6 +8,13 @@ class Disassembler:
self._mpu = mpu
self._address_parser = address_parser
self.addrWidth = mpu.addrWidth
self.byteWidth = mpu.byteWidth
self.addrFmt = mpu.addrFmt
self.byteFmt = mpu.byteFmt
self.addrMask = mpu.addrMask
self.byteMask = mpu.byteMask
def instruction_at(self, pc):
""" Disassemble the instruction at PC and return a tuple
containing (instruction byte count, human readable text)
@ -22,94 +29,94 @@ class Disassembler:
elif addressing == 'abs':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
address_or_label = self._address_parser.label_for(address,
'$' + self.addrFmt % address)
disasm += ' ' + address_or_label
length = 3
elif addressing == 'abx':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
address_or_label = self._address_parser.label_for(address,
'$' + self.addrFmt % address)
disasm += ' %s,X' % address_or_label
length = 3
elif addressing == 'aby':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
address_or_label = self._address_parser.label_for(address,
'$' + self.addrFmt % address)
disasm += ' %s,Y' % address_or_label
length = 3
elif addressing == 'imm':
byte = self._mpu.ByteAt(pc + 1)
disasm += ' #$%02x' % byte
disasm += ' #$' + self.byteFmt % byte
length = 2
elif addressing == 'imp':
length = 1
elif addressing == 'ind':
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$%04x' % address)
address = self._mpu.WordAt(pc + 1)
address_or_label = self._address_parser.label_for(address,
'$' + self.addrFmt % address)
disasm += ' (%s)' % address_or_label
length = 3
elif addressing == 'iny':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'$' + self.byteFmt % zp_address)
disasm += ' (%s),Y' % address_or_label
length = 2
elif addressing == 'inx':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'$' + self.byteFmt % zp_address)
disasm += ' (%s,X)' % address_or_label
length = 2
elif addressing == 'rel':
opv = self._mpu.ByteAt(pc + 1)
targ = pc + 2
if opv & (1<<(8-1)):
targ -= (opv ^ 0xFF) + 1
if opv & (1<<(self.byteWidth-1)):
targ -= (opv ^ self.byteMask) + 1
else:
targ += opv
targ &= 0xffff
targ &= self.addrMask
address_or_label = self._address_parser.label_for(targ,
'$%04x' % targ)
address_or_label = self._address_parser.label_for(targ,
'$' + self.addrFmt % targ)
disasm += ' ' + address_or_label
length = 2
elif addressing == 'zpi':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'($%02x)' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'($' + self.byteFmt % zp_address + ')' )
disasm += ' %s' % address_or_label
length = 2
elif addressing == 'zpg':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'$' + self.byteFmt % zp_address)
disasm += ' %s' % address_or_label
length = 2
elif addressing == 'zpx':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'$' + self.byteFmt % zp_address)
disasm += ' %s,X' % address_or_label
length = 2
elif addressing == 'zpy':
zp_address = self._mpu.ByteAt(pc + 1)
address_or_label = self._address_parser.label_for(zp_address,
'$%02x' % zp_address)
address_or_label = self._address_parser.label_for(zp_address,
'$' + self.byteFmt % zp_address)
disasm += ' %s,Y' % address_or_label
length = 2
length = 2
return (length, disasm)

View File

@ -1,14 +1,19 @@
class ObservableMemory:
def __init__(self, subject=None):
def __init__(self, subject=None, addrWidth=16):
self.physMask = 0xffff
if addrWidth > 16:
# even with 32-bit address space, model only 256k memory
self.physMask = 0x3ffff
if subject is None:
subject = 0x10000 * [0x00]
subject = (self.physMask+1) * [0x00]
self._subject = subject
self._read_subscribers = {}
self._write_subscribers = {}
def __setitem__(self, address, value):
address &= self.physMask
callbacks = self._write_subscribers.get(address, [])
for callback in callbacks:
@ -19,6 +24,7 @@ class ObservableMemory:
self._subject[address] = value
def __getitem__(self, address):
address &= self.physMask
callbacks = self._read_subscribers.get(address, [])
final_result = None
@ -37,15 +43,18 @@ class ObservableMemory:
def subscribe_to_write(self, address_range, callback):
for address in address_range:
address &= self.physMask
callbacks = self._write_subscribers.setdefault(address, [])
if callback not in callbacks:
callbacks.append(callback)
def subscribe_to_read(self, address_range, callback):
for address in address_range:
address &= self.physMask
callbacks = self._read_subscribers.setdefault(address, [])
if callback not in callbacks:
callbacks.append(callback)
def write(self, start_address, bytes):
start_address &= self.physMask
self._subject[start_address:start_address+len(bytes)] = bytes

View File

@ -8,6 +8,7 @@ import sys
from asyncore import compact_traceback
from py65.devices.mpu6502 import MPU as NMOS6502
from py65.devices.mpu65c02 import MPU as CMOS65C02
from py65.devices.mpu65Org16 import MPU as V65Org16
from py65.disassembler import Disassembler
from py65.assembler import Assembler
from py65.utils.addressing import AddressParser
@ -16,13 +17,60 @@ from py65.utils.conversions import itoa
from py65.memory import ObservableMemory
class Monitor(cmd.Cmd):
def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None, stdout=None):
self._reset(mpu_type)
mpulist = {'6502': NMOS6502, '65C02': CMOS65C02, '65Org16': V65Org16}
def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None, stdout=None, argv=None):
self.mpu_type=mpu_type
if argv is None:
argv = sys.argv
self._reset(self.mpu_type)
self._width = 78
self._update_prompt()
self._add_shortcuts()
cmd.Cmd.__init__(self, completekey, stdin, stdout)
self.parseArgs(argv)
def parseArgs(self, argv):
import getopt
try:
options, args = getopt.getopt(argv[1:], 'hm:l:r:g:',
['help', 'mpu=', 'load=', 'rom=', 'goto='])
except getopt.GetoptError, err:
print str(err)
self.usage()
sys.exit(1)
for opt, value in options:
if opt in ('-l','--load'):
self.do_load(value)
if opt in ('-r','--rom'):
# load a ROM and run from the reset vector
self.do_load("%s %d" % (value, -1))
physMask = self._mpu.memory.physMask
ResetTo = self._mpu.ResetTo & physMask
ResetDestination = self._mpu.memory[ResetTo] + (self._mpu.memory[ResetTo+1] << self.byteWidth)
self.do_goto("%08x" % ResetDestination)
if opt in ('-g','--goto'):
self.do_goto(value)
if opt in ('-m','--mpu'):
if not self.mpulist.get(value, None):
print "Fatal: no such mpu. Available MPUs:", ', '.join(self.mpulist.keys())
sys.exit(1)
self.do_mpu(value)
elif opt in ("-h", "--help"):
self.usage()
sys.exit()
def usage(self):
print """\
\rUsage: monitor.py [options]
\rOptions:
-h, --help : Show this message
-m, --mpu <device> : Choose which MPU to emulate (default is 6502)
-l, --load <file> : Load a file at address 0
-r, --rom <file> : Load a rom at the top of address space and reset into it
-g, --goto <address> : Perform a goto command after loading any files
"""
def onecmd(self, line):
line = self._preprocess_line(line)
@ -42,6 +90,12 @@ class Monitor(cmd.Cmd):
def _reset(self, mpu_type):
self._mpu = mpu_type()
self.addrWidth = self._mpu.addrWidth
self.byteWidth = self._mpu.byteWidth
self.addrFmt = self._mpu.addrFmt
self.byteFmt = self._mpu.byteFmt
self.addrMask = self._mpu.addrMask
self.byteMask = self._mpu.byteMask
self._install_mpu_observers()
self._address_parser = AddressParser()
self._disassembler = Disassembler(self._mpu, self._address_parser)
@ -112,9 +166,13 @@ class Monitor(cmd.Cmd):
byte = 0
return byte
m = ObservableMemory()
def blocking_getc(address):
return ord(console.getch(self.stdin))
m = ObservableMemory(addrWidth=self.addrWidth)
m.subscribe_to_write([0xF001], putc)
m.subscribe_to_read([0xF004], getc)
m.subscribe_to_read([0xF005], blocking_getc)
self._mpu.memory = m
@ -147,18 +205,16 @@ class Monitor(cmd.Cmd):
self._reset(mpu_type=klass)
def do_mpu(self, args):
mpus = {'6502': NMOS6502, '65C02': CMOS65C02}
def available_mpus():
mpu_list = ', '.join(mpus.keys())
mpu_list = ', '.join(self.mpulist.keys())
self._output("Available MPUs: %s" % mpu_list)
if args == '':
self._output("Current MPU is %s" % self._mpu.name)
available_mpus()
else:
requested = args.upper()
new_mpu = mpus.get(requested, None)
requested = args
new_mpu = self.mpulist.get(requested, None)
if new_mpu is None:
self._output("Unknown MPU: %s" % args)
available_mpus()
@ -201,7 +257,7 @@ class Monitor(cmd.Cmd):
else:
end = start + len(bytes)
self._mpu.memory[start:end] = bytes
self.do_disassemble('%04x:%04x' % (start, end))
self.do_disassemble((self.addrFmt+":"+self.addrFmt) % (start, end))
def help_assemble(self):
self._output("assemble <address> <statement>")
@ -220,7 +276,7 @@ class Monitor(cmd.Cmd):
assembling = True
while assembling:
prompt = "\r$%04x " % (start)
prompt = "\r$" + ( self.addrFmt % start ) + " " + (" " * (1 + self.byteWidth/4) * 3)
line = console.line_input(prompt,
stdin=self.stdin, stdout=self.stdout)
@ -231,7 +287,7 @@ class Monitor(cmd.Cmd):
# assemble into memory
bytes = self._assembler.assemble(line, pc=start)
if bytes is None:
self.stdout.write("\r$%04x ???\n" % start)
self.stdout.write("\r$" + (self.addrFmt % start) + " ???\n")
continue
end = start + len(bytes)
self._mpu.memory[start:end] = bytes
@ -258,9 +314,11 @@ class Monitor(cmd.Cmd):
def _format_disassembly(self, address, bytes, disasm):
mem = ''
for byte in self._mpu.memory[address:address+bytes]:
mem += '%02x ' % byte
mem += self.byteFmt % byte + " "
return "$%04x %-10s%s" % (address, mem, disasm)
fieldwidth = 1 + (1 + self.byteWidth/4) * 3
fieldfmt = "%%-%ds" % fieldwidth
return "$" + self.addrFmt % address + " " + fieldfmt % mem + disasm
def help_disassemble(self):
self._output("disassemble <address_range>")
@ -272,7 +330,7 @@ class Monitor(cmd.Cmd):
def do_step(self, args):
self._mpu.step()
self.do_disassemble('%04x' % self._mpu.pc)
self.do_disassemble(self.addrFmt % self._mpu.pc)
def help_return(self):
self._output("return")
@ -338,7 +396,7 @@ class Monitor(cmd.Cmd):
return
self._output("+%u" % num)
self._output("$%02x" % num)
self._output("$" + self.byteFmt % num)
self._output("%04o" % num)
self._output(itoa(num, 2).zfill(8))
@ -360,9 +418,9 @@ class Monitor(cmd.Cmd):
self._output("Invalid register: %s" % register)
else:
try:
intval = self._address_parser.number(value) & 0xFFFF
intval = self._address_parser.number(value) & self.addrMask
if len(register) == 1:
intval &= 0xFF
intval &= self.byteMask
setattr(self._mpu, register, intval)
except KeyError, why:
self._output(why[0])
@ -399,6 +457,7 @@ class Monitor(cmd.Cmd):
filename = split[0]
if len(split) == 2:
# if the start address is -1, we will adjust it later
start = self._address_parser.number(split[1])
else:
start = self._mpu.pc
@ -412,7 +471,16 @@ class Monitor(cmd.Cmd):
self._output(msg)
return
self._fill(start, start, map(ord, bytes))
# if the start address was -1, we load to top of memory
if start == -1:
start = self.addrMask - len(bytes)/(self.byteWidth/8) + 1
if self.byteWidth==8:
bytes=map(ord, bytes)
elif self.byteWidth==16:
bytes=map(lambda msb,lsb: (ord(msb)<<8)+ord(lsb),bytes[0::2],bytes[1::2])
self._fill(start, start, bytes)
def do_save(self, args):
split = shlex.split(args)
@ -428,7 +496,9 @@ class Monitor(cmd.Cmd):
try:
f = open(filename, 'wb')
for byte in bytes:
f.write(chr(byte))
# output each octect from msb first
for shift in range (self.byteWidth-8,-1,-8):
f.write(chr((byte>>shift) & 0xff))
f.close()
except (OSError, IOError), why:
msg = "Cannot save file: [%d] %s" % (why[0], why[1])
@ -465,19 +535,20 @@ class Monitor(cmd.Cmd):
if start == end:
end = start + length - 1
if (end > 0xFFFF):
end = 0xFFFF
if (end > self.addrMask):
end = self.addrMask
while address <= end:
address &= 0xFFFF
self._mpu.memory[address] = (filler[index] & 0xFF)
address &= self.addrMask
self._mpu.memory[address] = (filler[index] & self.byteMask)
index += 1
if index == length:
index = 0
address += 1
fmt = (end - start + 1, start, end)
self._output("Wrote +%d bytes from $%04x to $%04x" % fmt)
starttoend = "$" + self.addrFmt + " to $" + self.addrFmt
self._output(("Wrote +%d bytes from " + starttoend) % fmt)
def help_mem(self):
self._output("mem <address_range>")
@ -486,15 +557,15 @@ class Monitor(cmd.Cmd):
def do_mem(self, args):
start, end = self._address_parser.range(args)
line = "%04x:" % start
line = self.addrFmt % start + ":"
for address in range(start, end+1):
byte = self._mpu.memory[address]
more = " %02x" % byte
more = " " + self.byteFmt % byte
exceeded = len(line) + len(more) > self._width
if exceeded:
self._output(line)
line = "%04x:" % address
line = self.addrFmt % address + ":"
line += more
self._output(line)
@ -524,7 +595,7 @@ class Monitor(cmd.Cmd):
byaddress = zip(values, keys)
byaddress.sort()
for address, label in byaddress:
self._output("%04x: %s" % (address, label))
self._output(self.addrFmt % address + ": " + label)
def help_delete_label(self):
self._output("delete_label <label>")

File diff suppressed because it is too large Load Diff