Skip to content

Commit

Permalink
Make tap2sna.py handle alternative bit encodings in PZX DATA blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Jun 24, 2024
1 parent 5d41c9c commit 7f4a45d
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 49 deletions.
55 changes: 33 additions & 22 deletions skoolkit/loadtracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
) for c in (0, 1)
)

class DataBlock:
def __init__(self, data, start, end, fast_load=True):
self.data = data
self.start = start
self.end = end
self.fast_load = fast_load

def _check_polarity(timings, polarity, edges, tstates, analyse):
if timings.polarity is not None:
ear = (len(edges) - 1) % 2
Expand All @@ -60,7 +67,6 @@ def get_edges(blocks, first_edge, polarity, analyse=False):
edges = [first_edge]
if polarity % 2:
edges.append(first_edge)
indexes = []
data_blocks = []
tstates = first_edge

Expand Down Expand Up @@ -124,7 +130,9 @@ def get_edges(blocks, first_edge, polarity, analyse=False):
bits = ''
data_len = len(data)
ear = (len(edges) - 1) % 2
print(f'{tstates:>10} {ear:>3} Data ({data_len} bytes{bits}; {timings.zero}/{timings.one} T-states)')
zero = ','.join(str(d) for d in timings.zero)
one = ','.join(str(d) for d in timings.one)
print(f'{tstates:>10} {ear:>3} Data ({data_len} bytes{bits}; {zero}/{one} T-states)')
start = len(edges) - 1
for k, b in enumerate(data, 1):
if k < len(data):
Expand All @@ -133,21 +141,24 @@ def get_edges(blocks, first_edge, polarity, analyse=False):
num_bits = timings.used_bits
for j in range(num_bits):
if b & 0x80:
duration = timings.one
durations = timings.one
else:
duration = timings.zero
for k in range(2):
tstates += duration
durations = timings.zero
for d in durations:
tstates += d
edges.append(tstates)
b *= 2
indexes.append((start, len(edges) - 1))
data_blocks.append(data)
if 0 in timings.zero or 0 in timings.one:
# This is sample data as opposed to byte values, so ensure it's
# not fast-loaded
data_blocks.append(DataBlock(data, len(edges) - 1, len(edges) - 1, False))
else:
data_blocks.append(DataBlock(data, start, len(edges) - 1))
elif i == len(blocks) - 1 or timings.data: # pragma: no cover
# If this block contains a Direct Recording, or is the last block
# on the tape and contains a Pulse Sequence, add a dummy (empty)
# data block to ensure that the pulses are read
indexes.append((len(edges) - 1, len(edges) - 1))
data_blocks.append(())
# on the tape and contains a Pulse Sequence, add a data block to
# ensure that the pulses are read
data_blocks.append(DataBlock((), len(edges) - 1, len(edges) - 1, False))

# Tail pulse
if timings.tail:
Expand All @@ -165,7 +176,7 @@ def get_edges(blocks, first_edge, polarity, analyse=False):
print(f'{tstates:>10} {ear:>3} Pause ({timings.pause} T-states)')
tstates += timings.pause

return edges, indexes, data_blocks
return edges, data_blocks

class LoadTracer(PagingTracer):
def __init__(self, simulator, blocks, accelerators, pause, first_edge, polarity,
Expand All @@ -177,7 +188,7 @@ def __init__(self, simulator, blocks, accelerators, pause, first_edge, polarity,
self.dec_a_jp_hits = 0
self.dec_a_misses = 0
self.simulator = simulator
self.edges, self.indexes, self.blocks = get_edges(blocks, first_edge, polarity)
self.edges, self.blocks = get_edges(blocks, first_edge, polarity)
self.pause = pause
self.in_min_addr = in_min_addr
self.announce_data = True
Expand Down Expand Up @@ -217,7 +228,7 @@ def __init__(self, simulator, blocks, accelerators, pause, first_edge, polarity,
elif dec_b_acc:
opcodes[0x05] = partial(self.dec_b_auto, registers, memory, dec_b_acc)
self.block_index = 0
self.block_data_index = self.indexes[0][0]
self.block_data_index = self.blocks[0].start
self.max_index = len(self.edges) - 1
self.border = border
if len(simulator.memory) == 65536:
Expand All @@ -232,7 +243,7 @@ def __init__(self, simulator, blocks, accelerators, pause, first_edge, polarity,
0, # state[0]: next edge (timestamp)
0, # state[1]: edge index
0, # state[2]: end of tape reached
self.indexes[0][1], # state[3]: index of final edge in current block
self.blocks[0].end, # state[3]: index of final edge in current block
0, # state[4]: tape running
accel_dec_a, # state[5]
list_accelerators, # state[6]
Expand Down Expand Up @@ -565,7 +576,7 @@ def _read_port(self, state, registers, port):
self.announce_data = False
state[4] = 1 # Signal: tape is running
registers[T] = self.edges[index]
length = len(self.blocks[self.block_index])
length = len(self.blocks[self.block_index].data)
if length:
write_line(f'Data ({length} bytes)')
elif index == self.max_index:
Expand All @@ -592,7 +603,8 @@ def next_block(self, tstates):
else:
self.state[1] = self.state[3] + 1
self.state[0] = self.edges[self.state[1]]
self.block_data_index, self.state[3] = self.indexes[self.block_index]
self.block_data_index = self.blocks[self.block_index].start
self.state[3] = self.blocks[self.block_index].end
self.state[4] = int(not self.pause) # Pause tape unless configured not to
self.announce_data = True

Expand All @@ -609,18 +621,17 @@ def fast_load(self, simulator):
while self.block_data_index <= self.state[1] < self.max_index:
self.next_block(registers[T])
if self.block_index < len(self.blocks):
block = self.blocks[self.block_index]
data_block = self.blocks[self.block_index]
else:
raise SkoolKitError("Failed to fast load block: unexpected end of tape")
if not block:
# This block has no separately defined data (e.g. Direct Recording
# block), so it can't be fast-loaded
if not data_block.fast_load:
return False # pragma: no cover

memory = simulator.memory
ix = registers[IXl] + 256 * registers[IXh] # Start address
de = registers[E] + 256 * registers[D] # Block length
a = registers[A]
block = data_block.data
data_len = len(block) - 2

# Exchange AF register pairs (as done at 0x0557), disable interrupts
Expand Down
10 changes: 5 additions & 5 deletions skoolkit/tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,13 @@ def __init__(self, number, tape_data, timings=None, block_id=None, name=None, in
self.block_data = block_data

class TapeBlockTimings:
def __init__(self, pilot_len=0, pilot=0, sync=(), zero=0, one=0, pause=0, used_bits=8,
pulses=(), data=False, tail=0, polarity=None, error=None):
def __init__(self, pilot_len=0, pilot=0, sync=(), zero=None, one=None, pause=0,
used_bits=8, pulses=(), data=False, tail=0, polarity=None, error=None):
self.pilot_len = pilot_len
self.pilot = pilot
self.sync = sync
self.zero = zero
self.one = one
self.zero = (zero, zero) if isinstance(zero, int) else zero
self.one = (one, one) if isinstance(one, int) else one
self.pause = pause
self.used_bits = used_bits
self.pulses = pulses
Expand Down Expand Up @@ -363,7 +363,7 @@ def _get_pzx_block(data, i, block_num, prev_rom_pilot):
'1-bit pulse sequence: {} (T-states)'.format(', '.join(str(p) for p in s1)),
f'Tail pulse: {tail} T-states'
))
timings = TapeBlockTimings(zero=s0[0], one=s1[0], used_bits=used_bits, tail=tail, polarity=polarity)
timings = TapeBlockTimings(zero=s0, one=s1, used_bits=used_bits, tail=tail, polarity=polarity)
elif block_id == 'PAUS':
name = 'Pause'
duration = get_dword(data, i + 8)
Expand Down
44 changes: 22 additions & 22 deletions tests/test_tap2sna.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,12 @@ def test_option_tape_analysis(self):
0 0 Tone (8063 x 2168 T-states)
17480584 1 Pulse (667 T-states)
17481251 0 Pulse (735 T-states)
17481986 1 Data (19 bytes; 855/1710 T-states)
17481986 1 Data (19 bytes; 855,855/1710,1710 T-states)
17765846 1 Pause (3500000 T-states)
21265846 1 Tone (3223 x 2168 T-states)
28253310 0 Pulse (667 T-states)
28253977 1 Pulse (735 T-states)
28254712 0 Data (5 bytes; 855/1710 T-states)
28254712 0 Data (5 bytes; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -521,7 +521,7 @@ def test_option_tape_analysis_with_tape_start(self):
0 0 Tone (3223 x 2168 T-states)
6987464 1 Pulse (667 T-states)
6988131 0 Pulse (735 T-states)
6988866 1 Data (5 bytes; 855/1710 T-states)
6988866 1 Data (5 bytes; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -538,7 +538,7 @@ def test_option_tape_analysis_with_tape_stop(self):
0 0 Tone (8063 x 2168 T-states)
17480584 1 Pulse (667 T-states)
17481251 0 Pulse (735 T-states)
17481986 1 Data (19 bytes; 855/1710 T-states)
17481986 1 Data (19 bytes; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -555,12 +555,12 @@ def test_option_tape_analysis_with_first_edge(self):
1 0 Tone (8063 x 2168 T-states)
17480585 1 Pulse (667 T-states)
17481252 0 Pulse (735 T-states)
17481987 1 Data (19 bytes; 855/1710 T-states)
17481987 1 Data (19 bytes; 855,855/1710,1710 T-states)
17765847 1 Pause (3500000 T-states)
21265847 1 Tone (3223 x 2168 T-states)
28253311 0 Pulse (667 T-states)
28253978 1 Pulse (735 T-states)
28254713 0 Data (5 bytes; 855/1710 T-states)
28254713 0 Data (5 bytes; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -577,12 +577,12 @@ def test_option_tape_analysis_with_polarity(self):
0 1 Tone (8063 x 2168 T-states)
17480584 0 Pulse (667 T-states)
17481251 1 Pulse (735 T-states)
17481986 0 Data (19 bytes; 855/1710 T-states)
17481986 0 Data (19 bytes; 855,855/1710,1710 T-states)
17765846 0 Pause (3500000 T-states)
21265846 0 Tone (3223 x 2168 T-states)
28253310 1 Pulse (667 T-states)
28253977 0 Pulse (735 T-states)
28254712 1 Data (5 bytes; 855/1710 T-states)
28254712 1 Data (5 bytes; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -597,7 +597,7 @@ def test_option_tape_analysis_with_unused_bits_in_last_byte(self):
0 0 Tone (3223 x 2168 T-states)
6987464 1 Pulse (667 T-states)
6988131 0 Pulse (735 T-states)
6988866 1 Data (5 bytes + 4 bits; 855/1710 T-states)
6988866 1 Data (5 bytes + 4 bits; 855,855/1710,1710 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -616,7 +616,7 @@ def test_option_tape_analysis_with_pure_tone_and_pause_and_pulse_sequence_and_pu
2200000 0 Pause (3500 T-states)
2203500 0 Pulse (256 T-states)
2203756 1 Pulse (512 T-states)
2204268 0 Data (4 bytes; 500/1000 T-states)
2204268 0 Data (4 bytes; 500,500/1000,1000 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -631,7 +631,7 @@ def test_option_tape_analysis_pzx_file(self):
pzx.add_puls(pulse_counts=((5000, 2168), (1, 430), (1, 870)))
pzx.add_data((11, 12, 13, 14, 15), (570, 570), (1050, 1050), tail=0, used_bits=5, polarity=0)
pzx.add_puls(pulse_counts=((2000, 1234), (2, 500), (1, 1000)))
pzx.add_data((16, 17, 18), (570, 600), (1050, 1100), tail=0)
pzx.add_data((16, 17, 18, 19, 20), (80, 0), (0, 80), tail=0)
pzx.add_puls(pulses=(0, 1000, 1000, 1000))
pzxfile = self.write_bin_file(pzx.data, suffix='.pzx')
output, error = self.run_tap2sna(f'--tape-analysis {pzxfile}', catch_exit=0)
Expand All @@ -641,24 +641,24 @@ def test_option_tape_analysis_pzx_file(self):
0 0 Tone (8063 x 2168 T-states)
17480584 1 Pulse (667 T-states)
17481251 0 Pulse (735 T-states)
17481986 1 Data (5 bytes; 855/1710 T-states)
17481986 1 Data (5 bytes; 855,855/1710,1710 T-states)
17562356 1 Tail pulse (945 T-states)
17563301 0 Pause (3500000 T-states)
21063301 0 Tone (3223 x 2168 T-states)
28050765 1 Pulse (667 T-states)
28051432 0 Pulse (735 T-states)
28052167 1 Data (5 bytes; 855/1710 T-states)
28052167 1 Data (5 bytes; 855,855/1710,1710 T-states)
28137667 1 Tail pulse (945 T-states)
28138612 0 Pause (3500000 T-states)
31638612 0 Tone (5000 x 2168 T-states)
42478612 0 Pulse (430 T-states)
42479042 1 Pulse (870 T-states)
42479912 0 Data (4 bytes + 5 bits; 570/1050 T-states)
42479912 0 Data (4 bytes + 5 bits; 570,570/1050,1050 T-states)
42533612 0 Tone (2000 x 1234 T-states)
45001612 0 Tone (2 x 500 T-states)
45002612 0 Pulse (1000 T-states)
45003612 1 Data (3 bytes; 570/1050 T-states)
45035772 1 Tone (3 x 1000 T-states)
45003612 1 Data (5 bytes; 80,0/0,80 T-states)
45006812 1 Tone (3 x 1000 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)

Expand All @@ -681,20 +681,20 @@ def test_option_tape_analysis_pzx_file_with_polarity_adjustments(self):
17480584 1 Pulse (667 T-states)
17481251 0 Pulse (735 T-states)
17481986 1 Polarity adjustment (1->0)
17481986 0 Data (5 bytes; 855/1710 T-states)
17481986 0 Data (5 bytes; 855,855/1710,1710 T-states)
17562356 0 Tail pulse (945 T-states)
17563301 1 Polarity adjustment (1->0)
17563301 0 Pause (3500000 T-states)
21063301 0 Tone (3223 x 2168 T-states)
28050765 1 Pulse (667 T-states)
28051432 0 Pulse (735 T-states)
28052167 1 Data (5 bytes; 855/1710 T-states)
28052167 1 Data (5 bytes; 855,855/1710,1710 T-states)
28137667 1 Pause (3500000 T-states)
31637667 1 Polarity adjustment (1->0)
31637667 0 Tone (3223 x 2168 T-states)
38625131 1 Pulse (667 T-states)
38625798 0 Pulse (735 T-states)
38626533 1 Data (3 bytes; 855/1710 T-states)
38626533 1 Data (3 bytes; 855,855/1710,1710 T-states)
38681253 1 Tail pulse (945 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)
Expand All @@ -718,20 +718,20 @@ def test_option_tape_analysis_pzx_file_with_polarity_inverted(self):
17480584 0 Pulse (667 T-states)
17481251 1 Pulse (735 T-states)
17481986 0 Polarity adjustment (0->1)
17481986 1 Data (5 bytes; 855/1710 T-states)
17481986 1 Data (5 bytes; 855,855/1710,1710 T-states)
17562356 1 Tail pulse (945 T-states)
17563301 0 Polarity adjustment (0->1)
17563301 1 Pause (3500000 T-states)
21063301 1 Tone (3223 x 2168 T-states)
28050765 0 Pulse (667 T-states)
28051432 1 Pulse (735 T-states)
28052167 0 Data (5 bytes; 855/1710 T-states)
28052167 0 Data (5 bytes; 855,855/1710,1710 T-states)
28137667 0 Pause (3500000 T-states)
31637667 0 Polarity adjustment (0->1)
31637667 1 Tone (3223 x 2168 T-states)
38625131 0 Pulse (667 T-states)
38625798 1 Pulse (735 T-states)
38626533 0 Data (3 bytes; 855/1710 T-states)
38626533 0 Data (3 bytes; 855,855/1710,1710 T-states)
38681253 0 Tail pulse (945 T-states)
"""
self.assertEqual(dedent(exp_output).lstrip(), output)
Expand Down

0 comments on commit 7f4a45d

Please sign in to comment.