DIRECTORY
AltoMicrocodeDefs: FROM "AltoMicrocodeDefs",
D0MicrocodeDefs: FROM "D0MicrocodeDefs", --used if D0
ImageDefs: FROM "ImageDefs",
InlineDefs: FROM "InlineDefs" USING [BITAND,BITOR],
JasmineDefs: FROM "JasmineDefs",
MemoryOps: FROM "MemoryOps" USING [MachineType,GetMemoryConfig],
Mopcodes: FROM "Mopcodes",
RamDefs: FROM "RamDefs", --used if AltoIIXM
SystemDefs: FROM "SystemDefs" USING [AllocateSegment,FreeSegment];

JasmineUtil: PROGRAM IMPORTS AltoMicrocodeDefs, D0MicrocodeDefs, ImageDefs, InlineDefs,
MemoryOps, RamDefs,SystemDefs,JasmineDefs EXPORTS JasmineDefs =
BEGIN OPEN InlineDefs,JasmineDefs;

--scanner settings
Reverse: BOOLEAN = FALSE;
DelaySetting
: CARDINAL ← 13;
SkipSamples: CARDINAL ← 0;
ArraySize: CARDINAL = 1024;
Time: CARDINAL ← 656;--38 usec ticks = 1/40 sec
Late: CARDINAL ← 0;
Bad: CARDINAL ← 0;
ReadMode: CARDINAL ← 2;--Read,Step
ScannerTaskBits: CARDINAL = 177376B;
StepTime: CARDINAL ← 130;--200 steps/second
TopBlank: CARDINAL ← 120;
nDiscardElements: CARDINAL ← 0;
nElements: CARDINAL ← ArraySize;
currentScanLine: CARDINAL ← 177777B;--not active
firstScanLine: CARDINAL ← 177777B;
nScanLines: CARDINAL ← 77777B;
FrontDelayCB: ScanCB ← [@DelayCB,0,CommandDELAY,StatusFREE,NIL];
DelayCB: ScanCB ← [NIL,0,CommandDELAY,StatusFREE,NIL];

Machine: MemoryOps.MachineType = MemoryOps.GetMemoryConfig[].AltoType;

SetBLV
: PUBLIC PROCEDURE[UNSPECIFIED] = MACHINE CODE BEGIN
Mopcodes.zLIW,477B/256,477B MOD 256;Mopcodes.zJRAM;
END;

JasmineCleanupProc: PROCEDURE [why: ImageDefs.CleanupReason] =
BEGIN
SELECT why FROM
Finish,Abort =>
BEGIN
JasmineMotorOff[];
JasmineControllerOff[];
ImageDefs.RemoveCleanupProcedure[@JasmineCleanupItem];
END;
OutLd => JasmineControllerOff[];
InLd => JasmineControllerOn[];
ENDCASE => ERROR;
END;

JasmineCleanupItem: ImageDefs.CleanupItem ← [NIL,177777B,JasmineCleanupProc];

JasmineInit: PUBLIC PROCEDURE =
BEGIN
muImage: RamDefs.MuImage;
ImageDefs.AddCleanupProcedure[@JasmineCleanupItem];
ScanCBHead↑ ← NIL;
ScanTime↑ ← Time;
SELECT Machine FROM
AltoI,AltoII => ERROR; --must be an AltoIIXM
AltoIIXM =>AltoMicrocodeDefs.LoadRam[];
D0 => NULL;
ENDCASE => ERROR; --unimplemented machine type
JasmineControllerOn[];
Init[];
JasmineSendCommand[SETDELAY,DelaySetting];
InitStartCommand[];
END;

JasmineSetDelay: PUBLIC PROCEDURE [delay: [0..15]] =
BEGIN
DelaySetting ← delay;
JasmineControllerOff[];
JasmineSendCommand[SETDELAY,DelaySetting];
JasmineControllerOn[];
END;

JasmineSetResolution: PUBLIC PROCEDURE [skips: [0..15]] =
BEGIN
SkipSamples ← skips;
InitStartCommand[];
END;

--readOnly=1, readAndStep=2, readStepAndDelay=3
JasmineSetReadMode: PUBLIC PROCEDURE [readMode: [1..3]] =
BEGIN
ReadMode ← readMode;
END;

JasmineNewPage: PUBLIC PROCEDURE [pageLen: CARDINAL] =--[>100 = 250u, <100=inches]
BEGIN
JasmineStep[TopBlank,TRUE];
currentScanLine ← 0;
END;

JasmineEject: PUBLIC PROCEDURE =
BEGIN
IF firstScanLine # 177777B THEN
BEGIN
JasmineStep[currentScanLine,FALSE];
JasmineStep[TopBlank+50,FALSE];
firstScanLine ← 177777B;
END;
END;

JasmineCoord: PUBLIC PROCEDURE [c: CARDINAL] RETURNS [CARDINAL] =
BEGIN
--(coord) returns # samples
RETURN[c/(SkipSamples+1)];
END;

JasmineSetWindow: PUBLIC PROCEDURE [xStart,xLen,yStart,yLen: CARDINAL] =
BEGIN--in full resolution coords (250 microns)
firstScanLine ← yStart;
nScanLines ← yLen;
IF Reverse THEN nDiscardElements ← ArraySize-(xStart+xLen)
ELSE nDiscardElements ← xStart;
nElements ← xLen;
IF Machine = AltoIIXM THEN
IF xLen < 500 THEN JasmineSetReadMode[3] ELSE JasmineSetReadMode[2];
END;

JasmineSetTime: PUBLIC PROCEDURE [ticks: CARDINAL] =
BEGIN--(ticks = 38usecond units)
Time ← ticks;
ScanTime↑ ← Time;
END;

JasmineLoadRam: PUBLIC PROCEDURE [ramvals: POINTER TO ARRAY [0..ArraySize) OF RAMval] =
BEGIN
i: CARDINAL;
load: dataOut ← [0,FALSE,LOAD,0];
we1: dataOut ← [0,FALSE,WE1,0];
we2: dataOut ← [0,FALSE,WE2,0];
we3: dataOut ← [0,FALSE,WE3,0];

JasmineControllerOff[];
JasmineSendCommand[SETDELAY,0];--enable addr counting during load
Init[];
IF Reverse THEN
FOR i DECREASING IN [0..ArraySize) DO
Pulse[load];
we1.data ← ramvals[i].q2;we2.data ← ramvals[i].q3;we3.data ← ramvals[i].q4;
Pulse[we1];
Pulse[we2];
Pulse[we3];
ENDLOOP
ELSE
FOR i IN [0..ArraySize) DO
Pulse[load];
we1.data ← ramvals[i].q2;we2.data ← ramvals[i].q3;we3.data ← ramvals[i].q4;
Pulse[we1];
Pulse[we2];
Pulse[we3];
ENDLOOP;
JasmineSendCommand[SETDELAY,DelaySetting];
Init[];
JasmineControllerOn[];
END;

JasmineScanInit: PUBLIC PROCEDURE RETURNS [POINTER TO ScanHead] =
BEGIN
nBlocks: CARDINAL = 4;
nBytes: CARDINAL = (nElements+SkipSamples)/(SkipSamples + 1);
nDiscardCommands: CARDINAL= IF nDiscardElements/(SkipSamples+1)<=1 THEN 0 ELSE 1;
wordsPerBlock: CARDINAL=(nBytes+1)/2 +
SIZE[ScanCB]*(ReadMode+SkipSamples+nDiscardCommands);
block: POINTER TO ScanHead ←
SystemDefs.AllocateSegment[SIZE[ScanHead] +nBlocks*wordsPerBlock];
blockBase: POINTER TO ScanCB ← LOOPHOLE[block + SIZE[ScanHead]];
i: CARDINAL;

block↑ ← [firstCB: blockBase,nBlocks: nBlocks,nextBlock: 0,
nCBsPerBlock: ReadMode+SkipSamples+nDiscardCommands,
nScanLines: nScanLines];
IF (firstScanLine#177777B) AND (currentScanLine # firstScanLine) THEN
BEGIN
JasmineStep[firstScanLine-currentScanLine,TRUE];
currentScanLine ← firstScanLine;
END;

ScanCBHead↑ ← NIL;
ScanTime↑ ← Time;
DelayCB.link ← NIL;
FOR i IN [0..nBlocks) DO NewReadCommand[block,i,@DelayCB.link];ENDLOOP;
ScanCBHead↑ ← @
FrontDelayCB;
RETURN[block];
END;

JasmineScanClose: PUBLIC PROCEDURE [s: POINTER TO ScanHead] =
BEGIN
ScanCBHead↑ ← NIL;
UNTIL JasmineReadLine[s,FALSE] = NIL DO ENDLOOP; --to update currentScanLine count
SystemDefs.FreeSegment[s];
END;

JasmineReadLine: PUBLIC PROCEDURE [s: POINTER TO ScanHead,continue: BOOLEAN ← TRUE]
RETURNS [POINTER TO PACKED ARRAY OF [0..377B]] =
BEGIN
thisBlock: CARDINAL ← s.nextBlock;
lastBlock: CARDINAL ← IF thisBlock = 0 THEN s.nBlocks-1 ELSE thisBlock-1;
nCBsPerBlock: CARDINAL ← s.nCBsPerBlock;
lastBlockptr: POINTER TO ScanCB ←
s.firstCB+lastBlock*nCBsPerBlock*SIZE[ScanCB];
thisBlockptr: POINTER TO ScanCB ←
s.firstCB+thisBlock*nCBsPerBlock*SIZE[ScanCB];
thisBlockend: POINTER TO ScanCB ←
thisBlockptr+(nCBsPerBlock-1)*SIZE[ScanCB];
blockStatus: JasmineCBStatus;

IF s.nScanLines <= 0 THEN continue ← FALSE;
IF (lastBlockptr.status = StatusFREE) AND continue THEN --queue up new command
NewReadCommand[s,lastBlock,ScanCBHead];

UNTIL (thisBlockend.status < 0) OR (ScanCBHead↑ = NIL) DO ENDLOOP;

IF thisBlockptr.buffer = NIL THEN
--was a discard
BEGIN
thisBlockptr.status ← StatusFREE;
thisBlockptr ← thisBlockptr.link;
UNTIL (thisBlockend.status < 0) OR (ScanCBHead↑ = NIL) DO ENDLOOP;
END;

blockStatus ← thisBlockptr.status;

s.nextBlock ← IF thisBlock = s.nBlocks-1 THEN 0 ELSE thisBlock+1;
thisBlockptr.status ← StatusFREE;


IF blockStatus = StatusDONE THEN
BEGIN
IF firstScanLine # 177777B THEN
currentScanLine ← currentScanLine+SkipSamples+1;
s.nScanLines ← s.nScanLines-(SkipSamples+1);
RETURN[thisBlockptr.buffer];
END;

IF NOT continue THEN RETURN[NIL];

IF blockStatus = StatusDATALATE THEN
BEGIN
Late ← Late+1;--CallSwat("DataLate")
JasmineSetDelay[DelaySetting];
DelayCB.link ← thisBlockend.link;
ScanCBHead↑ ← @FrontDelayCB;
RETURN[JasmineReadLine[s]];
END;

Bad ← Bad+1;--CallSwat("Bad")
RETURN[NIL];--NextScanLine(s)
END;

JasmineStep: PUBLIC PROCEDURE [nSteps: INTEGER,forward: BOOLEAN] =
BEGIN
reversed: BOOLEAN = nSteps < 0;
v: ScanCB;
CBHandle: POINTER TO ScanCB ← @v;

IF reversed THEN
BEGIN
forward ← NOT forward;
nSteps ← -nSteps;
END;

--
LOOK OUT!
--if you access "v.foo", and if v.foo is
--near the top of the local frame, it will be held in a microcode
--register on the D0, and won’t actually dereference the memory
--location until the next process switch (at 60Hz)
CBHandle.link ← NIL;
CBHandle.command ← IF forward THEN CommandFORWARD ELSE CommandBACK;
CBHandle.blank ← 0;
UNTIL ScanCBHead↑ = NIL DO ENDLOOP;
ScanTime↑ ← StepTime;
THROUGH [1..nSteps] DO
CBHandle.status ← StatusINUSE;
ScanCBHead↑ ← CBHandle;
UNTIL CBHandle.status = StatusDONE DO ENDLOOP;
ENDLOOP;

ScanTime↑ ← Time;
END;

--and the local procedures

JasmineControllerOn: PUBLIC PROCEDURE =
BEGIN
SELECT Machine FROM
AltoIIXM =>
BEGIN
SetBLV[ScannerTaskBits];[] ← RamDefs.StartIO[100000B];
END;
D0 => D0MicrocodeDefs.JumpRam[7010B];
ENDCASE => ERROR;
END;

JasmineControllerOff: PUBLIC PROCEDURE =
BEGIN
SELECT Machine FROM
AltoIIXM =>
BEGIN
SetBLV[177776B];[] ← RamDefs.StartIO[100000B];
END;
D0 => D0MicrocodeDefs.JumpRam[7011B];
ENDCASE => ERROR;
END;

NewReadCommand: PUBLIC PROCEDURE [block: POINTER TO ScanHead,num: CARDINAL,scanHead: POINTER TO POINTER TO ScanCB] =
BEGIN
nDiscardBytes: CARDINAL ← BITAND[nDiscardElements/(SkipSamples+1),177776B];
nBytes: CARDINAL ← BITAND[(nElements+SkipSamples)/(SkipSamples + 1)+1,177776B];
nWords: CARDINAL ← (nBytes+1)/2;
nCBsPerBlock: CARDINAL ← block.nCBsPerBlock;
base: POINTER TO ScanCB ← block.firstCB + num*nCBsPerBlock*SIZE[ScanCB];
bufferBase: POINTER TO PACKED ARRAY OF [0..377B] ←
LOOPHOLE[block.firstCB +(block.nBlocks)*nCBsPerBlock*SIZE[ScanCB]];
link,newLink: POINTER TO POINTER TO ScanCB;

thisCB: POINTER TO ScanCB ← base;
IF nDiscardBytes > 0 THEN
BEGIN
thisCB↑ ← [link: thisCB + SIZE[ScanCB],blank: 0,command: CommandREAD,
status: nDiscardBytes,buffer: NIL];
thisCB ← thisCB + SIZE[ScanCB];
END;

thisCB↑ ← [link: NIL,blank: 0,command: CommandREAD,
status: nBytes,buffer: bufferBase + num*nWords];

IF ReadMode > 1 THEN
BEGIN
THROUGH [0..SkipSamples] DO
thisCB.link ← thisCB + SIZE[ScanCB];
thisCB ← thisCB + SIZE[ScanCB];
thisCB↑ ← [link: NIL,blank: 0,command: CommandFORWARD,
status: StatusINUSE,buffer: ];
ENDLOOP;

IF ReadMode > 2 THEN
BEGIN
thisCB.link ← thisCB + SIZE[ScanCB];
thisCB ← thisCB + SIZE[ScanCB];
thisCB↑ ← [link: NIL,blank: 0,command: CommandDELAY,
status: StatusINUSE,buffer: ];
END;
END;

--the following code doesn’t work:
--
UNTIL link↑ eq NIL do link ← link↑
--because if link = scanHead, then
--between the test for "eq 0" and the assignment, scanHead may be zeroed
--by the ucode, causing us to add a chain through 77400 (0↑)

link ← scanHead;
DO
newLink ← LOOPHOLE[link↑];
IF newLink = NIL THEN EXIT;
link ← newLink;
ENDLOOP;
link↑ ← base;
IF scanHead↑ = NIL THEN scanHead↑ ← base;
END;

Init: PUBLIC PROCEDURE =
BEGIN
JasmineSendCommand[LOAD,0];JasmineSendCommand[SCANSTART,0];
END;

InitStartCommand: PUBLIC PROCEDURE =
BEGIN
StartCommand↑ ← [blank:0,enable:FALSE,command:SCANSTART,data:SkipSamples];
END;

JasmineMotorOff: PUBLIC PROCEDURE =
BEGIN
JasmineSendCommand[MOTORCTL,4];
END;

JasmineSendCommand: PUBLIC PROCEDURE [command: JasmineCommand,data: [0..17B]] =
BEGIN
Pulse[[blank:0,enable: FALSE,command: command,data: data]];
END;
Pulse: PUBLIC PROCEDURE [d: dataOut] =
BEGIN
enable: dataOut = [0,TRUE,LOOPHOLE[0],0];
SELECT Machine FROM
AltoIIXM =>
BEGIN
Outport↑ ← d;
Outport↑ ← InlineDefs.BITOR[d,enable];
Outport↑ ← d;
END;
D0 => D0MicrocodeDefs.JumpRamArg[7012B,d];
ENDCASE => ERROR;
END;

END.