Restructure a lot of VHDL generating code.
[matthijs/master-project/cλash.git] / VHDLTools.hs
1 module VHDLTools where
2
3 -- Standard modules
4 import qualified Maybe
5 import qualified Data.List as List
6 import qualified Data.Map as Map
7 import qualified Control.Monad as Monad
8 import qualified Control.Arrow as Arrow
9 import qualified Data.Monoid as Monoid
10 import Data.Accessor
11
12 -- ForSyDe
13 import qualified ForSyDe.Backend.VHDL.AST as AST
14
15 -- GHC API
16 import CoreSyn
17 import qualified Name
18 import qualified OccName
19 import qualified Var
20 import qualified Id
21 import qualified TyCon
22 import qualified Type
23 import qualified DataCon
24 import qualified CoreSubst
25
26 -- Local imports
27 import VHDLTypes
28 import CoreTools
29 import Pretty
30 import Constants
31
32 -----------------------------------------------------------------------------
33 -- Functions to generate concurrent statements
34 -----------------------------------------------------------------------------
35
36 -- Create an unconditional assignment statement
37 mkUncondAssign ::
38   Either CoreBndr AST.VHDLName -- ^ The signal to assign to
39   -> AST.Expr -- ^ The expression to assign
40   -> AST.ConcSm -- ^ The resulting concurrent statement
41 mkUncondAssign dst expr = mkAssign dst Nothing expr
42
43 -- Create a conditional assignment statement
44 mkCondAssign ::
45   Either CoreBndr AST.VHDLName -- ^ The signal to assign to
46   -> AST.Expr -- ^ The condition
47   -> AST.Expr -- ^ The value when true
48   -> AST.Expr -- ^ The value when false
49   -> AST.ConcSm -- ^ The resulting concurrent statement
50 mkCondAssign dst cond true false = mkAssign dst (Just (cond, true)) false
51
52 -- Create a conditional or unconditional assignment statement
53 mkAssign ::
54   Either CoreBndr AST.VHDLName -> -- ^ The signal to assign to
55   Maybe (AST.Expr , AST.Expr) -> -- ^ Optionally, the condition to test for
56                                  -- and the value to assign when true.
57   AST.Expr -> -- ^ The value to assign when false or no condition
58   AST.ConcSm -- ^ The resulting concurrent statement
59 mkAssign dst cond false_expr =
60   let
61     -- I'm not 100% how this assignment AST works, but this gets us what we
62     -- want...
63     whenelse = case cond of
64       Just (cond_expr, true_expr) -> 
65         let 
66           true_wform = AST.Wform [AST.WformElem true_expr Nothing] 
67         in
68           [AST.WhenElse true_wform cond_expr]
69       Nothing -> []
70     false_wform = AST.Wform [AST.WformElem false_expr Nothing]
71     dst_name  = case dst of
72       Left bndr -> AST.NSimple (varToVHDLId bndr)
73       Right name -> name
74     assign    = dst_name AST.:<==: (AST.ConWforms whenelse false_wform Nothing)
75   in
76     AST.CSSASm assign
77
78 mkAssocElems :: 
79   [AST.Expr]                    -- | The argument that are applied to function
80   -> AST.VHDLName               -- | The binder in which to store the result
81   -> Entity                     -- | The entity to map against.
82   -> [AST.AssocElem]            -- | The resulting port maps
83 mkAssocElems args res entity =
84     -- Create the actual AssocElems
85     Maybe.catMaybes $ zipWith mkAssocElem ports sigs
86   where
87     -- Turn the ports and signals from a map into a flat list. This works,
88     -- since the maps must have an identical form by definition. TODO: Check
89     -- the similar form?
90     arg_ports = ent_args entity
91     res_port  = ent_res entity
92     -- Extract the id part from the (id, type) tuple
93     ports     = map (Monad.liftM fst) (res_port : arg_ports)
94     -- Translate signal numbers into names
95     sigs      = (vhdlNameToVHDLExpr res : args)
96
97 -- | Create an VHDL port -> signal association
98 mkAssocElem :: Maybe AST.VHDLId -> AST.Expr -> Maybe AST.AssocElem
99 mkAssocElem (Just port) signal = Just $ Just port AST.:=>: (AST.ADExpr signal) 
100 mkAssocElem Nothing _ = Nothing
101
102 -- | Create an VHDL port -> signal association
103 mkAssocElemIndexed :: Maybe AST.VHDLId -> AST.VHDLId -> AST.VHDLId -> Maybe AST.AssocElem
104 mkAssocElemIndexed (Just port) signal index = Just $ Just port AST.:=>: (AST.ADName (AST.NIndexed (AST.IndexedName 
105                       (AST.NSimple signal) [AST.PrimName $ AST.NSimple index])))
106 mkAssocElemIndexed Nothing _ _ = Nothing
107
108 mkComponentInst ::
109   String -- ^ The portmap label
110   -> AST.VHDLId -- ^ The entity name
111   -> [AST.AssocElem] -- ^ The port assignments
112   -> AST.ConcSm
113 mkComponentInst label entity_id portassigns = AST.CSISm compins
114   where
115     -- We always have a clock port, so no need to map it anywhere but here
116     clk_port = Maybe.fromJust $ mkAssocElem (Just $ mkVHDLExtId "clk") (idToVHDLExpr $ mkVHDLExtId "clk")
117     compins = AST.CompInsSm (mkVHDLExtId label) (AST.IUEntity (AST.NSimple entity_id)) (AST.PMapAspect (portassigns ++ [clk_port]))
118
119 -----------------------------------------------------------------------------
120 -- Functions to generate VHDL Exprs
121 -----------------------------------------------------------------------------
122
123 -- Turn a variable reference into a AST expression
124 varToVHDLExpr :: Var.Var -> AST.Expr
125 varToVHDLExpr var = 
126   case Id.isDataConWorkId_maybe var of
127     Just dc -> dataconToVHDLExpr dc
128     -- This is a dataconstructor.
129     -- Not a datacon, just another signal. Perhaps we should check for
130     -- local/global here as well?
131     Nothing -> AST.PrimName $ AST.NSimple $ varToVHDLId var
132
133 -- Turn a VHDLName into an AST expression
134 vhdlNameToVHDLExpr = AST.PrimName
135
136 -- Turn a VHDL Id into an AST expression
137 idToVHDLExpr = vhdlNameToVHDLExpr . AST.NSimple
138
139 -- Turn a Core expression into an AST expression
140 exprToVHDLExpr = varToVHDLExpr . exprToVar
141
142 -- Turn a alternative constructor into an AST expression. For
143 -- dataconstructors, this is only the constructor itself, not any arguments it
144 -- has. Should not be called with a DEFAULT constructor.
145 altconToVHDLExpr :: CoreSyn.AltCon -> AST.Expr
146 altconToVHDLExpr (DataAlt dc) = dataconToVHDLExpr dc
147
148 altconToVHDLExpr (LitAlt _) = error "VHDL.conToVHDLExpr Literals not support in case alternatives yet"
149 altconToVHDLExpr DEFAULT = error "VHDL.conToVHDLExpr DEFAULT alternative should not occur here!"
150
151 -- Turn a datacon (without arguments!) into a VHDL expression.
152 dataconToVHDLExpr :: DataCon.DataCon -> AST.Expr
153 dataconToVHDLExpr dc = AST.PrimLit lit
154   where
155     tycon = DataCon.dataConTyCon dc
156     tyname = TyCon.tyConName tycon
157     dcname = DataCon.dataConName dc
158     lit = case Name.getOccString tyname of
159       -- TODO: Do something more robust than string matching
160       "Bit"      -> case Name.getOccString dcname of "High" -> "'1'"; "Low" -> "'0'"
161       "Bool" -> case Name.getOccString dcname of "True" -> "true"; "False" -> "false"
162
163 -----------------------------------------------------------------------------
164 -- Functions dealing with names, variables and ids
165 -----------------------------------------------------------------------------
166
167 -- Creates a VHDL Id from a binder
168 varToVHDLId ::
169   CoreSyn.CoreBndr
170   -> AST.VHDLId
171 varToVHDLId = mkVHDLExtId . varToString
172
173 -- Creates a VHDL Name from a binder
174 varToVHDLName ::
175   CoreSyn.CoreBndr
176   -> AST.VHDLName
177 varToVHDLName = AST.NSimple . varToVHDLId
178
179 -- Extracts the binder name as a String
180 varToString ::
181   CoreSyn.CoreBndr
182   -> String
183 varToString = OccName.occNameString . Name.nameOccName . Var.varName
184
185 -- Get the string version a Var's unique
186 varToStringUniq :: Var.Var -> String
187 varToStringUniq = show . Var.varUnique
188
189 -- Extracts the string version of the name
190 nameToString :: Name.Name -> String
191 nameToString = OccName.occNameString . Name.nameOccName
192
193 -- Shortcut for Basic VHDL Ids.
194 -- Can only contain alphanumerics and underscores. The supplied string must be
195 -- a valid basic id, otherwise an error value is returned. This function is
196 -- not meant to be passed identifiers from a source file, use mkVHDLExtId for
197 -- that.
198 mkVHDLBasicId :: String -> AST.VHDLId
199 mkVHDLBasicId s = 
200   AST.unsafeVHDLBasicId $ (strip_multiscore . strip_leading . strip_invalid) s
201   where
202     -- Strip invalid characters.
203     strip_invalid = filter (`elem` ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "_.")
204     -- Strip leading numbers and underscores
205     strip_leading = dropWhile (`elem` ['0'..'9'] ++ "_")
206     -- Strip multiple adjacent underscores
207     strip_multiscore = concat . map (\cs -> 
208         case cs of 
209           ('_':_) -> "_"
210           _ -> cs
211       ) . List.group
212
213 -- Shortcut for Extended VHDL Id's. These Id's can contain a lot more
214 -- different characters than basic ids, but can never be used to refer to
215 -- basic ids.
216 -- Use extended Ids for any values that are taken from the source file.
217 mkVHDLExtId :: String -> AST.VHDLId
218 mkVHDLExtId s = 
219   AST.unsafeVHDLExtId $ strip_invalid s
220   where 
221     -- Allowed characters, taken from ForSyde's mkVHDLExtId
222     allowed = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ " \"#&\\'()*+,./:;<=>_|!$%@?[]^`{}~-"
223     strip_invalid = filter (`elem` allowed)
224
225 -- Create a record field selector that selects the given label from the record
226 -- stored in the given binder.
227 mkSelectedName :: AST.VHDLName -> AST.VHDLId -> AST.VHDLName
228 mkSelectedName name label =
229    AST.NSelected $ name AST.:.: (AST.SSimple label) 
230
231 -----------------------------------------------------------------------------
232 -- Functions dealing with VHDL types
233 -----------------------------------------------------------------------------
234
235 -- | Maps the string name (OccName) of a type to the corresponding VHDL type,
236 -- for a few builtin types.
237 builtin_types = 
238   Map.fromList [
239     ("Bit", std_logicTM),
240     ("Bool", booleanTM) -- TysWiredIn.boolTy
241   ]
242
243 -- Translate a Haskell type to a VHDL type, generating a new type if needed.
244 vhdl_ty :: Type.Type -> VHDLSession AST.TypeMark
245 vhdl_ty ty = do
246   typemap <- getA vsTypes
247   let builtin_ty = do -- See if this is a tycon and lookup its name
248         (tycon, args) <- Type.splitTyConApp_maybe ty
249         let name = Name.getOccString (TyCon.tyConName tycon)
250         Map.lookup name builtin_types
251   -- If not a builtin type, try the custom types
252   let existing_ty = (fmap fst) $ Map.lookup (OrdType ty) typemap
253   case Monoid.getFirst $ Monoid.mconcat (map Monoid.First [builtin_ty, existing_ty]) of
254     -- Found a type, return it
255     Just t -> return t
256     -- No type yet, try to construct it
257     Nothing -> do
258       newty_maybe <- (construct_vhdl_ty ty)
259       case newty_maybe of
260         Just (ty_id, ty_def) -> do
261           -- TODO: Check name uniqueness
262           modA vsTypes (Map.insert (OrdType ty) (ty_id, ty_def))
263           return ty_id
264         Nothing -> error $ "Unsupported Haskell type: " ++ pprString ty
265
266 -- Construct a new VHDL type for the given Haskell type.
267 construct_vhdl_ty :: Type.Type -> VHDLSession (Maybe (AST.TypeMark, Either AST.TypeDef AST.SubtypeIn))
268 construct_vhdl_ty ty = do
269   case Type.splitTyConApp_maybe ty of
270     Just (tycon, args) -> do
271       let name = Name.getOccString (TyCon.tyConName tycon)
272       case name of
273         "TFVec" -> do
274           res <- mk_vector_ty (tfvec_len ty) (tfvec_elem ty)
275           return $ Just $ (Arrow.second Right) res
276         -- "SizedWord" -> do
277         --   res <- mk_vector_ty (sized_word_len ty) ty
278         --   return $ Just $ (Arrow.second Left) res
279         "RangedWord" -> do 
280           res <- mk_natural_ty 0 (ranged_word_bound ty)
281           return $ Just $ (Arrow.second Right) res
282         -- Create a custom type from this tycon
283         otherwise -> mk_tycon_ty tycon args
284     Nothing -> return $ Nothing
285
286 -- | Create VHDL type for a custom tycon
287 mk_tycon_ty :: TyCon.TyCon -> [Type.Type] -> VHDLSession (Maybe (AST.TypeMark, Either AST.TypeDef AST.SubtypeIn))
288 mk_tycon_ty tycon args =
289   case TyCon.tyConDataCons tycon of
290     -- Not an algebraic type
291     [] -> error $ "Only custom algebraic types are supported: " ++ pprString tycon
292     [dc] -> do
293       let arg_tys = DataCon.dataConRepArgTys dc
294       -- TODO: CoreSubst docs say each Subs can be applied only once. Is this a
295       -- violation? Or does it only mean not to apply it again to the same
296       -- subject?
297       let real_arg_tys = map (CoreSubst.substTy subst) arg_tys
298       elem_tys <- mapM vhdl_ty real_arg_tys
299       let elems = zipWith AST.ElementDec recordlabels elem_tys
300       -- For a single construct datatype, build a record with one field for
301       -- each argument.
302       -- TODO: Add argument type ids to this, to ensure uniqueness
303       -- TODO: Special handling for tuples?
304       let ty_id = mkVHDLExtId $ nameToString (TyCon.tyConName tycon)
305       let ty_def = AST.TDR $ AST.RecordTypeDef elems
306       return $ Just (ty_id, Left ty_def)
307     dcs -> error $ "Only single constructor datatypes supported: " ++ pprString tycon
308   where
309     -- Create a subst that instantiates all types passed to the tycon
310     -- TODO: I'm not 100% sure that this is the right way to do this. It seems
311     -- to work so far, though..
312     tyvars = TyCon.tyConTyVars tycon
313     subst = CoreSubst.extendTvSubstList CoreSubst.emptySubst (zip tyvars args)
314     -- Generate a bunch of labels for fields of a record
315     recordlabels = map (\c -> mkVHDLBasicId [c]) ['A'..'Z']
316
317 -- | Create a VHDL vector type
318 mk_vector_ty ::
319   Int -- ^ The length of the vector
320   -> Type.Type -- ^ The Haskell element type of the Vector
321   -> VHDLSession (AST.TypeMark, AST.SubtypeIn) -- The typemark created.
322
323 mk_vector_ty len el_ty = do
324   elem_types_map <- getA vsElemTypes
325   el_ty_tm <- vhdl_ty el_ty
326   let ty_id = mkVHDLExtId $ "vector-"++ (AST.fromVHDLId el_ty_tm) ++ "-0_to_" ++ (show len)
327   let range = AST.ConstraintIndex $ AST.IndexConstraint [AST.ToRange (AST.PrimLit "0") (AST.PrimLit $ show (len - 1))]
328   let existing_elem_ty = (fmap fst) $ Map.lookup (OrdType el_ty) elem_types_map
329   case existing_elem_ty of
330     Just t -> do
331       let ty_def = AST.SubtypeIn t (Just range)
332       return (ty_id, ty_def)
333     Nothing -> do
334       let vec_id = mkVHDLExtId $ "vector_" ++ (AST.fromVHDLId el_ty_tm)
335       let vec_def = AST.TDA $ AST.UnconsArrayDef [tfvec_indexTM] el_ty_tm
336       modA vsElemTypes (Map.insert (OrdType el_ty) (vec_id, vec_def))
337       --modA vsTypeFuns (Map.insert (OrdType el_ty) (genUnconsVectorFuns el_ty_tm vec_id)) 
338       let ty_def = AST.SubtypeIn vec_id (Just range)
339       return (ty_id, ty_def)
340
341 mk_natural_ty ::
342   Int -- ^ The minimum bound (> 0)
343   -> Int -- ^ The maximum bound (> minimum bound)
344   -> VHDLSession (AST.TypeMark, AST.SubtypeIn) -- The typemark created.
345 mk_natural_ty min_bound max_bound = do
346   let ty_id = mkVHDLExtId $ "nat_" ++ (show min_bound) ++ "_to_" ++ (show max_bound)
347   let range = AST.ConstraintRange $ AST.SubTypeRange (AST.PrimLit $ (show min_bound)) (AST.PrimLit $ (show max_bound))
348   let ty_def = AST.SubtypeIn naturalTM (Just range)
349   return (ty_id, ty_def)
350
351 -- Finds the field labels for VHDL type generated for the given Core type,
352 -- which must result in a record type.
353 getFieldLabels :: Type.Type -> VHDLSession [AST.VHDLId]
354 getFieldLabels ty = do
355   -- Ensure that the type is generated (but throw away it's VHDLId)
356   vhdl_ty ty
357   -- Get the types map, lookup and unpack the VHDL TypeDef
358   types <- getA vsTypes
359   case Map.lookup (OrdType ty) types of
360     Just (_, Left (AST.TDR (AST.RecordTypeDef elems))) -> return $ map (\(AST.ElementDec id _) -> id) elems
361     _ -> error $ "VHDL.getFieldLabels Type not found or not a record type? This should not happen! Type: " ++ (show ty)