SCRouteImpl.mesa: Implementation of SC.DetailedRoute
Copyright © 1986 by Xerox Corporation. All rights reserved.
Frank Bowers June 3, 1986 10:58:48 am PDT
Bryan Preas August 13, 1986 5:21:56 pm PDT
DIRECTORY
CD,
CDBasics,
CDCells,
CDOrient,
CDProperties,
CDRects,
CDSymbolicObjects,
Convert,
PW,
PWPins,
PWRoute,
Rope,
Route,
RouteUtil,
RTBasic,
SC,
SCChanUtil,
SCInstUtil,
SCNetUtil,
SCPrivate,
SCRoutePinsUtil,
SCRowUtil,
SCUtil;
SCRouteImpl: CEDAR PROGRAM
IMPORTS CD, CDBasics, CDCells, CDProperties, CDRects, CDSymbolicObjects, Convert, PW, PWPins, PWRoute, Rope, RouteUtil, RTBasic, SCChanUtil, SCInstUtil, SCNetUtil, SCRoutePinsUtil, SCRowUtil, SCUtil
EXPORTS SCPrivate
SHARES SC = BEGIN
CreatePinsForChannel: PROC [handle: SC.Handle, rowChan: SCPrivate.RowChan] = {
AllPins: SCInstUtil.EachPinProc = {
[instance: SCPrivate.Instance, pin: NAT, netPin: SCPrivate.PinNet]
IF netPin.net #NIL THEN {
IF side = SCInstUtil.PosOf[instance, netPin.pin].sideOn THEN {
rowOffset: SC.Number ← lgRow.rowOrg.p - lgRows.horzRowOrg;
rect: CD.Rect ← SCInstUtil.RotateRect[instance, netPin.pin.rect];
position: CD.Position ← SCUtil.PQToXY[handle, [p: rowOffset + instance.offset + rect.x1, q: rect.y1]];
SCRoutePinsUtil.EnterPin[rect, position, netPin, RTBasic.OtherSide[side], lgRow.cdOb]}}};
ExternalPins: SCInstUtil.EachPinProc = {
[instance: SCPrivate.Instance, pin: NAT, netPin: SCPrivate.PinNet]
IF netPin.net #NIL AND netPin.net.externNet = externalNet THEN {
IF side = SCInstUtil.PosOf[instance, netPin.pin].sideOn AND SCNetUtil.ExitOnSide[handle, netPin.net, side] THEN {
rowOffset: SC.Number ← lgRow.rowOrg.p - lgRows.horzRowOrg;
rect: CD.Rect ← SCInstUtil.RotateRect[instance, netPin.pin.rect];
position: CD.Position ← SCUtil.PQToXY[handle, [p: rowOffset + instance.offset + rect.x1, q: rect.y1]];
SCRoutePinsUtil.EnterPin[rect, position, netPin, RTBasic.OtherSide[side], lgRow.cdOb]}}};
InternalPinsInstance: SCRowUtil.EachInstProc = {
[] ← SCInstUtil.EnumeratePinsOnInst[instance, AllPins]};
ExternalPinsInstance: SCRowUtil.EachInstProc = {
[] ← SCInstUtil.EnumeratePinsOnInst[instance, ExternalPins]};
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
lgRows: SCPrivate.LgRows ← layoutData.lgRows;
chan: SCPrivate.MaxChanSr ← rowChan.chanNum;
side: SC.Side;
lgRow: SCPrivate.LgRow;
IF chan = 1 THEN {
side ← bottom; lgRow ← lgRows.rows[1];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ExternalPinsInstance]}
ELSE IF chan = lgRows.count + 1 THEN {
side ← top; lgRow ← lgRows.rows[lgRows.count];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ExternalPinsInstance]}
ELSE {
side ← top; lgRow ← lgRows.rows[chan - 1];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, InternalPinsInstance];
side ← bottom; lgRow ← lgRows.rows[chan];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, InternalPinsInstance]}};
CreateExitsForChannel: PROC [handle: SC.Handle, rowChan: SCPrivate.RowChan] = {
cell: CD.Object;
ExitPins: SCChanUtil.EachExitProc = {SCRoutePinsUtil.EnterExit[exit, lrSide, cell]};
EachSide: SCRowUtil.EachSideProc ~ {
[side: SC.Side, bpRow: SCPrivate.BpRow] RETURNS [quit: BOOL ← FALSE]
IF side = left OR side = right THEN {
cell ← CDCells.CreateEmptyCell[];
[] ← SCChanUtil.EnumerateExits[handle, rowChan, side, ExitPins];
rowChan.exitCells[side] ← cell}};
[] ← SCRowUtil.EnumerateSides[handle, EachSide]};
FinishExitsForChannel: PROC [handle: SC.Handle, rowChan: SCPrivate.RowChan] = {
EachSide: SCRowUtil.EachSideProc ~ {
[side: SC.Side, bpRow: SCPrivate.BpRow] RETURNS [quit: BOOL ← FALSE]
IF side = left OR side = right THEN {
rect: CD.Rect;
cell: CD.Object ← rowChan.exitCells[side];
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
RTBasic.RepositionCell[cell]}};
trunkWidth: SC.Number ← handle.rules.rowRules.trunkWidth;
[] ← SCRowUtil.EnumerateSides[handle, EachSide]};
CreateWireAndContact: PROC [handle: SC.Handle, pin: CD.Instance, size: Route.Position, length: CD.Number, trunkLayer: CD.Layer, cdLambda: Route.Number]
RETURNS [obj: CD.Object] = {
branchLayer: CD.Layer ← CDSymbolicObjects.GetLayer[pin];
obj ← CDCells.CreateEmptyCell[];
[] ← PW.IncludeInCell[obj, CreateWireFromPin[handle, pin, length]];
RouteUtil.AddVia[obj, CDSymbolicObjects.GetName[pin], [length-size.x/2, size.y/2], size, trunkLayer, branchLayer, cdLambda];
RTBasic.RepositionCell[obj]};
CreateWireFromPin: PROC [handle: SC.Handle, pin: CD.Instance, length: CD.Number]
RETURNS [wire: CD.Object] = {
wx: CD.Number ← length;
wy: CD.Number ← pin.ob.size.y;
wSize: CD.Position ← SCUtil.PQToXY[handle, [wx, wy]];
layer: CD.Layer ← CDSymbolicObjects.GetLayer[pin];
wire ← CDRects.CreateRect[wSize, layer]};
AlwaysExtendPin: PROC [handle: SC.Handle, inst: CD.Instance, extLen: CD.Number, side: SC.Side]
RETURNS [obj: CD.Object ← NIL] = {
create wire and copy pin to end of wire
IF CDSymbolicObjects.IsSymbolicOb[inst.ob] THEN {
pinInst, wireInst: CD.Instance;
name: Rope.ROPE ← CDSymbolicObjects.GetName[inst];
wire: CD.Object ← CreateWireFromPin[handle, inst, extLen];
pinOb: CD.Object ← CDSymbolicObjects.CreateSegment[inst.ob.size.y];
obj ← CDCells.CreateEmptyCell[];
pinInst ← PW.IncludeInCell[obj, pinOb, [extLen-pinOb.size.x, 0], CDSymbolicObjects.OrientFromDirection[east]];
wireInst ← PW.IncludeInCell[obj, wire];
CDProperties.PutProp[wireInst, $SignalName, name];
CDSymbolicObjects.SetName[pinInst, name];
CDSymbolicObjects.SetLayer[pinInst, CDSymbolicObjects.GetLayer[inst]];
RTBasic.SetCDCellName[obj, Rope.Cat[name, IF side = right THEN "Right" ELSE "Left", name]];
RTBasic.RepositionCell[obj]}};
LogicRoute: PROC [handle: SC.Handle] RETURNS [logicOb: CD.Object] = {
ForEachRow: SCRowUtil.EachRowProc = {
[row: SCPrivate.MaxRowSr, lgRow: SCPrivate.LgRow] RETURNS [quit: BOOL ← FALSE]
ExtendPin: PW.ForEachPinProc = {
IF Rope.Equal[CDSymbolicObjects.GetName[inst], layoutData.powerBuses[side].name] THEN
obj ← AlwaysExtendPin[handle, inst, extLen, side]};
extLen: CD.Number;
side: SCPrivate.LRSide;
leftObject, rightObject: CD.Object;
leftTemplate: CD.Object ← lgRow.lgsOnRow[1].object.cdOb;
rightTemplate: CD.Object ← lgRow.lgsOnRow[lgRow.nLgsOnRow].object.cdOb;
lgRows: SCPrivate.LgRows ← layoutData.lgRows;
maxRowWidth: CD.Number ← lgRows.maxRowWidth;
rowLength: SC.Number ← lgRow.size.p;
rowOffset: SC.Number ← lgRow.rowOrg.p - lgRows.horzRowOrg;
IF rowOffset > 0 AND rowLength < maxRowWidth THEN { -- needs filler on left and right
side ← left; extLen ← rowOffset;
leftObject ← TransferCell[template: leftTemplate, objSide: left, width: extLen, objProc: ExtendPin, stopEnumerateDeepPins: FALSE];
RTBasic.SetCDCellName[leftObject, Rope.Cat[handle.name, "LeftRow",Convert.RopeFromInt[lgRow.rowNum]]];
side ← right; extLen ← maxRowWidth - (rowOffset + rowLength);
rightObject ← TransferCell[template: rightTemplate, objSide: right, width: extLen, objProc: ExtendPin, stopEnumerateDeepPins: FALSE];
RTBasic.SetCDCellName[rightObject, Rope.Cat[handle.name, "RightRow",Convert.RopeFromInt[lgRow.rowNum]]]}
ELSE IF rowLength < maxRowWidth THEN { -- rowOffset = 0, no filler on left
side ← right; extLen ← maxRowWidth - rowLength;
rightObject ← TransferCell[template: rightTemplate, objSide: right, width: extLen, objProc: ExtendPin, stopEnumerateDeepPins: FALSE];
RTBasic.SetCDCellName[rightObject, Rope.Cat[handle.name, "RightRow",Convert.RopeFromInt[lgRow.rowNum]]];
leftObject ← NIL}
ELSE -- no filler needed
{leftObject ← NIL; rightObject ← NIL};
lgRow.cdOb ← ConstructRow[handle, leftObject, lgRow, rightObject]};
ForEachChannel: SCChanUtil.EachRowChanProc = {
rowChans: SCPrivate.RowChans ← layoutData.rowChans;
SCRoutePinsUtil.InitGetChanPins[handle];
IF 1 < rowChan.chanNum AND rowChan.chanNum < rowChans.count THEN
CreateExitsForChannel[handle: handle, rowChan: rowChan];
CreatePinsForChannel[handle: handle, rowChan: rowChan];
SCRoutePinsUtil.EnterNetDat[handle, chan, MakeNetPin, MakeExit, NIL];
SCRoutePinsUtil.TermGetChanPins[handle];
IF 1 < rowChan.chanNum AND rowChan.chanNum < rowChans.count THEN
FinishExitsForChannel[handle: handle, rowChan: rowChan]};
MakeExit: SCRoutePinsUtil.ExitProc = {
pinOb: CD.Object ← CDSymbolicObjects.CreatePin[size: [trunkWidth, exit.net.trunkWidth]];
pinInst: CD.Instance ← PW.IncludeInCell[cell: cell, obj: pinOb];
CDSymbolicObjects.SetName[pinInst, exit.net.name];
CDSymbolicObjects.SetLayer[pinInst, exit.layer]};
MakeNetPin: SCRoutePinsUtil.PinProc = {
[rect: SC.Rect, position: CD.Position, netPin: SCPrivate.PinNet, cell: CD.Object]
pinOb: CD.Object ← CDSymbolicObjects.CreatePin[CDBasics.SizeOfRect[rect]];
pinInst: CD.Instance ← PW.IncludeInCell[cell: cell, obj: pinOb, position: position];
CDSymbolicObjects.SetName[pinInst, netPin.net.name];
CDSymbolicObjects.SetLayer[pinInst, netPin.pin.layer]};
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
parms: SCPrivate.Parms ← NARROW[handle.parms];
technologyKey: ATOMNARROW[handle.rules.technology, CD.Technology].key;
routerParams: PWRoute.RouterParams ← NEW[PWRoute.RouterParamsRec ← [trunkLayer: handle.rules.horizLayer, branchLayer: handle.rules.vertLayer, technologyKey: technologyKey, signalBreakAtExit: FALSE, signalSinglePinNets: TRUE]];
[] ← SCRowUtil.EnumerateRows[handle: handle, eachRow: ForEachRow];
[] ← SCChanUtil.EnumerateRowChans[handle: handle, eachRowChan: ForEachChannel];
logicOb ← RouteRows[handle, routerParams];
};
IncludeTrunkPin: PROC [handle: SC.Handle, cell: CD.Object, name: Rope.ROPE, tbSide: SCPrivate.TBSide, trunkSize, trunkPos: SCPrivate.PQPos, layer: CD.Layer] = {
pin: CD.Object ← CDSymbolicObjects.CreateSegment[trunkSize.p];
pinPos: SCPrivate.PQPos ← [trunkPos.p, IF tbSide = top THEN trunkPos.q + trunkSize.q - pin.size.x ELSE trunkPos.q];
pos: CD.Position ← SCUtil.PQToXY[handle, pinPos];
pinInst: CD.Instance ← PW.IncludeInCell[cell, pin, pos, CDSymbolicObjects.OrientFromDirection[IF tbSide = top THEN north ELSE south]];
CDSymbolicObjects.SetName[pinInst, name];
CDSymbolicObjects.SetLayer[pinInst, layer]};
PowerRoute: PROC [handle: SC.Handle, pwOb: CD.Object]
RETURNS [obj: CD.Object] = {
ext end power pins on edges and put in power rails
EachSide: SCRowUtil.EachSideProc ~ {
[side: SC.Side, bpRow: SCPrivate.BpRow] RETURNS [quit: BOOL ← FALSE]
IF side = left OR side = right THEN {
ExtendPin: PW.ForEachPinProc = {
name: Rope.ROPE ← CDSymbolicObjects.GetName[inst];
otherPBus: SCPrivate.PowerBus = layoutData.powerBuses[RTBasic.OtherSide[side]];
SELECT TRUE FROM
Rope.Equal[name, pBus.name] =>
obj ← CreateWireAndContact[handle, inst, [trunkWidth, RTBasic.IRSize[inst.ob].y], width, trunkLayer, handle.rules.sideRules.CDLambda];
Rope.Equal[name, otherPBus.name] => NULL;
ENDCASE => obj ← AlwaysExtendPin[handle, inst, width, side]};
pBus: SCPrivate.PowerBus ← layoutData.powerBuses[side];
width: SC.Number ← MAX[pBus.width, rules.trunkToTrunk];
trunkWidth: SC.Number ← width - rules.trunkSpacing;
extLen: SC.Number ← rules.trunkSpacing;
trunkSize: SCPrivate.PQPos ← [p: trunkWidth, q: RTBasic.IRSize[pwOb].y];
trunkPos: SCPrivate.PQPos ← [IF side = right THEN extLen ELSE 0, 0];
pwSide: PWPins.Side ← SELECT side FROM right => right, left => left, ENDCASE => ERROR;
trunk: CD.Object ← CDRects.CreateRect[SCUtil.PQToXY[handle, trunkSize], trunkLayer];
ext[side] ← TransferCell[template: pwOb, objSide: pwSide, width: width, objProc: ExtendPin, stopEnumerateDeepPins: FALSE];
RTBasic.SetCDCellName[ext[side], Rope.Cat[handle.name, IF side = right THEN "RightPower" ELSE "LeftPower"]];
[] ← PW.IncludeInCell[ext[side], trunk, SCUtil.PQToXY[handle, trunkPos]];
IncludeTrunkPin[handle, ext[side], pBus.name, top, trunkSize, trunkPos , trunkLayer];
IncludeTrunkPin[handle, ext[side], pBus.name, bottom, trunkSize, trunkPos, trunkLayer]}};
ext: ARRAY SCPrivate.LRSide OF CD.Object;
rules: Route.DesignRules ← handle.rules.sideRules;
trunkLayer: CD.Layer ← rules.trunkLayer;
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
[] ← SCRowUtil.EnumerateSides[handle, EachSide];
obj ← ConstructLayout[o1: ext[left], o2: pwOb, o3: ext[right], name: handle.name]};
DetailRoute: PUBLIC PROC [handle: SC.Handle] RETURNS [result: SC.Result] = {
centerObj, fullObj: CD.Object;
lgRows: SCPrivate.LgRows ← NARROW[handle.layoutData, SCPrivate.LayoutData].lgRows;
SCInstUtil.AllOffsets[handle];
[lgRows.maxRowWidth, lgRows.numMaxRows] ← SCRowUtil.FindMaxRow[handle];
centerObj ← LogicRoute[handle];
fullObj ← PowerRoute[handle, centerObj];
result ← NEW[SC.ResultRec ← [handle: handle, object: fullObj]]};
TransferCell: PROC [template: PW.Object, objSide: PWPins.Side, width: INT, objProc: PW.ForEachPinProc, stopEnumerateDeepPins: BOOLEANTRUE] RETURNS [cell: CD.Object ← CDCells.CreateEmptyCell[]] = {
KeepPinOnEdge: PWPins.InstanceEnumerator = {
newObj: CD.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]]}};
iRect: CD.Rect;
-- Start with an empty cell of appropriate interestRect (origin in 0,0)
IF objSide=none THEN ERROR;
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];
RTBasic.RepositionCell[cell]};
IRect: PROC [templateRect: CD.Rect, otherDim: INT, side: PWPins.Side] RETURNS [iRect: CD.Rect] = {
iRect ← SELECT side FROM
top, bottom => [0, 0, templateRect.x2-templateRect.x1, otherDim],
left, right => [0, 0, otherDim, templateRect.y2-templateRect.y1],
ENDCASE => ERROR};
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: CD.Object, side: PWPins.Side] RETURNS [position: CD.Position] = {
position ← PW.GetLocation[inst, template];
SELECT side FROM
top, bottom => position.y ← 0;
left, right => position.x ← 0;
ENDCASE => ERROR};
ConstructRow: PROC [handle: SC.Handle, leftObject: CD.Object, lgRow: SCPrivate.LgRow, rightObject: CD.Object] RETURNS [row: CD.Object ← CDCells.CreateEmptyCell[]] ~ {
ForEachInstance: SCRowUtil.EachInstProc = {
[pos: NAT, instance: SCPrivate.Instance] RETURNS [quit: BOOL ← FALSE]
object: CD.Object ← instance.object.cdOb;
cdInst: CD.Instance ← PW.IncludeInCell[cell: row, obj: object, position: [offset, 0], orientation: SCInstUtil.CDOrien[instance]];
CDProperties.PutInstanceProp[cdInst, $InstanceName, instance.name];
CDProperties.PutProp[cdInst, $StopEnumerateDeepPins, $StopEnumerateDeepPins];
offset ← offset + RTBasic.IRSize[object].x};
offset: SC.Number ← 0;
IF leftObject # NIL THEN {
[] ← PW.IncludeInCell[cell: row, obj: leftObject, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[leftObject].x};
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ForEachInstance];
IF rightObject # NIL THEN {
[] ← PW.IncludeInCell[cell: row, obj: rightObject, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[rightObject].x};
RTBasic.RepositionCell[row];
RTBasic.SetCDCellName[row, Rope.Cat[handle.name, "Row",Convert.RopeFromInt[lgRow.rowNum]]]};
ConstructLayout: PROC [o1, o2, o3, o4, o5: CD.Object ← NIL, name: Rope.ROPE] RETURNS [obj: CD.Object ← CDCells.CreateEmptyCell[]] ~ {
offset: SC.Number ← 0;
IF o1 # NIL THEN {
[] ← PW.IncludeInCell[cell: obj, obj: o1, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[o1].x};
IF o2 # NIL THEN {
[] ← PW.IncludeInCell[cell: obj, obj: o2, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[o2].x};
IF o3 # NIL THEN {
[] ← PW.IncludeInCell[cell: obj, obj: o3, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[o3].x};
IF o4 # NIL THEN {
[] ← PW.IncludeInCell[cell: obj, obj: o4, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[o4].x};
IF o5 # NIL THEN {
[] ← PW.IncludeInCell[cell: obj, obj: o5, position: [offset, 0], orientation: 0];
offset ← offset + RTBasic.IRSize[o5].x};
RTBasic.RepositionCell[obj];
RTBasic.SetCDCellName[obj, name]};
RouteRows: PROC [handle: SC.Handle, routerParams: PWRoute.RouterParams] RETURNS [obj: CD.Object ← CDCells.CreateEmptyCell[]] ~ {
route the channels and include rows and channels into the layout
EachChannel: SCChanUtil.EachRowChanProc ~ {
[chan: SCPrivate.MaxChanSr, rowChan: SCPrivate.RowChan] RETURNS [quit: BOOL ← FALSE]
route each channel
IF chan # 1 THEN {
bottomRow: CD.Object ← lgRows.rows[rowChan.chanNum-1].cdOb;
rowInst: CD.Instance ← PW.IncludeInCell[cell: obj, obj: bottomRow, position: [0, offset], orientation: 0];
CDProperties.PutInstanceProp[rowInst, $InstanceName, Rope.Cat["Row", Convert.RopeFromInt[rowChan.chanNum]]];
offset ← offset + RTBasic.IRSize[ bottomRow].y;
IF chan # rowChans.count THEN {
topRow: CD.Object ← lgRows.rows[rowChan.chanNum].cdOb;
result: Route.RoutingResult ← PWRoute.DoRoute[bottomRow, topRow, rowChan.exitCells[left], rowChan.exitCells[right], routerParams, FALSE, channel];
channel: CD.Object ← PWRoute.GetRouting[result, NIL];
chanInst: CD.Instance ← PW.IncludeInCell[cell: obj, obj: channel, position: [0, offset], orientation: 0];
RTBasic.SetCDCellName[channel, Rope.Cat[handle.name, "Channel", Convert.RopeFromInt[rowChan.chanNum]]];
CDProperties.PutInstanceProp[chanInst, $InstanceName, Rope.Cat["Channel", Convert.RopeFromInt[rowChan.chanNum]]];
offset ← offset + RTBasic.IRSize[channel].y;
rowChans.chans[chan].routing ← result}}};
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
rowChans: SCPrivate.RowChans ← layoutData.rowChans;
lgRows: SCPrivate.LgRows ← layoutData.lgRows;
offset: SC.Number ← 0;
[] ← SCChanUtil.EnumerateRowChans[handle, EachChannel];
RTBasic.RepositionCell[obj];
RTBasic.SetCDCellName[obj, Rope.Cat[handle.name, "Center"]]};
END.