Search code examples
haskelltype-level-computationclash

Type-level conversion between period length and frequency


For ease of reading, I'd like to use clock frequency values as my type indices. However, I'll need to then check these for compatibility with clock domains, which represent their speed as period lengths.

To make this a bit more concrete, suppose I have the following datatype which, for readability purposes, uses the clock rate (in Hz) in its type; in a real program, this could be e.g. VGA timings:

data Rate (rate :: Nat) = MkRate

rate :: Rate 25175000
rate = MkRate

However, I need to use it together with types that represent the clock period (in picoseconds); in a real CLaSH program, that would be the clock domain itself.

data Period (ps :: Nat) = MkPeriod

period :: Period 39721
period = MkPeriod

And now the problems start, since one period at 25.175 MHz is not an integer number of picoseconds. First, let's try using multiplication:

connect1 :: ((rate * ps) ~ 1000000000000) => Rate rate -> Period ps -> ()
connect1 _ _ = ()

test1 :: ()
test1 = connect1 rate period

This fails in the expected way:

../clash-sandbox/lib/src-clash/Cactus/Clash/VGA.hs:79:9: error:
    • Couldn't match type ‘999976175000’ with ‘1000000000000’
        arising from a use of ‘connect1’
    • In the expression: connect1 rate period
      In an equation for ‘test1’: test1 = connect1 rate period
   |
79 | test1 = connect1 rate period
   |         ^^^^^^^^^^^^^^^^^^^^

Another thing we could try is type-level division:

connect2 :: (ps ~ (1000000000000 `div` rate)) => Rate rate -> Period ps -> ()
connect2 _ _ = ()

test2 :: ()
test2 = connect2 rate period

but that doesn't seem to reduce:

../clash-sandbox/lib/src-clash/Cactus/Clash/VGA.hs:85:9: error:
    • Couldn't match type ‘39721’ with ‘div0 1000000000000 25175000’
        arising from a use of ‘connect2’
    • In the expression: connect2 rate period
      In an equation for ‘test2’: test2 = connect2 rate period

Solution

  • Turns out the ghc-typelits-extra package has division which reduces; this allowed me to write

    connect :: (ps ~ (1000000000000 `Div` rate)) => Rate rate -> Period ps -> ()
    
    test = connect rate period
    

    and it typechecks OK.