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 [ 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: BOOL _ FALSE, enableTester: BOOL _ FALSE, enableSimulation: BOOL _ FALSE, singleCycle: BOOL _ FALSE, loopTest: BOOL _ FALSE, testButtonContainer: Viewer _ NIL, testButtonList: LIST OF Button _ NIL, testProcsTable: HashTable.Table _ NIL, currentTestProc: ROPE _ NIL, typeScriptContainer: Viewer _ NIL, outStream: IO.STREAM _ NIL, 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: BOOL _ FALSE, -- button monitor inStream: IO.STREAM _ NIL, port: Ports.Port _ NIL, cellType: Core.CellType _ NIL, clockAName: ROPE _ NIL, clockBName: ROPE _ NIL, 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: ROPE _ NIL, groups: LIST OF Group, assignments: LIST OF Assignments, period: Period] = { 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: standardButtons], clientData: h, proc: proc, fork: fork]; }; Prompt: PROC [name: ROPE, col: NAT, contents: ROPE _ NIL] 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]]; 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:", "", 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; h.testButtonContainer _ Containers.Create[[wy: height, wh: entryVSpace, scrollable: FALSE, parent: viewer, border: FALSE]]; Containers.ChildXBound[viewer, h.testButtonContainer]; height _ height + entryVSpace; 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: REF _ NIL; 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: 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, "\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: 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, "Wafer %g, ", IO.rope[ViewerTools.GetContents[h.wafer]]]; IF h.enableStepper THEN { } ; }; }; }; NextDie: PROC [h: Handle] RETURNS [done: BOOLEAN _ FALSE] = { 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 { }; 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 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 = { 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: BOOL _ IF 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: BOOL _ IF 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: 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.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] = { 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, 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"]; IMSTester.SetCyclePeriod[period]; }; Cleanup: PROC [h: Handle] = { 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. ΔICTestImpl.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 October 22, 1986 10:22:27 am PDT -- Standard button state -- -- Test buttons state-- -- Typescript -- ***Standard Buttons*** ***Test Buttons*** ***TypeScript*** EGlas.LampOn[]; IF h.testInProgress THEN EGlas.Load[]; --don't load wafer first time EGlas.Seek[h.currentDie.x, h.currentDie.y]; EGlas.ZUp[]; EGlas.LampOff[]; IF h.enableStepper THEN EGlas.LampOn[]; ENABLE IO.Error, RuntimeError.BoundsFault => {Message["Illegal parameter"]; CONTINUE}; 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) IF h.enableStepper THEN EGlas.Init[]; IF h.enableStepper THEN EGlas.LampOn[]; Κ)š˜šœ™Icodešœ Οmœ1™Jšœžœ "˜Kšžœžœžœ ˜3—KšžœL˜Nšžœžœ˜Kšœ™Kšžœžœ ™DKšœ˜—Kšœ˜—K˜—Kšœ˜K˜—š ‘œžœ žœžœžœ˜=š‘œžœ˜K˜ šžœžœžœžœžœžœžœ˜XKšœ˜Kšœ˜šžœžœ˜Kšœ0˜0Kšœ˜Kšœžœ˜Kšœ˜Kšžœžœ˜.K˜—K˜—K˜K˜—Kšœžœžœ˜Kšœžœ˜Kšœžœžœ˜ šžœžœ˜šžœžœž˜Kšœžœžœžœ˜=š žœžœžœžœžœ žœž˜1Kšœžœ˜(Kšœžœ˜(šœžœ˜2Kšžœžœžœ˜,—Kšžœ žœ˜Kšžœ:žœ˜G—Kšžœ˜—šžœžœžœ˜Jšœžœ ˜Kšœžœžœžœ˜bKšžœ!žœžœ˜SK˜—K˜—K˜šžœžœžœ˜K˜Jšœžœ˜šžœžœ˜Kšœ+™+Kšœ ™ Kšœ™K˜—šžœžœ%žœ˜@šžœ žœ˜Kšœ$˜$šžœ žœ˜K˜IKšžœ˜ K˜—šžœžœžœ˜0K˜&Kšžœ˜ 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šžœžœžœ'žœ˜?JšžœžœžœI˜aJšžœžœžœI˜aJš žœžœžœžœžœžœ ˜HKšœžœ˜+Kšœ žœžœžœ/˜JKšœ"žœžœ˜<šžœžœžœ 3˜QKšœ"˜"Kšœ$˜$KšœA˜AK˜ šžœžœž˜9šžœžœ˜Kšœ6˜6Kšœ˜K˜—Kšžœ˜—K˜ šžœžœž˜9šžœžœ˜Kšœ8˜8Kšœ˜K˜—Kšžœ˜—Kšœ˜Kšœ˜Kšœ" (˜JKšœ7˜7Kšœ žœ˜'Kšœžœ˜*Kšœ A˜WKšœžœ˜.šžœ žœž˜šžœžœ ž˜"Kšœ"žœ˜(Kšœ žœ˜%Kšœ$žœ˜*Kšœžœ˜"Kšžœ˜—Kšžœ˜—K˜—K˜ Kšœ+˜+Kšžœžœ™%Kšœ!˜!Kšœ˜K˜—š‘œžœ˜Kšžœžœ™'Kšžœžœ žœ˜(K˜K˜—š ‘œžœžœžœžœ˜YKšœ˜Kšœžœ˜ Kšœ2˜2Kš œ žœžœžœ žœžœžœ˜:Kšœ4˜4Kš œžœžœžœ žœžœžœ˜