content: module APaths(
                PathGraphInfo,
                PathUrgencyPairs, PathNode,
                aPathsPreSched, aPathsPostSched
               ) where
  
  -- ========================================================================
  -- APaths
  --
  -- Defines two aPath stages of the compiler.
  --
  -- The first stage takes an APackage prior to scheduling and produces:
  --   1) a graph of the paths through the package,
  -- This is used to produce two others pieces of information, which
  -- are used in the scheduling pass:
  --   2) urgency restrictions due to paths from WF(r1) to CF(r2),
  --   3) execution order restrictions due to values from r1 being
  --      combinationally passed to expressions in r2.
  -- If there are any cycles in the graph so far (sans scheduling),
  -- this stage reports an error.
  --
  -- The second stage takes in the graph from the first stage and the
  -- scheduler produced by the scheduling stage, and checks whether
  -- there are any cycles in the graph with scheduling edges added.
  -- If there is, the stage reports an error.  (There shouldn't be any
  -- new cycles, if scheduling has done its job.  Urgency and execution
  -- order cycles should be handled in the scheduling stage.)
  -- This stage also computes the path information from module inputs
  -- to module outputs, which will be added to the vModInfo for this module.
  --
  -- ==========
  --
  -- The nodes of the graph are:
  --   * All identifiers in the ADefT
  --
  --   * All arguments to methods of submodules
  --   * All return values of methods of submodules
  --   * All enable signals of methods of submodules
  --   * (There are no separate ready signals -- they are return values)
  --   * All instantiation arguments to modules (those that become ports,
  --     not parameters), except clocks (and their gates) and resets
  --     (though there is some infrastructure for tracking this, if we want,
  --     but it is not finished, because it will change with MCD support)
  --   * A control mux for all methods of submodules with arguments
  --     (the arguments to a single resource is potentially muxed, based on
  --     the enable signal of the rules which provide the inputs)
  --
  --   * All CAN_FIRE signals for the rules and method-rules
  --   * All WILL_FIRE signals for the rules and methods-rules
  --
  --   * All arguments to methods of the current module
  --   * All return values of methods of the current module
  --   * All enable signals of methods of the current module
  --   * (There are no separate ready signals -- they are return values)
  --   * All instantiation arguments of the current module
  --     (These are include clock and reset, except for "noinline" functions
  --      which do not have clock and reset.  And they include arguments to
  --      the module which become ports -- we do not generate Verilog
  --      parameters.)
  --   * CLK and RST for the current module
  --     (These are needed since they can appear in instantiation expressions
  --     to arguments of submodules, e.g. the change_clock module in ClockConv.)
  --
  -- We assume that we can ignore paths through foreign action functions
  -- and system tasks.
  --
  -- ==========
  --
  -- Urgency restrictions are found by:
  --   1) Add an edge from each of the method RDY to the method EN
  --      (because there is an assumption that the outside will consider
  --       the EN based on the RDY signal)
  --   2) For any path from WF(r1) to CF(r2), assert that r1 is more
  --      urgent than r2.
  --
  -- Execution order restrictions are found by:
  --   XXX ...
  --
  -- Path information for the module is computed by finding paths in the
  -- graph from module inputs:
  --   * method arguments for the current module
  --   * method enable signals for the current module
  --   * parameters for the current module
  -- to module outputs:
  --   * method return values for the current module
  --     (this includes method ready signals, currently)
  --
  -- ==========
  --
  -- For now, we do not distinguish between cycles which are stable and
  -- those which are unstable.  In the future, we may want to assign a
  -- boolean expression to each edge of the path graph, which is the
  -- condition under which that link is active or affects the value of
  -- the receiving node.  If the compiler can prove that all of the
  -- boolean conditions of a cycle cannot be satisfied at the same time,
  -- then the cycle is at least stable.  We can include this information
  -- in the warning, in case the user wants to allow stable cycles and
  -- only disallow unstable cycles.
  --
  -- But given our current compilation scheme, cycles between rules
  -- breaks TRS semantics.  Stable cycles are only okay inside a single
  -- rule.
  --
  -- ========================================================================
  
  import ErrorMonad(ErrorMonad(..), convErrorMonadToIO)
  import PFPrint
  import Flags(Flags)
  import ASyntax
  import Error(internalError, EMsg, ErrMsg(..), ErrorHandle)
  import VModInfo(vPath, vFields, vArgs,
                  VPathInfo(..), VName(..), VFieldInfo(..),
                  VArgInfo(..), VWireInfo(..),
                  getInputClockPorts, getInputResetPorts,
                  isClock, isReset, isPort, isParam, id_to_vName,mkNamedEnable)
  import Pragma
  import Control.Monad(when)
  import Data.Maybe(isJust, isNothing, fromJust)
  import Data.List(partition)
  import Id(unQualId, getIdBaseString)
  import Eval
  import Position(getPosition)
  import qualified Data.Map as M
  import qualified Data.Set as S
  
  import GraphWrapper
  
  import Util(itos, snd3, concatUnzip, concatUnzip3)
  import Debug.Trace(traceM)
  import IOUtil(progArgs)
  
  -- ====================================
  -- Tracing
  --
  
  trace_apaths :: Bool
  trace_apaths = "-trace-apaths" `elem` progArgs
  
  -- ====================================
  -- Data Types
  --
  
  
  type PathEnv = M.Map AId PathNode
  
  -- The information that is passed between the pre and post scheduler stages
  data PathGraphInfo = PathGraphInfo
                           { pgi_graph :: Graph PathNode,
                             pgi_inputs :: [PathNode],
                             pgi_outputs :: [PathNode] }
  
  instance PPrint PathGraphInfo where
      pPrint d p pgi =
          text "PathGraphInfo" <+> text "{" $+$
          nest 2 (text "graph" <+> text "=" <+>
                  pPrint d p (pgi_graph pgi) <+> text "," $+$
                  text "inputs" <+> text "=" <+>
                  pPrint d p (pgi_inputs pgi) <+> text "," $+$
                  text "outputs" <+> text "=" <+>
                  pPrint d p (pgi_outputs pgi)) $+$
          text "}"
  
  instance Hyper PathGraphInfo where
      hyper x y = hyper3 (pgi_graph x) (pgi_inputs x) (pgi_outputs x) y
  
  type PathUrgencyPairs = [(ARuleId, ARuleId, [PathNode])]
  
  data PathNode =
      -- ID in the ADefT
      PNDef AId  |
      -- arguments to methods of submodules (Ids: instance, method, arg #)
      PNStateMethodArg AId AId Integer  |
      -- return values of methods of submodules (Ids: instance, method)
      PNStateMethodRes AId AId  |
      -- enable signal of action methods of submodules (Ids: instance, method)
      PNStateMethodEnable AId AId  |
      -- imported state has no ready signal
      --PNStateMethodReady AId AId  |
      -- arguments of submodules (Ids: instance, arg VName, position in arg list)
      -- Note that arguments are Verilog ports, and are not parameters.
      -- The position is included because args in the source are only
      -- known to the user by position (the VName is compiler generated).
      PNStateArgument AId VName Integer |
      -- control mux for arguments to methods of submodules
      -- (Ids: instance, method)
      PNStateMethodArgMux AId AId |
      -- ready signal of a method or rule
      PNCanFire AId  |
      -- enable signal of a method or rule
      PNWillFire AId  |
      -- arguments to methods of current module (Ids: method, argument)
      PNTopMethodArg AId AId  |
      -- return values of methods of current module (Id: method)
      PNTopMethodRes AId  |
      -- this is an internal graph node for the method's ready signal,
      -- the real output port is handled by a separate read method
      -- (Id: method)
      PNTopMethodReady AId  |
      -- enable signal for action method (Id: method)
      PNTopMethodEnable AId  |
      -- instantiation arguments to the module
      -- (typically Verilog ports, but could be parameters)
      -- The position number is included because args in the source might only
      -- be known to the user by position (the VName is compiler generated).
      -- (Though it's generated from the user's name in BSV, isn't it?)
      PNTopArgument AId Integer |
      -- clock wire (input or state element output)
      PNClk AId |
      -- inverted reset wire (input or state element output)
      PNRstN AId |
      -- clock-gating signal (input)
      PNTopClkGate AId |
      -- clock-gating signal (state element output)
      PNStateClkGate AId AId
    deriving (Eq, Ord, Show)
  
  -- The Bool indicates whether to use pPrint or pvPrint
  printPathNode :: Bool -> PDetail -> Int -> PathNode -> Doc
  printPathNode use_pvprint d p node =
      let pp :: (PVPrint a, PPrint a) => a -> Doc
          pp = if (use_pvprint)
               then pvPrint d p
               else pPrint d p
      in  case (node) of
              (PNDef def_id) ->
                  fsep [text "Definition", quotes (pp def_id)]
              (PNStateMethodArg inst_id meth_id port_id) ->
                  fsep [text "Argument", pp port_id,
                        s2par "of method", quotes (pp meth_id),
                        s2par "of submodule", quotes (pp inst_id)]
              (PNStateMethodRes inst_id meth_id) ->
                  fsep [s2par "Return value",
                        s2par "of method", quotes (pp meth_id),
                        s2par "of submodule", quotes (pp inst_id)]
              (PNStateMethodEnable inst_id meth_id) ->
                  fsep [s2par "Enable signal",
                        s2par "of method", quotes (pp meth_id),
                        s2par "of submodule", quotes (pp inst_id)]
  --          (PNStateMethodReady inst_id meth_id) -> ...
              (PNStateArgument inst_id arg_id arg_num) ->
  --                fsep [s2par "Instantiation argument", pp arg_id]
  --              Report the position of the argument, not its mangled name
                  fsep [s2par "Instantiation argument", pp arg_num,
                        s2par "of submodule", quotes (pp inst_id)]
              (PNStateMethodArgMux inst_id meth_id) ->
                  fsep [s2par "Control mux for arguments ",
                        s2par "of method", quotes (pp meth_id),
                        s2par "of submodule", quotes (pp inst_id)]
              (PNCanFire rule_or_meth_id) ->
                  fsep [s2par "CanFire signal of rule/method",
                  quotes (pp rule_or_meth_id)]
              (PNWillFire rule_or_meth_id) ->
                  fsep [s2par "WillFire signal of rule/method",
                  quotes (pp rule_or_meth_id)]
              (PNTopMethodArg meth_id arg_id) ->
                  fsep [text "Argument", pp arg_id,
                        s2par "of top-level method", quotes (pp meth_id)]
              (PNTopMethodRes meth_id) ->
                  fsep [text "Output",
                        s2par "of top-level method", quotes (pp meth_id)]
              (PNTopMethodReady meth_id) ->
                  fsep [s2par "Ready condition",
                        s2par "of top-level method", quotes (pp meth_id)]
              (PNTopMethodEnable meth_id) ->
                  fsep [s2par "Enable signal",
                        s2par "of top-level method", quotes (pp meth_id)]
              (PNTopArgument arg_id arg_num) ->
                  fsep [s2par "Top-level module argument",
  --                      quotes (pp arg_id)]
  --              Report the position of the argument, not its mangled name
                        quotes (pp arg_num)]
              (PNClk clk_id) ->
                  s2par ("Clock: " ++ (pfpReadable clk_id))
              (PNRstN rstn_id) ->
                  s2par ("Reset: " ++ (pfpReadable rstn_id))
              (PNTopClkGate gate_id) ->
                  s2par ("Top-level module clock-gating input: " ++
                         pfpReadable gate_id)
              (PNStateClkGate inst_id clk_id) ->
                  fsep [s2par "Clock-gating output",
                        s2par "of clock", quotes (pp clk_id),
                        s2par "of submodule", quotes (pp inst_id)]
  
  instance PPrint PathNode where
      pPrint d p = printPathNode False d p
  
  instance PVPrint PathNode where
      pvPrint d p = printPathNode True d p
  
  instance Hyper PathNode where
      hyper x y = (x==x) `seq` y
  
  -- When reporting errors, all of the above are in the user's source
  -- except the defs, so don't report them.  This function filters them out.
  filterPNDefs :: [PathNode] -> [PathNode]
  filterPNDefs pns = filter (not . isPNDef) pns
      where isPNDef (PNDef _) = True
            isPNDef _ = False
  
  alwaysRdyNode :: [PProp] -> PathNode -> Bool
  alwaysRdyNode pps (PNTopMethodRes m) = isAlwaysRdy pps m
  alwaysRdyNode pps _ = False
  
  enWhenRdyNode :: [PProp] -> PathNode -> Bool
  enWhenRdyNode pps (PNTopMethodEnable m) = isEnWhenRdy pps m
  enWhenRdyNode pps _ = False
  
  -- ====================================
  -- aPathsPreSched
  --
  
  -- aPathsPreSched
  --    This is the first half of path analysis.  We construct all the nodes
  --    of the graph (even rules) and add in all edges (except between CanFire
  --    and WillFire signals, which we'll add after we have the scheduler).
  --
  -- Inputs:
  --   APackage
  --     mi = module ID
  --     fmod = is the module wrapped around a non-inlined function
  --     size_ps = size parameters (names in Verilog)
  --     inputs = module inputs (clock and reset, module arg ports)
  --     vs = state elements (Verilog instances)
  --     ds = local defs ([ADefT])
  --     ors = list of rules (in no specified order)
  --     rt = rule tree
  --     ifc = interface methods ([AIFaceT])
  --
  -- Outputs:
  --   Graph information used in the next stage of path computation.
  --   A list of urgency requirements implied by paths, to be used in
  --   scheduling.
  --
  aPathsPreSched :: ErrorHandle -> Flags -> APackage ->
                    IO (PathGraphInfo, PathUrgencyPairs)
  aPathsPreSched errh flags apkg = do
  
    let mi = apkg_name apkg
        -- fmod = apkg_is_wrapped apkg
        size_ps = apkg_size_params apkg
        inputs = apkg_inputs apkg
        wi = apkg_external_wires apkg
        clks = concatMap snd (apkg_clock_domains apkg)
        rsts = map snd (apkg_reset_list apkg)
        vs = apkg_state_instances apkg
        ds = apkg_local_defs apkg
        ors = apkg_rules apkg
        -- rt = apkg_schedule_pragmas apkg
        ifc = apkg_interface apkg
  
    -- ====================
    -- Debug
  
    when trace_apaths $ do
      traceM ("size_ps = " ++ ppReadable size_ps)
      traceM ("inputs = " ++ ppReadable inputs)
      traceM ("vs = " ++ ppReadable vs)
      traceM ("ds = " ++ ppReadable ds)
      traceM ("ifc = " ++ eshowList ifc)
  
    -- ====================
    -- Internal checks
  
  {- Top-level pragmas can mess with module inputs
    -- Inlined functions (fmod == True) do not have clock and reset,
    -- all other generated modules do
    when ((not fmod) && (length inputs < 2)) $
        internalError ("APath.aPathsPreSched: module has too few parameters")
  -}
  
    -- ====================
    -- Separate out the module inputs for clocks, resets, ports, and params
    -- along with more internal checks for consistency
  
    -- (The function call returns the inputs paired with their VArgInfo
    -- and does all the sanity checking on the two fields)
    let inputs_with_arginfo = getAPackageInputs apkg
  
    -- record the order of the arguments, so that we can report args to
    -- the user by their position, and zip with the VArgInfo so we can
    -- identify which are ports, params, etc
    -- XXX top-level arguments should have names, so the numbers aren't
    -- XXX needed now, right?
    let numbered_inputs =
            let pair_to_triple x (y,z) = (x,y,z)
            in  zipWith pair_to_triple [1..] inputs_with_arginfo
  
    -- the Verilog names of the clocks/resets according to the VWireInfo
    let clk_ports = getInputClockPorts (wClk wi)
    let rst_ports = getInputResetPorts (wRst wi)
  
    -- checks against the VWireInfo
    let is_clk i = (id_to_vName i) `elem` clk_ports
        is_rst i = (id_to_vName i) `elem` rst_ports
        is_clk_or_rst i = (is_clk i) || (is_rst i)
  
    -- separate out the input list according to the VArgInfo
    let
        getClkPorts (AAI_Clock osc Nothing) = [osc]
        getClkPorts (AAI_Clock osc (Just gate)) = [osc, gate]
        getClkPorts ai = internalError ("APaths: unexpected clk input: " ++
                                        ppReadable ai)
  
        getRstPort (AAI_Reset r) = r
        getRstPort ai = internalError ("APaths: unexpected rst input: " ++
                                       ppReadable ai)
  
        getSimplePort (AAI_Port p) = p
        getSimplePort ai = internalError ("APaths: unexpected port input: " ++
                                          ppReadable ai)
  
        -- drop the type info on clocks/resets, and the numbering
        clk_inputs = concatMap getClkPorts
                         [ ai | (n, ai, arginfo) <- numbered_inputs,
                                isClock arginfo ]
        rst_inputs = [ getRstPort ai | (n, ai, arginfo) <- numbered_inputs,
                                isReset arginfo ]
        -- keep the numbering and type for ports/params
        port_inputs  = [ (n,p) | (n, ai, arginfo) <- numbered_inputs,
                                  isPort arginfo,
                                  let p = getSimplePort ai ]
        param_inputs = [ (n,p) | (n, ai, arginfo) <- numbered_inputs,
                                  isParam arginfo,
                                  let p = getSimplePort ai ]
  
    -- sanity check the VArgInfo separation with the clks/rsts from VWireInfo
    when (any (not . is_clk) clk_inputs) $
        internalError ("APaths.aPathsPreSched: " ++
                       "clock inputs not in VWireInfo")
    when (any (not . is_rst) rst_inputs) $
        internalError ("APaths.aPathsPreSched: " ++
                       "reset inputs not in VWireInfo")
    when (any (\(n,(i,t)) -> is_clk_or_rst i) (port_inputs ++ param_inputs)) $
        internalError ("APaths.aPathsPreSched: " ++
                       "non-clk/rst inputs found as clk/rst in VWireInfo")
  
    -- We don't handle size parameters yet
    when (not (null size_ps)) $
        internalError "APath.aPathsPreSched: size parameters not supported"
  
  
    -- ====================
    -- Determine the nodes of the graph
  
    let defs = [(i, PNDef i) | (ADef i _ _ _) <- ds]
        def_nodes = map snd defs
  
    -- ----------
  
    let
        -- This produces a list of tuples:
        --  1) Verilog instance name
        --  2) Path info in the form of a list of pairs of VNames
        --  3) Triple for each instantiation argument (no clks or rsts)
        --     1) VName of associated Verilog port
        --     2) Argument position in the arg list (1,2,...)
        --     3) AExpr for the instantiation value
        --  4) A list of tuples for each method:
        --     1) method name
        --     2) input argument list zipped with [1..]
        --     3) (Maybe VName) if method has an enable signal (action method)
        --     4) (Maybe VName) if method has a result (now only value methods)
        --     5) (Maybe Id)    if method has an associated clock
        -- Note: the VPort's are stripped of VeriPortProp to be just VName
        -- XXX is the VeriPortProp info worth keeping?
        state_instances ::
            [ ( AId, [(VName, VName)], [(VName, Integer, AExpr)],
                [(AId, [(VName,Integer)], Maybe VName, Maybe VName, Maybe AId)] ) ]
        state_instances =
            [(inst_id, nns, args, meth_info) |
               avi <- vs,
               let vmi = avi_vmi avi,
               -- instance name
               let inst_id = avi_vname avi,
               let args = getVModInfoArgs avi,
               -- path info pairs
               let (VPathInfo nns) = vPath vmi,
  
               -- instantiation arguments
               -- (for now, we don't track paths through clk and rst)
               -- let (_,_,_,args) = getVModInfoArgs avi,
  
               -- method info
               let meth_info =
                     [(meth_id, numbered_args, maybe_EN, maybe_res, maybe_clk) |
                        vfieldinfo@(Method { vf_name = meth_id }) <- vFields vmi,
                        let args = map fst (vf_inputs vfieldinfo),
                        let numbered_args = zip args [1..],
                        let maybe_EN = (vf_enable vfieldinfo) >>= return . fst,
                        let maybe_res = (vf_output vfieldinfo) >>= return . fst,
                        let maybe_clk = vf_clock vfieldinfo
                     ]
            ]
  
        state_input_nodes =
            [ PNStateMethodArg inst_id meth_id arg_num |
                  (inst_id, _, _, methods) <- state_instances,
                  (meth_id, numbered_args, maybe_EN, maybe_res, _) <- methods,
                  arg_num <- map snd numbered_args
            ]
        state_output_nodes =
            [ PNStateMethodRes inst_id meth_id |
                  (inst_id, _, _, methods) <- state_instances,
                  (meth_id, _, _, Just res_name, _) <- methods
            ]
        state_enable_nodes =
            [ PNStateMethodEnable inst_id meth_id |
                  (inst_id, _, _, methods) <- state_instances,
                  (meth_id, _, Just en_name, _, _) <- methods
            ]
  
        state_arg_nodes =
            [ PNStateArgument inst_id arg_id arg_num |
                  (inst_id, _, arg_pairs, _) <- state_instances,
                  (arg_id, arg_num, _) <- arg_pairs
            ]
  
        state_mux_nodes =
            [ PNStateMethodArgMux inst_id meth_id |
                  (inst_id, _, _, methods) <- state_instances,
                  (meth_id, args, _, _, _) <- methods,
                  not (null args) ]
  
    -- ----------
  
    -- This creates WF and CF nodes for all rule (and method rule) ids,
    -- even though some may be removed from the scheduler (such as
    -- noAction rules).
    let rule_ids =
            -- methods
            [ r_id | (AIAction { aif_body = rs }) <- ifc,
                     (ARule { arule_id = r_id }) <- rs ] ++
            [ r_id | (AIActionValue { aif_body = rs }) <- ifc,
                     (ARule { arule_id = r_id }) <- rs ] ++
            -- rules
            [ r_id | (ARule { arule_id = r_id }) <- ors ]
  
    let can_fire_nodes = [ PNCanFire r_id | r_id <- rule_ids ]
    let will_fire_nodes = [ PNWillFire r_id | r_id <- rule_ids ]
  
    -- ----------
  
    let method_inputs =
            [(arg, PNTopMethodArg m arg) | (AIDef { aif_inputs = args,
                                                    aif_value = (ADef m _ _ _) }) <- ifc,
                                           (arg,_) <- args] ++
            [(arg, PNTopMethodArg m arg) | (AIAction { aif_inputs = args,
                                                       aif_name = m }) <- ifc,
                                           (arg,_) <- args] ++
            [(arg, PNTopMethodArg m arg) | (AIActionValue { aif_inputs = args,
                                                            aif_name = m }) <- ifc,
                                           (arg,_) <- args]
  
        method_outputs =
            [(m, PNTopMethodRes m) | (AIDef { aif_value = (ADef m _ _ _) }) <- ifc] ++
            [(m, PNTopMethodRes m) | (AIActionValue { aif_name = m, aif_value = (ADef m' _ _ _) }) <- ifc]
  
        method_enables =
            -- Name creation is safe, since it is based on VFieldInfo
            [(mkNamedEnable afi, PNTopMethodEnable m) |
                 (AIAction { aif_name = m, aif_fieldinfo = afi }) <- ifc ] ++
            [(mkNamedEnable afi, PNTopMethodEnable m) |
                 (AIActionValue { aif_name = m, aif_fieldinfo = afi }) <- ifc ]
  
        -- port and parameter module args which are not clocks or resets
        module_arg_ports =
            [ (arg_id, PNTopArgument arg_id arg_num) |
                  (arg_num, (arg_id,_)) <- port_inputs ]
        module_arg_params =
            [ (arg_id, PNTopArgument arg_id arg_num) |
                  (arg_num, (arg_id,_)) <- param_inputs ]
  
        clk_wires  = [ (i, PNClk i)
                           | AClock { aclock_osc = ASPort _ i }  <- clks ]
        input_clk_wires = filter (\(i,n) -> i `elem` clk_inputs) clk_wires
  
        gate_wires = [ (i, PNTopClkGate i)
                           | AClock { aclock_gate = ASPort _ i } <- clks ]
        input_clk_gate_wires =
            let -- should not be any which is not an elem of clk_inputs
                isInputClk i = elem i clk_inputs
                non_input_clk_gates =
                    filter (not . isInputClk) (map fst gate_wires)
            in  if (null non_input_clk_gates)
                then gate_wires
                else internalError
                        ("Found non-input clock gates referenced as ASPort: " ++
                         ppReadable non_input_clk_gates)
  
        rstn_wires     = [ (i, PNRstN i)    | AReset { areset_wire = ASPort _ i } <- rsts ]
        input_rstn_wires = filter (\(i,n) -> i `elem` rst_inputs) rstn_wires
  
        method_input_nodes = map snd method_inputs
        method_output_nodes = map snd method_outputs
        method_enable_nodes = map snd method_enables
        module_arg_port_nodes = map snd module_arg_ports
        module_arg_param_nodes = map snd module_arg_params
        clk_nodes            = map snd clk_wires
        input_clk_nodes      = map snd input_clk_wires
        gate_nodes           = map snd gate_wires
        input_clk_gate_nodes = map snd input_clk_gate_wires
        rstn_nodes           = map snd rstn_wires
        input_rstn_nodes     = map snd input_rstn_wires
  
        -- These are internal graph nodes, not part of the interface.
        -- There are separate read methods which become the Verilog ports.
        method_ready_nodes = map (PNTopMethodReady . aIfaceName) ifc
  
    -- ----------
  
        submod_clk_gate_nodes =
            [ (PNStateClkGate i c)
                  | AClock { aclock_gate = AMGate _ i c } <- clks ]
  
    -- ----------
  
    -- We assume there are no paths through tasks and foreign functions
  
    -- ----------
  
    let pathnodes = def_nodes ++
                    state_input_nodes ++ state_output_nodes ++
                    state_enable_nodes ++ state_arg_nodes ++
                    state_mux_nodes ++
                    can_fire_nodes ++ will_fire_nodes ++
                    method_input_nodes ++ method_output_nodes ++
                    method_enable_nodes ++ method_ready_nodes ++
                    module_arg_port_nodes ++ module_arg_param_nodes ++
                    clk_nodes ++ gate_nodes ++ rstn_nodes ++
                    submod_clk_gate_nodes
  
    -- ====================
    -- Determine the edges of the graph
  
    -- XXX could separate the ifc_env into maps for port/param?
    let def_map = M.fromList defs
    let ifc_env = method_inputs ++ method_outputs ++ method_enables ++
                  module_arg_ports ++ module_arg_params ++
                  clk_wires ++ gate_wires ++ rstn_wires
  
    -- internalError asserts that there is no name clash between ASDef and
    -- ASPort/ASParam
    -- (laziness ensures it will not be computed unless necessary)
    let overlap_error a b =
            internalError ("APaths.aPaths': ifc or def overlap: " ++
                           ppReadable a ++ ppReadable b)
  
    -- add ifc_env elements one-by-one,
    -- bailing with an error if we ever need to combine
    let env = foldr (uncurry (M.insertWith overlap_error)) def_map ifc_env
  
    when trace_apaths $
      traceM ("env = " ++ ppReadable (M.toList env))
  
    -- --------------------
    -- Module parameter defaults
  
    -- No paths should be possible for these, so no need to check these exprs
  
    -- --------------------
    -- Defs (ds)
  
    let mkDefEdges (ADef i _ e _) = mkEdges (PNDef i) e env
        def_edges = concatMap mkDefEdges ds
  
    -- --------------------
    -- methods (ifc)
  
    let mkMethodEdges :: AIFace -> [(PathNode,PathNode)]
        mkMethodEdges (AIDef mid inputs wp rdy (ADef m _ e _) _ _) =
            -- connect the rdy expression (likely just an ASDef reference)
            -- to the internal graph node for the method ready
            (mkEdges (PNTopMethodReady m) rdy env) ++
            -- make faux connections from the rdy to the arguments, so that
            -- dependencies in the other direction are caught as loops
            [(PNTopMethodReady m, PNTopMethodArg m arg) | (arg,_) <- inputs] ++
            -- connect the definition to the method result
            -- (this method has no enable, so it cannot contribute to any
            -- methcall argument muxes, so just use "mkEdges")
            (mkEdges (PNTopMethodRes m) e env)
        mkMethodEdges (AIAction inputs wp rdy m rs fi) =
            let rdy_node = PNTopMethodReady m
                en_node  = PNTopMethodEnable m
                mkMRuleEdges (ARule ri _ _ _ rpred actions _ _) =
                  -- Note: rule predicate   -> CanFire
                  --       method predicate -> CanFire
                  --       CanFire          -> method RDY
                  --       method EN        -> WillFire
                  --       WillFire         -> rule action enables
                  -- Connection from CanFire to WillFire added in sched_edges.
                  let cf_node  = PNCanFire ri
                      wf_node  = PNWillFire ri
                  in  -- add edges from rule predicate to the CanFire
                      (mkEdges cf_node rpred env) ++
                      -- add edges from method predicate to CanFire
                      (mkEdges cf_node rdy env) ++
                      -- add edge from the CanFire to method RDY
                      [(cf_node, rdy_node)] ++
                      -- add edge from the method EN to the WillFire
                      [(en_node, wf_node)] ++
                      -- add edges from rule WillFire to ENs in each action
                      (concatMap (mkActionEdges env wf_node) actions)
            in
                -- make faux connections from the rdy to the arguments and the
                -- enable, so that dependencies in the other direction are caught
                -- as loops
                [(rdy_node, en_node)] ++
                [(rdy_node, PNTopMethodArg m arg)
                     | (arg,_) <- inputs] ++
                -- connect the rules
                concatMap mkMRuleEdges rs
  
        mkMethodEdges (AIActionValue inputs wp rdy m rs (ADef m' _ e _) fi) =
            let rdy_node = PNTopMethodReady m
                en_node  = PNTopMethodEnable m
                mkMRuleEdges (ARule ri _ _ _ rpred actions _ _) =
                  -- Note: rule predicate   -> CanFire
                  --       method predicate -> CanFire
                  --       CanFire          -> method RDY
                  --       method EN        -> WillFire
                  --       WillFire         -> rule action enables
                  -- Connection from CanFire to WillFire added in sched_edges.
                  let cf_node  = PNCanFire ri
                      wf_node  = PNWillFire ri
                  in  -- add edges from rule predicate to the CanFire
                      (mkEdges cf_node rpred env) ++
                      -- add edges from method predicate to CanFire
                      (mkEdges cf_node rdy env) ++
                      -- add edge from the CanFire to method RDY
                      [(cf_node, rdy_node)] ++
                      -- add edge from the method EN to the WillFire
                      [(en_node, wf_node)] ++
                      -- add edges from rule WillFire to ENs in each action
                      (concatMap (mkActionEdges env wf_node) actions)
            in
                -- make faux connections from the rdy to the arguments and the
                -- enable, so that dependencies in the other direction are caught
                -- as loops
                [(rdy_node, en_node)] ++
                [(rdy_node, PNTopMethodArg m arg)
                     | (arg,_) <- inputs] ++
                -- connect the definition to the method result
                -- (this method's Enable could contribute to methcall argument
                -- muxes, so use "mkEdgesWithMux")
                (mkEdgesWithMux en_node (PNTopMethodRes m) e env) ++
                 -- connect the rules
                concatMap mkMRuleEdges rs
  
        mkMethodEdges (AIClock {}) = []
        mkMethodEdges (AIReset {}) = []
        mkMethodEdges (AIInout {}) = []
  
        method_edges = concatMap mkMethodEdges ifc
  
    -- --------------------
    -- rules (rt)
  
    let mkRuleEdges (ARule ri _ _ _ rpred actions _ _) =
            -- Note: Connection from canfire to willfire added in sched_edges.
            -- add edge from pred to can_fire
            (mkEdges (PNCanFire ri) rpred env) ++
            -- add edges from will_fire to ENs of actions
            (concatMap (mkActionEdges env (PNWillFire ri)) actions)
  
        rule_edges = concatMap mkRuleEdges ors
  
    -- --------------------
    -- state (connecting paths through instantiated state)
  
    -- Assumptions:
    --  * There is no path from a module's static parameters, clock, or reset
    --    (but there are paths for instantiation arguments which become ports)
  
    let findOutputPathNodes inst_id vname methods =
            [ (clk, PNStateMethodRes inst_id meth_id) |
                  (meth_id, _, _, Just res, clk) <- methods,
                  res == vname
            ]
        findInputPathNodes inst_id vname methods argpairs =
            [ (Nothing, PNStateArgument inst_id arg_id arg_num) |
                  (arg_id, arg_num, _) <- argpairs
            ] ++
            [ (clk, PNStateMethodEnable inst_id meth_id) |
                  (meth_id, _, Just enable, _, clk) <- methods,
                  enable == vname
            ] ++
            [ (clk, PNStateMethodArg inst_id meth_id arg_num) |
                  (meth_id, args, _, _, clk) <- methods,
                  (arg, arg_num) <- args,
                  arg == vname
            ]
  
    -- Connect module inputs and outputs based on the path annotations
    let state_internal_edges =
            [ (pn1, pn2) |
                  (inst_id, pathpairs, argpairs, methods) <- state_instances,
                  (vname1, vname2) <- pathpairs,
                  (clk1,pn1) <- findInputPathNodes inst_id vname1 methods argpairs,
                  (clk2,pn2) <- findOutputPathNodes inst_id vname2 methods,
                  (clk1 == clk2) || (isNothing clk1) || (isNothing clk2)
            ]
  
    -- Connect module instantiation arguments to the expressions provided
    -- at instantiation time for their values.
    let mkStateArgEdge inst_id (arg_id, arg_num, arg_expr) =
            let arg_node = PNStateArgument inst_id arg_id arg_num
            in  mkEdges arg_node arg_expr env
  
        state_arg_edges =
            [ e | (inst_id, _, arg_pairs, _) <- state_instances,
                  arg_pair <- arg_pairs,
                  e <- mkStateArgEdge inst_id arg_pair
            ]
  
    -- Connect the control mux for a method to the arguments of that method
    let state_mux_edges =
           [ (PNStateMethodArgMux inst_id meth_id,
              PNStateMethodArg inst_id meth_id arg_num) |
                 (inst_id, _, _, methods) <- state_instances,
                 (meth_id, args, _, _, _) <- methods,
                 (_, arg_num) <- args ]
  
    -- Combine all the submodule edges
    let state_edges =
            state_arg_edges ++ state_internal_edges ++ state_mux_edges
  
    -- --------------------
    -- scheduling edges (part 1)
  
    -- For all rules (and methods) connect the CAN_FIRE to the WILL_FIRE.
    -- (Except for rules which are dropped, or statically known to never
    -- fire, this has to be true.)
    -- This will let us catch (prior to scheduling) rules whose
    -- pre-condition (CAN_FIRE) depends on the rule's execution (WILL_FIRE).
    -- That kind of dependency is an error.  (The dependency may not matter
    -- if the rule is eventually dropped, but that's no excuse for not
    -- asserting an error.)
  
    let sched_edges =
            -- for rules
            [ (PNCanFire ri, PNWillFire ri) |
                  (ARule { arule_id = ri }) <- ors ] ++
            -- for methods
            [ (PNCanFire ri, PNWillFire ri) |
                  (AIAction { aif_body = rs }) <- ifc,
                  (ARule { arule_id = ri }) <- rs ] ++
            [ (PNCanFire ri, PNWillFire ri) |
                  (AIActionValue { aif_body = rs }) <- ifc,
                  (ARule { arule_id = ri }) <- rs ]
  
    -- --------------------
  
    when trace_apaths $ do
      traceM ("pathnodes = " ++ ppReadable pathnodes)
      traceM ("def_edges = " ++ ppReadable def_edges)
      traceM ("method_edges = " ++ ppReadable method_edges)
      traceM ("rule_edges = " ++ ppReadable rule_edges)
      traceM ("state_edges = " ++ ppReadable state_edges)
      traceM ("sched_edges = " ++ ppReadable sched_edges)
  
    let pathedges = def_edges ++ method_edges ++ rule_edges ++ state_edges ++
                    sched_edges
  
    let pathnodeset = S.fromList pathnodes
  
    -- assert that all nodes in the edges are actually nodes
    let unknown_nodes =
            [ n | (n1,n2) <- pathedges,
                  n <- filter (\x -> not (S.member x pathnodeset)) [n1,n2] ]
    when (not (null unknown_nodes)) $
        internalError ("APath.aPaths': nodes not in graph: " ++
                        show unknown_nodes)
  
    -- ====================
    -- Construct the graph
  
    pathgraph <- makeGraph pathnodes pathedges
  
    -- ====================
    -- Check for cycles
  
    cycles <- findCycles pathgraph
    when (not (null cycles)) $
        convErrorMonadToIO errh $ errPathCycles mi pathgraph cycles
  
    -- ====================
    -- Find urgency restrictions
    -- (a path from WF(r1) to CF(r2) implies r1 more urgent than r2)
  
    -- For urgency to be computed by paths, we must assume a path from
    -- a method's ready signal to its enable signal.
    let rdy_to_en_edges = [(PNTopMethodRes rdy_id, PNTopMethodEnable m_id) |
                               (AIAction { aif_pred = (ASDef _ rdy_id),
                                           aif_name =  m_id, aif_fieldinfo = m_fi }) <- ifc ] ++
                          [(PNTopMethodRes rdy_id, PNTopMethodEnable m_id) |
                               (AIActionValue { aif_pred = (ASDef _ rdy_id),
                                                aif_name =  m_id, aif_fieldinfo = m_fi }) <- ifc ]
  
    pathgraph' <- addEdgesWithNodes pathgraph rdy_to_en_edges
    let reachables = findReachables pathgraph' will_fire_nodes
  
    -- pairs of PathNode
    let urgency_pairs =
            -- edges from WF to CF of rule (or action/actionvalue method)
            [ (wf_rule_id, cf_rule_id, filtered_path) |
                  (PNWillFire wf_rule_id, rs) <- zip will_fire_nodes reachables,
                  cf_node@(PNCanFire cf_rule_id) <- can_fire_nodes,
                  let mpath = lookup cf_node rs,
                  isJust mpath,
                  let filtered_path = reverse (filterPNDefs (fromJust mpath)) ]
            ++
            -- edges from WF to RDY of value method
            [ (wf_rule_id, meth_id, filtered_path) |
                  (PNWillFire wf_rule_id, rs) <- zip will_fire_nodes reachables,
                  (AIDef { aif_value = (ADef meth_id _ _ _) }) <- ifc,
                  let meth_node = (PNTopMethodReady meth_id),
                  let mpath = lookup meth_node rs,
                  isJust mpath,
                  let filtered_path = reverse (filterPNDefs (fromJust mpath)) ]
  
  
    when trace_apaths $ do
      traceM ("urgency_pairs = " ++ ppReadable urgency_pairs)
  
    -- ====================
    -- Return the result
  
    let
        -- just the port-list inputs
        -- no need to report paths from params to output ports
        -- (thus, module_arg_param_nodes is not included)
        -- (if we did, then we'd want to add edges for the default exprs,
        -- which we don't currently do)
        mod_inputs = method_input_nodes ++ method_enable_nodes ++
                     module_arg_port_nodes ++ input_clk_nodes ++
                     input_clk_gate_nodes ++ input_rstn_nodes
        mod_outputs = method_output_nodes
  
    let pathGraphInfo = PathGraphInfo { pgi_graph = pathgraph,
                                        pgi_inputs = mod_inputs,
                                        pgi_outputs = mod_outputs }
    return (pathGraphInfo, urgency_pairs)
  
  
  -- ====================================
  -- aPathsPostSched
  --
  
  -- aPathsPostSched
  --    This is the second half of path analysis, adding scheduling info.
  --    We compute the vPathInfo for vModInfo (paths from inputs to outputs
  --    for the verilog module).  Any cycle in the graph should have been
  --    caught pre-scheduling and scheduling should not have introduced
  --    any, so internal error if any cycles found.
  --
  -- Inputs:
  --   apkg = the package (only the ifc VNames are needed)
  --   scheds = list of schedulers on conflict-free groups of rules
  --   ruleOrd = list of rule IDs in reverse order of execution
  --
  aPathsPostSched :: Flags -> [PProp] -> APackage ->
                     PathGraphInfo -> ASchedule -> IO (VPathInfo)
  aPathsPostSched flags pps apkg pathGraphInfo (ASchedule scheds _) = do
  
    -- ====================
    -- Extract path graph info
  
    let pathgraph0    = pgi_graph pathGraphInfo
        mod_inputs    = filter (not . (enWhenRdyNode pps)) (pgi_inputs pathGraphInfo)
        mod_outputs   = filter (not . (alwaysRdyNode pps)) (pgi_outputs pathGraphInfo)
        user_rule_ids = map aRuleName (apkg_rules apkg)
        rule_ids      = user_rule_ids ++
                        (map aRuleName (concatMap aIfaceRules
                                                  (apkg_interface apkg)))
  
    -- ====================
    -- Schedule connections
  
    let
  
        -- In Esposito, the scheduler can add the following types of paths:
        --   The WF for a user-generated rule can have paths from the
        --     WF of conflicting more urgent rules
        --   The WF for a user-generated rule can have paths from the
        --     RDY of conflicting more urgent value methods
        --   The RDY of a method can have paths from the WF of
        --     conflicting more urgent rules
        mkSchedEdges (ASchedEsposito ss) =
          let isUserRule i = i `elem` user_rule_ids
              isRule     i = i `elem` rule_ids
              ss'        = [(i, partition isRule cs) | (i,cs) <- ss ]
              (rule_sched, meth_sched) = partition (isUserRule . fst) ss'
              rr_edges = -- WF of conflicting rule -> WF of user-rule
                         [ (PNWillFire r2, PNWillFire r1)
                         | (r1,(rcs,_)) <- rule_sched
                         , r2 <- rcs
                         ]
              mr_edges = -- RDY of conflicting value method -> WF of user-rule
                         [ (PNTopMethodReady vm, PNWillFire r)
                         | (r,(_,vmcs)) <- rule_sched
                         , vm <- vmcs
                         ]
              rm_edges = -- WF of conflicting rule -> RDY of a method
                         [ (PNWillFire r, PNTopMethodReady m)
                         | (m,(rcs,_)) <- meth_sched
                         , r <- rcs
                         ]
          in rr_edges ++ mr_edges ++ rm_edges
  
    let sched_edges = concatMap mkSchedEdges scheds
  
    -- --------------------
  
    when trace_apaths $ do
      traceM ("sched_edges = " ++ ppReadable sched_edges)
  
    -- --------------------
  
    -- add the new connections for the scheduler, now that they are known
    -- XXX with nodes may be unnecessary here
    pathgraph <- addEdgesWithNodes pathgraph0 sched_edges
  
    -- ====================
    -- Check for cycles again?
  
    cycles <- findCycles pathgraph
  
    when (not (null cycles)) $
        internalError ("APath: cycles found post scheduling: " ++
                       ppReadable cycles)
  
    -- ====================
    -- Find paths from inputs to outputs
  
    let reachables = findReachables pathgraph mod_inputs
  
    -- pairs of PathNode
    let pathpairs = [ (input, output) |
                          -- we don't need to know the path
                          (input, rs) <- zip mod_inputs reachables,
                          output <- mod_outputs,
                          isJust (lookup output rs) ]
  
    -- --------------------
    -- Convert the path ids from PathNode to VName
  
    -- XXX is this conversion from Id to VName legal?
    let aidToVName i = VName (getIdBaseString i)
  
    -- We don't currently need the argument conversion info, because
    -- the node already contains the converted name (and not the number)
    let meth_info_map = M.fromList $
            [ (meth_id, ({-numbered_args,-} maybe_EN, maybe_res)) |
                meth <- apkg_interface apkg,
                let vfieldinfo = aif_fieldinfo meth,
                let meth_id = vf_name vfieldinfo,
                --let args = map fst (vf_inputs vfieldinfo),
                --let numbered_args = zip args [1..],
                let maybe_EN = (vf_enable vfieldinfo) >>= return . fst,
                let maybe_res = (vf_output vfieldinfo) >>= return . fst
            ]
  
    let findMethod m =
            case (M.lookup m meth_info_map) of
                Just info -> info
                Nothing -> internalError ("APaths findMethod: " ++ ppReadable m)
  
    -- the "arg" is already the VName and not a number
    let convertArg m arg = aidToVName arg
  
    let convertRes m =
            case (findMethod m) of
                (_, Just res) -> res
                _ -> internalError ("APaths convertRes: " ++ ppReadable m)
  
    let convertEnable m =
            case (findMethod m) of
                (Just enable, _) -> enable
                _ -> internalError ("APaths convertEnable: " ++ ppReadable m)
  
    -- convert PathNode back to VName
    let pnToVName (PNTopMethodArg m arg)     = convertArg m arg
        pnToVName (PNTopMethodRes m)         = convertRes m
        pnToVName (PNTopMethodEnable m)      = convertEnable m
        pnToVName (PNTopArgument a _)        = aidToVName a
        pnToVName (PNTopClkGate a)           = aidToVName a
        -- XXX clock and reset?
        pnToVName x = internalError ("APaths.pnToVName: " ++ show x)
  
    -- pairs of VName
    let vname_pathpairs = map (\(a,b) -> (pnToVName a, pnToVName b)) pathpairs
  
    -- --------------------
  
    when trace_apaths $ do
      traceM ("inputs: " ++ ppReadable mod_inputs)
      traceM ("outputs: " ++ ppReadable mod_outputs)
      traceM ("reachables: " ++ ppReadable reachables)
      traceM ("paths: " ++ ppReadable pathpairs)
  
    -- ====================
    -- Return the result
  
    return (VPathInfo vname_pathpairs)
  
  
  -- ====================================
  -- Traversing expressions to find variable use
  --
  
  -- Given an output node and an expression for that node, find all the
  -- contributors to that expression and make connections from the
  -- contributors to the output node.  Also return any edges internal to
  -- the expression (function call inputs to outputs, for example).
  -- Inputs:
  --   * output node
  --   * expression
  --   * environment
  mkEdges :: PathNode -> AExpr -> PathEnv -> [(PathNode, PathNode)]
  mkEdges pn e env =
      let (is, edges, _) = findEdges env e
      in  edges ++ (connectEdge pn is)
  
  -- Same as mkEdges, but used for expressions inside actions,
  -- so that method calls with arguments cause the enable of the action
  -- to be connected to the mux of the arguments.
  -- Inputs:
  --   * action enable node
  --   * output node
  --   * expression
  --   * environment
  mkEdgesWithMux :: PathNode -> PathNode -> AExpr -> PathEnv ->
                    [(PathNode, PathNode)]
  mkEdgesWithMux en pn e env =
      let (is, edges, mux_nodes) = findEdges env e
      in  edges ++ (connectEdge pn is) ++ (connectEdgeR en mux_nodes)
  
  -- Make an edge from all nodes in the list to the input node
  connectEdge :: PathNode -> [PathNode] -> [(PathNode, PathNode)]
  connectEdge pn pns = map (\x -> (x, pn)) pns
  
  -- Make an edge from the input node to all nodes in the list
  connectEdgeR :: PathNode -> [PathNode] -> [(PathNode, PathNode)]
  connectEdgeR pn pns = map (\x -> (pn, x)) pns
  
  
  mkActionEdges :: PathEnv -> PathNode -> AAction ->
                   [(PathNode, PathNode)]
  mkActionEdges env en (ACall state_id qual_meth_id (cond:exprs)) =
      let meth_id = unQualId qual_meth_id
          meth_en = PNStateMethodEnable state_id meth_id
          meth_arg_mux = PNStateMethodArgMux state_id meth_id
          meth_args =
              map (PNStateMethodArg state_id meth_id) [1..]
      in
        -- if the method has arguments, connect the enable signal of the
        -- action to the control mux for the arguments
        (if null exprs then [] else [(en, meth_arg_mux)]) ++
        -- connect the enable of the action to the enable of the method
        [(en, meth_en)] ++
        -- connect the arg expressions to the arguments
        concatMap (\(e,pn) -> mkEdgesWithMux en pn e env)
                  (zip exprs meth_args) ++
        -- connect non-split condition of the call to the enable of the method
        mkEdgesWithMux en meth_en cond env
  
  mkActionEdges env en (AFCall { aact_args = es@(cond:exprs) }) =
      -- XXX right now, we don't track cycles through function calls
      concatMap (snd3 . findEdges env) es
  mkActionEdges env en (ATaskAction { aact_args = es@(cond:exprs) }) =
      -- XXX right now, we don't track cycles through task calls
      concatMap (snd3 . findEdges env) es
  mkActionEdges env en action =
      internalError ("APaths.mkActionEdges: " ++ show (en, action))
  
  
  -- Returns:
  --   * a list of nodes contributing to the result of the
  --     expression (to be connected to the user of the result)
  --   * a list of any connections encountered along the way
  --     (function calls in argument expressions, etc)
  --   * a list of muxes encountered along the way
  --     (to be connected to EN, if this expr is inside an action)
  -- Inputs:
  --   * environment mapping Ids of local defs and module inputs
  --     to their PathNode
  --   * expression to be analyzed
  --
  findEdges :: PathEnv -> AExpr ->
               ([PathNode], [(PathNode, PathNode)], [PathNode])
  findEdges env (APrim i t op es) =
      -- make edge between inputs and output
      concatUnzip3 (map (findEdges env) es)
  findEdges env (AMethCall t i qmi exprs) =
      -- make edges between exprs and meth input
      -- return the output connection
      let mi = unQualId qmi
          -- like mkEdgesWithMux, but want to return the muxes, not connect them
          f (n,exp) = let (is, edges, muxes) = findEdges env exp
                          pn = PNStateMethodArg i mi n
                          es = edges ++ (connectEdge pn is)
                      in  (es, muxes)
          (edges, ms) = concatUnzip (map f (zip [1..] exprs))
          meth_arg_mux = PNStateMethodArgMux i mi
          muxes = if null exprs then ms else meth_arg_mux:ms
      in  ([PNStateMethodRes i mi], edges, muxes)
  findEdges env (AMethValue t i qmi) =
      ([PNStateMethodRes i (unQualId qmi)], [], [])
  findEdges env (ANoInlineFunCall { ae_args = es }) =
      -- return the function call inputs
      -- and return any connections found in the argument expressions
      concatUnzip3 (map (findEdges env) es)
  findEdges env (AFunCall { ae_args = es }) =
      -- return the function call inputs
      -- and return any connections found in the argument expressions
      concatUnzip3 (map (findEdges env) es)
  findEdges env (ATaskValue { }) = ([],[],[])
  -- module port reference
  findEdges env (ASPort t i) =
      case (M.lookup i env) of
          Nothing -> internalError ("findEdges: unknown ASPort: " ++ ppReadable i)
          Just pn -> ([pn],[],[])
  -- module parameter reference
  findEdges env (ASParam t i) =
      case (M.lookup i env) of
          Nothing -> internalError ("findEdges: unknown ASParam: " ++ ppReadable i)
          Just pn -> ([pn],[],[])
  -- ref to local def
  findEdges env (ASDef t i) =
      case (M.lookup i env) of
          Nothing -> internalError ("findEdges: unknown ASDef: " ++ ppReadable i)
          Just pn -> ([pn],[],[])
  findEdges env (ASInt _ _ _) = ([],[],[])
  findEdges env (ASReal _ _ _) = ([],[],[])
  findEdges env (ASStr _ _ _) = ([],[],[])
  -- if we know what the ASAny will turn into,
  -- the combinational paths will exist in the output hardware
  findEdges env (ASAny _ (Just e)) = findEdges env e
  findEdges env (ASAny _ Nothing) = ([],[],[])
  -- clock and reset edges should be irrelevant
  findEdges env (ASClock _ _) = ([],[],[]) -- XXX recurse into the osc and gate?
  findEdges env (ASReset _ _) = ([],[],[]) -- XXX recurse into the rst?
  findEdges env (ASInout _ _) = ([],[],[])
  -- output osc, gate, and reset
  findEdges env (AMGate _ i c) = ([PNStateClkGate i c], [], [])
  
  -- ====================================
  
  -- Converts a list of cycles (sets of SCC) into a list of errors.
  -- PNDefs in the cycle are dropped, because the names are not in the source.
  errPathCycles :: AId -> Graph PathNode -> [[PathNode]] -> ErrorMonad ()
  errPathCycles mid g cycles = EMError (concatMap (errPathCycle mid g) cycles)
  
  -- Report the following cycles:
  --  * Paths from a method's arguments or enable to its ready
  --  * Paths from a rule's WILL_FIRE to its CAN_FIRE
  --  * All other cycles report as a SCC
  errPathCycle :: AId -> Graph PathNode -> [PathNode] -> [EMsg]
  errPathCycle moduleId graph cycle =
      let
          -- display a path (list of PathNode) in user-friendly form
          -- (notice that PNDefs are omitted)
          printPath path = pPrint PDReadable 0 (filterPNDefs path)
  
          -- ---------------
          -- Functions for reporting the different types of errors
  
          -- function to make the default error message
          default_err =
              (getPosition moduleId, EPreSchedCycle (printPath cycle))
  
          -- function to make WF-to-CF error message
          mkWfToCfErr (ruleId, Just path) =
              (getPosition ruleId,
               ESelfUrgency (pfpString ruleId) (printPath path))
          mkWfToCfErr x = internalError
                              ("APaths.errPathCycle: WfToCf: " ++ show x)
  
          mkArgToRdyErr (methodId, argId, Just path) =
              (getPosition moduleId,
               EPathMethodArgToRdy
                   (pfpString methodId) (pfpString argId) (printPath path))
          mkArgToRdyErr x = internalError
                                ("APaths.errPathCycle: ArgToRdy: " ++ show x)
  
          mkEnToRdyErr (methodId, Just path) =
              (getPosition moduleId,
               EPathMethodEnToRdy (pfpString methodId) (printPath path))
          mkEnToRdyErr x = internalError
                               ("APaths.errPathCycle: EnToRdy: " ++ show x)
  
          -- ---------------
  
          method_arg_errs =
              [ mkArgToRdyErr (m1, argId, findPath graph arg rdy) |
                    arg@(PNTopMethodArg m1 argId) <- cycle,
                    rdy@(PNTopMethodReady m2) <- cycle,
                    m1 == m2 ]
  
          method_en_errs =
              [ mkEnToRdyErr (m1, findPath graph en rdy) |
                    en@(PNTopMethodEnable m1) <- cycle,
                    rdy@(PNTopMethodReady m2) <- cycle,
                    m1 == m2 ]
  
          method_errs = method_arg_errs ++ method_en_errs
  
          rule_errs =
              [ mkWfToCfErr (r1, findPath graph wf cf) |
                    wf@(PNWillFire r1) <- cycle,
                    cf@(PNCanFire r2) <- cycle,
                    r1 == r2 ]
  
          -- ---------------
      in
          if not (null method_errs)
          then method_errs
          else if not (null rule_errs)
          then rule_errs
          else [default_err]
  
  
  -- ====================================
  
  eshowList :: (Show a) => [a] -> String
  eshowList [] = "[]"
  eshowList xs =
    let eshowElem :: (Show a) => (a, Integer) -> Doc
        eshowElem (x,i) = text ((itos i) ++ ":") <+> text (show x)
    in  pretty 70 70
          (text "[" <+> foldr1 ($$) (map eshowElem (zip xs [1..])) <+> text "]")
  
  -- --------------------
  
  -- Get port-based instantiation arguments
  getVModInfoArgs :: AVInst -> [(VName, Integer, AExpr)]
  getVModInfoArgs avi = [(port, arg_num, e) | (Port (port,_) _ _, arg_num, e) <- zip3 (vArgs (avi_vmi avi)) [1..] (avi_iargs avi)]
  
  -- ========================================================================
  
  -- Return the module arguments (the ones which turn into ports, not
  -- parameters), as a triple:
  --   * clocks zipped with their instantiation expressions
  --   * clock gates zipped with their clock and instantiation expression
  --   * resets zipped with their instantiation expressions
  --   * non-clocks and non-resets zipped with their argument-list position
  --     and AExpr for the instantiation input expression
  --
  {-
  getVModInfoArgs :: AVInst -> ([(VName, AExpr)],
                                [(VName, VName, AExpr)],
                                [(VName, AExpr)],
                                [(VName, Integer, AExpr)])
  getVModInfoArgs avi =
      let -- VModInfo
          vmi = avi_vmi avi
          -- number of parameters (to be dropped from the AExpr list)
          num_params = vNParam vmi
          -- AExpr inputs to just the arguments (not the parameters)
          input_exprs = genericDrop num_params (avi_iargs avi)
          -- all port arguments (not parameters)
          input_names = vArgs vmi
  
          input_pairs = zip input_names input_exprs
  
          -- figure out which ports are clocks, clock gates, and resets
          clks = map fst (vClk vmi)
          clkgates = [ (gate_name, clk_name) |
                           (clk_name, Just gate_name) <- vClk vmi ]
          rsts = vRst vmi
          isClk i = elem i clks
          isGate i = lookup i clkgates
          isRst i = elem i rsts
  
          -- filter out clocks and resets
          foldfunc p@(i,e) (cs, gs, rs, as) =
              if (isClk i)
              then ((p:cs), gs, rs, as)
              else case (isGate i) of
                       Just clk_id -> let t = (i, clk_id, e)
                                      in  (cs, (t:gs), rs, as)
                       Nothing -> if (isRst i)
                                  then (cs, gs, (p:rs), as)
                                  else (cs, gs, rs, (p:as))
  
          (clk_pairs, gate_triples, rst_pairs, arg_pairs) =
              foldr foldfunc ([],[],[],[]) input_pairs
  
          mkArgTriple (arg_id, arg_expr) arg_num = (arg_id, arg_num, arg_expr)
          arg_triples = zipWith mkArgTriple arg_pairs [1..]
  
      in
          (clk_pairs, gate_triples, rst_pairs, arg_triples)
  -}
  