-- FILE: CheckImpl.mesa
-- Last edited by Ousterhout, August 23, 1983 1:03 pm

DIRECTORY
    Check,
    Flow,
    Globals,
    Hash,
    IO,
    Model,
    Parse,
    Printout,
    Rope;

CheckImpl: CEDAR PROGRAM
IMPORTS
    Flow,
    Globals,
    Hash,
    IO,
    Parse,
    Printout
EXPORTS Check =
BEGIN
OPEN Globals, Check;

-- Limit of how many message of any one type to print:

msgLimit: INT ← 100;

-- Limits for valid transistor ratios:

normalLow: REAL ← 3.8;
normalHigh: REAL ← 4.2;
passLow: REAL ← 7.8;
passHigh: REAL ← 8.2;

-- Info used to suppress printing of many identical ratio errors:

ratioLimit: PUBLIC INT ← 5;
ratioTable: Hash.Table;
ratioDups: INT;
totalRatioErrors: INT;
totalRatioLimit: PUBLIC INT ← 1000;
cmdTable: ARRAY[0..3] OF Rope.ROPE ←
    [
    "normalhigh",
    "normallow",
    "passhigh",
    "passlow"
    ];

-- Info shared between CheckCmd and checkProc:

msg1, msg2, msg3, msg4, msg5, msg6, skipped: INT;

IntRec: TYPE = RECORD[val: INT];


CheckCmd: PUBLIC CmdProc =
    BEGIN
    msg1 ← 0;
    msg2 ← 0;
    msg3 ← 0;
    msg4 ← 0;
    msg5 ← 0;
    msg6 ← 0;
    skipped ← 0;
    Hash.Enumerate[table: NodeTable, pattern: "*", proc: checkProc,
        errorStream: StdOut];
    END;

checkProc: Hash.EnumProc =
    BEGIN
    p: Pointer;
    f: Fet;
    node: Node ← NARROW[entry.clientData];
    drives, driven: BOOLEAN ← FALSE;
    
    -- 1. Make sure the node has transistors attached.
    
    IF node.firstPointer = NIL THEN
        BEGIN
        msg1 ← msg1 + 1;
        IF msg1 <= msgLimit THEN
            IO.PutF[StdOut, "Node %s doesn't connect to any transistors.\n",
                IO.rope[Printout.NodeRope[node]]]
        ELSE skipped ← skipped + 1;
        IF msg1 = msgLimit THEN
            IO.PutRope[StdOut, "    No more of these messages will be printed.\n"];
        RETURN;
        END;
    
    -- Go through all the transistors attached to the node.  See if the
    -- node is driven and if it drives anything, and also check out the
    -- transistors on the way (only check a transistor when its gate is
    -- seen, to avoid duplication).
    
    FOR p ← node.firstPointer, p.next UNTIL p = NIL DO
        f ← p.fet;
        IF f.source = node THEN
            BEGIN
            IF f.flowFromDrain THEN driven ← TRUE;
            IF f.flowFromSource THEN drives ← TRUE;
            END;
        IF f.drain = node THEN
            BEGIN
            IF f.flowFromSource THEN driven ← TRUE;
            IF f.flowFromDrain THEN drives ← TRUE;
            END;
        IF f.gate = node THEN
            BEGIN
            drives ← TRUE;
            
            -- 4. If transistor can't flow at all, error.
            
            IF NOT (f.flowFromSource OR f.flowFromDrain) THEN
                BEGIN
                msg4 ← msg4 + 1;
                IF msg4 <= msgLimit THEN
                    BEGIN
                    IO.PutF[StdOut, "No flow through FET: %s\n",
                        IO.rope[Printout.FetRope[f]]];
                    IF msg4 = 1 THEN
                        BEGIN
                        IO.PutRope[StdOut, "    Maybe you haven't marked all"];
                        IO.PutRope[StdOut, " the inputs and outputs?\n"];
                        END;
                    END
                ELSE skipped ← skipped + 1;
                IF msg4 = msgLimit THEN
                    BEGIN
                    IO.PutRope[StdOut, "    No more of these messages"];
                    IO.PutRope[StdOut, " will be printed.\n"];
                    END;
                END;
                
            -- 5. If transistor is bidirectional but has no flow indicator,
            -- issue a warning.
                
            IF f.flowFromSource AND f.flowFromDrain
                AND f.firstFlow = NIL THEN
                BEGIN
                msg5 ← msg5 + 1;
                IF msg5 <= msgLimit THEN
                    BEGIN
                    IO.PutF[StdOut, "Bidirectional FET: %s\n",
                        IO.rope[Printout.FetRope[f]]];
                    IF msg5 = 1 THEN
                        IO.PutRope[StdOut, "    Maybe you should label flow?\n"];
                    END
                ELSE skipped ← skipped + 1;
                IF msg5 = msgLimit THEN
                    BEGIN
                    IO.PutRope[StdOut, "    No more of these messages"];
                    IO.PutRope[StdOut, " will be printed.\n"];
                    END;
                END;
            
            -- 6. If transistor connects Vdd and Ground, error.
            
            IF (f.drain = VddNode AND f.source = GroundNode)
                OR (f.source = VddNode AND f.drain = GroundNode) THEN
                BEGIN
                msg6 ← msg6 + 1;
                IF msg6 <= msgLimit THEN
                    IO.PutF[StdOut, "FET between Vdd and Ground: %s\n",
                        IO.rope[Printout.FetRope[f]]]
                ELSE skipped ← skipped + 1;
                IF msg6 = msgLimit THEN
                    BEGIN
                    IO.PutRope[StdOut, "    No more of these messages"];
                    IO.PutRope[StdOut, " will be printed.\n"];
                    END;
                END;
            END;
        ENDLOOP;
        
    -- 3 & 4.  Make sure that the node can drive and be driven.
      
    IF NOT (node.output OR drives) THEN
        BEGIN
        msg3 ← msg3 + 1;
        IF msg3 <= msgLimit THEN
            BEGIN
            IO.PutF[StdOut, "Node doesn't drive anything: %s.\n",
                IO.rope[Printout.NodeRope[node]]];
            IF msg3 = 1 THEN
                BEGIN
                IO.PutRope[StdOut, "    Maybe you haven't marked all"];
                IO.PutRope[StdOut, " the inputs and outputs?\n"];
                END;
            END
        ELSE skipped ← skipped + 1;
        IF msg3 = msgLimit THEN
            BEGIN
            IO.PutRope[StdOut, "    No more of these messages"];
            IO.PutRope[StdOut, " will be printed.\n"];
            END;
        END;  
    IF NOT (node.input OR driven) THEN
        BEGIN
        msg2 ← msg2 + 1;
        IF msg2 <= msgLimit THEN
            BEGIN
            IO.PutF[StdOut, "Node isn't driven: %s.\n",
                IO.rope[Printout.NodeRope[node]]];
            IF msg2 = 1 THEN
                BEGIN
                IO.PutRope[StdOut, "    Maybe you haven't marked all"];
                IO.PutRope[StdOut, " the inputs and outputs?\n"];
                END;
            END
        ELSE skipped ← skipped + 1;
        IF msg2 = msgLimit THEN
            BEGIN
            IO.PutRope[StdOut, "    No more of these messages"];
            IO.PutRope[StdOut, " will be printed.\n"];
            END;
        END;
    END;


RatioCmd: PUBLIC CmdProc =
    BEGIN
    index: INT;
    val: REAL;
    ok: BOOLEAN;
    
    ratioTable ← Hash.NewTable[];
    ratioDups ← 0;
    totalRatioErrors ← 0;
    normalLow ← 3.8;
    normalHigh ← 4.2;
    passLow ← 7.8;
    passHigh ← 8.2;
    
    -- Read in parameters, if there are any.
    
    WHILE args # NIL DO
        TRUSTED {index ← Parse.Lookup[args.rope, DESCRIPTOR[cmdTable]]};
        IF index < 0 THEN
            BEGIN
            IO.PutF[StdOut, "Bad limit %s: try normalhigh, normallow, ",
                IO.rope[args.rope]];
            IO.PutRope[StdOut, "passhigh, or passlow.\n"];
            args ← args.next;
            LOOP;
            END;
        args ← args.next;
        IF args = NIL THEN
            BEGIN
            IO.PutF[StdOut, "No value given for %s.\n",
                IO.rope[cmdTable[index]]];
            EXIT;
            END;
        [ok, val] ← Parse.Real[args];
        args ← args.next;
        IF NOT ok THEN
            BEGIN
            IO.PutF[StdOut, "Bad value given for %s.\n",
                IO.rope[cmdTable[index]]];
            LOOP;
            END;
        SELECT index FROM
            =0 => normalHigh ← val;
            =1 => normalLow ← val;
            =2 => passHigh ← val;
            =3 => passLow ← val;
            ENDCASE;
         ENDLOOP;
     
     -- Process all of the nodes in the hash table.
     
     Hash.Enumerate[table: NodeTable, pattern: "*", proc: ratioProc,
         errorStream: StdOut];
     ratioTable ← NIL;
     IF totalRatioErrors > totalRatioLimit THEN
         IO.PutF[StdOut, "Ratio checking aborted after %d errors.\n",
             IO.int[totalRatioErrors]];
     IF ratioDups > 0 THEN
         IO.PutF[StdOut, "%d duplicate ratio errors not printed.\n",
             IO.int[ratioDups]];
     END;

ratioProc: Hash.EnumProc =
    BEGIN
    p: Pointer;
    node: Node ← NARROW[entry.clientData];
     
    IF (node = VddNode) OR (node = GroundNode) THEN RETURN;
    FOR p ← node.firstPointer, p.next UNTIL p = NIL DO
        IF p.fet.type = Model.fetNLoad THEN EXIT;
        ENDLOOP;
    IF p = NIL THEN RETURN;
    checkRatio[origin: node, current: node, loadSize: p.fet.aspect,
        curSize: 0.0, gotPass: FALSE];
    END;
 

checkRatio: PROC[origin, current: Node, loadSize, curSize: REAL,
    gotPass: BOOLEAN] =
    -- This is a recursive procedure that does all of the real work in checking
    -- transistor ratios.  It scans recursively through pulldowns and checks
    -- ratios whenever it reaches ground.  Error messages are printed if ratio
    -- errors are found.
     
    BEGIN
    p, p2: Pointer;
    f: Fet;
    newSize, ratio: REAL;
    newPass: BOOLEAN;
    error: Rope.ROPE;
    count: REF IntRec;
    next: Node;
    h: Hash.Entry;
     
    -- Set a flag to avoid infinite recursion through circular structures.
     
    current.inPath ← TRUE;
     
    -- Skim through all of the transistors attached to the node.  If the
    -- transistor connects to ground, check the  ratio.  Otherwise, call this
    -- routine recursively to check on the pother side.
     
    FOR p ← current.firstPointer, p.next UNTIL p = NIL DO
        IF totalRatioErrors > totalRatioLimit THEN
            BEGIN
            current.inPath ← FALSE;
            RETURN;
            END;
        f ← p.fet;
     
        -- Make sure that the transistor's source or drain connects to this
        -- node and that flow in this direction is OK.
     
        IF f.source = current THEN
            BEGIN
            next ← f.drain;
            IF NOT f.flowFromDrain THEN LOOP;
            END
        ELSE IF f.drain = current THEN
            BEGIN
            next ← f.source;
            IF NOT f.flowFromSource THEN LOOP;
            END
        ELSE LOOP;
     
        -- Ignore circular paths and paths to Vdd.
    
        IF next.inPath THEN LOOP;
        IF next = VddNode THEN LOOP;
        IF f.firstFlow # NIL THEN
            IF NOT Flow.Lock[f, next] THEN LOOP;
     
        -- See if the gate of this transistor is load driven or pass
        -- transistor driven.  Inputs are considered to be equal to
        -- load driven nodes.
     
        FOR p2 ← f.gate.firstPointer, p2.next UNTIL p2 = NIL DO
            IF (p2.fet.type = Model.fetNLoad)
                OR (p2.fet.type = Model.fetNSuper) THEN EXIT;
            ENDLOOP;
        IF (p2 = NIL) AND NOT f.gate.input THEN newPass ← TRUE
        ELSE newPass ← gotPass;
     
        -- Now check the ratio.
     
        newSize ← curSize + f.aspect;
        IF next = GroundNode THEN
            BEGIN
            ratio ← loadSize/newSize;
            IF newPass THEN
                BEGIN
                IF (ratio >= passLow) AND (ratio <= passHigh)  THEN
                    BEGIN
                    IF f.firstFlow # NIL THEN Flow.Unlock[f, next];
                    LOOP;
                    END;
                END
            ELSE IF (ratio >= normalLow) AND (ratio <= normalHigh) THEN
                BEGIN
                IF f.firstFlow # NIL THEN Flow.Unlock[f, next];
                LOOP;
                END;
          
            -- There's been an error.  This piece of code eliminates extra messages:
            -- there can be no more than one message about a particular node,  no
            -- more than a certain number of messages about a particular ratio,  and
            -- no more than a certain total number of messages.
         
            IF origin.ratioError THEN
                BEGIN
                ratioDups ← ratioDups + 1;
                IF f.firstFlow # NIL THEN Flow.Unlock[f, next];
                LOOP;
                END;
            origin.ratioError ← TRUE;
            totalRatioErrors ← totalRatioErrors + 1;
            error ← IO.PutFR["pullup = %.1f, pulldown = %.1f",
                IO.real[loadSize], IO.real[newSize]];
            h ← Hash.Find[table: ratioTable, name: error];
            IF h.clientData = NIL THEN h.clientData ← NEW[IntRec ← [0]];
            count ← NARROW[h.clientData];
            count.val ← count.val + 1;
            IF count.val > ratioLimit THEN ratioDups ← ratioDups + 1
            ELSE
                BEGIN
                IO.PutF[StdOut, "Ratio error at node %s: %s\n",
                    IO.rope[Printout.NodeRope[origin]], IO.rope[error]];
                IO.PutF[StdOut, "    Last pulldown is at (%.1f, %.1f)\n",
                    IO.real[f.x/Printout.Units], IO.real[f.y/Printout.Units]];
                END;
            END
        ELSE checkRatio[origin: origin, current: next, loadSize: loadSize,
            curSize: newSize, gotPass: newPass];
        IF f.firstFlow # NIL THEN Flow.Unlock[f, next];
        ENDLOOP;
    current.inPath ← FALSE;
    END;
           
END.