Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An UART in CλaSH v0.7
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-
- Copyright (c) 2015, 2017 Peter Lebbing <peter@digitalbrains.com>
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
-}
module Toolbox.ClockScale
( rateParams
, State(..)
, avg
, clkDiv
, oneShot
, staticAvg
, staticClkDiv
, staticOneShot
, staticAvgRate
, staticMaxRate
, staticOneShotPeriod
, ticksMinPeriod
, ticksMinPeriodTH
, ticksMaxRate
, ticksMaxRateTH
, nonNeg
) where
import CLaSH.Prelude
import Language.Haskell.TH
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import Toolbox.Misc
{-
- Compute the multiplier and divisor needed for the clock scaler to scale from
- the clockrate "from" to ticks with a rate of "to"
-
- This is not meant to be compiled to VHDL; instead, use it to create the
- parameters and use those parameters directly in the design. An example of
- this is the convenient "staticAvgRate" function in this module that can be
- instantiated with Template Haskell.
-}
rateParams :: Integer -> Integer -> (Integer, Integer)
rateParams from to = (m, d)
where
common = lcm from to
m = common `div` from
d = common `div` to
data State = Stop | Run | Clear
deriving (Eq, Show, Generic, NFData)
{-
- Scale a clock frequency as accurately as possible
-
- Outputs True once every "tick" of the desired frequency by counting and
- scaling the system clock.
-
- Inputs: m - Multiplier for the system clock d - Divisor for the target clock
-
- The counter never goes to 0; it is most accurate when initialised to 1.
-
- Make sure the target clock is lower than the system clock, or you will miss
- ticks. Not surprising, really :).
-
- Also, the degenerate case where the divisor is 1 (or equivalently, the
- target clock is equal to the system clock) *will not stop* when given the
- command Stop or Clear. Don't use this configuration; the following would
- probably be more what you need:
-
- tick = (== ClockScale.Run) <$> cmd
-
- Note that it is designed to be instantiated with a static divisor and
- multiplier. Changing them while running will cause glitching.
-}
avg :: (KnownNat n, KnownNat (n+1))
=> Unsigned (n+1)
-> (Unsigned n, Unsigned n, State)
-> (Unsigned (n+1), Bool)
avg s (m, d, cmd) = (s', o)
where
sinc = s + resize m
wrap = s >= resize d
s' = if cmd == Clear then
1
else if wrap then
sinc - resize d
else if cmd == Stop then
s
else
sinc
o = wrap
{- Divide the clock by an integer ratio
-
- See `avg` for more documentation.
-}
clkDiv :: KnownNat n
=> Unsigned n
-> (Unsigned n, State)
-> (Unsigned n, Bool)
clkDiv s (d, cmd) = (s', o)
where
wrap = s >= d
s' = if cmd == Clear then
1
else if wrap then
1
else if cmd == Stop then
s
else
s + 1
o = wrap
{-
- When triggered, count to d once, then stop
-
- Outputs whether d has been reached yet. Most often used to make sure an
- amount of work does not take less time than counting to d: trigger when
- you start the work, and then wait for the oneShot to output True when the
- work is done. If the amount of work took more than the allotted time,
- this makes it continue immediately; otherwise it will wait.
-}
oneShot :: KnownNat n
=> Unsigned n
-> (Unsigned n, Bool)
-> (Unsigned n, Bool)
oneShot s (d, trigger) = (s', done)
where
done = s >= d
s' = if trigger then
1
else if done then
d
else
s + 1
{-
- Instantiate a clock scaler with fixed parameters through Template Haskell
-
- The needed amount of bits in the state of the scaler is automatically
- determined from the parameters.
-}
staticAvg (m, d) = appE (appE [| staticAvg' |] (fitU m)) (fitU d)
staticClkDiv d = appE [| staticClkDiv' |] (fitU d)
{-
- Instantiate a oneShot with a fixed divider through Template Haskell
-}
staticOneShot d = appE [| staticOneShot' |] (fitU d)
{-
- Through Template Haskell, instantiate a clock scaler that converts clock
- rate `from` to ticks with a rate of `to`, with static parameters computed at
- compile time.
-}
staticAvgRate from to = staticAvg $ rateParams from to
{-
- Through Template Haskell, instantiate a clock divider that never exceeds the
- rate `to`.
-
- `staticAvgRate` can temporarily exceed the given rate (by one clock tick) to
- compensate for time lost. `staticMaxRate` however will never do that, but
- this means it will run slow if the target rate does not evenly divide the
- clock frequency.
-}
staticMaxRate from to = staticClkDiv $ ticksMaxRate from to
{-
- Instantiate a oneShot that waits for the specified period through
- Template Haskell
-
- `f` is the clock frequency, `p` the period.
-}
staticOneShotPeriod f p = staticOneShot $ ticksMinPeriod f p
{-
- Compute how many clock ticks need to pass before a certain period of time
- is reached
-
- `f` is the clock frequency, `p` the period.
-}
ticksMinPeriod f p = ceiling $ (fromInteger f) * p - 1
-- Shorthand for Template Haskell instantiation
ticksMinPeriodTH f p = intLit $ ticksMinPeriod f p
{-
- Compute which clock divider approximates the given rate as closely as
- possible without exceeding it
-}
ticksMaxRate from to = ticksMinPeriod from (1 / to)
-- Shorthand for Template Haskell instantiation
ticksMaxRateTH from to = intLit $ ticksMaxRate from to
{-
- When computing a delay in clock ticks, this function assures that when the
- result of the computation is negative, no delay is done (a delay of 0).
-
- Example: $(nonNeg $(ticksMinPeriod fClk 100e-9) - 4)
- Computes the extra delay to have a total delay of 100 ns of which 4 clock
- ticks have already been spent. If 4 clock ticks are more than 100 ns,
- don't delay.
-}
nonNeg = intLit . max 0
staticAvg' :: ( KnownNat (Max m n), KnownNat (Max m n + 1), KnownNat n
, KnownNat m)
=> Unsigned n
-> Unsigned m
-> Unsigned (Max m n + 1)
-> State
-> (Unsigned (Max m n + 1), Bool)
staticAvg' m d st cmd = avg st (resize m, resize d, cmd)
staticClkDiv' d s cmd = clkDiv s (d, cmd)
staticOneShot' d s trigger = oneShot s (d, trigger)
{-
- Copyright (c) 2012-2015, University of Twente
- Copyright (c) 2015,2017, Peter Lebbing <peter@digitalbrains.com>
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
-
- This file is based on examples/Fifo.hs from the CλaSH source code.
-}
module Toolbox.FIFO
( Pntr
, fifo
) where
import CLaSH.Prelude
import Control.Applicative
import Toolbox.Misc (showCodeLoc)
flowError = False
type Pntr n = Unsigned (n + 1)
{-
- This is the same FIFO implementation as from the CλaSH examples, with an
- added output: the number of elements in the FIFO can be examined. This could
- be useful for Dataflow elements that need multiple tokens to proceed.
-}
fifo :: forall n e . (KnownNat n, KnownNat (n+1), KnownNat (2^n))
=> (Pntr n, Pntr n, Vec (2^n) e)
-> (e, Bool, Bool)
-> ((Pntr n,Pntr n,Vec (2^n) e),(Bool,Bool,Pntr n, e))
fifo (rpntr, wpntr, elms) (datain, wrt, rd)
= ((rpntr', wpntr', elms'),(full, empty, flength, dataout))
where
wpntr' | wrt && not full = wpntr + 1
| wrt && full && flowError = error "FIFOn Overflow!"
| otherwise = wpntr
rpntr' | rd && not empty = rpntr + 1
| rd && empty && flowError = error "FIFOn Underflow!"
| otherwise = rpntr
mask = maxBound `shiftR` 1
wind = wpntr .&. mask
rind = rpntr .&. mask
elms' | wrt = replace wind datain elms
| otherwise = elms
n = fromInteger $ natVal rpntr - 1
flength = wpntr - rpntr
empty = flength == 0
full = flength == bit n
dataout = elms!!rind
{-
- Copyright (c) 2015, 2017 Peter Lebbing <peter@digitalbrains.com>
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
-}
module Toolbox.Misc
( showCodeLoc
, intLit
, uToFit
, fitU
, vS
, zip4
, zip5
, unzip4
, edgeTrigger
, tfold
, tfoldD
, vv
) where
import Language.Haskell.TH
import Control.Applicative
--import Data.Tuple.Select
import Unsafe.Coerce
import CLaSH.Prelude hiding (tfold)
import CLaSH.Signal.Bundle
import CLaSH.Signal.Explicit (Signal')
{- A Template Haskell string literal with the location of the invocation of
- showCodeLoc
-}
showCodeLoc = do loc <- location
litE $ StringL (show loc)
-- Shorthand for creating an integer literal
intLit :: Integer -> ExpQ
intLit = litE . integerL
{- Construct an Unsigned n type with n just large enough to be able to
- represent the number i
-}
uToFit :: Integer -> TypeQ
uToFit i = (appT (conT ''Unsigned)
( litT $ numTyLit $ toInteger $ max 1 $ floor
$ 1 + logBase 2 (fromInteger i)))
{- Convert an Integer expression to a constant of type "Unsigned n", with n
- being just large enough to contain the value
-}
fitU :: Integer -> ExpQ
fitU i = sigE (intLit i) (uToFit i)
{- Construct a vector from a list of signal names
- Example: $(vS ['s1, 's2, 's3]) is a vector of the three signals s1, s2 and
- s3.
-}
vS :: [Name] -> ExpQ
vS [] = [| pure Nil |]
vS (x:xs) = [| liftA2 (:>) $(varE x) $(vS xs) |]
zip4 as bs cs ds = (zipWith (\a (b,c,d) -> (a,b,c,d)) as) (zip3 bs cs ds)
zip5 as bs cs ds es
= (zipWith (\a (b,c,d,e) -> (a,b,c,d,e)) as) (zip4 bs cs ds es)
unzip4 :: Vec n (a,b,c,d) -> (Vec n a, Vec n b, Vec n c, Vec n d)
unzip4 xs = (as, bs, cs, ds)
where
-- as = map (sel1) xs
-- bs = map (sel2) xs
-- cs = map (sel3) xs
-- ds = map (sel4) xs
as = map (\(a, b, c, d) -> a) xs
bs = map (\(a, b, c, d) -> b) xs
cs = map (\(a, b, c, d) -> c) xs
ds = map (\(a, b, c, d) -> d) xs
-- Output True when input changes
edgeTrigger :: Eq a
=> a
-> a
-> (a, Bool)
edgeTrigger s i = (i, s /= i)
{- Fold a signal over time
-
- tfold f z (x, start)
- If start is True, re-initialise using (f z x)
-}
tfold :: (a -> a -> a)
-> a
-> (Signal a, Signal Bool)
-> Signal a
tfold f z = mealy (tfold' f z) z . bundle
tfold' f z s (x1, start) = (s', s')
where
x0 | start = z
| otherwise = s
s' = f x0 x1
-- tfold with built-in one-cycle delay element
tfoldD :: (a -> a -> a)
-> a
-> (Signal a, Signal Bool)
-> Signal a
tfoldD f z = moore (tfoldD' f z) id z . bundle
tfoldD' f z s (x1, start) = f x0 x1
where
x0 | start = z
| otherwise = s
-- TH vector of vectors
vv :: Lift a => [[a]] -> ExpQ
vv [] = [| Nil |]
vv (e:es) = [| $(listToVecTH e) :> $(vv es) |]
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-
- Copyright (c) 2015, 2017 Peter Lebbing <peter@digitalbrains.com>
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
-}
module Toolbox.Serial where
import CLaSH.Prelude
import Control.Applicative
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import qualified Toolbox.ClockScale as CS
import qualified Toolbox.FIFO as FIFO
{-
- Serial data output routine
-
- This routine will send bytes over a serial line according to the
- asynchronous start/stop protocol. It only supports the dataformat 8N1 (8
- databits, no parity, 1 stopbit).
-
- Inputs: (ck, ld, din):
- - ck: This should be the output of a ClockScaler that scales to the
- bitclock or baud rate of the serial line. It is True once every
- period of the baud rate.
- - ld: True if there's a new piece of data on din. Should only be
- asserted when output "done" is also true.
- - din: 8-bit word to send out
-
- Outputs: (scaler, done, txd):
- - scaler: a ClockScale.State that controls the ClockScaler that does
- the pacing for this serial output.
- - done: True when new data can be accepted because the previous data
- has been sent.
- - txd: The actual output line where data is sent using asynchronous
- start/stop.
-}
output :: (Signal Bool, Signal Bool, Signal (Unsigned 8))
-> (Signal CS.State, Signal Bool, Signal Bit)
output = mealyB output' (repeat 0)
output' :: Vec 10 Bit
-> (Bool, Bool, Unsigned 8)
-> (Vec 10 Bit, (CS.State, Bool, Bit))
output' shifter (ck, ld, din) = (shifter', (scaler, done, txd))
where
shifter' = if ld then
1 :> (bitCoerce din :< 0)
else if ck then
0 +>> shifter
else
shifter
-- CλaSH issue #202 prevents the next line from working
-- (scaler, done) = if shifter == repeat 0 then
(scaler, done) = if bitCoerce shifter == (0 :: Unsigned 10) then
(CS.Clear, True)
else
(CS.Run , False)
txd = if done then
1
else
last shifter
{-
- A variant of "output" that reads its data from a FIFO
-}
outputFIFO (ck, empty, din) = (ld, scaler, txd)
where
(scaler, done, txd) = output (ck, ld, din)
ld = (\(e, d) -> not e && d) <$> bundle (empty, done)
data State = WaitStart | Sample (Unsigned 2) | WaitHigh
deriving (Show, Generic, NFData)
{- Asynchronous start/stop receiver
-
- Frame format 8N1 only
-
- Inputs (cTick, dIn):
- - cTick: ticks from a continuously running ClockScaler that runs at 16
- times the baud rate of the incoming serial line. This divides one
- bitperiod in 16 equal parts which are used for the sampling of the
- line.
- - dIn: the pin of the FPGA the serial data is to be read from.
-
- Outputs ((frameErr, dOut), dValid):
- - dValid: True if (frameErr, dOut) is valid
- - frameErr: True if the stopbit was not "mark" (i.e., it was "space").
- This means something went wrong in reception, so the data might be
- corrupted as well.
- - dOut: The received databit
-
- Majority sampling principle: 3 samples are taken around the center of the
- databit, a majority counter counts the number of high samples. If this
- counter is 2 or 3, the most significant bit is high, otherwise, it is low.
- In other words, the most significant bit is the outcome of the majority
- vote.
-}
input :: (Signal Bool, Signal Bit) -> (Signal (Bool, Unsigned 8), Signal Bool)
input (cTick, dIn) = mealyB input' initInput (cTick, dInS)
where
dInS = register 1 $ register 1 dIn
input' s@(mode, wait, run, shift, sample) (cTick, dIn)
= (s', ((frameErr, dOut), dValid))
where
s'@(mode', wait', run', shift', sample') = input'' s cTick dIn
frameErr = head shift' == low
dOut = bitCoerce $ tail shift' :: Unsigned 8
dValid = run && (not run')
input'' :: (State, Unsigned 4, Bool, Vec 9 Bit, Unsigned 2)
-> Bool
-> Bit
-> (State, Unsigned 4, Bool, Vec 9 Bit, Unsigned 2)
input'' s False _
= s
input'' s@(WaitStart, 0 , _ , shift, sample) _ 1
= s
input'' (WaitStart, 0 , _ , shift, sample) _ 0
= (Sample 1 , 6, False, shift, 0)
input'' (Sample n , 0 , run , shift, sample) _ dIn
= case (n, run, last shift) of
(3, False, _) -> if majority == high then
waitEdge -- Not a startbit, but a glitch
else
(Sample 1, 13, True, 1 :> repeat 0, 0)
(3, True , 1) -> waitEdge
(3, True , 0) -> (Sample 1, 13, True, shift', 0)
(_, _ , _) -> (Sample (n + 1), 0, run, shift, sample')
where
sample' = if dIn == 1 then
sample + 1
else
sample
majority = msb sample'
shift' = majority +>> shift
waitEdge = if dIn == 1 then
(WaitStart, 0, False, shift', sample)
else
(WaitHigh , 0, False, shift', sample)
input'' s@(WaitHigh , 0 , run , shift, sample) _ 0
= s
input'' s@(WaitHigh , 0 , run , shift, sample) _ 1
= (WaitStart, 0, run, shift, sample)
input'' (mode , wait, run , shift, sample) _ _
= (mode, wait - 1, run, shift, sample)
-- Initial state for input
initInput :: (State, Unsigned 4, Bool, Vec 9 Bit, Unsigned 2)
initInput = (WaitStart, 0, False, repeat 0, 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.