packages feed

copilot-bluespec-4.6: src/Copilot/Compile/Bluespec/CodeGen.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | High-level translation of Copilot Core into Bluespec.
module Copilot.Compile.Bluespec.CodeGen
  ( -- * Type declarations
    mkStructDecln

    -- * Ring buffers
  , mkBuffDecln
  , mkIndexDecln
  , mkAccessDecln

    -- * Stream generators
  , mkGenFun

    -- * External streams
  , mkExtWireDecln

    -- * Monitor processing
  , mkStepRule
  , mkExtRule
  , mkTriggerRule

    -- * Module interface specifications
  , mkSpecIfcFields
  , mkSpecIfcRulesFields
  ) where

-- External imports
import Data.String (IsString (..))
import qualified Language.Bluespec.Classic.AST as BS
import qualified Language.Bluespec.Classic.AST.Builtin.Ids as BS
import qualified Language.Bluespec.Classic.AST.Builtin.Types as BS

-- Internal imports: Copilot
import Copilot.Core

-- Internal imports
import Copilot.Compile.Bluespec.Expr
import Copilot.Compile.Bluespec.External
import Copilot.Compile.Bluespec.Name
import Copilot.Compile.Bluespec.Representation
import Copilot.Compile.Bluespec.Type

-- | Write a generator function for a stream.
mkGenFun :: String -> Expr a -> Type a -> BS.CDefl
mkGenFun name expr ty =
    -- name :: ty
    -- name = expr
    BS.CLValueSign
      (BS.CDef nameId (BS.CQType [] (transType ty)) [def])
      []
  where
    nameId = BS.mkId BS.NoPos $ fromString $ lowercaseName name
    def    = BS.CClause [] [] (transExpr expr)

-- | Bind a @Wire@ variable using @mkBypassWire@.
mkExtWireDecln :: String -> Type a -> BS.CStmt
mkExtWireDecln name ty =
  BS.CSBindT
    (BS.CPVar (BS.mkId BS.NoPos (fromString (wireName name))))
    Nothing
    []
    (BS.CQType [] (tWire `BS.TAp` transType ty))
    (BS.CVar (BS.mkId BS.NoPos "mkBypassWire"))

-- | Bind a buffer variable and initialise it with the stream buffer.
mkBuffDecln :: forall a. Id -> Type a -> [a] -> [BS.CStmt]
mkBuffDecln sId ty xs =
    initVals ++ [BS.CSletrec [initBufSig]]
  where
    -- sId_0     :: Reg <ty> <- mkReg xs_0
    -- ...
    -- sId_(n-1) :: Reg <ty> <- mkReg xs_(n-1)
    initVals = zipWith mkInitVal xs [0..]
    -- sId :: Vector n (Reg <ty>)
    -- sId = update (... (update newVector 0 sId_0) ...) (n-1) sId_(n-1)
    initBufSig = BS.CLValueSign
                   (BS.CDef nameId (BS.CQType [] vecTy) [initBufDef])
                   []
    initBufDef = BS.CClause
                   []
                   []
                   (genVector
                     (\idx _ -> BS.CVar $ BS.mkId BS.NoPos $
                                fromString $ streamElemName sId idx)
                     xs)

    nameId   = BS.mkId BS.NoPos $ fromString $ streamName sId
    bsTy     = tReg `BS.TAp` transType ty
    vecTy    = tVector `BS.TAp` BS.cTNum numElems BS.NoPos `BS.TAp` bsTy
    numElems = toInteger $ length xs

    mkInitVal :: a -> Int -> BS.CStmt
    mkInitVal x elemNum =
        BS.CSBindT
          (BS.CPVar elemId)
          Nothing
          []
          (BS.CQType [] bsTy)
          (BS.CApply (BS.CVar (BS.mkId BS.NoPos "mkReg")) [constTy ty x])
      where
        elemName = streamElemName sId elemNum
        elemId   = BS.mkId BS.NoPos $ fromString elemName

-- | Make an index variable and initialise it to 0.
mkIndexDecln :: Id -> BS.CStmt
mkIndexDecln sId =
  -- sId_idx :: Reg (Bit 64) <- mkReg 0
  BS.CSBindT
    (BS.CPVar nameId)
    Nothing
    []
    (BS.CQType [] bsTy)
    (BS.CApply (BS.CVar (BS.mkId BS.NoPos "mkReg"))
               [cLit $ BS.LInt $ BS.ilDec 0])
  where
    nameId = BS.mkId BS.NoPos $ fromString $ indexName sId
    bsTy   = tReg `BS.TAp` BS.tBitN 64 BS.NoPos

-- | Define an accessor function for the ring buffer associated with a stream
mkAccessDecln :: Id -> Type a -> [a] -> BS.CDefl
mkAccessDecln sId ty xs =
    -- sId_get :: Bits 64 -> ty
    -- sId_get x = (select sId ((sId_idx + x) % buffLength))._read
    BS.CLValueSign (BS.CDef nameId (BS.CQType [] funTy) [def]) []
  where
    def        = BS.CClause [BS.CPVar argId] [] expr
    argTy      = BS.tBit `BS.TAp` BS.cTNum 64 BS.NoPos
    retTy      = transType ty
    funTy      = BS.tArrow `BS.TAp` argTy `BS.TAp` retTy
    name       = streamAccessorName sId
    nameId     = BS.mkId BS.NoPos $ fromString name
    buffLength = cLit $ BS.LInt $ BS.ilDec $ toInteger $ length xs
    argId      = BS.mkId BS.NoPos "x"
    index      = BS.CApply (BS.CVar (BS.idPercentAt BS.NoPos))
                   [ BS.CApply (BS.CVar BS.idPlus)
                       [ BS.CVar (BS.mkId BS.NoPos (fromString (indexName sId)))
                       , BS.CVar argId
                       ]
                   , buffLength
                   ]
    indexExpr  = cIndexVector
                   (BS.CVar (BS.mkId BS.NoPos (fromString (streamName sId))))
                   index
    expr       = BS.CSelect indexExpr (BS.id_read BS.NoPos)

-- | Define fields for a module interface containing a specification's trigger
-- functions and external variables.
mkSpecIfcFields :: [UniqueTrigger] -> [External] -> [BS.CField]
mkSpecIfcFields uniqueTriggers exts =
    concatMap mkTriggerFields uniqueTriggers ++ map mkExtField exts
  where
    -- trigger_guard :: Bool
    -- trigger_arg0 :: arg_ty_0
    -- ...
    -- trigger_arg(n-1) :: arg_ty_(n-1)
    mkTriggerFields :: UniqueTrigger -> [BS.CField]
    mkTriggerFields (UniqueTrigger uniqueName (Trigger _name _ args)) =
        triggerGuardField : triggerArgFields
      where
        triggerGuardField :: BS.CField
        triggerGuardField = mkField (guardName uniqueName) [] BS.tBool

        triggerArgFields :: [BS.CField]
        triggerArgFields =
          zipWith
            (\(UExpr arg _) argName -> mkField argName [] (transType arg))
            args
            (argNames uniqueName)

    -- ext :: ty -> Action
    mkExtField :: External -> BS.CField
    mkExtField (External name ty) =
      mkField
        name
        [ BS.PIPrefixStr ""
        , BS.PIArgNames [BS.mkId BS.NoPos $ fromString $ lowercaseName name]
        ]
        (BS.tArrow `BS.TAp` transType ty `BS.TAp` BS.tAction)

-- | Define fields for a module interface containing the actions to perform for
-- a specification's trigger functions and external variables.
mkSpecIfcRulesFields :: [Trigger] -> [External] -> [BS.CField]
mkSpecIfcRulesFields triggers exts =
    map mkTriggerField triggers ++ map mkExtField exts
  where
    -- trigger_action :: arg_ty_0 -> ... -> arg_ty_(n-1) -> Action
    mkTriggerField :: Trigger -> BS.CField
    mkTriggerField (Trigger name _ args) =
        mkField (actionName name) [] triggerFieldType
      where
        triggerFieldType :: BS.CType
        triggerFieldType = foldr addArgType BS.tAction args

        addArgType :: UExpr -> BS.CType -> BS.CType
        addArgType (UExpr arg _) res =
          BS.tArrow `BS.TAp` transType arg `BS.TAp` res

    -- ext_action :: ActionValue ty
    mkExtField :: External -> BS.CField
    mkExtField (External name ty) =
      mkField (actionName name) [] (BS.tActionValue `BS.TAp` transType ty)

-- | Define a rule for an external stream that performs an action on the most
-- recently computed value from the stream.
mkExtRule :: External -> BS.CRule
mkExtRule (External name _) =
    -- rules
    --   "ext": when True ==>
    --     action
    --       extVal <- ifcRules.ext_action
    --       ifc.ext extVal
    BS.CRule
      []
      (Just $ cLit $ BS.LString name)
      [BS.CQFilter $ BS.CCon BS.idTrue []]
      (BS.Caction BS.NoPos [callExtAction, callExt])
  where
    ifcArgId      = BS.mkId BS.NoPos $ fromString ifcArgName
    ifcRulesArgId = BS.mkId BS.NoPos $ fromString ifcRulesArgName

    extActionId = BS.mkId BS.NoPos $ fromString $ actionName name
    extId       = BS.mkId BS.NoPos $ fromString name
    extValId    = BS.mkId BS.NoPos $ fromString $ name ++ "Val"

    -- extVal <- ifcRules.ext_action
    callExtAction :: BS.CStmt
    callExtAction =
      BS.CSBind
        (BS.CPVar extValId)
        Nothing
        []
        (BS.CSelect (BS.CVar ifcRulesArgId) extActionId)

    -- ifc.ext extVal
    callExt :: BS.CStmt
    callExt =
      BS.CSExpr Nothing $
        BS.CApply (BS.CSelect (BS.CVar ifcArgId) extId) [BS.CVar extValId]

-- | Define a rule for a trigger function that performs an action when the rule
-- fires.
mkTriggerRule :: UniqueTrigger -> BS.CRule
mkTriggerRule (UniqueTrigger uniqueName (Trigger name _ args)) =
    -- rules
    --   "trigger": when ifc.trigger_guard ==>
    --     ifcRules.trigger_action ifc.trigger_arg0
    BS.CRule
      []
      (Just $ cLit $ BS.LString uniqueName)
      [ BS.CQFilter $
          BS.CSelect
            (BS.CVar ifcArgId)
            (BS.mkId BS.NoPos $ fromString $ guardName uniqueName)
      ]
      (BS.CApply actionNameExpr args')
  where
    ifcArgId      = BS.mkId BS.NoPos $ fromString ifcArgName
    ifcRulesArgId = BS.mkId BS.NoPos $ fromString ifcRulesArgName
    -- Note that we use 'name' here instead of 'uniqueName', as 'name' is the
    -- name of the actual external function.
    actionNameId   = BS.mkId BS.NoPos $ fromString $ actionName name
    actionNameExpr = BS.CSelect (BS.CVar ifcRulesArgId) actionNameId

    args'   = take (length args) (map argCall (argNames uniqueName))
    argCall = BS.CSelect (BS.CVar ifcArgId) . BS.mkId BS.NoPos . fromString

-- | Writes the @step@ rule that updates all streams.
mkStepRule :: [Stream] -> Maybe BS.CRule
mkStepRule streams
  | null allUpdates
  = -- If there is nothing to update, don't bother creating a step rule.
    -- Doing so wouldn't harm anything, but bsc will generate a warning
    -- when compiling such an empty rule.
    Nothing
  | otherwise
  = Just $
    BS.CRule
      []
      (Just $ cLit $ BS.LString "step")
      [BS.CQFilter $ BS.CCon BS.idTrue []]
      (BS.Caction BS.NoPos allUpdates)
  where
    allUpdates = bufferUpdates ++ indexUpdates
    (bufferUpdates, indexUpdates) = unzip $ map mkUpdateGlobals streams

    -- Write code to update global stream buffers and index.
    mkUpdateGlobals :: Stream -> (BS.CStmt, BS.CStmt)
    mkUpdateGlobals (Stream sId buff _ _) =
        (bufferUpdate, indexUpdate)
      where
        bufferUpdate =
          BS.CSExpr Nothing $
          BS.Cwrite
            BS.NoPos
            (cIndexVector (BS.CVar buffId) (BS.CVar indexId))
            (BS.CVar genId)

        indexUpdate =
          BS.CSExpr Nothing $
          BS.Cwrite
            BS.NoPos
            (BS.CVar indexId)
            (BS.CApply (BS.CVar (BS.idPercentAt BS.NoPos))
                       [incIndex, buffLength])
          where
            buffLength = cLit $ BS.LInt $ BS.ilDec $ toInteger $ length buff
            incIndex   = BS.CApply (BS.CVar BS.idPlus)
                           [ BS.CVar indexId
                           , cLit $ BS.LInt $ BS.ilDec 1
                           ]

        buffId  = BS.mkId BS.NoPos $ fromString $ streamName sId
        genId   = BS.mkId BS.NoPos $ fromString $ generatorName sId
        indexId = BS.mkId BS.NoPos $ fromString $ indexName sId

-- | Write a struct declaration based on its definition.
mkStructDecln :: Struct a => a -> BS.CDefn
mkStructDecln x =
    BS.Cstruct
      True
      BS.SStruct
      (BS.IdK structId)
      [] -- No type variables
      structFields
      -- Derive a Bits instance so that we can put this struct in a Reg
      [BS.CTypeclass BS.idBits]
  where
    structId     = BS.mkId BS.NoPos $ fromString $ uppercaseName $ typeName x
    structFields = map mkStructField $ toValues x

    mkStructField :: Value a -> BS.CField
    mkStructField (Value ty field) =
      mkField (fieldName field) [] (transType ty)

-- | Write a field of a struct or interface, along with its pragmas and type
-- signature.
mkField :: String -> [BS.IfcPragma] -> BS.CType -> BS.CField
mkField name pragmas ty =
  BS.CField
    { BS.cf_name = BS.mkId BS.NoPos $ fromString $ lowercaseName name
    , BS.cf_pragmas = Just pragmas
    , BS.cf_type = BS.CQType [] ty
    , BS.cf_default = []
    , BS.cf_orig_type = Nothing
    }

-- | The @Reg@ Bluespec interface type.
tReg :: BS.CType
tReg = BS.TCon $
  BS.TyCon
    { BS.tcon_name = BS.idReg
    , BS.tcon_kind = Just (BS.Kfun BS.KStar BS.KStar)
    , BS.tcon_sort = BS.TIstruct (BS.SInterface [])
                                 [BS.id_write BS.NoPos, BS.id_read BS.NoPos]
    }

-- | The @Wire@ Bluespec type.
tWire :: BS.CType
tWire = BS.TCon $
  BS.TyCon
    { BS.tcon_name = BS.mkId BS.NoPos "Wire"
    , BS.tcon_kind = Just (BS.Kfun BS.KStar BS.KStar)
    , BS.tcon_sort = BS.TItype 0 tReg
    }