summaryrefslogtreecommitdiff
path: root/decoders/z80/pd.py
diff options
context:
space:
mode:
Diffstat (limited to 'decoders/z80/pd.py')
-rw-r--r--decoders/z80/pd.py362
1 files changed, 362 insertions, 0 deletions
diff --git a/decoders/z80/pd.py b/decoders/z80/pd.py
new file mode 100644
index 0000000..2baa02a
--- /dev/null
+++ b/decoders/z80/pd.py
@@ -0,0 +1,362 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, see <http://www.gnu.org/licenses/>.
+##
+
+import sigrokdecode as srd
+from functools import reduce
+from .tables import instr_table_by_prefix
+
+class Ann:
+ ADDR, MEMRD, MEMWR, IORD, IOWR, INSTR, ROP, WOP, WARN = range(9)
+class Row:
+ ADDRBUS, DATABUS, INSTRUCTIONS, OPERANDS, WARNINGS = range(5)
+class Pin:
+ D0, D7 = 0, 7
+ M1, RD, WR, MREQ, IORQ = range(8, 13)
+ A0, A15 = 13, 28
+class Cycle:
+ NONE, MEMRD, MEMWR, IORD, IOWR, FETCH, INTACK = range(7)
+
+class OpState:
+ IDLE = 'IDLE' # no current instruction
+ PRE1 = 'PRE1' # first prefix
+ PRE2 = 'PRE2' # second prefix
+ PREDIS = 'PREDIS' # pre-opcode displacement
+ OPCODE = 'OPCODE' # opcode byte
+ POSTDIS = 'POSTDIS' # post-opcode displacement
+ IMM1 = 'IMM1' # first byte of immediate
+ IMM2 = 'IMM2' # second byte of immediate
+ ROP1 = 'ROP1' # first byte of read operand
+ ROP2 = 'ROP2' # second byte of read operand
+ WOP1 = 'WOP1' # first byte of write operand
+ WOP2 = 'WOP2' # second byte of write operand
+ RESTART = 'RESTART' # restart instruction decoding
+
+ann_data_cycle_map = {
+ Cycle.MEMRD: Ann.MEMRD,
+ Cycle.MEMWR: Ann.MEMWR,
+ Cycle.IORD: Ann.IORD,
+ Cycle.IOWR: Ann.IOWR,
+ Cycle.FETCH: Ann.MEMRD,
+ Cycle.INTACK: Ann.IORD,
+}
+
+def reduce_bus(bus):
+ if 0xFF in bus:
+ return None # unassigned bus probes
+ else:
+ return reduce(lambda a, b: (a << 1) | b, reversed(bus))
+
+def signed_byte(byte):
+ return byte if byte < 128 else byte - 256
+
+class Decoder(srd.Decoder):
+ api_version = 1
+ id = 'z80'
+ name = 'Z80'
+ longname = 'Zilog Z80 CPU'
+ desc = 'Zilog Z80 microprocessor disassembly.'
+ license = 'gplv2+'
+ inputs = ['logic']
+ outputs = ['z80']
+ probes = [
+ {'id': 'd0', 'name': 'D0', 'desc': 'Data bus line 0'},
+ {'id': 'd1', 'name': 'D1', 'desc': 'Data bus line 1'},
+ {'id': 'd2', 'name': 'D2', 'desc': 'Data bus line 2'},
+ {'id': 'd3', 'name': 'D3', 'desc': 'Data bus line 3'},
+ {'id': 'd4', 'name': 'D4', 'desc': 'Data bus line 4'},
+ {'id': 'd5', 'name': 'D5', 'desc': 'Data bus line 5'},
+ {'id': 'd6', 'name': 'D6', 'desc': 'Data bus line 6'},
+ {'id': 'd7', 'name': 'D7', 'desc': 'Data bus line 7'},
+ {'id': 'm1', 'name': '/M1', 'desc': 'Machine cycle 1'},
+ {'id': 'rd', 'name': '/RD', 'desc': 'Memory or I/O read'},
+ {'id': 'wr', 'name': '/WR', 'desc': 'Memory or I/O write'},
+ ]
+ optional_probes = [
+ {'id': 'mreq', 'name': '/MREQ', 'desc': 'Memory request'},
+ {'id': 'iorq', 'name': '/IORQ', 'desc': 'I/O request'},
+ {'id': 'a0', 'name': 'A0', 'desc': 'Address bus line 0'},
+ {'id': 'a1', 'name': 'A1', 'desc': 'Address bus line 1'},
+ {'id': 'a2', 'name': 'A2', 'desc': 'Address bus line 2'},
+ {'id': 'a3', 'name': 'A3', 'desc': 'Address bus line 3'},
+ {'id': 'a4', 'name': 'A4', 'desc': 'Address bus line 4'},
+ {'id': 'a5', 'name': 'A5', 'desc': 'Address bus line 5'},
+ {'id': 'a6', 'name': 'A6', 'desc': 'Address bus line 6'},
+ {'id': 'a7', 'name': 'A7', 'desc': 'Address bus line 7'},
+ {'id': 'a8', 'name': 'A8', 'desc': 'Address bus line 8'},
+ {'id': 'a9', 'name': 'A9', 'desc': 'Address bus line 9'},
+ {'id': 'a10', 'name': 'A10', 'desc': 'Address bus line 10'},
+ {'id': 'a11', 'name': 'A11', 'desc': 'Address bus line 11'},
+ {'id': 'a12', 'name': 'A12', 'desc': 'Address bus line 12'},
+ {'id': 'a13', 'name': 'A13', 'desc': 'Address bus line 13'},
+ {'id': 'a14', 'name': 'A14', 'desc': 'Address bus line 14'},
+ {'id': 'a15', 'name': 'A15', 'desc': 'Address bus line 15'},
+ ]
+ options = {}
+ annotations = [
+ ['addr', 'Memory or I/O address'],
+ ['memrd', 'Byte read from memory'],
+ ['memwr', 'Byte written to memory'],
+ ['iord', 'Byte read from I/O port'],
+ ['iowr', 'Byte written to I/O port'],
+ ['instr', 'Z80 CPU instruction'],
+ ['rop', 'Value of input operand'],
+ ['wop', 'Value of output operand'],
+ ['warning', 'Warning message'],
+ ]
+ annotation_rows = (
+ ('addrbus', 'Address bus', (Ann.ADDR,)),
+ ('databus', 'Data bus', (Ann.MEMRD, Ann.MEMWR, Ann.IORD, Ann.IOWR)),
+ ('instructions', 'Instructions', (Ann.INSTR,)),
+ ('operands', 'Operands', (Ann.ROP, Ann.WOP)),
+ ('warnings', 'Warnings', (Ann.WARN,))
+ )
+
+ def __init__(self, **kwargs):
+ self.prev_cycle = Cycle.NONE
+ self.op_state = OpState.IDLE
+
+ def start(self):
+ self.out_ann = self.register(srd.OUTPUT_ANN)
+ self.bus_data = None
+ self.samplenum = None
+ self.addr_start = None
+ self.data_start = None
+ self.dasm_start = None
+ self.pend_addr = None
+ self.pend_data = None
+ self.ann_data = None
+ self.ann_dasm = None
+ self.prev_cycle = Cycle.NONE
+ self.op_state = OpState.IDLE
+
+ def decode(self, ss, es, data):
+ for (self.samplenum, pins) in data:
+ cycle = Cycle.NONE
+ if pins[Pin.MREQ] != 1: # default to asserted
+ if pins[Pin.RD] == 0:
+ cycle = Cycle.FETCH if pins[Pin.M1] == 0 else Cycle.MEMRD
+ elif pins[Pin.WR] == 0:
+ cycle = Cycle.MEMWR
+ elif pins[Pin.IORQ] == 0: # default to not asserted
+ if pins[Pin.M1] == 0:
+ cycle = Cycle.INTACK
+ elif pins[Pin.RD] == 0:
+ cycle = Cycle.IORD
+ elif pins[Pin.WR] == 0:
+ cycle = Cycle.IOWR
+
+ if cycle != Cycle.NONE:
+ self.bus_data = reduce_bus(pins[Pin.D0:Pin.D7+1])
+ if cycle != self.prev_cycle:
+ if self.prev_cycle == Cycle.NONE:
+ self.on_cycle_begin(reduce_bus(pins[Pin.A0:Pin.A15+1]))
+ elif cycle == Cycle.NONE:
+ self.on_cycle_end()
+ else:
+ self.on_cycle_trans()
+ self.prev_cycle = cycle
+
+ def on_cycle_begin(self, bus_addr):
+ if self.pend_addr is not None:
+ self.put_text(self.addr_start, Ann.ADDR,
+ '{:04X}'.format(self.pend_addr))
+ self.addr_start = self.samplenum
+ self.pend_addr = bus_addr
+
+ def on_cycle_end(self):
+ self.op_state = getattr(self, 'on_state_' + self.op_state)()
+ if self.ann_dasm is not None:
+ self.put_disasm()
+ if self.op_state == OpState.RESTART:
+ self.op_state = self.on_state_IDLE()
+
+ if self.ann_data is not None:
+ self.put_text(self.data_start, self.ann_data,
+ '{:02X}'.format(self.pend_data))
+ self.data_start = self.samplenum
+ self.pend_data = self.bus_data
+ self.ann_data = ann_data_cycle_map[self.prev_cycle]
+
+ def on_cycle_trans(self):
+ self.put_text(self.samplenum - 1, Ann.WARN,
+ 'Illegal transition between control states')
+ self.pend_addr = None
+ self.ann_data = None
+ self.ann_dasm = None
+
+ def put_disasm(self):
+ text = self.mnemonic.format(r=self.arg_reg, d=self.arg_dis,
+ i=self.arg_imm, ro=self.arg_read,
+ wo=self.arg_write)
+ self.put_text(self.dasm_start, self.ann_dasm, text)
+ self.ann_dasm = None
+ self.dasm_start = self.samplenum
+
+ def put_text(self, ss, ann_idx, ann_text):
+ self.put(ss, self.samplenum, self.out_ann, [ann_idx, [ann_text]])
+
+ def on_state_IDLE(self):
+ if self.prev_cycle != Cycle.FETCH:
+ return OpState.IDLE
+ self.want_dis = 0
+ self.want_imm = 0
+ self.want_read = 0
+ self.want_write = 0
+ self.want_wr_be = False
+ self.op_repeat = False
+ self.arg_dis = 0
+ self.arg_imm = 0
+ self.arg_read = 0
+ self.arg_write = 0
+ self.arg_reg = ''
+ self.mnemonic = ''
+ self.instr_pend = False
+ self.read_pend = False
+ self.write_pend = False
+ self.dasm_start = self.samplenum
+ self.op_prefix = 0
+ if self.bus_data in (0xCB, 0xED, 0xDD, 0xFD):
+ return OpState.PRE1
+ else:
+ return OpState.OPCODE
+
+ def on_state_PRE1(self):
+ if self.prev_cycle != Cycle.FETCH:
+ self.mnemonic = 'Prefix not followed by fetch'
+ self.ann_dasm = Ann.WARN
+ return OpState.RESTART
+ self.op_prefix = self.pend_data
+ if self.op_prefix in (0xDD, 0xFD):
+ if self.bus_data == 0xCB:
+ return OpState.PRE2
+ if self.bus_data in (0xDD, 0xED, 0xFD):
+ return OpState.PRE1
+ return OpState.OPCODE
+
+ def on_state_PRE2(self):
+ if self.prev_cycle != Cycle.MEMRD:
+ self.mnemonic = 'Missing displacement'
+ self.ann_dasm = Ann.WARN
+ return OpState.RESTART
+ self.op_prefix = (self.op_prefix << 8) | self.pend_data
+ return OpState.PREDIS
+
+ def on_state_PREDIS(self):
+ if self.prev_cycle != Cycle.MEMRD:
+ self.mnemonic = 'Missing opcode'
+ self.ann_dasm = Ann.WARN
+ return OpState.RESTART
+ self.arg_dis = signed_byte(self.pend_data)
+ return OpState.OPCODE
+
+ def on_state_OPCODE(self):
+ (table, self.arg_reg) = instr_table_by_prefix[self.op_prefix]
+ self.op_prefix = 0
+ instruction = table.get(self.pend_data, None)
+ if instruction is None:
+ self.mnemonic = 'Invalid instruction'
+ self.ann_dasm = Ann.WARN
+ return OpState.RESTART
+ (self.want_dis, self.want_imm, self.want_read, want_write,
+ self.op_repeat, self.mnemonic) = instruction
+ self.want_write = abs(want_write)
+ self.want_wr_be = (want_write < 0)
+ if self.want_dis > 0:
+ return OpState.POSTDIS
+ if self.want_imm > 0:
+ return OpState.IMM1
+ self.ann_dasm = Ann.INSTR
+ if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
+ return OpState.ROP1
+ if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR):
+ return OpState.WOP1
+ return OpState.RESTART
+
+ def on_state_POSTDIS(self):
+ self.arg_dis = signed_byte(self.pend_data)
+ if self.want_imm > 0:
+ return OpState.IMM1
+ self.ann_dasm = Ann.INSTR
+ if self.want_read > 0:
+ return OpState.ROP1
+ if self.want_write > 0:
+ return OpState.WOP1
+ return OpState.RESTART
+
+ def on_state_IMM1(self):
+ self.arg_imm = self.pend_data
+ if self.want_imm > 1:
+ return OpState.IMM2
+ self.ann_dasm = Ann.INSTR
+ if self.want_read > 0:
+ return OpState.ROP1
+ if self.want_write > 0:
+ return OpState.WOP1
+ return OpState.RESTART
+
+ def on_state_IMM2(self):
+ self.arg_imm |= self.pend_data << 8
+ self.ann_dasm = Ann.INSTR
+ if self.want_read > 0:
+ return OpState.ROP1
+ if self.want_write > 0:
+ return OpState.WOP1
+ return OpState.RESTART
+
+ def on_state_ROP1(self):
+ self.arg_read = self.pend_data
+ if self.want_write > 0:
+ return OpState.WOP1
+ if self.want_read > 1:
+ return OpState.ROP2
+ if self.op_repeat and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
+ return OpState.ROP1
+ self.mnemonic = '{ro:02X}'
+ self.ann_dasm = Ann.ROP
+ return OpState.RESTART
+
+ def on_state_ROP2(self):
+ self.arg_read |= self.pend_data << 8
+ self.mnemonic = '{ro:04X}'
+ self.ann_dasm = Ann.ROP
+ if self.want_write > 0:
+ return OpState.WOP1
+ return OpState.RESTART
+
+ def on_state_WOP1(self):
+ self.arg_write = self.pend_data
+ if self.want_read > 1:
+ return OpState.ROP2
+ if self.want_write > 1:
+ return OpState.WOP2
+ if self.want_read > 0 and self.op_repeat and \
+ self.prev_cycle in (Cycle.MEMRD, Cycle.IORD):
+ return OpState.ROP1
+ self.mnemonic = '{wo:02X}'
+ self.ann_dasm = Ann.WOP
+ return OpState.RESTART
+
+ def on_state_WOP2(self):
+ if self.want_wr_be:
+ self.arg_write = (self.arg_write << 8) | self.pend_data
+ else:
+ self.arg_write |= self.pend_data << 8
+ self.mnemonic = '{wo:04X}'
+ self.ann_dasm = Ann.WOP
+ return OpState.RESTART