-- FILE: Main.mesa
-- Last edited by Ousterhout, April 16, 1985 11:36:38 am PST

DIRECTORY
    Atom,
    BasicTime,
    Build,
    Check,
    CommandTool,
    Delay,
    DPrint,
    Flow,
    FS,
    Globals,
    Hash,
    Icons,
    IO,
    Mark,
    Menus,
    Model,
    Parse,
    Printout,
    Process,
    ProcessProps,
    Rope,
    SafeStorage,
    TypeScript,
    ViewerIO,
    ViewerOps,
    ViewerTools,
    VMStatistics;
    
Main: CEDAR PROGRAM
IMPORTS
    Atom,
    BasicTime,
    Build,
    Check,
    CommandTool,
    Delay,
    DPrint,
    Flow,
    FS,
    Globals,
    Hash,
    Icons,
    IO,
    Mark,
    Menus,
    Model,
    Parse,
    Printout,
    Process,
    ProcessProps,
    SafeStorage,
    TypeScript,
    ViewerIO,
    ViewerOps,
    ViewerTools,
    VMStatistics =

BEGIN
OPEN Globals;

inStream: IO.STREAM;
typeScript: TypeScript.TS;
crystalProcess: PROCESS;
stopButton, resetButton: Menus.MenuEntry;

-- The following variables hold information passed between high-level
-- procedures and hash table enumeration procedures.

ivalue: INT;


-- This page defines the Crystal commands.

CmdTable: ARRAY[0..31) OF Rope.ROPE ←
    [
    "build file",
    "bus node node ...",
    "capacitance pfs node node ...",
    "check",
    "clear",
    "critical [file] [index{m}]",
    "delay",
    "dump file",
    "flow in/out/off/ignore/normal flow flow ...",
    "help",
    "input node node ...",
    "markdynamic node value node value ...",
    "model [name]",
    "options [name value] [name value] ...",
    "output node node ...",
    "parameter",
    "prcapacitance [-t pfs] [node node ...]",
    "precharged node node ...",
    "prfets [node node ...]",
    "prnodes [node node ...]",
    "prresistance [-t ohms] [node node ...]",
    "ratio [limit value] [limit value] ...",
    "recompute",
    "resistance ohms node node ...",
    "set 0/1 node node ...",
    "source file",
    "statistics",
    "thyme file",
    "transistor",
    "undump file",
    "watch node node ..."
    ];
 
 CmdProcs: ARRAY[0..31) OF CmdProc ←
     [
     BuildCmd,
     Mark.BusCmd,
     Mark.CapCmd,
     Check.CheckCmd,
     ClearCmd,
     DPrint.CriticalCmd,
     Delay.DelayCmd,
     DPrint.DumpCmd,
     Flow.FlowCmd,
     HelpCmd,
     Mark.InputCmd,
     MarkDynamicCmd,
     Model.ModelCmd,
     OptionsCmd,
     Mark.OutputCmd,
     Model.ParmCmd,
     Printout.PrintCap,
     Mark.PrechargedCmd,
     Printout.PrintFets,
     Printout.PrintNodes,
     Printout.PrintRes,
     Check.RatioCmd,
     DPrint.RecomputeCmd,
     Mark.ResCmd,
     Mark.SetCmd,
     SourceCmd,
     StatsCmd,
     ThymeCmd,
     Model.TransistorCmd,
     DPrint.UndumpCmd,
     Mark.WatchCmd
     ];
 

-- The stuff on this page defines the class of each command, so that
-- we can make sure that comands are issued in the right order.

CmdClass: TYPE = {model, afterClear, circuit, dynamic,
    check, setup, delay, clear, anytime};

curClass: CmdClass ← model;
marked: BOOLEAN;
    -- Marked is TRUE if flow and power rails have been marked,
    -- FALSE means we still have to do it at some point.

classNames: ARRAY CmdClass OF Rope.ROPE ←
    [
    "model",
    "clear",
    "circuit",
    "markdynamic",
    "check",
    "setup",
    "delay",
    "clear",
    "miscellaneous"
    ];

classes: ARRAY[0..31) OF CmdClass ← 
    [
    circuit,			-- build
    circuit,			-- bus
    circuit,			-- capacitance
    check,			-- check
    clear,			-- clear
    anytime,  	    -- critical
    delay,			-- delay
    anytime,		-- dump
    circuit,			-- flow
    anytime,		-- help
    circuit,			-- input
    dynamic,		-- markdynamic
    model,			-- model
    anytime,		-- options
    circuit,			-- output
    model,			-- parameter
    anytime,		-- prcapacitance
    setup,			-- precharged
    anytime,		-- prfets
    anytime,		-- prnodes
    anytime,		-- prresistance
    check,			-- ratio
    anytime,		-- recompute
    circuit,			-- resistance
    setup,			-- set
    anytime,		-- source
    anytime,		-- stats
    circuit,			-- thyme
    model,			-- transistor
    anytime,	    -- undump
    anytime			-- watch
    ];   
 

HelpCmd: CmdProc =
    -- Prints out a synopsis of the commands.
    
    BEGIN
    FOR i: INTEGER IN [0..LENGTH[CmdTable]) DO
        IO.PutF[StdOut, "%s\n",  IO.rope[CmdTable[i]]];
        ENDLOOP;
    END;

BuildCmd: CmdProc =
    -- Reads in the .sim file.
    
    BEGIN
    msg: Rope.ROPE;
    
    IF args = NIL THEN
        BEGIN
        IO.PutRope[StdOut, "No file name given.\n"];
        RETURN;
        END;
    
    msg ← Build.NetFromSim[args.rope];
    IF msg # NIL THEN
        IO.PutF[StdOut, "%s\n", IO.rope[msg]];
    END;

ThymeCmd: CmdProc =
    -- Reads in a Thyme-format file.
    
    BEGIN
    IF args = NIL THEN
        BEGIN
        IO.PutRope[StdOut, "No file name given.\n"];
        RETURN;
        END;
    
    Build.NetFromThyme[args.rope];
    END;

SourceCmd: CmdProc =
    -- Reads in more commands from a command file.
    
    BEGIN
    stream: IO.STREAM;
    msg: Rope.ROPE;
    
    IF args = NIL THEN
        BEGIN
        IO.PutRope[StdOut, "No file name given.\n"];
        RETURN;
        END;
    
    stream ← FS.StreamOpen[args.rope
        ! FS.Error => IF error.group # bug
        THEN {msg ← error.explanation;  CONTINUE}];
    IF msg # NIL THEN
        BEGIN
        IO.PutF[StdOut, "Couldn't open %s: %s.\n",
            IO.rope[args.rope], IO.rope[msg]];
        RETURN;
        END;
    IntrpCmds[stream, FALSE];
    IO.Close[stream];
    END;

StatsCmd: CmdProc = 
    -- Calls various routines to print out statistics about what
    -- Crystal has done.
    
    BEGIN
    Build.Stats[];
    Flow.Stats[];
    Mark.Stats[];
    Delay.Stats[];
    DPrint.Stats[];
    END;


OptionsCmd: CmdProc =
    -- This command procedure reads "name value" argument pairs
    -- and sets internal options.  Some of the options require no
    -- values.  If no names or values are given, then the current
    -- option settings are printed.
    
    BEGIN
    optionTable: ARRAY[0..19) OF Rope.ROPE ←
        [
        "bus",
        "graphics",
        "limit",
        "mempaths",
        "noprintedgespeeds",
        "noseedelays",
        "noseedynamic",
        "noseesettings",
        "paths",
        "printedgespeeds",
        "ratiodups",
        "ratiolimit",
        "seealldelays",
        "seeallsettings",
        "seedelays",
        "seedynamic",
        "seesettings",
        "units",
        "watchpaths"
        ];
    index, i: INT;
    r: REAL;
    ok: BOOLEAN;
    anyOptions: BOOLEAN ← FALSE;
    
    WHILE args # NIL DO
        anyOptions ← TRUE;
        TRUSTED {index ← Parse.Lookup[rope: args.rope,
            table: DESCRIPTOR[optionTable]]};
        IF index = -1 THEN
            IO.PutF[StdOut, "Ambiguous option name: %s.\n",
                IO.rope[args.rope]]
        ELSE IF index = -2 THEN
            IO.PutF[StdOut, "Unknown option name: %s.\n",
                IO.rope[args.rope]]
        ELSE SELECT index FROM
        
            -- bus:  set bus threshold.
            
            0 =>
                BEGIN
                args ← args.next;
                [ok, r] ← Parse.Real[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No bus threshold given.\n"]
                ELSE Delay.BusThreshold ← r;
                END;
            
            -- graphics: set graphical output format.
            
            1 =>
                BEGIN
                args ← args.next;
                IF args = NIL THEN
                    IO.PutRope[StdOut, "No graphical style given.\n"]
                ELSE IO.PutRope[StdOut, "Sorry, \"graphics\" isn't implemented yet.\n"];
                END;
            
            -- limit: set maximum number of delay calculations before despair.
            
            2 =>
                BEGIN
                args ← args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No delay limit value given.\n"]
                ELSE Delay.DelayLimit ← i;
                END;
            
            -- mempaths: set count of how many memory paths to record.
            
            3 =>
                BEGIN
                args ← args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No memory path count given.\n"]
                ELSE IF i > DPrint.MaxPaths THEN
                    IO.PutF[StdOut, "Can't record more than %d paths.\n",
                        IO.int[DPrint.MaxPaths]]
                ELSE DPrint.NumPaths[memory] ← i;
                END;
            
            -- noprintedgespeeds:  set boolean not to print out edge speeds in
            -- "critical" command.
            
            4 =>
                BEGIN
                DPrint.PrintEdgeSpeeds ← FALSE;
                END;
            
            -- noseedelays: set flag not to print out delays as they are computed.
            
            5 =>
                BEGIN
                Delay.Print ← FALSE;
                Delay.PrintAll ← FALSE;
                END;
            
            -- noseedynamic: set flag not to print out dynamic nodes as they are found.
            
            6=>
                Mark.SeeDynamic ← FALSE;
            
            -- noseesettings: set flag not to print out node settings when they are made.
            
            7 =>
                BEGIN
                Mark.SeeSettings ← FALSE;
                Mark.SeeAllSettings ← FALSE;
                END;
            
            -- paths: set count of how many paths to record.
            
            8 =>
                BEGIN
                args ← args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No path count given.\n"]
                ELSE IF i > DPrint.MaxPaths THEN
                    IO.PutF[StdOut, "Can't record more than %d paths.\n",
                        IO.int[DPrint.MaxPaths]]
                ELSE DPrint.NumPaths[any] ← i;
                END;
            
            -- printedgespeeds: set boolean to print out edge speeds.
            
            9 =>
                BEGIN
                DPrint.PrintEdgeSpeeds ← TRUE;
                END;
            
            -- ratiodups: set count of how ratio messages per ratio type.
            
            10 =>
                BEGIN
                args ←args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No duplicate ratio limit given.\n"]
                ELSE Check.ratioLimit ← i;
                END;
            
            -- ratiolimit: set count of maximum allowabl ratio error messages.
            
            11 =>
                BEGIN
                args ← args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No ratio error limit given.\n"]
                ELSE Check.totalRatioLimit ← i;
                END;
            
            -- seealldelays:  set flag to print out all delays as they are computed.
            
            12 =>
                Delay.PrintAll ← TRUE;

            -- seeallsettings:  set flag to print out all node settings as they are made.
            
            13 =>
                Mark.SeeAllSettings ← TRUE;
            
            -- seedelays: set flag to print out delays for named nodes only.
            
            14 =>
                BEGIN
                Delay.PrintAll ← FALSE;
                Delay.Print ← TRUE;
                END;
            
            -- seedynamic: set flag to print out dynamic nodes as they are found.
            
            15 =>
                Mark.SeeDynamic ← TRUE;
            
            -- seesettings:  see settings of named nodes only.
            
            16 =>
                BEGIN
                Mark.SeeAllSettings ← FALSE;
                Mark.SeeSettings ← TRUE;
                END;
            
            -- units:  set units for printout and read-in.
            
            17 =>
                BEGIN
                args ← args.next;
                [ok, r] ← Parse.Real[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No units value given.\n"]
                ELSE
                    BEGIN
                    Build.Units ← r;
                    Printout.Units ← r;
                    END;
                END;
            
            -- watchpaths: set count of how many watched paths to record.
            
            18 =>
                BEGIN
                args ← args.next;
                [ok, i] ← Parse.Int[args];
                IF NOT ok THEN
                    IO.PutRope[StdOut, "No watched path count given.\n"]
                ELSE IF i > DPrint.MaxPaths THEN
                    IO.PutF[StdOut, "Can't record more than %d paths.\n",
                        IO.int[DPrint.MaxPaths]]
                ELSE DPrint.NumPaths[watched] ← i;
                END;

            ENDCASE;
            
        IF args # NIL THEN args ← args.next;
        ENDLOOP;
    
    IF NOT anyOptions THEN
        BEGIN
        IO.PutF[StdOut, "Bus threshold is %.1f pf\n",
            IO.real[Delay.BusThreshold]];
        IO.PutF[StdOut, "Delay limit is %d stages\n",
            IO.int[Delay.DelayLimit]];
        IO.PutF[StdOut, "Print units are %.1f microns per lambda\n",
            IO.real[Printout.Units]];
        IO.PutF[StdOut, "Recording %d memory paths, %d arbitrary paths",
            IO.int[DPrint.NumPaths[memory]], IO.int[DPrint.NumPaths[any]]];
        IO.PutF[StdOut, ", %d watched paths\n",
            IO.int[DPrint.NumPaths[watched]]];
        IO.PutF[StdOut, "Ratio limits are %d errors per ratio, %d total\n",
            IO.int[Check.ratioLimit], IO.int[Check.totalRatioLimit]];
        IF Delay.PrintAll THEN
            IO.PutRope[StdOut, "Printing all new delays to nodes\n"]
        ELSE IF Delay.Print THEN
            IO.PutRope[StdOut, "Printing new delays to named nodes\n"]
        ELSE IO.PutRope[StdOut, "Not printing new delays\n"];
        IF Mark.SeeDynamic THEN
            IO.PutRope[StdOut, "Printing out dynamic node settings\n"]
        ELSE IO.PutRope[StdOut, "Not printing dynamic nodes\n"];
        IF Mark.SeeAllSettings THEN
            IO.PutRope[StdOut, "Printing all node settings\n"]
        ELSE IF Mark.SeeSettings THEN
            IO.PutRope[StdOut, "Printing named node settings\n"]
        ELSE IO.PutRope[StdOut, "Not printing node settings\n"];
        IF DPrint.PrintEdgeSpeeds THEN
            IO.PutRope[StdOut, "Printing edge speeds\n"]
        ELSE IO.PutRope[StdOut, "Not printing edge speeds\n"];
        END;
    END;  


ClearCmd: CmdProc =
    -- This command procedure reinitializes timing and flag information
    -- in all of the nodes and transistors.  Any information set by "set",
    -- "flow", and "delay" commands is cleared.  The procedure also checks
    -- to be sure that certain flags, like inPath, haven't accidentally been
    -- left set.
    
    BEGIN
    
    Hash.Enumerate[table: NodeTable, pattern: "*", proc: clearProc,
        errorStream: StdOut];
    
    -- It is crucial that Vdd and Ground get left marked as inputs and
    -- as fixed in value.  Otherwise the marking routines will misbehave
    -- a most horrendous manner.
    
    IF GroundNode # NIL THEN 
        BEGIN
        GroundNode.input ← TRUE;
        GroundNode.always0 ← TRUE;
        END;
    IF VddNode # NIL THEN
        BEGIN
        VddNode.input ← TRUE;
        VddNode.always1 ← TRUE;
        END;
    DPrint.Clear[];
    END;

clearProc: Hash.EnumProc =
    BEGIN
    p: Pointer;
    f: Fet;
    node: Node ← NARROW[entry.clientData];
    
    IF node.inPath THEN
        BEGIN
        IO.PutF[StdOut, "Crystal bug: inPath left set at %s.\n",
            IO.rope[Printout.NodeRope[node]]];
        node.inPath ← FALSE;
        END;
    node.always0 ← FALSE;
    node.always1 ← FALSE;
    node.precharged ← FALSE;
    node.ratioError ← FALSE;
    node.watched ← FALSE;
    node.hiTime ← -1;
    node.loTime ← -1;
    
    p ← node.firstPointer;
    WHILE p # NIL DO
        f ← p.fet;
        p ← p.next;
        IF f.gate # node THEN LOOP;
        f.flowFromSource ← FALSE;
        f.flowFromDrain ← FALSE;
        f.forcedOn ← FALSE;
        f.forcedOff ← FALSE;
        ENDLOOP;
    END;
    

IntrpCmds: PROC[stream: IO.STREAM, interactive: BOOLEAN] =
    -- This procedure reads command lines from stream, parses them
    -- up, looks up the commands in CmdTable, and invokes
    -- command routines.    This routine returns only upon receiving
    -- an end of file or a "quit" command.  Interactive specifies whether
    -- or not this is an interactive interpreter.  If it is TRUE, then
    -- prompts are output on StdOut before each command.
    
    BEGIN
    line: Rope.ROPE;
    args: Arg;
    index: INT;
    class: CmdClass;
    pulses: BasicTime.Pulses;
    secs: REAL;
    faults: INT;
    words: INT;
    
    {ENABLE IO.Error => CONTINUE;
    WHILE TRUE DO
        IF IO.EndOf[stream] THEN RETURN;
        IF Stop↑ THEN
             BEGIN
             IF interactive THEN IO.PutRope [StdOut, "[Interrupted]\n"]
             ELSE RETURN;
             END;
        IF interactive THEN IO.PutRope[StdOut, ": "];
        line ← IO.GetLineRope[stream];
        args ← Parse.Args[line];
        IF interactive THEN Stop↑ ← FALSE;
        IF args = NIL THEN LOOP;
        IF NOT interactive THEN IO.PutF[StdOut, ": %s\n", IO.rope[line]];
        TRUSTED {index ← Parse.Lookup[args.rope, DESCRIPTOR[CmdTable]]};
        IF index = -1 THEN
            IO.PutF[StdOut, "Ambiguous command: %s\n", IO.rope[args.rope]]
        ELSE IF index = -2 THEN
            IO.PutF[StdOut, "Unknown command: %s\n", IO.rope[args.rope]]
        ELSE
            BEGIN 
            
            -- Make sure that the command is being given in the right order.
            -- Also take care of automatic flow and Vdd/Ground marking.
            
            class ← classes[index];
            IF class < curClass THEN
                BEGIN
                IO.PutF[StdOut, "The %s command shouldn't be", IO.rope[args.rope]];
                IO.PutF[StdOut, " invoked after a %s command.\n",
                    IO.rope[classNames[curClass]]];
                END;
            SELECT class FROM
            
                anytime => NULL;
                
                model, circuit => IF class > curClass THEN curClass ← class;
                
                dynamic, check, setup, delay =>
                    BEGIN
                    IF NOT marked THEN
                        BEGIN
                        IO.PutRope[StdOut, "Marking transistor flow...\n"];
                        Flow.Mark[NIL];
                        IO.PutRope[StdOut, "Setting Vdd to 1...\n"];
                        Mark.SetNodeValue[node: VddNode, value: 1, propAnyway: TRUE];
                        IO.PutRope[StdOut, "Setting GND to 0...\n"];
                        Mark.SetNodeValue[node: GroundNode, value: 0, propAnyway: TRUE];
                        marked ← TRUE;
                        END;
                    IF curClass < class THEN curClass ← class;
                    END;
                
                clear =>
                    BEGIN
                    marked ← FALSE;
                    curClass ← afterClear;
                    END;
                ENDCASE;
            
            -- Actually exeute the command.
            
            pulses ← BasicTime.GetClockPulses[];
            TRUSTED
            {
                faults ← VMStatistics.pageFaults;
                words ← SafeStorage.NWordsAllocated[];
            };
            (CmdProcs[index])[args.next];
            pulses ← BasicTime.GetClockPulses[] - pulses;
            secs ← BasicTime.PulsesToSeconds[pulses];
            TRUSTED
            {
                faults ← VMStatistics.pageFaults - faults;
                words ← SafeStorage.NWordsAllocated[] - words;
            };
            IO.PutF[StdOut, "[%.2f secs, %d words, %d faults]\n",
                IO.real[secs], IO.int[words], IO.int[faults]];
            END;
        ENDLOOP;
    };
    TypeScript.Destroy[typeScript];
    END;


MarkDynamicCmd: CmdProc =
    BEGIN
    ok: BOOLEAN;
    name: Rope.ROPE;
    
    WHILE args # NIL DO
        name ← args.rope;
        args ← args.next;
        IF args = NIL THEN
            BEGIN
            IO.PutF[StdOut, "No value given for %s.\n", IO.rope[name]];
            EXIT;
            END;
        [ok, ivalue] ← Parse.Int[args];
        IF ivalue < 0 OR ivalue > 1 OR NOT ok THEN
            BEGIN
            IO.PutF[StdOut, "Bad value given for %s: %s.  Try 0 or 1.\n",
                IO.rope[name], IO.rope[args.rope]];
            args ← args.next;
            LOOP;
            END;
        Hash.Enumerate[table: NodeTable, pattern: name, proc: mdProc,
            errorStream: StdOut];
        args ← args.next;
        ENDLOOP;
    Mark.MarkDynamic[];
    ClearCmd[NIL];
    marked ← FALSE;
    END;
    
mdProc: Hash.EnumProc =
    BEGIN
    node: Node ← NARROW[entry.clientData];
    Mark.SetNodeValue[node, ivalue, TRUE];
    END;

StopProc: Menus.ClickProc =
    BEGIN
    Stop↑ ← TRUE;
    END;

ResetProc: Menus.ClickProc =
    BEGIN
    TypeScript.Reset[typeScript];
    Init[];
    IO.PutRope[StdOut, ": "];
    END;

Init: PROC =
    BEGIN
    resetButton ← Menus.CreateEntry[name: "Reset", proc: ResetProc, fork: FALSE];
    Menus.SetGuarded[resetButton, TRUE];
    Menus.InsertMenuEntry[menu: typeScript.menu, entry: resetButton];
    stopButton ← Menus.CreateEntry[name: "STOP!", proc: StopProc, fork: FALSE];
    Menus.InsertMenuEntry[menu: typeScript.menu, entry: stopButton];
    ViewerOps.PaintViewer[viewer: typeScript, hint: menu];
    IO.PutRope[StdOut, "Crystal, version 2X\n"];
    ViewerTools.SetSelection[viewer: typeScript]; 
    NodeTable ← Hash.NewTable[];
    VddNode ← Build.BuildNode["Vdd"];
    GroundNode ← Build.BuildNode["GND"];
    END;

-- Initialization and startup:

TopLevelProc: PROC[stream: IO.STREAM, interactive: BOOLEAN, wd: Rope.ROPE] =
    BEGIN
        
    inner: PROC[] =
        BEGIN
        IntrpCmds[stream, interactive];
        END;
    
    ProcessProps.AddPropList[Atom.PutPropOnList[NIL, $WorkingDirectory, wd], inner];
    END;

typeScript ← TypeScript.Create[info: [name: "Crystal", iconic: FALSE]];
[in:inStream, out:StdOut] ← ViewerIO.CreateViewerStreams[name: "Crystal",
   viewer: typeScript];
Init[];

typeScript.icon ← Icons.NewIconFromFile["Crystal.icons", 2
    !ANY =>
        BEGIN
        IO.PutRope[StdOut, "Couldn't find Crystal.icons.\n"];
        IO.PutRope[StdOut, "I hope you're satisfied with the normal boring icon.\n"];
        CONTINUE;
        END];

TRUSTED {crystalProcess ← FORK TopLevelProc[inStream, TRUE, 
     CommandTool.CurrentWorkingDirectory[]]};
TRUSTED {Process.Detach[crystalProcess]};

END.