{-|
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
showList :: [Unit] -> ShowS
$cshowList :: [Unit] -> ShowS
show :: Unit -> String
$cshow :: Unit -> String
showsPrec :: Int -> Unit -> ShowS
$cshowsPrec :: Int -> 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
enumFromThenTo :: Unit -> Unit -> Unit -> [Unit]
$cenumFromThenTo :: Unit -> Unit -> Unit -> [Unit]
enumFromTo :: Unit -> Unit -> [Unit]
$cenumFromTo :: Unit -> Unit -> [Unit]
enumFromThen :: Unit -> Unit -> [Unit]
$cenumFromThen :: Unit -> Unit -> [Unit]
enumFrom :: Unit -> [Unit]
$cenumFrom :: Unit -> [Unit]
fromEnum :: Unit -> Int
$cfromEnum :: Unit -> Int
toEnum :: Int -> Unit
$ctoEnum :: Int -> Unit
pred :: Unit -> Unit
$cpred :: Unit -> Unit
succ :: Unit -> Unit
$csucc :: Unit -> Unit
Enum, Unit
Unit -> Unit -> Bounded Unit
forall a. a -> a -> Bounded a
maxBound :: Unit
$cmaxBound :: Unit
minBound :: Unit
$cminBound :: Unit
Bounded, Unit -> Unit -> Bool
(Unit -> Unit -> Bool) -> (Unit -> Unit -> Bool) -> Eq Unit
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Unit -> Unit -> Bool
$c/= :: Unit -> Unit -> Bool
== :: Unit -> Unit -> Bool
$c== :: 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
min :: Unit -> Unit -> Unit
$cmin :: Unit -> Unit -> Unit
max :: Unit -> Unit -> Unit
$cmax :: Unit -> Unit -> Unit
>= :: Unit -> Unit -> Bool
$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
compare :: Unit -> Unit -> Ordering
$ccompare :: Unit -> Unit -> Ordering
$cp1Ord :: Eq 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
$cto :: forall x. Rep Unit x -> Unit
$cfrom :: forall x. Unit -> Rep Unit x
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
hash :: Unit -> Int
$chash :: Unit -> Int
hashWithSalt :: Int -> Unit -> Int
$chashWithSalt :: Int -> Unit -> Int
$cp1Hashable :: Eq Unit
Hashable, Unit -> ()
(Unit -> ()) -> NFData Unit
forall a. (a -> ()) -> NFData a
rnf :: Unit -> ()
$crnf :: 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
showList :: [Period] -> ShowS
$cshowList :: [Period] -> ShowS
show :: Period -> String
$cshow :: Period -> String
showsPrec :: Int -> Period -> ShowS
$cshowsPrec :: Int -> 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
$cto :: forall x. Rep Period x -> Period
$cfrom :: forall x. Period -> Rep Period x
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
hash :: Period -> Int
$chash :: Period -> Int
hashWithSalt :: Int -> Period -> Int
$chashWithSalt :: Int -> Period -> Int
$cp1Hashable :: Eq Period
Hashable, Period -> Period -> Bool
(Period -> Period -> Bool)
-> (Period -> Period -> Bool) -> Eq Period
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Period -> Period -> Bool
$c/= :: Period -> Period -> Bool
== :: Period -> Period -> Bool
$c== :: Period -> Period -> Bool
Eq, Period -> ()
(Period -> ()) -> NFData Period
forall a. (a -> ()) -> NFData a
rnf :: Period -> ()
$crnf :: 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
showList :: [Scale] -> ShowS
$cshowList :: [Scale] -> ShowS
show :: Scale -> String
$cshow :: Scale -> String
showsPrec :: Int -> Scale -> ShowS
$cshowsPrec :: Int -> 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
$cto :: forall x. Rep Scale x -> Scale
$cfrom :: forall x. Scale -> Rep Scale x
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
hash :: Scale -> Int
$chash :: Scale -> Int
hashWithSalt :: Int -> Scale -> Int
$chashWithSalt :: Int -> Scale -> Int
$cp1Hashable :: Eq Scale
Hashable, Scale -> Scale -> Bool
(Scale -> Scale -> Bool) -> (Scale -> Scale -> Bool) -> Eq Scale
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Scale -> Scale -> Bool
$c/= :: Scale -> Scale -> Bool
== :: Scale -> Scale -> Bool
$c== :: Scale -> Scale -> Bool
Eq, Scale -> ()
(Scale -> ()) -> NFData Scale
forall a. (a -> ()) -> NFData a
rnf :: Scale -> ()
$crnf :: 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 :: Period
step :: Scale -> Period
step, Period
precision :: Period
precision :: Scale -> 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 (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))