Latch.mesa,
Copyright c 1985 by Xerox Corporation. All rights reserved.
RemoveButton CD; CreateButton L Compile Latch; Run Latch
Last Edited by Curry, April 11, 1985 8:57:00 pm PST
DIRECTORY
Basics,
CD,
CDCells USING [CreateEmptyCell],
CDDirectory,
CDRects USING [CreateRect],
CMos,
CMosContacts,
CMosTransistors,
IFUPW,
PW,
PWBasics,
Rope;
Latch: CEDAR PROGRAM
IMPORTS
CDCells, CDRects, CMosContacts, CMosTransistors, CMos, IFUPW, PW, PWBasics, Rope
EXPORTS IFUPW =
BEGIN OPEN IFUPW;
rngByte: INT = 4;
rngBit: INT = 8;
ioRestRef: List ← LIST["VDD", "GND"];
IFULatchRow: PUBLIC PROC [ -- Assumes byte interleaving (rngBit cells of rngByte bits)
design:  CD.Design,
ctlNames:  List,
ctlSelects:  LIST OF REF,
enable:  PW.ROPE,
ioTop:   LIST OF REF,
output:  LIST OF REF,
ioBot:   LIST OF REF,
sequential: BOOLFALSE ]
RETURNS [cell: PW.ObjName] = {
ctlSelects, ioTop, output and ioBot share a common format.
IF a top level item is a rope then a byte and bit index are appended to each rope.
IF a top level item is a List of rope then a bit index is appended to each rope.
IF a top level item is a List of List of rope then each rope is used as is.
cells: LIST OF PW.ObjName;
FOR ii: INT DECREASING IN [0..rngByte*rngBit) DO
index: INT ← IF sequential THEN ii MOD rngBit ELSE ii / rngByte;
byte: INT ← IF sequential THEN ii / rngBit ELSE ii MOD rngByte;
cells ← CONS[ MuxLatch[
design: design,
ctlNames: ctlNames,
ctlSelects: ExpandList[byte, index, ctlSelects, ioRestRef],
enable: enable,
ioTop:  ExpandList[byte, index, ioTop,  ioRestRef],
output: ExpandList[byte, index, output,  ioRestRef],
ioBot:  ExpandList[byte, index, ioBot,   ioRestRef]], cells];
ENDLOOP;
RETURN[PW.AbutListX[cells]]};
A multiplexed latch has up to 6 vertical metal input/output wires and built-in inputs:
"GND" and "VDD".
Any number of horizontal poly control wires can be included but each can select at most one of the possible inputs.
A noMux latch is implied when only one (dummy) horizontal poly control line is specified. In this case there is space for 7 vertical metal input/output wires.
Output lines are specified by the output list.
NIL in either the top or bottom lists will preserve a space at the appropiate location.
ctlNames and ctlSelects are ordered: LIST[bottom...top].
top and bot are ordered: LIST[left...right].
MuxLatch: PROC [
design: CD.Design,
ctlNames: List,
ctlSelects: List,
enable: PW.ROPE,
ioTop:  List,
output: List,
ioBot:  List]
RETURNS [PW.ObjName] = {
AddMux: PROC [index: Location] = {
patch: CD.ObPtr ← CDRects.CreateRect[[8,8], CMos.ndif];
dif: CD.ObPtr ← CDRects.CreateRect[[(range.x+ioRestLength-index.x)*pitch.x,8], CMos.ndif];
xstr: CD.ObPtr ← CMosTransistors.CreateTransistor[w: 8, l: 4, difLev: CMos.ndif];
ctct: CD.ObPtr ← CMosContacts.CreateDifCon[l: 8, difLev: CMos.ndif];
half: INT  ← IF (index.y MOD 2)=0 THEN pitch.y/2 ELSE -pitch.y/2;
loc: Location ← [index.x*pitch.x, (mIBias+index.y)*pitch.y-2];
IF range.y=1 -- noMux Latch
THEN [] ← PWBasics.IncludeApplication[cell, patch, [loc.x,  loc.y]]
ELSE [] ← PWBasics.IncludeApplication[cell, xstr,  [loc.x-4, loc.y-2]];
[] ← PWBasics.IncludeApplication[cell, dif, [loc.x,  loc.y+half]];
[] ← PWBasics.IncludeApplication[cell, ctct, [loc.x,  loc.y-half]] };
Constants
pitch:   Size = [mPitch, pBigPitch];
mIBias:  INT = 5;
oIBias:  INT = 2;
topTail:  INT = 4;
botTail:  INT = 2; -- Need tails to prevend pins from appearing on two sides
cell:    CD.ObPtr  ← CDCells.CreateEmptyCell[];
range: Size ← [MAX[ListLength[ioTop],ListLength[ioBot]], ListLength[ctlNames]+mIBias];
ioRest:   List ← ioRestRef;
ioRestLength: CARDINAL ← ListLength[ioRest];
noMux:   BOOL ← range.y=1;
noIORest:   BOOLFALSE;
blank:    BOOL ← ctlSelects=NIL OR ListAllNil[ctlSelects];
FOR ioRest ← ioRest, ioRest.rest WHILE ioRest#NIL DO-- are VDD or GND used
IF ListItemIndexMax[ctlSelects, ioRest.first]#-1 THEN EXIT ENDLOOP;
IF ioRest#NIL
THEN ioRest ← ioRestRef -- at least one is used so do regular case
ELSE noIORest ← TRUE;
IF noIORest THEN {ioRest ← NIL; ioRestLength𡤀}; -- noMux/noIORest latch
Primary Metal wires
FOR i: INT IN [0..range.x) DO
size: Size  ← [6, 0];
loc: Location ← [i*pitch.x, 0];
tIO: PW.ROPE ← ListIndexItem[ioTop, i];
bIO: PW.ROPE ← ListIndexItem[ioBot, i];
bY: INT ← ListItemIndexMax[ctlSelects, bIO];
tY:  INT ← ListItemIndexMin[ctlSelects, tIO];
bY ← IF bY#-1
THEN bY+mIBias ELSE IF ListItemIndexMax[output, bIO]#-1 THEN oIBias ELSE -1;
tY ← IF ListItemIndexMin[output, tIO]#-1
THEN oIBias ELSE IF tY#-1 THEN tY+mIBias ELSE range.y+mIBias;
IF tY >=mIBias THEN tY ← (tY -((tY +1-mIBias) MOD 2));
IF bY >=mIBias THEN bY ← (bY -((bY +1-mIBias) MOD 2));
IF Rope.Equal[tIO, bIO]
THEN tY𡤋Y𡤀
ELSE IF tY<=bY THEN ERROR;
size.y ← (range.y-tY) *pitch.y - pitch.y/2 + topTail;
loc.y ← tY  *pitch.y+6;
IF tIO#NILTHEN AddRet[cell:cell, size:size, loc:loc, level: CMos.met];
size.y ← bY *pitch.y+6 + botTail;
loc.y ← - botTail;
IF bIO#NILTHEN AddRet[cell:cell, size:size, loc:loc, level: CMos.met];
ENDLOOP;
IORest metal wires
IF NOT(noIORest OR blank) THEN
FOR ix: INT IN [0..ioRestLength) DO
bY: INT ← ListItemIndexMax[ctlSelects, ListIndexItem[ioRest, ix]];
IF bY=-1 THEN LOOP;
AddRet[ cell: cell, level: CMos.met,
loc: [(range.x+ix) * pitch.x, mIBias    * pitch.y - 2],
size: [8,       (bY+(bY MOD 2)) * pitch.y - pitch.y/2 + topTail]];
ENDLOOP;
Collection node metal wire
IF NOT(noMux OR blank) THEN {
bY: INT ← range.y-mIBias-1;
AddRet[ cell: cell, level: CMos.met,
loc: [(range.x+ioRestLength) * pitch.x, (mIBias+1) * pitch.y-2],
size: [8,         (bY-(bY MOD 2)) * pitch.y]]};
Insert Metal Pins
FOR i: INT IN [0..range.x) DO
tName: PW.ROPE ← ListIndexItem[ioTop, i];
bName: PW.ROPE ← ListIndexItem[ioBot, i];
size: Size  ← [6, 6];
loc: Location ← [i*pitch.x, range.y*pitch.y-2+topTail-size.y];
IF tName #NIL THEN PutPin [cell, size, loc,    CMos.met, tName];
IF bName #NIL THEN PutPin [cell, size, [loc.x, -botTail], CMos.met, bName];
ENDLOOP;
Enable and power Wires
AddRet[cell:cell, size:[cellWidth, 8], loc:[-leftTail, 70],  level: CMos.met2];
AddRet[cell:cell, size:[cellWidth,18], loc:[-leftTail, 42],  level: CMos.met2];
AddRet[cell:cell, size:[cellWidth,20], loc:[-leftTail, 0],  level: CMos.met2];
AddRet[cell:cell, size:[cellWidth, 4], loc:[-leftTail, 0],  level: CMos.pol];
Insert Enable and power Pins
PutPin [cell, [4, 8], [-leftTail,     70], CMos.met2, "VBB"];
PutPin [cell, [4, 8], [cellWidth-leftTail-4, 70], CMos.met2, "VBB"];
PutPin [cell, [4,18], [-leftTail,     42], CMos.met2, "VDD"];
PutPin [cell, [4,18], [cellWidth-leftTail-4, 42], CMos.met2, "VDD"];
PutPin [cell, [4,20], [-leftTail,     0],  CMos.met2, "GND"];
PutPin [cell, [4,20], [cellWidth-leftTail-4, 0],  CMos.met2, "GND"];
PutPin [cell, [4, 4], [-leftTail,     0],  CMos.pol,  enable];
PutPin [cell, [4, 4], [cellWidth-leftTail-4, 0],  CMos.pol,  enable];
Poly wires and latch input node contacts
FOR i: INT IN [0..range.y-mIBias) DO
cName: PW.ROPE ← ListIndexItem[ctlNames, i];
ctct: CD.ObPtr ← CMosContacts.CreateDifCon[l: 8, difLev: CMos.ndif];
size: Size  ← [cellWidth,     4];
loc: Location ← [-leftTail, (mIBias+i)*pitch.y ];
IF NOT blank AND (i MOD 2)=0 THEN [] ← PWBasics.IncludeApplication
[cell, ctct, [(range.x+ioRestLength)*pitch.x, loc.y+6]];
IF noMux THEN EXIT; -- noMux latch
AddRet[cell:cell, size:size, loc:loc, level:CMos.pol];
Insert Pins
IF cName #NIL THEN {
PutPin [cell, [4, 4], loc,        CMos.pol, cName];
PutPin [cell, [4, 4], [cellWidth-leftTail-4, loc.y], CMos.pol, cName]};
ENDLOOP;
Mux connections
FOR ctlIndex: INT IN [0..range.y-mIBias) DO
ioIndex: INT  ← -1;
ioItem: PW.ROPE ← ListIndexItem[ctlSelects, ctlIndex];
IF ioItem = NIL THEN LOOP;
IF ioIndex < 0 THEN ioIndex ← ListItemIndexMax[ioTop, ioItem];
IF ioIndex < 0 THEN ioIndex ← ListItemIndexMax[ioBot, ioItem];
IF ioIndex < 0 THEN {
ioIndex ← ListItemIndexMax[ioRest, ioItem];
IF ioIndex < 0 THEN LOOP;
ioIndex ← ioIndex+range.x};
AddMux[index: [ioIndex, ctlIndex]];
ENDLOOP;
Ouput connections
IF NOT blank THEN FOR i: INT IN [0..range.x) DO
IF ListItemIndexMax[output, ListIndexItem[ioTop, i]]#-1
OR ListItemIndexMax[output, ListIndexItem[ioBot, i]]#-1
THEN {
loc: Location ← [i*pitch.x, oIBias*pitch.y+4];
size: Size  ← [(range.x-i+(IF noIORest THEN 0 ELSE 1))*pitch.x, 4];
[] ← PWBasics.IncludeApplication[cell, CMosContacts.CreatePolyCon[l: 8], loc];
AddRet[cell:cell, size:size, loc:[loc.x, loc.y+2], level:CMos.pol]};
ENDLOOP;
Latch Cell
IF NOT blank THEN [] ← PWBasics.IncludeApplication[
cell:  cell,
subcell: PWBasics.ObjFromName[design, (IF noIORest
THEN "SimLatch"
ELSE "MuxLatch")],
location: [range.x*pitch.x, 0]];
PWBasics.RepositionCell[design, cell];
RETURN[PWBasics.NameFromObj[cell]]};
IFUTriDriverRow: PUBLIC PROC [ -- Assumes byte interleaving (rngBit cells of rngByte bits)
design:  CD.Design,
ioTop:   LIST OF REF,
inSelect:  LIST OF REF, -- exactly one element
enable:  PW.ROPE,
disable:  PW.ROPE,
outSelect:  LIST OF REF,
ioBot:   LIST OF REF,
sequential: BOOLFALSE ]
RETURNS [cell: PW.ObjName] = {
ctlSelects, ioTop, output and ioBot share a common format.
IF a top level item is a rope then a byte and bit index are appended to each rope.
IF a top level item is a List of rope then a bit index is appended to each rope.
IF a top level item is a List of List of rope then each rope is used as is.
cells: LIST OF PW.ObjName;
FOR ii: INT DECREASING IN [0..rngByte*rngBit) DO
index: INT ← IF sequential THEN ii MOD rngBit ELSE ii / rngByte;
byte: INT ← IF sequential THEN ii / rngBit ELSE ii MOD rngByte;
cells ← CONS[ TriDriver[
design: design,
ioTop:  ExpandList[byte, index, ioTop, ioRestRef],
inSelect: ExpandList[byte, index, inSelect, ioRestRef],
enable: enable,
disable: disable,
outSelect: ExpandList[byte, index, outSelect],
ioBot:  ExpandList[byte, index, ioBot, ioRestRef]], cells];
ENDLOOP;
RETURN[PW.AbutListX[cells]]};
TriDriver: PROC [
design:  CD.Design,
ioTop:   List,
inSelect:  List,
enable:  PW.ROPE,
disable:  PW.ROPE,
outSelect:  List,
ioBot:   List ]
RETURNS [PW.ObjName] = {
Constants
topTail:  INT = 8;
botTail:  INT = 2;
pitch:   Size = [mPitch, pBigPitch];
cell:    CD.ObPtr ← CDCells.CreateEmptyCell[];
rangeX:   INTMAX[ListLength[ioTop],ListLength[ioBot]];
blank:    BOOL ← inSelect=NIL OR inSelect.first=NIL;
gnd:    BOOL ← Rope.Compare["GND", inSelect.first]=equal;
vdd:    BOOL ← Rope.Compare["VDD", inSelect.first]=equal;
Metal wires
FOR i: INT IN [0..rangeX) DO
Add: PROC[bindex, tindex, toptail, bottail: INT] = {
loc.y ← bindex*pitch.y - bottail;
size.y ← (tindex-bindex)*pitch.y + toptail + bottail;
IF size.y>0 THEN AddRet[cell:cell, size:size, loc:loc, level: CMos.met]};
size: Size  ← [6, 0];
loc: Location ← [i*pitch.x, 0];
tIO: PW.ROPE ← ListIndexItem[ioTop,  i];
bIO: PW.ROPE ← ListIndexItem[ioBot,  i];
tIn: BOOL  ← ListItemIndexMax[inSelect, tIO]#-1;
bIn: BOOL  ← ListItemIndexMax[inSelect, bIO]#-1;
tOut: BOOL  ← ListItemIndexMax[outSelect, tIO]#-1;
bOut: BOOL  ← ListItemIndexMax[outSelect, bIO]#-1;
IF tIO#NIL THEN SELECT TRUE FROM
Rope.Equal[tIO, bIO] => {Add[0, 4, topTail, botTail]};
tOut      => {Add[1, 4, topTail, 0]};
tIn      => {Add[3, 4, topTail, 0]; IF bIn THEN ERROR}
ENDCASE     => ERROR; -- top not connected anywhere ABORT;
IF bIO#NIL THEN SELECT TRUE FROM
Rope.Equal[bIO, tIO] => LOOP;
bIn      => {Add[0, 3, 0, botTail]};
bOut      => {Add[0, 1, 0, botTail]};
ENDCASE     => ERROR; -- bottom not connected anywhere ABORT
ENDLOOP;
Insert Metal Pins
FOR i: INT IN [0..rangeX) DO
tName: PW.ROPE ← ListIndexItem[ioTop, i];
bName: PW.ROPE ← ListIndexItem[ioBot, i];
size: Size  ← [6, 6];
loc: Location ← [i*pitch.x, 4*pitch.y+topTail-size.y];
IF tName #NIL THEN PutPin [cell, size, loc,    CMos.met, tName];
IF bName #NIL THEN PutPin [cell, size, [loc.x, -botTail], CMos.met, bName];
ENDLOOP;
Enables and power Wires
AddRet[cell:cell, size:[cellWidth, 4], loc:[-leftTail, 64], level: CMos.pol];
AddRet[cell:cell, size:[cellWidth,16], loc:[-leftTail, 48], level: CMos.met2];
AddRet[cell:cell, size:[cellWidth,16], loc:[-leftTail, 12], level: CMos.met2];
AddRet[cell:cell, size:[cellWidth, 4], loc:[-leftTail, 0],  level: CMos.pol];
Insert Enable and power Pins
PutPin [cell,  [4, 4],  [-leftTail,    64], CMos.pol,  enable];
PutPin [cell,  [4, 4],  [cellWidth-leftTail-4, 64], CMos.pol,  enable];
PutPin [cell,  [4,16],  [-leftTail,    48], CMos.met2, "VDD"];
PutPin [cell,  [4,16],  [cellWidth-leftTail-4, 48], CMos.met2, "VDD"];
PutPin [cell,  [4,16],  [-leftTail,    12], CMos.met2, "GND"];
PutPin [cell,  [4,16],  [cellWidth-leftTail-4, 12], CMos.met2, "GND"];
PutPin [cell,  [4, 4],  [-leftTail,     0], CMos.pol,  disable];
PutPin [cell,  [4, 4],  [cellWidth-leftTail-4,  0], CMos.pol,  disable];
Input connections
IF NOT (blank OR vdd OR gnd) THEN FOR i: INT IN [0..rangeX) DO
IF ListItemIndexMin[inSelect, ListIndexItem[ioTop, i]]#-1
OR ListItemIndexMax[inSelect, ListIndexItem[ioBot, i]]#-1
THEN {
loc: Location ← [  i  * pitch.x, 3*pitch.y];
size: Size  ← [(rangeX-i) * pitch.x,  4];
[] ← PWBasics.IncludeApplication
[cell, CMosContacts.CreatePolyCon[l: 8], [loc.x, loc.y-2]];
AddRet[cell:cell, size:size, loc:loc, level:CMos.pol]};
ENDLOOP;
Ouput connections
IF NOT blank THEN FOR i: INT IN [0..rangeX) DO
IF ListItemIndexMin[outSelect, ListIndexItem[ioTop, i]]#-1
OR ListItemIndexMax[outSelect, ListIndexItem[ioBot, i]]#-1
THEN {
loc: Location ← [  i  * pitch.x, 1*pitch.y];
size: Size  ← [(rangeX-i) * pitch.x,  4];
[] ← PWBasics.IncludeApplication
[cell, CMosContacts.CreatePolyCon[l: 8], [loc.x, loc.y-2]];
AddRet[cell:cell, size:size, loc:loc, level:CMos.pol]};
ENDLOOP;
Cell
IF NOT blank THEN [] ← PWBasics.IncludeApplication[
cell:  cell,
subcell: PWBasics.ObjFromName[design, (SELECT TRUE FROM
gnd => "TriStateGND", vdd => "TriStateVDD", ENDCASE => "TriStateDriver")],
location: [rangeX*pitch.x, 0]];
PWBasics.RepositionCell[design, cell];
RETURN[PWBasics.NameFromObj[cell]]};
TDrive: PW.UserProc = { RETURN[ TriDriver[
design:  design,
ioTop:  LIST["XB", "Alpa", "Beta", "Pass",  "In"],
inSelect: LIST["In"],
enable: "PhB",
disable: "PhB'",
outSelect: LIST["XB"],
ioBot:  LIST["XB", "Alpa", "Beta", "Pass"]] ]};
MuxLatch6x6x6: PW.UserProc = { RETURN[ MuxLatch[
design:  design,
ctlNames: LIST["Reset", "Hold",  "Set",   "ASel", "Adv", "RdXBus"],
ctlSelects: LIST["GND", "Last",  "VDD",  "Alpa",  "In",  "XB"],
enable: "PhB",
ioTop:  LIST["XB", "Alpa", "Beta", "Pass", "Out",  "In"],
output:  LIST["Out"],
ioBot:  LIST["XB", "Alpa", "Beta", "Pass", "Out",  "Last"]] ]};
MuxLatch6x7x6: PW.UserProc = { RETURN[ MuxLatch[
design:  design,
ctlNames: LIST["Reset", "Hold",  "Set",   "BSel", "ASel", "Adv", "RdXBus"],
ctlSelects: LIST["GND", "Last",  "VDD",  "Beta",  "Alpa",  "In",  "XB"],
enable: "PhB",
ioTop:  LIST["XB", "Alpa", "Beta", "Pass", "Out",  "In"],
output:  LIST["Out"],
ioBot:  LIST["XB", "Alpa", "Beta", "Pass", "Out",  "Last"]] ]};
Latch6x6: PW.UserProc = { RETURN[ MuxLatch[
design:  design,
ctlNames: LIST["Reset", "Hold",  "Set",   "BSel", "ASel", "Adv", "RdXBus"],
ctlSelects: LIST["In"], -- one ctlSelect => plain latch
enable: "PhB",
ioTop:  LIST["XB", "Alpa", "Beta", "Pass", "In",   "Out2"],
output: LIST["Out",  "Out2"],
ioBot:  LIST["XB", "Alpa", "Beta", "Pass", "Out",  "Out2"]] ]};
Blank: PW.UserProc = { RETURN[ MuxLatch[
design:  design,
ctlNames: LIST["Reset", "Hold",  "Set",   "BSel", "ASel", "Adv", "RdXBus"],
ctlSelects: NIL, -- => no Latch => output = NIL
enable: "PhB",
ioTop:  LIST["XB", "Alpa", "Beta"],
output: NIL,
ioBot:  LIST["XB", "Alpa", "Beta"]] ]};
IFULatchRowTest: PW.UserProc = { RETURN[ IFULatchRow[
design:  design,
enable: "PhB",
ctlNames: LIST["Reset", "Set",   "Hold",   "Adv",  "RdXBus", "ASel", "BSel"],
ctlSelects: LIST["GND", "VDD", "PCPipe2AB.#", "PCPipe1AB.#", "XB.#", "Alpha.3", "Beta.2"],
ioTop:  LIST["XB.#", "PCPipe1BA.#", "PCPipe1AB.#", "Gamma.1", "Beta.2", "Alpha.3"],
output: LIST["PCPipe1BA.#"],
ioBot:  LIST["XB.#", "PCPipe1BA.#", "PCPipe2AB.#", "Gamma.1", "Beta.2", "Alpha.3"] ]
]};
IFULatchRows: PW.UserProc = {
cell1: PW.ObjName ← IFULatchRow[
design:  design,
enable: "PhA",
ctlNames: LIST["Reset", "Set",   "Hold",   "Adv",  "RdXBus", "ASel", "BSel"],
ctlSelects: LIST["GND", "VDD", "PCPipe1BA.#", "PCPipe0BA.#", "XB.#", "Alpha.3", "Beta.2"],
ioTop:  LIST["XB.#", "PCPipe0BA.#", "PCPipe1AB.#", "Gamma.1", "Beta.2", "Alpha.3"],
output: LIST["PCPipe1AB.#"],
ioBot:  LIST["XB.#", "PCPipe1BA.#", "PCPipe1AB.#", "Gamma.1", "Beta.2", "Alpha.3"] ];
cell2: PW.ObjName ← IFULatchRow[
design:  design,
enable: "PhB",
ctlNames: LIST["Reset", "Set",   "Hold",   "Adv",  "RdXBus", "ASel", "BSel"],
ctlSelects: LIST["GND", "VDD", "PCPipe2AB.#", "PCPipe1AB.#", "XB.#", "Alpha.3", "Beta.2"],
ioTop:  LIST["XB.#", "PCPipe1BA.#", "PCPipe1AB.#", "Gamma.1", "Beta.2", "Alpha.3"],
output: LIST["PCPipe1BA.#"],
ioBot:  LIST["XB.#", "PCPipe1BA.#", "PCPipe2AB.#", "Gamma.1", "Beta.2", "Alpha.3"] ];
RETURN[PW.AbutY[cell2, cell1]]};
PW.Register[MuxLatch6x6x6,  "Latch6x6x6"];
PW.Register[MuxLatch6x7x6,  "Latch6x7x6"];
PW.Register[Latch6x6,    "Latch6x6"];
PW.Register[Blank,     "Blank"];
PW.Register[IFULatchRowTest, "IFULatchRowTest"];
PW.Register[IFULatchRows,  "IFULatchRows"];
PW.Register[TDrive,     "TDrive"];
END.