Skip to content

Commit ef57021

Browse files
committed
AltairZ80: choose DSK boot sector from image size
Based on open-simh PR 512 by Peter Schorn, record the attached DSK image size and use it to select the standard boot ROM start sector. Mini-Disk images keep the Mini-Disk boot ROM, regular Altair disk images start at sector 0, and other standard DSK images keep the existing sector 8 behavior. This version adds integration coverage for the boot ROM bytes, documents the behavior in the AltairZ80 manual, and notes the Python dependency for the full integration test suite.
1 parent 3c8d06b commit ef57021

6 files changed

Lines changed: 173 additions & 11 deletions

File tree

BUILDING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ For testing, you will need:
7272

7373
- `cmocka` 1.1.5 or newer
7474
Provides the host-side C unit-test framework used under `tests/unit/`.
75+
- Python 3
76+
Runs integration-test drivers that need more than a simple simulator
77+
command script.
7578

7679
For networking, you will need the libraries for the backends you enable:
7780

@@ -97,6 +100,9 @@ Notes:
97100
instead of `bison.exe`. The CMake build accepts either executable name.
98101
- `cmocka` is for host-side unit tests rather than the historical
99102
simulator binaries themselves.
103+
- Python 3 is required for the full integration test suite. CMake can
104+
configure without Python, but Python-backed tests are skipped when no
105+
interpreter is found.
100106
- `SDL2_ttf` is the usual missing piece when a video-enabled build does
101107
not configure successfully.
102108
- `libedit` is optional, but without it SCP falls back to plainer
@@ -352,6 +358,7 @@ pkg-config --modversion libpng
352358
pkg-config --modversion zlib
353359
pkg-config --modversion libedit
354360
pkg-config --modversion slirp
361+
python3 --version
355362
bison --version
356363
```
357364

docs/simulators/AltairZ80/altairz80.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,11 @@ for disk 0 to the original number of 77. The command `D TRACKS[0-7]
947947
77` changes the highest track number for all disks to 77. The Mini-Disk
948948
support was added by Mike Douglas in May 2014.
949949

950+
When booting from `DSK`, AltairZ80 chooses the boot ROM behavior from
951+
the attached image size. Mini-Disk-sized images use the Mini-Disk boot
952+
ROM. Standard Altair disk images boot from track 0, sector 0. Other
953+
image sizes use the standard boot ROM starting at track 0, sector 8.
954+
950955
The DSK device can be configured with
951956

952957
| command | action |

simulators/AltairZ80/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,25 @@ add_simulator(altairz80
9393
LABEL AltairZ80
9494
PKG_FAMILY altairz80_family
9595
TEST altairz80)
96+
97+
set(altairz80_boot_rom_choice_output_dir
98+
"${CMAKE_CURRENT_BINARY_DIR}/tests/altairz80-boot-rom-choice")
99+
100+
find_package(Python3 COMPONENTS Interpreter)
101+
if (Python3_Interpreter_FOUND)
102+
add_test(
103+
NAME zimh-altairz80-boot-rom-choice
104+
COMMAND
105+
${Python3_EXECUTABLE}
106+
"${CMAKE_CURRENT_SOURCE_DIR}/tests/boot_rom_choice_test.py"
107+
$<TARGET_FILE:altairz80>
108+
--work-dir
109+
"${altairz80_boot_rom_choice_output_dir}")
110+
111+
set_tests_properties(
112+
zimh-altairz80-boot-rom-choice
113+
PROPERTIES
114+
LABELS "zimh;integration;zimh-AltairZ80"
115+
ENVIRONMENT
116+
"SIMH_TEST_OUTPUT_DIR=${altairz80_boot_rom_choice_output_dir}")
117+
endif ()

simulators/AltairZ80/altairz80_defs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
#define LDA_INSTRUCTION 0x3e /* op-code for LD A,<8-bit value> instruction */
5353
#define UNIT_NO_OFFSET_1 0x37 /* LD A,<unitno> */
5454
#define UNIT_NO_OFFSET_2 0xb4 /* LD a,80h | <unitno> */
55+
/* Standard disk boot ROM patch points. */
56+
#define LDB_INSTRUCTION 0x06
57+
#define START_SECTOR_OFFSET 0x57
5558

5659
#define CPU_INDEX_8080 4 /* index of default PC register */
5760

simulators/AltairZ80/altairz80_dsk.c

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@
157157
#define MINI_DISK_SIZE (MINI_DISK_TRACKS * MINI_DISK_SECT * DSK_SECTSIZE)
158158
#define MINI_DISK_DELTA 4096 /* threshold for detecting mini disks */
159159

160+
#define ALTAIR_DISK_SIZE 337664 /* size of regular Altair disks */
161+
#define ALTAIR_DISK_DELTA 256 /* threshold for detecting Altair disks */
162+
160163
int32 dsk10(const int32 port, const int32 io, const int32 data);
161164
int32 dsk11(const int32 port, const int32 io, const int32 data);
162165
int32 dsk12(const int32 port, const int32 io, const int32 data);
@@ -187,6 +190,8 @@ static int32 sectors_per_track [NUM_OF_DSK] = { DSK_SECT, DSK_SECT, DSK_SECT, DS
187190
DSK_SECT, DSK_SECT, DSK_SECT, DSK_SECT,
188191
DSK_SECT, DSK_SECT, DSK_SECT, DSK_SECT,
189192
DSK_SECT, DSK_SECT, DSK_SECT, DSK_SECT };
193+
static int32 current_image_size [NUM_OF_DSK] = {
194+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
190195
static int32 tracks [NUM_OF_DSK] = { MAX_TRACKS, MAX_TRACKS, MAX_TRACKS, MAX_TRACKS,
191196
MAX_TRACKS, MAX_TRACKS, MAX_TRACKS, MAX_TRACKS,
192197
MAX_TRACKS, MAX_TRACKS, MAX_TRACKS, MAX_TRACKS,
@@ -310,6 +315,8 @@ static REG dsk_reg[] = {
310315
"Number of tracks register array"), REG_CIRC },
311316
{ BRDATAD (SECTPERTRACK, sectors_per_track, 10, 32, NUM_OF_DSK,
312317
"Number of sectors per track register array"), REG_CIRC },
318+
{ BRDATAD (IMAGESIZE, current_image_size, 10, 32, NUM_OF_DSK,
319+
"Size of disk image array"), REG_CIRC + REG_RO },
313320
{ DRDATAD (IN9COUNT, in9_count, 4,
314321
"Count of IN(9) register"), REG_RO },
315322
{ DRDATAD (IN9MESSAGE, in9_message, 4,
@@ -396,6 +403,23 @@ static t_stat dsk_reset(DEVICE *dptr) {
396403
sim_map_resource(0x0A, 1, RESOURCE_TYPE_IO, &dsk12, "dsk12", dptr->flags & DEV_DIS);
397404
return SCPE_OK;
398405
}
406+
407+
static t_bool dsk_image_size_is_near(
408+
const int32 image_size, const int32 expected_size, const int32 delta)
409+
{
410+
return (expected_size - delta < image_size) &&
411+
(image_size < expected_size + delta);
412+
}
413+
414+
static int32 dsk_boot_start_sector(const int32 image_size)
415+
{
416+
if (dsk_image_size_is_near(
417+
image_size, ALTAIR_DISK_SIZE, ALTAIR_DISK_DELTA))
418+
return 0;
419+
420+
return 8;
421+
}
422+
399423
/* dsk_attach - determine type of drive attached based on disk image size */
400424

401425
static t_stat dsk_attach(UNIT *uptr, const char *cptr) {
@@ -413,8 +437,10 @@ static t_stat dsk_attach(UNIT *uptr, const char *cptr) {
413437
tracks to 16, otherwise, 32 sectors per track. */
414438

415439
imageSize = sim_fsize(uptr -> fileref);
416-
sectors_per_track[thisUnitIndex] = (((MINI_DISK_SIZE - MINI_DISK_DELTA < imageSize) &&
417-
(imageSize < MINI_DISK_SIZE + MINI_DISK_DELTA)) ?
440+
current_image_size[thisUnitIndex] = imageSize;
441+
sectors_per_track[thisUnitIndex] = (dsk_image_size_is_near(
442+
imageSize, MINI_DISK_SIZE,
443+
MINI_DISK_DELTA) ?
418444
MINI_DISK_SECT : DSK_SECT);
419445
return SCPE_OK;
420446
}
@@ -426,7 +452,8 @@ void install_ALTAIRbootROM(void) {
426452
}
427453

428454
/* The boot routine modifies the boot ROM in such a way that subsequently
429-
the specified disk is used for boot purposes.
455+
the specified disk is used for boot purposes. For the standard boot ROM,
456+
the starting sector is chosen from the attached image size.
430457
*/
431458
static t_stat dsk_boot(int32 unitno, DEVICE *dptr) {
432459
if (cpu_unit.flags & (UNIT_CPU_ALTAIRROM | UNIT_CPU_BANKED)) {
@@ -435,17 +462,25 @@ static t_stat dsk_boot(int32 unitno, DEVICE *dptr) {
435462
ALTAIR_ROM_LOW, TRUE) == SCPE_OK);
436463
ASSURE(result);
437464
} else {
438-
/* check whether we are really modifying an LD A,<> instruction */
465+
if (bootrom_dsk[START_SECTOR_OFFSET - 1] == LDB_INSTRUCTION)
466+
bootrom_dsk[START_SECTOR_OFFSET] =
467+
dsk_boot_start_sector(current_image_size[unitno]);
468+
else {
469+
sim_printf("Incorrect boot ROM first sector offset detected.\n");
470+
return SCPE_IERR;
471+
}
472+
473+
/* check whether we are really modifying an LD A,<> instruction */
439474
if ((bootrom_dsk[UNIT_NO_OFFSET_1 - 1] == LDA_INSTRUCTION) &&
440475
(bootrom_dsk[UNIT_NO_OFFSET_2 - 1] == LDA_INSTRUCTION)) {
441-
bootrom_dsk[UNIT_NO_OFFSET_1] = unitno & 0xff; /* LD A,<unitno> */
442-
bootrom_dsk[UNIT_NO_OFFSET_2] = 0x80 | (unitno & 0xff); /* LD a,80h | <unitno> */
443-
} else { /* Attempt to modify non LD A,<> instructions is refused. */
444-
sim_printf("Incorrect boot ROM offsets detected.\n");
445-
return SCPE_IERR;
476+
bootrom_dsk[UNIT_NO_OFFSET_1] = unitno & 0xff; /* LD A,<unitno> */
477+
bootrom_dsk[UNIT_NO_OFFSET_2] = 0x80 | (unitno & 0xff); /* LD a,80h | <unitno> */
478+
} else { /* Attempt to modify non LD A,<> instructions is refused. */
479+
sim_printf("Incorrect boot ROM unit number offset detected.\n");
480+
return SCPE_IERR;
481+
}
482+
install_ALTAIRbootROM(); /* install modified ROM */
446483
}
447-
install_ALTAIRbootROM(); /* install modified ROM */
448-
}
449484
}
450485
*((int32 *) sim_PC->loc) = ALTAIR_ROM_LOW;
451486
return SCPE_OK;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import re
5+
import subprocess
6+
from pathlib import Path
7+
8+
9+
def write_sized_file(path: Path, size: int) -> None:
10+
chunk = b"x" * 32768
11+
remaining = size
12+
13+
with path.open("wb") as image:
14+
while remaining >= len(chunk):
15+
image.write(chunk)
16+
remaining -= len(chunk)
17+
if remaining:
18+
image.write(chunk[:remaining])
19+
20+
21+
def run_boot_rom_case(
22+
simulator: Path,
23+
work_dir: Path,
24+
name: str,
25+
image_size: int,
26+
expected_bytes: tuple[str, str, str],
27+
) -> None:
28+
image = work_dir / f"{name}.dsk"
29+
script = work_dir / f"{name}.ini"
30+
31+
write_sized_file(image, image_size)
32+
script.write_text(
33+
"\n".join(
34+
[
35+
"set cpu 8080",
36+
"set cpu altairrom",
37+
"break 0xff00",
38+
f'attach dsk0 "{image}"',
39+
"boot dsk0",
40+
"examine 0xff56-0xff58",
41+
"exit 0",
42+
"",
43+
]
44+
),
45+
encoding="utf-8",
46+
)
47+
48+
result = subprocess.run(
49+
[str(simulator), str(script), "-v"],
50+
check=False,
51+
text=True,
52+
capture_output=True,
53+
timeout=10,
54+
)
55+
if result.returncode != 0:
56+
raise RuntimeError(
57+
f"{name}: simulator exited with {result.returncode}\n"
58+
f"stdout:\n{result.stdout}\n"
59+
f"stderr:\n{result.stderr}"
60+
)
61+
62+
for address, expected_byte in zip(("FF56", "FF57", "FF58"), expected_bytes):
63+
pattern = rf"{address}:\s+{re.escape(expected_byte)}\b"
64+
if re.search(pattern, result.stdout) is None:
65+
raise AssertionError(
66+
f"{name}: expected {address} to be {expected_byte}\n"
67+
f"stdout:\n{result.stdout}"
68+
)
69+
70+
71+
def main() -> None:
72+
parser = argparse.ArgumentParser()
73+
parser.add_argument("simulator", type=Path)
74+
parser.add_argument("--work-dir", type=Path, required=True)
75+
args = parser.parse_args()
76+
77+
args.work_dir.mkdir(parents=True, exist_ok=True)
78+
run_boot_rom_case(
79+
args.simulator, args.work_dir, "mini-disk", 76720, ("00", "3E", "10")
80+
)
81+
run_boot_rom_case(
82+
args.simulator, args.work_dir, "regular-altair", 337664, ("06", "00", "C5")
83+
)
84+
run_boot_rom_case(
85+
args.simulator, args.work_dir, "nonstandard", 1000, ("06", "08", "C5")
86+
)
87+
88+
89+
if __name__ == "__main__":
90+
main()

0 commit comments

Comments
 (0)