DIRECTORY Basics, Buttons, ChoiceButtons, Commander, CommandTool, Containers, Convert, Core, CoreOps, EGlas, FS, ICTest, IO, IMSTester, MessageWindow, Ports, Process, Rope, --Rosemary,-- Rules, RuntimeError, SymTab, ViewerClasses, ViewerIO, ViewerOps, ViewerTools; ICTestImpl: CEDAR PROGRAM IMPORTS Basics, Buttons, ChoiceButtons, Commander, CommandTool, Containers, Convert, CoreOps, EGlas, FS, IO, IMSTester, MessageWindow, Ports, Process, Rope, --Rosemary,-- Rules, RuntimeError, SymTab, ViewerIO, ViewerOps, ViewerTools EXPORTS ICTest = BEGIN OPEN ICTest; maxErrors: NAT _ 10; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; 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; AbortDieSignal: PUBLIC ERROR = CODE; AbortWaferSignal: PUBLIC ERROR = CODE; InterruptSignal: PUBLIC ERROR = CODE; Stop: TYPE = {dont, abortDie, abortWafer, interrupt}; DiePosition: TYPE = RECORD [x,y: NAT]; TestHandle: TYPE = REF TestHandleRec; TestHandleRec: TYPE = RECORD[h: Handle, proc: TestProc]; 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 [ waferFile: Viewer _ NIL, run: Viewer _ NIL, wafer: Viewer _ NIL, die: Viewer _ NIL, errorCycle: Viewer _ 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: BOOL _ FALSE, enableTester: BOOL _ FALSE, enableSimulation: BOOL _ FALSE, singleCycle: BOOL _ FALSE, loopTest: BOOL _ FALSE, repeatTest: BOOL _ FALSE, testButtonList: LIST OF Buttons.Button, currentTestProc: TestProc _ NIL, 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: BOOL _ FALSE, -- button monitor inStream: IO.STREAM _ NIL, outStream: IO.STREAM _ NIL, port: Ports.Port _ NIL, cellType: Core.CellType _ NIL, forceGroups: IMSTester.ForceGroups _ NIL, acquireGroups: IMSTester.AcquireGroups _ NIL, forceMap: LIST OF MapRec _ NIL, acquireMap: LIST OF MapRec _ NIL, cycle: IMSTester.Cycle _ 0, buffer: IMSTester.Buffer _ 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 ptsPerIn: 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..4); MakeStandardViewer: PUBLIC PROC [name: Core.ROPE _ NIL, cellType: Core.CellType, testButtons: LIST OF TestButton, groups: LIST OF Group _ NIL, assignments: LIST OF Assignments _ NIL, period: Period _ 100] = { CreateTestButton: PROC [buttonName: ROPE, proc: TestProc] = { first: BOOLEAN _ h.testButtonList = NIL; testHandle: TestHandle _ NEW[TestHandleRec _ [h, proc]]; h.testButtonList _ CONS[Buttons.Create[info: [name: buttonName, wx: column*2*ptsPerIn, wy: height, ww: 0,wh: entryHeight, parent: viewer, border: TRUE], clientData: testHandle, proc: DoTestButton], h.testButtonList]; IF first THEN { h.currentTestProc _ proc; Buttons.SetDisplayStyle[h.testButtonList.first, $BlackOnGrey]; }; IF column = LAST[Column] THEN { column _ 0; height _ height + entryHeight + entryVSpace; } ELSE column _ column+1; }; Button: PROC [name: ROPE, col: NAT, proc: Buttons.ButtonProc, fork: BOOL _ TRUE] = { [] _ Buttons.Create[info: [name: name, wx: col, wy: height, ww: 0, wh: entryHeight, parent: viewer], clientData: h, proc: proc, fork: fork]; }; Prompt: PROC [name: ROPE, col: NAT, contents: ROPE _ NIL] RETURNS [Viewer] = { RETURN[ChoiceButtons.BuildTextPrompt[viewer, col, height, name, contents, NIL, 1*pointsPerHalfInch].textViewer]; }; h: Handle _ NEW[ICTestRec]; rule: Rules.Rule; child: Viewer; viewer: Viewer _ Containers.Create[[name: Rope.Concat["IC Test Tool - ", name], iconic: FALSE, column: left, scrollable: FALSE]]; column: Column _ 0; height: CARDINAL _ 0; Commander.Register[key: "///Commands/Name", proc: LookUpName, doc: "Map an IMS signal name to/from the shortened version", clientData: h]; h.cellType _ cellType; h.groups _ groups; h.assignments _ assignments; h.waferFile _ ChoiceButtons.BuildTextPrompt[viewer, col1, height, "Wafer File:", "SingleDie.dat", NIL, 7*ptsPerIn].textViewer; height _ height + entryHeight + entryVSpace; h.run _ Prompt["Run:", col1]; Button["Start Test", col2, StartTest]; 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]; Button["Repeat Test", col5, RepeatTest]; 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: viewer, wy: height, ww: viewer.cw, wh: 2]]; Containers.ChildXBound[viewer, rule]; height _ height + entryVSpace; Button["Get Parameters", col1, 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[viewer, col1, height, "Group:", "", NIL, 2*ptsPerIn].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: viewer, wy: height, ww: viewer.cw, wh: 2]]; Containers.ChildXBound[viewer, rule]; height _ height + entryVSpace; FOR l: LIST OF TestButton _ testButtons, l.rest WHILE l#NIL DO CreateTestButton[l.first.name, l.first.proc]; ENDLOOP; height _ height + entryHeight + entryVSpace/2; rule _ Rules.Create[[parent: viewer, wy: height, ww: viewer.cw, wh: 2]]; Containers.ChildXBound[viewer, rule]; child _ ViewerOps.CreateViewer[flavor: $TypeScript, info:[parent: viewer, wy: height+2, ww: 7*ptsPerIn, wh: 6*ptsPerIn, scrollable: TRUE, border: FALSE]]; Containers.ChildYBound[viewer, child]; [ , h.outStream] _ ViewerIO.CreateViewerStreams[name: "ICTestTypescript", viewer: child]; height _ height + 1*ptsPerIn; }; DoTestButton: Buttons.ButtonProc = { t: TestHandle _ NARROW[clientData]; h: Handle _ t.h; selectedButton: Viewer _ NARROW[parent]; FOR l: LIST OF Buttons.Button _ h.testButtonList, l.rest WHILE l#NIL DO Buttons.SetDisplayStyle[l.first, IF l.first = selectedButton THEN $BlackOnGrey ELSE $BlackOnWhite]; ENDLOOP; h.currentTestProc _ t.proc; }; 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: BOOLEAN _ FALSE] = { 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, "\nRun %g", IO.rope[ViewerTools.GetContents[h.run]]]; }; IF done THEN { IO.Close[h.inStream]; h.testInProgress _ FALSE; }; }; }; NextWafer: PROC [h: Handle] RETURNS [done: BOOLEAN _ FALSE] = { 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, "\nWafer %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: BOOLEAN _ FALSE] = { Eval: PROC [evalCycleType: EvalCycleType] = { -- IF h.enableSimulation THEN Rosemary.Settle[Rosemary.InstantiateCellType[h.cellType, h.port]]; CheckStop[h]; SELECT evalCycleType FROM force => ForceDataToBuffer[h]; sense => { IF h.singleCycle THEN { CompareDataToBuffer[h]; BufferToIMS[h, 1]; } ELSE { CompareDataToBuffer[h]; h.cycle _ h.cycle+1; IF h.cycle = h.buffer.cycle THEN NewBuffer[h]; }; }; ENDCASE => ERROR; }; 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[]; }; h.currentTestProc[h.port, Eval]; IF NOT h.singleCycle THEN BufferToIMS[h, h.cycle]; IF h.enableStepper THEN EGlas.LampOn[]; count _ IMSTester.ErrorCount[]; IO.PutF[h.outStream, IF count#0 THEN "Fail " ELSE "Pass; "]; IF count#0 THEN IO.PutF[h.outStream, "(%g); ", IO.card[count]]; }; }; 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; h.testInProgress _ FALSE; }; 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; }; pause: INT _ 10; StartTest: Buttons.ButtonProc = { ENABLE IO.Error, RuntimeError.BoundsFault => {Message["Illegal parameter"]; CONTINUE}; firstTime: BOOL _ TRUE; h: Handle _ NARROW[clientData]; WHILE firstTime OR h.repeatTest DO firstTime _ FALSE; IF NOT h.testInProgress THEN { h: Handle _ NARROW[clientData]; done: BOOLEAN _ FALSE; 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]; }; Process.Pause[Process.SecondsToTicks[pause]]; ENDLOOP; }; 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 $BlackOnGrey 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 $BlackOnGrey 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 $BlackOnGrey 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 $BlackOnGrey ELSE $BlackOnWhite]; }; StopLoop: Buttons.ButtonProc = { IMSTester.Stop[]; }; RepeatTest: Buttons.ButtonProc = { h: Handle _ NARROW[clientData]; selectedButton: Viewer _ NARROW[parent]; h.repeatTest _ NOT h.repeatTest; Buttons.SetDisplayStyle[selectedButton, IF h.repeatTest THEN $BlackOnGrey ELSE $BlackOnWhite]; }; 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: IMSTester.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: BOOL _ IF port.d=force THEN FALSE ELSE TRUE; SELECT port.type FROM l, ls => { SELECT (IF port.type=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.type=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: IMSTester.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: BOOL _ IF port.d=expect THEN FALSE ELSE TRUE; SELECT port.type FROM l, ls => { SELECT (IF port.type=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.type=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; }; BufferToIMS: PROC [h: Handle, cycles: IMSTester.Cycle] = { halt: IMSTester.Cycle _ IF NOT h.loopTest THEN cycles-1 ELSE LAST[IMSTester.Cycle]; --i.e. never jump: IMSTester.Jumps _ IF h.loopTest THEN LIST[[cycles-1, 0]] ELSE NIL; IF cycles>0 THEN IMSTester.SetIMSMemory[forceGroups: h.forceGroups, acquireGroups: h.acquireGroups, buffer: h.buffer, cycles: cycles, halt: halt, jumps: jump]; IMSTester.Start[]; CheckStop[h]; }; MapPortToBuffer: PROC [h: Handle] = { EachPair: PROC [wire: Core.Wire, port: Ports.Port] RETURNS [subElements: BOOL _ TRUE, quit: BOOL _ FALSE] --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.type#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] = { IF name = NIL THEN name _ "IMS"; IF Rope.Length[name] > length THEN { c: CHAR; beforeRope, numericRope, afterRope: ROPE _ NIL; beforeLength, numericLength, afterLength: NAT; after: BOOL _ FALSE; s: IO.STREAM _ IO.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] = { 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 { 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; 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.public, TRUE]; IMSTester.checkSyntax _ NOT h.enableTester; 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.DefineGroups[h.forceGroups, h.acquireGroups]; h.buffer _ NEW[IMSTester.BufferRec[1]]; h.buffer[0] _ NEW[IMSTester.CycleDataRec]; }; h.cycle _ 0; ViewerTools.SetContents[h.errorCycle, "0"]; IF h.enableStepper THEN EGlas.Init[]; period _ ((IO.GetInt[IO.RIS[ViewerTools.GetContents[h.period]]]+5)/10)*10; ViewerTools.SetContents[h.period, IO.PutR1[IO.int[period]]]; 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. xICTestImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Created by: Gasbarro December 3, 1985 6:15:34 pm PST Last Edited by: Gasbarro May 28, 1986 5:34:01 pm PDT -- control panel state -- ViewerOps.SetOpenHeight[viewer, height]; {p: PROC ~ {h.currentTestProc[h]}; CedarProcess.DoWithPriority[priority: foreground, action: p]}; Looks for ropes in the form: "beforeRope, numericRope, afterRope". Truncates afterRope first and numericRope last in order to get rope to conform to length. 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). re-reverse the elements on the list (for aesthetic reasons only) add the leftovers to the list (in aesthetic order) Κ&™˜šœ™Icodešœ Οmœ1™Jšœ žœ "˜7Jšœžœ "˜@Jšœžœ .˜PJšœžœ ˜3Jšœžœ ˜1Jšœžœ ˜3Jšœžœ ˜2Jšœžœžœ˜J˜šΟnœžœžœ žœžœ(žœžœžœžœ žœžœžœžœ˜Πš‘œžœžœ˜=Jšœžœžœ˜(Jšœžœ˜8šœžœ(˜?Jšœ"˜"Jšœ˜JšœžœB˜^—šžœžœ˜Jšœ˜Jšœ>˜>J˜—šžœ žœ žœ˜Jšœ ˜ Jšœ,˜,Jšœžœ˜—J˜J˜—š ‘œžœžœžœ"žœžœ˜TšœS˜SJšœ8˜8K˜K˜——š‘œžœžœžœ žœžœžœ ˜NšžœDžœ#˜pK˜K˜——Jšœ žœ ˜J˜Jšœ˜JšœXžœžœ˜Jšœ˜Jšœžœ˜J˜šœ=˜=JšœL˜L—J˜J˜J˜J˜Jšœbžœ˜~J˜Jšœ,˜,JšœjžœVžœžœ˜ΰJ˜Jšœ,˜,JšœFžœK˜–J˜Jšœ,˜,JšœFžœ}˜ΘJ˜Jšœ,˜,JšœXžœX˜΅J˜Jšœ0˜0JšœH˜Hšœ%˜%J˜—Jšœ˜JšœΤ˜ΤJ˜Jšœ,˜,JšœXžœ ˜ϋJ˜Jšœ0˜0JšœH˜Hšœ%˜%J˜—Jšœ˜š žœžœžœ"žœžœž˜>J˜-Jšžœ˜—Jšœ.˜.JšœH˜HJšœ%˜%J˜Jšœ„žœ žœ˜šJšœ&˜&JšœY˜YJšœ˜Jšœ(™(J˜J˜—š‘ œ˜$Kšœžœ ˜#K˜Kšœžœ ˜(š žœžœžœ+žœžœž˜GKšœ!žœžœžœ˜cKšžœ˜—Kšœ˜Kšœ˜K˜—š‘œžœ˜š ‘ œžœ žœžœžœ˜6Kšœžœžœžœžœžœžœžœ!˜]Kšœžœ˜Kšœ˜K˜—š ‘ œžœžœžœžœ˜/Kšœ˜Kšœ˜K˜—š ‘œžœ žœžœžœ˜=Kšœžœ˜Kšœžœžœ˜ šžœžœ˜šžœžœž˜Kšœžœžœžœ˜=š žœžœžœžœžœž˜1Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšžœ žœ˜KšžœEžœ˜R—Kšžœ˜—šžœžœžœ˜šœžœ˜Kšžœžœžœ ˜3—KšžœM˜Ošžœžœ˜Kšœ˜Kšžœžœ ˜DKšœ˜—Kšœ˜—K˜—Kšœ˜K˜—š ‘œžœ žœžœžœ˜=š‘œžœ#˜-KšžœžœC˜`K˜ šžœž˜šœž˜ Kšœ˜—šœ ˜ šžœžœ˜Kšœ˜K˜šœžœ˜Kšœ˜Kšœ˜Kšžœžœ˜.Kšœ˜——K˜—Kšžœžœ˜—K˜K˜—Kšœžœžœ˜Kšœžœ˜Kšœžœžœ˜ šžœžœ˜šžœžœž˜Kšœžœžœžœ˜=š žœžœžœžœžœ žœž˜1Kšœžœ˜(Kšœžœ˜(šœžœ˜2Kšžœžœžœ˜,—Kšžœ žœ˜Kšžœ:žœ˜G—Kšžœ˜—šžœžœžœ˜Jšœžœ ˜Kšœžœžœžœ˜bKšžœQ˜SK˜—K˜—K˜šžœžœžœ˜K˜Jšœžœ˜šžœžœ˜Kšœ+˜+Kšœ ˜ Kšœ˜K˜—Kšœ ˜ K•StartOfExpansion3[priority: CedarProcess.Priority, action: PROC]šœžœY™aKšžœžœžœ˜2Kšžœžœ˜'Kšœ˜Kšžœžœ žœ žœ ˜˜bKšžœ˜—Kšžœ˜—K˜K˜—š ‘œžœžœ žœžœ žœ˜`Jšœ™Jšžœžœžœ˜ šžœžœ˜$Jšœžœ˜Jšœ$žœžœ˜/Jšœ*žœ˜.Jšœžœžœ˜Jš œžœžœžœžœ˜šžœžœ ž˜šžœž˜Jš žœ žœ'žœ žœžœ6žœ9 3˜ψšœžœ˜Jšœ9˜9š žœžœ žœžœžœ˜?Jšœ@˜@Jšžœ˜—Jšœžœ˜ J˜—Jšžœžœ ˜-—Jšžœ˜—Jšœ'˜'Jšœ)˜)Jšœ%˜%šžœžœ˜šžœ)žœ˜0šžœ7žœ˜LJšžœ'˜+—JšžœU˜Y—Jšžœ/˜3—šžœžœ'ž &˜XJšœžœ˜šžœ(žœ˜0J˜TJšœ)˜)Jšœ žœžœžœ˜BJšœžœ˜Jšœ,˜,Jšœ&˜&J˜—Jšžœ˜—Kšœžœ˜—Kš žœžœ'žœžœ ˜PK˜K˜—š ‘œžœ3žœžœžœžœ˜‹Kšœƒ™ƒKšœ)˜)Kšœžœžœ˜+Kšœ žœ˜Kšœ žœ˜Kšœ ˜ Kšœ žœ˜Kšœžœ˜šžœ%žœžœž˜6šžœ*žœžœž˜;Kšœ˜Kšžœ˜—Kšœžœžœ(˜KKš žœ žœžœžœžœ/˜cšžœ žœ žœžœ˜<šœ@™@Kšœžœ˜šžœ'žœžœž˜8Kšœžœ˜-Kšžœ˜—Kšœžœ˜1Kšœžœ ˜'K˜——Kšžœ˜—Kšœ2™2Kšœžœ˜š žœ žœžœžœ'žœžœž˜MKšœžœ˜-Kšžœ˜—Kšœžœ˜1K˜K˜—š‘œžœ$žœ#žœ1˜šš žœžœžœ%žœžœž˜Ašžœžœ˜"šžœ.žœžœž˜?šžœžœ!žœ˜Kšžœ*žœžœž˜;Kšžœ#žœžœ ˜IKšžœ˜—šœžœžœ˜,Kšœ˜KšœF˜FKšœ2˜2—Kšžœ˜K˜—šž˜šžœ˜ šœžœžœ ˜:Kšœ&˜&Kšœ˜Kšœ˜šœžœžœ˜#Kšœ˜KšœE˜EKšœ8˜8————Kšžœ˜ —K˜—Kšžœ˜—˜K˜——š‘œžœ˜,Kšœžœžœ˜&Kšœžœ˜ Kšœ$˜$Kšœ(˜(š žœžœžœžœžœž˜6šžœžœ&žœ˜NKšœz˜zKšœžœžœ˜?Kšœ ˜ š žœžœžœ(žœžœž˜DKš œžœ=žœžœžœ˜Kšœžœ˜3Kšœ#žœ2˜YKšœ˜Kšžœ˜—K˜—šžœ žœ&žœ˜PKšœ€˜€Kšœžœžœ˜CKšœ ˜ š žœžœžœ(žœžœž˜DKš œžœAžœžœžœt˜ωKšœžœ#˜9Kšœ%žœ6˜_Kšœ˜Kšžœ˜—K˜—Kšžœ˜—K˜K˜—š‘œžœ˜Jš œ'žœžœžœžœ˜TJšœžœ˜ Kšœ ž˜K˜K˜Kšœ0˜0šœ žœ˜%Kšžœžœ#žœ˜NKšžœ˜ —Kšžœžœžœ.žœ˜FKšœžœ˜+šžœžœžœ 3˜QKšœ"˜"Kšœ$˜$KšœA˜AK˜ šžœžœž˜9šžœžœ˜Kšœ6˜6Kšœ˜K˜—Kšžœ˜—K˜ šžœžœž˜9šžœžœ˜Kšœ8˜8Kšœ˜K˜—Kšžœ˜—Kšœ˜Kšœ˜Kšœ7˜7Kšœ žœ˜'Kšœžœ˜*K˜—K˜ Kšœ+˜+Kšžœžœ˜%Kšœ žœžœžœ/˜JKšœ"žœžœ˜