-- ChipSimFile.mesa

-- Subroutines to analyze the tree of cell calls and structure
-- of nets constructed by the net extraction.

-- last modified by E. McCreight, December 21, 1983  10:19 AM
-- last modified by R. Barth, July 8, 1983  12:11 PM
-- written by E. McCreight, November 3, 1981  3:06 PM

DIRECTORY
  Ascii,
  ChipNetDefs,
  ChipUserInt,
  InlineDefs,
  ppdddefs,
  ppdefs,
  ppMainDefs,
  StreamDefs,
  StringDefs;

ChipSimFile: PROGRAM
  IMPORTS ChipNetDefs, ChipUserInt,
    InlineDefs, ppdddefs, ppdefs, ppMainDefs,
    StreamDefs, StringDefs
  EXPORTS ChipNetDefs =
  BEGIN OPEN StringDefs, StreamDefs, ppdefs, ChipUserInt,
    ChipNetDefs;

  HashIndex: TYPE = INTEGER[0..253);
  XstrHashTable: TYPE = ARRAY HashIndex OF XstrCallPtr;
  XstrHashPtr: TYPE = LONG POINTER TO XstrHashTable;

  simFile, thyFile: DiskHandle;
  simInCrystalFormat: BOOLEAN ← FALSE;
  showHier, showEquivalences: BOOLEAN;
  h: XstrHashPtr;
  transNo: LONG CARDINAL;

  WriteSimFile: PUBLIC PROCEDURE[c: CellCallPtr] =
    BEGIN

    ShouldMakeFile: PROCEDURE[name: STRING]
      RETURNS[BOOLEAN] =
      BEGIN
      question: STRING ← [100];
      question.length ← 0;
      AppendString[to: question, from: "Make "];
      AppendString[to: question, from: name];
      AppendString[to: question, from: "?"];
      RETURN[HeSetsParamsAndSaysYes[question]];
      END;

    s: STRING ← [300];
    name: STRING;
    h ← uz.NEW[XstrHashTable];
    name ← RequestString["Output file name?"L];
    IF name=NIL OR name.length=0 THEN
      name ← newString[ppMainDefs.fileName];
    IF ShouldMakeFile[name ←
      FixExtension[name, ".sim"L]]
    THEN
      BEGIN
      simFile ← NewByteStream[name, WriteAppend];
      simInCrystalFormat ← HeSaysYes["Want it in Crystal format?"];
      END
    ELSE simFile ← NIL;
    thyFile ← IF ShouldMakeFile[name ←
      FixExtension[name, ".thy"L]] THEN
      NewByteStream[name, WriteAppend] ELSE NIL;
    showHier ← HeSetsParamsAndSaysYes["Show hierarchy?"L];
    showEquivalences ← simFile#NIL AND showHier AND
      HeSetsParamsAndSaysYes["Show terminal equivalences?"L];

    IF simFile#NIL THEN
      BEGIN
      HashTransistors[c];
      IF simInCrystalFormat THEN
        BEGIN
        s.length ← 0;
        AppendString[s, "| units: "];
        AppendCoord[s, ppdddefs.pCifScale/Lambda];
        SaySimLine[s];
        END;
      s.length ← 0;
      WriteCellInst[s, c, 0, SimFileCellInst];
      TruncateDiskStream[simFile];
      END;

    IF thyFile#NIL THEN
      BEGIN -- introductory boiler plate
      pCifScalesPerLambda: INTEGER = 100;
        -- pCifScale is in centimicrons per lambda
      s.length ← 0;
      SayThyLine[""];
      SayThyLine["-- N o d e s"L];
      SayThyLine[""];
      WriteThyNodes[s, c, 0];
      HashTransistors[c];
      transNo ← 0;
      SayThyLine[""];
      SayThyLine[""];
      SayThyLine["-- T r a n s i s t o r s"L];
      SayThyLine[""];
      WriteCellInst[s, c, 0, ThyFileCellInst];
      SayThyLine[""];
      TruncateDiskStream[thyFile];
      END;

    FreeString[name];
    uz.FREE[@h];
    END; -- of WriteSimFile


  WriteCellInst: PROCEDURE[s: STRING, call: CellCallPtr,
    depth: CARDINAL,
    HandleCell: PROCEDURE[s: STRING, call: CellCallPtr,
      depth: CARDINAL]] =
    BEGIN
    IF depth>0 THEN HashTransistors[call];

    HandleCell[s, call, depth];

    FOR c: InstancePtr ← call.offspring, c.sibling
      WHILE c#NIL DO
      WITH dc: c SELECT FROM
        cell =>
          WriteCellInst[s, @dc,
            depth+(IF showHier THEN 1 ELSE 0), HandleCell];
        ENDCASE => NULL;
      ENDLOOP;
    END; -- of WriteCellInst

  SimFileCellInst: PROCEDURE[s: STRING, call: CellCallPtr,
    depth: CARDINAL] =
    BEGIN
    c: InstancePtr;
    FOR c ← call.offspring, c.sibling WHILE c#NIL DO
      WITH dc: c SELECT FROM
        xstr => EXIT;
        ENDCASE => NULL;
      ENDLOOP;

    IF depth>0 AND (c#NIL OR call.nets#NIL OR call.clusters#NIL)
      THEN
      BEGIN
      SaySimLine[""];
      Indent[s, depth];
      AppendString[s, "| "];
      AppendCallerChain[s, call];
      SaySimLine[s];
      END;

    FOR c ← c, c.sibling WHILE c#NIL DO
      WITH dc: c SELECT FROM
        xstr => {Indent[s, depth]; WriteSimTransistor[s, c]};
        ENDCASE => NULL;
      ENDLOOP;

    FOR netId: NetIdPtr ← call.nets, netId.next WHILE netId#NIL DO
      WITH normNetId: netId SELECT FROM
        normal =>
          {Indent[s, depth]; WriteSimNetLine[s, @normNetId]};
        ENDCASE => NULL;
      ENDLOOP;

    FOR cluster: ClusterPtr ← call.clusters, cluster.next
      WHILE cluster#NIL DO
      id: NormalNetIdPtr ← GetNormalNetId[@cluster.first.net];
      idMakesItemRef: BOOLEAN;
      itemRef: ItemRef;
      -- print a net = line if it is not an identity
      WITH did: id SELECT FROM
        qualified => 
          BEGIN
          idMakesItemRef ← TRUE;
          itemRef ← [did.source, did.see];
          END;
        ENDCASE => idMakesItemRef ← FALSE;

      IF showEquivalences AND (NOT idMakesItemRef OR
        itemRef#cluster.first.source) THEN
        BEGIN
        Indent[s, depth];
        AppendString[s, "= "];
        cluster.first.net ← AppendNet[s, cluster.first.net];
        Space[s];
        AppendTerminalName[s, cluster.first.source];
        SaySimLine[s];
        END;
      ENDLOOP;
    END; -- of SimFileCellInst 

  ThyFileCellInst: PROCEDURE[s: STRING, call: CellCallPtr,
    depth: CARDINAL] =
    BEGIN
    c: InstancePtr;
    FOR c ← call.offspring, c.sibling WHILE c#NIL DO
      WITH dc: c SELECT FROM
        xstr => EXIT;
        ENDCASE => NULL;
      ENDLOOP;

    IF depth>0 AND c#NIL THEN
      BEGIN
      SayThyLine[""];
      Indent[s, depth+2];
      AppendString[s, "-- "];
      AppendCallerChain[s, call];
      SayThyLine[s];
      END;

    FOR c ← c, c.sibling WHILE c#NIL DO
      WITH dc: c SELECT FROM
        xstr => {Indent[s, depth+2]; WriteThyTransistor[s, c]};
        ENDCASE => NULL;
      ENDLOOP;
    END; -- of ThyFileCellInst 

  WriteThyNodes: PROCEDURE[s: STRING, call: CellCallPtr,
    depth: CARDINAL] =
    BEGIN
    IF depth>0 AND call.nets#NIL THEN
      BEGIN
      SayThyLine[""];
      Indent[s, depth+2];
      AppendString[s, "-- "];
      AppendCallerChain[s, call];
      SayThyLine[s];
      END;

    FOR netId: NetIdPtr ← call.nets, netId.next WHILE netId#NIL DO
      WITH normNetId: netId SELECT FROM
        normal =>
          {Indent[s, depth+2]; WriteThyNetLine[s, @normNetId]};
        ENDCASE => NULL;
      ENDLOOP;

    FOR c: InstancePtr ← call.offspring, c.sibling
      WHILE c#NIL DO
      WITH dc: c SELECT FROM
        cell => WriteThyNodes[s, @dc,
          depth+(IF showHier THEN 1 ELSE 0)];
        ENDCASE => NULL;
      ENDLOOP;
    END; -- of WriteThyNodes 

  HashTransistors: PROCEDURE[c: CellCallPtr] =
    BEGIN

    HashLevel: PROCEDURE[c: CellCallPtr] =
      BEGIN
      FOR instance: InstancePtr ← c.offspring, instance.sibling
        WHILE instance#NIL DO
        WITH dinst: instance SELECT FROM
          cell => IF NOT showHier THEN HashLevel[@dinst];
          xstr =>
            BEGIN
            i: HashIndex;
            dinst.inSimFile ← FALSE;
            FOR t: XstrTerminals IN XstrTerminals DO
              dinst.map[t] ← CanonNet[dinst.map[t]];
              ENDLOOP;
            i ← XstrHashFn[@dinst];
            dinst.sameHash ← h[i];
            h[i] ← @dinst;
            END;
          ENDCASE => NULL;
        ENDLOOP;
      END;

    FOR i: HashIndex IN HashIndex DO
      h[i] ← NIL;
      ENDLOOP;
    HashLevel[c];
    END; -- of HashTransistors

  CollectSimilarTransistors: PROCEDURE[model, candidates:
    XstrCallPtr, modelOb: LONG POINTER TO xstr object]
    RETURNS[locNum] =
    BEGIN
    width: CARDINAL ← 0; -- in 10ths of a Lambda
    FOR cand: XstrCallPtr ← candidates, cand.sameHash
      WHILE cand#NIL DO
      IF cand.map[gate]=model.map[gate] AND
        ((cand.map[source]=model.map[source] AND
          cand.map[drain]=model.map[drain]) OR
        (cand.map[source]=model.map[drain] AND
          cand.map[drain]=model.map[source])) THEN
        BEGIN
        WITH candOb: cand.proto.ob SELECT FROM
          xstr =>
            BEGIN
            IF modelOb.length=candOb.length AND
              modelOb.impl=candOb.impl AND
              modelOb.l=candOb.l THEN
              BEGIN
              width ← width+(candOb.width*10)/Lambda;
              cand.inSimFile ← TRUE;
              END;
            END;
          ENDCASE;
        END;
      ENDLOOP;
    RETURN[(Lambda*width)/10];
    END;

  XstrHashFn: PROCEDURE[xstr: XstrCallPtr]
    RETURNS[HashIndex] =
    BEGIN
    RETURN[InlineDefs.LowHalf[
      (LOOPHOLE[xstr.map[gate], LONG CARDINAL]+
      LOOPHOLE[xstr.map[source], LONG CARDINAL]+
      LOOPHOLE[xstr.map[drain], LONG CARDINAL]) MOD
       (LAST[HashIndex]+1)]];
    END;

  WriteSimTransistor: PROCEDURE[s: STRING, t: InstancePtr] =
    BEGIN
    WITH trans: t SELECT FROM
      xstr => IF NOT trans.inSimFile THEN
        BEGIN
        WITH tob: t.proto.ob SELECT FROM
          xstr =>
            BEGIN
            p: Point;
            AppendChar[s, IF tob.l=pdif -- should know whether CMOS or NMOS here
              THEN (IF simInCrystalFormat THEN 'p ELSE 'c)
              ELSE (SELECT tob.impl FROM
                enhancement => 'e,
                strongDepletion => 'd,
                zeroThresh => 'z,
                ENDCASE => 'x)];
            FOR term: XstrTerminals IN XstrTerminals DO
              Space[s];
              AppendNetId[s, GetNormalNetId[@trans.map[term]]];
              ENDLOOP;
            AppendLocNum[s, tob.length];
            AppendLocNum[s, 
              CollectSimilarTransistors[@trans,
                h[XstrHashFn[@trans]], @tob] -- width --];
            p ← RefCoordPt[ItemInWorld[t.caller]];
            AppendCoord[s, p.x/Lambda];
            AppendCoord[s, p.y/Lambda];
            IF simInCrystalFormat THEN FOR term: XstrTerminals IN XstrTerminals DO
              attr: Atom = SELECT term FROM
                gate => crystalG,
                source => crystalS,
                ENDCASE -- drain -- => crystalD;
              value: Atom = FindPropValue[lp: ItemRefToLp[trans.caller], attribute: attr];
              IF value#NIL THEN
                BEGIN
                Space[s];
                AppendString[s, SELECT term FROM
                  gate => "g=Cr:",
                  source => "s=Cr:",
                  ENDCASE -- drain -- => "d=Cr:"];
                AppendLongString[s, AtomToString[value]];
                END;
              ENDLOOP;
            SaySimLine[s];
            END;
          ENDCASE => NULL;
        END;
      ENDCASE => NULL
    END; -- of WriteSimTransistor


  crystalS: Atom = MakeAtom["Crystal-S"];
  crystalG: Atom = MakeAtom["Crystal-G"];
  crystalD: Atom = MakeAtom["Crystal-D"];


  WriteThyTransistor: PROCEDURE[s: STRING, t: InstancePtr] =
    BEGIN
    WITH trans: t SELECT FROM
      xstr => IF NOT trans.inSimFile THEN
        BEGIN
        WITH tob: t.proto.ob SELECT FROM
          xstr =>
            BEGIN
            defaultL: ARRAY DepletionStrength OF locNum =
              [ -- enhancement -- 2*Lambda, -- 0-thresh -- 2*Lambda,
                -- wk depl --  4*Lambda, -- strong depl -- 4*Lambda];
            defaultW: ARRAY DepletionStrength OF locNum =
              [-- enhancement -- 4*Lambda, -- 0-thresh -- 4*Lambda,
                -- wk depl -- 2*Lambda, -- strong depl -- 2*Lambda];
            first: BOOLEAN;
            AppendChar[s, 'Q];
            AppendCard[s, transNo ← transNo+1];
            AppendChar[s, ':];
            AppendChar[s, IF tob.l=pdif -- should know whether CMOS or NMOS here
              THEN 'C
              ELSE (SELECT tob.impl FROM
                enhancement => 'E,
                strongDepletion => 'D,
                zeroThresh => 'Z,
                ENDCASE => 'X)];
            AppendString[s, "Tran["];
            FOR term: XstrTerminals IN XstrTerminals DO
              IF term#FIRST[XstrTerminals] THEN
                AppendChar[s, ',];
              AppendThyNetId[s, GetNormalNetId[@trans.map[term]]];
              ENDLOOP;
            first ← AppendThyDist[s, tob.length, "L",
              defaultL[tob.impl], TRUE];
            [] ← AppendThyDist[s, CollectSimilarTransistors[@trans,
              h[XstrHashFn[@trans]], @tob] -- width --, "W",
              defaultW[tob.impl], first];
            AppendString[s, "];"];
            SayThyLine[s];
            END;
          ENDCASE => NULL;
        END;
      ENDCASE => NULL
    END; -- of WriteThyTransistor

  WriteSimNetLine: PROCEDURE[s: STRING, id: NormalNetIdPtr] =
    BEGIN
    AppendString[s, "N "];
    AppendNetId[s, id];
    IF simInCrystalFormat THEN
      BEGIN
      AppendLayerArea[s, id.caps[ndif].area+id.caps[pdif].area];
      AppendLayerPerim[s, id.caps[ndif].perimeter+id.caps[pdif].perimeter];
      AppendLayerArea[s, id.caps[poly].area];
      AppendLayerPerim[s, id.caps[poly].perimeter];
      AppendLayerArea[s, id.caps[metal].area+id.caps[metal2].area];
      AppendLayerPerim[s, id.caps[metal].perimeter+id.caps[metal2].perimeter];
      END
    ELSE
      BEGIN
      AppendLayerArea[s, id.caps[metal].area];
      AppendLayerArea[s, id.caps[poly].area];
      AppendLayerArea[s, id.caps[ndif].area+id.caps[pdif].area];
      AppendLayerPerim[s, id.caps[ndif].perimeter+id.caps[pdif].perimeter];
      AppendLayerArea[s, id.caps[metal2].area];
      END;
    SaySimLine[s];
    END;


  WriteThyNetLine: PROCEDURE[s: STRING, id: NormalNetIdPtr] =
    BEGIN
    nodeName: STRING ← [100];
    first: BOOLEAN;
    nodeName.length ← 0;
    AppendThyNetId[nodeName, id];

    SELECT TRUE FROM
      EquivalentString[nodeName, "Gnd"],
      EquivalentString[nodeName, "Vdd"] =>
        AppendString[s, "-- "]; -- not allowed to describe these
      ENDCASE => NULL;

    AppendString[s, nodeName];
    AppendString[s, ":node; ?:Stray["];
    AppendString[s, nodeName];
    first ← AppendThyLayerArea[s, id.caps[metal2].area, "aM2", TRUE];
    first ← AppendThyLayerArea[s, id.caps[metal].area, "aM", first];
    first ← AppendThyLayerArea[s, id.caps[poly].area, "aP", first];
    first ← AppendThyLayerArea[s, id.caps[ndif].area, "anD", first];
    first ← AppendThyDist[s, id.caps[ndif].perimeter,
      "pnD", 0, first];
    first ← AppendThyLayerArea[s, id.caps[pdif].area, "apD", first];
    [] ← AppendThyDist[s, id.caps[pdif].perimeter,
      "ppD", 0, first];
    AppendString[s, "];"];
    SayThyLine[s];
    END;

  AppendThyNetId: PROCEDURE[s: STRING, id: NormalNetIdPtr] =
    BEGIN
    name: STRING ← [100];
    name.length ← 0;
    AppendNetId[name, id];
    SELECT TRUE FROM
      EquivalentString[name, "Gnd"] =>
        AppendString[s, "Gnd"];
      EquivalentString[name, "Vdd"] =>
        AppendString[s, "Vdd"];
      ENDCASE =>
        FOR i: CARDINAL IN [0..name.length) DO
          SELECT name[i] FROM
            IN ['0..'9], IN ['a..'z], IN ['A..'Z] => NULL;
            ENDCASE => GOTO HasSpecialChar;
          REPEAT
            HasSpecialChar =>
              BEGIN
              AppendChar[s, '$];
              AppendString[s, name];
              AppendChar[s, '$];
              END;
            FINISHED => AppendString[s, name];
          ENDLOOP;
    END;

  AppendLayerArea: PROCEDURE[s: STRING, area: LONG CARDINAL -- square locNum's --] =
    BEGIN
    Space[s];
    AppendCard[s, area/(Lambda*Lambda)];
    END;

  AppendLayerPerim: PROCEDURE[s: STRING, perimeter: LONG CARDINAL -- locNum's --] =
    BEGIN
    Space[s];
    AppendCard[s, perimeter/Lambda];
    END;

  AppendThyLayerArea: PROCEDURE[s: STRING, area: LONG CARDINAL -- square locNum's --,
    layer: STRING, first: BOOLEAN] RETURNS[BOOLEAN] =
    BEGIN
    IF area>0 THEN
      BEGIN
      AppendChar[s, IF first THEN '| ELSE ',];
      AppendString[s, layer];
      AppendString[s, "←"];
      AppendCard[s, area/(Lambda*Lambda)];
      RETURN[FALSE];
      END;
    RETURN[first];
    END;

  AppendThyDist: PROCEDURE[s: STRING, dist: LONG CARDINAL -- locNum's --,
    layer: STRING, default: CARDINAL -- locNum's --, first: BOOLEAN]
    RETURNS[BOOLEAN] =
    BEGIN
    IF dist#default THEN
      BEGIN
      AppendChar[s, IF first THEN '| ELSE ',];
      AppendString[s, layer];
      AppendString[s, "←"];
      AppendCard[s, dist/Lambda];
      RETURN[FALSE];
      END;
    RETURN[first];
    END;

  Space: PROCEDURE[s: STRING] = INLINE
    {AppendChar[s, ' ]};

  Indent: PROCEDURE[s: STRING, depth: CARDINAL] =
    BEGIN
    FOR i: CARDINAL IN [0..2*depth) DO s[i] ← '  ENDLOOP;
    s.length ← 2*depth;
    END;

  SaySimLine: PROCEDURE[s: STRING] =
    BEGIN
    IF simFile#NIL THEN
      BEGIN
      FOR i: CARDINAL IN [0..s.length) DO
        simFile.put[simFile, s[i]] ENDLOOP;
      simFile.put[simFile, Ascii.CR];
      END;
    END;

  SayThyLine: PROCEDURE[s: STRING] =
    BEGIN
    IF thyFile#NIL THEN
      BEGIN
      FOR i: CARDINAL IN [0..s.length) DO
        thyFile.put[thyFile, s[i]] ENDLOOP;
      thyFile.put[thyFile, Ascii.CR];
      END;
    END;

  END. -- of ChipSimFile