<> <> <> <> <<>> DIRECTORY CD, CDBasics, CDCells, CDOrient, CDPinObjects, CDProperties, CDRects, CDSymbolicObjects, Convert, PW, PWPins, PWRoute, Rope, Route, RouteUtil, RTBasic, SC, SCChanUtil, SCInstUtil, SCPrivate, SCRowUtil, SCUtil; SCRouteImpl: CEDAR PROGRAM IMPORTS CD, CDBasics, CDCells, CDPinObjects, CDProperties, CDRects, CDSymbolicObjects, Convert, PW, PWPins, PWRoute, Rope, RouteUtil, RTBasic, SCChanUtil, SCInstUtil, SCRowUtil, SCUtil EXPORTS SCPrivate SHARES SC = BEGIN RowCellArray: TYPE = ARRAY [1..SCPrivate.maxLgRows] OF PW.Object; RowCells: TYPE = REF RowCellArray; NumSeqRec: TYPE = RECORD [ c: SEQUENCE size: NAT OF REF SC.Number]; NumSeq: TYPE = REF NumSeqRec; EachPinProc: TYPE = PROC [instance: SCPrivate.Instance, netPin: SCPrivate.PinNet, side: SC.Side]; LRSide: TYPE = SCPrivate.LRSide; TBSide: TYPE = SCPrivate.TBSide; LayoutData: TYPE = SCPrivate.LayoutData; ROPE: TYPE = Rope.ROPE; PQPos: TYPE = RTBasic.PQPos; SideObjs: TYPE = REF SideObjsRec; SideObjsRec: TYPE = ARRAY LRSide OF PW.ListOb; IRSize: PROC [obj: PW.Object] RETURNS [size: CD.Position] = {size _ CDBasics.SizeOfRect[CD.InterestRect[obj]]}; CreatePinsForChannel: PROC [handle: SC.Handle, chan: NAT, rowCells: RowCells] = BEGIN side: SC.Side; row: NAT; externOnly: BOOLEAN; layoutData: SCPrivate.LayoutData _ NARROW[handle.layoutData]; lgRows: SCPrivate.LgRows _ layoutData.lgRows; MakeNetPin: SCInstUtil.EachPinProc = { <<[instance: SCPrivate.Instance, pin: NAT, netPin: SCPrivate.PinNet]>> <<>> IF netPin.net #NIL AND (~externOnly OR (externOnly AND netPin.net.externNet = externalNet)) THEN { IF side = SCInstUtil.PosOf[instance, netPin.pin].sideOn THEN { rect: CD.Rect _ SCInstUtil.RotateRect[instance, netPin.pin.rect]; pinOb: CD.Object _ CDPinObjects.CreatePinOb[CDBasics.SizeOfRect[rect]]; position: CD.Position _ SCUtil.PQToXY[handle, [p: instance.offset + rect.x1, q: rect.y1]]; pinInst: CD.Instance; IF ~externOnly AND netPin.net.pinsOnChan <= 1 THEN RETURN; pinInst _ PW.IncludeInCell[cell: rowCells[row], obj: pinOb, position: position]; CDPinObjects.SetName[pinInst, netPin.net.name]; CDPinObjects.SetLayer[pinInst, netPin.pin.layer]}}; }; ResetInstCount: EachPinProc = { IF netPin.net # NIL AND side = SCInstUtil.PosOf[instance, netPin.pin].sideOn THEN netPin.net.pinsOnChan _ 0; }; CountInsts: EachPinProc = { IF netPin.net # NIL AND side = SCInstUtil.PosOf[instance, netPin.pin].sideOn THEN netPin.net.pinsOnChan _ netPin.net.pinsOnChan + 1; }; ForEachExtInstance: SCRowUtil.EachInstProc = { externOnly _ TRUE; [] _ SCInstUtil.EnumeratePinsOnInst[instance, MakeNetPin]}; ForEachIntInstance: SCRowUtil.EachInstProc = { externOnly _ FALSE; [] _ SCInstUtil.EnumeratePinsOnInst[instance, MakeNetPin]}; IF chan = 1 THEN { side _ bottom; row _ 1; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachExtInstance]} ELSE IF chan = lgRows.count + 1 THEN { side _ top; row _ lgRows.count; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachExtInstance]} ELSE { EnumeratePinsOnChan[handle, chan, CountInsts]; side _ top; row _ chan - 1; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachIntInstance]; side _ bottom; row _ chan; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachIntInstance]; EnumeratePinsOnChan[handle, chan, ResetInstCount]; }; END; EnumeratePinsOnChan: PROC [handle: SC.Handle, chan: NAT, action: EachPinProc] = { side: SC.Side; row: NAT; ForEachPin: SCInstUtil.EachPinProc = { action[instance, netPin, side]}; ForEachIntInstance: SCRowUtil.EachInstProc = { [] _ SCInstUtil.EnumeratePinsOnInst[instance, ForEachPin]}; side _ top; row _ chan - 1; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachIntInstance]; side _ bottom; row _ chan; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachIntInstance]; }; CreateExitsForChannel: PROC [handle: SC.Handle, rowChan: SCPrivate.RowChan, exitCells: SideObjs] = BEGIN cell: CD.Object; trunkWidth: SC.Number _ handle.rules.rowRules.trunkWidth; CreateNewExit: SCChanUtil.EachExitProc = { pinOb: CD.Object _ CDPinObjects.CreatePinOb[size: [trunkWidth, trunkWidth]]; pinInst: CD.Instance _ PW.IncludeInCell[cell: cell, obj: pinOb]; CDPinObjects.SetName[pinInst, exit.net.name]; CDPinObjects.SetLayer[pinInst, exit.layer]; exit.net.pinsOnChan _ exit.net.pinsOnChan + 1; }; FOR side: LRSide IN LRSide DO rect: CD.Rect; cell _ PW.CreateEmptyCell[]; [] _ SCChanUtil.EnumerateExits[handle, rowChan, side, CreateNewExit]; SELECT side FROM -- pw needs a better way of finding sides left => rect _ [-trunkWidth, -trunkWidth, trunkWidth, 2*trunkWidth]; right => rect _ [0, -trunkWidth, 2*trunkWidth, 2*trunkWidth]; ENDCASE; CDCells.SetInterestRect[cell, rect]; -- set interestRect of cell PW.RepositionCell[cell]; exitCells[side] _ CONS[cell, exitCells[side]]; ENDLOOP; END; CreateWireAndContact: PROC [handle: SC.Handle, pin: PW.Instance, size: Route.Position, length: CD.Number, trunkLayer: CD.Layer] RETURNS [obj: CD.Object] = { branchLayer: CD.Layer _ CDPinObjects.GetLayer[pin]; obj _ PW.CreateEmptyCell[]; [] _ PW.IncludeInCell[obj, CreateWireFromPin[handle, pin, length]]; RouteUtil.AddVia[obj, CDPinObjects.GetName[pin], [length-size.x/2, size.y/2], size, trunkLayer, branchLayer]; PW.RepositionCell[obj]; }; CreateWireFromPin: PROC [handle: SC.Handle, pin: PW.Instance, length: CD.Number] RETURNS [wire: CD.Object] = BEGIN wx: CD.Number _ length; wy: CD.Number _ pin.ob.size.y; wSize: CD.Position _ SCUtil.PQToXY[handle, [wx, wy]]; layer: CD.Layer _ CDPinObjects.GetLayer[pin]; wire _ CDRects.CreateRect[wSize, layer]; END; AlwaysExtendPin: PROC [handle: SC.Handle, inst: PW.Instance, extLen: CD.Number] RETURNS [obj: CD.Object _ NIL] = BEGIN <> IF CDSymbolicObjects.IsPin[inst.ob] THEN { pinInst: CD.Instance; pinSize: CD.Position _ SCUtil.PQToXY[handle, [inst.ob.size.x, inst.ob.size.y]]; wire: CD.Object _ CreateWireFromPin[handle, inst, extLen]; pinOb: CD.Object _ CDPinObjects.CreatePinOb[pinSize]; obj _ PW.CreateEmptyCell[]; pinInst _ PW.IncludeInCell[obj, pinOb, [extLen-inst.ob.size.x, 0]]; [] _ PW.IncludeInCell[obj, wire]; CDPinObjects.SetName[pinInst, CDPinObjects.GetName[inst]]; CDPinObjects.SetLayer[pinInst, CDPinObjects.GetLayer[inst]]; PW.RepositionCell[obj]}; END; ExtendPowerBuses: PROC [handle: SC.Handle, rowList: PW.ListOb] RETURNS [rows: PW.ListOb _ NIL] = BEGIN <> extLen: CD.Number; side: LRSide; rl: PW.ListOb _ NIL; abutOb: PW.Object; pwSide: PWPins.Side; rowNum: NAT _ 0; layout: LayoutData _ NARROW[handle.layoutData]; maxRowLength: CD.Number _ SCRowUtil.FindMaxRow[handle].maxRowWidth; ExtendPin: PW.ForEachPinProc = { IF Rope.Equal[CDPinObjects.GetName[inst], layout.powerBuses[side].name] THEN obj _ AlwaysExtendPin[handle, inst, extLen]}; FOR rl _ rowList, rl.rest UNTIL rl = NIL DO rowLength: CD.Number _ IRSize[rl.first].x; rowNum _ rowNum + 1; IF rowLength < maxRowLength THEN { ext: ARRAY LRSide OF PW.Object _ ALL[NIL]; FOR side IN LRSide DO extLen _ SELECT side FROM left => (maxRowLength - rowLength)/2, right => maxRowLength - rowLength - (maxRowLength - rowLength)/2, ENDCASE => ERROR; pwSide _ SELECT side FROM right => right, left => left, ENDCASE => ERROR; ext[side] _ TransferCell[template: rl.first, objSide: pwSide, width: extLen, objProc: ExtendPin, stopEnumerateDeepPins: FALSE]; ENDLOOP; abutOb _ PW.AbutListX[LIST[ext[left], rl.first, ext[right]]]} ELSE abutOb _ rl.first; rows _ CONS[IncludeCell[abutOb, Rope.Cat["Row",Convert.RopeFromInt[rowNum]], FALSE], rows]; ENDLOOP; END; CreateRowCell: PROC [handle: SC.Handle, row: NAT, rowList: PW.ListOb, rowCells: RowCells] RETURNS [PW.ListOb] = BEGIN rowOb: PW.Object _ NIL; listOb: PW.ListOb _ NIL; rCell: CD.Object _ NIL; -- cell obtained from rowOb ForEachInstance: SCRowUtil.EachInstProc = { thisObj: CD.Object _ instance.object.cdOb; listOb _ CONS[thisObj, listOb]}; [] _ SCRowUtil.EnumerateAllInstsOnRow[handle, row, ForEachInstance]; listOb _ PW.Reverse[listOb]; rowOb _ PW.AbutListX[listOb]; rCell _ IncludeCell[rowOb, Rope.Cat["Row", Convert.RopeFromInt[row]], TRUE]; rowList _ CONS[rCell, rowList]; rowCells[row] _ rCell; RETURN[rowList]; END; LogicRoute: PROC [handle: SC.Handle] RETURNS [logicOb: PW.Object] = BEGIN exitCells: SideObjs _ NEW[SideObjsRec _ ALL[NIL]]; rowList: PW.ListOb _ NIL; rowCells: RowCells _ NEW[RowCellArray]; layoutData: SCPrivate.LayoutData _ NARROW[handle.layoutData]; rowChans: SCPrivate.RowChans _ layoutData.rowChans; parms: SCPrivate.Parms _ NARROW[handle.parms]; routerParams: PWRoute.RouterParams _ NEW[ PWRoute.RouterParamsRec _ [handle.rules.horizLayer, handle.rules.vertLayer, parms.cdDesign.technology.key]]; abutOb: PW.Object; ForEachRow: SCRowUtil.EachRowProc = { rowList _ CreateRowCell[handle, row, rowList, rowCells]; }; ForEachChannel: SCChanUtil.EachRowChanProc = { IF 1 < rowChan.chanNum AND rowChan.chanNum < rowChans.count THEN CreateExitsForChannel[handle: handle, rowChan: rowChan, exitCells: exitCells]; CreatePinsForChannel[handle: handle, chan: chan, rowCells: rowCells]; }; SCInstUtil.AllOffsets[handle]; [] _ SCRowUtil.EnumerateRows[handle: handle, eachRow: ForEachRow]; [] _ SCChanUtil.EnumerateRowChans[handle: handle, eachRowChan: ForEachChannel]; rowList _ ExtendPowerBuses[handle, rowList]; exitCells[left] _ PW.Reverse[exitCells[left]]; exitCells[right] _ PW.Reverse[exitCells[right]]; abutOb _ PWRoute.AbutChRouteListY[ listOb: rowList, leftListOb: exitCells[left], rightListOb: exitCells[right], params: routerParams -- ! Route.Signal => RESUME -- ]; logicOb _ IncludeCell[abutOb, Rope.Cat[handle.name, "Logic"], FALSE]; END; IncludeTrunkPin: PROC [handle: SC.Handle, cell: CD.Object, name: ROPE, tbSide: TBSide, trunkSize, trunkPos: PQPos, layer: CD.Layer] = BEGIN pin: CD.Object _ CDPinObjects.CreatePinOb[[trunkSize.p, trunkSize.p]]; pos: CD.Position; pinInst: CD.Instance; trunkPos.q _ trunkPos.q + (IF tbSide = top THEN trunkSize.q - trunkSize.p ELSE 0); pos _ SCUtil.PQToXY[handle, trunkPos]; pinInst _ PW.IncludeInCell[cell, pin, pos]; CDPinObjects.SetName[pinInst, name]; CDPinObjects.SetLayer[pinInst, layer]; END; PowerRoute: PROC [handle: SC.Handle, pwOb: PW.Object] RETURNS [ob: PW.Object] = BEGIN <> <<>> ext: ARRAY LRSide OF PW.Object _ ALL[NIL]; trunkLayer: CD.Layer _ handle.rules.sideRules.trunkLayer; pwSide: PWPins.Side; pBus: SCPrivate.PowerBus; side: LRSide; trunk, abutOb: CD.Object; trunkSize, trunkPos: PQPos; extLen, width, trunkWidth: SC.Number; layout: LayoutData _ NARROW[handle.layoutData]; ExtendPin: PW.ForEachPinProc = { name: ROPE _ CDPinObjects.GetName[inst]; otherPBus: SCPrivate.PowerBus = layout.powerBuses[RTBasic.OtherSide[side]]; SELECT TRUE FROM Rope.Equal[name, pBus.name] => obj _ CreateWireAndContact[handle, inst, [trunkWidth, IRSize[inst.ob].y], width, trunkLayer]; Rope.Equal[name, otherPBus.name] => NULL; ENDCASE => obj _ AlwaysExtendPin[handle, inst, width]}; FOR side IN LRSide DO pBus _ layout.powerBuses[side]; width _ pBus.width; trunkWidth _ pBus.net.trunkWidth; extLen _ width - trunkWidth; trunkSize _ [p: trunkWidth, q: IRSize[pwOb].y]; trunkPos _ [IF side = right THEN extLen ELSE 0, 0]; pwSide _ SELECT side FROM right => right, left => left, ENDCASE => ERROR; ext[side] _ TransferCell[template: pwOb, objSide: pwSide, width: width, objProc: ExtendPin, name: IF side = right THEN "RPower" ELSE "LPower", stopEnumerateDeepPins: FALSE]; trunk _ MakeTrunk[handle: handle, trunkSize: trunkSize, lev: trunkLayer]; [] _ PW.IncludeInCell[ext[side], trunk, SCUtil.PQToXY[handle, trunkPos]]; FOR tbSide: TBSide IN TBSide DO IncludeTrunkPin[handle, ext[side], pBus.name, tbSide, trunkSize, trunkPos , trunkLayer]; ENDLOOP; ENDLOOP; abutOb _ PW.AbutListX[LIST [ext[left], pwOb, ext[right]]]; ob _ IncludeCell[abutOb, Rope.Cat[handle.name, "WPower"], FALSE]; END; MakeTrunk: PROC [handle: SC.Handle, trunkSize: PQPos, lev: CD.Layer] RETURNS [obj: CD.Object] = BEGIN obj _ CDRects.CreateRect[SCUtil.PQToXY[handle, trunkSize], lev]; END; AcBusForName: PROC [handle: SC.Handle, side: LRSide, name: ROPE] RETURNS [acBus: SCPrivate.AcBus _ NIL, i: NAT] = BEGIN layout: LayoutData _ NARROW[handle.layoutData]; sigs: SCPrivate.AcBusSigs _ layout.acBuses[side].sigs; FOR i IN [0..sigs.size) DO IF Rope.Equal[name, sigs[i].name] THEN RETURN [sigs[i], i]; ENDLOOP; END; ComputeTrunkEnd: PROC [trunkEnd: NumSeq, q: SC.Number, index: NAT, fromTB: TBSide] = BEGIN prev: REF SC.Number _ trunkEnd[index]; trunkEnd[index] _ NEW[SC.Number]; trunkEnd[index]^ _ IF prev = NIL THEN q ELSE SELECT fromTB FROM top => MIN[q, prev^], bottom => MAX[q, prev^], ENDCASE => ERROR; END; CreateAcTrunks: PROC [handle: SC.Handle, cell: CD.Object, acBusSide: SCPrivate.AcBusSides, trunkEnd: NumSeq, layer: CD.Layer] = BEGIN side: LRSide; trunk: CD.Object; trunkSize, trunkPos: PQPos; bus: SCPrivate.AcBus; FOR i: NAT IN [0..acBusSide.sigs.size) DO bus _ acBusSide.sigs[i]; side _ bus.onSide; trunkPos.p _ SELECT side FROM left => IRSize[cell].x - bus.depth, right => bus.depth, ENDCASE => ERROR; trunkPos.q _ SELECT bus.fromTB FROM bottom => 0, top => trunkEnd[i]^, ENDCASE => ERROR; trunkSize.p _ bus.net.trunkWidth; trunkSize.q _ SELECT bus.fromTB FROM bottom => trunkEnd[i]^, top => IRSize[cell].y - trunkEnd[i]^, ENDCASE => ERROR; trunk _ MakeTrunk[handle: handle, trunkSize: trunkSize, lev: layer]; [] _ PW.IncludeInCell[cell, trunk, SCUtil.PQToXY[handle, trunkPos]]; IncludeTrunkPin[handle, cell, bus.name, bus.fromTB, trunkSize, trunkPos, layer]; ENDLOOP; END; ClockRoute: PROC [handle: SC.Handle, pwOb: PW.Object] RETURNS [ob: PW.Object] = BEGIN trunkEnd: NumSeq; -- "q" coordinate of trunk end point ext: ARRAY LRSide OF PW.Object _ ALL[NIL]; trunkLayer: CD.Layer _ handle.rules.sideRules.trunkLayer; side: LRSide; abutOb: CD.Object; width: SC.Number; layout: LayoutData _ NARROW[handle.layoutData]; ExtendPin: PW.ForEachPinProc = { <<[inst: Instance] RETURNS [obj: Object];>> <<>> pinName: ROPE _ CDPinObjects.GetName[inst]; acBus: SCPrivate.AcBus; sigIndex: NAT; [acBus, sigIndex] _ AcBusForName[handle, side, pinName]; IF acBus = NIL THEN obj _ AlwaysExtendPin[handle, inst, width] ELSE { ComputeTrunkEnd[trunkEnd: trunkEnd, q: inst.location.y, index: sigIndex, fromTB: acBus.fromTB]; obj _ CreateWireAndContact[handle, inst, [acBus.net.trunkWidth, acBus.net.branchWidth], acBus.depth, trunkLayer]}}; FOR side IN LRSide DO pwSide: PWPins.Side; acBusSide: SCPrivate.AcBusSides _ layout.acBuses[side]; IF acBusSide = NIL OR acBusSide.sigs = NIL THEN LOOP; trunkEnd _ NEW[NumSeqRec[acBusSide.sigs.size]]; FOR i: NAT IN [0..trunkEnd.size) DO trunkEnd[i] _ NIL; ENDLOOP; pwSide _ SELECT side FROM right => right, left => left, ENDCASE => ERROR; width _ acBusSide.width; ext[side] _ TransferCell[template: pwOb, objSide: pwSide, width: width, objProc: ExtendPin, stopEnumerateDeepPins: FALSE]; CreateAcTrunks[handle, ext[side], acBusSide, trunkEnd, trunkLayer]; ENDLOOP; abutOb _ PW.AbutListX[LIST [ext[left], pwOb, ext[right]]]; ob _ IncludeCell[abutOb, Rope.Cat[handle.name, "WClocks"], FALSE]; END; IORoute: PROC [handle: SC.Handle, pwOb: PW.Object] RETURNS [ob: PW.Object] = BEGIN layout: LayoutData _ NARROW[handle.layoutData]; bpRows: SCPrivate.BpRows _ layout.bpRows; FOR side: SC.Side IN SC.Side DO IF bpRows[side].fnlBpFxd THEN { <> <> < position in bp bottom => origin lower left hand side top => lower right hand side origin>> < upper left side>> }; ENDLOOP; <> <> END; DetailRoute: PUBLIC PROC [handle: SC.Handle] RETURNS [result: SC.Result] = BEGIN pwOb: PW.Object _ NIL; pwOb _ LogicRoute[handle]; pwOb _ PowerRoute[handle, pwOb]; pwOb _ ClockRoute[handle, pwOb]; result _ NEW[SC.ResultRec _ []]; result.object _ pwOb; result.handle _ handle; <> END; IncludeCell: PROC [rowOb: PW.Object, name: Rope.ROPE _ NIL, StopEnumerateDeepPins: BOOLEAN _ TRUE] RETURNS [rCell: CD.Object] = BEGIN rCellPtr: CD.CellPtr; inst: CD.Instance; rCell _ PW.CreateEmptyCell[]; rCellPtr _ NARROW [rCell.specificRef]; inst _ NEW [CD.InstanceRep _ [ob: rowOb]]; IF StopEnumerateDeepPins THEN CDProperties.PutPropOnInstance[inst, $StopEnumerateDeepPins, $StopEnumerateDeepPins]; rCellPtr.contents _ CONS [inst, rCellPtr.contents]; CDCells.SetInterestRect[rCell, CD.InterestRect[rowOb]]; [] _ PW.RepositionCell[rCell]; END; TransferCell: PROC [template: PW.Object, objSide: PWPins.Side, width: INT, objProc: PW.ForEachPinProc, name: Rope.ROPE _ "Transfer", stopEnumerateDeepPins: BOOLEAN _ TRUE] RETURNS [cell: PW.Object] = BEGIN KeepPinOnEdge: PWPins.InstanceEnumerator = BEGIN newObj: PW.Object; side: PWPins.Side _ PWPins.GetSide[template, inst].side; IF side=objSide THEN { newObj _ objProc[inst]; IF newObj=NIL THEN RETURN; [] _ PW.IncludeInCell[cell, newObj, Position[inst, template, objSide], SideToOrient[objSide]]; }; END; iRect: CD.Rect; <<-- Start with an empty cell of appropriate interestRect (origin in 0,0)>> IF objSide=none THEN ERROR; cell _ PW.CreateEmptyCell[]; iRect _ CD.InterestRect[template]; -- copy interestRect of obj CDCells.SetInterestRect[cell, IRect[iRect, width, objSide]]; -- set interestRect of cell <<-- Parse the pins >> [] _ PWPins.EnumerateEdgePins[template, KeepPinOnEdge, stopEnumerateDeepPins]; PW.RepositionCell[cell]; END; IRect: PROC [templateRect: CD.Rect, otherDim: INT, side: PWPins.Side] RETURNS [iRect: CD.Rect] = BEGIN iRect _ SELECT side FROM top, bottom => [0, 0, templateRect.x2-templateRect.x1, otherDim], left, right => [0, 0, otherDim, templateRect.y2-templateRect.y1], ENDCASE => ERROR; END; SideToOrient: PROC [side: PWPins.Side] RETURNS [orient: CD.Orientation] ~ { orient _ SELECT side FROM bottom => CDOrient.rotate270, right => CDOrient.original, top => CDOrient.rotate90, left => CDOrient.rotate180, ENDCASE => ERROR; }; Position: PROC [inst: CD.Instance, template: PW.Object, side: PWPins.Side] RETURNS [position: CD.Position] = BEGIN position _ PW.GetLocation[inst, template]; SELECT side FROM top, bottom => position.y _ 0; left, right => position.x _ 0; ENDCASE => ERROR; END; END.