start of tape loading. hw works and have a generation script
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
Name casette;
|
||||
PartNo 00;
|
||||
Date 2026-10-03;
|
||||
Revision 01;
|
||||
Designer joppe;
|
||||
Company unknown;
|
||||
Assembly None;
|
||||
Location unknown;
|
||||
Device g22v10;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Constants
|
||||
------------------------------------------------------------ */
|
||||
|
||||
ar = 'b'0;
|
||||
sp = 'b'0;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Pin assignments
|
||||
|
||||
Physical signals IOSEL, RD and WR are active-low.
|
||||
Their polarity is expressed in the equations with !.
|
||||
------------------------------------------------------------ */
|
||||
|
||||
/* inputs */
|
||||
|
||||
PIN 1 = CLK;
|
||||
|
||||
PIN 2 = A1;
|
||||
PIN 3 = A2;
|
||||
|
||||
PIN 4 = IOSEL;
|
||||
PIN 5 = RD;
|
||||
PIN 6 = A0;
|
||||
|
||||
PIN 7 = COMP;
|
||||
PIN 8 = WR;
|
||||
|
||||
|
||||
/* data bus */
|
||||
|
||||
PIN 14 = D0;
|
||||
PIN 15 = D1;
|
||||
PIN 16 = D2;
|
||||
PIN 17 = D3;
|
||||
PIN 18 = D4;
|
||||
PIN 19 = D5;
|
||||
PIN 20 = D6;
|
||||
PIN 21 = D7;
|
||||
|
||||
|
||||
/* registered output */
|
||||
|
||||
PIN 22 = LED;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Read register at offset 0
|
||||
|
||||
Conditions:
|
||||
|
||||
/IOSEL = 0
|
||||
/RD = 0
|
||||
/WR = 1
|
||||
A2:A0 = 000
|
||||
|
||||
Result:
|
||||
|
||||
D0 = COMP
|
||||
D7..D1 = 0
|
||||
------------------------------------------------------------ */
|
||||
|
||||
D0 = COMP;
|
||||
|
||||
D1 = 'b'0;
|
||||
D2 = 'b'0;
|
||||
D3 = 'b'0;
|
||||
D4 = 'b'0;
|
||||
D5 = 'b'0;
|
||||
D6 = 'b'0;
|
||||
D7 = 'b'0;
|
||||
|
||||
|
||||
D0.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D1.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D2.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D3.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D4.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D5.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D6.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D7.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Writable LED register
|
||||
|
||||
Write conditions:
|
||||
|
||||
/IOSEL = 0
|
||||
/RD = 1
|
||||
/WR = 0
|
||||
A2:A0 = 000
|
||||
|
||||
During such a write:
|
||||
|
||||
LED.D = D0
|
||||
|
||||
Otherwise:
|
||||
|
||||
LED.D = LED
|
||||
|
||||
Pin 1 (CLK) clocks the registered macrocell.
|
||||
------------------------------------------------------------ */
|
||||
|
||||
LED.D =
|
||||
(
|
||||
!IOSEL
|
||||
& RD
|
||||
& !WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0
|
||||
& D0
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
(
|
||||
(
|
||||
IOSEL
|
||||
# !RD
|
||||
# WR
|
||||
# A2
|
||||
# A1
|
||||
# A0
|
||||
)
|
||||
& LED
|
||||
);
|
||||
|
||||
|
||||
LED.ar = ar;
|
||||
LED.sp = sp;
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
CUPL(WM) 5.0a Serial# MW-10400000
|
||||
Device g22v10 Library DLIB-h-40-1
|
||||
Created Sat Oct 03 16:22:02 2026
|
||||
Name casette
|
||||
Partno 00
|
||||
Revision 01
|
||||
Date 2026-10-03
|
||||
Designer joppe
|
||||
Company unknown
|
||||
Assembly None
|
||||
Location unknown
|
||||
*QP24
|
||||
*QF5892
|
||||
*G1
|
||||
*F0
|
||||
*L00416 00000000000000000000000011111111
|
||||
*L00448 11111111111111111111111111111111
|
||||
*L00480 11111111101110111011011110111111
|
||||
*L00512 10111111110111111111111011110111
|
||||
*L00544 11111111111111111111111111111111
|
||||
*L00576 11101111111110111111111111111111
|
||||
*L00608 11111111111111101111111111111111
|
||||
*L00640 11110111111111111111111111100111
|
||||
*L00672 11111111111111111111111111111111
|
||||
*L00704 11110110111111111111111111111111
|
||||
*L00736 11111111111111111110111111111111
|
||||
*L00768 01111111111111111111111100000000
|
||||
*L00896 00000000000000000000000000001111
|
||||
*L00928 10111011101110111011111101111111
|
||||
*L00960 11111111000000000000000000000000
|
||||
*L01472 00000000000000000000000011111011
|
||||
*L01504 10111011101110111111011111111111
|
||||
*L01536 11110000000000000000000000000000
|
||||
*L02144 00000000000011111011101110111011
|
||||
*L02176 10111111011111111111111100000000
|
||||
*L02880 00000000000000000000000011111011
|
||||
*L02912 10111011101110111111011111111111
|
||||
*L02944 11110000000000000000000000000000
|
||||
*L03648 00001111101110111011101110111111
|
||||
*L03680 01111111111111110000000000000000
|
||||
*L04288 00000000000000000000000011111011
|
||||
*L04320 10111011101110111111011111111111
|
||||
*L04352 11110000000000000000000000000000
|
||||
*L04864 00000000000000000000111110111011
|
||||
*L04896 10111011101111110111111111111111
|
||||
*L05344 00000000000000000000000011111011
|
||||
*L05376 10111011101110111111011111111111
|
||||
*L05408 11111111111111111111111111110111
|
||||
*L05440 11111111111111110000000000000000
|
||||
*L05792 00000000000000000010111111111111
|
||||
*L05824 11110011000000110000000000000000
|
||||
*C5BE4
|
||||
*6A87
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
z80_tape_encode.py
|
||||
|
||||
Encode a binary file as a mono PCM WAV using robust two-tone Manchester FSK.
|
||||
|
||||
Defaults:
|
||||
- 48 kHz sample rate
|
||||
- 600 bit/s
|
||||
- 2 kHz / 4 kHz tones
|
||||
- Manchester FSK:
|
||||
0 -> low tone for half a bit, then high tone
|
||||
1 -> high tone for half a bit, then low tone
|
||||
- 1 second 4 kHz leader
|
||||
- 64 bytes of 0x55 preamble
|
||||
- 256-byte CRC-protected blocks
|
||||
- CRC-16/CCITT-FALSE
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import struct
|
||||
import wave
|
||||
from pathlib import Path
|
||||
|
||||
SYNC = b"\xD3\x91"
|
||||
PREAMBLE_BYTE = 0x55
|
||||
|
||||
|
||||
def crc16_ccitt_false(data: bytes) -> int:
|
||||
crc = 0xFFFF
|
||||
for byte in data:
|
||||
crc ^= byte << 8
|
||||
for _ in range(8):
|
||||
crc = ((crc << 1) ^ 0x1021) & 0xFFFF if (crc & 0x8000) else (crc << 1) & 0xFFFF
|
||||
return crc
|
||||
|
||||
|
||||
class FSKWriter:
|
||||
def __init__(self, sample_rate: int, baud: int, f_low: float, f_high: float, amplitude: float):
|
||||
if sample_rate % (2 * baud) != 0:
|
||||
raise ValueError("sample_rate must be divisible by 2*baud")
|
||||
self.sample_rate = sample_rate
|
||||
self.baud = baud
|
||||
self.f_low = f_low
|
||||
self.f_high = f_high
|
||||
self.amplitude = amplitude
|
||||
self.samples_per_half_bit = sample_rate // (2 * baud)
|
||||
self.phase = 0.0
|
||||
self.samples = bytearray()
|
||||
|
||||
def _tone(self, frequency: float, sample_count: int) -> None:
|
||||
phase_step = 2.0 * math.pi * frequency / self.sample_rate
|
||||
scale = int(32767 * self.amplitude)
|
||||
phase = self.phase
|
||||
out = self.samples
|
||||
|
||||
for _ in range(sample_count):
|
||||
sample = int(round(scale * math.sin(phase)))
|
||||
out.extend(struct.pack("<h", sample))
|
||||
phase += phase_step
|
||||
if phase >= 2.0 * math.pi:
|
||||
phase -= 2.0 * math.pi
|
||||
|
||||
self.phase = phase
|
||||
|
||||
def leader(self, seconds: float) -> None:
|
||||
self._tone(self.f_high, round(seconds * self.sample_rate))
|
||||
|
||||
def bit(self, value: int) -> None:
|
||||
# Manchester-FSK:
|
||||
# 0 = low -> high
|
||||
# 1 = high -> low
|
||||
if value:
|
||||
first, second = self.f_high, self.f_low
|
||||
else:
|
||||
first, second = self.f_low, self.f_high
|
||||
|
||||
self._tone(first, self.samples_per_half_bit)
|
||||
self._tone(second, self.samples_per_half_bit)
|
||||
|
||||
def byte(self, value: int) -> None:
|
||||
# MSB first
|
||||
for bit_no in range(7, -1, -1):
|
||||
self.bit((value >> bit_no) & 1)
|
||||
|
||||
def bytes(self, data: bytes) -> None:
|
||||
for value in data:
|
||||
self.byte(value)
|
||||
|
||||
|
||||
def make_block(sequence: int, payload: bytes) -> bytes:
|
||||
header = struct.pack("<HH", sequence & 0xFFFF, len(payload))
|
||||
crc = crc16_ccitt_false(header + payload)
|
||||
return SYNC + header + payload + struct.pack("<H", crc)
|
||||
|
||||
|
||||
def make_eof_block(sequence: int) -> bytes:
|
||||
header = struct.pack("<HH", sequence & 0xFFFF, 0)
|
||||
crc = crc16_ccitt_false(header)
|
||||
return SYNC + header + struct.pack("<H", crc)
|
||||
|
||||
|
||||
def encode(
|
||||
input_path: Path,
|
||||
output_path: Path,
|
||||
*,
|
||||
sample_rate: int = 48_000,
|
||||
baud: int = 600,
|
||||
f_low: float = 2_000.0,
|
||||
f_high: float = 4_000.0,
|
||||
leader_seconds: float = 1.0,
|
||||
preamble_bytes: int = 64,
|
||||
block_size: int = 256,
|
||||
amplitude: float = 0.75,
|
||||
) -> None:
|
||||
data = input_path.read_bytes()
|
||||
|
||||
if not 0 < amplitude <= 1.0:
|
||||
raise ValueError("amplitude must be > 0 and <= 1")
|
||||
if not 1 <= block_size <= 0xFFFF:
|
||||
raise ValueError("block_size must be in 1..65535")
|
||||
|
||||
writer = FSKWriter(sample_rate, baud, f_low, f_high, amplitude)
|
||||
|
||||
writer.leader(leader_seconds)
|
||||
writer.bytes(bytes([PREAMBLE_BYTE]) * preamble_bytes)
|
||||
|
||||
sequence = 0
|
||||
for start in range(0, len(data), block_size):
|
||||
payload = data[start:start + block_size]
|
||||
writer.bytes(make_block(sequence, payload))
|
||||
sequence = (sequence + 1) & 0xFFFF
|
||||
|
||||
writer.bytes(make_eof_block(sequence))
|
||||
|
||||
with wave.open(str(output_path), "wb") as wav:
|
||||
wav.setnchannels(1)
|
||||
wav.setsampwidth(2)
|
||||
wav.setframerate(sample_rate)
|
||||
wav.writeframes(writer.samples)
|
||||
|
||||
duration = len(writer.samples) / 2 / sample_rate
|
||||
|
||||
print(f"Input: {input_path}")
|
||||
print(f"Output: {output_path}")
|
||||
print(f"Input bytes: {len(data)}")
|
||||
print(f"Bit rate: {baud} bit/s")
|
||||
print(f"Tones: {f_low:g} Hz / {f_high:g} Hz")
|
||||
print(f"Sample rate: {sample_rate} Hz")
|
||||
print(f"Half-bit: {writer.samples_per_half_bit} samples")
|
||||
print(f"Block size: {block_size} bytes")
|
||||
print(f"WAV duration: {duration:.2f} s")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Encode a binary file as Manchester two-tone FSK WAV.")
|
||||
parser.add_argument("input", type=Path, help="binary input file")
|
||||
parser.add_argument("output", type=Path, nargs="?", help="output WAV; default: INPUT.wav")
|
||||
parser.add_argument("--baud", type=int, default=600)
|
||||
parser.add_argument("--low", type=float, default=2000.0, help="low tone in Hz")
|
||||
parser.add_argument("--high", type=float, default=4000.0, help="high tone in Hz")
|
||||
parser.add_argument("--sample-rate", type=int, default=48000)
|
||||
parser.add_argument("--leader", type=float, default=1.0, help="leader seconds")
|
||||
parser.add_argument("--preamble", type=int, default=64, help="number of 0x55 preamble bytes")
|
||||
parser.add_argument("--block-size", type=int, default=64)
|
||||
parser.add_argument("--amplitude", type=float, default=0.75)
|
||||
|
||||
args = parser.parse_args()
|
||||
output = args.output or args.input.with_name(args.input.name + ".wav")
|
||||
|
||||
encode(
|
||||
args.input,
|
||||
output,
|
||||
sample_rate=args.sample_rate,
|
||||
baud=args.baud,
|
||||
f_low=args.low,
|
||||
f_high=args.high,
|
||||
leader_seconds=args.leader,
|
||||
preamble_bytes=args.preamble,
|
||||
block_size=args.block_size,
|
||||
amplitude=args.amplitude,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
.include "bios.inc"
|
||||
|
||||
.area TAPELOAD (ABS)
|
||||
.org TAPE_LOADER_ROM_ADDRESS
|
||||
|
||||
COMP_PORT = 0x18
|
||||
|
||||
WORK_BASE = 0x0B000
|
||||
LOAD_START = 0x08000
|
||||
LOAD_LIMIT_HI = 0x0B0
|
||||
STACK_TOP = 0x0BFFF
|
||||
|
||||
OLD_SP = WORK_BASE+0x00
|
||||
|
||||
start:
|
||||
ld (OLD_SP), sp
|
||||
ld sp, #STACK_TOP
|
||||
|
||||
di
|
||||
|
||||
poll_loop:
|
||||
|
||||
; Read from the hardware port COMP_PORT
|
||||
in a, (COMP_PORT)
|
||||
out (COMP_PORT), a
|
||||
|
||||
; Save the raw port status in register B
|
||||
ld b, a
|
||||
|
||||
; Print a carriage return '\r' (ASCII 0x0D)
|
||||
ld a, #0x0D
|
||||
call BIOS_PUTC
|
||||
|
||||
; Restore the port status and isolate the relevant bit
|
||||
; (Assuming bit 0 determines 0 or 1; adjust 'and' mask if it is a different bit)
|
||||
ld a, b
|
||||
and #0x01
|
||||
|
||||
; Convert the binary 0 or 1 into ASCII '0' (0x30) or '1' (0x31)
|
||||
add a, #0x30
|
||||
|
||||
; Print the '0' or '1' character
|
||||
call BIOS_PUTC
|
||||
|
||||
; Infinite loop: poll continuously
|
||||
jr poll_loop
|
||||
|
||||
ld sp, (OLD_SP)
|
||||
ei
|
||||
ret
|
||||
Reference in New Issue
Block a user