--- title: "Tape Read/Write Routines" id: 57126 type: "computer_media" slug: "unknown-machine-code-routine-1" url: "http://localhost/computer_media/unknown-machine-code-routine-1/" markdown_url: "http://localhost/computer_media/unknown-machine-code-routine-1.md" published_at: "2024-10-03T23:55:59+00:00" modified_at: "2026-03-30T21:19:44+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/75_Unk.png" excerpt: "Two Z80 machine code routines tucked inside REM lines deliver custom tape read and write operations, triggered directly from BASIC with a single RAND USR call." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_contents: - id: 56733 title: "Timex Sinclair Public Domain Library Tape 1002" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1002/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/75_Unk.png" media_type_tags: "Programming" --- # Tape Read/Write Routines This program stores two machine code routines in REM statements for cassette tape I/O on a ZX81/TS1000, invoked via RAND USR 16514 (write) and RAND USR 16549 (read). The Z80 machine code at line 10 implements low-level tape routines that bypass the built-in SAVE/LOAD, likely for faster or custom data transfer. The routines reference address 16400 (0x4010) to locate an array and use IN/OUT instructions (DB FE / D3 FF) for direct ULA port access during tape operations. A DIM statement must be executed first to allocate the array space that the machine code accesses. Line 50 saves the program to tape under the name “10075”. *** ## Program Analysis ### Program Structure The program is minimal by design — it exists primarily as a carrier for embedded machine code. Its six lines serve distinct roles: 1. **Line 10** — REM statement containing the raw Z80 machine code for both tape routines. 2. **Line 20** — REM comment documenting that a DIM statement must be run first to allocate array space. 3. **Line 30** — REM documenting the entry point address for the write routine: RAND USR 16514. 4. **Line 40** — REM documenting the entry point address for the read routine: RAND USR 16549. 5. **Line 50** — SAVE command to store the program to tape. 6. **Line 60** — LIST to display the program after loading. The two entry point addresses (16514 and 16549) are offsets into line 10’s REM data. On a ZX81/TS1000, a REM statement’s byte content begins at a fixed offset after the line header, placing the machine code at predictable absolute addresses as long as this line is first in the program — hence the explicit note in line 20. ### Machine Code Disassembly The REM at line 10 contains 69 bytes of Z80 code. The two routines share the same REM block, with the write routine starting at offset 0 and the read routine following it. Key instructions identified: - `CD E7 02` — CALL 02E7h, invoking a ZX81 ROM routine (likely the display/sync routine). - `2A 10 40` — LD HL,(4010h), loading the address stored at 16400 — the start of the user array allocated by DIM. - `23 4E 23 46 23` — INC HL; LD C,(HL); INC HL; LD B,(HL); INC HL — reading a 16-bit length value from the array header into BC. - `DB FE` — IN A,(FEh), reading from the ULA/tape-in port. - `D3 FF` — OUT (FFh),A, writing to the tape-out port. - `17` — RLA, rotating the tape bit into the carry flag for serial decoding. - `CB 16` — RL (HL), rotating carry into the byte being assembled from tape. - `C9` — RET, returning to BASIC. ### Tape I/O Technique Both routines use direct port I/O rather than the ROM’s built-in SAVE/LOAD. Port FEh is the standard ZX81 ULA input port (bit 7 carries the tape signal), and port FFh is used for tape output. The read routine uses `RLA` to shift successive tape bits into the carry flag, then `RL (HL)` to build bytes one bit at a time, with loop counters managing bit depth (`0E 08` loads C with 8 for the bit loop) and byte count from the array length field in BC. The write routine similarly loops over the array data, outputting each bit via `OUT (FFh),A`. The check `FE 56 / 30 E4` (CP 56h; JR NC) appears to be a timing or sync-byte threshold test during the read loop. ### Array Addressing Convention Both routines locate data via `LD HL,(4010h)`. Address 4010h (16400 decimal) is the ZX81/TS1000 system variable `E_LINE` area or more precisely a pointer the author has arranged to hold the array’s base address. The subsequent three INC HL / LD r,(HL) instructions walk past the array’s type byte to read its 16-bit length into BC, which then drives the byte-count loop. This requires the DIM statement to have been executed beforehand, as stated in line 20. ### Entry Point Arithmetic On a ZX81/TS1000, when this program is first in memory, line 10 begins at address 16509 (407Dh). The REM token and length bytes occupy 5 bytes of header, placing the first machine code byte at 16514 — exactly the write entry point stated in line 30. The read routine begins 35 bytes later at 16549, matching line 40’s documentation. ### Notable Techniques and Observations - Storing machine code in a REM is the standard ZX81 technique; the mandatory “first in program” constraint preserves the absolute addresses. - The DJNZ / DJNZ pattern (`10 F6`, `10 F5`) provides tight inner timing loops critical for reliable tape speed. - `3F` (CCF — complement carry flag) appears in the read path, used to invert the tape bit polarity between phases. - The `C5` / `C1` (PUSH BC / POP BC) instructions preserve the byte counter across the inner bit loop. - `78 B1` (LD A,B; OR C) followed by `C8` (RET Z) or `20 F4` (JR NZ) implements a 16-bit zero-test on BC for the outer loop termination, a common Z80 idiom. ## Source Code ``` 10 REM \CD\E7\02\06\0E\21\00\FF\2B\7C\B5\20\FB\10\F6\2A\10\40\23\4E\23\46\23\C5\5E\CD\1F\03\C1\0B\78\B1\20\F4\C9\CD\E7\02\2A\10\40\23\4E\23\46\23\C5\1E\08\DB\FE\D3\FF\17\30\F9\0E\94\06\1A\0D\DB\FE\17\CB\79\79\38\F5\10\F5\20\04\FE\56\30\E4\3F\CB\16\1D\20\DE\C1\0B\78\B1\C8\18\D3 20 REM DIM SPACE FOR ARRAY MUST BE FIRST IN PGM. 30 REM RAND USR 16514 TO WRITE 40 REM RAND USR 16549 TO READ 50 SAVE "1007%5" 60 LIST ```