ICTestImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Created by: Gasbarro December 3, 1985 6:15:34 pm PST
Last Edited by: Gasbarro October 22, 1986 10:22:27 am PDT
DIRECTORY Basics, Buttons, ChoiceButtons, Commander, CommandTool, Containers, Convert, Core, CoreOps, --EGlas,-- FS, HashTable, ICTest, IO, IMSTester, MessageWindow, Ports, Rope, Rules, RuntimeError, SymTab, ViewerClasses, ViewerIO, ViewerOps, ViewerTools;
ICTestImpl: CEDAR PROGRAM
IMPORTS Basics, Buttons, ChoiceButtons, Commander, CommandTool, Containers, Convert, CoreOps, --EGlas,-- FS, HashTable, IO, IMSTester, MessageWindow, Ports, Rope, Rules, RuntimeError, SymTab, ViewerIO, ViewerOps, ViewerTools
EXPORTS ICTest
= BEGIN
OPEN ICTest;
maxErrors: NAT ← 40;
testNamesTable: HashTable.Table ← HashTable.Create[equal: HashTable.RopeEqual, hash: HashTable.HashRope];
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
Button: TYPE = Buttons.Button;
signalNameLength: CARDINAL = 6; --IMS signal name length restriction
groupNameLength: CARDINAL = 9; --IMS group name name length restriction
PodTimingGroups: TYPE = IMSTester.PodTimingGroups;
Board: TYPE = IMSTester.Board;
PodTiming: TYPE = IMSTester.PodTiming;
Channel: TYPE = IMSTester.Channel;
PodChannel: TYPE = IMSTester.PodChannel;
Cycle: TYPE = IMSTester.Cycle;
Jumps: TYPE = IMSTester.Jumps;
AbortDieSignal: PUBLIC ERROR = CODE;
AbortWaferSignal: PUBLIC ERROR = CODE;
InterruptSignal: PUBLIC ERROR = CODE;
Test: TYPE = REF TestRec;
TestRec: TYPE = RECORD[proc: TestProc, start: Cycle, length: Cycle];
Stop: TYPE = {dont, abortDie, abortWafer, interrupt};
DiePosition: TYPE = RECORD [x,y: NAT];
MapRec: TYPE = RECORD[b: Board, p: PodChannel, port: Ports.Port, index: NAT];
BoardToSlot: TYPE = ARRAY Board OF RECORD[slot: NAT, programable: BOOL];
ForceSubGroupsRec: TYPE = RECORD[fullName: ROPE, subGroups: IMSTester.ForceGroups];
AcquireSubGroupsRec: TYPE = RECORD[fullName: ROPE, subGroups: IMSTester.AcquireGroups];
Handle: TYPE = REF ICTestRec;
ICTestRec: PUBLIC TYPE = RECORD [
-- Standard button state --
waferFile: Viewer ← NIL,
run: Viewer ← NIL,
wafer: Viewer ← NIL,
die: Viewer ← NIL,
errorCycle: Viewer ← NIL,
startTest: Button ← NIL,
period: Viewer ← NIL,
group: Viewer ← NIL,
delay: Viewer ← NIL,
width: Viewer ← NIL,
sample: Viewer ← NIL,
hiDrive: Viewer ← NIL,
loDrive: Viewer ← NIL,
threshold: Viewer ← NIL,
enableStepper: BOOLFALSE,
enableTester: BOOLFALSE,
enableSimulation: BOOLFALSE,
singleCycle: BOOLFALSE,
loopTest: BOOLFALSE,
-- Test buttons state--
testButtonContainer: Viewer ← NIL,
testButtonList: LIST OF Button ← NIL,
testProcsTable: HashTable.Table ← NIL,
currentTestProc: ROPENIL,
-- Typescript --
typeScriptContainer: Viewer ← NIL,
outStream: IO.STREAMNIL,
firstFreeCycle: Cycle ← 0,
currentDie: DiePosition, -- die to return to upon "Continue" button
stop: Stop ← dont, -- state of control buttons
backupToken: IO.STREAM, -- temp for file stream parsing
testInProgress: BOOLFALSE, -- button monitor
inStream: IO.STREAMNIL,
port: Ports.Port ← NIL,
cellType: Core.CellType ← NIL,
clockAName: ROPENIL,
clockBName: ROPENIL,
clockAPort: Ports.Port ← NIL,
clockBPort: Ports.Port ← NIL,
forceGroups: IMSTester.ForceGroups ← NIL,
acquireGroups: IMSTester.AcquireGroups ← NIL,
forceMap: LIST OF MapRec ← NIL,
acquireMap: LIST OF MapRec ← NIL,
cycle: Cycle ← 0,
buffer: IMSTester.Buffer ← NIL,
nullCycleData: IMSTester.CycleData ← NIL,
forceNamesTab: SymTab.Ref ← NIL,
acquireNamesTab: SymTab.Ref ← NIL,
groups: LIST OF Group ← NIL,
assignments: LIST OF Assignments ← NIL,
forceBoardToSlot: REF BoardToSlot ← NEW[BoardToSlot],
acquireBoardToSlot: REF BoardToSlot ← NEW[BoardToSlot],
forceSubGroups: LIST OF ForceSubGroupsRec,
acquireSubGroups: LIST OF AcquireSubGroupsRec
]; 
entryHeight: NAT = 15; -- how tall to make each line of items
entryVSpace: NAT = 4;  -- vertical leading space between lines
pointsPerInch: NAT = 72;  -- horizontal space for text ropes
pointsPerHalfInch: NAT = 36;  -- horizontal space for text ropes
col1: NAT = 0*pointsPerHalfInch; -- horizontal space to first column of buttons
col2: NAT = 4*pointsPerHalfInch; -- second column
col3: NAT = 7*pointsPerHalfInch; -- third column
col4: NAT = 10*pointsPerHalfInch; -- fourth column
col5: NAT = 13*pointsPerHalfInch; -- fifth column
Column: TYPE = NAT [0..5);
ColumnStart: ARRAY Column OF NAT = [col1, col2, col3, col4, col5];
MakeStandardViewer: PUBLIC PROC [testName: ROPE, cellType: Core.CellType, clockAName: ROPE, clockBName: ROPENIL, groups: LIST OF Group, assignments: LIST OF Assignments, period: Period] = {
Button: PROC [name: ROPE, col: NAT, proc: Buttons.ButtonProc, fork: BOOLTRUE] = {
[] ← Buttons.Create[info: [name: name, wx: col, wy: height, ww: 0, wh: entryHeight,
parent: standardButtons], clientData: h, proc: proc, fork: fork];
};
Prompt: PROC [name: ROPE, col: NAT, contents: ROPENIL] RETURNS [Viewer] = {
RETURN[ChoiceButtons.BuildTextPrompt[standardButtons, col, height, name, contents, NIL, 1*pointsPerHalfInch].textViewer];
};
h: Handle ← NEW[ICTestRec];
rule: Rules.Rule;
typeScript: Viewer;
height: CARDINAL ← 0;
viewer: Viewer ← Containers.Create[[name: Rope.Concat["IC Test Tool - ", testName], scrollable: FALSE]];
***Standard Buttons***
standardButtons: Viewer ← Containers.Create[[scrollable: FALSE, parent: viewer, border: FALSE]];
Containers.ChildXBound[viewer, standardButtons];
Containers.ChildYBound[viewer, standardButtons];
h.waferFile ← ChoiceButtons.BuildTextPrompt[standardButtons, col1, height, "Wafer File:", "SingleDie.dat", NIL, 7*pointsPerInch].textViewer;
height ← height + entryHeight + entryVSpace;
h.run ← Prompt["Run:", col1];
h.startTest ← Buttons.Create[info: [name: "Start Test", wx: col2, wy: height, wh: entryHeight,
parent: standardButtons], clientData: h, proc: StartTest, fork: TRUE];
Button["Interrupt", col3, Interrupt, FALSE]; Button["Single Cycle", col4, SingleCycle]; h.period ← Prompt["Period (nS):", col5, IO.PutR1[IO.int[period]]];
height ← height + entryHeight + entryVSpace;
h.wafer ← Prompt["Wafer:", col1]; Button["Abort Die", col2, AbortDie, FALSE]; Button["Continue", col3, Continue]; Button["Loop Test", col4, LoopTest];
height ← height + entryHeight + entryVSpace;
h.die ← Prompt["Die:", col1]; Button["Abort Wafer", col2, AbortWafer, FALSE]; Button["Enable Tester", col3, EnableTester]; Button["Stop Loop", col4, StopLoop];
height ← height + entryHeight + entryVSpace;
h.errorCycle ← Prompt["Error Cycle:", col1, "0"]; Button["Abort Test", col2, AbortTest, FALSE]; Button["Enable Stepper", col3, EnableStepper]; Button["Get Errors", col4, GetErrors];
height ← height + entryHeight + entryVSpace/2+1;
rule ← Rules.Create[[parent: standardButtons, wy: height, wh: 2]];
Containers.ChildXBound[standardButtons, rule];
height ← height + entryVSpace;
Button["Get Parameters", col1+2, GetParameters]; Button["Set Parameters", col2, SetParameters]; h.delay ← Prompt["Delay:", col3, "?"]; h.width ← Prompt["Width:", col4, "?"]; h.sample ← Prompt["Sample:", col5, "?"];
height ← height + entryHeight + entryVSpace;
h.group ← ChoiceButtons.BuildTextPrompt[standardButtons, col1, height, "Group:", "<Group name>", NIL, 2*pointsPerInch].textViewer; h.hiDrive ← Prompt["HiDrive:", col3, "?"]; h.loDrive ← Prompt["LoDrive:", col4, "?"]; h.threshold ← Prompt["Threshold:", col5, "?"];
height ← height + entryHeight + entryVSpace/2+1;
rule ← Rules.Create[[parent: standardButtons, wy: height, wh: 2]];
Containers.ChildXBound[standardButtons, rule];
height ← height + 2;
***Test Buttons***
h.testButtonContainer ← Containers.Create[[wy: height, wh: entryVSpace, scrollable: FALSE, parent: viewer, border: FALSE]];
Containers.ChildXBound[viewer, h.testButtonContainer];
height ← height + entryVSpace;
***TypeScript***
h.typeScriptContainer ← Containers.Create[[wy: height, scrollable: FALSE, parent: viewer, border: FALSE]];
Containers.ChildXBound[viewer, h.typeScriptContainer];
Containers.ChildYBound[viewer, h.typeScriptContainer];
rule ← Rules.Create[[parent: h.typeScriptContainer, wy: 0, wh: 2]];
Containers.ChildXBound[h.typeScriptContainer, rule];
typeScript ← ViewerOps.CreateViewer[flavor: $TypeScript, info:[parent: h.typeScriptContainer, wy: 2, ww: 7*pointsPerInch, wh: 6*pointsPerInch, scrollable: TRUE, border: FALSE]];
Containers.ChildXBound[h.typeScriptContainer, typeScript];
Containers.ChildYBound[h.typeScriptContainer, typeScript];
[ , h.outStream] ← ViewerIO.CreateViewerStreams[name: "ICTestTS", viewer: typeScript];
Commander.Register[key: "///Commands/Name", proc: LookUpName,
doc: "Map an IMS signal name to/from the shortened version", clientData: h];
h.testProcsTable ← HashTable.Create[equal: HashTable.RopeEqual, hash: HashTable.HashRope];
[] ← HashTable.Store[table: testNamesTable, key: testName, value: h];
h.cellType ← cellType;
h.groups ← groups;
h.assignments ← assignments;
h.clockAName ← clockAName;
h.clockBName ← clockBName;
};
RegisterTestProc: PUBLIC PROC [testName: ROPE, procName: ROPE, proc: TestProc] = {
h: Handle ← NIL;
ref: REFNIL;
found: BOOL ← FALSE;
row, column: CARDINAL;
count: CARDINAL ← 0;
button: Button ← NIL;
[found, ref] ← HashTable.Fetch[testNamesTable, testName];
IF NOT found THEN ERROR; --couldn't find the tester handle, probably bad testName
h ← NARROW[ref];
IF NOT HashTable.Fetch[h.testProcsTable, procName].found THEN { --add a new button
FOR l: LIST OF Button ← h.testButtonList, l.rest WHILE l#NIL DO count ← count+1 ENDLOOP;
row ← count/(LAST[Column]+1);
column ← count MOD (LAST[Column]+1);
IF column=FIRST[Column] THEN { --make a new row
ViewerOps.MoveViewer[h.testButtonContainer, h.testButtonContainer.wx, h.testButtonContainer.wy, h.testButtonContainer.ww, h.testButtonContainer.wh+entryHeight+entryVSpace];
ViewerOps.MoveViewer[h.typeScriptContainer, h.typeScriptContainer.wx, h.typeScriptContainer.wy+entryHeight+entryVSpace, h.typeScriptContainer.ww, h.typeScriptContainer.wh];
};
button ← Buttons.Create[info: [name: procName,
wx: ColumnStart[column]+2, wy: row*(entryHeight+entryVSpace)+entryVSpace,
ww: 0, wh: entryHeight,
parent: h.testButtonContainer, border: TRUE], clientData: h, proc: TestButtonProc];
h.testButtonList ← CONS[button, h.testButtonList];
};
[] ← HashTable.Store[table: h.testProcsTable, key: procName, value: NEW[TestRec ← [proc, 0, 0]]];
};
TestButtonProc: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
selectedButton: ViewerClasses.Viewer ← NARROW[parent];
FOR l: LIST OF Button ← h.testButtonList, l.rest WHILE l#NIL DO
Buttons.SetDisplayStyle[l.first, IF l.first = selectedButton THEN $WhiteOnBlack ELSE $BlackOnWhite];
ENDLOOP;
h.currentTestProc ← selectedButton.name;
};
DoTest: PROC [h: Handle] = {
NextToken: PROC [h: Handle] RETURNS [s: IO.STREAM] = {
s ← IF h.backupToken # NIL THEN h.backupToken ELSE IO.RIS[IO.GetTokenRope[h.inStream].token];
h.backupToken ← NIL;
};
BackupToken: PROC [s: IO.STREAM] RETURNS [] = {
h.backupToken ← s;
};
NextRun: PROC [h: Handle] RETURNS [done: BOOLEANFALSE] = {
c: CHAR;
s: IO.STREAM;
IF h.stop # interrupt THEN {
WHILE NOT done DO
s ← NextToken[h ! IO.EndOfStream => {done ← TRUE; CONTINUE}];
IF NOT done THEN SELECT (c ← IO.PeekChar[s]) FROM
'r, 'R => EXIT;
'w, 'W => NULL;
'd, 'D => NULL;
IN ['0..'9] => NULL;
ENDCASE => {Message["Error: Bad token in data file, aborting test"]; done ← TRUE};
ENDLOOP;
IF NOT done THEN {
ViewerTools.SetContents[h.run, IO.GetTokenRope[NextToken[h !
IO.EndOfStream => {done ← TRUE; CONTINUE}]].token];
IO.PutF[h.outStream, "\n%g: Run %g, ", IO.rope[h.currentTestProc], IO.rope[ViewerTools.GetContents[h.run]]];
};
IF done THEN {
IO.Close[h.inStream];
IF NOT h.loopTest THEN h.testInProgress ← FALSE;
};
};
};
NextWafer: PROC [h: Handle] RETURNS [done: BOOLEANFALSE] = {
c: CHAR;
s: IO.STREAM;
IF h.stop # interrupt THEN {
WHILE NOT done DO
s ← NextToken[h ! IO.EndOfStream => {done ← TRUE; CONTINUE}];
IF NOT done THEN SELECT (c ← IO.PeekChar[s]) FROM
'r, 'R => {done ← TRUE; BackupToken[s]};
'w, 'W => EXIT;
'd, 'D => NULL;
IN ['0..'9] => NULL;
ENDCASE => {Message["Error: Bad token in data file, aborting test"]; done ← TRUE};
ENDLOOP;
IF NOT done THEN {
ViewerTools.SetContents[h.wafer, IO.GetTokenRope[NextToken[h !
IO.EndOfStream => {done ← TRUE; CONTINUE}]].token];
IO.PutF[h.outStream, "Wafer %g, ", IO.rope[ViewerTools.GetContents[h.wafer]]];
IF h.enableStepper THEN {
EGlas.LampOn[];
IF h.testInProgress THEN EGlas.Load[]; --don't load wafer first time
} ;
};
};
};
NextDie: PROC [h: Handle] RETURNS [done: BOOLEANFALSE] = {
Eval: PROC = {
CheckStop[h];
IF (h.clockAPort#NIL AND h.clockAPort.b) OR (h.clockBPort#NIL AND h.clockBPort.b) THEN {
ForceDataToBuffer[h];
CompareDataToBuffer[h];
IF h.singleCycle THEN {
BufferToIMS[h: h, cycles: 1, start: 0, halt: 0];
IMSTester.Start[];
} ELSE {
h.cycle ← h.cycle+1;
IF h.cycle = h.buffer.cycle THEN NewBuffer[h];
};
};
};
count: LONG CARDINAL;
c: CHAR;
s: IO.STREAM;
IF h.stop # interrupt THEN {
WHILE NOT done DO
s ← NextToken[h ! IO.EndOfStream => {done ← TRUE; CONTINUE}];
IF NOT done THEN SELECT (c ← IO.PeekChar[s]) FROM
'r, 'R => {done ← TRUE; BackupToken[s]};
'w, 'W => {done ← TRUE; BackupToken[s]};
'd, 'D => h.currentDie.y ← IO.GetInt[NextToken[h !
IO.EndOfStream => {done ← TRUE; CONTINUE}]];
IN ['0..'9] => EXIT;
ENDCASE => {Message["Error in data file, aborting test"]; done ← TRUE};
ENDLOOP;
IF NOT done THEN {
h.currentDie.x ← IO.GetInt[s];
ViewerTools.SetContents[h.die, IO.PutFR["%g,%g", IO.int[h.currentDie.y], IO.int[h.currentDie.x]]];
IO.PutF[h.outStream, "Die %g,%g ", IO.int[h.currentDie.y], IO.int[h.currentDie.x]];
};
};
IF NOT done THEN {
h.stop ← dont;
h.testInProgress ← TRUE;
IF h.enableStepper THEN {
EGlas.Seek[h.currentDie.x, h.currentDie.y];
EGlas.ZUp[];
EGlas.LampOff[];
};
IF h.singleCycle THEN test.proc[h.cellType, h.port, Eval] ELSE {
IF test.start=0 THEN {
test.proc[h.cellType, h.port, Eval];
IF h.cycle=0 THEN {
Message["Error: test has zero cycles (maybe you never set clock TRUE?)"];
RETURN[];
};
IF h.firstFreeCycle+h.cycle > LAST[Cycle] THEN {
Message["Out of vector memory space"];
RETURN[];
};
BufferToIMS[h: h, cycles: h.cycle, start: h.firstFreeCycle];
test.start ← h.firstFreeCycle;
test.length ← h.cycle;
h.firstFreeCycle ← h.firstFreeCycle+h.cycle+1; --reserve one word for halt inst.
};
IF h.loopTest THEN Jump[h, test.start+test.length, test.start] ELSE
Halt[h, test.start+test.length];
Dispatch[h, test.start];
IF h.enableStepper THEN EGlas.LampOn[];
IF NOT h.loopTest THEN {
count ← IMSTester.ErrorCount[];
IO.PutF[h.outStream, IF count#0 THEN "Fail " ELSE "Pass; "];
IF count#0 THEN IO.PutF[h.outStream, "%g errors; ", IO.card[count]];
};
};
};
};
test: Test ← NARROW[HashTable.Fetch[table: h.testProcsTable, key: h.currentTestProc].value];
IF h.inStream # NIL THEN
DO IF NextRun[h] THEN EXIT;
DO IF NextWafer[h] THEN EXIT;
DO IF NextDie[h !
AbortDieSignal => CONTINUE;
AbortWaferSignal => EXIT] THEN EXIT
ENDLOOP;
ENDLOOP;
ENDLOOP;
IO.PutF[h.outStream, "(Start address %g) Done\n", IO.int[test.start]];
IF NOT h.loopTest THEN h.testInProgress ← FALSE;
};
Jump: PROC [h: Handle, source: Cycle, dest: Cycle] ~ {
tempCycleData: IMSTester.CycleData ← h.buffer[0];
h.buffer[0] ← h.nullCycleData;
BufferToIMS[h: h, cycles: 1, start: source, jumps: LIST[[source, dest]]];
h.buffer[0] ← tempCycleData;
};
Halt: PROC [h: Handle, cycle: Cycle] ~ {
tempCycleData: IMSTester.CycleData ← h.buffer[0];
h.buffer[0] ← h.nullCycleData;
BufferToIMS[h: h, cycles: 1, start: cycle, halt: cycle];
h.buffer[0] ← tempCycleData;
};
Dispatch: PROC [h: Handle, start: Cycle] ~ {
Jump[h, 0, start];
IMSTester.Start[];
};
BufferToIMS: PROC [h: Handle, cycles, start, halt: Cycle←LAST[Cycle], jumps: Jumps←NIL] = {
IMSTester.SetIMSMemory[h.forceGroups, h.acquireGroups, h.buffer, cycles, start, halt, jumps];
CheckStop[h];
};
NewBuffer: PROC [h: Handle] = {
oldBuffer: IMSTester.Buffer ← h.buffer;
h.buffer ← NEW[IMSTester.BufferRec[SELECT TRUE FROM
h.cycle<10 => 10,
h.cycle<100 => 100,
h.cycle<1000 => 1000,
h.cycle<10000 => 10000,
ENDCASE => 16383]];
FOR i: NAT IN [0..h.buffer.cycle) DO
h.buffer[i] ← IF i < h.cycle THEN oldBuffer[i] ELSE NEW[IMSTester.CycleDataRec];
ENDLOOP;
};
StartTest: Buttons.ButtonProc = {
ENABLE IO.Error, RuntimeError.BoundsFault => {Message["Illegal parameter"]; CONTINUE};
h: Handle ← NARROW[clientData];
selectedButton: Viewer ← NARROW[parent];
IF h.testInProgress THEN Message["Test in progress"];
IF h.currentTestProc=NIL THEN Message["Please select a test procedure"];
IF NOT h.testInProgress AND h.currentTestProc#NIL THEN {
IF h.loopTest THEN Buttons.SetDisplayStyle[selectedButton, $BlackOnGrey];
IMSTester.stop ← FALSE;
h.stop ← dont;
IF h.inStream # NIL THEN IO.Close[h.inStream]; --just in case last test was aborted
Init[h];
DoTest[h ! InterruptSignal => CONTINUE];
Cleanup[h];
};
};
AbortDie: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
IMSTester.stop ← TRUE;
h.stop ← abortDie;
};
AbortWafer: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
IMSTester.stop ← TRUE;
h.stop ← abortWafer;
};
AbortTest: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
IMSTester.stop ← TRUE;
h.stop ← interrupt;
h.testInProgress ← FALSE;
};
CheckStop: PROC [h: Handle] = {
IMSTester.stop ← FALSE;
SELECT h.stop FROM
dont => NULL;
abortDie => {Message["Abort die"]; ERROR AbortDieSignal};
abortWafer => {Message["Abort wafer"]; ERROR AbortWaferSignal};
interrupt => {Message["Interrupt"]; ERROR InterruptSignal};
ENDCASE => ERROR;
};
EnableTester: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
selectedButton: Viewer ← NARROW[parent];
h.enableTester ← NOT h.enableTester;
Buttons.SetDisplayStyle[selectedButton, IF h.enableTester THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
EnableStepper: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
selectedButton: Viewer ← NARROW[parent];
h.enableStepper ← NOT h.enableStepper;
Buttons.SetDisplayStyle[selectedButton, IF h.enableStepper THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
GetParameters: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
ReallyGetParameters[h];
};
ReallyGetParameters: PROC [h: Handle] = {
targetGroup: ROPE ← ViewerTools.GetContents[h.group];
fg: IMSTester.ForceGroup ← NIL;
ag: IMSTester.AcquireGroup ← NIL;
FOR l: LIST OF ForceSubGroupsRec ← h.forceSubGroups, l.rest WHILE l#NIL DO
IF Rope.Equal[l.first.fullName, targetGroup] THEN {
fg ← l.first.subGroups.first;
ViewerTools.SetContents[h.delay, IO.PutR1[IO.int[fg.delay]]];
ViewerTools.SetContents[h.width, IO.PutR1[IO.int[fg.width]]];
ViewerTools.SetContents[h.hiDrive, IF fg.programable THEN IO.PutR1[IO.real[fg.hiDrive]] ELSE "TTL"];
ViewerTools.SetContents[h.loDrive, IF fg.programable THEN IO.PutR1[IO.real[fg.loDrive]] ELSE "TTL"];
EXIT;
};
REPEAT
FINISHED => {
ViewerTools.SetContents[h.delay, "?"];
ViewerTools.SetContents[h.width, "?"];
ViewerTools.SetContents[h.hiDrive, "?"];
ViewerTools.SetContents[h.loDrive, "?"];
};
ENDLOOP;
FOR l: LIST OF AcquireSubGroupsRec ← h.acquireSubGroups, l.rest WHILE l#NIL DO
IF Rope.Equal[l.first.fullName, targetGroup] THEN {
ag ← l.first.subGroups.first;
ViewerTools.SetContents[h.sample, IO.PutR1[IO.int[ag.sample]]];
ViewerTools.SetContents[h.threshold, IF ag.programable THEN IO.PutR1[IO.real[ag.threshold]] ELSE "TTL"];
EXIT;
};
REPEAT
FINISHED => {
ViewerTools.SetContents[h.sample, "?"];
ViewerTools.SetContents[h.threshold, "?"];
};
ENDLOOP;
IF fg=NIL AND ag=NIL THEN Message[IO.PutFR1["Group name \"%g\" not found.\n", IO.rope[targetGroup]]];
};
SetParameters: Buttons.ButtonProc = {
ENABLE IO.Error, RuntimeError.BoundsFault => {Message["Illegal parameter"]; CONTINUE};
h: Handle ← NARROW[clientData];
ReallySetParameters[h];
};
ReallySetParameters: PROC [h: Handle] = {
fg: IMSTester.ForceGroups ← NIL;
ag: IMSTester.AcquireGroups ← NIL;
targetGroup: ROPE ← ViewerTools.GetContents[h.group];
FOR l: LIST OF ForceSubGroupsRec ← h.forceSubGroups, l.rest WHILE l#NIL DO
IF Rope.Equal[l.first.fullName, targetGroup] THEN {
fg ← l.first.subGroups;
FOR f: IMSTester.ForceGroups ← fg, f.rest WHILE f#NIL DO
f.first.delay ← IO.GetInt[IO.RIS[ViewerTools.GetContents[h.delay]]];
f.first.width ← ((IO.GetInt[IO.RIS[ViewerTools.GetContents[h.width]]]+5)/10)*10;
IF f.first.programable THEN {
f.first.hiDrive ← IO.GetReal[IO.RIS[ViewerTools.GetContents[h.hiDrive]]];
f.first.loDrive ← IO.GetReal[IO.RIS[ViewerTools.GetContents[h.loDrive]]];
};
ENDLOOP;
};
ENDLOOP;
FOR l: LIST OF AcquireSubGroupsRec ← h.acquireSubGroups, l.rest WHILE l#NIL DO
IF Rope.Equal[l.first.fullName, targetGroup] THEN {
ag ← l.first.subGroups;
FOR a: IMSTester.AcquireGroups ← ag, a.rest WHILE a#NIL DO
a.first.sample ← IO.GetInt[IO.RIS[ViewerTools.GetContents[h.sample]]];
IF a.first.programable THEN a.first.threshold ← IO.GetReal[IO.RIS[ViewerTools.GetContents[h.threshold]]];
ENDLOOP;
};
ENDLOOP;
ReallyGetParameters[h];
IF fg#NIL OR ag#NIL THEN IMSTester.RedefineGroups[fg, ag]
ELSE Message[IO.PutFR1["Group name \"%g\" not found.\n", IO.rope[targetGroup]]];
};
Interrupt: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
h.stop ← interrupt;
};
Continue: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
IMSTester.stop ← FALSE;
IF h.testInProgress AND (h.stop = interrupt) THEN {
DoTest[h ! InterruptSignal => CONTINUE];
Cleanup[h];
};
};
SingleCycle: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
selectedButton: Viewer ← NARROW[parent];
h.singleCycle ← NOT h.singleCycle;
Buttons.SetDisplayStyle[selectedButton, IF h.singleCycle THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
LoopTest: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
selectedButton: Viewer ← NARROW[parent];
h.loopTest ← NOT h.loopTest;
Buttons.SetDisplayStyle[selectedButton, IF h.loopTest THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
StopLoop: Buttons.ButtonProc = {
h: Handle ← NARROW[clientData];
IF h.testInProgress AND h.loopTest THEN Buttons.SetDisplayStyle[h.startTest, $BlackOnWhite];
IMSTester.Stop[];
h.testInProgress ← FALSE;
};
GetErrors: Buttons.ButtonProc = {
ENABLE IO.Error, Convert.Error, RuntimeError.BoundsFault => {Message["Illegal parameter"]; CONTINUE};
h: Handle ← NARROW[clientData];
errors: IMSTester.Errors;
IF h.enableTester THEN {
errors ← IMSTester.GetErrors[h.acquireGroups, h.buffer, maxErrors, Convert.IntFromRope[ViewerTools.GetContents[h.errorCycle]], h.cycle];
IO.PutChar[h.outStream, '\n];
FOR l: IMSTester.Errors ← errors, l.rest WHILE l#NIL DO
IO.PutF[h.outStream, "Cycle: %g, %g, Expected: %g\n", IO.int[l.first.cycle], IO.rope[GetName[h, l.first.pin.signalName].acquireName], IO.bool[l.first.expected]];
ViewerTools.SetContents[h.errorCycle, IO.PutR1[IO.int[l.first.cycle]]];
ENDLOOP;
};
};
Message: PROC [rope: ROPE] = {
MessageWindow.Append[rope, TRUE];
MessageWindow.Blink[];
MessageWindow.Append[rope, TRUE];
};
ForceDataToBuffer: PROC [h: Handle] = {
cycle: Cycle ← h.cycle;
FOR l: LIST OF MapRec ← h.forceMap, l.rest WHILE l#NIL DO
board: IMSTester.Board ← l.first.b;
pod: IMSTester.PodChannel ← l.first.p;
port: Ports.Port ← l.first.port;
index: NAT ← l.first.index;
inhibit: BOOLIF port.d=force THEN FALSE ELSE TRUE;
SELECT port.levelType FROM
l, ls => {
SELECT (IF port.levelType=l THEN port.l ELSE port.ls[index]) FROM
L => {
h.buffer[cycle][board][pod].forceData ← FALSE;
h.buffer[cycle][board][pod].inhibit ← FALSE;
};
H => {
h.buffer[cycle][board][pod].forceData ← TRUE;
h.buffer[cycle][board][pod].inhibit ← FALSE;
};
X => h.buffer[cycle][board][pod].inhibit ← TRUE;
ENDCASE => ERROR;
};
b, bs => {
h.buffer[cycle][board][pod].forceData ← IF port.levelType=b THEN port.b ELSE port.bs[index];
h.buffer[cycle][board][pod].inhibit ← inhibit;
};
c => {
bitMask: CARDINAL ← Basics.BITSHIFT[08000h, -port.fieldStart-index];
h.buffer[cycle][board][pod].forceData ← Basics.BITAND[port.c, bitMask]#0;
h.buffer[cycle][board][pod].inhibit ← inhibit;
};
lc => {
bitMask: LONG CARDINAL ← Basics.DoubleShift[[lc[080000000h]], -port.fieldStart-index].lc;
h.buffer[cycle][board][pod].forceData ← Basics.DoubleAnd[[lc[port.lc]], [lc[bitMask]]].lc#0;
h.buffer[cycle][board][pod].inhibit ← inhibit;
};
ENDCASE => ERROR;
CheckStop[h];
ENDLOOP;
};
CompareDataToBuffer: PROC [h: Handle] = {
cycle: Cycle ← h.cycle;
FOR l: LIST OF MapRec ← h.acquireMap, l.rest WHILE l#NIL DO
board: IMSTester.Board ← l.first.b;
pod: IMSTester.PodChannel ← l.first.p;
port: Ports.Port ← l.first.port;
index: NAT ← l.first.index;
mask: BOOLIF port.d=expect THEN FALSE ELSE TRUE;
SELECT port.levelType FROM
l, ls => {
SELECT (IF port.levelType=l THEN port.l ELSE port.ls[index]) FROM
L => {
h.buffer[cycle][board][pod].compareData ← FALSE;
h.buffer[cycle][board][pod].mask ← TRUE
};
H => {
h.buffer[cycle][board][pod].compareData ← TRUE;
h.buffer[cycle][board][pod].mask ← TRUE
};
X => h.buffer[cycle][board][pod].mask ← TRUE;
ENDCASE => ERROR;
};
b, bs => {
h.buffer[cycle][board][pod].compareData←IF port.levelType=b THEN port.b ELSE port.bs[index];
h.buffer[cycle][board][pod].mask ← mask;
};
c => {
bitMask: CARDINAL ← Basics.BITSHIFT[08000h, -port.fieldStart-index];
h.buffer[cycle][board][pod].compareData ← Basics.BITAND[port.c, bitMask]#0;
h.buffer[cycle][board][pod].mask ← mask;
};
lc => {
bitMask: LONG CARDINAL ← Basics.DoubleShift[[lc[080000000h]], -port.fieldStart-index].lc;
h.buffer[cycle][board][pod].compareData ← Basics.DoubleAnd[[lc[port.lc]], [lc[bitMask]]].lc#0;
h.buffer[cycle][board][pod].mask ← mask;
};
ENDCASE => ERROR;
CheckStop[h];
ENDLOOP;
};
MapPortToBuffer: PROC [h: Handle] = {
EachPair: PROC [wire: Core.Wire, port: Ports.Port] RETURNS [subElements: BOOLTRUE, quit: BOOLFALSE] --Ports.EachPortPairProc-- = {
podChannel: IMSTester.PodChannel ← (SELECT a.first.pod FROM A=>0, B=>8, AT=>16, BT=>17 ENDCASE=>ERROR) + a.first.channel;
IF CoreOps.IsFullWireName[h.cellType.public, wire, a.first.name] AND a.first.group#0 THEN {
IF port#NIL THEN rootPort ← port;
IF (g.first.directionality=force) OR (g.first.directionality=biDirectional) THEN {
FOR l: LIST OF MapRec ← h.forceMap, l.rest WHILE l#NIL DO
IF l.first.b=a.first.board AND l.first.p=podChannel THEN ERROR; --two force ports map to same buffer
ENDLOOP;
h.forceMap ← CONS[[a.first.board, podChannel, rootPort, IF port=NIL THEN count ELSE 0], h.forceMap];
};
IF (g.first.directionality=acquire) OR (g.first.directionality=biDirectional) THEN {
FOR l: LIST OF MapRec ← h.acquireMap, l.rest WHILE l#NIL DO
IF l.first.b=a.first.board AND l.first.p=podChannel THEN ERROR; --two acquire ports map to same buffer
ENDLOOP;
h.acquireMap ← CONS[[a.first.board, podChannel, rootPort, IF port=NIL THEN count ELSE 0], h.acquireMap];
};
RETURN[TRUE];
};
IF port#NIL AND port.levelType#composite THEN {count ← 0; rootPort ← port}
ELSE count ← count+1;
};
count: NAT ← 0;
a: LIST OF Assignments;
g: LIST OF Group;
rootPort: Ports.Port;
FOR g ← h.groups, g.rest WHILE g#NIL DO
FOR a ← h.assignments, a.rest WHILE a#NIL DO
IF g.first.number=a.first.group THEN [] ← Ports.VisitBinding[h.cellType.public, h.port, EachPair];
ENDLOOP;
ENDLOOP;
};
LimitNameLength: PROC [nameTab: SymTab.Ref, name: ROPE, length: NAT] RETURNS [newName: ROPE] = {
Looks for ropes in the form: "beforeRope, numericRope, afterRope". Truncates afterRope first and numericRope last in order to get rope to conform to length.
IF name = NIL THEN name ← "IMS";
IF Rope.Length[name] > length THEN {
c: CHAR;
beforeRope, numericRope, afterRope: ROPENIL;
beforeLength, numericLength, afterLength: NAT;
after: BOOLFALSE;
s: IO.STREAMIO.RIS[name];
WHILE NOT s.EndOf[] DO
SELECT (c ← s.GetChar[]) FROM
IN ['!..'$], IN ['&..'+], '-, '., '/, ':, '<, '>, '?, IN ['A..'~] => IF after THEN afterRope ← Rope.Concat[afterRope, Rope.FromChar[c]] ELSE beforeRope ← Rope.Concat[beforeRope, Rope.FromChar[c]]; --any printable char but SP, ', '= '% and '; are OK
'%, IN ['0..'9] => {
numericRope ← Rope.Concat[numericRope, Rope.FromChar[c]];
WHILE NOT s.EndOf[] AND (c ← s.PeekChar[]) >= '0 AND c <='9 DO
numericRope ← Rope.Cat[numericRope, Rope.FromChar[s.GetChar[]]];
ENDLOOP;
after ← TRUE;
};
ENDCASE => ERROR -- illegal character in name
ENDLOOP;
beforeLength ← Rope.Length[beforeRope];
numericLength ← Rope.Length[numericRope];
afterLength ← Rope.Length[afterRope];
IF numericLength < length THEN
IF (beforeLength + numericLength) < length THEN
IF (beforeLength + numericLength + afterLength) < length THEN newName ← name
ELSE newName ← Rope.Substr[name, 0, length]
ELSE newName ← Rope.Concat[Rope.Substr[beforeRope, 0, length-numericLength], numericRope]
ELSE newName ← Rope.Substr[numericRope, 0, length];
WHILE NOT SymTab.Insert[nameTab, newName, name] DO --if short name exists, add %## to it
number, index: INTEGER;
IF (index ← Rope.Find[newName, "%"]) # -1 THEN {
number ← (Rope.Fetch[newName, index+1]-'0)*10 + (Rope.Fetch[newName, index+2]-'0)+1;
newName ← Rope.Substr[newName, 0, index];
newName ← IO.PutFR["%g%%%02g", IO.rope[newName], IO.card[number]];
} ELSE {
newName ← Rope.Substr[newName, 0, length-3];
newName ← Rope.Concat[newName, "%00"];
};
ENDLOOP;
} ELSE newName ← name;
IF NOT SymTab.Insert[nameTab, name, newName] THEN ERROR; --name multiply defined
};
LimitGroupSize: PROC [h: Handle, groups: PodTimingGroups, boardToSlot: REF BoardToSlot] RETURNS [listOfGroups: LIST OF PodTimingGroups] = {
Splits up groups which are larger that the IMS maximum of 32 channels into subgroups. Also splits up groups which span programable drive level or acquire threshold modules so that such modules are referenced by unique group names (another IMS restriction).
newGroup, reversedGroup: PodTimingGroups;
reversedGroupList: LIST OF PodTimingGroups;
channels: NAT;
addToGroup: BOOL;
channels ← 0;
newGroup ← NIL;
reversedGroupList ← NIL;
FOR p: PodTimingGroups ← groups, p.rest WHILE p#NIL DO
FOR l: IMSTester.Pins ← p.first.pins, l.rest WHILE l#NIL DO
channels ← channels + 1;
ENDLOOP;
addToGroup ← channels <= 32 AND NOT boardToSlot[p.first.board].programable;
IF newGroup#NIL THEN addToGroup ← addToGroup AND NOT boardToSlot[newGroup.first.board].programable;
IF addToGroup THEN newGroup ← CONS[p.first, newGroup] ELSE {
re-reverse the elements on the list (for aesthetic reasons only)
reversedGroup ← NIL;
FOR p: PodTimingGroups ← newGroup, p.rest WHILE p#NIL DO
reversedGroup ← CONS[p.first, reversedGroup];
ENDLOOP;
listOfGroups ← CONS[reversedGroup, listOfGroups];
channels ← 0; newGroup ← LIST[p.first];
};
ENDLOOP;
add the leftovers to the list (in aesthetic order)
reversedGroup ← NIL;
IF newGroup#NIL THEN FOR p: PodTimingGroups ← newGroup, p.rest WHILE p#NIL DO
reversedGroup ← CONS[p.first, reversedGroup];
ENDLOOP;
listOfGroups ← CONS[reversedGroup, listOfGroups];
};
MakePodTimingGroup: PROC [h: Handle, g: Group, boardToSlot: REF BoardToSlot, nameTab: SymTab.Ref] RETURNS [podTimingGroups: IMSTester.PodTimingGroups] = {
FOR a: LIST OF Assignments ← h.assignments, a.rest WHILE a#NIL DO
IF a.first.group = g.number THEN {
FOR l: PodTimingGroups ← podTimingGroups, l.rest WHILE l#NIL DO
IF l.first.board = a.first.board AND l.first.podTiming = a.first.pod THEN {
FOR p: IMSTester.Pins ← l.first.pins, p.rest WHILE p#NIL DO
IF p.first.channel = a.first.channel THEN ERROR; --channel assigned twice
ENDLOOP;
l.first.pins ← CONS[NEW[IMSTester.PinRec ← [
channel: a.first.channel,
signalName: LimitNameLength[nameTab, a.first.name, signalNameLength],
packagePin: a.first.probeCardPin]], l.first.pins];
EXIT;
};
REPEAT
FINISHED =>
podTimingGroups ← CONS[NEW[IMSTester.PodTimingGroupRec ← [
slot: boardToSlot[a.first.board].slot,
board: a.first.board,
podTiming: a.first.pod,
pins: LIST[NEW[IMSTester.PinRec ← [
channel: a.first.channel,
signalName: LimitNameLength[nameTab, a.first.name, signalNameLength],
packagePin: a.first.probeCardPin]]]]], podTimingGroups];
ENDLOOP;
};
ENDLOOP;
};
MakeForceAcquireGroups: PROC [h: Handle] = {
listOfGroups: LIST OF PodTimingGroups;
count: NAT;
forceSubGroup: IMSTester.ForceGroup;
acquireSubGroup: IMSTester.AcquireGroup;
FOR g: LIST OF Group ← h.groups, g.rest WHILE g#NIL DO
IF g.first.directionality=force OR g.first.directionality=biDirectional THEN {
listOfGroups ← LimitGroupSize[h, MakePodTimingGroup[h, g.first, h.forceBoardToSlot, h.forceNamesTab], h.forceBoardToSlot];
h.forceSubGroups ← CONS[[g.first.name, NIL], h.forceSubGroups];
count ← 0;
FOR l: LIST OF PodTimingGroups ← listOfGroups, l.rest WHILE l#NIL DO
forceSubGroup ← NEW[IMSTester.ForceGroupRec ← [LimitNameLength[h.forceNamesTab, IO.PutFR["F%g%%%02g", IO.rope[g.first.name], IO.card[count]], groupNameLength], l.first, g.first.format, g.first.delay, g.first.width, g.first.programable, g.first.hiDrive, g.first.loDrive]];
h.forceGroups ← CONS[forceSubGroup, h.forceGroups];
h.forceSubGroups.first.subGroups ← CONS[forceSubGroup, h.forceSubGroups.first.subGroups];
count ← count+1;
ENDLOOP;
};
IF g.first.directionality=acquire OR g.first.directionality=biDirectional THEN {
listOfGroups ← LimitGroupSize[h, MakePodTimingGroup[h, g.first, h.acquireBoardToSlot, h.acquireNamesTab], h.acquireBoardToSlot];
h.acquireSubGroups ← CONS[[g.first.name, NIL], h.acquireSubGroups];
count ← 0;
FOR l: LIST OF PodTimingGroups ← listOfGroups, l.rest WHILE l#NIL DO
acquireSubGroup ← NEW[IMSTester.AcquireGroupRec ← [LimitNameLength[h.acquireNamesTab, IO.PutFR["A%g%%%02g", IO.rope[g.first.name], IO.card[count]], groupNameLength], l.first, g.first.sample, g.first.compare, g.first.programable, g.first.threshold]];
h.acquireGroups ← CONS[acquireSubGroup, h.acquireGroups];
h.acquireSubGroups.first.subGroups ← CONS[acquireSubGroup, h.acquireSubGroups.first.subGroups];
count ← count+1;
ENDLOOP;
};
ENDLOOP;
};
Init: PROC[h: Handle] = {
forceBoard, acquireBoard, programable: PACKED ARRAY IMSTester.SlotNumber OF BOOLEAN;
board: NAT;
filename: ROPE;
period: Period;
filename ← ViewerTools.GetContents[h.waferFile];
h.inStream ← FS.StreamOpen[filename !
FS.Error => {Message[IO.PutFR["Wafer file: %g not found", IO.rope[filename]]];
CONTINUE}];
IF h.port=NIL THEN h.port ← Ports.CreatePort[h.cellType, TRUE];
IF h.clockAName#NIL THEN h.clockAPort ← h.port[Ports.PortIndex[h.cellType.public, h.clockAName]];
IF h.clockBName#NIL THEN h.clockBPort ← h.port[Ports.PortIndex[h.cellType.public, h.clockBName]];
IF h.clockAPort=NIL AND h.clockBPort=NIL THEN ERROR; --can't find clocks
IMSTester.checkSyntax ← NOT h.enableTester;
period ← ((IO.GetInt[IO.RIS[ViewerTools.GetContents[h.period]]]+5)/10)*10;
ViewerTools.SetContents[h.period, IO.PutR1[IO.int[period]]];
IF h.forceNamesTab=NIL THEN { --only do it the first time "Start Test" is pressed
h.forceNamesTab ← SymTab.Create[];
h.acquireNamesTab ← SymTab.Create[];
[forceBoard, acquireBoard, programable] ← IMSTester.Initialize[];
board ← 0;
FOR slot: IMSTester.SlotNumber IN IMSTester.SlotNumber DO
IF forceBoard[slot] THEN {
h.forceBoardToSlot[board] ← [slot, programable[slot]];
board ← board+1;
};
ENDLOOP;
board ← 0;
FOR slot: IMSTester.SlotNumber IN IMSTester.SlotNumber DO
IF acquireBoard[slot] THEN {
h.acquireBoardToSlot[board] ← [slot, programable[slot]];
board ← board+1;
};
ENDLOOP;
MakeForceAcquireGroups[h];
MapPortToBuffer[h];
IMSTester.SetCyclePeriod[period]; --have to do this BEFORE defining groups
IMSTester.DefineGroups[h.forceGroups, h.acquireGroups];
h.buffer ← NEW[IMSTester.BufferRec[1]];
h.buffer[0] ← NEW[IMSTester.CycleDataRec];
h.firstFreeCycle ← 1; -- reserve location zero for dispatches and singlecycle execution
h.nullCycleData ← NEW[IMSTester.CycleDataRec];
FOR b: Board IN Board DO
FOR p: PodChannel IN PodChannel DO
h.nullCycleData[b][p].forceData ← FALSE;
h.nullCycleData[b][p].inhibit ← TRUE;
h.nullCycleData[b][p].compareData ← FALSE;
h.nullCycleData[b][p].mask ← TRUE;
ENDLOOP;
ENDLOOP;
};
h.cycle ← 0;
ViewerTools.SetContents[h.errorCycle, "0"];
IF h.enableStepper THEN EGlas.Init[];
IMSTester.SetCyclePeriod[period];
};
Cleanup: PROC [h: Handle] = {
IF h.enableStepper THEN EGlas.LampOn[];
IF NOT h.loopTest THEN IMSTester.Stop[];
};
GetName: PROC [h: Handle, key: Rope.ROPE] RETURNS [forceName, acquireName: Rope.ROPE] = {
val: SymTab.Val;
found: BOOL;
[found, val] ← SymTab.Fetch[h.forceNamesTab, key];
forceName ← IF found THEN NARROW[val, Rope.ROPE] ELSE NIL;
[found, val] ← SymTab.Fetch[h.acquireNamesTab, key];
acquireName ← IF found THEN NARROW[val, Rope.ROPE] ELSE NIL;
};
LookUpName: Commander.CommandProc = {
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
key, forceName, acquireName: Rope.ROPE;
FOR i: NAT IN [1..argv.argc) DO
key ← argv[i];
IF Rope.Length[key] = 0 THEN LOOP ELSE EXIT;
ENDLOOP;
[forceName, acquireName] ← GetName[NARROW[cmd.procData.clientData], key];
IF forceName#NIL THEN IO.PutF[cmd.out, "Force name: %g\n", IO.rope[forceName]];
IF acquireName#NIL THEN IO.PutF[cmd.out, "Acquire name: %g\n", IO.rope[forceName]];
IF forceName=NIL AND acquireName=NIL THEN IO.PutF[cmd.out, "%g not found\n", IO.rope[key]];
EXITS
failed => NULL;
};
END.