SCRouteImpl.mesa: Implements of SC.DetailedRoute
Copyright © 1986, 1987 by Xerox Corporation. All rights reserved.
Frank Bowers June 3, 1986 10:58:48 am PDT
Last Edited by: Bryan Preas February 17, 1987 6:30:59 pm PST
NOTE: Both PWPins and CoreGeometry Pins are used. This will change as soon as PWRoute removes dependence on PWPins
DIRECTORY
CD, CDBasics, CDCells, CDOps, CDProperties, CDRects, CDSymbolicObjects, Convert, Core, CoreGeometry, CoreOps, ExtendCells, HashTable, PWPins, PWRoute, Rope, Route, RouteUtil, RTBasic, SC, SCChanUtil, SCInstUtil, SCNetUtil, SCPrivate, SCRoutePinsUtil, SCRowUtil, SCUtil, Sinix, SinixOps;
SCRouteImpl: CEDAR PROGRAM
IMPORTS CDBasics, CDCells, CDOps, CDProperties, CDRects, CDSymbolicObjects, Convert, CoreGeometry, CoreOps, ExtendCells, HashTable, PWPins, PWRoute, Rope, RouteUtil, RTBasic, SCChanUtil, SCInstUtil, SCNetUtil, SCRoutePinsUtil, SCRowUtil, SCUtil, Sinix, SinixOps
EXPORTS SCPrivate
SHARES SC = BEGIN
DetailRoute: PUBLIC PROC [handle: SC.Handle, viaTable: HashTable.Table] RETURNS [result: SC.Result] = {
lgRows: SCPrivate.LgRows ← NARROW[handle.layoutData, SCPrivate.LayoutData].lgRows;
SCInstUtil.AllOffsets[handle];
[lgRows.maxRowWidth, lgRows.numMaxRows] ← SCRowUtil.FindMaxRow[handle];
PrepareLogicRoute[handle];
result ← NEW[SC.ResultRec ← [handle: handle, object: RouteRows[handle, viaTable]]];
SCInstUtil.AsgnChanPos[handle];
[] ← SCUtil.WriteResults["End detailed routing\n", handle, 0]};
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 {
alwaysUse: BOOLEAN ← (chan = 1 OR chan = rowChans.count) AND netPin.net.externNet = externalNet AND SCNetUtil.ExitOnSide[handle, netPin.net, side];
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.shell, alwaysUse]}}};
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.shell, TRUE]}}};
InternalPinsInstance: SCRowUtil.EachInstProc = {
[] ← SCInstUtil.EnumeratePinsOnInst[instance, AllPins]};
ExternalPinsInstance: SCRowUtil.EachInstProc = {
[] ← SCInstUtil.EnumeratePinsOnInst[instance, ExternalPins]};
lgRows: SCPrivate.LgRows ← NARROW[handle.layoutData, SCPrivate.LayoutData].lgRows;
rowChans: SCPrivate.RowChans ← NARROW[handle.layoutData, SCPrivate.LayoutData].rowChans;
chan: SCPrivate.MaxChanSr ← rowChan.chanNum;
side: SC.Side;
lgRow: SCPrivate.LgRow;
SELECT TRUE FROM
chan = 1 AND lgRows.count = 1 => {
only one row, must route one exterior channel. Choose the bottom one
side ← bottom; lgRow ← lgRows.rows[1];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, InternalPinsInstance]};
chan = 1 => {
bottom exterior channel, transfer the exterior pins
side ← bottom; lgRow ← lgRows.rows[1];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ExternalPinsInstance]};
chan = lgRows.count + 1 => {
top exterior channel, transfer the exterior pins
side ← top; lgRow ← lgRows.rows[lgRows.count];
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ExternalPinsInstance]};
ENDCASE => {
the interior channels
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] = {
ExitPins: SCChanUtil.EachExitProc = {
PROC [exitNum: SCPrivate.MaxExitsSr, lrSide: SCPrivate.LRSide, rowChan: SCPrivate.RowChan, exit: SCPrivate.Exit] RETURNS [quit: BOOLFALSE]
SCRoutePinsUtil.EnterExit[exit, lrSide, cell]};
EachSide: SCRowUtil.EachSideProc ~ {
[side: SC.Side, bpRow: SCPrivate.BpRow] RETURNS [quit: BOOLFALSE]
IF side = left OR side = right THEN {
cell ← CDCells.CreateEmptyCell[];
[] ← SCChanUtil.EnumerateExits[handle, rowChan, side, ExitPins];
rowChan.exitCells[side] ← cell}};
cell: CD.Object;
[] ← SCRowUtil.EnumerateSides[handle, EachSide]};
FinishExitsForChannel: PROC [handle: SC.Handle, rowChan: SCPrivate.RowChan] = {
EachSide: SCRowUtil.EachSideProc ~ {
[side: SC.Side, bpRow: SCPrivate.BpRow] RETURNS [quit: BOOLFALSE]
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[design: NIL, cell: cell, r: rect]; -- set interestRect of cell
RTBasic.RepositionCell[cell]}};
trunkWidth: SC.Number ← handle.rules.rowRules.trunkWidth;
[] ← SCRowUtil.EnumerateSides[handle, EachSide]};
PrepareLogicRoute: PROC [handle: SC.Handle] = {
ForEachRow: SCRowUtil.EachRowProc = {
[row: SCPrivate.MaxRowSr, lgRow: SCPrivate.LgRow] RETURNS [quit: BOOLFALSE]
ExtendPin: ExtendCells.ExtendSegmentProc = {
PROC [wire: Wire, min, max: INT, layer: Layer, side: Side, extension: INT] RETURNS [Object ← NIL];
obj: CD.Object ← CDCells.CreateEmptyCell[];
connection: CD.Object ← CDRects.CreateRect[[extension, max-min], layer];
connectionInst: CD.Instance ← RouteUtil.Include[obj, connection];
name: Rope.ROPE ← CoreOps.GetShortWireName[wire];
CDProperties.PutProp[connectionInst, $SignalName, name];
CDProperties.PutProp[connectionInst, $InstanceName, name];
RTBasic.RepositionCell[obj];
RETURN[obj]};
leftObject, rightObject: CD.Object ← NIL;
maxRowWidth: CD.Number ← layoutData.lgRows.maxRowWidth;
rowLength: SC.Number ← lgRow.size.p;
rowOffset: SC.Number ← lgRow.rowOrg.p - layoutData.lgRows.horzRowOrg;
IF rowOffset > 0THEN { -- needs filler on left to extend power
leftWirePins: LIST OF ExtendCells.WirePin ← BuildWirePins[handle: handle, cellType: lgRow.lgsOnRow[1].object.cellType, objSide: left];
leftObject ← ExtendCells.ExtendObject[wirePins: leftWirePins, size: [rowOffset, lgRow.size.q], side: left, extendProc: ExtendPin];
RTBasic.SetCDCellName[leftObject, Rope.Cat[handle.name, "Left", Convert.RopeFromInt[lgRow.rowNum]]]};
IF rowOffset + rowLength < maxRowWidth THEN { -- needs filler on right to extend power
rightWirePins: LIST OF ExtendCells.WirePin ← BuildWirePins[handle: handle, cellType: lgRow.lgsOnRow[lgRow.nLgsOnRow].object.cellType, objSide: right];
rightObject ← ExtendCells.ExtendObject[wirePins: rightWirePins, size: [maxRowWidth - (rowOffset + rowLength), lgRow.size.q], side: right, extendProc: ExtendPin];
RTBasic.SetCDCellName[rightObject, Rope.Cat[handle.name, "Right", Convert.RopeFromInt[lgRow.rowNum]]]};
lgRow.shell ← CDCells.CreateEmptyCell[];
lgRow.cdOb ← ConstructRow[handle, leftObject, lgRow, rightObject]};
ForEachChannel: SCChanUtil.EachRowChanProc = {
PROC [chan: SCPrivate.MaxChanSr, rowChan: SCPrivate.RowChan] RETURNS [quit: BOOLFALSE]
useThisChan: BOOLEAN ← (1 < rowChan.chanNum AND rowChan.chanNum < rowChans.count) OR (rowChan.chanNum = 1 AND rowChans.count = 2);
SCRoutePinsUtil.InitGetChanPins[handle];
IF useThisChan THEN CreateExitsForChannel[handle: handle, rowChan: rowChan];
CreatePinsForChannel[handle: handle, rowChan: rowChan];
SCRoutePinsUtil.EnterNetDat[handle, chan, MakeNetPin, MakeExit, NIL];
SCRoutePinsUtil.TermGetChanPins[handle];
IF useThisChan THEN FinishExitsForChannel[handle: handle, rowChan: rowChan]};
MakeExit: SCRoutePinsUtil.ExitProc = {
PROC [exit: SCPrivate.Exit, cell: CD.Object, trunkWidth: SC.Number, side: SCPrivate.LRSide];
pinOb: CD.Object ← CDSymbolicObjects.CreatePin[size: [trunkWidth, exit.net.trunkWidth]];
pinInst: CD.Instance ← RouteUtil.Include[cell: cell, ob: pinOb];
CDSymbolicObjects.SetName[pinInst, exit.net.name];
CDSymbolicObjects.SetLayer[pinInst, exit.layer]};
MakeNetPin: SCRoutePinsUtil.PinProc = {
PROC [rect: SC.Rect, position: CD.Position, netPin: SCPrivate.PinNet, cell: CD.Object];
pinOb: CD.Object ← CDSymbolicObjects.CreatePin[CDBasics.SizeOfRect[rect]];
pinInst: CD.Instance ← RouteUtil.Include[cell: cell, ob: pinOb, position: position];
CDSymbolicObjects.SetName[pinInst, netPin.net.name];
CDSymbolicObjects.SetLayer[pinInst, netPin.pin.layer]};
FinishRow: SCRowUtil.EachRowProc = {
[row: SCPrivate.MaxRowSr, lgRow: SCPrivate.LgRow] RETURNS [quit: BOOLFALSE]
rect: CD.Rect ← [0, 0, layoutData.lgRows.maxRowWidth, lgRow.size.q];
CDCells.SetInterestRect[design: NIL, cell: lgRow.shell, r: rect]; -- set interestRect of cell
RTBasic.RepositionCell[lgRow.shell]};
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
rowChans: SCPrivate.RowChans ← layoutData.rowChans;
[] ← SCRowUtil.EnumerateRows[handle: handle, eachRow: ForEachRow];
[] ← SCChanUtil.EnumerateRowChans[handle: handle, eachRowChan: ForEachChannel];
[] ← SCRowUtil.EnumerateRows[handle: handle, eachRow: FinishRow]};
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: BOOLFALSE]
object: CD.Object ← instance.object.cdOb;
instOrientation: CD.Orientation ← SCInstUtil.CDOrien[instance];
instPosition: CD.Position ← CDOps.FitObjectI[ob: object, location: [offset, 0], orientation: instOrientation].off;
cdInst: CD.Instance ← RouteUtil.Include[cell: row, ob: object, position: instPosition, orientation: instOrientation];
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 {
[] ← RouteUtil.Include[cell: row, ob: leftObject, position: [offset, 0]];
offset ← offset + RTBasic.IRSize[leftObject].x};
[] ← SCRowUtil.EnumerateAllInstsOnRow[handle, lgRow.rowNum, ForEachInstance];
IF rightObject # NIL THEN {
[] ← RouteUtil.Include[cell: row, ob: rightObject, position: [offset, 0]];
offset ← offset + RTBasic.IRSize[rightObject].x};
RTBasic.SetCDCellName[row, Rope.Cat[handle.name, "IntRow", Convert.RopeFromInt[lgRow.rowNum]]];
RTBasic.RepositionCell[row]};
RouteRows: PROC [handle: SC.Handle, viaTable: HashTable.Table] RETURNS [obj: CD.Object ← CDCells.CreateEmptyCell[]] ~ {
route the channels and include rows and channels into the layout
IncludeRow: PROC [rowNum: NAT] ~ {
include the specified row in the object being constructed
RowSidePublics: CoreGeometry.EachWirePinProc ~ {
PROC [wire: Wire, min, max: INT, side: Side, layer: CD.Layer] RETURNS [quit: BOOLFALSE];
add decoration for public; wire is an unbound wire
pin: SCPrivate.PublicPin ← MakeSidePin[wire, min, max, side, layer];
IF pin # NIL THEN lgRow.publics ← CONS[pin, lgRow.publics]};
lgRow: SCPrivate.LgRow ← lgRows.rows[rowNum];
row: CD.Object ← lgRow.cdOb;
rowInst: CD.Instance ← RouteUtil.Include[cell: obj, ob: row, position: [0, offset]];
[] ← CoreGeometry.EnumerateNonOverlappingSides[mode.decoration, lgRow.lgsOnRow[1].object.cellType, RowSidePublics];
[] ← CoreGeometry.EnumerateNonOverlappingSides[mode.decoration, lgRow.lgsOnRow[1].object.cellType, RowSidePublics];
CDProperties.PutInstanceProp[rowInst, $InstanceName, Rope.Cat["Row", Convert.RopeFromInt[rowNum]]];
IF rowNum = 1 THEN AddTBPublics[handle, lgRow, bottom, 0];
offset ← offset + lgRow.size.q;
IF rowNum = lgRows.count THEN AddTBPublics[handle, lgRow, top, offset]};
RouteChannel: PROC [bottomRow, topRow: CD.Object, rowChan: SCPrivate.RowChan] ~ {
route the specified channeland include in the object being constructed
ChanPublics: CoreGeometry.EachWirePinProc ~ {
PROC [wire: Wire, min, max: INT, side: Side, layer: CD.Layer] RETURNS [quit: BOOLFALSE];
add decoration for public; wire is an unbound wire
pin: SCPrivate.PublicPin ← MakeSidePin[wire, min, max, side, layer];
IF pin # NIL THEN rowChan.publics ← CONS[pin, rowChan.publics]};
result: Route.RoutingResult ← PWRoute.DoRoute[bottomRow, topRow, rowChan.exitCells[left], rowChan.exitCells[right], routerParams, FALSE, channel];
channel: CD.Object ← PWRoute.GetRouting[result, NIL, routerParams];
chanInst: CD.Instance ← RouteUtil.Include[cell: obj, ob: channel, position: [0, offset]];
cellType: Core.CellType ← NARROW [Sinix.Extract[channel, mode].result];
[] ← CoreGeometry.EnumerateNonOverlappingSides[mode.decoration, cellType, ChanPublics];
CDProperties.PutInstanceProp[chanInst, $InstanceName, Rope.Cat[handle.name, "Chan", Convert.RopeFromInt[rowChan.chanNum]]];
offset ← offset + RTBasic.IRSize[channel].y;
rowChan.routing ← result;
rowChan.chanWidth ← RTBasic.IRSize[channel].y};
MakeSidePin: PROC [wire: Core.Wire, min, max: INT, side: CoreGeometry.Side, layer: CD.Layer] RETURNS [pin: SCPrivate.PublicPin ← NIL] ~ {
make a public for decoration
IF side = left OR side = right THEN {
name: Rope.ROPE ← CoreOps.GetShortWireName[wire];
net: SCPrivate.Net ← SCUtil.FindNet[handle, name];
trunkWidth: SC.Number ← handle.rules.rowRules.trunkWidth;
position: CD.Position ←
[IF side = left THEN 0 ELSE lgRows.maxRowWidth - trunkWidth, offset + min];
rect: CD.Object ← CDRects.CreateRect[[trunkWidth, max-min], layer];
trans: CD.Transformation ← [position, original];
RETURN[NEW[SCPrivate.PublicPinRec ← [net.wire, rect, trans]]]}};
EachChannel: SCChanUtil.EachRowChanProc ~ {
[chan: SCPrivate.MaxChanSr, rowChan: SCPrivate.RowChan] RETURNS [quit: BOOLFALSE]
route each channel
IF chan # 1 THEN IncludeRow[chan-1];
IF chan = 1 AND rowChans.count = 2 THEN {
do the bottom channel if it is the only one
TransferExits: ExtendCells.ExtendSegmentProc = {
PROC [wire: Wire, min, max: INT, layer: Layer, side: Side, extension: INT] RETURNS [Object ← NIL];
net: SCPrivate.Net ← SCUtil.FindNetByWire[handle, wire];
xfer: BOOLEAN ← net.externNet = externalNet AND SCNetUtil.ExitOnSide[handle, net, bottom] AND side = bottom;
IF xfer THEN {
pinInst: CD.Instance;
pinSize: CD.Position ← [max - min, handle.rules.rowRules.branchWidth];
object: CD.Object ← CDCells.CreateEmptyCell[];
pinInst ← RouteUtil.Include[cell: object, ob: CDSymbolicObjects.CreatePin[pinSize], position: [0, extension - handle.rules.rowRules.branchWidth]];
CDSymbolicObjects.SetName[pinInst, net.name];
CDSymbolicObjects.SetLayer[pinInst, layer];
RTBasic.RepositionCell[object];
RETURN[object]}};
topRow: CD.Object ← lgRows.rows[chan].shell;
wirePins: LIST OF ExtendCells.WirePin ← BuildWirePinsFromObj[handle: handle, template: topRow, objSide: bottom];
bottomRow: CD.Object ← ExtendCells.ExtendObject[wirePins: wirePins, size: RTBasic.IRSize[topRow], side: bottom, extendProc: TransferExits];
RouteChannel[bottomRow, topRow, rowChan]};
IF chan # rowChans.count AND chan # 1 THEN {
do the interior channels
bottomRow: CD.Object ← lgRows.rows[chan-1].shell;
topRow: CD.Object ← lgRows.rows[chan].shell;
RouteChannel[bottomRow, topRow, rowChan]}};
layoutData: SCPrivate.LayoutData ← NARROW[handle.layoutData];
rowChans: SCPrivate.RowChans ← layoutData.rowChans;
lgRows: SCPrivate.LgRows ← layoutData.lgRows;
mode: Sinix.Mode ← SinixOps.GetExtractMode[handle.rules.technology];
table: HashTable.Table ← IF viaTable # NIL THEN viaTable ELSE HashTable.Create[equal: EqualProc, hash: HashProc];
routerParams: PWRoute.RouterParams ← NEW[PWRoute.RouterParamsRec
← [trunkLayer: handle.rules.horizLayer,
branchLayer: handle.rules.vertLayer,
technologyKey: NARROW[handle.rules.technology, CD.Technology].key,
signalBreakAtExit: FALSE,
signalSinglePinNets: TRUE,
viaTable: table]];
offset: SC.Number ← 0;
parms: SCPrivate.Parms ← NARROW[handle.parms];
parms.viaTable ← table;
[] ← SCChanUtil.EnumerateRowChans[handle, EachChannel];
RTBasic.SetCDCellName[obj, handle.name];
RTBasic.RepositionCell[obj]};
BuildWirePins: PROC [handle: SC.Handle, cellType: Core.CellType, objSide: CoreGeometry.Side] RETURNS [wirePins: LIST OF ExtendCells.WirePin ← NIL] ~ {
extract a list of wirePins from side of template
EachWirePin: CoreGeometry.EachWirePinProc = {
IF side=objSide THEN wirePins ← CONS [[wire, min, max, layer], wirePins]};
mode: Sinix.Mode = SinixOps.GetExtractMode[handle.rules.technology];
[] ← CoreGeometry.EnumerateWireSides[mode.decoration, cellType, EachWirePin]};
BuildWirePinsFromObj: PROC [handle: SC.Handle, template: CD.Object, objSide: PWPins.Side] RETURNS [wirePins: LIST OF ExtendCells.WirePin ← NIL] ~ {
extract a list of wirePins from side of template
TEMPORARY
EachPin: PWPins.InstanceEnumerator = {
IF objSide=PWPins.GetSide[template, inst] THEN {
layer: CD.Layer ← CDSymbolicObjects.GetLayer[inst];
net: SCPrivate.Net ← SCUtil.FindNet[handle, CDSymbolicObjects.GetName[inst]];
rect: CD.Rect ← CDSymbolicObjects.Denotes[inst];
min: CD.Number ← IF objSide = top OR objSide = bottom THEN rect.x1 ELSE rect.y1;
max: CD.Number ← IF objSide = top OR objSide = bottom THEN rect.x2 ELSE rect.y2;
wirePins ← CONS [[net.wire, min, max, layer], wirePins]}};
[] ← PWPins.EnumerateEdgePins[template, EachPin]};
AddTBPublics: PROC[handle: SC.Handle, lgRow: SCPrivate.LgRow, objSide: PWPins.Side, yCord: CD.Number] ~ {
add pins on side of template to public decoration
TEMPORARY
EachPin: PWPins.InstanceEnumerator = {
IF objSide=PWPins.GetSide[lgRow.shell, inst] THEN {
layer: CD.Layer ← CDSymbolicObjects.GetLayer[inst];
net: SCPrivate.Net ← SCUtil.FindNet[handle, CDSymbolicObjects.GetName[inst]];
trunkWidth: SC.Number ← handle.rules.rowRules.trunkWidth;
denote: CD.Rect ← CDSymbolicObjects.Denotes[inst];
public: CD.Object ← CDRects.CreateRect[[denote.x2-denote.x1, trunkWidth], layer];
position: CD.Position ←
[denote.x1, IF objSide = bottom THEN yCord ELSE yCord - trunkWidth];
lgRow.publics ← CONS[NEW[SCPrivate.PublicPinRec ← [net.wire, public, [position, original]]], lgRow.publics]}};
[] ← PWPins.EnumerateEdgePins[lgRow.shell, EachPin]};
EqualProc: PROC [k1, k2: HashTable.Key] RETURNS [eq: BOOL] = {
p1: Route.Position ← NARROW[k1, REF Route.Position]^;
p2: Route.Position ← NARROW[k2, REF Route.Position]^;
eq ← p1.x = p2.x AND p1.y = p2.y};
HashProc: PROC [k: HashTable.Key] RETURNS [hash: CARDINAL] = {
size: Route.Position ← NARROW[k, REF Route.Position]^;
hash ← size.x + size.y};
END.