WireIconExtrasImpl.mesa
Don Curry May 14, 1987 4:33:34 pm PDT
Last Edited by: Don Curry September 15, 1987 1:58:56 pm PDT
DIRECTORY CD, CDCommandOps, CDDirectory, CDInstances, CDLayers, CDOps, CDPanelFonts, CDProperties, CDRects, CDSatellites, CDSequencer, CDTexts, CDViewer, Core, CoreCreate, CoreOps, CoreProperties, PopUpMenus, IO, PW, REFBit, Rope, Sisyph, TerminalIO, ViewerClasses, ViewerOps, WireIconExtras;
WireIconExtrasImpl: CEDAR PROGRAM
IMPORTS CD, CDCommandOps, CDDirectory, CDInstances, CDLayers, CDOps, CDPanelFonts, CDProperties, CDRects, CDSatellites, CDTexts, CDViewer, CoreCreate, CoreProperties, CoreOps, IO, PW, REFBit, Rope, Sisyph, TerminalIO, ViewerOps
EXPORTS WireIconExtras = BEGIN
ROPE: TYPE = Rope.ROPE;
Wire: TYPE = Core.Wire;
Wire From Cedar Types
RecWire: PUBLIC PROC[rec: ROPE, dual: BOOLFALSE] RETURNS[wire: Wire] =
{RETURN[RefWire[REFBit.NEWFromName[rec], rec.Substr[rec.Index[0,"."]+1]]]};
RefWire: PUBLIC PROC[ref: REF, name: ROPE, dual: BOOLFALSE] RETURNS[wire: Wire] = {
ZeroIfOne: PROC[s: INT] RETURNS[INT] = {RETURN[IF s=1 THEN 0 ELSE s]};
fieldForm: REFBit.Format ← REFBit.Desc[ref].fieldForm;
wires:   LIST OF Wire ← NIL;
temp:   LIST OF Wire ← NIL;
FOR field: INT IN[0..fieldForm.size) DO
fieldName: ROPE ← BitRopeToSigRope[fieldForm[field].name];
SELECT fieldForm[field].bitSize FROM
1   =>
{wires ← CONS[CoreCreate.Seq[fieldName, IF dual THEN 2 ELSE 0], wires]};
>1   => {
fieldWire: Wire ← CoreOps.CreateWires[fieldForm[field].bitSize, fieldName];
wires ← CONS[fieldWire, wires];
FOR bit: INT IN[0..fieldWire.size) DO
fieldWire[bit] ← CoreCreate.Seq[NIL, IF dual THEN 2 ELSE 0] ENDLOOP};
ENDCASE => ERROR;
ENDLOOP;
temp ← wires; wires ← NIL;
FOR temp ← temp, temp.rest WHILE temp#NIL DO wires ← CONS[temp.first, wires] ENDLOOP;
wire ← CoreOps.CreateWire[wires, name]};
ETWire: PUBLIC PROC[prefix, suffix, type: ROPE] RETURNS[wire: Wire] = {
bitName, bitNameInv: ROPE;
refREF: REF ← REFBit.NEWFromName[type];
format: REFBit.Format ← REFBit.Desc[refREF].bitForm;
wire ← CoreOps.CreateWires[MAX[2, format.size], prefix.Cat[suffix]];
FOR i: INT IN[0..format.size) DO
bitName  ← BitRopeToSigRope[format[i].name];
bitNameInv ← BitRopeToSigRope[format[i].nameInv];
IF ((format.size#1) = (i+1=format.size)) = (bitName#NIL) THEN ERROR;
IF ((format.size#1) = (bitNameInv#NIL)) = (bitName#NIL) THEN ERROR;
IF bitName=NIL THEN bitName ← bitNameInv;
wire[i] ← CoreOps.CreateWires[0, prefix.Cat[bitName, suffix]];
IF format.size=1 THEN
wire[1] ← CoreOps.CreateWires[0, prefix.Cat[bitNameInv, suffix]];
ENDLOOP};
BitRopeToSigRope: PROC [name: ROPE] RETURNS [ROPE] ~ {
Cap: PROC[rope: ROPE, idx: INT] RETURNS[ROPE] = {
char: CHAR ← rope.Fetch[idx+1];
IF char IN ['a..'z] THEN char ← char + LOOPHOLE['A - 'a];
RETURN[IO.PutFR["%g", IO.char[char]]]};
IF name = NIL THEN RETURN[NIL];
name ← Rope.Cat[Cap[name, -1], name.Substr[1]];
DO-- remove peiods and Capitalize next letters until end or next char is number
index: INT ← name.Index[0, "."];
IF index+1 >= name.Length[] OR name.Fetch[index+1] IN ['0..'9] THEN RETURN[name];
name ← Rope.Cat[name.Substr[0,index], Cap[name, index], name.Substr[index+2]];
ENDLOOP };
Icon From Cedar Types
ConstructRecWireIconCommand: PROC [comm: CDSequencer.Command] ~ {
type: ROPE  ← TerminalIO.RequestRope["Type (eg. Def.Record): "];
wire: Core.Wire ← RecWire[type];
name: ROPE  ← type.Substr[type.Index[0,"."]+1];
icon: CD.Object ← ConstructWireIcon[comm, wire, name.Cat[".icon"]];
CDProperties.PutObjectProp[icon, $CodeFor, Rope.Cat["WireIconExtras.RecWire[\"", type, "\"]" ]];
CDProperties.PutObjectProp[icon, Sisyph.mode.extractProcProp, $SisyphExtractNamedWireIcon]};
ConstructDETWireIconCommand: PROC [comm: CDSequencer.Command] ~ {
type: ROPE  ← TerminalIO.RequestRope["Type (eg. Def.Enumerated): "];
pre: ROPE  ← TerminalIO.RequestRope["Prefix:"];
suf: ROPE  ← TerminalIO.RequestRope["Suffix:"];
wire: Core.Wire ← ETWire[pre, suf, type];
icon: CD.Object ← ConstructWireIcon[comm, wire, Rope.Cat[pre, suf, ".icon"]];
CDProperties.PutObjectProp
[icon, $CodeFor, IO.PutFR["WireIconExtras.ETWire[\"%g\", \"%g\", \"%g\"]",
IO.rope[pre], IO.rope[suf], IO.rope[type] ]];
CDProperties.PutObjectProp[icon, Sisyph.mode.extractProcProp, $SisyphExtractNamedWireIcon]};
ConstructWireIcon: PROC [comm: CDSequencer.Command, wire: Core.Wire, name: ROPE]
RETURNS[icon: CD.Object] ~ {
font:   CDTexts.CDFont ← CDPanelFonts.CurrentFont[comm.design];
w:    INT ← CDLayers.LayerWidth[comm.design, CD.commentLayer];
fw2:   INT ← font.height/2 - w - font.origin.y;
grid:   CD.Number ← Grid[comm.design, font];
insts:   CD.InstanceList ← NIL;
pinObject: CD.Object  ← CDRects.CreateRect[size: [grid/2, w], l: CD.commentLayer];
text:   CD.Object;
sat:   CD.Instance;
pin:   CD.Instance;
maxX:   INT ← 0;
FOR i: INT IN [0..wire.size) DO
text ← CDTexts.Create[CoreOps.GetShortWireName[wire[i]], font];
maxX ← MAX[ maxX, CD.InterestSize[text].x];
sat  ← CDInstances.NewInst[text,  [[x: grid, y: grid*(i+2)-fw2 ]]];
pin ← CDInstances.NewInst[pinObject, [[x: 0,  y: grid*(i+2)   ]]];
insts ← CONS[sat, CONS[pin, insts]];
CDSatellites.Associate[master: pin, text: sat] ENDLOOP;
text ← CDTexts.Create[CoreOps.GetShortWireName[wire], font];
maxX ← ((MAX[ maxX, CD.InterestSize[text].x] + 3*grid-1)/grid)*grid;
sat  ← CDInstances.NewInst[text, [[x: maxX-grid-CD.InterestSize[text].x, y: grid-fw2 ]]];
pin ← CDInstances.NewInst[pinObject, [[x: maxX-grid/2, y: grid ]]];
insts ← CONS[sat, CONS[pin, insts]];
CDSatellites.Associate[master: pin, text: sat];
icon ← PW.CreateCell[instances: insts, ir: [0, 0, maxX, grid*(wire.size+2)+w]];
IF NOT CDDirectory.Include[comm.design, icon, name] THEN
{TerminalIO.PutF["*** Directory insertion of %g failed.\n", IO.rope[name]]; ERROR};
CDCells.SetSimplificationTreshhold[cell: icon, val: 30, inPixels: TRUE];
[]�Ops.IncludeObjectI[comm.design, icon, comm.pos]};
Grid: PROC[design: CD.Design, font: CDTexts.CDFont ← NIL] RETURNS[grid: NAT] = {
viewers: CDViewer.ViewerList ← CDViewer.ViewersOf[design];
IF viewers#NIL
THEN WITH ViewerOps.GetViewer[viewers.first, $Grid] SELECT FROM
rgrid: REF CD.Number => grid ← rgrid^; ENDCASE => NULL
ELSE grid ← design.technology.lambda*2;
IF font#NIL THEN WHILE font.height > (grid*4)/3 DO grid ← grid*2 ENDLOOP};
Icon From Schematic Procs -- Old
ConstructIconCommand: PROC [comm: CDSequencer.Command] ~ {
schFullName: ROPE;
iconName: ROPE;
icon:   CD.Object;
cell:   Core.CellType;
selected:  CD.Instance;
multiple:  BOOL;
cellRef:  REF;
sort:   BOOL;
[selected, multiple] ← CDOps.SelectedInstance[comm.design];
IF ~IsSingleSelectedAndCell[selected, multiple] THEN RETURN;
schFullName ← CDDirectory.Name[selected.ob, comm.design];
IF schFullName=NIL THEN
{TerminalIO.PutF["*** Selected schematic has no name.\n"]; RETURN};
IF NOT Rope.Match["*.sch", schFullName] THEN TerminalIO.PutF["*** Convention for schematics is to suffix them with '.sch'.\n"];
cellRef ← SinixOps.ExtractCDInstance[selected, comm.design, Sisyph.mode].result;
IF ISTYPE [cellRef, Core.CellType]
THEN cell ← NARROW [cellRef]
ELSE {TerminalIO.PutF["*** Selected cell does not extract to Core CellType\n"]; RETURN};
iconName ← TerminalIO.RequestRope["Type icon short name: "];
IF Rope.IsEmpty[iconName]
THEN iconName ← IF Rope.Match["*.sch", schFullName]
THEN Rope.Substr[schFullName, 0, Rope.Length[schFullName]-4]
ELSE schFullName;
IF Rope.IsEmpty[schFullName] THEN
{TerminalIO.PutF["No name provided, no default from schematic.\n"]; RETURN};
IF CDDirectory.Fetch[comm.design, Rope.Cat[iconName, ".icon"]]#NIL THEN {
TerminalIO.PutF["*** The icon %g.icon already exists!\n", IO.rope[iconName]];
RETURN};
sort ← SELECT comm.key FROM
$ConstructIconCommand  => FALSE,
$ConstructIconCommandSort => TRUE,
ENDCASE       => ERROR;
icon ← IconFromSchematic[cell, schFullName, iconName, comm.design, sort];
IF icon=NIL THEN RETURN;
CDCells.SetSimplificationTreshhold[cell: icon, val: 30, inPixels: TRUE];
[]�Ops.IncludeObjectI[comm.design, icon, comm.pos]};
mark: ATOM ← CoreProperties.RegisterProperty[$TemporaryMark];
IconFromSchematic: PROC[
schCT: Core.CellType,
schFullName: ROPE,
iconName:  ROPE,
design:   CD.Design,
sort:    BOOL ]
RETURNS [iconObj: CD.Object] ~ {
L16: PROC[in: INT] RETURNS[INT] =
{XX: INT ← design.technology.lambda*16; RETURN[((in+XX-1)/XX)*XX]};
font:   CDTexts.CDFont ← CDPanelFonts.CurrentFont[design];
grid:   CD.Number ← Grid[design, font];
insts:   CD.InstanceList ← NIL;
schDeco:  CoreGeometry.Decoration ← Sisyph.mode.decoration;
schSize:  CD.Position ← CD.InterestSize[CoreGeometry.GetObject[schDeco, schCT]];
iconSize:  CD.Position;
hChans:  INT;
vChans:  INT;
iNmOb:  CD.Object ← CDTexts.Create[iconName, font];
pins:   ARRAY CoreGeometry.Side OF CD.InstanceList ← ALL[NIL];
cnt:   ARRAY CoreGeometry.Side OF NAT    ← ALL[0];
smax:   ARRAY CoreGeometry.Side OF INT     ← ALL[16];
w:    INT ← CDLayers.LayerWidth[design, CD.commentLayer];
fw2:   INT ← font.height/2 - w - font.origin.y;
iconFullName: ROPE ← iconName.Cat[".icon"];
pinObject:  CD.Object;
horWall:   CD.Object;
verWall:   CD.Object;
clearMark:  CoreOps.EachWireProc = {CoreProperties.PutWireProp[wire, mark, NIL]};
FOR side: CoreGeometry.Side IN CoreGeometry.Side DO
eachSortedPin: CoreGeometry.EachSortedPinProc = {
IF CoreProperties.GetWireProp[wire, mark]=NIL THEN {
text: CD.Object  ← CDTexts.Create[CoreOps.GetShortWireName[wire], font];
inst: CD.Instance ← CDInstances.NewInst[text];
CoreProperties.PutWireProp[wire, mark, mark];
pins[side]  ← CONS[inst, pins[side]];
cnt[side]   ← cnt[side] + 1;
smax[side]  ← MAX[ smax[side], CD.InterestSize[text].x] }};
[] ← CoreGeometry.EnumerateSortedSides[schDeco, schCT, side, eachSortedPin];
[]𡤌oreOps.VisitWire[schCT.public, clearMark];
ENDLOOP;
IF sort THEN FOR side: CoreGeometry.Side IN CoreGeometry.Side DO DO
ok: BOOLTRUE;
FOR insts: CD.InstanceList ← pins[side], insts.rest WHILE insts#NIL AND insts.rest#NIL DO
TwoObj: TYPE = RECORD[ob1, ob2: CD.Object];
r1: ROPENARROW[insts.first.ob.specific,  CDTexts.TextSpecific].text;
r2: ROPENARROW[insts.rest.first.ob.specific, CDTexts.TextSpecific].text;
SELECT Rope.Compare[r1, r2] FROM
less  => LOOP;
equal  => ERROR; ENDCASE;
[insts.first.ob, insts.rest.first.ob] ← TwoObj[insts.rest.first.ob, insts.first.ob];
ok←FALSE;
ENDLOOP;
IF ok THEN EXIT; ENDLOOP; ENDLOOP;
hChans  ← MAX[cnt[left], cnt[right]];
vChans  ← MAX[cnt[top], cnt[bottom]];
iconSize.x← L16[(vChans+4)*grid + 2*MAX[smax[left], smax[right],CD.InterestSize[iNmOb].x]];
iconSize.y← L16[(hChans+4)*grid + 2*MAX[smax[top], smax[bottom]]];
IF schSize.x > schSize.y
THEN iconSize.x ← L16[MAX[iconSize.x, (iconSize.y*schSize.x + schSize.y/2) /schSize.y]]
ELSE iconSize.y ← L16[MAX[iconSize.y, (iconSize.x*schSize.y + schSize.x/2) /schSize.x]];
pinObject ← CDRects.CreateRect[size: [grid/2, w], l: CD.commentLayer];
horWall ← CDRects.CreateRect[size: [iconSize.x, w], l: CD.commentLayer];
verWall ← CDRects.CreateRect[size: [w, iconSize.y], l: CD.commentLayer];
FOR side: CoreGeometry.Side DECREASING IN CoreGeometry.Side DO
tr: CD.Transformation ← SELECT side FROM
top  => [ [iconSize.x/2 - vChans*grid/2, iconSize.y],  rotate270 ],
bottom => [ [iconSize.x/2 - vChans*grid/2, 0],    rotate90 ],
left  => [ [0,    iconSize.y/2 - hChans*grid/2], original],
right  => [ [iconSize.x, iconSize.y/2 - hChans*grid/2], rotate180],
ENDCASE => ERROR;
index: INT ← (cnt[side]+(SELECT side FROM top,bottom=>vChans, ENDCASE => hChans)+1)/2;
FOR temp: CD.InstanceList ← pins[side], temp.rest WHILE temp#NIL DO
pin, sat: CD.Instance;
index ← index-1;
insts ← CONS[(sat ← temp.first), insts];
insts ← CONS[(pin ← CDInstances.NewInst[pinObject]), insts];
sat.trans.orient ← pin.trans.orient ← tr.orient;
CDSatellites.Associate[master: pin, text: sat];
SELECT side FROM
top  => {
pin.trans.off ← CDBasics.AddPoints[tr.off, [index*grid+0,   0  ]];
sat.trans.off ← CDBasics.AddPoints[tr.off, [index*grid+0-fw2, -grid ]]};
bottom => {
pin.trans.off ← CDBasics.AddPoints[tr.off, [index*grid+w,  0  ]];
sat.trans.off ← CDBasics.AddPoints[tr.off, [index*grid+w+fw2, +grid ]]};
left  => {
pin.trans.off ← CDBasics.AddPoints[tr.off, [0,  index*grid+0  ]];
sat.trans.off ← CDBasics.AddPoints[tr.off, [+grid, index*grid+0-fw2 ]]};
right  => {
pin.trans.off ← CDBasics.AddPoints[tr.off, [0,  index*grid+w  ]];
sat.trans.off ← CDBasics.AddPoints[tr.off, [-grid, index*grid+w+fw2 ]]};
ENDCASE => ERROR;
ENDLOOP;
ENDLOOP;
insts ← CONS[CDInstances.NewInst[horWall, [off:[0,     0    ]]], insts];
CDProperties.PutInstanceProp[insts.first, Sisyph.mode.extractProcProp, $ExtractNull];
insts ← CONS[CDInstances.NewInst[horWall, [off:[0,     iconSize.y-w ]]], insts];
CDProperties.PutInstanceProp[insts.first, Sisyph.mode.extractProcProp, $ExtractNull];
insts ← CONS[CDInstances.NewInst[verWall, [off:[0,     0    ]]], insts];
CDProperties.PutInstanceProp[insts.first, Sisyph.mode.extractProcProp, $ExtractNull];
insts ← CONS[CDInstances.NewInst[verWall, [off:[iconSize.x-w, 0    ]]], insts];
CDProperties.PutInstanceProp[insts.first, Sisyph.mode.extractProcProp, $ExtractNull];
insts ← CONS[CDInstances.NewInst[iNmOb, [off:[grid,   iconSize.y-2*grid ]]], insts];
iconObj ← PW.CreateCell[instances: insts];
IF NOT CDDirectory.Include[design, iconObj, iconFullName] THEN
{TerminalIO.PutF["*** Directory insertion of %g failed.\n", IO.rope[iconFullName]]; ERROR};
CDProperties.PutObjectProp[iconObj, Sisyph.mode.extractProcProp, $SisyphExtractCellIcon];
CDProperties.PutObjectProp[iconObj, $IconFor,       schFullName]};
LayoutStructureAndDrcCheckOfSelectedIcons: PROC [comm: CDSequencer.Command] = {
count:  INT ← 0;
drcErrors:  INT ← 0;
errs:  INT ← 0;
result:  REF;
badguys:  LIST OF ROPE;
objName:  ROPE;
errorMsg:  ROPE;
errorType: ATOM;
sourceCT:  Core.CellType;
indirectOb: CD.Object;
directOb:  CD.Object;
directCT:  Core.CellType;
drcAtomDesign: ATOM ← DesignRules.FetchRulesID[comm.design];
drcAtom:   ATOMIF drcAtomDesign#NIL THEN drcAtomDesign ELSE $VTI;
rules:    DesignRules.Rules ← DesignRules.GetRuleSet[drcAtom];
tech:    Drc.Tech ← DrcCMOSB.NewTechnology[DrcCMOSB.cMosBcompleteKey, rules];
IF comm.design.actual.rest#NIL THEN
{TerminalIO.PutF["Can't handle pushed in cell\n"]; RETURN};
TerminalIO.PutF["Using %g design rules\n", IO.atom[drcAtom]];
FOR w: CD.InstanceList ← CDOps.InstList[comm.design], w.rest WHILE w#NIL DO
IF NOT w.first.selected THEN LOOP;
objName ← CDDirectory.Name[w.first.ob, comm.design];
IF Rope.Find[objName, ".icon"]=-1 THEN
{TerminalIO.PutF["%g is not an icon.\n", IO.rope[objName]]; LOOP};
result ← SinixOps.ExtractCDInstance[w.first, comm.design, Sisyph.mode].result;
IF result=NIL OR NOT ISTYPE[result, Core.CellType] THEN
{TerminalIO.PutF["%g does not extract as a cell.\n", IO.rope[objName]]; LOOP};
sourceCT ← NARROW[result];
errorType ← NIL;
indirectOb ← PWCore.Layout[sourceCT !
PWCore.Error => {errorType ← type; errorMsg ← message; CONTINUE}];
IF errorType#NIL THEN {
TerminalIO.PutF["Cell for %g has Layout %g ERROR\n %g.\n",
IO.rope[objName], IO.atom[errorType], IO.rope[errorMsg]]; LOOP};
PWCoreLichen.Compare[sourceCT];
TerminalIO.PutF["Done extracting and comparing.\n"];
directOb ← CDDirectory.Expand1[indirectOb].new;
IF directOb=NIL THEN ERROR;
directCT ← NARROW[Sinix.Extract[directOb, PWCore.extractMode].result];
IF directCT=NIL THEN ERROR;
errs ← Drc.CheckDesignRules[directCT, CoreOps.CopyWire[directCT.public], tech, TRUE, NIL, PWCore.extractMode.decoration];
TerminalIO.PutF[" %2g errors in %g.\n", IO.int[errs],
IO.rope[CoreOps.GetCellTypeName[directCT]]];
IF errs#0 THEN {
[]�ug.Draw[directOb, comm.design.technology, objName];
badguys ← CONS[objName, badguys]};
drcErrors ← drcErrors+errs;
count ← count+1;
ENDLOOP;
TerminalIO.PutF["%2g drc errors in %g cells.\n", IO.int[drcErrors], IO.int[count]];
FOR badguys ← badguys, badguys.rest WHILE badguys#NIL DO
TerminalIO.PutF[" %g\n", IO.rope[badguys.first]]; ENDLOOP};
IsSingleSelected: PROC [selected: CD.Instance, multiple: BOOL] RETURNS [BOOL] = {
IF selected=NIL THEN
{TerminalIO.PutF["*** No current selection--can't do it.\n"]; RETURN[FALSE]};
IF multiple THEN
{TerminalIO.PutF["*** Multiple instances selected--can't do it.\n"]; RETURN[FALSE]};
RETURN[TRUE]};
IsSingleSelectedAndCell: PROC [selected: CD.Instance, multiple: BOOL] RETURNS [BOOL] = {
IF ~IsSingleSelected[selected, multiple] THEN RETURN [FALSE];
IF ~CDCells.IsCell[selected.ob] THEN
{TerminalIO.PutF["*** Selected instance is not a cell—can't do it.\n"]; RETURN[FALSE]};
RETURN[TRUE]};
CDCommandOps.RegisterWithMenu[
menu:  $OtherProgramMenu,
entry:  "Library Layout Check",
doc:  "Lichen structure check and design rule check of the layouts of all selected icons",
key:  $LayoutStructureAndDrcCheckOfSelectedIcons,
proc:  LayoutStructureAndDrcCheckOfSelectedIcons];
CDCommandOps.RegisterWithMenu[
menu:  $SisyphIconMenu,
entry:  "Create Cell Icon from schematics - Grid 2",
doc:  "Pins on grid 2. Overall size in units of 16.",
key:  $ConstructIconCommand,
proc:  ConstructIconCommand ];
CDCommandOps.RegisterWithMenu[
menu:  $SisyphIconMenu,
entry:  "Create Cell Icon from schematics - Grid 2 - Sorted",
doc:  "Pins on grid 2. Overall size in units of 16. Names sorted.",
key:  $ConstructIconCommandSort,
proc:  ConstructIconCommand ];
Initialization
CDCommandOps.RegisterWithMenu[
menu:  $SisyphIconMenu,
entry:  "Create Wire Icon from Cedar Record Type",
doc:  "RECORDs with field types such as: RECORD, INT, BOOL, enumerated, subranges",
key:  $ConstructRecWireIconCommand,
proc:  ConstructRecWireIconCommand ];
CDCommandOps.RegisterWithMenu[
menu:  $SisyphIconMenu,
entry:  "Create Wire Icon from Cedar Enumerated Type",
doc:  "Decoded Enumerated types encoded as: 0, 3, 5, 9, 17 etc.",
key:  $ConstructDETWireIconCommand,
proc:  ConstructDETWireIconCommand ];
END.