DIRECTORY Basics, BasicTime, Core, CoreFlat, CoreOps, CoreProperties, FS, IO, Ports, RefTab, RefText, Rosemary, RosemaryUser, RosemaryVector, Rope, SymTab, TerminalIO, TiogaFileOps, TiogaStreams, MTSVector; MTSVectorImpl: CEDAR PROGRAM IMPORTS Basics, BasicTime, CoreFlat, CoreOps, CoreProperties, FS, IO, Ports, RefTab, RefText, Rosemary, RosemaryUser, RosemaryVector, Rope, SymTab, TerminalIO, TiogaFileOps, TiogaStreams EXPORTS MTSVector SHARES MTSVector ~ BEGIN OPEN MTSVector; bitMask: ARRAY [0..8) OF BYTE; vectorBlockLength: NAT _ 16384; -- number of bytes in a block of test vectors (end up with 0's) testFileHeaderSize: CARD16 _ 2048; -- minimum number of bytes in file header headerSignature: Rope.ROPE _ "MTSX003"; -- signature in header MapPublicToPackage: PROC [public: Core.WireSeq, package: Package] RETURNS [wireToPins: RefTab.Ref] ~ { Fail: PROC [name, explanation: Rope.ROPE] ~ { failed _ TRUE; TerminalIO.PutF["*** Wire %g : %g.\n", IO.rope[name], IO.rope[explanation]]; }; CheckDefined: PROC [wire: Core.Wire] ~ { IF NOT RefTab.Fetch[wireToPins, wire].found THEN { Fail[CoreOps.GetFullWireName[public, wire], "not described in pins file"]; [] _ RefTab.Insert[wireToPins, wire, NIL]; -- to avoid double messages }; }; Define: SymTab.EachPairAction ~ { wd: WireDescription _ NARROW [val]; wire: Core.Wire = CoreOps.FindWire[public, key]; SELECT TRUE FROM wire=NIL => Fail[key, "not found in public"]; wire.size#0 => Fail[key, "not atomic"]; RefTab.Insert[wireToPins, wire, wd] => NULL; -- all OK ENDCASE => Fail[key, "wire defined twice"]; }; failed: BOOL _ FALSE; wireToPins _ RefTab.Create[]; [] _ SymTab.Pairs[package.signals, Define]; -- lookup all signals in the public CoreOps.VisitRootAtomics[public, CheckDefined]; IF failed THEN ERROR; -- DUT pins do not match package pins }; PutHeader: PROC [file: File] ~ { PutByte: PROC [b: BYTE] ~ { IO.PutByte[file.stream, b]; file.ckSum _ Basics.BITAND[file.ckSum+b, 000FFH]; }; PutCard16: PROC [c: CARD16] ~ { PutByte[Basics.LowByte[c]]; PutByte[Basics.HighByte[c]]; }; PutInt32: PROC [i: INT32] ~ { n: Basics.LongNumber; n.li _ i; PutByte[n.ll]; PutByte[n.lh]; PutByte[n.hl]; PutByte[n.hh]; }; PutCRope: PROC [r: Rope.ROPE] ~ { EachChar: Rope.ActionType ~ { PutByte[ORD[c]] }; [] _ Rope.Map[base: r, action: EachChar]; PutByte[00H]; }; PutRope: PROC [r: Rope.ROPE, length: INT] ~ { EachChar: Rope.ActionType ~ { PutByte[ORD[c]] }; [] _ Rope.Map[base: r, action: EachChar, start: 0, len: MIN[length, Rope.Length[r]]]; THROUGH [Rope.Length[r] .. length) DO PutByte[00H] ENDLOOP; }; PutWireDescription: SymTab.EachPairAction ~ { wd: WireDescription = NARROW [val]; PutByte[ORD[wd.flavor]]; -- will be 0FFH for termination flag PutCRope[wd.name]; FOR pins: LIST OF PinDescription _ wd.pins, pins.rest UNTIL pins=NIL DO pin: PinDescription = pins.first; PutByte[pin.group]; -- will be 0xFF for terminator PutByte[pin.byte]; PutByte[pin.mask]; PutByte[pin.bit]; PutCRope[pin.pkgName]; PutCRope[pin.conName]; ENDLOOP; PutByte[0FFH]; -- end of pin descriptions list }; ckSumOffset: INT; -- this is where the checksum will go once completed... ckSumByte: BYTE; IO.SetIndex[file.stream, 0]; -- backup to start of file PutRope[headerSignature, 8]; PutInt32[file.hdrSize]; -- garbage on 1st pass... PutCard16[file.nGroups]; PutCard16[vectorBlockLength]; PutInt32[file.nVects]; PutInt32[LOOPHOLE[file.timeStamp]]; ckSumOffset _ IO.GetIndex[file.stream]; -- note down where checksum should go finally PutByte[00H]; -- this is the checksum position, patched at end PutRope[file.id, 120]; -- comment for display on the PC PutCRope[file.package.name]; -- start of package/fixture description PutCRope[file.package.fixtureName]; [] _ SymTab.Pairs[file.package.signals, PutWireDescription]; PutByte[0FFH]; -- end of wire descriptions list THROUGH [(IO.GetIndex[file.stream] MOD 1024) .. 1024) DO PutByte[00H] ENDLOOP; -- add adequate padding file.hdrSize _ IO.GetIndex[file.stream]; IF ( file.hdrSize MOD 1024 ) # 0 THEN ERROR; -- internal bug ckSumByte _ Basics.BITAND[0100H-file.ckSum, 000FFH]; -- recompute checksum for total 0 IO.SetIndex[file.stream, ckSumOffset]; -- get back to where the checksum should be IO.PutByte[file.stream, ckSumByte]; -- and write it IO.SetIndex[file.stream, file.hdrSize]; -- get back to end of header }; WriteBByte: PROC [file: File, b: BYTE] ~ { block: REF TEXT _ file.block; IF block.length>=block.maxLength THEN ERROR; -- buffer overflowed block[block.length] _ VAL[b]; block.length _ block.length+1; file.ckSum _ Basics.BITAND[file.ckSum+b, 000FFH]; }; FlushBlock: PROC [file: File, nBytes: NAT] ~ { block: REF TEXT _ file.block; IF block.length>nBytes THEN ERROR; -- internal bug UNTIL block.length=nBytes DO WriteBByte[file, 00H] ENDLOOP; IO.PutBlock[file.stream, block, 0, nBytes]; block.length _ 0; }; GetHeader: PROC [file: File] ~ { GetByte: PROC [] RETURNS [b: BYTE] ~ { b _ IO.GetByte[file.stream]; file.ckSum _ Basics.BITAND[file.ckSum+b, 000FFH]; }; GetInt32: PROC [] RETURNS [i: INT32] ~ { n: Basics.LongNumber; n.ll _ GetByte[]; n.lh _ GetByte[]; n.hl _ GetByte[]; n.hh _ GetByte[]; i _ n.li; }; GetCard16: PROC [] RETURNS [c: CARD16] ~ { n: Basics.ShortNumber; n.lo _ GetByte[]; n.hi _ GetByte[]; c _ n.sc; }; GetRope: PROC [length: NAT] RETURNS [r: Rope.ROPE] ~ { buf: REF TEXT _ RefText.ObtainScratch[length]; lastNonPad: INT _ -1; FOR i: NAT IN [0..length) DO b: BYTE = GetByte[]; buf _ RefText.AppendChar[buf, VAL[b]]; IF b#00H THEN lastNonPad _ i; ENDLOOP; r _ Rope.FromRefText[buf, 0, lastNonPad+1]; RefText.ReleaseScratch[buf]; }; GetCRope: PROC [] RETURNS [r: Rope.ROPE] ~ { buf: REF TEXT _ RefText.ObtainScratch[256]; -- quite reasonable length DO b: BYTE = GetByte[]; IF b=00H THEN EXIT; -- rope terminator found buf _ RefText.AppendChar[buf, VAL[b]]; ENDLOOP; r _ Rope.FromRefText[buf]; RefText.ReleaseScratch[buf]; }; signature: Rope.ROPE; blockLength: NAT; file.ckSum _ 0; signature _ GetRope[8]; IF NOT Rope.Equal[signature, headerSignature] THEN ERROR; -- invalid file format file.hdrSize _ GetInt32[]; file.nGroups _ GetCard16[]; blockLength _ GetCard16[]; IF blockLength#vectorBlockLength THEN ERROR; -- invalid file format file.nVects _ GetInt32[]; file.timeStamp _ LOOPHOLE[GetInt32[]]; [] _ GetByte[]; -- checksum file.id _ GetRope[120]; file.package _ NEW [PackageRep _ [signals: SymTab.Create[]]]; file.package.name _ GetCRope[]; file.package.fixtureName _ GetCRope[]; DO -- loop until wire description list exhausted b: BYTE _ GetByte[]; wd: WireDescription _ NEW [WireDescriptionRep]; IF b=0FFH THEN EXIT; -- end of wire description list wd.flavor _ VAL [b]; wd.name _ GetCRope[]; DO -- loop until pin description list for current wire exhausted pd: PinDescription; b _ GetByte[]; IF b=0FFH THEN EXIT; -- end of pin description list pd _ NEW [PinDescriptionRep]; pd.group _ b; pd.byte _ GetByte[]; pd.mask _ GetByte[]; pd.bit _ GetByte[]; pd.pkgName _ GetCRope[]; pd.conName _ GetCRope[]; wd.pins _ CONS [pd, wd.pins]; -- append (well, prepend) to list ENDLOOP; IF NOT SymTab.Insert[file.package.signals, wd.name, wd] THEN ERROR; -- incorrect file ENDLOOP; THROUGH [IO.GetIndex[file.stream] .. file.hdrSize) DO [] _ GetByte[] ENDLOOP; IF IO.GetIndex[file.stream]#file.hdrSize THEN ERROR; -- incorrect file UNTIL IO.EndOf[file.stream] DO [] _ GetByte[] ENDLOOP; -- to compute checksum IF Basics.BITAND[file.ckSum, 000FFH]#0 THEN ERROR; -- invalid checksum IO.SetIndex[file.stream, file.hdrSize]; -- back to the first vector }; Create: PUBLIC PROC [ct: Core.CellType, package: Rope.ROPE, vectors: Rope.ROPE, title: Rope.ROPE _ NIL] RETURNS [file: File] ~ { HighestBoard: SymTab.EachPairAction ~ { wd: WireDescription = NARROW [val]; SELECT wd.flavor FROM signal, pullup => { FOR pins: LIST OF PinDescription _ wd.pins, pins.rest UNTIL pins=NIL DO file.nGroups _ MAX [pins.first.group, file.nGroups]; ENDLOOP }; gnd, vdd, ignore => NULL; ENDCASE => ERROR; -- incorrect value ... }; file _ NEW [FileRep _ [dut: ct, nGroups: -1, ckSum: 0, nVects: 0, timeStamp: BasicTime.Now[]]]; file.package _ ReadPackage[MakeFileName[package, "mtsPins", FALSE]]; file.wireToPins _ MapPublicToPackage[ct.public, file.package]; [] _ SymTab.Pairs[file.package.signals, HighestBoard]; IF file.nGroups<0 THEN ERROR; -- incorrect pin file file.nGroups _ file.nGroups + 1; -- was index of highest board, not number of boards IF Rope.IsEmpty[title] THEN title _ IO.PutFR["%g on %g", IO.rope[CoreOps.GetCellTypeName[ct]], IO.rope[file.package.name]]; file.id _ IO.PutFR["%g -- %g", IO.rope[title], IO.time[]]; file.block _ RefText.New[vectorBlockLength]; file.vector _ NEW [VectorRep[file.nGroups]]; FOR i: NAT IN [0..file.vector.ngrps) DO file.vector[i] _ NEW [CGRep] ENDLOOP; file.stream _ FS.StreamOpen[MakeFileName[vectors, "xtv", TRUE], create]; TerminalIO.PutF["MTS vectors saved on %g\n", IO.rope[FS.GetName[FS.OpenFileFromStream[file.stream]].fullFName]]; PutHeader[file]; file.ckSum _ 0; -- ignore previous chksum, header will be rewritten ... }; WriteVector: PUBLIC PROC [file: File] ~ { bytesPerVector: NAT = 24*file.nGroups; file.nVects _ file.nVects+1; IF file.block.length+bytesPerVector>vectorBlockLength THEN FlushBlock[file, vectorBlockLength]; FOR g: NAT IN [0..file.vector.ngrps) DO group: CG = file.vector[g]; FOR i: NAT IN [0..8) DO WriteBByte[file, group.tristate.bytes[i]] ENDLOOP; FOR i: NAT IN [0..8) DO WriteBByte[file, group.data.bytes[i]] ENDLOOP; FOR i: NAT IN [0..8) DO WriteBByte[file, group.mask.bytes[i]] ENDLOOP; ENDLOOP; }; WriteVectorFromPorts: PUBLIC PROC [file: File, before: Ports.Port, after: Ports.Port] ~ { Record: PROC [wire: Core.Wire, levelBefore: Ports.Level, driveBefore: Ports.Drive, levelAfter: Ports.Level, driveAfter: Ports.Drive, mode: REF ANY] ~ { wd: WireDescription _ NARROW [RefTab.Fetch[wireToPins, wire].val]; source, data, mask: BOOL; -- the 3 bits defining a test vector "bit" useEarlyDrive: BOOL _ TRUE; -- If driven before, use that value IF wd.flavor=ignore THEN RETURN; -- pin not connected to tester, all default to FALSE SELECT mode FROM NIL => NULL; $After => useEarlyDrive _ FALSE; ENDCASE => ERROR; -- Unknown value of MTSOption property IF useEarlyDrive AND driveBefore=drive THEN { SELECT levelBefore FROM H => {source _ TRUE; data _ TRUE; mask _ TRUE}; -- 1: drive and expect a 1 L => {source _ TRUE; data _ FALSE; mask _ TRUE}; -- 0: drive and expect a 0 X => {source _ FALSE; data _ FALSE; mask _ FALSE}; -- X: don't drive, ignore result ENDCASE => ERROR; -- cannot happen } ELSE { -- driveBefore # drive SELECT driveAfter FROM expect => SELECT levelAfter FROM H => {source _ FALSE; data _ TRUE; mask _ TRUE}; -- H: don't drive, expect 1 L => {source _ FALSE; data _ FALSE; mask _ TRUE}; -- L: don't drive, expect 0 X => {source _ FALSE; data _ FALSE; mask _ FALSE}; -- X: don't drive, ignore result ENDCASE => ERROR; -- cannot happen inspect => { IF wd.flavor=pullup THEN SELECT levelAfter FROM H => {source _ FALSE; data _ TRUE; mask _ TRUE}; -- H: don't drive, expect a 1 L => {source _ TRUE; data _ FALSE; mask _ TRUE}; -- 0: drive and expect a 0 X => {source _ FALSE; data _ TRUE; mask _ TRUE}; -- H: don't drive, expect a 1 ENDCASE => ERROR -- cannot happen ELSE SELECT levelAfter FROM H => {source _ TRUE; data _ TRUE; mask _ FALSE}; -- T: drive 1 and check TS L => {source _ TRUE; data _ FALSE; mask _ FALSE}; -- F: drive 0 and check TS X => {source _ TRUE; data _ FALSE; mask _ FALSE}; -- F: drive 0 and check TS ENDCASE => ERROR; -- cannot happen }; none => {source _ FALSE; data _ FALSE; mask _ FALSE}; -- X: don't drive, ignore result drive => { IF useEarlyDrive THEN { -- didn't drive before, does after ??? source _ FALSE; data _ FALSE; mask _ FALSE; -- X: don't drive, ignore result } ELSE { -- special wire: always use the late drive value ... SELECT levelAfter FROM H => {source _ TRUE; data _ TRUE; mask _ TRUE}; -- 1: drive and expect a 1 L => {source _ TRUE; data _ FALSE; mask _ TRUE}; -- 0: drive and expect a 0 X => {source _ FALSE; data _ FALSE; mask _ FALSE}; -- X: don't drive, ignore result ENDCASE => ERROR; -- cannot happen }; }; ENDCASE => ERROR; -- Incorrect capture }; IF wd.flavor=vdd AND NOT ((NOT source) AND data AND mask) THEN ERROR; -- bug IF wd.flavor=gnd AND NOT ((NOT source) AND (NOT data) AND mask) THEN ERROR; -- bug FOR pins: LIST OF PinDescription _ wd.pins, pins.rest UNTIL pins=NIL DO pin: PinDescription = pins.first; group: CG _ vector[pin.group]; group.tristate.bits[pin.bit] _ source; group.data.bits[pin.bit] _ data; group.mask.bits[pin.bit] _ mask; ENDLOOP; }; VisitDUT: PROC [wire: Core.Wire, before: Ports.Port, after: Ports.Port] ~ { mode: REF ANY _ CoreProperties.GetWireProp[wire, $MTSOption]; IF before.levelType#after.levelType THEN ERROR; IF before.driveType#after.driveType THEN ERROR; SELECT before.levelType FROM l => Record[wire, before.l, before.d, after.l, after.d, mode]; b => Record[wire, IF before.b THEN H ELSE L, before.d, IF after.b THEN H ELSE L, after.d, mode]; ls => { IF before.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO Record[wire[i], before.ls[i], before.ds[i], after.ls[i], after.ds[i], mode]; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO Record[wire[i], before.ls[i], before.d, after.ls[i], after.d, mode]; ENDLOOP }; bs => { IF before.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO Record[wire[i], IF before.bs[i] THEN H ELSE L, before.ds[i], IF after.bs[i] THEN H ELSE L, after.ds[i], mode]; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO Record[wire[i], IF before.bs[i] THEN H ELSE L, before.d, IF after.bs[i] THEN H ELSE L, after.d, mode]; ENDLOOP }; c => ERROR; -- not yet supported lc => ERROR; -- not yet supported q => ERROR; -- not yet supported composite => { -- Explore sub-wires FOR i: NAT IN [0..wire.size) DO VisitDUT[wire[i], before[i], after[i]]; ENDLOOP; }; ENDCASE => ERROR; -- unexpected value }; vector: Vector _ file.vector; wireToPins: RefTab.Ref _ file.wireToPins; FOR i: NAT IN [0..vector.ngrps) DO -- force uninitialized bits to Ignore vector[i].tristate.bits _ ALL [FALSE]; vector[i].data.bits _ ALL [FALSE]; vector[i].mask.bits _ ALL [FALSE]; ENDLOOP; [] _ VisitDUT[file.dut.public, before, after]; -- compute vector WriteVector[file]; -- and add it to file }; Close: PUBLIC PROC [file: File] ~ { sum: CARD16 _ 0; IF file.block.length#0 THEN FlushBlock[file, vectorBlockLength]; TerminalIO.PutF["%g vectors captured during simulation\n", IO.int[file.nVects]]; PutHeader[file]; IO.SetIndex[file.stream, 0]; -- backtrack to start of file UNTIL IO.EndOf[file.stream] DO -- check file checksum byte: BYTE _ IO.GetByte[file.stream]; sum _ Basics.BITAND[sum+byte, 0FFH]; ENDLOOP; IF sum#0 THEN ERROR; -- implementation bug: file did not checksum properly !!! IO.Close[file.stream]; }; Read: PUBLIC PROC [ct: Core.CellType, vectors: Rope.ROPE, eachVector: PROC [File]] ~ { GetByte: PROC [] RETURNS [b: BYTE] ~ { b _ ORD [RefText.Fetch[block, blockOffset]]; blockOffset _ blockOffset+1; }; bytesPerVector: NAT; blockOffset: NAT _ vectorBlockLength; block: REF TEXT _ RefText.New[vectorBlockLength]; file: File _ NEW [FileRep _ [dut: ct, block: block, ckSum: 0, timeStamp: BasicTime.nullGMT]]; file.stream _ FS.StreamOpen[MakeFileName[vectors, "xtv", FALSE], read]; GetHeader[file]; IF ct#NIL THEN file.wireToPins _ MapPublicToPackage[ct.public, file.package]; -- ct=NIL for ASCII file.vector _ NEW [VectorRep[file.nGroups]]; FOR i: NAT IN [0..file.vector.ngrps) DO file.vector[i] _ NEW [CGRep] ENDLOOP; bytesPerVector _ 24*file.nGroups; TerminalIO.PutF["MTS vectors read from %g (%g vectors).\n", IO.rope[FS.GetName[FS.OpenFileFromStream[file.stream]].fullFName], IO.int[file.nVects]]; THROUGH [0..file.nVects) DO IF blockOffset+bytesPerVector>vectorBlockLength THEN { nBytesRead: NAT _ IO.GetBlock[file.stream, block, 0, vectorBlockLength]; IF nBytesRead#vectorBlockLength THEN ERROR; -- last block truncated blockOffset _ 0; }; FOR g: NAT IN [0..file.vector.ngrps) DO group: CG = file.vector[g]; FOR i: NAT IN [0..8) DO group.tristate.bytes[i] _ GetByte[] ENDLOOP; FOR i: NAT IN [0..8) DO group.data.bytes[i] _ GetByte[] ENDLOOP; FOR i: NAT IN [0..8) DO group.mask.bytes[i] _ GetByte[] ENDLOOP; ENDLOOP; eachVector[file]; -- call back user ENDLOOP; IF NOT IO.EndOf[file.stream] THEN ERROR; -- what is trailing behind the vectors ??? IO.Close[file.stream]; }; CreateCapture: PUBLIC PROC [ct: Core.CellType] RETURNS [capture: Capture] ~ { Failed: PROC [msg: Rope.ROPE] RETURNS [nil: Capture _ NIL] ~ { TerminalIO.PutF["*** %g, vectors will not be captured.\n", IO.rope[msg]]; }; targetRope: Rope.ROPE = NARROW [CoreProperties.GetCellTypeProp[ct, $DUT]]; vectors, package, title: Rope.ROPE; handle: RosemaryUser.RoseDisplay = RosemaryUser.RoseDisplayFor[ct]; targetFlatCT: CoreFlat.FlatCellType; targetCT: Core.CellType; capture _ NEW [CaptureRep]; IF handle=NIL THEN RETURN[Failed["Unable to recover Rosemary display"]]; IF Rope.IsEmpty[targetRope] THEN RETURN[Failed["Missing DUT description"]]; targetFlatCT _ NEW [CoreFlat.FlatCellTypeRec _ CoreFlat.ParseCellTypePath[ct, targetRope, handle.cutSet]]; IF CoreFlat.BelowCutSet[ct, targetFlatCT^, handle.cutSet] THEN RETURN[Failed["DUT is below simulation cutset"]]; targetCT _ CoreFlat.ResolveFlatCellType[ct, targetFlatCT^].cellType; vectors _ NARROW [CoreProperties.GetCellTypeProp[targetCT, $Vectors]]; package _ NARROW [CoreProperties.GetCellTypeProp[targetCT, $Package]]; title _ NARROW [CoreProperties.GetCellTypeProp[targetCT, $Title]]; IF vectors=NIL THEN vectors _ package; IF package=NIL THEN package _ vectors; IF Rope.IsEmpty[vectors] THEN RETURN[Failed["Unable to find name for vector file"]]; IF Rope.IsEmpty[package] THEN RETURN[Failed["Unable to find name for package file"]]; capture.before _ Ports.CreatePort[targetCT, TRUE]; capture.after _ Ports.CreatePort[targetCT, TRUE]; capture.target _ RosemaryVector.CreateTarget[handle.simulation, targetFlatCT, capture.before]; capture.mtsFile _ Create[targetCT, package, vectors, title]; }; EvalAndCapture: PUBLIC PROC [capture: Capture, Eval: RosemaryUser.TestEvalProc, memory: BOOL _ TRUE, useClockEval: BOOL _ TRUE, checkPorts: BOOL _ TRUE] ~ { IF useClockEval THEN { Eval[memory: memory, clockEval: TRUE, checkPorts: FALSE]; IF capture#NIL THEN RosemaryVector.SampleTarget[capture.target, capture.before]; Eval[memory: memory, clockEval: FALSE, checkPorts: checkPorts]; IF capture#NIL THEN { RosemaryVector.SampleTarget[capture.target, capture.after]; WriteVectorFromPorts[capture.mtsFile, capture.before, capture.after]; }; } ELSE { Eval[memory: memory, checkPorts: checkPorts]; IF capture#NIL THEN { RosemaryVector.SampleTarget[capture.target, capture.after]; WriteVectorFromPorts[capture.mtsFile, capture.after, capture.after]; }; }; }; CloseCapture: PUBLIC PROC [capture: Capture] ~ { IF capture#NIL THEN Close[capture.mtsFile]; }; ReadPort: PUBLIC PROC [ct: Core.CellType, p: Ports.Port, eachVector: PROC[]] ~ { ConvertVectorToPort: PROC [file: File] ~ { Setup: PROC [wire: Core.Wire] RETURNS [lvl: Ports.Level, drv: Ports.Drive] ~ { wd: WireDescription = NARROW [RefTab.Fetch[file.wireToPins, wire].val]; IF wd.flavor=ignore THEN { lvl _ L; drv _ inspect; -- ignore port contents, supplied externally } ELSE { group: CG = file.vector[wd.pins.first.group]; bit: [0..60) = wd.pins.first.bit; source, data, mask: BOOL; -- the 3 bits defining a test vector "bit" source _ group.tristate.bits[bit]; data _ group.data.bits[bit]; mask _ group.mask.bits[bit]; IF wd.flavor=pullup THEN { -- simulate the presence of an external pull-up IF source THEN IF data THEN IF mask THEN {lvl _ H; drv _ drive} -- 1 ELSE {lvl _ H; drv _ driveWeak} -- T ELSE IF mask THEN {lvl _ L; drv _ drive} -- 0 ELSE {lvl _ L; drv _ force} -- F, should not happen with pullup ELSE IF data THEN IF mask THEN {lvl _ H; drv _ driveWeak} -- H ELSE {lvl _ H; drv _ driveWeak} -- ? ELSE IF mask THEN {lvl _ L; drv _ expect} -- L ELSE {lvl _ H; drv _ driveWeak} -- X } ELSE { IF source THEN IF data THEN IF mask THEN {lvl _ H; drv _ drive} -- 1 ELSE {lvl _ H; drv _ force} -- T ELSE IF mask THEN {lvl _ L; drv _ drive} -- 0 ELSE {lvl _ L; drv _ force} -- F ELSE IF data THEN IF mask THEN {lvl _ H; drv _ expect} -- H ELSE {lvl _ X; drv _ none} -- ? ELSE IF mask THEN {lvl _ L; drv _ expect} -- L ELSE {lvl _ X; drv _ none} -- X } }; }; EachWirePortPair: Ports.EachWirePortPairProc ~ { subElements _ FALSE; -- presume that we won't explore children SELECT port.levelType FROM l => [port.l, port.d] _ Setup[wire]; b => { [port.l, port.d] _ Setup[wire]; port.b _ SELECT port.l FROM H => TRUE, L => FALSE, ENDCASE => ERROR; }; ls => { drv: Ports.Drive; IF port.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO [port.ls[i], port.ds[i]] _ Setup[wire[i]]; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO [port.ls[i], drv] _ Setup[wire[i]]; IF i=0 THEN port.d _ drv ELSE IF port.d#drv THEN ERROR; -- should be separate drives ... ENDLOOP }; bs => { lvl: Ports.Level; drv: Ports.Drive; IF port.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO [lvl, port.ds[i]] _ Setup[wire[i]]; port.bs[i] _ SELECT lvl FROM H => TRUE, L => FALSE, ENDCASE => ERROR; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO [lvl, drv] _ Setup[wire[i]]; port.bs[i] _ SELECT lvl FROM H => TRUE, L => FALSE, ENDCASE => ERROR; IF i=0 THEN port.d _ drv ELSE IF port.d#drv THEN ERROR; -- should be separate drives ... ENDLOOP }; c => ERROR; -- not yet supported lc => ERROR; -- not yet supported q => ERROR; -- not yet supported composite => subElements _ TRUE; -- explore sub-elements ENDCASE => ERROR; -- unexpected value }; [] _ Ports.VisitBinding[ct.public, p, EachWirePortPair]; -- compute port eachVector[]; }; vectors: Rope.ROPE = NARROW [CoreProperties.GetCellTypeProp[ct, $Vectors]]; IF Rope.IsEmpty[vectors] THEN { TerminalIO.PutF["*** MTS vector file name not specified.\n"]; ERROR; }; Read[ct, vectors, ConvertVectorToPort]; }; WriteAsciiHeader: PROC [file: File, out: IO.STREAM] ~ { EachWD: SymTab.EachPairAction ~ { wd: WireDescription = NARROW [val]; IO.PutRope[out, wd.name]; SELECT wd.flavor FROM signal => IO.PutF[out, " : "]; pullup => IO.PutF[out, " /PU : "]; gnd => IO.PutF[out, " /0V : "]; vdd => IO.PutF[out, " /5V : "]; ignore => IF wd.pins#NIL THEN IO.PutF[out, " /NC : "] ELSE IO.PutF[out, " /NC"]; ENDCASE => ERROR; FOR pins: LIST OF PinDescription _ wd.pins, pins.rest UNTIL pins=NIL DO pin: PinDescription = pins.first; IO.PutF[out, " %g:%g", IO.rope[pin.pkgName], IO.rope[pin.conName]]; IO.PutF[out, "[G:%g,B:%g,M:%x,b:%g]", IO.int[pin.group], IO.int[pin.byte], IO.int[pin.mask], IO.int[pin.bit]]; ENDLOOP; IO.PutF[out, ";"]; TiogaStreams.EndNode[out, same]; }; IO.PutF[out, "%lTitle:%l %g", IO.rope["b"], IO.rope["B"], IO.rope[file.id]]; TiogaStreams.ChangeDepth[out, 1]; TiogaStreams.EndNode[out, reset]; IO.PutF[out, "%lFixture:%l %g", IO.rope["b"], IO.rope["B"], IO.rope[file.package.fixtureName]]; TiogaStreams.EndNode[out, reset]; IO.PutF[out, "%lSignals:%l", IO.rope["b"], IO.rope["B"]]; TiogaStreams.ChangeDepth[out, 1]; [] _ SymTab.Pairs[file.package.signals, EachWD]; TiogaStreams.EndNode[out, reset]; IO.PutF[out, "%lVectors:%l", IO.rope["b"], IO.rope["B"]]; TiogaStreams.ChangeDepth[out, 1]; }; MTSToAscii: PROC [base: Rope.ROPE] ~ { EachVector: PROC [file: File] ~ { IF firstVector THEN WriteAsciiHeader[file, out]; firstVector _ FALSE; IO.PutF[out, "%l", IO.rope["sf"]]; FOR i: NAT IN [0..file.vector.ngrps) DO group: CG = file.vector[i]; FOR rank: [0..60) IN [0..60) DO source, data, mask: BOOL; -- the 3 bits defining a test vector "bit" c: CHAR; source _ group.tristate.bits[rank]; data _ group.data.bits[rank]; mask _ group.mask.bits[rank]; IF source THEN IF data THEN IF mask THEN c _ '1 ELSE c _ 'T ELSE IF mask THEN c _ '0 ELSE c _ 'F ELSE IF data THEN IF mask THEN c _ 'H ELSE c _ '? ELSE IF mask THEN c _ 'L ELSE c _ 'X; IO.PutChar[out, c]; IF rank=15 OR rank=31 OR rank=47 THEN IO.PutChar[out, ' ]; ENDLOOP; IO.PutChar[out, ' ]; ENDLOOP; IO.PutF[out, "%l", IO.rope["SF"]]; TiogaStreams.EndNode[out, same]; }; firstVector: BOOL _ TRUE; root: TiogaFileOps.Ref _ TiogaFileOps.CreateRoot[]; out: IO.STREAM _ TiogaStreams.CreateOutput[to: root, defaultFormat: "code"]; Read[NIL, base, EachVector]; IO.Close[out]; TiogaFileOps.Store[root, MakeFileName[base, "mtsAscii", TRUE]]; }; EstimatePacking: PROC [base: Rope.ROPE] RETURNS [before: INT, after: INT] ~ { EachVector: PROC [file: File] ~ { same: BOOL _ TRUE; IF previousVector=NIL THEN { previousVector _ NEW [VectorRep[file.vector.ngrps]]; before _ file.nVects; same _ FALSE; } ELSE { FOR i: NAT IN [0..file.vector.ngrps) DO FOR j: NAT IN [0..8) DO same _ same AND (file.vector[i].tristate.bytes[j]=previousVector[i].tristate.bytes[j]) AND (file.vector[i].data.bytes[j]=previousVector[i].data.bytes[j]) AND (file.vector[i].mask.bytes[j]=previousVector[i].mask.bytes[j]); ENDLOOP; ENDLOOP; }; IF NOT same THEN { after _ after+1; FOR i: NAT IN [0..file.vector.ngrps) DO FOR j: NAT IN [0..8) DO previousVector[i].tristate.bytes[j] _ file.vector[i].tristate.bytes[j]; previousVector[i].data.bytes[j] _ file.vector[i].data.bytes[j]; previousVector[i].mask.bytes[j] _ file.vector[i].mask.bytes[j]; ENDLOOP; ENDLOOP; }; }; previousVector: Vector _ NIL; before _ 0; after _ 0; Read[NIL, base, EachVector]; }; AsciiToMTS: PROC [base: Rope.ROPE] ~ { ERROR; }; MTSCaptureTP: PUBLIC RosemaryUser.TestProc ~ { logicTime: NAT = Ports.PortIndex[cellType.public, "RosemaryLogicTime"]; memory: BOOL = GetBool[cellType, $Memory, FALSE]; capture: Capture _ CreateCapture[cellType]; p[logicTime].b _ TRUE; p[logicTime].d _ drive; DO ENABLE Rosemary.Stop => IF reason=$BoolWireHasX THEN REJECT ELSE { TerminalIO.PutF["Simulation completed; msg: %g; reason %g\n", IO.rope[msg], IO.atom[reason]]; EXIT}; p[logicTime].b _ NOT p[logicTime].b; EvalAndCapture[capture: capture, Eval: Eval, memory: memory]; ENDLOOP; IF capture#NIL THEN Close[capture.mtsFile]; }; OldMTSReplayTP: PUBLIC RosemaryUser.TestProc ~ { EachVector: PROC [] ~ { Ports.CopyPortValue[from: vectorPort, to: p]; Eval[memory: memory, clockEval: TRUE, checkPorts: FALSE]; Ports.CopyPortValue[from: vectorPort, to: p]; Eval[memory: memory, clockEval: FALSE, checkPorts: TRUE]; }; memory: BOOL = GetBool[cellType, $Memory, FALSE]; vectorPort: Ports.Port; vectorPort _ Ports.CreatePort[cellType, TRUE]; ReadPort[cellType, vectorPort, EachVector]; }; MakeFileName: PROC [source: Rope.ROPE, suffix: Rope.ROPE, local: BOOL] RETURNS [new: Rope.ROPE] ~ { cp: FS.ComponentPositions; server, dir, subDirs, base: Rope.ROPE; [source, cp] _ FS.ExpandName[source]; server _ source.Substr[cp.server.start, cp.server.length]; dir _ source.Substr[cp.dir.start, cp.dir.length]; subDirs _ source.Substr[cp.subDirs.start, cp.subDirs.length]; base _ source.Substr[cp.base.start, cp.base.length]; IF server.Length[]#0 THEN { -- force file name to be within working directory dir _ NIL; subDirs _ NIL; }; new _ FS.ConstructFName[[NIL, dir, subDirs, base, suffix, NIL]]; }; GetBool: PROC [ct: Core.CellType, prop: ATOM, default: BOOL] RETURNS [BOOL] ~ { rb: REF BOOL _ NARROW [CoreProperties.GetCellTypeProp[ct, prop]]; RETURN [IF rb=NIL THEN default ELSE rb^]; }; Fixture: TYPE ~ REF FixtureRep; FixtureRep: TYPE ~ RECORD [ name: Rope.ROPE, connectors: SymTab.Ref, -- name -> ConnectorDescription pins: SymTab.Ref -- name -> PinDescription ]; ConnectorDescription: TYPE ~ REF ConnectorDescriptionRep; ConnectorDescriptionRep: TYPE ~ RECORD [ name: Rope.ROPE, board: [0..8), port: [1..3] ]; Token: IO.BreakProc ~ { -- stop on whitespace and /;, ignore ,: RETURN[SELECT char FROM IN [IO.NUL .. IO.SP], ',, ': => sepr, '/, '; => break, ENDCASE => other] }; SyntaxError: PUBLIC SIGNAL [msg: Rope.ROPE, at: INT, s: IO.STREAM] ~ CODE; Complain: PROC [msg: Rope.ROPE, s: IO.STREAM] ~ { pos: INT _ IO.GetIndex[s]; SIGNAL SyntaxError[msg, pos, s]; }; EndOfStatement: PROC [s: IO.STREAM, skip: BOOL _ FALSE] ~ { buffer: REF TEXT _ RefText.ObtainScratch[256]; DO kind: IO.TokenKind; token: REF TEXT; [tokenKind: kind, token: token] _ IO.GetCedarToken[s, buffer]; SELECT TRUE FROM kind=tokenEOF => {Complain["End of file in the middle of a statement", s]; EXIT}; kind=tokenSINGLE AND token[0]='; => EXIT; skip => NULL; ENDCASE => Complain["Extraneous information ignored", s]; skip _ TRUE; ENDLOOP; RefText.ReleaseScratch[buffer]; }; AssignPin: PROC [port: [1..3], index: [1..20]] RETURNS [bit: [0..60), byte: [0..8), mask: BYTE] ~ { bit _ 20*(port-1)+index-1; byte _ bit/8; mask _ bitMask[bit MOD 8]; }; ReadFixture: PUBLIC PROC [file: Rope.ROPE] RETURNS [f: Fixture] ~ { s: IO.STREAM = FS.StreamOpen[MakeFileName[file, "mtsFixture", FALSE], read]; tester: ARRAY [0..8) OF RECORD [ -- to check that physical resources are used only once connectors: ARRAY [1..3] OF Rope.ROPE, pins: ARRAY [0..60) OF Rope.ROPE ]; FOR i: NAT IN [0..8) DO FOR j: NAT IN [1..3] DO tester[i].connectors[j] _ NIL ENDLOOP; FOR j: NAT IN [0..60) DO tester[i].pins[i] _ NIL ENDLOOP; ENDLOOP; f _ NIL; DO ENABLE IO.EndOfStream => EXIT; id: Rope.ROPE; n: INT; id _ IO.GetTokenRope[s, Token].token; SELECT TRUE FROM Rope.Equal[id, "Name", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; name: Rope.ROPE _ IO.GetTokenRope[s, Token].token; IF f#NIL THEN Complain["Name of fixture already defined", s] ELSE f _ NEW [FixtureRep _ [name: name, connectors: SymTab.Create[], pins: SymTab.Create[]]]; EndOfStatement[s]; }; Rope.Equal[id, "Connector", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; connector: ConnectorDescription _ NEW [ConnectorDescriptionRep]; connector.name _ IO.GetTokenRope[s, Token].token; -- of connector IF NOT SymTab.Insert[f.connectors, connector.name, connector] THEN Complain["Connector already defined", s]; n _ IO.GetInt[s]; IF n NOT IN [0..8) THEN Complain["Board number not in [0..8)", s] ELSE connector.board _ n; id _ IO.GetTokenRope[s, Token].token; SELECT TRUE FROM Rope.Equal[id, "T1"] => connector.port _ 1; Rope.Equal[id, "T2"] => connector.port _ 2; Rope.Equal[id, "T3"] => connector.port _ 3; ENDCASE => Complain["Invalid port identification", s]; id _ tester[connector.board].connectors[connector.port]; -- previously allocated? IF id#NIL THEN Complain[Rope.Cat["Connector slot already in use under the name ", id], s] ELSE tester[connector.board].connectors[connector.port] _ connector.name; EndOfStatement[s]; }; Rope.Equal[id, "Pin", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; pin: PinDescription = NEW [PinDescriptionRep]; connector: ConnectorDescription _ NIL; pin.pkgName _ IO.GetTokenRope[s, Token].token; -- of pin on package IF NOT SymTab.Insert[f.pins, pin.pkgName, pin] THEN Complain["Pin already defined", s]; connector _ NARROW [SymTab.Fetch[f.connectors, IO.GetTokenRope[s, Token].token].val]; IF connector=NIL THEN Complain["Connector has not been defined", s]; pin.group _ connector.board; n _ IO.GetInt[s]; IF n NOT IN [1..20] THEN Complain["Pin number on connector not in [1..20]", s] ELSE { pin.conName _ IO.PutFR["%g.%02g", IO.rope[connector.name], IO.int[n]]; [pin.bit, pin.byte, pin.mask] _ AssignPin[connector.port, n]; }; id _ tester[pin.group].pins[pin.bit]; IF id#NIL THEN Complain[Rope.Cat["Pin slot already in use under the name ", id], s] ELSE tester[pin.group].pins[pin.bit] _ pin.pkgName; EndOfStatement[s]; }; ENDCASE => { Complain["Unknown statement type", s]; EndOfStatement[s, TRUE]; }; REPEAT PrematureEOF => Complain["End of file in the middle of a statement", s]; ENDLOOP; }; ReadPackage: PUBLIC PROC [file: Rope.ROPE] RETURNS [p: Package] ~ { s: IO.STREAM _ FS.StreamOpen[MakeFileName[file, "mtsPackage", FALSE], read]; pinToWire: SymTab.Ref _ SymTab.Create[]; -- to check each pin is used at most once fixture: Fixture _ NIL; p _ NIL; DO ENABLE IO.EndOfStream => EXIT; id: Rope.ROPE; id _ IO.GetID[s]; SELECT TRUE FROM Rope.Equal[id, "Name", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; name: Rope.ROPE _ IO.GetTokenRope[s, Token].token; IF p#NIL THEN Complain["Name of package already defined", s] ELSE p _ NEW [PackageRep _ [name: name, signals: SymTab.Create[]]]; EndOfStatement[s]; }; Rope.Equal[id, "Fixture", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; id _ IO.GetTokenRope[s, Token].token; -- of fixture, allow . and / inside ... fixture _ ReadFixture[id]; p.fixtureName _ fixture.name; EndOfStatement[s]; }; Rope.Equal[id, "Wire", FALSE] => { ENABLE IO.EndOfStream => GOTO PrematureEOF; wire: WireDescription = NEW [WireDescriptionRep _ [flavor: signal]]; wire.name _ IO.GetTokenRope[s, Token].token; -- of pin IF NOT SymTab.Insert[p.signals, wire.name, wire] THEN Complain["Wire already defined", s]; id _ IO.GetTokenRope[s, Token].token; -- pin name or '/ IF Rope.Equal[id, "/"] THEN { id _ IO.GetTokenRope[s, Token].token; -- option token SELECT TRUE FROM Rope.Equal[id, "0V", FALSE] => wire.flavor _ gnd; Rope.Equal[id, "5V", FALSE] => wire.flavor _ vdd; Rope.Equal[id, "PU", FALSE] => wire.flavor _ pullup; Rope.Equal[id, "NC", FALSE] => wire.flavor _ ignore; ENDCASE => Complain["Incorrect option", s]; IF wire.flavor#signal THEN id _ IO.GetTokenRope[s, Token].token; }; UNTIL Rope.Equal[id, ";"] DO pin: PinDescription = NARROW [SymTab.Fetch[fixture.pins, id].val]; IF pin=NIL THEN Complain["Pin not available in package", s] ELSE { wire.pins _ CONS [pin, wire.pins]; id _ NARROW [SymTab.Fetch[pinToWire, pin.pkgName].val]; IF id#NIL THEN Complain[Rope.Cat["Pin already connected to wire ", id], s] ELSE [] _ SymTab.Insert[pinToWire, pin.pkgName, wire.name]; }; id _ IO.GetTokenRope[s, Token].token; -- pin name or ; ENDLOOP; IF wire.flavor#ignore AND wire.pins=NIL THEN Complain["No pins specified for this wire", s]; }; ENDCASE => { Complain["Unknown statement type", s]; EndOfStatement[s, TRUE]; }; REPEAT PrematureEOF => Complain["End of file in the middle of a statement", s]; ENDLOOP; }; DumpFixture: PROC [in: Rope.ROPE, out: Rope.ROPE _ NIL] ~ { EachPin: SymTab.EachPairAction ~ { pd: PinDescription = NARROW [val]; IO.PutF[s, "%s %s %g %g %g\n", IO.rope[pd.pkgName], IO.rope[pd.conName], IO.int[pd.group], IO.int[pd.byte], IO.int[pd.mask]]; }; f: Fixture = ReadFixture[in]; s: IO.STREAM; IF out=NIL THEN out _ in; s _ FS.StreamOpen[MakeFileName[out, "fxt", TRUE], create]; [] _ SymTab.Pairs[f.pins, EachPin]; IO.Close[s]; }; WriteFixtureSetup: PROC [packageName: IO.ROPE] ~ { PinDesc: TYPE = RECORD[name: IO.ROPE, flavor: WireFlag, pin: PinDescription]; TwoPinDesc: TYPE = RECORD[pd1, pd2: PinDesc]; root: TiogaFileOps.Ref _ TiogaFileOps.CreateRoot[]; out: IO.STREAM _ TiogaStreams.CreateOutput[to: root]; pins: LIST OF PinDesc _ NIL; package: Package _ ReadPackage[packageName]; eachWire: SymTab.EachPairAction = { wd: WireDescription _ NARROW[val]; list: LIST OF PinDescription _ wd.pins; FOR list _ list, list.rest WHILE list#NIL DO pins _ CONS[[wd.name, wd.flavor, list.first], pins] ENDLOOP}; [] _ SymTab.Pairs[package.signals, eachWire]; DO done: BOOL _ TRUE; FOR list: LIST OF PinDesc _ pins, list.rest WHILE list#NIL AND list.rest#NIL DO SELECT Rope.Compare[list.first.pin.conName, list.rest.first.pin.conName] FROM less => LOOP; greater => { [list.first, list.rest.first] _ TwoPinDesc[list.rest.first, list.first]; done _ FALSE}; ENDCASE => ERROR; ENDLOOP; IF done THEN EXIT ENDLOOP; out.PutF["Package: %g", IO.rope[package.name]]; TiogaStreams.EndNode[out, same]; out.PutF["Fixture: %g", IO.rope[package.fixtureName]]; TiogaStreams.EndNode[out, same]; out.PutF["Pins:"]; TiogaStreams.ChangeDepth[out, 1]; FOR list: LIST OF PinDesc _ pins, list.rest WHILE list#NIL DO out.PutF["%l%g ", IO.rope["f"], IO.rope[list.first.pin.conName]]; SELECT list.first.flavor FROM signal => out.PutRope[" "]; pullup => out.PutRope["PU "]; gnd => out.PutRope["0V "]; vdd => out.PutRope["5V "]; ignore => out.PutRope["NC "]; ENDCASE => ERROR; out.PutF["%g", IO.rope[list.first.name]]; TiogaStreams.EndNode[out, same]; ENDLOOP; out.Close[]; TiogaFileOps.Store[root, Rope.Cat[packageName, ".mtsFixtureSetup"]]}; PortFromVectorProc: TYPE ~ PROC [file: File, wire: Core.Wire] RETURNS [lvl: Ports.Level, drv: Ports.Drive]; PortFromVector: PROC [file: File, port: Ports.Port, EachSignal: PortFromVectorProc] ~ { EachWirePortPair: Ports.EachWirePortPairProc ~ { subElements _ FALSE; -- presume that we won't explore children SELECT port.levelType FROM l => [port.l, port.d] _ EachSignal[file, wire]; b => { [port.l, port.d] _ EachSignal[file, wire]; port.b _ SELECT port.l FROM H => TRUE, L => FALSE, ENDCASE => ERROR; }; ls => { drv: Ports.Drive; IF port.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO [port.ls[i], port.ds[i]] _ EachSignal[file, wire[i]]; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO [port.ls[i], drv] _ EachSignal[file, wire[i]]; IF i=0 THEN port.d _ drv ELSE IF port.d#drv THEN ERROR; -- should be separate drives ... ENDLOOP }; bs => { lvl: Ports.Level; drv: Ports.Drive; IF port.driveType=separate THEN FOR i: NAT IN [0..wire.size) DO [lvl, port.ds[i]] _ EachSignal[file, wire[i]]; port.bs[i] _ SELECT lvl FROM H => TRUE, L => FALSE, ENDCASE => ERROR; ENDLOOP ELSE FOR i: NAT IN [0..wire.size) DO [lvl, drv] _ EachSignal[file, wire[i]]; port.bs[i] _ SELECT lvl FROM H => TRUE, L => FALSE, ENDCASE => ERROR; IF i=0 THEN port.d _ drv ELSE IF port.d#drv THEN ERROR; -- should be separate drives ... ENDLOOP }; c => ERROR; -- not yet supported lc => ERROR; -- not yet supported q => ERROR; -- not yet supported composite => subElements _ TRUE; -- explore sub-elements ENDCASE => ERROR; -- unexpected value }; [] _ Ports.VisitBinding[file.dut.public, port, EachWirePortPair]; }; VerifyPublicState: PROC [file: File, simulation: Rosemary.Simulation, port: Ports.Port, EachSignal: PortFromVectorProc] ~ { VerifyPublic: Ports.EachWirePortPairProc ~ { Complain: PROC [] ~ { msg: IO.STREAM _ IO.ROS[]; nbits: NAT; SELECT port.levelType FROM l, b => nbits _ 1; ls => nbits _ port.ls.size; bs => nbits _ port.bs.size; ENDCASE => ERROR; IO.PutF[msg, "Wire %g expected ", IO.rope[CoreOps.GetFullWireName[simulation.cellType.public, wire]]]; SELECT port.levelType FROM l => IO.PutRope[msg, Ports.levelNames[port.l]]; b => IO.PutRope[msg, IF port.b THEN "1" ELSE "0"]; ls => IO.PutRope[msg, Ports.LSToRope[port.ls]]; bs => { FOR bit: NAT IN [0..port.bs.size) DO IO.PutRope[msg, IF port.bs[bit] THEN "1" ELSE "0"] ENDLOOP; }; ENDCASE => ERROR; IO.PutF[msg, ", but was %g", IO.rope[Ports.LSToRope[ls, nbits]]]; SIGNAL Ports.CheckError[IO.RopeFromROS[msg]]; }; IF port.levelType#composite THEN { -- otherwise, continue recursive descent firstFreeBit: NAT _ 0; subElements _ FALSE; flatWire.wire _ wire; FOR vals: Rosemary.RoseValues _ Rosemary.GetValues[simulation, flatWire], vals.rest UNTIL vals=NIL DO currentValue: Ports.LevelSequence _ vals.first.roseWire.currentValue; IF currentValue=NIL THEN { ls[firstFreeBit] _ vals.first.roseWire.wireLevel; firstFreeBit _ firstFreeBit + 1; } ELSE { firstBit: NAT _ vals.first.fieldStart; FOR bit: NAT IN [0..vals.first.fieldWidth) DO ls[firstFreeBit + bit] _ currentValue[firstBit+bit]; ENDLOOP; firstFreeBit _ firstFreeBit + vals.first.fieldWidth; }; ENDLOOP; SELECT port.levelType FROM l => IF port.l#X AND port.l#ls[0] THEN Complain[]; b => SELECT ls[0] FROM H => IF NOT port.b THEN Complain[]; L => IF port.b THEN Complain[]; ENDCASE => Complain[]; ls => FOR i: NAT IN [0..port.ls.size) DO IF port.ls[i]#X AND port.ls[i]#ls[i] THEN { Complain[]; EXIT; }; ENDLOOP; bs => FOR i: NAT IN [0..port.bs.size) DO SELECT ls[i] FROM H => IF NOT port.bs[i] THEN {Complain[]; EXIT}; L => IF port.bs[i] THEN {Complain[]; EXIT}; ENDCASE => {Complain[]; EXIT}; ENDLOOP; ENDCASE => ERROR; -- only l, b, ls, bs ports are supported }; }; ls: Ports.LevelSequence _ NEW [Ports.LevelSequenceRec[1024]]; flatWire: CoreFlat.FlatWire _ NEW [CoreFlat.FlatWireRec]; flatWire.flatCell _ CoreFlat.rootCellType; flatWire.wireRoot _ public; PortFromVector[file, port, EachSignal]; [] _ Ports.VisitBinding[simulation.cellType.public, port, VerifyPublic]; }; StandardDriving: PortFromVectorProc ~ { wd: WireDescription = NARROW [RefTab.Fetch[file.wireToPins, wire].val]; group: CG = file.vector[wd.pins.first.group]; bit: [0..60) = wd.pins.first.bit; SELECT TRUE FROM group.tristate.bits[bit] => { drv _ drive; lvl _ IF group.data.bits[bit] THEN H ELSE L; }; wd.flavor=pullup => { -- simulate pullup when tester is not driving drv _ driveWeak; lvl _ H; }; ENDCASE => { drv _ none; lvl _ L; }; }; StandardChecking: PortFromVectorProc ~ { wd: WireDescription = NARROW [RefTab.Fetch[file.wireToPins, wire].val]; group: CG = file.vector[wd.pins.first.group]; bit: [0..60) = wd.pins.first.bit; IF group.tristate.bits[bit] OR group.mask.bits[bit]THEN { drv _ expect; lvl _ IF group.data.bits[bit] THEN H ELSE L; } ELSE { drv _ none; lvl _ X; }; }; MTSReplayTP: PUBLIC RosemaryUser.TestProc ~ { EachVector: PROC [file: File] ~ { PortFromVector[file, p, StandardDriving]; -- get new driving info Eval[memory: memory, clockEval: FALSE, checkPorts: FALSE]; VerifyPublicState[file, simulation, p, StandardChecking]; -- verify state }; memory: BOOL = GetBool[cellType, $Memory, FALSE]; vectors: Rope.ROPE = NARROW [CoreProperties.GetCellTypeProp[cellType, $Vectors]]; IF Rope.IsEmpty[vectors] THEN { TerminalIO.PutF["*** MTS vector file name not specified.\n"]; ERROR; }; TerminalIO.PutF["Replaying MTS vectors from %g on %g at transistor level.\n", IO.rope[vectors], IO.rope[CoreOps.GetCellTypeName[cellType]]]; Read[cellType, vectors, EachVector]; }; MTSReplayGateTP: RosemaryUser.TestProc ~ { EachVector: PROC [file: File] ~ { PortFromVector[file, p, StandardDriving]; -- get new driving info Eval[memory: memory, clockEval: TRUE, checkPorts: FALSE]; PortFromVector[file, p, StandardDriving]; -- get new driving info (same as previous) Eval[memory: memory, clockEval: FALSE, checkPorts: FALSE]; VerifyPublicState[file, simulation, p, StandardChecking]; -- verify state }; memory: BOOL = GetBool[cellType, $Memory, FALSE]; vectors: Rope.ROPE = NARROW [CoreProperties.GetCellTypeProp[cellType, $Vectors]]; IF Rope.IsEmpty[vectors] THEN { TerminalIO.PutF["*** MTS vector file name not specified.\n"]; ERROR; }; TerminalIO.PutF["Replaying MTS vectors from %g on %g at gate level.\n", IO.rope[vectors], IO.rope[CoreOps.GetCellTypeName[cellType]]]; Read[cellType, vectors, EachVector]; }; MTSReplayTSTP: RosemaryUser.TestProc ~ { ERROR; -- Function is not yet implemented }; MTSReplayTSGateTP: RosemaryUser.TestProc ~ { ERROR; -- Function is not yet implemented }; Init: PROC [] RETURNS [] ~ { bitMask[0] _ 080H; bitMask[1] _ 040H; bitMask[2] _ 020H; bitMask[3] _ 010H; bitMask[4] _ 008H; bitMask[5] _ 004H; bitMask[6] _ 002H; bitMask[7] _ 001H; RosemaryUser.RegisterTestProc["MTSCapture", MTSCaptureTP]; RosemaryUser.RegisterTestProc["MTSReplay", MTSReplayTP]; RosemaryUser.RegisterTestProc["MTSReplayGate", MTSReplayGateTP]; RosemaryUser.RegisterTestProc["MTSReplayTS", MTSReplayTSTP]; RosemaryUser.RegisterTestProc["MTSReplayTSGate", MTSReplayTSGateTP]; RosemaryUser.RegisterTestProc["OldMTSReplay", OldMTSReplayTP]; }; Init[]; END. #^MTSVectorImpl.mesa Copyright ำ 1987 by Xerox Corporation. All rights reserved. Jean-Marc Frailong October 11, 1988 6:35:19 pm PDT Don Curry September 6, 1988 5:53:51 pm PDT Test vector generation for the PC-based MTS tester Types & constants Meaning of the codes: Code Resp Mask Data Function 0 1 1 0 Issue 0 & check it L 0 1 0 Check 0 emitted by DUT 1 1 1 1 Issue 1 & check it H 0 1 1 Check 1 emitted by DUT T 1 0 1 Issue 1, don't check F 1 0 0 Issue 0, don't check ? 0 0 1 data 1, don't drive or sense X 0 0 0 data 0, don't drive or sense Pin management Build the wireToPins field of a file from dut.public & package Physical file access details for write Write header. The file pointer is moved to the end of the header. The first call to WriteHeader is a dummy in order to ascertain the header size and fill the file up to the start point of the first vector block. The real header is written only at closing time (this simplifies a lot of things). File position on entry is ignored, and is set to start pont of 1st vector block on exit (always a multiple of 1K for efficiency on PC). Header format is known by PC software and should not be changed. Write a byte into file and then update file checksum (not buffered). Write a 16-bit integer in little-endian order (LSB first) Write a 32-bit integer in little-endian order (LSB first) PutCard: PROC [c: CARD32] ~ { Write a 32-bit integer in little-endian order (LSB first) n: Basics.LongNumber; n.lc _ c; PutByte[n.ll]; PutByte[n.lh]; PutByte[n.hl]; PutByte[n.hh]; }; Write the rope C-style (NULL terminated). Write the rope extended to length characters with NULLs (or truncated if too long) Write byte into file buffer and update file checksum (physically written only on flush). Flush current block padded to nBytes with zeroes. Physical file access details for read Read the header in and fill in adequate fields of the File structure Header format is known by PC software and should not be changed. Read a byte and compute checksum Read a 32-bit integer in little-endian order (LSB first) Read a 16-bit integer in little-endian order (LSB first) Read a NULL-padded rope of the required length. Strip padding on return. Read a NULL-terminated rope. Strip padding on return. Low-level client access to MTS files Create the handle and make it ready to accept vectors. Vectors are written into fileBase.xtv, pin information is read from fileBase.mtsPins. Compute the highest used board number for signals that are driven by the tester during simulation (i.e. exclude power and special pins) Write file.vector at the end of the file Convert a pair of Rosemary ports into an MTS vector and write it into file. Note that this version does NOT support force... Record the right information into the test vector Flush out current block if any, write file header & close the file stream. Call back user for each vector that's read in -- Note that there is no need to verify the checksum since GetHeader did it... High-level client access to MTS files Build the capture structure from CT properties, NIL means no capture. Properties are: - On top level cell: DUT: -- CoreFlat path to the device under test Title: -- title of the generated MTS file CutSet: (c.f. RosemaryUser) Tests: LIST ["MTSCapture"] (seen by RosemaryUser, but required for this procedure...) - On the DUT: Package: -- name of the file describing the package, and hence the fixture. Vectors: -- name of the file where vectors will be stored. Capture state before Eval, then do eval once or twice (for clockEval hack), then capture state again. Derive vector from before & after state, then write it. Setup the right information into the port ELSE {lvl _ X; drv _ drive} -- X [wire: Core.Wire, port: Port] RETURNS [subElements: BOOL _ TRUE, quit: BOOL _ FALSE] PrintPort: PROC [ct: Core.CellType, p: Ports.Port] ~ { This is flaky debugging code, activate only if needed... Print in clear the contents of a port on the terminal EachWirePortPair: Ports.EachWirePortPairProc ~ { [wire: Core.Wire, port: Port] RETURNS [subElements: BOOL _ TRUE, quit: BOOL _ FALSE] subElements _ port.levelType=composite; IF subElements THEN RETURN; -- no need to print in that case TerminalIO.PutF["%g:(%g)", IO.rope[CoreOps.GetFullWireName[ct.public, wire]], IO.rope[Ports.driveNames[port.d]]]; SELECT port.levelType FROM l => TerminalIO.PutF["%g ", IO.rope[Ports.levelNames[port.l]]]; b => TerminalIO.PutF["%g ", IO.rope[IF port.b THEN "H" ELSE "L"]]; ls => { IF port.driveType=separate THEN ERROR -- not supported yet for debug ... ELSE TerminalIO.PutF["%g ", IO.rope[Ports.LSToRope[port.ls, port.ls.size]]]; }; bs => ERROR; -- not supported yet for debug ... c => ERROR; -- not yet supported lc => ERROR; -- not yet supported q => ERROR; -- not yet supported composite => subElements _ TRUE; -- explore sub-elements ENDCASE => ERROR; -- unexpected value }; [] _ Ports.VisitBinding[ct.public, p, EachWirePortPair]; -- explore port TerminalIO.PutRope["\n"]; }; Conversion between MTS file format and ASCII format Convert base.xtv into base.mtsAscii This version is not definitive... The format may still change, or even be dropped altogether. Estimate how many vectors would be left after compaction Convert base.mtsAscii into base.xtv -- NOT YET IMPLEMENTED Vector capture Extension of LogicRosemaryImpl.LogicTest to provide MTS vector capture Extension of LogicRosemaryImpl.LogicTest to provide MTS vector capture Odds and ends Extend source using suffix. Force file to be local is local is TRUE. Read a boolean property from a CT with the specified default Specification descriptions Syntax: Colon tokens are ignored. ; : / are special characters. : token is always ignored. Fixture Name ; Connector ; Pin ; Package Name ; Fixture ; Wire [ / option ] ... ; Option is one of: 0V : signal is power ground. Check before starting test, TS it afterwards 5V : signal is power +5V. Check before starting test, TS it afterwards PU : signal has a pullup. Check presence before starting test NC : signal is not connected to the tester. There may be no pins on this signal (i.e. it is really a no-connect), or there may be some pins. In the latter case, the tester will never drive or sample the values on those pins (typically strange power supplies). To ensure that the tester does not burn up, power supplies outside of the range 0V-5V should be connected to the DUT without any connection to the tester itself. not specified: normal signal Compute the bit position, byte position & mask given the conector port and index in the port (pin number on the board connector is twice the index as odd-numbered wires are grounded for noise immunity). Connectors are checked to have unique names. Connectors are checked not to use twice the same (board, port) combination; Fixture dumping Reformat the fixture description so that the PC can scan it very easily. Format is { }* Fixture setup description Vector replay Call EachSignal on each wire used in the port to setup information [wire: Core.Wire, port: Port] RETURNS [subElements: BOOL _ TRUE, quit: BOOL _ FALSE] Verify that the values in port match current values in simulation. Port is for the simulation public (this is intended for replay purposes only). Drive values are not used. H and L indicate expect those values, X means don't care. PROC [wire: Core.Wire, port: Port] RETURNS [subElements: BOOL _ TRUE, quit: BOOL _ FALSE] Issue an error message explaining the error [file: File, wire: Core.Wire] RETURNS [lvl: Ports.Level, drv: Ports.Drive] Simulates normal driving phase of tester [file: File, wire: Core.Wire] RETURNS [lvl: Ports.Level, drv: Ports.Drive] Simulates normal verification phase of tester (used by VerifyPublicState) [simulation: Rosemary.Simulation, cellType: Core.CellType, p: Ports.Port, Eval: TestEvalProc] Replay proc at transistor level (without ClockEval) without tri-state verification [simulation: Rosemary.Simulation, cellType: Core.CellType, p: Ports.Port, Eval: TestEvalProc] Replay proc at gate level (with ClockEval) without tri-state verification [simulation: Rosemary.Simulation, cellType: Core.CellType, p: Ports.Port, Eval: TestEvalProc] Replay proc at transistor level (without ClockEval) with tri-state verification [simulation: Rosemary.Simulation, cellType: Core.CellType, p: Ports.Port, Eval: TestEvalProc] Replay proc at transistor level (with ClockEval) with tri-state verification Initialization Various initializations RosemaryUser.RegisterTestProc["MTSCapture2", MTSCaptureTP2]; Jean-Marc Frailong September 14, 1988 2:31:52 pm PDT Minor aesthetic modifications changes to: Fail (local of MapPublicToPackage) changed mesage, Read added vector count print out, ReadPort changed message Jean-Marc Frailong September 16, 1988 12:23:01 pm PDT Need hack to take in account X on gates ... changes to: Record (local of WriteVectorFromPort) ส7็– "cedar" style˜codešœ™Kšœ<™—šฯi™Itable3š ™Mš ™Mš ™Mš ™Mš ™Mš ™Mš ™Mš $™$Mš $™$—™šฯnœœ*œ˜fK™>šกœœœ˜-Kšœ œ˜Kšœ'œ œ˜LK˜—šก œœ˜(šœœ&œ˜2K˜JKšœ%œŸ˜FKšœ˜—K˜—šกœ˜!Kšœœ˜#Kšœ0˜0šœœ˜Kšœœ%˜-Kšœ'˜'Kšœ'œŸ ˜6Kšœ$˜+—K˜—Kšœœœ˜K˜Kšœ,Ÿ#˜OK˜/KšœœœŸ&˜KšœŸ ˜7KšœŸ'˜DKšœ#˜#Jšœ<˜˜>Kšœ6˜6KšœœœŸ˜3Kšœ!Ÿ3˜TKš œœ œœ$œ˜{Kšœ œœœ ˜:Kšœ,˜,Kšœœ˜,Kš œœœœœ œ˜MKšœœ)œ ˜HKšœ@ฯtœ-˜pKšœ˜KšœŸ7˜GK˜K˜—šก œœœ˜)K™(Kšœœ˜&Kšœ˜Kšœ4œ%˜_šœœœ˜'Kšœœ˜Kš œœœœ+œ˜JKš œœœœ'œ˜FKš œœœœ'œ˜FKšœ˜—K˜K˜—šกœœœ8˜YKšœ)œ™KK™0šกœœœœ˜—K™1Kšœœ&˜BKšœœŸ*˜DKšœœœŸ#˜?KšœœœŸ4˜Ušœ˜Kšœœ˜ Kšœœ˜ KšœœŸ&˜8—šœœœ˜-šœ ˜Kšœœ œ œŸ˜JKšœœ œ œŸ˜KKšœœ œ œŸ ˜SKšœœŸ˜"——K˜šœŸ˜šœ ˜šœ œ ˜ Kšœœ œ œŸ˜LKšœœ œ œŸ˜MKšœœ œ œŸ ˜SKšœœŸ˜"—šœ ˜ šœœœ ˜/Kšœœ œ œŸ˜NKšœœ œ œŸ˜KKšœœ œ œŸ˜NKšœœŸ˜!—šœœ ˜Kšœœ œ œŸ˜KKšœœ œ œŸ˜LKšœœ œ œŸ˜LKšœœŸ˜"—K˜—Kšœœ œ œŸ ˜Všœ ˜ šœœŸ&˜>Kšœ œ œ œŸ˜LKšœ˜—šœŸ4˜;šœ ˜Kšœœ œ œŸ˜JKšœœ œ œŸ˜KKšœœ œ œŸ ˜SKšœœŸ˜"—Kšœ˜—K˜—KšœœŸ˜&—Kšœ˜—Kšœœœœ œœœœŸ˜LKšœœœœ œœœœœŸ˜Rš œœœ%œœ˜GKšœ!˜!Kšœœ˜Kšœ&˜&Kšœ ˜ Kšœ ˜ Kšœ˜—K˜—šกœœ=˜KKšœœœ0˜=Kšœ"œœ˜/Kšœ"œœ˜/šœ˜Kšœ>˜>Kš œœ œœœ œœ˜`šœ˜š œœœœœ˜AKšœL˜LKš˜—š œœœœ˜$KšœD˜DKš˜—K˜—šœ˜š œœœœœ˜AKš œœœœœ œœ˜nKš˜—š œœœœ˜$Kš œœœœœ œœ˜fKš˜—K˜—KšœœŸ˜ KšœœŸ˜!KšœœŸ˜ šœŸ˜#šœœœ˜Kšœ'˜'Kšœ˜—Kšœ˜—KšœœŸ˜%—K˜—K˜K˜)š œœœœŸ%˜HKšœœœ˜&Kšœœœ˜"Kšœœœ˜"Kšœ˜—Kšœ/Ÿ˜@KšœŸ˜(K˜K˜—šกœœœ˜#K™JKšœœ˜Kšœœ%˜@KšœP˜PKšœ˜KšœŸ˜:šœœœŸ˜5Kšœœœ˜%Kšœ œ˜$Kšœ˜—KšœœœŸ9˜NKšœ˜K˜K˜—šกœ œ#œœ ˜VK™-šกœœœœ˜&Kšœœ%˜,Kšœ˜K˜—Kšœœ˜Kšœ œ˜%Kšœœœ"˜1Kšœ œM˜]Kšœœ)œ ˜GKšœ˜šœœ˜Kšœ?Ÿ˜R—Kšœœ˜,Kš œœœœœ œ˜MKšœ!˜!Kš œ<œœ œ.œ˜”šœ˜šœ.œ˜6Kšœ œœ4˜HK™NKšœœœŸ˜CKšœ˜Kšœ˜—šœœœ˜'Kšœœ˜Kš œœœœ%œ˜DKš œœœœ!œ˜@Kš œœœœ!œ˜@Kšœ˜—KšœŸ˜#Kšœ˜—Kš œœœœœŸ*˜SKšœ˜K˜——™%šก œœœœ˜MKšœ!œ œ™E™™Kšœ Ÿ)™5K™0K™-KšœœJ™U—šœ œ™KšœŸB™RKšœŸ1™A——š กœœ œœœ˜>Kšœ;œ ˜IK˜—Kšœœœ,˜JKšœœ˜#JšœC˜CK˜$Kšœ˜Jšœ œ˜Kšœœœœ/˜HKšœœœ$˜KKšœœX˜jKšœ8œœ+˜pKšœD˜DKšœ œ6˜FKšœ œ6˜FKšœ œ4˜CKšœ œœ˜&Kšœ œœ˜&Kšœœœ0˜TKšœœœ1˜UKšœ,œ˜2Kšœ+œ˜1Kšœ^˜^Kšœ<˜šœ˜Kšœ$˜$šœ˜Kšœ˜Kš œ œœœœœœ˜DKšœ˜—šœ˜K˜š œœœœœ˜?Kšœ*˜*Kš˜—š œœœœ˜$Kšœ#˜#Kšœœ ˜Kš œœ œœŸ ˜?Kš˜—K˜—šœ˜K˜#š œœœœœ˜?Kšœ#˜#Kš œ œœœœœœ˜EKš˜—š œœœœ˜$Kšœ˜Kš œ œœœœœœ˜EKšœœ ˜Kš œœ œœŸ ˜?Kš˜—K˜—KšœœŸ˜ KšœœŸ˜!KšœœŸ˜ KšœœŸ˜8KšœœŸ˜%—K˜—Kšœ9Ÿ˜HKšœ ˜ K˜—Kšœœœ0˜Kšœœ˜Kšœ=˜=Kšœ˜Kšœ˜—Kšœ'˜'K˜K˜—šก œœ'™6Kšฯb8™8K™5šกœ ™0Kš œœœœœœ™TKšœ'™'Kšœ œœŸ ™˜LJšœœ˜Jšœ ˜Jšœ8œ˜?K˜K˜—š กœœ œœ œ œ˜MK™8šก œœ˜!Kšœœœ˜šœœœ˜Kšœœ ˜4Kšœ˜Kšœœ˜ Kšœ˜—šœ˜šœœœ˜'šœœœ˜šœ ˜KšœG˜JKšœ?˜BKšœ?˜?—Kšœ˜—Kšœ˜—Kšœ˜—šœœœ˜K˜šœœœ˜'šœœœ˜KšœG˜GKšœ?˜?Kšœ?˜?Kšœ˜—Kšœ˜—Kšœ˜—K˜—Jšœœ˜J˜ J˜ Jšœœ˜K˜K˜—šก œœ œ˜&Kšœ#™#Kšœ˜KšŸะbc™K˜——™šก œœ˜.Kšœ4œ™FKšœ œ9˜GKšœœœ˜1Kšœ+˜+Kšœœ˜Kšœ˜š˜š œœœœœ˜BKšœ>œ œ˜]Kšœ˜—Jšœœ˜$Jšœ=˜=Kšœ˜—Kšœ œœ˜+K˜K˜—šกœœ˜0Kšœ4œ™Fšก œœ˜Kšœ-˜-Kšœ œœ˜9Kšœ-˜-Kšœ œœ˜9K˜—Kšœœœ˜1K˜Kšœ(œ˜.Kšœ+˜+K˜K˜——™ šก œœœœ œœ œ˜cKšœ?œ™DKšœœ˜Kšœ!œ˜&Kšœœ˜%Kšœ:˜:Kšœ1˜1Kšœ=˜=Kšœ4˜4šœœŸ1˜MKšœœ œ˜Kšœ˜—Kšœœœœ˜@K˜K˜—š กœœœ œœœ˜OKšœœ™šœœ˜KšœKœ˜QKšœœœ˜)Kšœœ˜ Kšœ2˜9—Kšœœ˜ Kšœ˜—Kšœ˜K˜K˜—šก œœ œ$œ˜cK™สKšœ˜K˜ Kšœœ˜K˜K˜—š ก œœœ œœ˜CK™xJš œœœœ-œ ˜LšœœœœŸ6˜WKšœ œœœ˜&Kšœœ œ˜ Kšœ˜—šœœœ˜Kš œœœœœœ˜>Kš œœœ œœœ˜9Kšœ˜—Jšœœ˜š˜Kšœœœ˜Kšœ œœ˜Kšœœ˜%šœœ˜šœœ˜"Kšœœœ˜+Kšœ œœ˜2Kšœœœ/˜šœ˜Kšœ/˜/šœ˜Kšœ*˜*Kš œ œœœœœœ˜DKšœ˜—šœ˜K˜š œœœœœ˜?Kšœ5˜5Kš˜—š œœœœ˜$Kšœ.˜.Kšœœ ˜Kš œœ œœŸ ˜?Kš˜—K˜—šœ˜K˜#š œœœœœ˜?Kšœ.˜.Kš œ œœœœœœ˜EKš˜—š œœœœ˜$Kšœ'˜'Kš œ œœœœœœ˜EKšœœ ˜Kš œœ œœŸ ˜?Kš˜—K˜—KšœœŸ˜ KšœœŸ˜!KšœœŸ˜ KšœœŸ˜8KšœœŸ˜%—K˜—KšœA˜AK˜K˜—šกœœd˜{K™ๆšก œ ˜,Kš œœœœœœ™Yšกœœ˜K™+Kš œœœœœ˜Kšœœ˜ šœ˜Kšœ˜Kšœ˜Kšœ˜Kšœœ˜—Kšœ œB˜fšœ˜Kšœœ(˜/Kš œœœœœ˜2Kšœœ'˜/šœ˜šœœœ˜$Kšœœœœ˜2Kšœ˜—Kšœ˜—Kšœœ˜—Kšœœ"˜AKšœœ˜-K˜—šœœŸ(˜KKšœœ˜Kšœœ˜Kšœ˜šœQœœ˜eKšœE˜Ešœœœ˜Kšœ1˜1Kšœ ˜ K˜—šœ˜Kšœ œ˜&šœœœ˜-Kšœ4˜4Kšœ˜—Kšœ4˜4K˜—Kšœ˜—šœ˜Kšœœ œœ ˜2šœœ˜Kšœœœœ ˜#Kšœœœ ˜Kšœ˜—šœœœœ˜(šœœœ˜+K˜ Kšœ˜Kšœ˜—Kšœ˜—šœœœœ˜(šœ˜Kš œœœ œœ˜/Kšœœ œœ˜+Kšœœ˜—Kšœ˜—KšœœŸ(˜:—Kšœ˜—K˜—Kšœœ ˜=Kšœœ˜9Kšœ*˜*K˜Kšœ'˜'KšœH˜HK˜K˜—šกœ˜'Kšœœ%™JK™(Kšœœ+˜GKšœœ$˜-Kšœ!˜!šœœ˜šœ˜K˜ Kšœœœœ˜,Kšœ˜—šœŸ-˜CKšœ˜K˜Kšœ˜—šœ˜ K˜ K˜Kšœ˜——K˜K˜—šกœ˜(Kšœœ%™JK™IKšœœ+˜GKšœœ$˜-Kšœ!˜!šœœœ˜9K˜ Kšœœœœ˜,Kšœ˜—šœ˜K˜ K˜Kšœ˜—K˜K˜—šก œœ˜-KšœJกœ™]K™Ršก œœ˜!Kšœ*Ÿ˜AKšœ œœ˜:Kšœ:Ÿ˜IK˜—Kšœœœ˜1Kšœœœ6˜Qšœœ˜Kšœ=˜=Kšœ˜Kšœ˜—KšœNœœ*˜ŒKšœ$˜$K˜K˜—šกœ˜*KšœJกœ™]K™Išก œœ˜!Kšœ*Ÿ˜AKšœ œœ˜9Kšœ*Ÿ*˜TKšœ œœ˜:Kšœ:Ÿ˜IK˜—Kšœœœ˜1Kšœœœ6˜Qšœœ˜Kšœ=˜=Kšœ˜Kšœ˜—KšœHœœ*˜†Kšœ$˜$K˜K˜—šก œ˜(KšœJกœ™]K™OJšœŸ"˜)K˜K˜—šกœ˜,KšœJกœ™]K™LJšœŸ"˜)K˜K˜——™šกœœœ˜K™K˜K˜K˜K˜K˜K˜K˜K˜Kšœ:˜:Kšœ<™˜>K˜K˜—K˜—K˜K˜Kšœ˜™4K™Kšœ ฯrœ-ฅœฅ œ™z—™5K™+Kšœ ฅœ™1—K™—…—งxฝ