diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,19 @@
 # Changelog for the Clash project
+## 1.4.7 *Jan 30th 2022*
+Fixed:
+  * Clash now shows days in time strings for compile runs which take longer than a day [#1989](https://github.com/clash-lang/clash-compiler/compare/issue-1989).
+  * Types defined in the package head are no longer qualified in the package body when rendering VHDL [#1996](https://github.com/clash-lang/clash-compiler/issues/1996).
+  * `asyncRam` with different read and write clocks no longer produce the wrong results in Haskell simulation. [#2031](https://github.com/clash-lang/clash-compiler/pull/2031)
+  * `Clash.Explicit.RAM.asyncRam#` Haskell simulation incorrectly treated an _undefined_ write enable as asserted. It now causes an _undefined_ value to be written instead. This problem did not propagate to the other `asyncRam` functions, where the same condition would simultaneously lead to an undefined write address, which would be handled correctly. This problem also only affects Haskell simulation, not the generated HDL. [#2031](https://github.com/clash-lang/clash-compiler/pull/2031)
+  * `Clash.Explicit.BlockRam.blockRam#` and `Clash.Explicit.BlockRam.File.blockRamFile#` Haskell simulation incorrectly treated an _undefined_ write enable as asserted. It now causes an _undefined_ value to be written instead. This problem did not propagate to the other `blockRam` functions, where the same condition would simultaneously lead to an undefined write address, which would be handled correctly. This problem also only affects Haskell simulation, not the generated HDL.([#2054](https://github.com/clash-lang/clash-compiler/pull/2054))
+
+Internal changes:
+  * Removed instances of `Hashable Term` and `Hashable Type` [#1986](https://github.com/clash-lang/clash-compiler/pull/1986)
+  * Added structural equality on `Term` (`Clash.Core.Subst.eqTerm`) and `Type` (`Clash.Core.Subst.eqType`)
+
+Internal fixes:
+  * Enable used to be a `Bool` in the Blackbox DSL, so we could use `boolToBit`. However it now has its own type in the DSL (`Enable domainName`), so we've added a new conversion function in order to convert it to a Bool.
+
 
 ## 1.4.6 *Oct 26th 2021*
 
diff --git a/clash-lib.cabal b/clash-lib.cabal
--- a/clash-lib.cabal
+++ b/clash-lib.cabal
@@ -1,6 +1,6 @@
 Cabal-version:        2.2
 Name:                 clash-lib
-Version:              1.4.6
+Version:              1.4.7
 Synopsis:             Clash: a functional hardware description language - As a library
 Description:
   Clash is a functional hardware description language that borrows both its
@@ -144,7 +144,7 @@
                       base16-bytestring       >= 0.1.1    && < 1.1,
                       binary                  >= 0.8.5    && < 0.11,
                       bytestring              >= 0.10.0.2 && < 0.12,
-                      clash-prelude           == 1.4.6,
+                      clash-prelude           == 1.4.7,
                       concurrent-supply       >= 0.1.7    && < 0.2,
                       containers              >= 0.5.0.0  && < 0.7,
                       cryptohash-sha256       >= 0.11     && < 0.12,
@@ -159,7 +159,7 @@
                       filepath                >= 1.3.0.1  && < 1.5,
                       ghc                     >= 8.4.0    && < 9.1,
                       ghc-boot-th,
-                      hashable                >= 1.2.1.0  && < 1.4,
+                      hashable                >= 1.2.1.0  && < 1.5,
                       haskell-src-meta        >= 0.8      && < 0.9,
                       hint                    >= 0.7      && < 0.10,
                       interpolate             >= 0.2.0    && < 1.0,
@@ -175,9 +175,9 @@
                       template-haskell        >= 2.8.0.0  && < 2.18,
                       temporary               >= 1.2.1    && < 1.4,
                       terminal-size           >= 0.3      && < 0.4,
-                      text                    >= 1.2.2    && < 1.3,
+                      text                    >= 1.2.2    && < 2.1,
                       text-show               >= 3.7      && < 3.10,
-                      time                    >= 1.4.0.1  && < 1.13,
+                      time                    >= 1.4.0.1  && < 1.14,
                       transformers            >= 0.5.2.0  && < 0.7,
                       trifecta                >= 1.7.1.1  && < 2.2,
                       utf8-string             >= 1.0.1    && < 1.1,
@@ -186,7 +186,7 @@
                       unordered-containers    >= 0.2.3.3  && < 0.3
 
   if impl(ghc >= 9.0.0)
-    build-depends:     ghc-bignum >=1.0 && <1.1
+    build-depends:     ghc-bignum >=1.0 && <1.3
   else
     build-depends:     integer-gmp >=1.0 && <1.1
 
diff --git a/prims/commonverilog/Clash_Explicit_Testbench.primitives b/prims/commonverilog/Clash_Explicit_Testbench.primitives
new file mode 100644
--- /dev/null
+++ b/prims/commonverilog/Clash_Explicit_Testbench.primitives
@@ -0,0 +1,9 @@
+[ { "BlackBox" :
+    { "name" : "Clash.Explicit.Testbench.tbEnableGen"
+    , "workInfo" : "Always"
+    , "kind" : "Declaration"
+    , "type" : "tbEnableGen :: Enable dom"
+    , "template" : "assign ~RESULT = 1'b1;"
+    }
+  }
+]
diff --git a/prims/commonverilog/Clash_Signal_Internal.primitives b/prims/commonverilog/Clash_Signal_Internal.primitives
--- a/prims/commonverilog/Clash_Signal_Internal.primitives
+++ b/prims/commonverilog/Clash_Signal_Internal.primitives
@@ -16,12 +16,4 @@
     , "template" : "~ARG[0]"
     }
   }
-, { "BlackBox" :
-    { "name" : "Clash.Explicit.Testbench.tbEnableGen"
-    , "workInfo" : "Always"
-    , "kind" : "Declaration"
-    , "type" : "tbEnableGen :: Enable dom"
-    , "template" : "assign ~RESULT = 1'b1;"
-    }
-  }
 ]
diff --git a/prims/systemverilog/Clash_Explicit_Testbench.primitives b/prims/systemverilog/Clash_Explicit_Testbench.primitives
--- a/prims/systemverilog/Clash_Explicit_Testbench.primitives
+++ b/prims/systemverilog/Clash_Explicit_Testbench.primitives
@@ -57,4 +57,35 @@
 // assertBitVector end"
     }
   }
+, { "BlackBox" :
+    { "name" : "Clash.Explicit.Testbench.tbClockGen"
+    , "workInfo" : "Always"
+    , "kind" : "Declaration"
+    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
+    , "type" :
+"tbClockGen
+  :: KnownDomain dom     -- ARG[0]
+  => Signal dom Bool      -- ARG[1]
+  -> Clock dom"
+    , "template" :
+"// tbClockGen begin
+// pragma translate_off
+// 1 = 0.1ps
+localparam ~GENSYM[half_period][0] = (~PERIOD[0]0 / 2);
+always begin
+  ~RESULT = ~IF~ACTIVEEDGE[Rising][0]~THEN 0 ~ELSE 1 ~FI;
+  #30000 forever begin
+    if (~ ~ARG[1]) begin
+      $finish;
+    end
+    ~RESULT = ~ ~RESULT;
+    #~SYM[0];
+    ~RESULT = ~ ~RESULT;
+    #~SYM[0];
+  end
+end
+// pragma translate_on
+// tbClockGen end"
+    }
+  }
 ]
diff --git a/prims/systemverilog/Clash_Signal_Internal.primitives b/prims/systemverilog/Clash_Signal_Internal.primitives
--- a/prims/systemverilog/Clash_Signal_Internal.primitives
+++ b/prims/systemverilog/Clash_Signal_Internal.primitives
@@ -109,37 +109,6 @@
     }
   }
 , { "BlackBox" :
-    { "name" : "Clash.Explicit.Testbench.tbClockGen"
-    , "workInfo" : "Always"
-    , "kind" : "Declaration"
-    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
-    , "type" :
-"tbClockGen
-  :: KnownDomain dom     -- ARG[0]
-  => Signal dom Bool      -- ARG[1]
-  -> Clock dom"
-    , "template" :
-"// tbClockGen begin
-// pragma translate_off
-// 1 = 0.1ps
-localparam ~GENSYM[half_period][0] = (~PERIOD[0]0 / 2);
-always begin
-  ~RESULT = ~IF~ACTIVEEDGE[Rising][0]~THEN 0 ~ELSE 1 ~FI;
-  #30000 forever begin
-    if (~ ~ARG[1]) begin
-      $finish;
-    end
-    ~RESULT = ~ ~RESULT;
-    #~SYM[0];
-    ~RESULT = ~ ~RESULT;
-    #~SYM[0];
-  end
-end
-// pragma translate_on
-// tbClockGen end"
-    }
-  }
-, { "BlackBox" :
     { "name" : "Clash.Signal.Internal.resetGenN"
     , "workInfo" : "Always"
     , "kind" : "Declaration"
diff --git a/prims/verilog/Clash_Explicit_Testbench.primitives b/prims/verilog/Clash_Explicit_Testbench.primitives
--- a/prims/verilog/Clash_Explicit_Testbench.primitives
+++ b/prims/verilog/Clash_Explicit_Testbench.primitives
@@ -57,4 +57,38 @@
 // assertBitVector end"
     }
   }
+, { "BlackBox" :
+    { "name" : "Clash.Explicit.Testbench.tbClockGen"
+    , "workInfo"  : "Always"
+    , "kind" : "Declaration"
+    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
+    , "type" :
+"tbClockGen
+  :: KnownDomain dom     -- ARG[0]
+  => Signal dom Bool      -- ARG[1]
+  -> Clock dom"
+    , "template" :
+"// tbClockGen begin
+// pragma translate_off
+reg ~TYPO ~GENSYM[clk][0];
+// 1 = 0.1ps
+localparam ~GENSYM[half_period][1] = (~PERIOD[0]0 / 2);
+always begin
+  // Delay of 1 mitigates race conditions (https://github.com/steveicarus/iverilog/issues/160)
+  #1 ~SYM[0] = ~IF~ACTIVEEDGE[Rising][0]~THEN 0 ~ELSE 1 ~FI;
+  #30000 forever begin
+    if (~ ~ARG[1]) begin
+      $finish;
+    end
+    ~SYM[0] = ~ ~SYM[0];
+    #~SYM[1];
+    ~SYM[0] = ~ ~SYM[0];
+    #~SYM[1];
+  end
+end
+assign ~RESULT = ~SYM[0];
+// pragma translate_on
+// tbClockGen end"
+    }
+  }
 ]
diff --git a/prims/verilog/Clash_Signal_Internal.primitives b/prims/verilog/Clash_Signal_Internal.primitives
--- a/prims/verilog/Clash_Signal_Internal.primitives
+++ b/prims/verilog/Clash_Signal_Internal.primitives
@@ -115,40 +115,6 @@
     }
   }
 , { "BlackBox" :
-    { "name" : "Clash.Explicit.Testbench.tbClockGen"
-    , "workInfo"  : "Always"
-    , "kind" : "Declaration"
-    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
-    , "type" :
-"tbClockGen
-  :: KnownDomain dom     -- ARG[0]
-  => Signal dom Bool      -- ARG[1]
-  -> Clock dom"
-    , "template" :
-"// tbClockGen begin
-// pragma translate_off
-reg ~TYPO ~GENSYM[clk][0];
-// 1 = 0.1ps
-localparam ~GENSYM[half_period][1] = (~PERIOD[0]0 / 2);
-always begin
-  // Delay of 1 mitigates race conditions (https://github.com/steveicarus/iverilog/issues/160)
-  #1 ~SYM[0] = ~IF~ACTIVEEDGE[Rising][0]~THEN 0 ~ELSE 1 ~FI;
-  #30000 forever begin
-    if (~ ~ARG[1]) begin
-      $finish;
-    end
-    ~SYM[0] = ~ ~SYM[0];
-    #~SYM[1];
-    ~SYM[0] = ~ ~SYM[0];
-    #~SYM[1];
-  end
-end
-assign ~RESULT = ~SYM[0];
-// pragma translate_on
-// tbClockGen end"
-    }
-  }
-, { "BlackBox" :
     { "name"      : "Clash.Signal.Internal.resetGenN"
     , "workInfo"  : "Always"
     , "kind" : "Declaration"
diff --git a/prims/vhdl/Clash_Explicit_Testbench.primitives b/prims/vhdl/Clash_Explicit_Testbench.primitives
--- a/prims/vhdl/Clash_Explicit_Testbench.primitives
+++ b/prims/vhdl/Clash_Explicit_Testbench.primitives
@@ -154,4 +154,46 @@
 -- assertBitVector end"
     }
   }
+, { "BlackBox" :
+    { "name" : "Clash.Explicit.Testbench.tbClockGen"
+    , "workInfo"  : "Always"
+    , "kind" : "Declaration"
+    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
+    , "type" :
+"tbClockGen
+  :: KnownDomain dom     -- ARG[0]
+  => Signal dom Bool     -- ARG[1]
+  -> Clock dom"
+    , "comment" :
+        "ModelSim and Vivado seem to round time values to an integer number of picoseconds.
+        Use two half periods to prevent rounding errors from affecting the full period."
+    , "template" :
+"-- tbClockGen begin
+-- pragma translate_off
+~GENSYM[clkGen][0] : process is
+  constant ~GENSYM[half_periodH][1] : time := ~PERIOD[0]000 fs / 2;
+  constant ~GENSYM[half_periodL][2] : time := ~PERIOD[0]000 fs - ~SYM[1];
+begin
+  ~RESULT <= ~IF~ACTIVEEDGE[Rising][0]~THEN'0'~ELSE'1'~FI;
+  wait for 3000 ps;
+  while ~ARG[1] loop
+    ~RESULT <= not ~RESULT;
+    wait for ~SYM[1];
+    ~RESULT <= not ~RESULT;
+    wait for ~SYM[2];
+  end loop;
+  wait;
+end process;
+-- pragma translate_on
+-- tbClockGen end"
+    }
+  }
+, { "BlackBox" :
+    { "name" : "Clash.Explicit.Testbench.tbEnableGen"
+    , "workInfo" : "Always"
+    , "kind" : "Declaration"
+    , "type" : "tbEnableGen :: Enable dom"
+    , "template" : "~RESULT <= true;"
+    }
+  }
 ]
diff --git a/prims/vhdl/Clash_Signal_Internal.primitives b/prims/vhdl/Clash_Signal_Internal.primitives
--- a/prims/vhdl/Clash_Signal_Internal.primitives
+++ b/prims/vhdl/Clash_Signal_Internal.primitives
@@ -159,37 +159,6 @@
     }
   }
 , { "BlackBox" :
-    { "name" : "Clash.Explicit.Testbench.tbClockGen"
-    , "workInfo"  : "Always"
-    , "kind" : "Declaration"
-    , "warning" : "Clash.Signal.Internal.tbClockGen is not synthesizable!"
-    , "type" :
-"tbClockGen
-  :: KnownDomain dom     -- ARG[0]
-  => Signal dom Bool     -- ARG[1]
-  -> Clock dom"
-    , "template" :
-"-- tbClockGen begin
--- pragma translate_off
-~GENSYM[clkGen][0] : process is
-  constant ~GENSYM[half_periodH][1] : time := ~PERIOD[0]000 fs / 2;
-  constant ~GENSYM[half_periodL][2] : time := ~PERIOD[0]000 fs - ~SYM[1];
-begin
-  ~RESULT <= ~IF~ACTIVEEDGE[Rising][0]~THEN'0'~ELSE'1'~FI;
-  wait for 3000 ps;
-  while ~ARG[1] loop
-    ~RESULT <= not ~RESULT;
-    wait for ~SYM[1];
-    ~RESULT <= not ~RESULT;
-    wait for ~SYM[2];
-  end loop;
-  wait;
-end process;
--- pragma translate_on
--- tbClockGen end"
-    }
-  }
-, { "BlackBox" :
     { "name"      : "Clash.Signal.Internal.resetGenN"
     , "workInfo"  : "Always"
     , "kind" : "Declaration"
@@ -219,14 +188,6 @@
     , "type" :
 "unsafeToReset :: Signal dom Bool -> Reset dom"
     , "template" : "~RESULT <= '1' when ~ARG[0] = true else '0';"
-    }
-  }
-, { "BlackBox" :
-    { "name" : "Clash.Explicit.Testbench.tbEnableGen"
-    , "workInfo" : "Always"
-    , "kind" : "Declaration"
-    , "type" : "tbEnableGen :: Enable dom"
-    , "template" : "~RESULT <= true;"
     }
   }
 ]
diff --git a/src/Clash/Backend/VHDL.hs b/src/Clash/Backend/VHDL.hs
--- a/src/Clash/Backend/VHDL.hs
+++ b/src/Clash/Backend/VHDL.hs
@@ -94,6 +94,8 @@
   -- ^ Files to be stored: (filename, contents). These files are generated
   -- during the execution of 'genNetlist'.
   , _idSeen    :: IdentifierSet
+  , _tyPkgCtx :: Bool
+  -- ^ Are we in the context of generating the @_types@ package?
   , _intWidth  :: Int
   -- ^ Int/Word/Integer bit-width
   , _hdlsyn    :: HdlSyn
@@ -121,6 +123,7 @@
     , _dataFiles=[]
     , _memoryDataFiles=[]
     , _idSeen=Id.emptyIdentifierSet esc lw VHDL
+    , _tyPkgCtx=False
     , _intWidth=w
     , _hdlsyn=hdlsyn_
     , _undefValue=undefVal
@@ -359,14 +362,15 @@
 -- | Generate a VHDL package containing type definitions for the given HWTypes
 mkTyPackage_ :: ModName -> [HWType] -> VHDLM [(String,Doc)]
 mkTyPackage_ modName (map filterTransparent -> hwtys) = do
-    { syn <- Mon hdlSyn
+    { Mon (tyPkgCtx .= True)
+    ; syn <- Mon hdlSyn
     ; let usedTys     = concatMap mkUsedTys hwtys
     ; let normTys0    = nub (map mkVecZ (hwtys ++ usedTys))
     ; let sortedTys0  = topSortHWTys normTys0
           packageDec  = vcat $ mapM tyDec (nubBy eqTypM sortedTys0)
           (funDecs,funBodies) = unzip . mapMaybe (funDec syn) $ nubBy eqTypM (map normaliseType sortedTys0)
 
-    ; (:[]) <$> (TextS.unpack (modName `TextS.append` "_types"),) <$>
+    ; pkg <- (:[]) <$> (TextS.unpack (modName `TextS.append` "_types"),) <$>
       "library IEEE;" <> line <>
       "use IEEE.STD_LOGIC_1164.ALL;" <> line <>
       "use IEEE.NUMERIC_STD.ALL;" <> line <> line <>
@@ -375,6 +379,8 @@
                     vcat (sequence funDecs)
                   ) <> line <>
       "end" <> semi <> packageBodyDec funBodies
+    ; Mon (tyPkgCtx .= False)
+    ; return pkg
     }
   where
     packageBodyDec :: [VHDLM Doc] -> VHDLM Doc
@@ -640,13 +646,13 @@
   )
 
 funDec _ t@(Product _ labels elTys) = Just
-  ( "function" <+> "toSLV" <+> parens ("p :" <+> sizedQualTyName t) <+> "return std_logic_vector" <> semi <> line <>
-    "function" <+> "fromSLV" <+> parens ("slv" <+> colon <+> "in" <+> "std_logic_vector") <+> "return" <+> sizedQualTyName t <> semi
-  , "function" <+> "toSLV" <+> parens ("p :" <+> sizedQualTyName t) <+> "return std_logic_vector" <+> "is" <> line <>
+  ( "function" <+> "toSLV" <+> parens ("p :" <+> sizedTyName t) <+> "return std_logic_vector" <> semi <> line <>
+    "function" <+> "fromSLV" <+> parens ("slv" <+> colon <+> "in" <+> "std_logic_vector") <+> "return" <+> sizedTyName t <> semi
+  , "function" <+> "toSLV" <+> parens ("p :" <+> sizedTyName t) <+> "return std_logic_vector" <+> "is" <> line <>
     "begin" <> line <>
     indent 2 ("return" <+> parens (hcat (punctuate " & " elTyToSLV)) <> semi) <> line <>
     "end" <> semi <> line <>
-    "function" <+> "fromSLV" <+> parens ("slv" <+> colon <+> "in" <+> "std_logic_vector") <+> "return" <+> sizedQualTyName t <+> "is" <> line <>
+    "function" <+> "fromSLV" <+> parens ("slv" <+> colon <+> "in" <+> "std_logic_vector") <+> "return" <+> sizedTyName t <+> "is" <> line <>
       "alias islv : std_logic_vector(0 to slv'length - 1) is slv;" <> line <>
     "begin" <> line <>
     indent 2 ("return" <+> parens (hcat (punctuate "," elTyFromSLV)) <> semi) <> line <>
@@ -1001,8 +1007,12 @@
 
   -- Custom types:
   _ -> do
+    pkgCtx <- Mon (use tyPkgCtx)
     modName <- Mon (use modNm)
-    pretty modName <> "_types." <> tyName hwty
+
+    if pkgCtx
+      then tyName hwty
+      else pretty modName <> "_types." <> tyName hwty
 
 -- | Generates a unique name for a given type. This action will cache its
 -- results, thus returning the same answer for the same @HWType@ argument.
diff --git a/src/Clash/Core/DataCon.hs b/src/Clash/Core/DataCon.hs
--- a/src/Clash/Core/DataCon.hs
+++ b/src/Clash/Core/DataCon.hs
@@ -22,7 +22,6 @@
 
 import Control.DeepSeq                        (NFData(..))
 import Data.Binary                            (Binary)
-import Data.Hashable                          (Hashable)
 import qualified Data.Text                    as Text
 import GHC.Generics                           (Generic)
 
@@ -53,7 +52,7 @@
   -- ^ Argument types
   , dcFieldLabels :: [Text.Text]
   -- ^ Names of fields. Used when data constructor is referring to a record type.
-  } deriving (Generic,NFData,Hashable,Binary)
+  } deriving (Generic,NFData,Binary)
 
 instance Show DataCon where
   show = show . dcName
diff --git a/src/Clash/Core/FreeVars.hs b/src/Clash/Core/FreeVars.hs
--- a/src/Clash/Core/FreeVars.hs
+++ b/src/Clash/Core/FreeVars.hs
@@ -83,7 +83,7 @@
 -- set, its kind variables also aren´t; so in order to prevent collisions due to
 -- shadowing we close using an empty inScope set.
 --
--- See also: https://git.haskell.org/ghc.git/commitdiff/503514b94f8dc7bd9eab5392206649aee45f140b
+-- See also: https://gitlab.haskell.org/ghc/ghc/-/commit/503514b94f8dc7bd9eab5392206649aee45f140b
 typeFreeVars'
   :: (Contravariant f, Applicative f)
   => (forall b . Var b -> Bool)
@@ -221,7 +221,7 @@
 -- set, its type variables also aren´t; so in order to prevent collisions due to
 -- shadowing we close using an empty inScope set.
 --
--- See also: https://git.haskell.org/ghc.git/commitdiff/503514b94f8dc7bd9eab5392206649aee45f140b
+-- See also: https://gitlab.haskell.org/ghc/ghc/-/commit/503514b94f8dc7bd9eab5392206649aee45f140b
 termFreeVars'
   :: (Contravariant f, Applicative f)
   => (forall b . Var b -> Bool)
diff --git a/src/Clash/Core/Subst.hs b/src/Clash/Core/Subst.hs
--- a/src/Clash/Core/Subst.hs
+++ b/src/Clash/Core/Subst.hs
@@ -10,6 +10,8 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -53,6 +55,9 @@
     -- * Alpha equivalence
   , aeqType
   , aeqTerm
+    -- * Structural equivalence
+  , eqTerm
+  , eqType
   )
 where
 
@@ -64,9 +69,13 @@
 import           Data.Text.Prettyprint.Doc
 #endif
 
+import           Data.Hashable             (Hashable (hashWithSalt))
 import qualified Data.List                 as List
 import qualified Data.List.Extra           as List
 import           Data.Ord                  (comparing)
+import           GHC.SrcLoc.Extra          ()
+import           GHC.TypeLits
+  (TypeError, ErrorMessage (Text, (:<>:)))
 
 import           Clash.Core.FreeVars
   (noFreeVarsOfType, localFVsOfTerms, tyFVsOfTypes)
@@ -819,12 +828,8 @@
   go env (AppTy s1 t1) (AppTy s2 t2) =
     go env s1 s2 `thenCompare` go env t1 t2
   go _ (LitTy l1) (LitTy l2) = compare l1 l2
-  go env (AnnType _ t1) (AnnType _ t2) =
-    -- XXX: maybe ignore annotations, like we ignore ticks, i.e.
-    --
-    -- go env (AnnType t1) t2 = go env t1 t2
-    -- go env t1 (AnnType t2) = go env t1 t2
-    go env t1 t2
+  go env (AnnType a1 t1) (AnnType a2 t2) =
+    compare a1 a2 `thenCompare` go env t1 t2
   go _ t1 t2 = compare (getRank t1) (getRank t2)
 
   getRank :: Type -> Word
@@ -835,6 +840,22 @@
   getRank (AppTy {})    = 4
   getRank (ForAllTy {}) = 5
 
+-- | Structural equality on 'Type'
+eqType
+  :: Type
+  -> Type
+  -> Bool
+eqType = go
+ where
+  go (VarTy tv1) (VarTy tv2) = tv1 == tv2
+  go (ConstTy c1) (ConstTy c2) = c1 == c2
+  go (ForAllTy tv1 t1) (ForAllTy tv2 t2) =
+    tv1 == tv2 && go (varType tv1) (varType tv2) && go t1 t2
+  go (AppTy s1 t1) (AppTy s2 t2) = go s1 s2 && go t1 t2
+  go (LitTy l1) (LitTy l2) = l1 == l2
+  go (AnnType a1 t1) (AnnType a2 t2) = a1 == a2 && go t1 t2
+  go _ _ = False
+
 -- | Alpha equality for terms
 aeqTerm
   :: Term
@@ -906,10 +927,8 @@
     go env e1 e2 `thenCompare`
     acmpType' env l1 l2 `thenCompare`
     acmpType' env r1 r2
-  -- Look through ticks for aeq
-  go env (Tick _ e1) e2 = go env e1 e2
-  go env e1 (Tick _ e2) = go env e1 e2
-
+  go env (Tick t1 e1) (Tick t2 e2) =
+    compare t1 t2 `thenCompare` go env e1 e2
   go _ e1 e2 = compare (getRank e1) (getRank e2)
 
   goAlt env (DataPat c1 tvs1 ids1,e1) (DataPat c2 tvs2 ids2,e2) =
@@ -938,6 +957,47 @@
 thenCompare EQ rel = rel
 thenCompare rel _  = rel
 
+-- | Structural equality on 'Term'
+eqTerm :: Term -> Term -> Bool
+eqTerm = go
+ where
+  go (Var id1) (Var id2) = id1 == id2
+  go (Data dc1) (Data dc2) = dc1 == dc2
+  go (Literal l1) (Literal l2) = l1 == l2
+  go (Prim p1) (Prim p2) = primName p1 == primName p2
+  go (Lam b1 e1) (Lam b2 e2) =
+    b1 == b2 && eqType (varType b1) (varType b2) && go e1 e2
+  go (TyLam b1 e1) (TyLam b2 e2) =
+    b1 == b2 && eqType (varType b1) (varType b2) && go e1 e2
+  go (App l1 r1) (App l2 r2) = go l1 l2 && go r1 r2
+  go (TyApp l1 r1) (TyApp l2 r2) = go l1 l2 && eqType r1 r2
+  go (Letrec bs1 e1) (Letrec bs2 e2) =
+    go e1 e2 &&
+    List.all2 goBind bs1 bs2
+   where
+    goBind (b1,r1) (b2,r2) =
+      b1 == b2 &&
+      -- We need to check the types of Rec bindings, because:
+      --
+      -- letrec (x : Bool) = x in X
+      --
+      -- is not structurally equivalent to
+      --
+      -- letrec (x : Int) = x in x
+      eqType (varType b1) (varType b2) &&
+      go r1 r2
+  go (Case e1 _ a1) (Case e2 _ a2) =
+    go e1 e2 &&
+    List.all2 goAlt a1 a2
+   where
+    goAlt (p1,r1) (p2,r2) = p1 == p2 && go r1 r2
+  go (Cast e1 l1 r1) (Cast e2 l2 r2) =
+    go e1 e2 &&
+    eqType l1 l2 &&
+    eqType r1 r2
+  go (Tick t1 e1) (Tick t2 e2) = t1 == t2 && go e1 e2
+  go _ _ = False
+
 instance Eq Type where
   (==) = aeqType
 
@@ -947,5 +1007,16 @@
 instance Eq Term where
   (==) = aeqTerm
 
+instance TypeError (
+        'Text "A broken implementation of Hashable Term has been "
+  ':<>: 'Text "removed in Clash 1.4.7. If this is an issue for you, please submit "
+  ':<>: 'Text "an issue report at https://github.com/clash-lang/clash-compiler/issues."
+  ) => Hashable Term where
+    hashWithSalt = error "Term.hashWithSalt: unreachable"
+
+
+
 instance Ord Term where
   compare = acmpTerm
+
+deriving instance Ord TickInfo
diff --git a/src/Clash/Core/Term.hs b/src/Clash/Core/Term.hs
--- a/src/Clash/Core/Term.hs
+++ b/src/Clash/Core/Term.hs
@@ -13,6 +13,7 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 module Clash.Core.Term
   ( Term (..)
@@ -33,6 +34,7 @@
   , Alt
   , TickInfo (..)
   , stripTicks
+  , stripAllTicks
   , partitionTicks
   , NameMod (..)
   , PrimInfo (..)
@@ -59,8 +61,8 @@
 import qualified Data.DList                    as DList
 import Data.Either                             (lefts, rights)
 import Data.Foldable                           (foldl')
-import Data.Maybe                              (catMaybes)
 import Data.Hashable                           (Hashable)
+import Data.Maybe                              (catMaybes)
 import Data.List                               (nub, partition)
 import Data.Text                               (Text)
 import GHC.Generics
@@ -77,7 +79,7 @@
 import {-# SOURCE #-} Clash.Core.Subst         () -- instance Eq Type
 import {-# SOURCE #-} Clash.Core.Type          (Type)
 import Clash.Core.Var                          (Var(Id), Id, TyVar)
-import Clash.Util                              (curLoc)
+import Clash.Util                              (curLoc, second)
 
 -- | Term representation in the CoreHW language: System F + LetRec + Case
 data Term
@@ -94,7 +96,7 @@
                                             -- alternatives, list of alternatives
   | Cast    !Term !Type !Type               -- ^ Cast a term from one type to another
   | Tick    !TickInfo !Term                 -- ^ Annotated term
-  deriving (Show,Generic,NFData,Hashable,Binary)
+  deriving (Show, Generic, NFData, Binary)
 
 data TickInfo
   = SrcSpan !SrcSpan
@@ -107,7 +109,7 @@
   | NoDeDup
   -- ^ Do not deduplicate, i.e. /keep/, an expression inside a case-alternative;
   -- do not try to share expressions between multiple branches.
-  deriving (Eq,Show,Generic,NFData,Hashable,Binary)
+  deriving (Eq, Show, Generic, NFData, Binary)
 
 -- | Tag to indicate which instance/register name modifier was used
 data NameMod
@@ -119,12 +121,12 @@
   -- ^ @Clash.Magic.suffixNameP@
   | SetName
   -- ^ @Clash.Magic.setName@
-  deriving (Eq,Show,Generic,NFData,Hashable,Binary)
+  deriving (Eq,Ord,Show,Generic,NFData,Hashable,Binary)
 
 data IsMultiPrim
   = SingleResult
   | MultiResult
-  deriving (Show, Generic, NFData, Hashable, Binary)
+  deriving (Show, Generic, NFData, Eq, Hashable, Binary)
 
 data PrimInfo = PrimInfo
   { primName :: !Text
@@ -137,7 +139,7 @@
   -- the variables it should assign its results to.
   --
   -- See: 'Clash.Normalize.Transformations.setupMultiResultPrim'
-  } deriving (Show,Generic,NFData,Hashable,Binary)
+  } deriving (Show, Generic, NFData, Binary)
 
 data MultiPrimInfo = MultiPrimInfo
   { mpi_primInfo :: PrimInfo
@@ -171,7 +173,7 @@
   -- ^ Literal pattern
   | DefaultPat
   -- ^ Default pattern
-  deriving (Eq,Ord,Show,Generic,NFData,Hashable,Binary)
+  deriving (Eq, Ord, Show, Generic, NFData, Binary)
 
 type Alt = (Pat,Term)
 
@@ -237,7 +239,7 @@
   -- ^ Body of a Cast
   | TickC TickInfo
   -- ^ Body of a Tick
-  deriving (Show, Generic, NFData, Hashable, Binary)
+  deriving (Show, Generic, NFData, Binary)
 
 -- | A list of @CoreContext@ describes the complete navigation path from the
 -- top-level to a specific sub-expression.
@@ -282,6 +284,20 @@
 stripTicks :: Term -> Term
 stripTicks (Tick _ e) = stripTicks e
 stripTicks e = e
+
+-- | Like 'stripTicks' but removes all ticks from subexpressions.
+stripAllTicks :: Term -> Term
+stripAllTicks = go
+ where
+  go (Lam i x) = Lam i (go x)
+  go (TyLam i x) = TyLam i (go x)
+  go (App f x) = App (go f) (go x)
+  go (TyApp f a) = TyApp (go f) a
+  go (Letrec bs x) = Letrec (map (second go) bs) (go x)
+  go (Case x ty alts) = Case (go x) ty (fmap go <$> alts)
+  go (Cast x a b) = Cast (go x) a b
+  go (Tick _ x) = go x
+  go x = x
 
 -- | Split a (Type)Application in the applied term and it arguments
 collectArgs :: Term -> (Term, [Either Term Type])
diff --git a/src/Clash/Core/Type.hs b/src/Clash/Core/Type.hs
--- a/src/Clash/Core/Type.hs
+++ b/src/Clash/Core/Type.hs
@@ -16,6 +16,7 @@
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 module Clash.Core.Type
   ( Type (..)
@@ -61,7 +62,7 @@
 import           Control.DeepSeq        as DS
 import           Data.Binary            (Binary)
 import           Data.Coerce            (coerce)
-import           Data.Hashable          (Hashable)
+import           Data.Hashable          (Hashable (hashWithSalt))
 import           Data.List              (foldl')
 import           Data.List.Extra        (splitAtList)
 import           Data.Maybe             (isJust, mapMaybe)
@@ -69,6 +70,7 @@
 import           GHC.Generics           (Generic(..))
 import           GHC.Integer            (smallInteger)
 import           GHC.Integer.Logarithms (integerLogBase#)
+import           GHC.TypeLits           (type TypeError, ErrorMessage(Text, (:<>:)))
 
 -- GHC API
 #if MIN_VERSION_ghc(9,0,0)
@@ -132,7 +134,14 @@
   | AppTy    !Type !Type        -- ^ Type Application
   | LitTy    !LitTy             -- ^ Type literal
   | AnnType  [Attr'] !Type      -- ^ Annotated type, see Clash.Annotations.SynthesisAttributes
-  deriving (Show,Generic,NFData,Hashable,Binary)
+  deriving (Show, Generic, NFData, Binary)
+
+instance TypeError (
+        'Text "A broken implementation of Hashable Type has been "
+  ':<>: 'Text "removed in Clash 1.4.7. If this is an issue for you, please submit "
+  ':<>: 'Text "an issue report at https://github.com/clash-lang/clash-compiler/issues."
+  ) => Hashable Type where
+    hashWithSalt = error "Type.hashWithSalt: unreachable"
 
 -- | An easier view on types
 data TypeView
diff --git a/src/Clash/Core/Type.hs-boot b/src/Clash/Core/Type.hs-boot
--- a/src/Clash/Core/Type.hs-boot
+++ b/src/Clash/Core/Type.hs-boot
@@ -13,7 +13,6 @@
 
 import Control.DeepSeq                  (NFData)
 import Data.Binary                      (Binary)
-import Data.Hashable                    (Hashable)
 import GHC.Generics                     (Generic)
 
 import                Clash.Core.Name
@@ -28,7 +27,6 @@
 instance Generic  Type
 instance Show     Type
 instance NFData   Type
-instance Hashable Type
 instance Binary   Type
 
 mkTyConTy :: TyConName -> Type
diff --git a/src/Clash/Driver/Manifest.hs b/src/Clash/Driver/Manifest.hs
--- a/src/Clash/Driver/Manifest.hs
+++ b/src/Clash/Driver/Manifest.hs
@@ -19,6 +19,7 @@
 import           Data.Aeson
   (ToJSON(toJSON), FromJSON(parseJSON), KeyValue ((.=)), (.:), (.:?))
 import           Data.Aeson.Types (Parser)
+import qualified Data.Binary as Binary
 import qualified Data.ByteString.Base16 as Base16
 import qualified Data.ByteString.Lazy as ByteStringLazy
 import           Data.ByteString (ByteString)
@@ -378,7 +379,9 @@
     , opt_hdlDir = Nothing
     }
 
-  topHash = hash
+  -- TODO: Binary encoding does not account for alpha equivalence (nor should
+  --       it?), so the cache behaves more pessimisticly than it could.
+  topHash = hash $ Binary.encode
     ( tops
     , hashCompiledPrimMap primMap
     , show clashModDate
diff --git a/src/Clash/Driver/Types.hs b/src/Clash/Driver/Types.hs
--- a/src/Clash/Driver/Types.hs
+++ b/src/Clash/Driver/Types.hs
@@ -314,6 +314,44 @@
 newtype SdcInfo = SdcInfo
   { sdcClock :: [(Text, VDomainConfiguration)]
   }
+  deriving (Show)
+
+
+instance Eq ClashOpts where
+  s0 == s1 =
+    opt_inlineLimit s0 == opt_inlineLimit s1 &&
+    opt_specLimit s0 == opt_specLimit s1 &&
+    opt_inlineFunctionLimit s0 == opt_inlineFunctionLimit s1 &&
+    opt_inlineConstantLimit s0 == opt_inlineConstantLimit s1 &&
+    opt_evaluatorFuelLimit s0 == opt_evaluatorFuelLimit s1 &&
+    opt_cachehdl s0 == opt_cachehdl s1 &&
+    opt_clear s0 == opt_clear s1 &&
+    opt_primWarn s0 == opt_primWarn s1 &&
+    (opt_color s0 `eqOverridingBool` opt_color s1) &&
+    opt_intWidth s0 == opt_intWidth s1 &&
+    opt_hdlDir s0 == opt_hdlDir s1 &&
+    opt_hdlSyn s0 == opt_hdlSyn s1 &&
+    opt_errorExtra s0 == opt_errorExtra s1 &&
+    opt_floatSupport s0 == opt_floatSupport s1 &&
+    opt_importPaths s0 == opt_importPaths s1 &&
+    opt_componentPrefix s0 == opt_componentPrefix s1 &&
+    opt_newInlineStrat s0 == opt_newInlineStrat s1 &&
+    opt_escapedIds s0 == opt_escapedIds s1 &&
+    opt_lowerCaseBasicIds s0 == opt_lowerCaseBasicIds s1 &&
+    opt_ultra s0 == opt_ultra s1 &&
+    opt_forceUndefined s0 == opt_forceUndefined s1 &&
+    opt_checkIDir s0 == opt_checkIDir s1 &&
+    opt_aggressiveXOpt s0 == opt_aggressiveXOpt s1 &&
+    opt_aggressiveXOptBB s0 == opt_aggressiveXOptBB s1 &&
+    opt_inlineWFCacheLimit s0 == opt_inlineWFCacheLimit s1 &&
+    opt_edalize s0 == opt_edalize s1
+
+   where
+    eqOverridingBool :: OverridingBool -> OverridingBool -> Bool
+    eqOverridingBool Auto Auto = True
+    eqOverridingBool Always Always = True
+    eqOverridingBool Never Never = True
+    eqOverridingBool _ _ = False
 
 -- | Render an SDC file from an SdcInfo.
 -- The clock periods, waveforms, and targets are all hardcoded.
diff --git a/src/Clash/Netlist.hs b/src/Clash/Netlist.hs
--- a/src/Clash/Netlist.hs
+++ b/src/Clash/Netlist.hs
@@ -30,7 +30,6 @@
 import           Data.Char                        (ord)
 import           Data.Either                      (partitionEithers, rights)
 import           Data.Foldable                    (foldlM)
-import qualified Data.HashMap.Strict              as HashMapS
 import qualified Data.HashMap.Lazy                as HashMap
 import           Data.List                        (elemIndex, partition, sortOn)
 import           Data.List.Extra                  (zipEqual)
@@ -193,7 +192,7 @@
         , _isTestBench=isTb
         , _backEndITE=ite
         , _backend=be
-        , _htyCache=HashMapS.empty
+        , _htyCache=mempty
         }
 
 -- | Generate names for all binders in "BindingMap", except for the ones already
diff --git a/src/Clash/Netlist/BlackBox/Types.hs b/src/Clash/Netlist/BlackBox/Types.hs
--- a/src/Clash/Netlist/BlackBox/Types.hs
+++ b/src/Clash/Netlist/BlackBox/Types.hs
@@ -48,7 +48,7 @@
   -- ^ Render blackbox, even if result type is void
   | NoRenderVoid
   -- ^ Don't render blackbox result type is void. Default for all blackboxes.
-  deriving (Show, Generic, NFData, Binary, Hashable, FromJSON)
+  deriving (Show, Generic, NFData, Binary, Eq, Hashable, FromJSON)
 
 data TemplateKind
   = TDecl
@@ -201,7 +201,7 @@
   | CtxName
   -- ^ The "context name", name set by `Clash.Magic.setName`, defaults to the
   -- name of the closest binder
-  deriving (Show, Generic, NFData, Binary, Hashable)
+  deriving (Show, Generic, NFData, Binary, Eq, Hashable)
 
 -- | Component instantiation hole. First argument indicates which function argument
 -- to instantiate. Third argument corresponds to output and input assignments,
@@ -225,7 +225,7 @@
       -- to zero. Haskell blackboxes can use this data type.
       [(BlackBoxTemplate,BlackBoxTemplate)]
       -- FIELD (name of signal, type of signal)
-  deriving (Show, Generic, NFData, Binary, Hashable)
+  deriving (Show, Generic, NFData, Binary, Eq, Hashable)
 
 data HdlSyn = Vivado | Quartus | Other
   deriving (Eq, Show, Read, Generic, NFData, Binary, Hashable)
diff --git a/src/Clash/Netlist/Types.hs b/src/Clash/Netlist/Types.hs
--- a/src/Clash/Netlist/Types.hs
+++ b/src/Clash/Netlist/Types.hs
@@ -53,6 +53,7 @@
 import qualified Data.List                  as List
 import Data.IntMap                          (IntMap, empty)
 import Data.Map.Ordered                     (OMap)
+import Data.Map                             (Map)
 import Data.Maybe                           (mapMaybe)
 import qualified Data.Set                   as Set
 import Data.Text                            (Text)
@@ -99,7 +100,7 @@
   -- ^ (Maybe) a topentity annotation
   , topIsTestBench :: Bool
   -- ^ Whether this entity is a test bench
-  } deriving (Generic, Show)
+  } deriving (Generic, Show, Eq)
 
 -- | Same as "TopEntity", but with all port names that end up in HDL specified
 data ExpandedTopEntity a = ExpandedTopEntity
@@ -132,7 +133,7 @@
   deriving newtype (Functor, Monad, Applicative, MonadReader NetlistEnv,
                     Strict.MonadState NetlistState, Strict.MonadIO, MonadFail)
 
-type HWMap = HashMap Type (Either String FilteredHWType)
+type HWMap = Map Type (Either String FilteredHWType)
 
 -- | See 'is_freshCache'
 type FreshCache = HashMap Text (IntMap Word)
diff --git a/src/Clash/Netlist/Types.hs-boot b/src/Clash/Netlist/Types.hs-boot
--- a/src/Clash/Netlist/Types.hs-boot
+++ b/src/Clash/Netlist/Types.hs-boot
@@ -30,3 +30,5 @@
 data NetlistMonad a
 data PreserveCase = PreserveCase | ToLower
 instance Hashable PreserveCase
+instance Eq PreserveCase
+instance Show PreserveCase
diff --git a/src/Clash/Netlist/Util.hs b/src/Clash/Netlist/Util.hs
--- a/src/Clash/Netlist/Util.hs
+++ b/src/Clash/Netlist/Util.hs
@@ -45,6 +45,8 @@
 import           Control.Applicative     (Alternative((<|>)))
 import           Data.List               (unzip4, partition)
 import qualified Data.List               as List
+import qualified Data.Map                as Map
+import           Data.Map                (Map)
 import           Data.Maybe
   (catMaybes, fromMaybe, isNothing, mapMaybe, isJust, listToMaybe, maybeToList)
 import           Text.Printf             (printf)
@@ -379,19 +381,19 @@
   -- ^ Type to convert to HWType
   -> State HWMap (Either String FilteredHWType)
 coreTypeToHWType builtInTranslation reprs m ty = do
-  htyM <- HashMap.lookup ty <$> get
+  htyM <- Map.lookup ty <$> get
   case htyM of
     Just hty -> return hty
     _ -> do
       hty0M <- builtInTranslation reprs m ty
       hty1  <- go hty0M ty
-      modify (HashMap.insert ty hty1)
+      modify (Map.insert ty hty1)
       return hty1
  where
   -- Try builtin translation; for now this is hardcoded to be the one in ghcTypeToHWType
   go :: Maybe (Either String FilteredHWType)
      -> Type
-     -> State (HashMap Type (Either String FilteredHWType))
+     -> State (Map Type (Either String FilteredHWType))
               (Either String FilteredHWType)
   go (Just hwtyE) _ = pure $ maybeConvertToCustomRepr reprs ty <$> hwtyE
   -- Strip transparant types:
@@ -655,7 +657,7 @@
   -> Bool
 representableType builtInTranslation reprs stringRepresentable m =
     either (const False) isRepresentable .
-    flip evalState HashMap.empty .
+    flip evalState mempty .
     coreTypeToHWType' builtInTranslation reprs m
   where
     isRepresentable hty = case hty of
diff --git a/src/Clash/Normalize/Transformations.hs b/src/Clash/Normalize/Transformations.hs
--- a/src/Clash/Normalize/Transformations.hs
+++ b/src/Clash/Normalize/Transformations.hs
@@ -77,7 +77,6 @@
 import           Data.Default
 import qualified Data.Either                 as Either
 import qualified Data.HashMap.Lazy           as HashMap
-import qualified Data.HashMap.Strict         as HashMapS
 import           Data.List                   ((\\))
 import qualified Data.List                   as List
 import qualified Data.List.Extra             as List
@@ -516,8 +515,29 @@
 {-# SCC inlineNonRepWorker #-}
 
 
+{-
+NOTE: caseOneAlt before caseCon'
+
+When you put a bang on a signal argument:
+    f :: Signal d a -> _
+    f !x = ...
+GHC generates a case like:
+    case x of
+      _ :- _ -> ...
+
+When this f is inlined in an:
+    g = f (pure False)
+And clash does its Signal d a ~ a thing we get:
+    g = case False of
+      _ :- _ -> ...
+Because no pattern matches caseCon transforms this into
+    g = undefined
+
+By trying caseOneAlt first clash can instead drop the case
+and use the body of the single alternative.
+-}
 caseCon :: HasCallStack => NormRewrite
-caseCon = apply "caseOneAlt" (const caseOneAlt) >-! caseCon'
+caseCon = const caseOneAlt >-! caseCon'
 
 -- | Specialize a Case-decomposition (replace by the RHS of an alternative) if
 -- the subject is (an application of) a DataCon; or if there is only a single
@@ -676,7 +696,7 @@
         let subjTy = termType tcm subj
         tran <- Lens.view typeTranslator
         reprs <- Lens.view customReprs
-        case (`evalState` HashMapS.empty) (coreTypeToHWType tran reprs tcm subjTy) of
+        case (`evalState` mempty) (coreTypeToHWType tran reprs tcm subjTy) of
           Right (FilteredHWType (Void (Just hty)) _areVoids)
             | hty `elem` [BitVector 0, Unsigned 0, Signed 0, Index 1]
             -- If we know that the type of the subject is zero-bits wide and
@@ -1359,10 +1379,12 @@
 appPropFast ctx@(TransformContext is _) = \case
   e@App {}
     | let (fun,args,ticks) = collectArgsTicks e
-    -> go is (deShadowTerm is fun) args ticks
+    -> do (eN,hasChanged) <- listen (go is (deShadowTerm is fun) args ticks)
+          if Monoid.getAny hasChanged then return eN else return e
   e@TyApp {}
     | let (fun,args,ticks) = collectArgsTicks e
-    -> go is (deShadowTerm is fun) args ticks
+    -> do (eN,hasChanged) <- listen (go is (deShadowTerm is fun) args ticks)
+          if Monoid.getAny hasChanged then return eN else return e
   e          -> return e
  where
   go :: InScopeSet -> Term -> [Either Term Type] -> [TickInfo]
@@ -1935,7 +1957,7 @@
     -- corresponding (sub)field from the target variable.
     --
     -- TODO: See [Note: Breaks on constants and predetermined equality]
-    eqApp tcm v args (collectArgs . stripTicks -> (Var v',args'))
+    eqApp tcm v args (collectArgs . stripAllTicks -> (Var v',args'))
       | isGlobalId v'
       , v == v'
       , let args2 = Either.lefts args'
@@ -1943,9 +1965,9 @@
       = and (zipWith (eqArg tcm) args args2)
     eqApp _ _ _ _ = False
 
-    eqArg _ v1 v2@(stripTicks -> Var {})
+    eqArg _ v1 v2@Var {}
       = v1 == v2
-    eqArg tcm v1 v2@(collectArgs . stripTicks -> (Data _, args'))
+    eqArg tcm v1 v2@(collectArgs -> (Data _, args'))
       | let t1 = normalizeType tcm (termType tcm v1)
       , let t2 = normalizeType tcm (termType tcm v2)
       , t1 == t2
diff --git a/src/Clash/Primitives/DSL.hs b/src/Clash/Primitives/DSL.hs
--- a/src/Clash/Primitives/DSL.hs
+++ b/src/Clash/Primitives/DSL.hs
@@ -1,6 +1,7 @@
 {-|
   Copyright   :  (C) 2019, Myrtle Software Ltd.
-                     2020, QBayLogic B.V.
+                     2020-2021, QBayLogic B.V.
+                     2021, Myrtle.ai
   License     :  BSD2 (see the file LICENSE)
   Maintainer  :  QBayLogic B.V. <devops@qbaylogic.com>
 
@@ -56,6 +57,7 @@
   -- ** Conversion
   , toBV
   , fromBV
+  , enableToBit
   , boolToBit
   , boolFromBit
   , boolFromBitVector
@@ -370,6 +372,25 @@
         ]
     pure texp
   tExpr -> error $ "boolToBit: Got \"" <> show tExpr <> "\" expected Bool"
+
+-- | Convert an enable to a bit.
+enableToBit
+  :: (HasCallStack, Backend backend)
+  => Text
+  -- ^ Name hint for intermediate signal
+  -> TExpr
+  -> State (BlockState backend) TExpr
+enableToBit bitName = \case
+  TExpr ena@(Enable _) enableExpr -> do
+    texp@(~(TExpr _ (Identifier uniqueBitName Nothing))) <- declare bitName Wire Bit
+    addDeclaration $
+      CondAssignment uniqueBitName Bit enableExpr ena
+        -- Enable normalizes to Bool for all current backends
+        [ (Just (BoolLit True), Literal Nothing (BitLit H))
+        , (Nothing            , Literal Nothing (BitLit L))
+        ]
+    pure texp
+  tExpr -> error $ "enableToBit: Got \"" <> show tExpr <> "\" expected Enable"
 
 -- | Use to create an output `Bool` from a `Bit`. The expression given
 --   must be the identifier of the bool you wish to get assigned.
diff --git a/src/Clash/Primitives/Types.hs b/src/Clash/Primitives/Types.hs
--- a/src/Clash/Primitives/Types.hs
+++ b/src/Clash/Primitives/Types.hs
@@ -129,13 +129,13 @@
   -- ^ Template source stored in file on filesystem
   | TInline Text
   -- ^ Template stored inline
-  deriving (Show, Eq, Generic, NFData)
+  deriving (Show, Eq, Hashable, Generic, NFData)
 
 
 data TemplateFormat
   = TTemplate
   | THaskell
-  deriving (Show, Generic, Hashable, NFData)
+  deriving (Show, Generic, Eq, Hashable, NFData)
 
 -- | Data type to indicate what arguments are in use by a BlackBox
 data UsedArguments
@@ -143,7 +143,7 @@
   -- ^ Only these are used
   | IgnoredArguments [Int]
   -- ^ All but these are used
-  deriving (Show, Generic, Hashable, NFData, Binary)
+  deriving (Show, Generic, Eq, Hashable, NFData, Binary)
 
 -- | Externally defined primitive
 data Primitive a b c d
@@ -229,7 +229,7 @@
   , primSort :: !Text
     -- ^ Additional information
   }
-  deriving (Show, Generic, NFData, Binary, Hashable, Functor)
+  deriving (Show, Generic, NFData, Binary, Eq, Hashable, Functor)
 
 instance FromJSON UnresolvedPrimitive where
   parseJSON (Object v) =
diff --git a/src/Clash/Rewrite/Types.hs b/src/Clash/Rewrite/Types.hs
--- a/src/Clash/Rewrite/Types.hs
+++ b/src/Clash/Rewrite/Types.hs
@@ -30,7 +30,6 @@
 import Control.Monad.State.Strict            (State)
 import Control.Monad.Writer                  (MonadWriter (..))
 import Data.Binary                           (Binary)
-import Data.Hashable                         (Hashable)
 import Data.IntMap.Strict                    (IntMap)
 import Data.Monoid                           (Any)
 import qualified Data.Set                    as Set
@@ -68,7 +67,7 @@
   -- ^ Term before `apply`
   , t_after  :: Term
   -- ^ Term after `apply`
-  } deriving (Show, Generic, NFData, Hashable, Binary)
+  } deriving (Show, Generic, NFData, Binary)
 
 -- | State of a rewriting session
 data RewriteState extra
diff --git a/src/Clash/Rewrite/Util.hs b/src/Clash/Rewrite/Util.hs
--- a/src/Clash/Rewrite/Util.hs
+++ b/src/Clash/Rewrite/Util.hs
@@ -78,7 +78,7 @@
 import           Clash.Core.Name
 import           Clash.Core.Pretty           (showPpr)
 import           Clash.Core.Subst
-  (substTmEnv, aeqTerm, aeqType, extendIdSubst, mkSubst, substTm)
+  (substTmEnv, aeqTerm, aeqType, extendIdSubst, mkSubst, substTm, eqTerm)
 import           Clash.Core.Term
 import           Clash.Core.TermInfo
 import           Clash.Core.TyCon            (TyConMap)
@@ -269,7 +269,7 @@
                      ]
             ) (return ())
 
-  Monad.when (lvl >= DebugSilent && not hasChanged && not (exprOld `aeqTerm` exprNew)) $
+  Monad.when (lvl >= DebugSilent && not hasChanged && not (exprOld `eqTerm` exprNew)) $
     error $ $(curLoc) ++ "Expression changed without notice(" ++ name ++  "): before"
                       ++ before ++ "\nafter:\n" ++ after
 
@@ -768,7 +768,7 @@
       specBndrs :: [Either Id TyVar]
       specBndrs = map (Lens.over _Left (normalizeId tcm)) specBndrsIn
       specAbs :: Either Term Type
-      specAbs = either (Left . (`mkAbstraction` specBndrs)) (Right . id) specArg
+      specAbs = either (Left . stripAllTicks . (`mkAbstraction` specBndrs)) (Right . id) specArg
   -- Determine if 'f' has already been specialized on (a type-normalized) 'specArg'
   specM <- Map.lookup (f,argLen,specAbs) <$> Lens.use (extra.specMapLbl)
   case specM of
diff --git a/src/Clash/Util.hs b/src/Clash/Util.hs
--- a/src/Clash/Util.hs
+++ b/src/Clash/Util.hs
@@ -276,15 +276,18 @@
 pkgIdFromTypeable = tyConPackage . typeRepTyCon . typeOf
 
 reportTimeDiff :: UTCTime -> UTCTime -> String
-reportTimeDiff start end =
-  Clock.formatTime Clock.defaultTimeLocale fmt
+reportTimeDiff end start
+  | diff >= Clock.nominalDay = show days <> "d" <> Clock.formatTime Clock.defaultTimeLocale fmt
+    (Clock.UTCTime (toEnum 0) (fromRational (toRational hms)))
+  | otherwise = Clock.formatTime Clock.defaultTimeLocale fmt
     (Clock.UTCTime (toEnum 0) (fromRational (toRational diff)))
  where
-  diff = Clock.diffUTCTime start end
+  diff = Clock.diffUTCTime end start
+  (days,hms) = divMod @Integer (floor diff) (floor Clock.nominalDay)
   fmt  | diff >= 3600
-       = "%-Hh%-Mm%-S%03Qs"
+       = "%-Hh%-Mm%-Ss"
        | diff >= 60
-       = "%-Mm%-S%03Qs"
+       = "%-Mm%-Ss"
        | otherwise
        = "%-S%03Qs"
 
@@ -302,6 +305,8 @@
 --  * compiling files with clash
 --  * running output tests with runghc
 --  * compiling (local) Template/Blackbox functions with Hint
+--
+-- When changing this list please update docs/developing-hardware/language.rst
 wantedLanguageExtensions :: [LangExt.Extension]
 wantedLanguageExtensions =
   [ LangExt.BinaryLiterals
diff --git a/src/Data/Aeson/Extra.hs b/src/Data/Aeson/Extra.hs
--- a/src/Data/Aeson/Extra.hs
+++ b/src/Data/Aeson/Extra.hs
@@ -49,7 +49,7 @@
 -- | Aeson versions <1.5.0 accept unescaped newlines in JSON strings. This is in
 -- violation of RFC 7159. Aeson 1.5.0 fixes this bug. Unfortunately, "Clash
 -- JSON" files rely on the old behavior. This function replaces newlines (in
--- stings) with their escaped variants.
+-- strings) with their escaped variants.
 toSpecNewlines
   :: ByteString
   -> Either UnicodeException (LineMap, ByteString)
diff --git a/src/Data/List/Extra.hs b/src/Data/List/Extra.hs
--- a/src/Data/List/Extra.hs
+++ b/src/Data/List/Extra.hs
@@ -11,6 +11,7 @@
   , equalLength
   , countEq
   , zipEqual
+  , all2
 
   -- * From Control.Monad.Extra
   , anyM
@@ -85,6 +86,13 @@
 equalLength [] [] = True
 equalLength (_:as) (_:bs) = equalLength as bs
 equalLength _ _ = False
+
+-- | Like 'all', but the predicate operates over two lists. Asserts to 'False'
+-- when the lists are of unequal length
+all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
+all2 _ [] [] = True
+all2 p (a:as) (b:bs) = p a b && all2 p as bs
+all2 _ _ _ = False
 
 -- | Return number of occurrences of an item in a list
 countEq
diff --git a/src/GHC/SrcLoc/Extra.hs b/src/GHC/SrcLoc/Extra.hs
--- a/src/GHC/SrcLoc/Extra.hs
+++ b/src/GHC/SrcLoc/Extra.hs
@@ -33,6 +33,12 @@
 import FastString                           (FastString (..), bytesFS, mkFastStringByteList)
 #endif
 
+
+#if MIN_VERSION_ghc(9,0,0)
+deriving instance Ord SrcSpan
+deriving instance Ord UnhelpfulSpanReason
+#endif
+
 deriving instance Generic SrcSpan
 instance Hashable SrcSpan
 
