Checking in an intermediate LDA implementation but going to change it to conform 100% w SIMH
This commit is contained in:
parent
5ef8965399
commit
6ef31cb034
1 changed files with 113 additions and 0 deletions
113
boot.py
113
boot.py
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue