-- File: DJExtract.mesa -- Circuit extractor for disjoint -- Written by Martin Newell/Dan Fitzpatrick June 1981 -- Last edited: 13-Aug-81 14:42:21 DIRECTORY CIFUtilitiesDefs: FROM "CIFUtilitiesDefs" USING [ClearClipRectangle], DisjointAllocDefs: FROM "DisjointAllocDefs" USING [EnumerateSymbols], DisjointPropDefs: FROM "DisjointPropDefs" USING [GetProp, GetLongProp, AllocPropID, PutProp,PutLongProp, RemoveProp], DisjointTypes: FROM "DisjointTypes" USING [Instance, Rectangle, RectangleRecord, Symbol, Geometry,PropID], DJExtDefs: FROM "DJExtDefs", DJExtractDefs: FROM "DJExtractDefs", DJExtAllocDefs: FROM "DJExtAllocDefs" USING [MakeHEdge, MakeVEdge, FreeEdge, AllocateSegment,AllocateBox, AllocateActualParameter, MakeNodeLocation], DJExtCapDefs: FROM "DJExtCapDefs" USING [SelectCap, RenumberCap,CopyCap,AddCapPerim], DJExtCombineDefs: FROM "DJExtCombineDefs" USING [AttachNode,FindNode], DJExtDebugDefs: FROM "DJExtDebugDefs" USING [Debug], DJExtGeomDefs: FROM "DJExtGeomDefs" USING [ExtractGeometry], DJExtGraphicsDefs: FROM "DJExtGraphicsDefs" USING [DrawSymbol, DrawSegment], DJExtMergeDefs: FROM "DJExtMergeDefs" USING [InitMerge, GenNodeNumber, FinishMerge, ReserveNodeNumbers,LookSmall,GetSmall], DJExtSegDefs: FROM "DJExtSegDefs" USING [SegBucketSort, SegListSort], DJExtSortDefs: FROM "DJExtSortDefs" USING [InitSorter, SortBox], DJExtTransDefs: FROM "DJExtTransDefs" USING [SelectTransistors, RenumberTrans,CopyTrans], DJExtTypes: FROM "DJExtTypes" USING [Edge, NodeLocation, NodeNumber, Position, Segment, Box,ActualParameter, APLimit, poly, diff, metal, gate, Node], IODefs: FROM "IODefs" USING [WriteString,WriteLine], Real: FROM "Real" USING [FixI]; DJExtract: PROGRAM IMPORTS CIFUtilitiesDefs, DisjointAllocDefs, DisjointPropDefs, DJExtAllocDefs, DJExtCapDefs, DJExtCombineDefs, DJExtDebugDefs, DJExtGeomDefs, DJExtGraphicsDefs, DJExtMergeDefs, DJExtSegDefs, DJExtSortDefs, DJExtTransDefs, IODefs, Real EXPORTS DJExtDefs, DJExtractDefs = BEGIN OPEN CIFUtilitiesDefs, DisjointAllocDefs, DisjointTypes, DisjointPropDefs, DJExtAllocDefs, DJExtCapDefs, DJExtCombineDefs, DJExtDebugDefs, DJExtGeomDefs, DJExtGraphicsDefs, DJExtMergeDefs, DJExtSegDefs, DJExtSortDefs, DJExtTransDefs, DJExtTypes, IODefs, Real; Interrupt: PUBLIC SIGNAL = CODE; Extract: PUBLIC PROCEDURE[symbol: Symbol] = BEGIN ENABLE Interrupt => { WriteLine["Interrupt!"]; CONTINUE; }; Mark: PROC[s:Symbol] RETURNS[BOOLEAN] = BEGIN PutProp[@s.prop,extractID,0]; RETURN[FALSE]; END; RemoveMark: PROC[s:Symbol] RETURNS[BOOLEAN] = BEGIN RemoveProp[s.prop,extractID]; RETURN[FALSE]; END; debug _ Debug[]; [] _ EnumerateSymbols[Mark]; ExtractSymbol[symbol]; CleanUp[symbol]; [] _ EnumerateSymbols[RemoveMark]; IF NOT debug THEN WriteLine[""]; END; CleanUp: PROCEDURE[symbol: Symbol] = -- since this is the out most symbol remove external references BEGIN next:Node; intTrans:Node _ GetLongProp[symbol.prop,intID]; intCap:Node _ GetLongProp[symbol.prop,intCapID]; FOR ptr:Node _ GetLongProp[symbol.prop,extID], next UNTIL ptr = NIL DO next _ ptr.next; ptr.next _ intTrans; intTrans _ ptr; ENDLOOP; PutLongProp[@symbol.prop,intID,intTrans]; FOR ptr:Node _ GetLongProp[symbol.prop,extCapID], next UNTIL ptr = NIL DO next _ ptr.next; ptr.next _ intCap; intCap _ ptr; ENDLOOP; PutLongProp[@symbol.prop,intCapID,intCap]; END; ExtractSymbol: PROCEDURE[symbol: Symbol] = BEGIN -- for each callee of symbol see if it has been extracted, if not extract it -- only one extraction can take place at a time so we can have only one merge table & -- one element list -- symbol.geom _ Strip[symbol.geom]; FOR in: Instance _ symbol.insts,in.next UNTIL in = NIL DO IF GetProp[in.symbol.prop,extractID] # 1 THEN ExtractSymbol[in.symbol]; ENDLOOP; -- start extraction of symbol InitExtractSymbol[symbol]; -- enter each window of symbol FOR w:Rectangle _ symbol.windows,w.next UNTIL w = NIL DO EnterWindow[w]; ENDLOOP; -- for each callee of symbol sort its set of external segments into the ElementList FOR in: Instance _ symbol.insts,in.next UNTIL in = NIL DO GetInstance[in]; ENDLOOP; -- enter geometry of symbol into the ElementList FOR g:Geometry _ symbol.geom,g.next UNTIL g = NIL DO EnterGeometry[g]; ENDLOOP; -- find trans regions of diffusion segments SortSegments[]; -- extract the conectivity of the geometry ExtractGeometry[]; FinishExtractSymbol[symbol]; -- output an upper bound on the node numbers of this symbol PutLongProp[@symbol.prop,nNodesID,GetSmall[]]; -- mark symbol as checked PutProp[@symbol.prop,extractID,1]; IF NOT debug THEN WriteString["."]; END; InitExtractSymbol: PROCEDURE[symbol: Symbol] = BEGIN InitMerge[]; --WindowList _ NIL; --GeomList _ NIL; firstActual _ lastActual _ NIL; Offset _ 0; NodeLocList _ NIL; -- compute bounding box of current symbol SetBBox[@bbox,symbol.windows]; FOR w:Rectangle _ symbol.windows.next,w.next UNTIL w = NIL DO ExpandBBox[@bbox,w]; ENDLOOP; InitSorter[@bbox]; ClearClipRectangle[]; IF debug THEN DrawSymbol[symbol,FALSE,1]; END; FinishExtractSymbol: PROCEDURE[symbol: Symbol] = BEGIN enext: Edge; snext: Segment; segList: Segment _ NIL; intTrans,extTrans: Node; intCap,extCap: Node; i:INTEGER; -- put all segments on one long list FOR i IN [0..32) DO FOR e: Edge _ LeftWindowBin[i],enext UNTIL e = NIL DO enext _ e.next; FOR s: Segment _ e.segment,snext UNTIL s = NIL DO snext _ s.next; s.next _ segList; s.node _ LookSmall[s.node]; segList _ s; ENDLOOP; FreeEdge[e]; ENDLOOP; FOR e: Edge _ BotWindowBin[i],enext UNTIL e = NIL DO enext _ e.next; FOR s: Segment _ e.segment,snext UNTIL s = NIL DO snext _ s.next; s.next _ segList; s.node _ LookSmall[s.node]; segList _ s; ENDLOOP; FreeEdge[e]; ENDLOOP; FOR e: Edge _ RightWindowBin[i],enext UNTIL e = NIL DO enext _ e.next; FOR s: Segment _ e.segment,snext UNTIL s = NIL DO snext _ s.next; s.next _ segList; s.node _ LookSmall[s.node]; segList _ s; ENDLOOP; FreeEdge[e]; ENDLOOP; FOR e: Edge _ TopWindowBin[i],enext UNTIL e = NIL DO enext _ e.next; FOR s: Segment _ e.segment,snext UNTIL s = NIL DO snext _ s.next; s.next _ segList; s.node _ LookSmall[s.node]; segList _ s; ENDLOOP; FreeEdge[e]; ENDLOOP; ENDLOOP; [intTrans,extTrans] _ SelectTransistors[]; [intCap,extCap] _ SelectCap[]; RenumberTrans[extTrans]; RenumberCap[extCap]; PutLongProp[@symbol.prop,extID,extTrans]; -- hang external trans on cell PutLongProp[@symbol.prop,extCapID,extCap]; -- hang external caps on cell -- output an upper bound on the external segments of this symbol PutLongProp[@symbol.prop,nParamID,GetSmall[]]; RenumberTrans[intTrans]; RenumberCap[intCap]; PutLongProp[@symbol.prop,intID,intTrans]; -- hang internal trans on cell PutLongProp[@symbol.prop,intCapID,intCap]; -- hang internal caps on cell FOR a: ActualParameter _ firstActual,a.next UNTIL a = NIL DO FOR i: CARDINAL IN [0..a.count) DO a.node[i] _ LookSmall[a.node[i]]; ENDLOOP; ENDLOOP; -- output node numbers of parameters of this symbol PutLongProp[@symbol.prop,paramID,firstActual]; BotWindowBin _ ALL[NIL]; TopWindowBin _ ALL[NIL]; LeftWindowBin _ ALL[NIL]; RightWindowBin _ ALL[NIL]; -- renumber all the nodes so that we get a minimum number description ReNumber[]; -- attach parameter list to symbol PutLongProp[@symbol.prop,segmentID,segList]; -- attach node locations to symbol PutLongProp[@symbol.prop,nodeLocID,NodeLocList]; FinishMerge[]; END; EnterWindow: PROCEDURE[window:Rectangle] = -- each window of the current symbol is broken into its four components, left, bottom, right, & top -- each component is placed in a bucket table, for later comparison against geometry & segments -- as each component is enter it is compared against other members of the bucket to see if it is -- a continuation of any edges already entered. BEGIN h,v: Edge; n: INTEGER; -- bottom edge h _ MakeHEdge[window.b,window.l,window.r]; n _ SelectHBin[window.b]; EnterHWindowEdge[h,@BotWindowBin[n]]; -- top edge h _ MakeHEdge[window.t,window.l,window.r]; n _ SelectHBin[window.t]; EnterHWindowEdge[h,@TopWindowBin[n]]; -- left edge v _ MakeVEdge[window.l,window.b,window.t]; n _ SelectVBin[window.l]; EnterVWindowEdge[v,@LeftWindowBin[n]]; -- right edge v _ MakeVEdge[window.r,window.b,window.t]; n _ SelectVBin[window.r]; EnterVWindowEdge[v,@RightWindowBin[n]]; END; GetInstance: PROCEDURE[in:Instance] = BEGIN tmp: ActualParameter; n: LONG CARDINAL _ GetLongProp[in.symbol.prop,nParamID]; -- number of params in this symbol -- reserve n node numbers ReserveNodeNumbers[n]; FOR trans: Node _ GetLongProp[in.symbol.prop,extID],trans.next UNTIL trans = NIL DO AttachNode[trans.node+Offset,CopyTrans[trans,Offset]]; ENDLOOP; FOR cap: Node _ GetLongProp[in.symbol.prop,extCapID],cap.next UNTIL cap = NIL DO AttachNode[cap.node+Offset,CopyCap[cap,Offset]]; ENDLOOP; FOR seg: Segment _ GetLongProp[in.symbol.prop,segmentID],seg.next UNTIL seg = NIL DO EnterSegment[seg,seg.node+Offset,in.xOffset,in.yOffset]; ENDLOOP; -- make note of node numbers assigned to this instance -- this loop causes: Offset _ Offset + n; FOR i: LONG CARDINAL IN [0..n) DO IF lastActual = NIL OR lastActual.count = APLimit THEN { tmp _ AllocateActualParameter[]; IF lastActual = NIL THEN firstActual _ tmp ELSE lastActual.next _ tmp; lastActual _ tmp; lastActual.count _ 0; lastActual.next _ NIL; }; lastActual.node[lastActual.count] _ Offset _ Offset + 1; lastActual.count _ lastActual.count + 1; ENDLOOP; END; EnterSegment: PROCEDURE[segment:Segment, node:NodeNumber, x,y:REAL] = -- Each segment creates a zero width (or height) geometry cell with given node number -- It is recorded that this node number was given to this segment by adding a record to paramList -- The segment is checked against the window edges to see if is a parameter to the outside world -- if so a new (outside) segment is created with the given node number BEGIN box: Box _ AllocateBox[]; box.l _ segment.bx + x; box.b _ segment.by + y; box.r _ segment.tx + x; box.t _ segment.ty + y; box.layer _ segment.layer; box.node _ node; AddCapPerim[FindNode[node,box.layer,box.l,box.b],box.layer, 2*(box.l+box.b-box.r-box.t)]; SELECT segment.pos FROM Top => CheckTop[box]; Bottom => CheckBottom[box]; Left => CheckLeft[box]; Right => CheckRight[box]; ENDCASE; SortBox[box]; END; EnterGeometry: PROCEDURE[geom:Geometry] = -- Each piece of geometry is checked against the windows to see if it is to be a parameter -- if it is, it is given a node number and added segList -- the geometry is then added to the list of geometry to be extracted BEGIN box: Box _ AllocateBox[]; box.l _ geom.l; box.b _ geom.b; box.r _ geom.r; box.t _ geom.t; box.layer _ geom.layer; box.node _ 0; IF Active[box.layer] THEN { CheckLeft[box]; CheckRight[box]; CheckBottom[box]; CheckTop[box]; }; SortBox[box]; END; ReNumber: PROCEDURE = BEGIN FOR p:NodeLocation _ NodeLocList, p.next UNTIL p = NIL DO p.node _ LookSmall[p.node]; ENDLOOP; END; CheckLeft: PROCEDURE[box:Box] = BEGIN n: INTEGER _ SelectVBin[box.l]; FOR ptr: Edge _ LeftWindowBin[n],ptr.next UNTIL ptr = NIL DO IF box.l = ptr.bx AND ptr.by < box.t AND box.b < ptr.ty THEN { box.node _ AddSegment[@ptr.segment,box.l,box.b,box.t,box.node,Left,box.layer]; ptr.count _ ptr.count + 1; RETURN; } ENDLOOP; END; CheckRight: PROCEDURE[box:Box] = BEGIN n: INTEGER _ SelectVBin[box.r]; FOR ptr: Edge _ RightWindowBin[n],ptr.next UNTIL ptr = NIL DO IF box.r = ptr.bx AND ptr.by < box.t AND box.b < ptr.ty THEN { box.node _ AddSegment[@ptr.segment,box.r,box.b,box.t,box.node,Right,box.layer]; ptr.count _ ptr.count + 1; RETURN; } ENDLOOP; END; CheckBottom: PROCEDURE[box:Box] = BEGIN n: INTEGER _ SelectHBin[box.b]; FOR ptr: Edge _ BotWindowBin[n],ptr.next UNTIL ptr = NIL DO IF box.b = ptr.by AND ptr.bx < box.r AND box.l < ptr.tx THEN { box.node _ AddSegment[@ptr.segment,box.b,box.l,box.r,box.node,Bottom,box.layer]; ptr.count _ ptr.count + 1; RETURN; } ENDLOOP; END; CheckTop: PROCEDURE[box:Box] = BEGIN n: INTEGER _ SelectHBin[box.t]; FOR ptr: Edge _ TopWindowBin[n],ptr.next UNTIL ptr = NIL DO IF box.t = ptr.by AND ptr.bx < box.r AND box.l < ptr.tx THEN { box.node _ AddSegment[@ptr.segment,box.t,box.l,box.r,box.node,Top,box.layer]; ptr.count _ ptr.count + 1; RETURN; } ENDLOOP; END; AddSegment: PROCEDURE[list:LONG POINTER TO Segment, a,low,high:REAL, node:NodeNumber, pos:Position, layer:INTEGER] RETURNS[NodeNumber] = -- Add a segment from high to low onto the segment list of e -- if there already is a segment which overlaps this new segment combine them BEGIN seg: Segment; SELECT pos FROM Top,Bottom => { IF debug THEN DrawSegment[low,a,high,a]; seg _ AllocateSegment[]; seg.pos _ pos; seg.by _ seg.ty _ a; seg.bx _ low; seg.tx _ high; seg.layer _ layer; seg.node _ node; seg.next _ list^; list^ _ seg; -- remember diffusion isn't a conductor IF seg.layer # diff AND seg.node = 0 THEN { seg.node _ GenNodeNumber[]; RecordNodeLoc[seg.node,(seg.bx+seg.tx)/2,(seg.by+seg.ty)/2]; }; RETURN[seg.node]; }; Left,Right => { IF debug THEN DrawSegment[a,low,a,high]; seg _ AllocateSegment[]; seg.pos _ pos; seg.bx _ seg.tx _ a; seg.by _ low; seg.ty _ high; seg.layer _ layer; seg.node _ node; seg.next _ list^; list^ _ seg; -- remember diffusion isn't a conductor IF seg.layer # diff AND seg.node = 0 THEN { seg.node _ GenNodeNumber[]; RecordNodeLoc[seg.node,(seg.bx+seg.tx)/2,(seg.by+seg.ty)/2]; }; RETURN[seg.node]; }; ENDCASE; RETURN[0]; -- we never reach this statement but it keeps the compiler happy END; SortSegments: PROCEDURE = BEGIN i:INTEGER; FOR i IN [0..32) DO FOR e: Edge _ LeftWindowBin[i],e.next UNTIL e = NIL DO IF e.count > nBreak THEN e.segment _ SegBucketSort[e.segment,e.count,e.by,e.ty,Left] ELSE e.segment _ SegListSort[e.segment,Left]; ENDLOOP; FOR e: Edge _ BotWindowBin[i],e.next UNTIL e = NIL DO IF e.count > nBreak THEN e.segment _ SegBucketSort[e.segment,e.count,e.bx,e.tx,Bottom] ELSE e.segment _ SegListSort[e.segment,Bottom]; ENDLOOP; FOR e: Edge _ RightWindowBin[i],e.next UNTIL e = NIL DO IF e.count > nBreak THEN e.segment _ SegBucketSort[e.segment,e.count,e.by,e.ty,Right] ELSE e.segment _ SegListSort[e.segment,Right]; ENDLOOP; FOR e: Edge _ TopWindowBin[i],e.next UNTIL e = NIL DO IF e.count > nBreak THEN e.segment _ SegBucketSort[e.segment,e.count,e.bx,e.tx,Top] ELSE e.segment _ SegListSort[e.segment,Top]; ENDLOOP; ENDLOOP; END; EnterHWindowEdge: PROCEDURE [e:Edge, list: LONG POINTER TO Edge] = BEGIN next: Edge; tmp: Edge _ NIL; FOR ptr: Edge _ list^,next UNTIL ptr = NIL DO next _ ptr.next; IF e.by = ptr.by AND ptr.bx <= e.tx AND e.bx <= ptr.bx THEN { e.bx _ MIN[e.bx,ptr.bx]; e.tx _ MAX[e.tx,ptr.tx]; FreeEdge[ptr]; LOOP; }; ptr.next _ tmp; tmp _ ptr; ENDLOOP; e.next _ tmp; list^ _ e; END; EnterVWindowEdge: PROCEDURE [e:Edge, list: LONG POINTER TO Edge] = BEGIN next: Edge; tmp: Edge _ NIL; FOR ptr: Edge _ list^,next UNTIL ptr = NIL DO next _ ptr.next; IF e.bx = ptr.bx AND ptr.by <= e.ty AND e.by <= ptr.by THEN { e.by _ MIN[e.by,ptr.by]; e.ty _ MAX[e.ty,ptr.ty]; FreeEdge[ptr]; LOOP; }; ptr.next _ tmp; tmp _ ptr; ENDLOOP; e.next _ tmp; list^ _ e; END; SelectHBin: PROCEDURE [y: REAL] RETURNS [n: INTEGER] = BEGIN n _ FixI[32*(y - bbox.b)/(bbox.t - bbox.b + 1)]; END; SelectVBin: PROCEDURE [x: REAL] RETURNS [n: INTEGER] = BEGIN n _ FixI[32*(x - bbox.l)/(bbox.r - bbox.l + 1)]; END; SetBBox: PROCEDURE [bb1,bb2: Rectangle] = BEGIN bb1.l _ bb2.l; bb1.b _ bb2.b; bb1.r _ bb2.r; bb1.t _ bb2.t; END; ExpandBBox: PROCEDURE [bb1,bb2: Rectangle] = BEGIN IF bb2.l < bb1.l THEN bb1.l _ bb2.l; IF bb2.b < bb1.b THEN bb1.b _ bb2.b; IF bb1.r < bb2.r THEN bb1.r _ bb2.r; IF bb1.t < bb2.t THEN bb1.t _ bb2.t; END; GetSegmentList: PUBLIC PROCEDURE[symbol: Symbol] RETURNS[segList: Segment] = BEGIN segList _ GetLongProp[symbol.prop,segmentID]; END; Strip: PUBLIC PROCEDURE[in: Geometry] RETURNS[out: Geometry] = BEGIN geom: Geometry; out _ NIL; UNTIL in = NIL DO geom _ in; in _ in.next; IF geom.layer = 2 THEN { geom.next _ out; out _ geom; }; ENDLOOP; END; RecordNodeLoc: PUBLIC PROCEDURE[node: NodeNumber, x,y:REAL] = BEGIN tmp: NodeLocation _ MakeNodeLocation[node,x,y]; tmp.next _ NodeLocList; NodeLocList _ tmp; END; GetNodeLocID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[nodeLocID]; END; GetNodesID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[nNodesID]; END; GetSegmentID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[segmentID]; END; GetParamID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[paramID]; END; GetNParamID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[nParamID]; END; GetIntTransID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[intID]; END; GetExtTransID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[extID]; END; GetIntCapID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[intCapID]; END; GetExtCapID: PUBLIC PROCEDURE RETURNS[PropID] = BEGIN RETURN[extCapID]; END; debug:BOOLEAN _ TRUE; -- debugging flag bbox: RectangleRecord; -- bounding box of current symbol firstActual: ActualParameter; lastActual: ActualParameter; Offset: LONG CARDINAL; NodeLocList: NodeLocation; -- loaction of nodes in this symbol BotWindowBin: ARRAY [0..32) OF Edge _ ALL[NIL]; TopWindowBin: ARRAY [0..32) OF Edge _ ALL[NIL]; LeftWindowBin: ARRAY [0..32) OF Edge _ ALL[NIL]; RightWindowBin: ARRAY [0..32) OF Edge _ ALL[NIL]; extractID: PropID _ AllocPropID[]; -- tag which marks whether the sub-circuit has been extracted segmentID: PropID _ AllocPropID[]; -- tag to get the segments of the sub-circuit paramID: PropID _ AllocPropID[]; -- tag to get list of parameter numbers for this symbol nParamID: PropID _ AllocPropID[]; -- tag to get number of segments nNodesID: PropID _ AllocPropID[]; -- tag to get number of nodes in sub-circuit nodeLocID: PropID _ AllocPropID[]; -- tag to get node locations for sub-circuit extID: PropID _ AllocPropID[]; -- tag to get external transistors intID: PropID _ AllocPropID[]; -- tag to get internal transistors extCapID: PropID _ AllocPropID[]; -- tag to get external caps intCapID: PropID _ AllocPropID[]; -- tag to get internal caps nBreak: CARDINAL = 8; MaxLayer: CARDINAL = 16; Active: ARRAY [0..MaxLayer) OF BOOLEAN _ ALL[FALSE]; Active[poly] _ Active[diff] _ Active[metal] _ Active[gate] _ TRUE; END. (672)\153b10B1537b9B547b7B48b4B102b10B267b7B33i60I498b13B40i19I6i48I5i83I4i16I39i1I147i20I35i1I3i21I93i1I4i19I6i44I12i1I92i33I102i40I22i39I55i56I53i5I6i11I81b17B135i1I20i43I128i1I93b19B157i33I1242i27I48i26I5i61I143i27I48i26I165i48I160i66I18i25I57i25I80b11B35i96I4i92I4i141I36i11I115i8I115i9I116i10I120b11B118i31I5i22I482i51I5i16I428b12B61i82I1i88I13i93I4i68I436b13B32i87I1i49I11i66I272b8B127b9B335b10B337b11B336b8B333b10B137i19I4i4I3i25I6i74I293i36I431i36I205i61I8b12B903b16B383b16B383b10B108b10B108b7B112b10B200b14B123b5B227b13B156b12B70b10B69b12B70b10B68b11B69b13B66b13B66b11B69b11B96i14I30i30I115i32I236i58I39i42I37i52I38i29I38i41I39i41I36i31I36i31I38i24I38i24I