From 6ef31cb03460d7b2d9aa9640eaa9e6a076431a2e Mon Sep 17 00:00:00 2001 From: Neil Webber Date: Wed, 10 Apr 2024 07:11:03 -0500 Subject: [PATCH] Checking in an intermediate LDA implementation but going to change it to conform 100% w SIMH --- boot.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/boot.py b/boot.py index 9b2a281..6248c1d 100644 --- a/boot.py +++ b/boot.py @@ -117,6 +117,119 @@ def boot_bin(p, fname, /, *, addr=0, deposit_only=False, return addr if deposit_only else None +def _must_read_n(f, n): + """read exactly n bytes from f; raise exception if can't. n==0 allowed.""" + + if n == 0: # read(0) works but feels dirty + return bytes() + b = f.read(n) + if len(b) != n: + raise ValueError(f"needed {n} bytes; got {len(b)}") + return b + + +def _byte_phys_write(p, b, addr): + """write b (bytes()) to addr. b may be odd length. addr may be odd.""" + + # addr can be odd, length can be odd. Pick up the bookend bytes + # as needed so physRW_N can do it as words. + if addr & 1: + addr -= 1 + b = bytes([p.physRW(addr) & 255]) + b + if len(b) & 1: + b += bytes([(p.physRW(addr+len(b)-1) >> 8) & 255]) + + words = [(b[i+1] << 8) | b[i] for i in range(0, len(b), 2)] + p.physRW_N(addr, len(words), words) + + +def load_lda_f(p, f): + """Read open file f as an 'absolute loader' (.LDA, sometimes .BIC) file + This is the same format that simh defines for its load (binary) command. + + Returns: the address specified in the END block (per LDA docs) + + Any file format errors or I/O errors will raise an exception. + """ + + # An LDA/BIC/absolute loader file may start with an arbitrary number + # of zero bytes (possible w/paper tape). Any leading zero bytes will be + # ignored. After that it should be repeated blocks: + # [HEADER] -- 6 bytes + # [DATA] -- size determined by header, can be zero length + # [CHECKSUM] -- 1 byte + # + # The header is six individual bytes, as follows: + # 1 -- literally, a byte with value 1 + # 0 -- "MUST" be zero (this code ignores this byte) + # len-lsb -- lower 8 bits of block length + # len-msb -- upper 8 bits of block length + # addr-lsb -- lower 8 bits of address + # addr-msb -- upper 8 bits of address + # + # The 'block length' includes the header bytes but not the checksum. + # Thus the lengthof [DATA] is six less than the block length given. + # Note that the [DATA] length is allowed to be odd, or zero. + # + # If the [DATA] length is zero (block length 6) the block is + # an "END" block that terminates the format. The address indicated + # is the "start address" and is returned. + + while (header := _must_read_n(f, 1)) == 0: + pass + + header += _must_read_n(f, 5) # form the rest of the first header + + while True: + if header[0] != 1: + raise ValueError(f"header starts with {header[0]} not 1") + + count = (header[3] << 8) | header[2] + addr = (header[5] << 8) | header[4] + + if count < 6: + raise ValueError(f"header error, {count=}") + + b = _must_read_n(f, count-6) + + chksum = _must_read_n(f, 1) + if (sum(header) + sum(b) + sum(chksum)) & 0xFF: + raise ValueError(f"checksum mismatch, {header=}") + + if count == 6: + return addr + + _byte_phys_write(p, b, addr) + header = _must_read_n(f, 6) + + assert False, "unreachable" + +def boot_lda(p, fname, /, *, force_run=True): + """Load and boot an LDA/BIC/absolute-loader file. + + By default, the loaded code is started even if the start address + given in the LDA file is odd (which is normally a flag to not start it). + The start address is rounded down 1 to even in such cases. + + To override that behavior, specify force_run=False. The file will only + be run if the start address is even. + + In all cases the raw start address is returned; however, if the loaded + code is successfully started there will never be a return unless that + code eventually halts. + """ + with open(fname, 'rb') as f: + addr = rawaddr = load_lda_f(f) + + if rawaddr & 1: + if not force_run: + return rawaddr + addr = rawaddr - 1 + + p.run(pc=addr) + return rawaddr + + def make_unix_machine(*, loglevel='INFO', drivenames=[]): p = PDP1170(loglevel=loglevel)