XilinxIOImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Barth, March 1, 1990 3:29:11 pm PST
Last Edited by: Gasbarro July 18, 1989 6:23:49 pm PDT
DIRECTORY Atom, CDCommandOps, CDSequencer, Core, CoreCDUser, CoreClasses, CoreFlat, CoreOps, CoreProperties, FS, RefTab, Sequence, Sisyph, SymTab, TerminalIO, IO, Rope, XilinxIO;
XilinxIOImpl: CEDAR PROGRAM
IMPORTS Atom, CDCommandOps, CoreCDUser, CoreClasses, CoreFlat, CoreOps, CoreProperties, FS, RefTab, Sisyph, SymTab, TerminalIO, IO, Rope
EXPORTS XilinxIO
SHARES Sisyph
= BEGIN OPEN XilinxIO;
primitives: SymTab.Ref ← SymTab.Create[]; -- cell type name to Primitive
directionNames: ARRAY InOrOut OF ROPE ← ["I", "O"];
Placement
seqProp: ATOM = $XilinxIOSeqProp;
SeqData: TYPE = REF SeqDataRec;
SeqDataRec: TYPE = RECORD [
axis: Axis,
startIndex: NAT,
orthogonalIndex: NAT,
delta: INT];
Loc: PUBLIC PROC [cx: Sisyph.Context, x, y: INT] RETURNS [trash: NAT ← 0] = {
SELECT TRUE FROM
x>=0 AND y>=0 => Sisyph.AddProp[cx, $ParLoc, IO.PutFR["\"LOC=%g%g\"", IO.char[NumToLet[y]], IO.char[NumToLet[x]]], TRUE];
x<0 AND y<0 => NULL;
ENDCASE => ERROR;
};
Set: PUBLIC PROC [cx: Sisyph.Context, x, y: INT, axis: Axis ← ax] RETURNS [trash: NAT ← 0] = {
Sisyph.Store[cx, "x", NEW[INT ← x]];
Sisyph.Store[cx, "y", NEW[INT ← y]];
Sisyph.Store[cx, "axis", NEW[Axis ← axis]];
};
Seq: PUBLIC PROC [cx: Sisyph.Context, axis: Axis, startIndex, orthogonalIndex: NAT, delta: INT ← 1] RETURNS [trash: NAT ← 0] = {
previousProps: Core.Properties ← Sisyph.GetCoreInstProps[cx];
Sisyph.Store[cx, Sisyph.coreInstPropsRope, NEW [Core.Properties ← CoreProperties.PutProp[previousProps, seqProp, NEW[SeqDataRec ← [axis, startIndex, orthogonalIndex, delta]]]]];
};
Functions
SaveCellType: PUBLIC PROC [cellType: Core.CellType, fileName: ROPENIL] RETURNS [multiplyDriven: LORNIL] = {
circuit: FlatCircuit ← FlattenCircuit[cellType];
pruned: RefTab.Ref ← PruneFlattenedCircuit[circuit];
multiplyDriven ← CheckMultipleDrivers[circuit];
IF fileName=NIL THEN fileName ← Rope.Cat[CoreOps.GetCellTypeName[cellType], ".xnf"];
WriteFlattenedCircuit[circuit, pruned, fileName];
};
FlattenCircuit: PROC [cellType: Core.CellType] RETURNS [circuit: FlatCircuit] = {
PushPins: CoreOps.EachWireProc = {
pinList: ROPENARROW[CoreProperties.GetWireProp[wire, $Pins]];
IF pinList#NIL THEN {
ris: IO.STREAMIO.RIS[pinList];
FOR i: INT IN [0..wire.size) DO
pin: ROPEIO.GetCedarTokenRope[ris].token;
currentValue: ROPENARROW[CoreProperties.GetWireProp[wire[i], $Pin]];
IF i#(wire.size-1) THEN {
kind: IO.TokenKind;
token: IO.ROPE;
[kind, token] ← IO.GetCedarTokenRope[ris];
IF kind#tokenSINGLE OR NOT Rope.Equal[token, ","] THEN ERROR;
};
IF wire[i].size#0 THEN ERROR;
IF currentValue=NIL THEN CoreProperties.PutWireProp[wire[i], $Pin, pin];
ENDLOOP;
};
};
FlattenCellType: CoreFlat.BoundFlatCellProc = {
GetParms: CoreOps.EachWireProc = {
FindParms: PROC [prop: ATOM, val: REF ANY] = {
propName: ROPE ← Atom.GetPName[prop];
IF Rope.Match["Par*", propName, FALSE] THEN {
SetParm: PROC [wire: Core.Wire] = {
signal: Signal ← CreateBoundSignal[wire, circuit, cellType, flatWireToSignal, bindings, flatCell];
signal.parameters ← CONS[parm, signal.parameters];
};
parm: ROPENARROW[val];
IF wire.size#0 THEN [] ← CoreOps.VisitRootAtomics[wire, SetParm]
ELSE SetParm[wire];
};
};
CoreProperties.Enumerate[wire.properties, FindParms];
};
name: ROPE ← CoreOps.GetCellTypeName[cell];
primitive: Primitive ← NARROW[SymTab.Fetch[primitives, name].val];
clbMapProp: REF BOOLNARROW[CoreProperties.GetCellTypeProp[cell, $CLBMap]];
IF clbMapProp#NIL AND clbMapProp^ THEN CreateCLBMap[circuit, cellType, cell, instance, name, flatCell, flatWireToSignal, bindings, part2000, NIL];
IF cell.class=CoreClasses.recordCellClass THEN {
rct: CoreClasses.RecordCellType ← NARROW[cell.data];
FOR instanceIndex: NAT IN [0..rct.size) DO
seqData: SeqData ← NARROW[CoreProperties.GetCellInstanceProp[rct[instanceIndex], seqProp]];
IF seqData#NIL THEN {
child: Core.CellType ← rct[instanceIndex].type;
sct: Sequence.SequenceCellType ← NARROW[child.data];
recast: Core.CellType ← CoreOps.Recast[child];
recastRCT: CoreClasses.RecordCellType ← NARROW[recast.data];
FOR subInstanceIndex: NAT IN [0..recastRCT.size) DO
seqIndex: NAT ← seqData.startIndex + (subInstanceIndex*seqData.delta);
x, y: NAT ← 0;
SELECT seqData.axis FROM
ax => {x ← seqIndex; y ← seqData.orthogonalIndex};
ay => {x ← seqData.orthogonalIndex; y ← seqIndex};
ENDCASE => ERROR;
CoreProperties.PutCellInstanceProp[recastRCT[subInstanceIndex], $ParLoc, IO.PutFR["LOC=%g%g", IO.char[NumToLet[y]], IO.char[NumToLet[x]]]];
ENDLOOP;
};
ENDLOOP;
[] ← CoreOps.VisitWireSeq[NARROW[cell.data, CoreClasses.RecordCellType].internal, GetParms];
};
IF primitive=NIL THEN CoreFlat.NextBoundCellType[cell, target, flatCell, instance, index, parent, flatParent, data, bindings, FlattenCellType]
ELSE primitive.translate[circuit, cellType, cell, instance, name, flatCell, flatWireToSignal, bindings, part2000, primitive.data];
};
flatWireToSignal: RefTab.Ref ← RefTab.Create[hash: CoreFlat.FlatWireHash, equal: CoreFlat.FlatWireEqual];
part2000: BOOLFALSE;
circuit ← NEW[FlatCircuitRec];
circuit.nameTable ← SymTab.Create[];
circuit.partType ← NARROW[CoreProperties.InheritCellTypeProp[cellType, $XilinxPartType]];
IF circuit.partType=NIL THEN circuit.partType ← "2064PC68-33";
part2000 ← Rope.Fetch[circuit.partType]='2;
[] ← CoreOps.VisitWire[cellType.public, PushPins];
FlattenCellType[cell: cellType, bindings: CoreFlat.InitialBindingTable[cellType]];
};
clbMapPins: Pins ← PinRecListToPinList[LIST[
["A", "A", In],
["B", "B", In],
["C", "C", In],
["D", "D", In],
["E", "E", In],
["DI", "DI", In],
["CE", "CE", In],
["K", "K", In],
["RD", "RD", In],
["X", "X", In],
["Y", "Y", In]]];
CreateCLBMap: TranslateProc = {
MakeBind: PROC [wire: Core.Wire] = {
coreName: ROPENARROW[CoreProperties.GetWireProp[wire, $Pin]];
IF coreName=NIL AND NOT GlobalDelete[CoreOps.GetFullWireName[public, wire]] THEN ERROR;
IF coreName#NIL THEN bindlist ← CONS[[wire, coreName], bindlist];
};
bindlist: BindList ← NIL;
symbol: Symbol ← NIL;
public: Core.Wire ← cell.public;
CoreOps.VisitRootAtomics[public, MakeBind];
symbol ← MakeSymbol[CoreFlat.CellTypePathRope[root, flatCell], "CLBMAP", bindlist, clbMapPins, circuit, root, flatCell, flatWireToSignal, bindings];
GetParameters[symbol, instance];
};
trace: BOOLFALSE;
PruneFlattenedCircuit: PROC [circuit: FlatCircuit] RETURNS [pruned: RefTab.Ref] = {
CheckSignal: RefTab.EachPairAction = {
signal: Signal ← NARROW[key];
sensed, physical, driven, tristate: BOOLFALSE;
IF RefTab.Fetch[pruned, signal].found THEN RETURN;
[sensed, physical, driven, tristate] ← SensedPhysicalDrivenTristate[signal, pruned];
IF (NOT sensed OR NOT (driven OR tristate)) AND (signal.unbonded OR NOT physical) THEN {
IF NOT sensed AND (signal.unbonded OR NOT physical) THEN {
IF trace THEN TerminalIO.PutF["\npruning signal: %g", IO.rope[signal.name]];
IF NOT RefTab.Insert[pruned, signal, signal] THEN ERROR;
FOR cl: SymbolSignalPins ← signal.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF ssp.pin.direction=Out THEN IF NOT RefTab.Insert[pruned, ssp, ssp] THEN ERROR;
[] ← RefTab.Insert[symbolCheck, ssp.symbol, ssp.symbol];
ENDLOOP;
};
};
CheckSymbol: RefTab.EachPairAction = {
symbol: Symbol ← NARROW[key];
IF RefTab.Fetch[pruned, symbol].found THEN RETURN;
FOR cl: SymbolSignalPins ← symbol.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF ssp.pin.direction=Out AND NOT RefTab.Fetch[pruned, ssp].found THEN RETURN;
ENDLOOP;
IF trace THEN TerminalIO.PutF["\npruning symbol: %g", IO.rope[symbol.name]];
IF NOT RefTab.Insert[pruned, symbol, symbol] THEN ERROR;
FOR cl: SymbolSignalPins ← symbol.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF ssp.pin.direction=In THEN IF NOT RefTab.Insert[pruned, ssp, ssp] THEN ERROR;
[] ← RefTab.Insert[signalCheck, ssp.signal, ssp.signal];
ENDLOOP;
};
signalCheck: RefTab.Ref ← RefTab.Create[];
symbolCheck: RefTab.Ref ← RefTab.Create[];
pruned ← RefTab.Create[];
FOR sl: Signals ← circuit.signals, sl.rest UNTIL sl=NIL DO
IF NOT RefTab.Insert[signalCheck, sl.first, sl.first] THEN ERROR;
ENDLOOP;
UNTIL RefTab.GetSize[symbolCheck]=0 AND RefTab.GetSize[signalCheck]=0 DO
[] ← RefTab.Pairs[signalCheck, CheckSignal];
RefTab.Erase[signalCheck];
[] ← RefTab.Pairs[symbolCheck, CheckSymbol];
RefTab.Erase[symbolCheck];
ENDLOOP;
};
CheckMultipleDrivers: PROC [circuit: FlatCircuit] RETURNS [names: LORNIL]= {
Driven: TYPE = {Never, Always, Tristate};
FOR sl: Signals ← circuit.signals, sl.rest UNTIL sl=NIL DO
driven: Driven ← Never;
FOR cl: SymbolSignalPins ← sl.first.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF ssp.pin.direction=Out THEN {
IF driven=Always OR (driven=Tristate AND NOT ssp.pin.tristate) THEN {
names ← CONS[sl.first.name, names];
EXIT;
};
driven ← IF ssp.pin.tristate THEN Tristate ELSE Always;
};
ENDLOOP;
ENDLOOP;
};
WriteFlattenedCircuit: PROC [circuit: FlatCircuit, pruned: RefTab.Ref, fileName: ROPE] = {
WriteRecord: PROC [record: ROPE, parameters: LORNIL] = {
FOR rl: LOR ← parameters, rl.rest UNTIL rl=NIL DO
record ← Rope.Cat[record, ",", rl.first];
ENDLOOP;
IF Rope.Length[record]>=253 THEN ERROR;
IO.PutF[stream, "%g\n", IO.rope[record]];
};
stream: IO.STREAMFS.StreamOpen[fileName, $create];
WriteRecord["LCANET,2"];
WriteRecord[IO.PutFR["PROG,Core2XNF,1,%g", IO.time[]]];
WriteRecord[Rope.Cat["PART,", circuit.partType]];
FOR sl: Symbols ← circuit.symbols, sl.rest UNTIL sl=NIL DO
symbol: Symbol ← sl.first;
IF NOT RefTab.Fetch[pruned, symbol].found THEN {
WriteRecord[IO.PutFR["SYM,%g,%g", IO.rope[symbol.name], IO.rope[symbol.type]], symbol.parameters];
FOR cl: SymbolSignalPins ← symbol.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF NOT RefTab.Fetch[pruned, ssp.signal].found THEN {
pinRecord: ROPEIO.PutFR["PIN,%g,%g,%g", IO.rope[ssp.pin.xilinxName], IO.rope[directionNames[ssp.pin.direction]], IO.rope[ssp.signal.name]];
IF ssp.pin.invert OR ssp.parameters#NIL THEN pinRecord ← Rope.Cat[pinRecord, ","];
IF ssp.pin.invert THEN pinRecord ← Rope.Cat[pinRecord, ",INV"];
WriteRecord[pinRecord, ssp.parameters];
};
ENDLOOP;
FOR rl: LOR ← symbol.configs, rl.rest UNTIL rl=NIL DO
WriteRecord[Rope.Cat["CFG,", rl.first]];
ENDLOOP;
WriteRecord["END"];
};
ENDLOOP;
WriteRecord["PWR,1,$Vdd"];
WriteRecord["PWR,0,$Gnd"];
FOR sl: Signals ← circuit.signals, sl.rest UNTIL sl=NIL DO
signal: Signal ← sl.first;
sensed, physical, driven, tristate: BOOLFALSE;
[sensed, physical, driven, tristate] ← SensedPhysicalDrivenTristate[signal, pruned];
IF physical OR signal.parameters#NIL THEN {
signalRecord: ROPEIF physical THEN "EXT," ELSE "SIG,";
signalRecord ← Rope.Cat[signalRecord, signal.name];
IF physical THEN {
signalRecord ← Rope.Cat[signalRecord, SELECT TRUE FROM
signal.unbonded => ",U",
driven AND sensed => ",B",
driven AND tristate => ",T",
driven => ",O",
sensed => ",I",
ENDCASE => ERROR];
IF signal.pin#NIL OR signal.parameters#NIL THEN
signalRecord ← Rope.Cat[signalRecord, SELECT TRUE FROM
signal.pin#NIL => IO.PutFR[",,LOC=%g%g", IO.rope[IF Rope.Fetch[signal.pin] IN ['0..'9] THEN "P" ELSE NIL], IO.rope[signal.pin]],
signal.parameters#NIL => ",",
ENDCASE => ERROR];
};
WriteRecord[signalRecord, signal.parameters];
};
ENDLOOP;
WriteRecord["EOF"];
IO.Close[stream];
};
Interactive Command
WriteXNF: PROC [command: CDSequencer.Command] = {
XNFOne: CoreCDUser.EachRootCellTypeProc ~ {
md: LOR ← SaveCellType[root];
FOR lor: LOR ← md, lor.rest UNTIL lor=NIL DO
TerminalIO.PutF["\nMultiply driven: %g", IO.rope[lor.first]];
ENDLOOP;
TerminalIO.PutF["\nXNF written: %g", IO.rope[CoreOps.GetCellTypeName[root]]];
};
[] ← CoreCDUser.EnumerateSelectedCellTypes[command.design, XNFOne];
};
Utilities
NumToLet: PROC [n: NAT] RETURNS [l: CHAR] = {
l ← LOOPHOLE[LOOPHOLE['A, NAT] + n];
};
RegisterPrimitive: PUBLIC PROC [coreName: ROPE, proc: TranslateProc, data: REF ANYNIL] = {
primitive: Primitive ← NEW[PrimitiveRec ← [
translate: proc,
data: data]];
[] ← SymTab.Store[primitives, coreName, primitive];
};
PinRecListToPinList: PUBLIC PROC [pins: PinRecList] RETURNS [refPins: Pins ← NIL] = {
t: Pins ← NIL;
FOR pl: PinRecList ← pins, pl.rest UNTIL pl=NIL DO
t ← CONS[NEW[PinRec ← pl.first], t];
ENDLOOP;
FOR pl: Pins ← t, pl.rest UNTIL pl=NIL DO
refPins ← CONS[pl.first, refPins];
ENDLOOP;
};
MakeBindList: PUBLIC PROC [public: Core.Wire] RETURNS [bindlist: BindList ← NIL] = {
MakeBind: PROC [wire: Core.Wire] = {
coreName: ROPE ← CoreOps.GetFullWireName[public, wire];
IF NOT GlobalDelete[coreName] THEN bindlist ← CONS[[wire, coreName], bindlist];
};
CoreOps.VisitRootAtomics[public, MakeBind];
};
MakeSymbol: PUBLIC PROC [name, type: ROPE, bind: BindList, pins: Pins, circuit: FlatCircuit, root: Core.CellType, flatCell: CoreFlat.FlatCellTypeRec, flatWireToSignal, bindings: RefTab.Ref, parameters, configs: LORNIL] RETURNS [symbol: Symbol] = {
symbol ← CreateSymbol[circuit, name, type, parameters, configs];
FOR bl: BindList ← bind, bl.rest UNTIL bl=NIL DO
IF NOT GlobalDelete[bl.first.name] THEN FOR pl: Pins ← pins, pl.rest UNTIL pl=NIL DO
IF Rope.Equal[pl.first.coreName, bl.first.name] THEN {
signal: Signal ← CreateBoundSignal[bl.first.wire, circuit, root, flatWireToSignal, bindings, flatCell];
CreateSSP[symbol, pl.first, signal];
EXIT;
};
REPEAT FINISHED => ERROR;
ENDLOOP;
ENDLOOP;
};
AddSymbol: PUBLIC PROC [circuit: FlatCircuit, name, type: ROPE, bindings: PinSignals] RETURNS [symbol: Symbol] = {
symbol ← CreateSymbol[circuit, name, type];
FOR ps: PinSignals ← bindings, ps.rest UNTIL ps=NIL DO
CreateSSP[symbol, ps.first.pin, ps.first.signal];
ENDLOOP;
};
CreateSymbol: PROC [circuit: FlatCircuit, name, type: ROPE, parameters, configs: LORNIL] RETURNS [symbol: Symbol] = {
symbol ← NEW[SymbolRec];
symbol.name ← name;
symbol.type ← type;
symbol.parameters ← parameters;
symbol.configs ← configs;
circuit.symbols ← CONS[symbol, circuit.symbols];
};
CreateBoundSignal: PUBLIC PROC [wire: Core.Wire, circuit: FlatCircuit, root: Core.CellType, flatWireToSignal: RefTab.Ref, bindings: RefTab.Ref, flatCell: CoreFlat.FlatCellTypeRec] RETURNS [signal: Signal] = {
flatWire: CoreFlat.FlatWire ← NARROW[RefTab.Fetch[bindings, wire].val];
IF flatWire=NIL THEN {
flatWire ← NEW[CoreFlat.FlatWireRec];
flatWire.wire ← wire;
flatWire.flatCell ← flatCell;
};
signal ← NARROW[RefTab.Fetch[flatWireToSignal, flatWire].val];
IF signal=NIL THEN {
newFlatWire: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec ← flatWire^];
signal ← CreateSignal[circuit, SignalName[circuit, root, newFlatWire]];
IF NOT RefTab.Insert[flatWireToSignal, newFlatWire, signal] THEN ERROR;
signal.pin ← NARROW[CoreProperties.GetWireProp[flatWire.wire, $Pin]];
};
};
CreateSignal: PUBLIC PROC [circuit: FlatCircuit, name: ROPENIL] RETURNS [signal: Signal] = {
signal ← NEW[SignalRec];
IF name=NIL THEN name ← SignalName[circuit, NIL, NIL];
signal.name ← name;
circuit.signals ← CONS[signal, circuit.signals];
};
CreateSSP: PROC [symbol: Symbol, pin: Pin, signal: Signal] = {
ssp: SymbolSignalPin ← NEW[SymbolSignalPinRec];
ssp.symbol ← symbol;
ssp.pin ← pin;
ssp.signal ← signal;
symbol.connections ← CONS[ssp, symbol.connections];
signal.connections ← CONS[ssp, signal.connections];
};
vddName: ROPE ← "Vdd";
gndName: ROPE ← "Gnd";
GlobalDelete: PROC [name: ROPE] RETURNS [yes: BOOLFALSE] = {
yes ← Rope.Equal[name, vddName] OR Rope.Equal[name, gndName] OR Rope.Equal[name, "RosemaryLogicTime"];
};
Never call this procedure more than once with the same arguments! (except NIL)
SignalName: PROC [circuit: FlatCircuit, cellType: Core.CellType, flatWire: CoreFlat.FlatWire] RETURNS [name: ROPENIL] = {
CoreFlat uses Letter Digit / ( ) [ ] . *
LCA allows Letter Digit $ — - < > /
but / is reserved for path names and names are case insensitive
BuildNew: Rope.ActionType = {
c ← SELECT c FROM
'/ => '$,
'(, '[ => '<,
'), '] => '>,
'. => '-,
'* => '←,
ENDCASE => c;
IF c IN ['A..'Z] THEN name ← Rope.Concat[name, "$"];
name ← Rope.Concat[name, Rope.FromChar[c]];
};
ManufactureName: PROC = {
name ← IO.PutFR["N%g", IO.int[circuit.nameCount]];
circuit.nameCount ← circuit.nameCount + 1;
};
IF cellType=NIL OR flatWire=NIL THEN {
ManufactureName[];
IF NOT SymTab.Insert[circuit.nameTable, name, $Manufactured] THEN ERROR;
}
ELSE {
old: ROPE ← CoreFlat.WirePathRope[cellType, flatWire^];
shortName: ROPE ← CoreOps.GetShortWireName[flatWire.wire];
IF Rope.Length[old]>7 AND Rope.Equal[Rope.Substr[old, 0, 7], "public."] THEN old ← Rope.Substr[old, 7];
SELECT TRUE FROM
Rope.Equal[shortName, vddName] => name ← "$Vdd";
Rope.Equal[shortName, gndName] => name ← "$Gnd";
ENDCASE => [] ← Rope.Map[base: old, action: BuildNew];
IF Rope.Length[name]>16 THEN name ← Rope.Substr[name, Rope.Length[name]-16];
IF NOT SymTab.Insert[circuit.nameTable, name, flatWire] THEN {
ManufactureName[];
IF NOT SymTab.Insert[circuit.nameTable, name, flatWire] THEN ERROR;
};
};
};
SensedPhysicalDrivenTristate: PROC [signal: Signal, pruned: RefTab.Ref] RETURNS [sensed, physical, driven, tristate: BOOLFALSE] = {
FOR cl: SymbolSignalPins ← signal.connections, cl.rest UNTIL cl=NIL DO
ssp: SymbolSignalPin ← cl.first;
IF NOT RefTab.Fetch[pruned, ssp].found THEN {
sensed ← sensed OR ssp.pin.direction=In;
physical ← physical OR ssp.pin.physicalPin;
driven ← driven OR ssp.pin.direction=Out;
tristate ← tristate OR ssp.pin.tristate;
};
ENDLOOP;
};
GetParameters: PUBLIC PROC [symbol: Symbol, instance: CoreClasses.CellInstance] = {
FindParms: PROC [prop: ATOM, val: REF ANY] = {
propName: ROPE ← Atom.GetPName[prop];
IF Rope.Match["Par*", propName, FALSE] THEN {
parm: ROPENARROW[val];
symbol.parameters ← CONS[parm, symbol.parameters];
};
};
CoreProperties.Enumerate[instance.properties, FindParms];
};
CDCommandOps.RegisterWithMenu[$SpecialMenu, "WriteXNF", "Extract selected cells and write xnf files", $WriteXNF, WriteXNF];
END.