    {-|
    Copyright  :  (C) 2022,      Google Inc.,
    License    :  BSD2 (see the file LICENSE)
    Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>

    Utilities and definitions to deal with Verilog's time unit. These definitions
    are here mostly to deal with varying @`timescale@ defintions, see:

      https://www.chipverify.com/verilog/verilog-timescale

    -}

    {-# LANGUAGE DeriveAnyClass #-}
    {-# LANGUAGE DeriveGeneric #-}
    {-# LANGUAGE NamedFieldPuns #-}
    {-# LANGUAGE TypeFamilies #-}

    module Clash.Backend.Verilog.Time where

    import Clash.Class.HasDomain.HasSingleDomain
      (TryDomain, TryDomainResult(NotFound))

    import Control.DeepSeq (NFData)
    import Data.Char (toLower, isDigit)
    import Data.Hashable (Hashable)
    import Data.List (find)
    import Data.Word (Word64)
    import GHC.Generics (Generic)
    import Text.Read (readMaybe)

    -- | Verilog time units
    data Unit = Fs | Ps | Ns | Us | Ms | S
      deriving (Int -> Unit -> ShowS
    [Unit] -> ShowS
    Unit -> String
    (Int -> Unit -> ShowS)
    -> (Unit -> String) -> ([Unit] -> ShowS) -> Show Unit
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> Unit -> ShowS
    showsPrec :: Int -> Unit -> ShowS
    $cshow :: Unit -> String
    show :: Unit -> String
    $cshowList :: [Unit] -> ShowS
    showList :: [Unit] -> ShowS
    Show, Int -> Unit
    Unit -> Int
    Unit -> [Unit]
    Unit -> Unit
    Unit -> Unit -> [Unit]
    Unit -> Unit -> Unit -> [Unit]
    (Unit -> Unit)
    -> (Unit -> Unit)
    -> (Int -> Unit)
    -> (Unit -> Int)
    -> (Unit -> [Unit])
    -> (Unit -> Unit -> [Unit])
    -> (Unit -> Unit -> [Unit])
    -> (Unit -> Unit -> Unit -> [Unit])
    -> Enum Unit
    forall a.
    (a -> a)
    -> (a -> a)
    -> (Int -> a)
    -> (a -> Int)
    -> (a -> [a])
    -> (a -> a -> [a])
    -> (a -> a -> [a])
    -> (a -> a -> a -> [a])
    -> Enum a
    $csucc :: Unit -> Unit
    succ :: Unit -> Unit
    $cpred :: Unit -> Unit
    pred :: Unit -> Unit
    $ctoEnum :: Int -> Unit
    toEnum :: Int -> Unit
    $cfromEnum :: Unit -> Int
    fromEnum :: Unit -> Int
    $cenumFrom :: Unit -> [Unit]
    enumFrom :: Unit -> [Unit]
    $cenumFromThen :: Unit -> Unit -> [Unit]
    enumFromThen :: Unit -> Unit -> [Unit]
    $cenumFromTo :: Unit -> Unit -> [Unit]
    enumFromTo :: Unit -> Unit -> [Unit]
    $cenumFromThenTo :: Unit -> Unit -> Unit -> [Unit]
    enumFromThenTo :: Unit -> Unit -> Unit -> [Unit]
    Enum, Unit
    Unit -> Unit -> Bounded Unit
    forall a. a -> a -> Bounded a
    $cminBound :: Unit
    minBound :: Unit
    $cmaxBound :: Unit
    maxBound :: Unit
    Bounded, Unit -> Unit -> Bool
    (Unit -> Unit -> Bool) -> (Unit -> Unit -> Bool) -> Eq Unit
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: Unit -> Unit -> Bool
    == :: Unit -> Unit -> Bool
    $c/= :: Unit -> Unit -> Bool
    /= :: Unit -> Unit -> Bool
    Eq, Eq Unit
    Eq Unit =>
    (Unit -> Unit -> Ordering)
    -> (Unit -> Unit -> Bool)
    -> (Unit -> Unit -> Bool)
    -> (Unit -> Unit -> Bool)
    -> (Unit -> Unit -> Bool)
    -> (Unit -> Unit -> Unit)
    -> (Unit -> Unit -> Unit)
    -> Ord Unit
    Unit -> Unit -> Bool
    Unit -> Unit -> Ordering
    Unit -> Unit -> Unit
    forall a.
    Eq a =>
    (a -> a -> Ordering)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> Bool)
    -> (a -> a -> a)
    -> (a -> a -> a)
    -> Ord a
    $ccompare :: Unit -> Unit -> Ordering
    compare :: Unit -> Unit -> Ordering
    $c< :: Unit -> Unit -> Bool
    < :: Unit -> Unit -> Bool
    $c<= :: Unit -> Unit -> Bool
    <= :: Unit -> Unit -> Bool
    $c> :: Unit -> Unit -> Bool
    > :: Unit -> Unit -> Bool
    $c>= :: Unit -> Unit -> Bool
    >= :: Unit -> Unit -> Bool
    $cmax :: Unit -> Unit -> Unit
    max :: Unit -> Unit -> Unit
    $cmin :: Unit -> Unit -> Unit
    min :: Unit -> Unit -> Unit
    Ord, (forall x. Unit -> Rep Unit x)
    -> (forall x. Rep Unit x -> Unit) -> Generic Unit
    forall x. Rep Unit x -> Unit
    forall x. Unit -> Rep Unit x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. Unit -> Rep Unit x
    from :: forall x. Unit -> Rep Unit x
    $cto :: forall x. Rep Unit x -> Unit
    to :: forall x. Rep Unit x -> Unit
    Generic, Eq Unit
    Eq Unit => (Int -> Unit -> Int) -> (Unit -> Int) -> Hashable Unit
    Int -> Unit -> Int
    Unit -> Int
    forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
    $chashWithSalt :: Int -> Unit -> Int
    hashWithSalt :: Int -> Unit -> Int
    $chash :: Unit -> Int
    hash :: Unit -> Int
    Hashable, Unit -> ()
    (Unit -> ()) -> NFData Unit
    forall a. (a -> ()) -> NFData a
    $crnf :: Unit -> ()
    rnf :: Unit -> ()
    NFData)

    type instance TryDomain t Unit = 'NotFound

    -- | Verilog time period. A combination of a length and a unit.
    data Period = Period Word64 Unit
      deriving (Int -> Period -> ShowS
    [Period] -> ShowS
    Period -> String
    (Int -> Period -> ShowS)
    -> (Period -> String) -> ([Period] -> ShowS) -> Show Period
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> Period -> ShowS
    showsPrec :: Int -> Period -> ShowS
    $cshow :: Period -> String
    show :: Period -> String
    $cshowList :: [Period] -> ShowS
    showList :: [Period] -> ShowS
    Show, (forall x. Period -> Rep Period x)
    -> (forall x. Rep Period x -> Period) -> Generic Period
    forall x. Rep Period x -> Period
    forall x. Period -> Rep Period x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. Period -> Rep Period x
    from :: forall x. Period -> Rep Period x
    $cto :: forall x. Rep Period x -> Period
    to :: forall x. Rep Period x -> Period
    Generic, Eq Period
    Eq Period =>
    (Int -> Period -> Int) -> (Period -> Int) -> Hashable Period
    Int -> Period -> Int
    Period -> Int
    forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
    $chashWithSalt :: Int -> Period -> Int
    hashWithSalt :: Int -> Period -> Int
    $chash :: Period -> Int
    hash :: Period -> Int
    Hashable, Period -> Period -> Bool
    (Period -> Period -> Bool)
    -> (Period -> Period -> Bool) -> Eq Period
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: Period -> Period -> Bool
    == :: Period -> Period -> Bool
    $c/= :: Period -> Period -> Bool
    /= :: Period -> Period -> Bool
    Eq, Period -> ()
    (Period -> ()) -> NFData Period
    forall a. (a -> ()) -> NFData a
    $crnf :: Period -> ()
    rnf :: Period -> ()
    NFData)

    -- | Verilog timescale. Influences simulation precision.
    data Scale = Scale
      { -- | Time step in wait statements, e.g. `#1`.
        Scale -> Period
    step :: Period

        -- | Simulator precision - all units will get rounded to this period.
      , Scale -> Period
    precision :: Period
      }
      deriving (Int -> Scale -> ShowS
    [Scale] -> ShowS
    Scale -> String
    (Int -> Scale -> ShowS)
    -> (Scale -> String) -> ([Scale] -> ShowS) -> Show Scale
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> Scale -> ShowS
    showsPrec :: Int -> Scale -> ShowS
    $cshow :: Scale -> String
    show :: Scale -> String
    $cshowList :: [Scale] -> ShowS
    showList :: [Scale] -> ShowS
    Show, (forall x. Scale -> Rep Scale x)
    -> (forall x. Rep Scale x -> Scale) -> Generic Scale
    forall x. Rep Scale x -> Scale
    forall x. Scale -> Rep Scale x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. Scale -> Rep Scale x
    from :: forall x. Scale -> Rep Scale x
    $cto :: forall x. Rep Scale x -> Scale
    to :: forall x. Rep Scale x -> Scale
    Generic, Eq Scale
    Eq Scale =>
    (Int -> Scale -> Int) -> (Scale -> Int) -> Hashable Scale
    Int -> Scale -> Int
    Scale -> Int
    forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
    $chashWithSalt :: Int -> Scale -> Int
    hashWithSalt :: Int -> Scale -> Int
    $chash :: Scale -> Int
    hash :: Scale -> Int
    Hashable, Scale -> Scale -> Bool
    (Scale -> Scale -> Bool) -> (Scale -> Scale -> Bool) -> Eq Scale
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: Scale -> Scale -> Bool
    == :: Scale -> Scale -> Bool
    $c/= :: Scale -> Scale -> Bool
    /= :: Scale -> Scale -> Bool
    Eq, Scale -> ()
    (Scale -> ()) -> NFData Scale
    forall a. (a -> ()) -> NFData a
    $crnf :: Scale -> ()
    rnf :: Scale -> ()
    NFData)

    -- | Pretty print 'Scale' to Verilog `timescale
    --
    -- >>> scaleToString (Scale (Period 100 Ps) (Period 10 Fs))
    -- "`timescale 100ps/10fs"
    --
    scaleToString :: Scale -> String
    scaleToString :: Scale -> String
    scaleToString (Scale{Period
    step :: Scale -> Period
    step :: Period
    step, Period
    precision :: Scale -> Period
    precision :: Period
    precision}) =
      String
    "`timescale " String -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> Period -> String
    periodToString Period
    step String -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> String
    "/" String -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> Period -> String
    periodToString Period
    precision

    -- | Convert 'Unit' to Verilog time unit
    --
    -- >>> periodToString (Period 100 Fs)
    -- "100fs"
    --
    periodToString :: Period -> String
    periodToString :: Period -> String
    periodToString (Period Word64
    len Unit
    unit) = Word64 -> String
    forall a. Show a => a -> String
    show Word64
    len String -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> Unit -> String
    unitToString Unit
    unit

    -- | Convert 'Unit' to Verilog time unit
    --
    -- >>> unitToString Ms
    -- "ms"
    --
    unitToString :: Unit -> String
    unitToString :: Unit -> String
    unitToString = (Char -> Char) -> ShowS
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toLower ShowS -> (Unit -> String) -> Unit -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Unit -> String
    forall a. Show a => a -> String
    show

    -- | Parse string representing a Verilog time unit to 'Unit'.
    --
    -- >>> parseUnit "ms"
    -- Just Ms
    -- >>> parseUnit "xs"
    -- Nothing
    --
    parseUnit :: String -> Maybe Unit
    parseUnit :: String -> Maybe Unit
    parseUnit String
    s = (Unit -> Bool) -> [Unit] -> Maybe Unit
    forall (t :: Type -> Type) a.
    Foldable t =>
    (a -> Bool) -> t a -> Maybe a
    find Unit -> Bool
    tryUnit [Unit
    forall a. Bounded a => a
    minBound..]
     where
      tryUnit :: Unit -> Bool
      tryUnit :: Unit -> Bool
    tryUnit Unit
    u = Unit -> String
    unitToString Unit
    u String -> String -> Bool
    forall a. Eq a => a -> a -> Bool
    == String
    s

    -- | Parse a Verilog
    --
    -- >>> parsePeriod "100ms"
    -- Just (Period 100 Ms)
    -- >>> parsePeriod "100xs"
    -- Nothing
    -- >>> parsePeriod "100"
    -- Nothing
    -- >>> parsePeriod "ms"
    -- Nothing
    --
    parsePeriod :: String -> Maybe Period
    parsePeriod :: String -> Maybe Period
    parsePeriod String
    s =
      case (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span Char -> Bool
    isDigit String
    s of
        (String
    len0, String
    unit0) -> do
          Word64
    len1 <- String -> Maybe Word64
    forall a. Read a => String -> Maybe a
    readMaybe String
    len0
          Unit
    unit1 <- String -> Maybe Unit
    parseUnit String
    unit0
          Period -> Maybe Period
    forall a. a -> Maybe a
    forall (f :: Type -> Type) a. Applicative f => a -> f a
    pure (Word64 -> Unit -> Period
    Period Word64
    len1 Unit
    unit1)

    -- | Convert a period to a specific time unit. Will always output a minimum
    -- of 1, even if the given 'Period' is already of the right 'Unit'.
    --
    -- >>> convertUnit Ps (Period 100 Ps)
    -- 100
    -- >>> convertUnit Fs (Period 100 Ps)
    -- 100000
    -- >>> convertUnit Ns (Period 100 Ps)
    -- 1
    -- >>> convertUnit Ms (Period 0 Ms)
    -- 1
    --
    convertUnit :: Unit -> Period -> Word64
    convertUnit :: Unit -> Period -> Word64
    convertUnit Unit
    targetUnit = Period -> Word64
    go
     where
      go :: Period -> Word64
    go (Period Word64
    len Unit
    unit) =
        case Unit -> Unit -> Ordering
    forall a. Ord a => a -> a -> Ordering
    compare Unit
    unit Unit
    targetUnit of
          Ordering
    LT -> Period -> Word64
    go (Word64 -> Unit -> Period
    Period (Word64
    len Word64 -> Word64 -> Word64
    forall a. Integral a => a -> a -> a
    `div` Word64
    1000) (Unit -> Unit
    forall a. Enum a => a -> a
    succ Unit
    unit))
          Ordering
    EQ -> Word64 -> Word64 -> Word64
    forall a. Ord a => a -> a -> a
    max Word64
    1 Word64
    len
          Ordering
    GT -> Period -> Word64
    go (Word64 -> Unit -> Period
    Period (Word64
    len Word64 -> Word64 -> Word64
    forall a. Num a => a -> a -> a
    * Word64
    1000) (Unit -> Unit
    forall a. Enum a => a -> a
    pred Unit
    unit))