-- MakeLoaderFile.mesa
-- last edited:
-- January 9, 1984 4:46 PM by Taft
-- HGM on: February 6, 1984 10:10 PM

-- Make D0 boot files from .mb files

DIRECTORY
AltoFileDefs USING [CFA],
ImageDefs USING [MakeImage, StopMesa],
InlineDefs USING [BITAND, BITNOT, BITOR, BITSHIFT, BITXOR, LowHalf, HighHalf],
IODefs USING [
CR, ReadChar, ReadID, ReadLine, ReadOctal, WriteDecimal,
WriteChar, WriteLine, WriteOctal, WriteString],
MiscDefs USING [CommandLineCFA],
SegmentDefs USING [
Append, FileHandle, FileNameError, GetFileTimes, InsertFile,
InvalidFP, Read, ReadWrite, Write],
StreamDefs USING [
CreateByteStream, DiskHandle, FileLength, GetIndex, IndexToPosition,
JumpToFA, ModifyIndex, NewByteStream, NewWordStream, SetIndex,
SetPosition, StreamError, WriteBlock],
String USING [
AppendChar, AppendString, EquivalentString, LowerCase, StringToOctal,
WordsForString],
Storage USING [Node],
TimeDefs USING [PackedTime];

MakeLoaderFile: PROGRAM
IMPORTS ImageDefs, InlineDefs, IODefs, MiscDefs, SegmentDefs,
StreamDefs, String, Storage =
BEGIN

-- Central to this program is the array IMem, a model of the instruction memory.
-- Commands exist to load IMem from .mb format files, and
-- to dump IMem to .bt and .sb format files.
IMem: POINTER TO ARRAY IMemRange OF IMemCell;
IMemRange: TYPE = [0..4096);
IMemCell: TYPE = RECORD [first, second, third: WORD];
nullIMemCell: IMemCell = [0, 1, 0]; -- odd parity
PackedInst: TYPE = MACHINE DEPENDENT RECORD [SELECT OVERLAID * FROM
inst => [addr: [0..4095], word2: [0..15], word0: WORD, word1: WORD],
end => [addr: [0..4095], fill: [0..15], checkSum: WORD, startLoc: WORD], -- addr=4095
words => [a, b, c: WORD],
ENDCASE];
miPerSector: CARDINAL = 256/SIZE[PackedInst]; -- number of microinstructions per sector
bpCount: CARDINAL ← 0; -- counts BPs encountered

ClearIMem: PROCEDURE =
BEGIN
IMem↑ ← ALL[nullIMemCell];
END;

Word: TYPE = MACHINE DEPENDENT RECORD [SELECT OVERLAID * FROM
bytes => [left, right: [0..255]],
word => [word: WORD],
ENDCASE];

LoadIMem: PROCEDURE =
BEGIN OPEN InlineDefs;
MicroIMemory: CARDINAL = 1;
MicroRMemory: CARDINAL = 2;
MicroMMemory: CARDINAL = 3;
MicroZMemory: CARDINAL = 4;
NoMemory: CARDINAL = 100;
fun: ARRAY [0..16] OF WORD = [
0,100000B,140000B,160000B,170000B,
174000B,176000B,177000B,177400B,177600B,177700B,
177740B,177760B,177770B,177774B,177776B,177777B];
currentMemory: WORD ← NoMemory;
type: Word;
-- Interpret Micro/MicroD-style output file
-- (see Appendix 3 of Micro section of D0 Microprogrammmer’s Manual)
DO
type ← [word[GetWord[]]]; -- right byte is significant; left is don’t care here
SELECT type.right FROM
0 => -- end of file
EXIT;
1 => -- data word for current location of current memory
BEGIN
IMemAddr, IMemAddrWord: WORD;
temp: IMemCell;
SkipWords[1]; -- source line number
SELECT currentMemory FROM
MicroRMemory, MicroMMemory, MicroZMemory => SkipWords[1]; -- data
MicroIMemory =>
BEGIN
temp ← [GetWord[], BITAND[GetWord[], 177776B], GetWord[]];
IMemAddrWord ← GetWord[];
Parity[@temp];
IMemAddr ← BITAND[7777B, IMemAddrWord];
IF (BITAND[IMemAddrWord, 140000B]#0) THEN
BEGIN
bpCount ← bpCount+1;
IODefs.WriteString["BREAKPOINT instr at loc "]; IODefs.WriteOctal[IMemAddr];
IODefs.WriteLine[""]
END;
IMem[IMemAddr] ← temp
END;
ENDCASE => IODefs.WriteString[" unknownMemoryType"];
END;
2 => -- switch memories/location
BEGIN
currentMemory ← GetWord[];
SkipWords[1]; -- currentLocation; overridden by address in type 1 format
END;
-- 3 => forward reference fixup
4 => -- memory-symbolic name correlation
BEGIN
SkipWords[2]; -- memory #, width of memory
SkipString[] -- symbolic name
END;
5 => -- address symbol definition
BEGIN
SkipWords[2]; -- memory #, value
SkipString[] -- symbol name
END;
6 => -- reference to undefined symbol
BEGIN
SkipWords[3]; -- memory #, location, first bit,,last bit
SkipString[] -- symbol name
END;
ENDCASE => IODefs.WriteString["UnexpectedMicroFormat"]
ENDLOOP
END;

Parity: PROCEDURE [t: POINTER TO IMemCell] =
BEGIN OPEN InlineDefs;
p: CARDINAL;
t.second ← BITAND[t.second, 177776B];
p ← BITXOR[t.first, t.second];
p ← BITXOR[p, BITAND[t.third, 170000B]];
p ← BITXOR[p, BITSHIFT[p, -8]];
p ← BITXOR[p, BITSHIFT[p, -4]];
p ← BITXOR[p, BITSHIFT[p, -2]];
p ← BITNOT[BITXOR[p, BITSHIFT[p, -1]]];
t.second ← BITOR[t.second, BITAND[p, 1]]
END;

Format: TYPE = {soft, ether}; -- See MakeLoaderFile.memo for details

softVersionNumber: CARDINAL ← 0;
etherVersionNumber: CARDINAL ← 1;

DumpIMem: PROCEDURE [format: Format, startLoc: CARDINAL] =
BEGIN OPEN InlineDefs;
i: CARDINAL; x: IMemCell; px: PackedInst;
endMarker: PackedInst ← [end[addr: 7777B, fill: 0, checkSum: , startLoc: startLoc]];
checkSum: WORD ← 0; -- accumulator for soft format check sum
FOR i IN IMemRange DO
x ← IMem[i];
IF x#nullIMemCell THEN
BEGIN
px ← [inst[addr: i, word2: BITSHIFT[x.third, -12], word0: x.first, word1: x.second]];
checkSum ← checkSum + px.a + px.b + px.c;
PutInst[format: format, inst: px, andFlush: FALSE]
END
ENDLOOP;
-- Put out end marker and flush buffer (if appropriate)
endMarker.checkSum ← 0-(checkSum + endMarker.a + endMarker.c);
PutInst[format: format, inst: endMarker, andFlush: TRUE]
END;


-- Input/output routines
-- GetX means read from stream
-- PutX means write to stream
-- InX means read from command file or keyboard
-- OutX means write to command file or display

-- Input stream
infile: STRING = [40];
is: StreamDefs.DiskHandle; -- a word stream

GetWord: PROCEDURE RETURNS [WORD] =
BEGIN
RETURN[is.get[is]]
END;

SkipWords: PROCEDURE [count: CARDINAL] =
BEGIN
THROUGH [0..count) DO [] ← is.get[is] ENDLOOP
END;

SkipString: PROCEDURE = -- skip Bcpl-style string
BEGIN
word: Word;
DO
word ← [word[is.get[is]]];
IF word.left=0 OR word.right=0 THEN EXIT
ENDLOOP
END;

-- Output stream
outfile: STRING = [40];
os: StreamDefs.DiskHandle; -- a word stream
wordsWritten: CARDINAL ← 0;

PutWord: PROCEDURE [w: WORD] =
BEGIN
os.put[os, w];
wordsWritten ← wordsWritten+1
END;

PutBlock: PROCEDURE [address: POINTER, words: CARDINAL] =
BEGIN
[] ← StreamDefs.WriteBlock[os, address, words];
wordsWritten ← wordsWritten+words
END;

PutHeader: PROCEDURE [format: Format, name: STRING] =
BEGIN
SELECT format FROM
soft => PutWord[softVersionNumber];
ether =>
BEGIN
createTime: TimeDefs.PackedTime ←
SegmentDefs.GetFileTimes[os.file].create;
destMaxLength: CARDINAL ← name.length + name.length MOD 2;
wordsForString: CARDINAL ← String.WordsForString[destMaxLength];
-- The first 5 words must agree with the format of an Alto boot file
PutWord[etherVersionNumber];
PutWord[0]; -- or else boot file will get reformated
PutWord[0];
PutWord[InlineDefs.HighHalf[createTime]]; -- BCPL format
PutWord[InlineDefs.LowHalf[createTime]];
PutWord[name.length]; -- length
PutWord[destMaxLength]; -- maxlength
PutBlock[@name.text, wordsForString-2];
-- We should probably put a version stamp and our version stamp in here
PutPadding[];
-- Last word used for checksum (to keep NS booters happy)
END;
ENDCASE => ERROR
END;

PutInst: PROCEDURE [format: Format, inst: PackedInst, andFlush: BOOLEAN] =
BEGIN
PutWord[inst.a]; PutWord[inst.b]; PutWord[inst.c];
END;

PutTrailer: PROCEDURE [format: Format] =
BEGIN
SELECT format FROM
ether => NULL;
soft => PutPadding[];
ENDCASE => ERROR
END;

PutPadding: PROCEDURE = -- make sure file size is multiple of 512 bytes
BEGIN
wordsOnLastPage: CARDINAL = wordsWritten MOD 256;
IF wordsOnLastPage~=0 THEN
THROUGH [0..256-wordsOnLastPage) DO PutWord[0] ENDLOOP
END;

-- Command stream
commandFile: BOOLEAN ← FALSE; -- FALSE => read from keyboard
isCommand: StreamDefs.DiskHandle; -- a byte stream (also used to read Com.cm)

CR: CHARACTER = 15C;
Space: CHARACTER = ’ ;
Tab: CHARACTER = 11C;

tstr: STRING ← [200];
tstrLength: CARDINAL;
InChar: PROCEDURE RETURNS [ch: CHARACTER] =
BEGIN
tstr.length ← 0;
IF commandFile THEN
BEGIN
InLine[tstr];
RETURN[ch ← tstr[0]]
END
ELSE ch ← IODefs.ReadChar[]
END;

InLine: PROCEDURE [st: STRING] =
BEGIN
i: CARDINAL; ch: CHARACTER;
IF commandFile THEN
BEGIN
st.length ← i ← 0;
UNTIL (ch ← isCommand.get[isCommand])=CR DO
String.AppendChar[st,ch];
i ← i+1
ENDLOOP;
tstrLength ← st.length ← i;
i ← 0; -- skip over comments
UNTIL i=st.length OR st[i]=Space OR st[i]=Tab DO i ← i+1 ENDLOOP;
st.length ← i
END
ELSE IODefs.ReadLine[st]
END;

InString: PROCEDURE [st: STRING] =
BEGIN
i: CARDINAL; ch: CHARACTER;
IF commandFile THEN
BEGIN
i ← 0;
UNTIL (ch ← isCommand.get[isCommand])=CR DO
IODefs.WriteChar[ch];
st[i] ← ch;
i ← i+1
ENDLOOP;
st.length ← i;
i ← 0; -- skip over comments
UNTIL i=st.length OR st[i]=Space OR st[i]=Tab DO i ← i+1 ENDLOOP;
st.length ← i
END
ELSE IODefs.ReadID[st]
END;

InOctal: PROCEDURE RETURNS [c: CARDINAL] =
BEGIN
i: CARDINAL ← 0; st: STRING ← [7]; ch: CHARACTER;
IF commandFile THEN
BEGIN
UNTIL (ch ← LOOPHOLE[isCommand.get[isCommand]])=CR OR ch=Space DO
IODefs.WriteChar[ch];
st[i] ← ch;
i ← i+1
ENDLOOP;
st.length ← i;
c ← String.StringToOctal[st]
END
ELSE c ← IODefs.ReadOctal[]
END;

InBoolean: PROCEDURE RETURNS [b: BOOLEAN] =
BEGIN
b ← String.LowerCase[InChar[]]=’y;
OutLine[IF b THEN "Yes" ELSE "No"]
END;

InFileName: PROCEDURE [name, ext: STRING] =
BEGIN
i: CARDINAL;
InString[name]; OutLine[""];
FOR i DECREASING IN [0..name.length) DO
IF name[i]=’. THEN RETURN
ENDLOOP;
String.AppendString[to: name, from: ext]
END;

OutString: PROCEDURE [st:STRING] =
BEGIN IF TRUE OR NOT commandFile THEN IODefs.WriteString[st] END;

OutLine: PROCEDURE [st:STRING] =
BEGIN IF TRUE OR NOT commandFile THEN IODefs.WriteLine[st] END;

WriteCR: PROCEDURE =
BEGIN IODefs.WriteChar[IODefs.CR] END;

Resynch: PROCEDURE =
BEGIN
IODefs.WriteString["Type any key to exit."]; [] ← IODefs.ReadChar[]
END;

GetToken: PROCEDURE [stream: StreamDefs.DiskHandle, string: STRING] =
BEGIN
c: CHARACTER;
string.length ← 0;
DO
IF (c←stream.get[stream ! StreamDefs.StreamError => EXIT]) <= Space THEN
BEGIN IF string.length # 0 THEN EXIT END
ELSE String.AppendChar[string,c];
ENDLOOP;
END;

-- Checksum
checkFile: STRING = [40];
cs: StreamDefs.DiskHandle;

ComputeChecksum: PROCEDURE [stream: StreamDefs.DiskHandle] RETURNS [WORD] =
BEGIN
sum: WORD ← 0;
UNTIL stream.endof[stream] DO
sum ← LeftCycle[OnesAdd[sum, stream.get[stream]], 1];
ENDLOOP;
RETURN[sum];
END;

NewValueToMakeChecksumZero: PROCEDURE [
oldChecksum: WORD, oldValue: WORD, offsetOfOldValue: LONG CARDINAL, -- words
length: LONG CARDINAL] RETURNS [newValue: WORD] =
BEGIN
newValue ← OnesAdd[
LeftCycle[OnesSub[0, oldChecksum], offsetOfOldValue - length], oldValue]
END;

OnesAdd: PROCEDURE [a, b: CARDINAL] RETURNS [c: CARDINAL] = INLINE
BEGIN c ← a + b; IF c < a THEN c ← c + 1; IF c = 177777B THEN c ← 0; END;

OnesSub: PROCEDURE [a, b: CARDINAL] RETURNS [CARDINAL] = INLINE
BEGIN RETURN[OnesAdd[a, InlineDefs.BITNOT[b]]]; END;

LeftCycle: PROCEDURE [a: WORD, b: LONG INTEGER] RETURNS [c: CARDINAL] = INLINE
BEGIN
n: LONG INTEGER ← b MOD 16;
c ← a;
IF b < 0 THEN n ← n + 16;
UNTIL n = 0 DO
IF c < 100000B THEN c ← c*2 ELSE c ← c*2 + 1; n ← n - 1; ENDLOOP;
END;

TestForMakeImage: PROCEDURE =
-- makes image if command line says "/i"
-- returns with isCommand set up to read commands
BEGIN OPEN SegmentDefs, StreamDefs;
cfa: POINTER TO AltoFileDefs.CFA ← MiscDefs.CommandLineCFA[];
cfile: FileHandle ← InsertFile[@cfa.fp,Read];
s: STRING ← [40];
isCommand ← NIL;
isCommand ← CreateByteStream[cfile,Read ! InvalidFP => CONTINUE];
IF isCommand # NIL THEN
BEGIN
JumpToFA[isCommand,@cfa.fa];
WHILE isCommand.get[isCommand
! StreamError => GOTO nocommands] <= Space DO
NULL ENDLOOP;
SetIndex[isCommand,ModifyIndex[GetIndex[isCommand],-1]];
EXITS nocommands =>
BEGIN JumpToFA[isCommand,@cfa.fa]; RETURN END;
END;
GetToken[isCommand, s];
IF String.EquivalentString[s, "/i"L] THEN
BEGIN
isCommand.destroy[isCommand];
ImageDefs.MakeImage["MakeLoaderFile.image"L];
isCommand ← NewByteStream["Com.Cm"L, Read];
GetToken[isCommand, s]; -- skip image file name
END
ELSE
BEGIN
JumpToFA[isCommand,@cfa.fa];
END;
END;


-- Main code

BEGIN
format: Format ← ether;
appendToCurrentFile: BOOLEAN ← FALSE;
str: STRING ← [40];

TestForMakeImage[]; -- sets up isCommand
IODefs.WriteLine["MakeLoaderFile of 21-Jun-82 15:51:55"];

-- Check for command file specified in Com.cm
GetToken[isCommand, str];
isCommand.destroy[isCommand];
IF str.length#0 THEN
BEGIN
i: CARDINAL;
FOR i IN [0..str.length) DO
IF str[i] = ’. THEN EXIT;
REPEAT FINISHED => String.AppendString[str, ".mlf"];
ENDLOOP;
isCommand ← StreamDefs.NewByteStream[name: str, access: SegmentDefs.Read
! SegmentDefs.FileNameError => GOTO NoFile];
commandFile ← TRUE;
IODefs.WriteString["Commands from "]; IODefs.WriteLine[str]
EXITS NoFile => NULL
END;

-- Allocate space for IMem
IMem ← Storage.Node[SIZE[IMemCell]*(LAST[IMemRange]+1)];
ClearIMem[];

DO
OutString["Command: "];
SELECT String.LowerCase[InChar[]] FROM

’* => BEGIN tstr.length ← tstrLength; IODefs.WriteLine[tstr]; END; -- Comment

’f =>
BEGIN
wordOffset: CARDINAL = 255; -- last word on header page
byteOffset: CARDINAL = 2*wordOffset;
bytes: LONG CARDINAL;
words: CARDINAL;
oldChecksum, oldValue, newValue: WORD;
OutString["Fix Checksum of file: "];
InFileName[checkFile, ".eb"];
cs ← StreamDefs.NewWordStream[
name: checkFile, access: SegmentDefs.ReadWrite
! SegmentDefs.FileNameError => GOTO NoFile];
bytes ← StreamDefs.IndexToPosition[StreamDefs.FileLength[cs]];
IF (bytes MOD 2) # 0 THEN GOTO OddByte;
IF bytes > 64000 THEN GOTO TooLong;
words ← InlineDefs.LowHalf[bytes/2];
IODefs.WriteString["The file length is "];
IODefs.WriteDecimal[words];
IODefs.WriteLine[" words."];
StreamDefs.SetPosition[cs, byteOffset];
oldValue ← cs.get[cs];
IODefs.WriteString["The old value is "];
IODefs.WriteOctal[oldValue];
IODefs.WriteLine["."];
StreamDefs.SetPosition[cs, 0];
oldChecksum ← ComputeChecksum[cs];
IODefs.WriteString["The old checksum was "];
IODefs.WriteOctal[oldChecksum];
IODefs.WriteLine["."];
newValue ← NewValueToMakeChecksumZero[
oldChecksum: oldChecksum,
oldValue: oldValue,
offsetOfOldValue: wordOffset,
length: words];
IODefs.WriteString["The new value is "];
IODefs.WriteOctal[newValue];
IODefs.WriteLine["."];
StreamDefs.SetPosition[cs, byteOffset];
cs.put[cs, newValue];
StreamDefs.SetPosition[cs, words];
cs.destroy[cs];
EXITS
NoFile =>
BEGIN
IODefs.WriteString["Can’t find "];
IODefs.WriteLine[checkFile];
Resynch[];
EXIT;
END;
OddByte =>
BEGIN
IODefs.WriteLine["Input file has an odd length."];
Resynch[];
EXIT;
END;
TooLong =>
BEGIN
IODefs.WriteLine["Input file too long."];
Resynch[];
EXIT;
END;
END;

’r =>
BEGIN
OutString["Read file: "];
InFileName[infile, ".mb"];
is ← StreamDefs.NewWordStream[name: infile, access: SegmentDefs.Read
! SegmentDefs.FileNameError => GOTO NoFile];
LoadIMem[];
is.destroy[is]
EXITS NoFile =>
BEGIN
IODefs.WriteString["Can’t find "];
IODefs.WriteLine[infile];
Resynch[];
EXIT;
END;
END;

’p =>
BEGIN
IODefs.WriteLine["Pad to the end of this page (for making initial boot files)"];
PutPadding[];
END;

’s =>
BEGIN
IODefs.WriteLine["(Old fashioned) Soft boot format"];
format ← soft
END;

’w =>
BEGIN
startLoc: CARDINAL;
OutString["Write"];

IF NOT appendToCurrentFile THEN
BEGIN
trailer: STRING;
SELECT format FROM
soft => trailer ← ".sb";
ether => trailer ← ".eb";
ENDCASE => ERROR;
OutString[" onto file: "];
InFileName[outfile, trailer];
os ← StreamDefs.NewWordStream[name: outfile, access: SegmentDefs.Write+SegmentDefs.Append];
PutHeader[format, outfile]
END
ELSE OutLine[""];

OutString["Starting location (octal) : "]; startLoc ← InOctal[]; OutLine[" "];

OutLine["**If you want overlays, answer No to next question**"];
OutString["Close output file afterwords? "]; appendToCurrentFile ← ~InBoolean[];

DumpIMem[format, startLoc];

IODefs.WriteString[outfile]; IODefs.WriteString[" contains "];
IODefs.WriteDecimal[(wordsWritten+255)/256]; IODefs.WriteLine[" pages"];

IF ~appendToCurrentFile THEN
BEGIN PutTrailer[format]; os.destroy[os]; wordsWritten ← 0 END;

OutString["Clear Control store? "]; IF InBoolean[] THEN ClearIMem[]
END;

’q =>
BEGIN
IODefs.WriteLine["Quit"];
IF appendToCurrentFile THEN
BEGIN
IODefs.WriteLine["Error - last write didn’t close output file"];
Resynch[];
END;
EXIT
END;
ENDCASE => OutLine["Commands are: FIx Checksum, Read, Soft format, Pad to end of page, Write, Quit"]
ENDLOOP
END;

IF commandFile THEN isCommand.destroy[isCommand];
ImageDefs.StopMesa[];

END.

LOG
Time: June 13, 1978
By: HankinsAction: Created file
Time: October 25, 1978
By: HankinsAction: ???
Time: January, 1978
By: HankinsAction: ???
Time: March 12, 1979 4:21 PM
By: McJonesAction: Converted to Mesa 5; added soft booting
Time: May 2, 1979 5:07 PM
By: JohnssonAction: run from bcd
Time: May 23, 1979 5:19 PM
By: JohnssonAction: Breakpoints command
Time: June 4, 1979 9:42 AM
By: JohnssonAction: fix BP vs. CheckSum
Time: December 30, 1979 4:43 PM
By: MurrayAction: ether format
Time: Aug 80
By: MurrayAction: add padding option
Time: September 23, 1980 7:30 PM
By: MurrayAction: cleanup+bugfixes
Time: September 23, 1980 7:30 PM
By: MurrayAction: Fixup BoundsCheck glitch in checksum, delete Clear and version commands
21-Jun-82 15:50:49 Taft Add filename to header
7-Jul-82 15:38:42 Taft Make embedded date be same as file creation date
January 9, 1984 4:45 PM Taft Add PutBlock; fix bug in padding computation for header
February 6, 1984 6:56 PM, HGM, Add "F"ix checksum option (for NS booting)