|
#!/usr/bin/python |
|
# |
|
# decodes Atmel TPI opcodes (for programming ATtinys) |
|
# accepts string of raw hex bytes on stdin, such as "72 00 f3 00" |
|
# invalid characters will be skipped, but please avoid text like "deadcow" |
|
# |
|
|
|
import sys |
|
|
|
class TpiOpcode: |
|
"""Holds information about a TPI opcode.""" |
|
def __init__(self, opcode, mask, name, fmt, args=1): |
|
self.opcode = opcode |
|
self.mask = mask |
|
self.name = name |
|
self.fmt = fmt |
|
self.args = args |
|
|
|
def match(self, insn): |
|
"""Tests if the instruction byte matches this opcode.""" |
|
return insn & self.mask == self.opcode |
|
|
|
def __str__(self): |
|
return self.name |
|
|
|
class TpiOpcodeDirect(TpiOpcode): |
|
def getaddr(self, x): |
|
return ((x & 0x60) >> 1) | (x & 0xF) |
|
|
|
class TpiOpcodeCS(TpiOpcode): |
|
def getaddr(self, x): |
|
return x & 0xF |
|
|
|
class TpiOpcodePR(TpiOpcode): |
|
def getaddr(self, x): |
|
return x & 0x1 |
|
|
|
|
|
class TpiInsn: |
|
"""TPI instruction. Represents an "instance" of an opcode.""" |
|
|
|
def __init__(self, insn, opcode, args): |
|
self.insn = insn |
|
self.opcode = opcode |
|
self.args = args |
|
|
|
def __str__(self): |
|
fmt = self.opcode.fmt |
|
|
|
# has address? |
|
if hasattr(self.opcode, 'getaddr'): |
|
fmt = fmt.replace('<a>', hex(self.opcode.getaddr(self.insn))) |
|
|
|
fmt = fmt.replace('<data>', ','.join([hex(a) for a in self.args])) |
|
|
|
return str(self.opcode) + ' ' + fmt |
|
|
|
class TpiParser: |
|
"""Parser for TPI opcodes.""" |
|
|
|
opcodes = [ |
|
TpiOpcode(0x20, 0xFF, 'SLD', 'PR = <data>'), |
|
TpiOpcode(0x24, 0xFF, 'SLD', 'PR+ = <data>'), |
|
|
|
TpiOpcode(0x60, 0xFF, 'SST', 'PR, <data>'), |
|
TpiOpcode(0x64, 0xFF, 'SST', 'PR+, <data>'), |
|
|
|
TpiOpcodePR(0x68, 0xFE, 'SSTPR', '<a>, <data>'), |
|
TpiOpcodeDirect(0x10, 0x90, 'SIN', '<a> = <data>'), |
|
TpiOpcodeDirect(0x90, 0x90, 'SOUT', '<a>, <data>'), |
|
TpiOpcodeCS(0x80, 0x80, 'SLDCS', '<a> = <data>'), |
|
TpiOpcodeCS(0xC0, 0xC0, 'SSTCS', '<a>, <data>'), |
|
TpiOpcode(0xE0, 0xFF, 'SKEY', '<data>', 8), |
|
] |
|
|
|
@staticmethod |
|
def parse1(q): |
|
""" |
|
Parses the first recognizable instruction from byte array @q. |
|
The instruction and its argument(s) will be popped off. |
|
If the instruction is not recognized or its full argument(s) are not |
|
present, it returns None and the byte array is untouched. |
|
""" |
|
|
|
insn = q[0] |
|
|
|
opcode = None |
|
for o in TpiParser.opcodes: |
|
if o.match(insn): |
|
opcode = o |
|
break |
|
|
|
if opcode and len(q) - 1 >= opcode.args: |
|
q.pop(0) |
|
inst = TpiInsn(insn, opcode, q[0 : opcode.args]) |
|
del q[0:opcode.args] |
|
return inst |
|
|
|
return None |
|
|
|
def main(): |
|
hexin = '' |
|
q = [] |
|
for line in sys.stdin: |
|
for c in line: |
|
# if hex digit, accept it |
|
if c.isdigit() or c.lower() in 'abcdef': |
|
hexin += c |
|
elif hexin: |
|
# if an invalid character, reset state |
|
hexin = '' |
|
|
|
# two hex digits make a byte |
|
if len(hexin) == 2: |
|
q.append(int(hexin, 16)) |
|
hexin = '' |
|
|
|
while q: |
|
insn = TpiParser.parse1(q) |
|
if insn: |
|
print insn |
|
else: |
|
break |
|
|
|
# there shouldn't be any remaining instruction bytes left |
|
assert not q, "unrecognized opcodes: %r" % [hex(x) for x in q] |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |
|
|