Checking in an intermediate LDA implementation but going to change it to conform 100% w SIMH

This commit is contained in:
Neil Webber 2024-04-10 07:11:03 -05:00
parent 5ef8965399
commit 6ef31cb034

113
boot.py
View file

@ -117,6 +117,119 @@ def boot_bin(p, fname, /, *, addr=0, deposit_only=False,
return addr if deposit_only else None 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=[]): def make_unix_machine(*, loglevel='INFO', drivenames=[]):
p = PDP1170(loglevel=loglevel) p = PDP1170(loglevel=loglevel)