/* * DBtcontact.c -- * * Management of contacts. * This file makes a distinction between the following two terms: * * Layer -- Logical type as specified in "types" section of .tech * file. A layer may consist of many TileTypes, as is the * case when it is a contact. * Type -- TileType stored in a tile * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/database/DBtcontact.c,v 1.3 2008/09/05 13:56:25 tim Exp $"; #endif /* not lint */ #include #include #include #include "utils/magic.h" #include "utils/geometry.h" #include "utils/utils.h" #include "utils/malloc.h" #include "tiles/tile.h" #include "utils/hash.h" #include "database/database.h" #include "database/databaseInt.h" #include "utils/tech.h" #include "textio/textio.h" /* type-to-bitmask conversion (1 bit per entry) */ TileTypeBitMask DBLayerTypeMaskTbl[NT]; /* Filled in after contact types have been generated */ TileTypeBitMask DBPlaneTypes[PL_MAXTYPES]; TileTypeBitMask DBHomePlaneTypes[PL_MAXTYPES]; PlaneMask DBTypePlaneMaskTbl[NT]; /* --------------------- Data local to this file ---------------------- */ /* Table of the properties of all layers */ LayerInfo dbLayerInfo[NT]; /* Array of pointers to the entries in above table for contacts only */ LayerInfo *dbContactInfo[NT]; int dbNumContacts; /* Forward declaration */ void dbTechMatchResidues(); void dbTechAddStackedContacts(); int dbTechAddOneStackedContact(); /* * ---------------------------------------------------------------------------- * * DBTechInitContact -- * * Mark all types as being non-contacts initially. * * Results: * None. * * Side effects: * Initializes dbLayerInfo. * * ---------------------------------------------------------------------------- */ void DBTechInitContact() { TileType t; LayerInfo *lp; for (t = 0; t < TT_MAXTYPES; t++) { lp = &dbLayerInfo[t]; lp->l_type = t; lp->l_isContact = FALSE; lp->l_pmask = 0; TTMaskZero(&lp->l_residues); TTMaskSetOnlyType(&DBLayerTypeMaskTbl[t], t); } dbNumContacts = 0; } /* * ---------------------------------------------------------------------------- * * DBTechAddContact -- * * Add the definition of a new contact type. * The syntax of each line in the "contact" section is: * * contactType res1 res2 [res3...] * * where res1, res2, res3, etc. are the residue types on the planes * connected by the contact. The home plane of the contact (the * plane specified in the "types" section for the type contactType) * must be the lowest-numbered plane in the list of residues. Each * listed residue must be on a different plane. There are no other * restrictions on contacts. * * Magic-7.3 additional syntax: * * stackable type1 [type2 [alias12] [type3 [alias13] ...]] * * where type1 and type2 are contact types, allows type1 and type2 * to be drawn on top of one other by generating an extra contact * type to represent the union of type1 and type2 on the shared * plane. If more than two types are specified, then type1 will be * set to be stackable with each of the indicated types. If only * type1 is specified, then type1 will be made stackable with all * other (existing) contact types. Wherever a layer name exists * after a known type that is not a known layer name, it is * considered to be an alias name for the stacked type. e.g., * "stackable pc via pm12contact" is valid if "pc" and "via" are * defined types. Thie provides compatibility with layout files * created with technology files that explicitly define stacked * contact types, especially those from magic-7.2 and earlier * versions. * * stackable * * allows all contact types to stack, where applicable. This * statement overrides any other "stackable" statement. * * Notes: * All indicated types must have been declared in the "contact" * section prior to use of the "stackable" keyword. The 1- and * 2-argument variants will create stacking types with all * previously-declared contact types. * * Expanded syntax: * Now allows the section "image" with statements "contact" and * "device". Statements beginning with the layer name are * backwardly-compatible with the original syntax. "contact" * statements are equivalent to the original syntax after skipping * the keyword "contact". "device" statements have the same * syntax as the "contact" statement but describe a composite * image such as a transistor or capacitor formed by layers on * different planes. * * Results: * FALSE on error, TRUE if successful. * * Side effects: * Adds the definition of a new contact type. * * ---------------------------------------------------------------------------- */ bool DBTechAddContact(sectionName, argc, argv) char *sectionName; int argc; char *argv[]; { TileType contactType; int nresidues; if ((contactType = DBTechNameType(*argv)) < 0) { if (!strcmp(*argv, "contact") || !strcmp(*argv, "device")) { argc--; argv++; if ((contactType = DBTechNameType(*argv)) < 0) { DBTechNoisyNameType(*argv); return FALSE; } } else if (!strcmp(*argv, "stackable")) { TileType stackType, newType = -1; LayerInfo *lim, *lin; if (argc == 1) dbTechAddStackedContacts(); else { contactType = DBTechNoisyNameType(*++argv); if (contactType < 0) return FALSE; else if (argc == 2) { int n, result; lim = &dbLayerInfo[contactType]; for (n = 0; n < dbNumContacts; n++) { lin = dbContactInfo[n]; if (lim == lin) continue; result = dbTechAddOneStackedContact(lim->l_type, lin->l_type); if (result == -3) return FALSE; /* overran tiletype maximum number */ } } else { TileType lastType = TT_SPACE; char *primary; while (--argc > 1) { stackType = DBTechNameType(*++argv); if (stackType >= 0) { newType = dbTechAddOneStackedContact(contactType, stackType); if (newType == -1) TechError("Contact types %s and %s do not stack\n", DBTypeLongNameTbl[contactType], DBTypeLongNameTbl[stackType]); lastType = stackType; } else if (lastType >= TT_SPACE) { /* (*argv) becomes an alias name for layer newType */ if (newType < 0) TechError("Contact type %s unknown or contact " "missing in stackable statement\n", *argv); else DBTechAddNameToType(*argv, newType, FALSE); lastType = TT_SPACE; } else { DBTechNoisyNameType(*argv); lastType = TT_SPACE; } } } } return TRUE; } else { DBTechNoisyNameType(*argv); return FALSE; } } /* Read the contact residues and check them for validity */ nresidues = dbTechContactResidues(--argc, ++argv, contactType); if (nresidues < 0) return FALSE; /* Remember this as a paintable contact */ dbContactInfo[dbNumContacts++] = &dbLayerInfo[contactType]; return TRUE; } /* * ---------------------------------------------------------------------------- * dbTechAddStackedContacts -- * * Generate new contact types where existing contact types share a * residue. These contact types will exist only on the planes * shared between the two contact types. This method allows contacts * to be stacked without requiring a declaration of every combination * in the techfile. * * When searching for contact types with shared planes, we want to * make sure that no existing contact exactly matches the stacked * type, in case stacked contacts are explicitly called out in the * tech file (e.g., pm12contact). * * Results: * None. * * Side effects: * Adds to the tiletype database. * * ---------------------------------------------------------------------------- */ void dbTechAddStackedContacts() { int m, n; LayerInfo *lim, *lin; int dbNumUserContacts = dbNumContacts; int result; for (m = 0; m < dbNumUserContacts; m++) { lim = dbContactInfo[m]; for (n = m + 1; n < dbNumUserContacts; n++) { lin = dbContactInfo[n]; result = dbTechAddOneStackedContact(lim->l_type, lin->l_type); if (result == -3) return; /* overran tiletype maximum number */ } } /* Diagnostic */ /* fprintf(stderr, "DBNumUserLayers = %d, DBNumTypes = %d\n", DBNumUserLayers, DBNumTypes); fflush(stderr); */ } /* * ---------------------------------------------------------------------------- * dbTechAddOneStackedContact -- * * Generate one new stacked contact type representing the union of * types "type1" and "type2" on their shared plane(s) (normally * one, but not necessarily). * * Results: * tile type of new stacked contact if successful, -1 if stacking * not allowed, -2 if a contact type already exists which stacks * type1 and type2, or -3 on overrun of the total number of layers. * ---------------------------------------------------------------------------- */ int dbTechAddOneStackedContact(type1, type2) TileType type1, type2; { LayerInfo *lim, *lin, *lp; TileTypeBitMask ttshared, ttall, mmask; TileType stackedType, sres; lim = &dbLayerInfo[type1]; lin = &dbLayerInfo[type2]; /* Both types must be contacts */ if (!lim->l_isContact || !lin->l_isContact) return -1; /* Contacts do not stack if they share more than one plane */ /* if (lim->l_pmask == lin->l_pmask) return -1; */ if (((lim->l_pmask & lin->l_pmask) & ((lim->l_pmask & lin->l_pmask) - 1)) != 0) return -1; TTMaskAndMask3(&ttshared, &lim->l_residues, &lin->l_residues); if (!TTMaskEqual(&ttshared, &DBZeroTypeBits)) { /* Find if there exists an image with the same residue */ /* mask as the combination of these two contact types. */ TTMaskZero(&ttall); TTMaskSetMask3(&ttall, &lim->l_residues, &lin->l_residues); dbTechMatchResidues(&ttall, &mmask, TRUE); if (!TTMaskEqual(&mmask, &DBZeroTypeBits)) return -2; /* Contact type exists, so don't create one. */ /* Also check if there is a stacking type made of these contact */ /* images. If we already have one, don't re-make it. */ else if (DBTechFindStacking(type1, type2) != -1) return -2; /* Stacking type exists, so don't create one. */ /* All clear to set the residue bitmask for this contact type */ /* Diagnostic */ /* fprintf(stderr, "Stackable %s and %s\n", DBTypeLongName(lim->l_type), DBTypeLongName(lin->l_type)); fflush(stderr); */ stackedType = dbTechNewStackedType(lim->l_type, lin->l_type); /* Error condition (usually, reached max. no. tile types) */ if (stackedType < 0) return -3; /* fill in layer info */ lp = &dbLayerInfo[stackedType]; lp->l_isContact = TRUE; /* The residue of a stacked contact is the two contacts */ /* which make it up. Residues which are contact types */ /* are unique to stacking types. */ TTMaskZero(&lp->l_residues); TTMaskSetType(&lp->l_residues, lim->l_type); TTMaskSetType(&lp->l_residues, lin->l_type); lp->l_pmask = lin->l_pmask | lim->l_pmask; /* The home plane of the contact is the plane of the */ /* first shared residue found. */ for (sres = TT_TECHDEPBASE; sres < DBNumUserLayers; sres++) if (TTMaskHasType(&ttshared, sres)) { DBPlane(stackedType) = DBPlane(sres); break; } /* Remember this as a paintable contact */ dbContactInfo[dbNumContacts++] = &dbLayerInfo[stackedType]; return (int)stackedType; /* success */ } return -1; } /* * ---------------------------------------------------------------------------- * * DBPlaneToResidue -- * * For the given tile type and plane, return the residue of that type on the * plane. * * Results: * A tile type. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ TileType DBPlaneToResidue(type, plane) TileType type; int plane; { TileType rt, rt2; LayerInfo *lp = &dbLayerInfo[type], *lr; for (rt = TT_TECHDEPBASE; rt < DBNumUserLayers; rt++) if (TTMaskHasType(&lp->l_residues, rt)) { if (type >= DBNumUserLayers) /* Stacked type */ { lr = &dbLayerInfo[rt]; for (rt2 = TT_TECHDEPBASE; rt2 < DBNumUserLayers; rt2++) if (TTMaskHasType(&lr->l_residues, rt2)) if (DBPlane(rt2) == plane) return rt2; } else if (DBPlane(rt) == plane) /* Normal type */ return rt; } return TT_SPACE; /* no residue on plane */ } /* * ---------------------------------------------------------------------------- * * DBMaskAddStacking --- * * A general-purpose routine to add stacked types containing types that are * already in the mask. * * ---------------------------------------------------------------------------- */ void DBMaskAddStacking(mask) TileTypeBitMask *mask; { TileType ttype; TileTypeBitMask *rMask; for (ttype = DBNumUserLayers; ttype < DBNumTypes; ttype++) { rMask = DBResidueMask(ttype); if (TTMaskIntersect(rMask, mask)) TTMaskSetType(mask, ttype); } } /* * ---------------------------------------------------------------------------- * * dbTechContactResidues -- * * Process an argc/argv vector of contact residue type names, creating * image types for each, and ensuring that: * No residue is itself a contact * One of the residues is on the home plane of contactType. * * Results: * Returns the number of residues in the contact, * or -1 in the event of an error. * * Side effects: * Adds to database of layer types. * * ---------------------------------------------------------------------------- */ int dbTechContactResidues(argc, argv, contactType) int argc; char **argv; TileType contactType; { int homePlane, residuePlane, nresidues; PlaneMask pMask; TileType residueType, imageType; bool residueOnHome; LayerInfo *lp; TileTypeBitMask rmask, mmask; nresidues = 0; pMask = 0; residueOnHome = FALSE; TTMaskZero(&rmask); homePlane = DBPlane(contactType); for ( ; argc > 0; argc--, argv++) { if ((residueType = DBTechNoisyNameType(*argv)) < 0) return -1; if (IsContact(residueType)) { TechError("Residue type %s is a contact itself\n", DBTypeLongName(residueType)); return -1; } /* * Make sure the residue is on the same or an adjacent plane * to the contact's home type. */ residuePlane = DBPlane(residueType); if (residuePlane < 0) { TechError("Residue type %s doesn't have a home plane\n", DBTypeLongName(residueType)); return -1; } /* Enforce a single residue per plane */ if (PlaneMaskHasPlane(pMask, residuePlane)) { TechError("Contact residues (%s) must be on different planes\n", DBTypeLongName(residueType)); return -1; } pMask |= PlaneNumToMaskBit(residuePlane); if (homePlane == residuePlane) residueOnHome = TRUE; TTMaskSetType(&rmask, residueType); } if (!residueOnHome) { TechError("Contact type %s missing a residue on its home plane\n", DBTypeLongName(contactType)); return -1; } /* * See if there are any other contact types with identical residues; * if so, disallow contactType. * * Can this restriction be lifted? ---Tim 07/24/03 * This restriction is partially lifted, as one can create a non-stacked * type such as "pad" having the same residues as one stacked type, * such as "m123c". Due to the way magic handles stacked types, these * will not be confused. ---Tim 05/11/04 * * Restriction entirely lifted 6/18/04. Error message left as a warning * until it is clear that there are no unwanted side effects of having * two contact types with identical residues. */ /* Find if there exists an image with the same residue mask */ dbTechMatchResidues(&rmask, &mmask, TRUE); /* Ignore self */ TTMaskClearType(&mmask, contactType); if (!TTMaskEqual(&mmask, &DBZeroTypeBits)) { TxPrintf("Contact residues for %s identical to those for ", DBTypeLongName(contactType)); for (imageType = TT_TECHDEPBASE; imageType < DBNumTypes; imageType++) if (TTMaskHasType(&mmask, imageType)) TxPrintf("%s ", DBTypeLongName(imageType)); TxPrintf("\n"); } /* All clear to set the residue bitmask for this contact type */ lp = &dbLayerInfo[contactType]; lp->l_isContact = TRUE; TTMaskSetMask(&lp->l_residues, &rmask); lp->l_pmask = pMask; return nresidues; } /* * ---------------------------------------------------------------------------- * * dbTechMatchResidues -- * * Find the types whose residues match those of the supplied inMask. * * All this masking about is rather subtle, so pay attention: Each TYPE * has a RESIDUAL, which for a contact is (generally) the type which surrounds * each contact image on its plane (remember, there is one contact image per * plane contacted). So when creating the paint/erase tables, we want to * knock out planes from the residual mask of a contact and see if what's * left is another kind of contact. So from the original contact TYPE, we * make a mask of RESIDUALS, then this routine compares that to the mask * of residuals for all other (contact) types, then create another mask of * all the types which had matching residual masks, and return it. Grok * that? * * Note that the stacked contact mechanism returns the stacked type where * residues of two types match. This is required when composing paint * rules for contacts on contacts. * * Results: * None. * * Side effects: * Yet another TileType bitmask pointer, outMask, is filled in with * the matching types. * * ---------------------------------------------------------------------------- */ void dbTechMatchResidues(inMask, outMask, contactsOnly) TileTypeBitMask *inMask, *outMask; bool contactsOnly; { TileType type; LayerInfo *li; TTMaskZero(outMask); for (type = TT_TECHDEPBASE; type < DBNumUserLayers; type++) { li = &dbLayerInfo[type]; if (!li->l_isContact && contactsOnly) continue; if (TTMaskEqual(inMask, &li->l_residues)) TTMaskSetType(outMask, type); } } /* * ---------------------------------------------------------------------------- * DBTechFindStacking -- * * Find a stacking tile type which connects the two indicated types. * Stacking types have the l_residues field of the LayerInfo entry filled * with the mask of the two types which make up the stacked contact type. * * Results: * The stacking tile type, if it exists, or -1 if there's no stacking * type connecting type1 and type2. * * Side effects: * None. * ---------------------------------------------------------------------------- */ TileType DBTechFindStacking(type1, type2) TileType type1, type2; { TileType rtype, rtype1, rtype2, stackType; LayerInfo *li; for (stackType = DBNumUserLayers; stackType < DBNumTypes; stackType++) { rtype1 = rtype2 = -1; li = &dbLayerInfo[stackType]; for (rtype = TT_TECHDEPBASE; rtype < DBNumUserLayers; rtype++) if (TTMaskHasType(&li->l_residues, rtype)) { rtype1 = rtype; break; } for (++rtype; rtype < DBNumUserLayers; rtype++) if (TTMaskHasType(&li->l_residues, rtype)) { rtype2 = rtype; break; } if (((rtype1 == type1) && (rtype2 == type2)) || ((rtype1 == type2) && (rtype2 == type1))) return stackType; } return -1; } /* * ---------------------------------------------------------------------------- * * DBTechFinalContact -- * * Conclude reading the "contact" section of a technology file. * At this point, all tile types are known so we can call dbTechInitPaint() * to fill in the default paint/erase tables, and dbTechInitMasks() to fill * in the various exported TileTypeBitMasks. * * Results: * None. * * Side effects: * Fills in the dbLayerInfo table for non-contacts. * Sets DBLayerTypeMaskTbl to its final value. * Initializes DBTypePlaneMaskTbl[] and DBPlaneTypes[]. * * ---------------------------------------------------------------------------- */ void DBTechFinalContact() { TileType primaryType; LayerInfo *lp; int pNum; /* Fill in plane and residue info for non-contact types */ for (primaryType = 0; primaryType < DBNumTypes; primaryType++) { lp = &dbLayerInfo[primaryType]; pNum = DBPlane(primaryType); if (!lp->l_isContact && (pNum > 0)) { lp->l_pmask = PlaneNumToMaskBit(pNum); TTMaskSetOnlyType(&lp->l_residues, primaryType); } } /* * Initialize the masks of planes on which each type appears. * It will contain all planes (except router) for space, * the home plane for each type up to DBNumTypes, and no * planes for undefined types. Also update the mask of * types visible on each plane. */ DBTypePlaneMaskTbl[TT_SPACE] = ~(PlaneNumToMaskBit(PL_ROUTER)); for (primaryType = 0; primaryType < DBNumTypes; primaryType++) { pNum = DBPlane(primaryType); if (pNum > 0) { DBTypePlaneMaskTbl[primaryType] = PlaneNumToMaskBit(pNum); if (!IsContact(primaryType)) TTMaskSetType(&DBPlaneTypes[pNum], primaryType); else { lp = &dbLayerInfo[primaryType]; /* if (primaryType < DBNumUserLayers) */ DBTypePlaneMaskTbl[primaryType] |= lp->l_pmask; for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) if (PlaneMaskHasPlane(lp->l_pmask, pNum)) TTMaskSetType(&DBPlaneTypes[pNum], primaryType); } } } /* Create a mask for which each type only appears on one plane. */ /* This is useful for non-redundant searches. */ for (pNum = 0; pNum < PL_MAXTYPES; pNum++) TTMaskZero(&DBHomePlaneTypes[pNum]); for (primaryType = TT_SPACE + 1; primaryType < DBNumTypes; primaryType++) TTMaskSetType(&DBHomePlaneTypes[DBPlane(primaryType)], primaryType); } /* * ---------------------------------------------------------------------------- * DBTechTypesOnPlane -- * * Given a tile type bitmask and a plane index, check if all types in the * bitmask have an image on the indicated plane. * * Results: * TRUE if all types in "src" have at least one image on plane. * FALSE if any type in "src" does not contain any image in plane. * * Side effects: * None. * ---------------------------------------------------------------------------- */ bool DBTechTypesOnPlane(src, plane) TileTypeBitMask *src; int plane; { int i; PlaneMask pmask; for (i = 0; i < DBNumTypes; i++) if (TTMaskHasType(src, i)) if (!PlaneMaskHasPlane(DBTypePlaneMaskTbl[i], plane)) return FALSE; return TRUE; } /* * ---------------------------------------------------------------------------- * * DBTechGetContact -- * * Given two tile types, determine the corresponding contact type. * (Or rather, for two types, get the first contact type connecting * the two planes on which those types lie. . . not quite the same * thing.) * * Results: * Returns a contact type. * * Side effects: * Prints stuff if it can't find a contact type. * * ---------------------------------------------------------------------------- */ TileType DBTechGetContact(type1, type2) TileType type1, type2; { int pmask; LayerInfo *lp; TileType t; pmask = DBTypePlaneMaskTbl[type1] | DBTypePlaneMaskTbl[type2]; for (t = TT_TECHDEPBASE; t < DBNumTypes; t++) { lp = &dbLayerInfo[t]; if (lp->l_isContact) if (lp->l_pmask == pmask) return t; } TxPrintf("No contact type for %d %d\n", type1, type2); return (TileType) -1; } /* * ---------------------------------------------------------------------------- * * DBIsContact -- * * Like IsContact(), except as a subroutine, not a macro. For export * to other routines. * * ---------------------------------------------------------------------------- */ bool DBIsContact(type) TileType type; { if (IsContact(type)) return TRUE; return FALSE; } /* * ---------------------------------------------------------------------------- * * DBResidueMask -- * * Get the residue mask of the specified type. For export to other * routines. * ---------------------------------------------------------------------------- */ TileTypeBitMask * DBResidueMask(type) TileType type; { LayerInfo *li = &dbLayerInfo[type]; return (&li->l_residues); } /* * ---------------------------------------------------------------------------- * * DBFullResidueMask -- * * Get the residue mask of the specified type. For stacking contacts, * decompose the contact residues into their component residue layers. * * Results: * None. * * Side Effects: * "rmask" is a pointer to a TileTypeBitMask. The result is placed * in this location. * ---------------------------------------------------------------------------- */ void DBFullResidueMask(type, rmask) TileType type; TileTypeBitMask *rmask; { TileType t; TileTypeBitMask *lmask; LayerInfo *li, *lr; li = &dbLayerInfo[type]; lmask = &li->l_residues; TTMaskZero(rmask); if (type < DBNumUserLayers) { TTMaskSetMask(rmask, &li->l_residues); } else { for (t = TT_TECHDEPBASE; t < DBNumUserLayers; t++) if (TTMaskHasType(lmask, t)) { lr = &dbLayerInfo[t]; TTMaskSetMask(rmask, &lr->l_residues); } } } /* * ---------------------------------------------------------------------------- * * dbTechPrintContacts -- * * DEBUGGING. * Print a list of the contact types to which each possible contact image * belongs. * * Results: * None. * * Side effects: * Prints stuff. * * ---------------------------------------------------------------------------- */ void dbTechPrintContacts() { LayerInfo *lpImage; TileType t; int m, pNum; for (m = 0; m < dbNumContacts; m++) { lpImage = dbContactInfo[m]; TxPrintf("Contact %s (on %s) ", DBTypeLongName(lpImage->l_type), DBPlaneLongName(DBPlane(lpImage->l_type))); TxPrintf(" connects:"); for (t = TT_TECHDEPBASE; t < DBNumTypes; t++) if ( TTMaskHasType(&DBConnectTbl[lpImage->l_type], t) ) TxPrintf(" %s", DBTypeLongName(t)); TxPrintf(" planes:"); for ( pNum = PL_TECHDEPBASE; pNum < PL_MAXTYPES; pNum++ ) if ( PlaneNumToMaskBit(pNum) & DBConnPlanes[lpImage->l_type] ) TxPrintf(" %s", DBPlaneLongName(pNum)); TxPrintf(" residues:"); for ( t = TT_TECHDEPBASE; t < DBNumTypes; t++ ) if (TTMaskHasType(&lpImage->l_residues, t)) TxPrintf(" %s on plane %s\n", DBTypeLongName(t), DBPlaneLongName(DBPlane(t))); TxPrintf("\n"); } }