/* * ext2dlys.c -- * * This program accepts as input two files: a .ext file produced as * the result of running Magic's circuit extractor on a routed layout * (along with all the rest of the .ext files in the hierarchical * tree), and a netlist file that is used to identify the electrical * pins on each net (for purposes of counting I/O loads). * * Usage: * ext2dlys file * [-d psPerPf] * [-l psPerCentimicron] * [-m min-multiplier max-multiplier] * [-o outfile] * [-D driver-file] * [-I cap-per-input] * [-O cap-per-input] * [-L netlist] * [-M scald-map-file] * [-S capacitance-scale] * > DLYS * * In addition, accepts the arguments documented in extflat(1). * * Normally, reads file.ext and file.net. If a different .net file * is desired, the -n flag may be used to specify the alternate file * (without the suffix). * * The options are as follows: * -d psPerPf Picoseconds per picofarad * -l psPerCentimicron Lightspeed delay in picoseconds per centimicron * of distance between a driver and receiver, * used for delays along transmission lines. * -m min max To give min and max delays, multiply the * computed delays by min and max respectively * (both may be real numbers) * -o outfile Write the output to outfile instead of stdout * -t capacitance-scale 1 unit of capacitance in the .ext file is * equal to capacitance-scale ATTOfarads (the * default is 1.0). May be a real number. * -I cap-per-input Count each input on a net as iload attofarads * of extra load. If only -I is present, both * inputs and outputs are counted as having iload * attofarads of extra load. If neither -I nor * -O are present, inputs and outputs are * considered to be free of any extra loading. * -D driver-file If this option is present, driver-file should * be a file containing two tokens per line: * a hierrchical pin name, and a drive factor. * The drive factor is in picoseconds per * picofarad (encoding the effective on * resistance of the driver). * -L file The .net file to read is file.net * -M scald-file If present, scald-file contains a mapping * between Magic names and scald names, with * each line consisting of the Magic name, * then white space, and then the Scald name * (which can itself contain white space) * up to the end of the line. * -O cap-per-output Count each output on a net as oload attofarads * of extra load. * * The standard output is a wire-delays file with the following format: * * = [:],... * [:]; * = [:],... * [:]; * * Here, is the name of each signal appearing in a * "comment" line prior to the terminals in the netlist, and each * is one of the terminals in the net (names are for * each physical instance of a part, so are made unique after * SIZE and TIMES expansion). Currently, the delays to all * locations in the same net are the same. The end of file * is indicated by two consecutive semicolons. */ #include #ifdef __STDC__ #include #endif #include #include #include "magic.h" #include "hash.h" #include "utils.h" #include "geometry.h" #include "malloc.h" #include "extflat.h" #ifndef __STDC__ extern char *strchr(); #endif /* Forward declarations */ int mainArgs(); char *scaldOutName(); /* Common constants */ #define LINESIZE 1024 /* Max size of all strings */ #define INITHASHSIZE 32 /* Initial size of all hash tables */ float capScale = 1.0; float minMult = 1.0, maxMult = 1.0; float psPerPf = 100.0; /* Assuming 100 ohm drivers (typical bipolar) */ float psPerCU = 3.33e-5; /* Lightspeed = 3e16 CU/sec */ int inputLoad = 0, outputLoad = 0; HashTable scaldHash, driveHash, signalHash, termHash; bool hasILoad = FALSE, hasOLoad = FALSE; char *extName = NULL; char *netName = NULL; char *outName = NULL; char *driveName = NULL; char *scaldName = NULL; main(argc, argv) int argc; char *argv[]; { EFInit(); extName = EFArgs(argc, argv, mainArgs, (ClientData) NULL); if (extName == NULL) exit (1); /* Default initializations */ if (hasILoad && !hasOLoad) outputLoad = inputLoad; if (netName == NULL) netName = extName; (void) fprintf(stderr, "%f femtofarads per .ext capacitance unit (default)\n", capScale); (void) fprintf(stderr, "%f picoseconds per picofarad\n", psPerPf); (void) fprintf(stderr, "%f picoseconds per centimicron\n", psPerCU); (void) fprintf(stderr, "Multipliers: best case = *%.2f, worst case = *%.2f\n", minMult, maxMult); (void) fprintf(stderr, "%d attofarads per input, %d attofarads per output\n", inputLoad, outputLoad); /* Open the output file */ if (outName && freopen(outName, "w", stdout) == NULL) { perror(outName); exit (1); } /* Read the .ext files into memory */ EFReadFile(extName); /* Read the SCALD mapping table if one is to be used */ HashInit(&scaldHash, INITHASHSIZE, HT_STRINGKEYS); if (scaldName) readScald(scaldName); /* Read the table giving all driving outputs and their drive factor */ HashInit(&driveHash, INITHASHSIZE, HT_STRINGKEYS); if (driveName) readDrive(driveName); /* Flatten the .ext files */ EFFlatBuild(extName, EF_FLATNODES|EF_FLATCAPS|EF_FLATDISTS); /* Read the .net file to get signal name to node name mappings */ HashInit(&termHash, INITHASHSIZE, HT_STRINGKEYS); HashInit(&signalHash, INITHASHSIZE, HT_STRINGKEYS); readNet(netName); /* Compute and output delays */ outDelays(); printf(";\n"); #ifdef free_all_mem /* Cleanup */ EFFlatDone(); EFDone(); #endif exit (0); } /* * ---------------------------------------------------------------------------- * * mainArgs -- * * Process those arguments that are specific to ext2sim. * Assumes that *pargv[0][0] is '-', indicating a flag * argument. * * Results: * None. * * Side effects: * After processing an argument, updates *pargc and *pargv * to point to after the argument. * * May initialize various global variables based on the * arguments given to us. * * Exits in the event of an improper argument. * * ---------------------------------------------------------------------------- */ Void mainArgs(pargc, pargv) int *pargc; char ***pargv; { char **argv = *pargv; int argc = *pargc; char *cp; switch (argv[0][1]) { case 'd': if ((cp = ArgStr(&argc, &argv, "delay")) == NULL) goto usage; psPerPf = MagAtof(cp); break; case 'l': if ((cp = ArgStr(&argc, &argv, "picoseconds")) == NULL) goto usage; psPerCU = MagAtof(cp); break; case 'm': if ((cp = ArgStr(&argc, &argv, "multiplier")) == NULL) goto usage; minMult = MagAtof(cp); if (argc-- <= 0) goto usage; maxMult = MagAtof(*argv++); break; case 'L': if ((netName = ArgStr(&argc, &argv, "filename")) == NULL) goto usage; break; case 'o': if ((outName = ArgStr(&argc, &argv, "filename")) == NULL) goto usage; break; case 't': if ((cp = ArgStr(&argc, &argv, "capacitance")) == NULL) goto usage; capScale = MagAtof(cp); break; case 'D': if ((driveName = ArgStr(&argc, &argv, "filename")) == NULL) goto usage; break; case 'I': if ((cp = ArgStr(&argc, &argv, "load")) == NULL) goto usage; inputLoad = atoi(cp); hasILoad = TRUE; break; case 'M': if ((scaldName = ArgStr(&argc, &argv, "filename")) == NULL) goto usage; break; case 'O': if ((cp = ArgStr(&argc, &argv, "load")) == NULL) goto usage; outputLoad = atoi(cp); hasOLoad = TRUE; break; default: (void) fprintf(stderr, "Unrecognized flag: \"%s\"\n", argv[0]); goto usage; } *pargv = argv; *pargc = argc; return; usage: (void) fprintf(stderr, "\ Usage: ext2dlys rootname [-d delay] [-m min-multiplier max-multiplier]\n\ [-o outfile] [-t capacitance-scale] [-I load-per-input] [-L netlist]\n\ [-M scaldmapfile] [-O load-per-input]\n\ or else see options to extcheck(1)\n"); exit (1); } /* * readDrive -- * * Read the drivers file. This file consists of lines containing * two tokens: * * pinName driveFactor * * where pinName is a full hierarchical label name and driveFactor is * picoseconds per picofarad. All nets driven by pinName will use * driveFactor ps/pF when converting from capacitance into delay. * * Creates an entry in the hash table driveHash for pinName with * a value pointing to a malloc'd float with value 'driveFactor' * (the malloc is to avoid machine dependencies). * * Results: * None. * * Side effects: * Builds the hash table driveHash described above. */ Void readDrive(driveName) char *driveName; { char line[LINESIZE], pinName[LINESIZE]; float drive, *pdrive; HashEntry *he; FILE *f; f = fopen(driveName, "r"); if (f == NULL) { perror(driveName); return; } while (fgets(line, sizeof line, f)) { if (sscanf(line, "%s %f", pinName, &drive) < 2) continue; he = HashFind(&driveHash, pinName); MALLOC(float *, pdrive, sizeof (float)); *pdrive = drive; HashSetValue(he, (ClientData) pdrive); } (void) fclose(f); } /* * readScald -- * * Read the Magic-to-SCALD name mapping file 'fileName'. * This file gives an equivalent SCALD name for each Magic * terminal name. Each line begins with a Magic name, * followed by white space, followed by the SCALD name * (which itself may contain embedded white space), and * terminated by the end of the line. * * Results: * None. * * Side effects: * Builds the hash table scaldHash; the value of the * hash entry for a given Magic name is its associated * SCALD name. */ readScald(fileName) char *fileName; { char line[LINESIZE]; register char *cp, *ep; FILE *file; HashEntry *he; file = fopen(fileName, "r"); if (file == (FILE *) NULL) { perror(fileName); return; } while (fgets(line, sizeof line, file)) { for (cp = line; *cp && !isspace(*cp); cp++) /* Nothing */; if (*cp == '\0') continue; /* Mark the end of the Magic name and skip to the SCALD name */ *cp++ = '\0'; while (*cp && isspace(*cp)) cp++; /* Zero the trailing newline */ for (ep = cp; *ep && *ep != '\n'; ep++) /* Nothing */; *ep = '\0'; he = HashFind(&scaldHash, line); if (HashGetValue(he)) { (void) fprintf(stderr, "Warning: multiple SCALD names for Magic name \"%s\"\n", line); continue; } ep = StrDup((char **) NULL, cp); HashSetValue(he, ep); } (void) fclose(file); } /* * readNet -- * * Read the .net file 'netFileName' to figure out the signal * names for each electrical net. Stores each signal name * in the EFNode to which each terminal name in the net belongs. * * Convention: * Each net in a .net file is preceded by a line beginning * with white space, but followed by the SIGNAL name of that * net (not necessarily one of the names of a terminal in * that net). * * Results: * None. * * Side effects: * Leaves entries in the signalHash table for each signal * name pointing to the corresponding EFNode. Adds entries * to termHash for each terminal name in the netlist. Uses * the efnode_client fields to point back to their associated * signal name (StrDup'd). */ Void readNet(netFileName) char *netFileName; { char line[LINESIZE], signalName[LINESIZE]; register char *cp; HashEntry *he; EFNode *node; FILE *file; file = PaOpen(netFileName, "r", ".net", ".", (char *) NULL, (char **) NULL); if (file == (FILE *) NULL) { (void) fprintf(stderr, "%s.net: ", netFileName); perror(""); return; } (void) strcpy(signalName, "(none)"); while (fgets(line, sizeof line, file)) { /* Nuke trailing newline */ cp = strchr(line, '\n'); if (cp) *cp = '\0'; /* Lines beginning with white space delimit nets */ if (line[0] == '\0' || isspace(line[0])) { /* Skip to the first non-white character to find the SIGNAL name */ for (cp = line; *cp && isspace(*cp); cp++) /* Nothing */; if (*cp) (void) strcpy(signalName, cp); else signalName[0] = '\0'; continue; } /* * If no signal name appeared before this terminal, then * make the signal name be the same as this terminal. */ (void) strcpy(signalName, line); /* Find the node for which 'line' is a name */ he = EFHNLook((HierName *) NULL, line, "Terminal"); if (he == NULL) continue; /* Add it to the table of all drivers/receivers */ (void) HashFind(&termHash, line); /* Find the entry in signalHash for signalName */ node = ((EFNodeName *) HashGetValue(he))->efnn_node; if (cp = (char *) node->efnode_client) { if (strcmp(cp, signalName) != 0) { (void) fprintf(stderr, "Node %s belongs to two different signals:\n", line); (void) fprintf(stderr, " %s and %s\n", cp, signalName); (void) fprintf(stderr, " Ignoring %s\n", signalName); } } else { node->efnode_client = StrDup((char **) NULL, signalName); he = HashFind(&signalHash, signalName); if (HashGetValue(he)) { (void) fprintf(stderr, "Strange: one signal (%s), two nodes\n", signalName); } else HashSetValue(he, (ClientData) node); } } (void) fclose(file); } /* * outDelays -- * * Output the delays file to the standard output. * * Results: * None. * * Side effects: * Writes to the standard output. */ Void outDelays() { float minCDelay, maxDelay, minWDelay, minDelay; float delay, drive; int numReceivers, numDrivers, minDist, maxDist; EFNodeName *nnR, *nnD; char *signalName; HashEntry *he; HashSearch hs; bool isFirst; EFNode *node; HashStartSearch(&hs); while (he = HashNext(&signalHash, &hs)) { signalName = he->h_key.h_name; node = (EFNode *) HashGetValue(he); /* Count number of drivers, receivers in this net */ numDrivers = numReceivers = 0; for (nnR = node->efnode_name; nnR; nnR = nnR->efnn_next) { if (isReceiver(nnR)) numReceivers++; if (isDriver(nnR)) numDrivers++; } /* * If no explicit drivers, pick the first name in the node * as a driver arbitrarily. */ if (numDrivers == 0) { char *termName = EFHNToStr(node->efnode_name->efnn_hier); numReceivers--; numDrivers++; (void) HashFind(&driveHash, termName); } isFirst = TRUE; printf("%s =\n", signalName); for (nnR = node->efnode_name; nnR; nnR = nnR->efnn_next) { if (isReceiver(nnR)) { minCDelay = minWDelay = (float) INFINITE_THRESHOLD; maxDelay = 0.0; for (nnD = node->efnode_name; nnD; nnD = nnD->efnn_next) { if (isDriver(nnD)) { if (EFLookDist(nnD->efnn_hier, nnR->efnn_hier, &minDist, &maxDist)) { delay = psPerCU * (float) minDist; if (delay < minWDelay) minWDelay = delay; delay = psPerCU * (float) maxDist; if (delay > maxDelay) maxDelay = delay; } if (node->efnode_cap > 0) { /* * Capacitance is in attofarads (1e-18). * We want picoseconds (1e-12) * ps = picofarads * psPerPf * = (attofarads / 1e6) * psPerPf */ delay = capScale * node->efnode_cap; delay += inputLoad * numReceivers; delay += outputLoad * numDrivers; delay *= findDrive(nnD, &drive) ? drive : psPerPf; delay /= 1.0e6; if (delay < minCDelay) minCDelay = delay; if (delay > maxDelay) maxDelay = delay; } } } /* * Speed of light delay is always an absolute lower bound * on total delay, hence the following check. */ minDelay = (minWDelay < (float) INFINITE_THRESHOLD) ? MAX(minWDelay, minCDelay) : minCDelay; /* Delays are in ps, but get printed in ns; scale by 1e-3 */ if (!isFirst) printf(",\n"); printf(" %s[%.1f:%.1f]", scaldOutName(nnR), minMult * minDelay / 1000.0, maxMult * maxDelay / 1000.0); isFirst = FALSE; } } if (!isFirst) printf(";\n"); } } /* * isReceiver -- * * Determine whether a terminal is a receiver. * This will be true if it IS in the terminal hash table (termHash) * and NOT in the driver hash table (driveHash). * * Results: * TRUE if a receiver, FALSE if not. * * Side effects: * None. */ bool isReceiver(nn) EFNodeName *nn; { HashEntry *he; char *cp; cp = EFHNToStr(nn->efnn_hier); if (HashLookOnly(&termHash, cp) == NULL) return FALSE; if (HashLookOnly(&driveHash, cp) != NULL) return FALSE; return TRUE; } /* * isDriver -- * * Determine whether a terminal is a driver. * This will be true if it is in the driver hash table (driveHash). * We don't bother looking in the terminal hash table, since that * was already checked when the driver hash table was built up. * * Results: * TRUE if a driver, FALSE if not. * * Side effects: * None. */ bool isDriver(nn) EFNodeName *nn; { char *cp; cp = EFHNToStr(nn->efnn_hier); return HashLookOnly(&driveHash, cp) != NULL; } /* * findDrive -- * * Find the "drive factor" stored in the driveHash table * for the terminal identified by 'nn'. * * Results: * TRUE if drive information exists for this terminal, * FALSE if none does. * * Side effects: * None. */ bool findDrive(nn, pDrive) EFNodeName *nn; float *pDrive; { HashEntry *he; char *cp; cp = EFHNToStr(nn->efnn_hier); he = HashLookOnly(&driveHash, cp); if (he == NULL || HashGetValue(he) == NULL) return FALSE; *pDrive = * ((float *) HashGetValue(he)); return TRUE; } /* * scaldOutName -- * * Return a pointer to the string to be used to output this * terminal name. This will be either the SCALD name for * the Magic name, if there's an entry for nn's name in the * scaldHash table, or else just nn's name. * * Results: * TRUE if drive information exists for this terminal, * FALSE if none does. * * Side effects: * The string returned may be statically allocated, so * subsequent calls to EFHNToStr() or this procedure * will trash it. */ char * scaldOutName(nn) EFNodeName *nn; { HashEntry *he; char *cp; cp = EFHNToStr(nn->efnn_hier); he = HashLookOnly(&scaldHash, cp); if (he) return (char *) HashGetValue(he); return cp; }