packages feed

clashilator-0.1.2: src/Clash/Clashilator.hs

{-# LANGUAGE RecordWildCards, OverloadedStrings, ViewPatterns #-}
{-# LANGUAGE TemplateHaskell, CPP #-}
module Clash.Clashilator (generateFiles) where

import Clash.Driver.Manifest

import Control.Monad (forM_)
import Data.List (partition)

import System.FilePath

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Development.Shake (writeFileChanged)

import Text.Mustache
import qualified Text.Mustache.Compile.TH as TH
import Data.Aeson hiding (Options)
#if MIN_VERSION_aeson(2, 0, 0)
import qualified Data.Aeson.KeyMap as Aeson
#else
import qualified Data.HashMap.Strict as Aeson
#endif

data Port = Port Text Int
    deriving Show

data FFIType
    = FFIBit
    | FFIU8
    | FFIU16
    | FFIU32
    | FFIU64

ffiType :: Int -> FFIType
ffiType 1 = FFIBit
ffiType n
  | n <= 8 = FFIU8
  | n <= 16 = FFIU16
  | n <= 32 = FFIU32
  | n <= 64 = FFIU64
  | otherwise = error $ unwords ["ffiType:", show n]

cType :: FFIType -> Text
cType FFIBit = "bit"
cType FFIU8 = "uint8_t"
cType FFIU16 = "uint16_t"
cType FFIU32 = "uint32_t"
cType FFIU64 = "uint64_t"

hsType :: FFIType -> Text
hsType FFIBit = "Bit"
hsType FFIU8 = "Word8"
hsType FFIU16 = "Word16"
hsType FFIU32 = "Word32"
hsType FFIU64 = "Word64"

cName :: Text -> Text
cName = id

hsName :: Text -> Text -> Text
hsName tag s = tag <> s

getClockAndInPorts :: Maybe Text -> [ManifestPort] -> (Maybe Text, [Port])
getClockAndInPorts clkName inPorts = (clk, [ Port mpName mpWidth | ManifestPort{..} <- inPorts' ])
  where
    (map mpName -> clks, inPorts') = partition mpIsClock inPorts
    clk = case (clkName, clks) of
        (Just clkName, clks) | clkName `elem` clks -> Just clkName
        (Nothing, [clk]) -> Just clk
        _ -> Nothing

portInfo :: Text -> Port -> Value
portInfo tag (Port name width) = object
    [ "cName" .= cName name
    , "cType" .= cType ty
    , "hsName" .= hsName tag name
    , "hsType" .= hsType ty
    ]
  where
    ty = ffiType width

manifestInfo :: Maybe String -> FilePath -> FilePath -> Maybe Text -> Manifest -> Value
manifestInfo cflags srcDir outputDir clkName Manifest{..} = object
    [ "inPorts"      .= (markEnds $ map (portInfo "i") ins)
    , "outPorts"     .= (markEnds $ map (portInfo "o") outs)
    , "clock"        .= fmap (\clock -> object ["cName" .= cName clock]) clock
    , "hdlDir"       .= srcDir
    , "srcs"         .= [ object ["verilogPath" .= TL.pack (srcDir </> T.unpack component <.> "v")]
                        | component <- componentNames
                        ]
    , "verilator"    .= fmap (\cflags -> object [ "cflags" .= TL.strip (TL.pack cflags) ]) cflags
    , "outputDir"    .= outputDir
    ]
  where
#if MIN_VERSION_clash_lib(1, 6, 0)
    inPorts = [ port | port@ManifestPort{..} <- ports, mpDirection `elem` [In, InOut] ]
    outPorts = [ port | port@ManifestPort{..} <- ports, mpDirection `elem` [InOut, Out] ]
#endif

    (clock, ins) = getClockAndInPorts clkName inPorts
    outs = [ Port mpName mpWidth | ManifestPort{..} <- outPorts ]

markEnds :: [Value] -> [Value]
markEnds [] = []
markEnds (v:vs) = markStart v : vs
  where
    markStart (Object o) = Object $ o <> Aeson.fromList [ "first" .= True ]

templates =
    [ ("src/Interface.h", $(TH.compileMustacheFile "template/Interface.h.mustache"))
    , ("src/Impl.cpp", $(TH.compileMustacheFile "template/Impl.cpp.mustache"))
    , ("src/Impl.h", $(TH.compileMustacheFile "template/Impl.h.mustache"))
    , ("Makefile",  $(TH.compileMustacheFile "template/Makefile.mustache"))
    , ("src/Clash/Clashilator/FFI.hsc", $(TH.compileMustacheFile "template/FFI.hsc.mustache"))
    ]

generateFiles :: Maybe String -> FilePath -> FilePath -> Maybe Text -> Manifest -> IO ()
generateFiles cflags inputDir outputDir clkName manifest = do
    let vals = manifestInfo cflags inputDir outputDir clkName manifest
    forM_ templates $ \(fname, template) -> do
        writeFileChanged (outputDir </> fname) $ TL.unpack $ renderMustache template vals