diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for ice40-prim
+
+## 0.1.0.0 -- 2021-02-15
+
+* First version. 
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2020, dopamane
+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.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# ice40-prim
+![Haskell CI](https://github.com/standardsemiconductor/ice40-prim/workflows/Haskell%20CI/badge.svg)
+
+Lattice iCE40 Primitive IP
+## Supported IP Modules
+* [Ice40.Spram](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Spram.hs) - For more information see the [iCE40 SPRAM Usage Guide](https://github.com/standardsemiconductor/VELDT-info/blob/master/FPGA-TN-02022-1-2-iCE40-SPRAM-Usage-Guide.pdf)
+  * sysMem Single Port RAM Memory (SPRAM)
+  * Each block of SPRAM is 16k x 16 (256 kbits)
+  * 16-bit data width with nibble mast control
+  * Cascadable design for deeper/wider SPRAM
+  * Three power modes, standby, sleep, and power off
+
+* [Ice40.Mac](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Mac.hs) - For more information see the [DSP Function Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/DSPFunctionUsageGuideforICE40Devices.pdf)
+  * 16-bit x 16-bit Multiplier, or two independent 8-bit x 8-bit multipliers
+  * Optional independent pipeline control on input Register, Output Register, and Intermediate Register for faster clock performance
+  * 32-bit accumulator, or two independent 16-bit accumulators
+  * 32-bit, or two independent 16-bit adder/subtractor functions, registered or asynchronous
+  * Cascadable to create wider accumulator blocks
+
+* [Ice40.Osc](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Osc.hs)
+  * on-chip oscillator
+  * Low-power low frequency oscillator of 10 kHz
+  * High frequency oscillator configurable to 48 Mhz, 24 Mhz, 12 Mhz, or 6 Mhz
+  * See also [Ice40.Clock](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Clock.hs) for clock domains and reset
+
+* [Ice40.Rgb](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Rgb.hs) - For more information see the [iCE40 LED Driver Usage Guide](https://github.com/standardsemiconductor/VELDT-info/blob/master/ICE40LEDDriverUsageGuide.pdf)
+  * RGB High Current Drive I/O Pins
+  * Provides sinking current to an LED connecting to the positive supply
+  * Three outputs designed to drive the RGB LEDs
+  * RGB drive current is user programmable from 4mA to 24mA, in increments of 4mA
+
+* [Ice40.Led](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Led.hs) - For more information see the [iCE40 LED Driver Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/ICE40LEDDriverUsageGuide.pdf)
+  * LED PWM IP
+  * Provide easier usage of RGB high current drivers
+  * Provides flexibility for user to dynamically change the modulation width of each of the RGB LED driver
+  * User can dynamically change ON and OFF-time durations
+  * Ability to turn LEDs on and off gradually with breath-on and breath-off time
+
+* [Ice40.Spi](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/Spi.hs) - For more information see the [Advanced SPI and I2C Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/AdvancediCE40SPII2CHardenedIPUsageGuide.pdf)
+  * User SPI IP
+  * Configurable Boss and Worker modes
+  * Full-Duplex data transfer
+  * Mode fault error flag with CPU interrupt capability
+  * Double-buffered data register
+  * Serial clock with programmable polarity and phase
+  * LSB First or MSB First data transfer
+
+* [Ice40.IO](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/IO.hs)
+  * sysIO
+
+* [Ice40.I2c](https://github.com/standardsemiconductor/ice40-prim/blob/main/src/Ice40/I2c.hs) - For more information see the [Advanced SPI and I2C Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/AdvancediCE40SPII2CHardenedIPUsageGuide.pdf)
+  * User I2C IP
+  * Boss and Worker operation
+  * 7-bit and 10-bit addressing
+  * Multi-master arbitration support
+  * Clock stretching
+  * Up to 400 kHz data transfer speed
+  * General Call support
+  * Optionally delaying input or output data, or both
+  * Optional filter on SCL input
+
+## Lattice Documentation
+
+[iCE40 UltraPlus Family Data Sheet PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/FPGA-DS-02008-1-9-iCE40-UltraPlus-Family-Data-Sheet.pdf)
+
+[iCE Technology Library PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf)
+
+[Advanced SPI and I2C Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/AdvancediCE40SPII2CHardenedIPUsageGuide.pdf)
+
+[iCE40 LED Driver Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/ICE40LEDDriverUsageGuide.pdf)
+
+[iCE40 SPRAM Usage Guide](https://github.com/standardsemiconductor/VELDT-info/blob/master/FPGA-TN-02022-1-2-iCE40-SPRAM-Usage-Guide.pdf)
+
+[DSP Function Usage Guide PDF](https://github.com/standardsemiconductor/VELDT-info/blob/master/DSPFunctionUsageGuideforICE40Devices.pdf)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/ice40-prim.cabal b/ice40-prim.cabal
new file mode 100644
--- /dev/null
+++ b/ice40-prim.cabal
@@ -0,0 +1,89 @@
+cabal-version:       >=1.10
+name:                ice40-prim
+version:             0.1.0.0
+synopsis:            Lattice iCE40 Primitive IP
+description:         Clash primitives to instantiate Lattice Semiconductor's iCE40 FPGA hard IP
+bug-reports:         https://github.com/standardsemiconductor/ice40-prim/issues
+license:             BSD3
+license-file:        LICENSE
+author:              dopamane
+maintainer:          dopamane <standard.semiconductor@gmail.com>
+copyright:           Copyright (c) 2020-2021 David Cox
+category:            Hardware
+build-type:          Simple
+extra-source-files:  CHANGELOG.md, README.md
+
+source-repository head
+  type: git
+  location: git://github.com/standardsemiconductor/ice40-prim.git
+                     
+library
+        exposed-modules: Ice40.Clock,
+                         Ice40.Led,
+                         Ice40.Mac,
+                         Ice40.Osc,
+                         Ice40.Spram,
+                         Ice40.Rgb,
+                         Ice40.Spi,
+                         Ice40.I2c,
+                         Ice40.IO
+        hs-source-dirs: src
+        build-depends: base >=4.12 && <4.15,
+                       Cabal,
+                       interpolate,
+                       clash-prelude >=1.2.5 && <1.4,
+                       ghc-typelits-natnormalise,
+                       ghc-typelits-extra,
+                       ghc-typelits-knownnat
+        ghc-options:
+          -Wall -Wcompat
+          -haddock
+
+          -- Plugins to support type-level constraint solving on naturals
+          -fplugin GHC.TypeLits.Extra.Solver
+          -fplugin GHC.TypeLits.Normalise
+          -fplugin GHC.TypeLits.KnownNat.Solver
+
+          -- Worker wrappers introduce unstable names for functions that might
+          -- have blackboxes attached for them. You can disable this,
+          -- but be sure to add a no-specialize pragma to every function
+          -- with a blackbox.
+          -fno-worker-wrapper
+        default-language: Haskell2010
+        default-extensions:
+          BangPatterns
+          BinaryLiterals
+          ConstraintKinds
+          DataKinds
+          DefaultSignatures
+          DeriveAnyClass
+          DeriveDataTypeable
+          DeriveFoldable
+          DeriveFunctor
+          DeriveGeneric
+          DeriveLift
+          DeriveTraversable
+          DerivingStrategies
+          InstanceSigs
+          KindSignatures
+          LambdaCase
+          NoStarIsType
+          MagicHash
+          PolyKinds
+          RankNTypes
+          RecordWildCards
+          ScopedTypeVariables
+          StandaloneDeriving
+          TupleSections
+          TypeApplications
+          TypeFamilies
+          TypeOperators
+          ViewPatterns
+          
+          -- TemplateHaskell is used to support convenience functions such as
+          -- 'listToVecTH' and 'bLit'.
+          TemplateHaskell
+          QuasiQuotes
+
+          -- Prelude isn't imported by default as Clash offers Clash.Prelude
+          NoImplicitPrelude
diff --git a/src/Ice40/Clock.hs b/src/Ice40/Clock.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Clock.hs
@@ -0,0 +1,36 @@
+{-|
+Module      : Ice40.Clock
+Description : Ice40 Clock domains
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+Commonly used clock domains with ice40 IP
+-}
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Ice40.Clock
+  ( vLattice10kHz
+  , Lattice10kHz
+  , vLattice48Mhz
+  , vLattice24Mhz
+  , vLattice12Mhz
+  , vLattice6Mhz
+  , Lattice48Mhz
+  , Lattice24Mhz
+  , Lattice12Mhz
+  , Lattice6Mhz
+  , latticeRst
+  ) where
+
+import Clash.Prelude
+
+createDomain vXilinxSystem{vName="Lattice10kHz", vPeriod=100000000}
+createDomain vXilinxSystem{vName="Lattice48Mhz", vPeriod=20833}
+createDomain vXilinxSystem{vName="Lattice24Mhz", vPeriod=41666}
+createDomain vXilinxSystem{vName="Lattice12Mhz", vPeriod=83333}
+createDomain vXilinxSystem{vName="Lattice6Mhz",  vPeriod=166660}
+
+-- | Lattice reset signal, always inactive
+latticeRst :: KnownDomain dom => Reset dom
+latticeRst = unsafeFromHighPolarity $ pure False
diff --git a/src/Ice40/I2c.hs b/src/Ice40/I2c.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/I2c.hs
@@ -0,0 +1,231 @@
+{-|
+Module      : Ice40.I2c
+Description : Ice40 I2C hard IP primitive
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+I2C hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.I2c ( i2c ) where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN i2cPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.I2c.i2cPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "i2cPrim
+    :: String          -- ARG[0]  initAddr
+    -> String          -- ARG[1]  busAddr
+    -> Clock dom       -- ARG[2]  sbclki
+    -> Signal dom Bool -- ARG[3]  sbrwi
+    -> Signal dom Bool -- ARG[4]  sbstbi
+    -> Signal dom Bit  -- ARG[5]  sbadri7
+    -> Signal dom Bit  -- ARG[6]  sbadri6
+    -> Signal dom Bit  -- ARG[7]  sbadri5
+    -> Signal dom Bit  -- ARG[8]  sbadri4
+    -> Signal dom Bit  -- ARG[9]  sbadri3
+    -> Signal dom Bit  -- ARG[10] sbadri2
+    -> Signal dom Bit  -- ARG[11] sbadri1
+    -> Signal dom Bit  -- ARG[12] sbadri0
+    -> Signal dom Bit  -- ARG[13] sbdati7
+    -> Signal dom Bit  -- ARG[14] sbdati6
+    -> Signal dom Bit  -- ARG[15] sbdati5
+    -> Signal dom Bit  -- ARG[16] sbdati4
+    -> Signal dom Bit  -- ARG[17] sbdati3
+    -> Signal dom Bit  -- ARG[18] sbdati2
+    -> Signal dom Bit  -- ARG[19] sbdati1
+    -> Signal dom Bit  -- ARG[20] sbdati0
+    -> Signal dom Bit  -- ARG[21] scli
+    -> Signal dom Bit  -- ARG[22] sdai
+    -> Unbundled dom
+         ( BitVector 8 -- sbdato
+         , Bool        -- sbacko
+         , Bit         -- i2cirq
+         , Bit         -- i2cwkup
+         , Bit         -- sclo
+         , Bit         -- scloe
+         , Bit         -- sdao
+         , Bit         -- sdaoe
+         )"
+       , "template" :
+  "//SB_I2C begin
+  wire ~GENSYM[sbdato7][0];
+  wire ~GENSYM[sbdato6][1];
+  wire ~GENSYM[sbdato5][2];
+  wire ~GENSYM[sbdato4][3];
+  wire ~GENSYM[sbdato3][4];
+  wire ~GENSYM[sbdato2][5];
+  wire ~GENSYM[sbdato1][6];
+  wire ~GENSYM[sbdato0][7];
+  wire ~GENSYM[sbacko][8];
+  wire ~GENSYM[i2cirq][9];
+  wire ~GENSYM[i2cwkup][10];
+  wire ~GENSYM[sclo][11];
+  wire ~GENSYM[scloe][12];
+  wire ~GENSYM[sdao][13];
+  wire ~GENSYM[sdaoe][14];
+
+  SB_I2C #(
+    .I2C_SLAVE_INIT_ADDR ( ~ARG[0] ),
+    .BUS_ADDR74          ( ~ARG[1] )
+  ) ~GENSYM[sb_i2c_inst][15] (
+    .SBCLKI  ( ~ARG[2]  ),
+    .SBRWI   ( ~ARG[3]  ),
+    .SBSTBI  ( ~ARG[4]  ),
+    .SBADRI7 ( ~ARG[5]  ),
+    .SBADRI6 ( ~ARG[6]  ),
+    .SBADRI5 ( ~ARG[7]  ),
+    .SBADRI4 ( ~ARG[8]  ),
+    .SBADRI3 ( ~ARG[9]  ),
+    .SBADRI2 ( ~ARG[10] ),
+    .SBADRI1 ( ~ARG[11] ),
+    .SBADRI0 ( ~ARG[12] ),
+    .SBDATI7 ( ~ARG[13] ),
+    .SBDATI6 ( ~ARG[14] ),
+    .SBDATI5 ( ~ARG[15] ),
+    .SBDATI4 ( ~ARG[16] ),
+    .SBDATI3 ( ~ARG[17] ),
+    .SBDATI2 ( ~ARG[18] ),
+    .SBDATI1 ( ~ARG[19] ),
+    .SBDATI0 ( ~ARG[20] ),
+    .SCLI    ( ~ARG[21] ),
+    .SDAI    ( ~ARG[22] ),
+    .SBDATO7 ( ~SYM[0]  ),
+    .SBDATO6 ( ~SYM[1]  ),
+    .SBDATO5 ( ~SYM[2]  ),
+    .SBDATO4 ( ~SYM[3]  ),
+    .SBDATO3 ( ~SYM[4]  ),
+    .SBDATO2 ( ~SYM[5]  ),
+    .SBDATO1 ( ~SYM[6]  ),
+    .SBDATO0 ( ~SYM[7]  ),
+    .SBACKO  ( ~SYM[8]  ),
+    .I2CIRQ  ( ~SYM[9]  ),
+    .I2CWKUP ( ~SYM[10] ),
+    .SCLO    ( ~SYM[11] ),
+    .SCLOE   ( ~SYM[12] ),
+    .SDAO    ( ~SYM[13] ),
+    .SDAOE   ( ~SYM[14] )
+  );
+
+  assign ~RESULT = { ~SYM[0]  // sbdato7
+                   , ~SYM[1]  // sbdato6
+                   , ~SYM[2]  // sbdato5
+                   , ~SYM[3]  // sbdato4
+                   , ~SYM[4]  // sbdato3
+                   , ~SYM[5]  // sbdato2
+                   , ~SYM[6]  // sbdato1
+                   , ~SYM[7]  // sbdato0
+                   , ~SYM[8]  // sbacko
+                   , ~SYM[9]  // i2cirq
+                   , ~SYM[10] // i2cwkup
+                   , ~SYM[11] // sclo
+                   , ~SYM[12] // scloe
+                   , ~SYM[13] // sdao
+                   , ~SYM[14] // sdaoe
+                   };
+  //SB_I2C end"
+       }
+     }
+  ]
+  |]) #-}
+
+{-# NOINLINE i2cPrim #-}
+i2cPrim
+  :: String          -- ARG[0]  initAddr
+  -> String          -- ARG[1]  busAddr
+  -> Clock dom       -- ARG[2]  sbclki
+  -> Signal dom Bool -- ARG[3]  sbrwi
+  -> Signal dom Bool -- ARG[4]  sbstbi
+  -> Signal dom Bit  -- ARG[5]  sbadri7
+  -> Signal dom Bit  -- ARG[6]  sbadri6
+  -> Signal dom Bit  -- ARG[7]  sbadri5
+  -> Signal dom Bit  -- ARG[8]  sbadri4
+  -> Signal dom Bit  -- ARG[9]  sbadri3
+  -> Signal dom Bit  -- ARG[10] sbadri2
+  -> Signal dom Bit  -- ARG[11] sbadri1
+  -> Signal dom Bit  -- ARG[12] sbadri0
+  -> Signal dom Bit  -- ARG[13] sbdati7
+  -> Signal dom Bit  -- ARG[14] sbdati6
+  -> Signal dom Bit  -- ARG[15] sbdati5
+  -> Signal dom Bit  -- ARG[16] sbdati4
+  -> Signal dom Bit  -- ARG[17] sbdati3
+  -> Signal dom Bit  -- ARG[18] sbdati2
+  -> Signal dom Bit  -- ARG[19] sbdati1
+  -> Signal dom Bit  -- ARG[20] sbdati0
+  -> Signal dom Bit  -- ARG[21] scli
+  -> Signal dom Bit  -- ARG[22] sdai
+  -> Unbundled dom
+       ( BitVector 8 -- sbdato
+       , Bool        -- sbacko
+       , Bit         -- i2cirq
+       , Bit         -- i2cwkup
+       , Bit         -- sclo
+       , Bit         -- scloe
+       , Bit         -- sdao
+       , Bit         -- sdaoe
+       )
+i2cPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_
+  = unbundle $ pure ( 0
+                    , False
+                    , 0
+                    , 0
+                    , 0
+                    , 0
+                    , 0
+                    , 0
+                    )
+
+-- | I2C primitive wrapper
+i2c
+  :: HiddenClock dom
+  => String                   -- ^ initAddr
+  -> String                   -- ^ busAddr
+  -> Signal dom Bool          -- ^ sbrwi
+  -> Signal dom Bool          -- ^ sbstbi
+  -> Signal dom (BitVector 8) -- ^ sbadri
+  -> Signal dom (BitVector 8) -- ^ sbdati
+  -> Signal dom Bit           -- ^ scli
+  -> Signal dom Bit           -- ^ sdai
+  -> Unbundled dom
+       ( BitVector 8 -- sbdato
+       , Bool        -- sbacko
+       , Bit         -- i2cirq
+       , Bit         -- i2cwkup
+       , Bit         -- sclo
+       , Bit         -- scloe
+       , Bit         -- sdao
+       , Bit         -- sdaoe
+       ) -- ^ (sbdato, sbacko, i2cirq, i2cwkup, sclo, scloe, sdao, sdaoe)
+i2c initAddr busAddr sbrwi sbstbi sbadri sbdati scli sdai =
+  i2cPrim initAddr
+          busAddr
+          hasClock
+          sbrwi
+          sbstbi
+          (bitAt 7 sbadri)
+          (bitAt 6 sbadri)
+          (bitAt 5 sbadri)
+          (bitAt 4 sbadri)
+          (bitAt 3 sbadri)
+          (bitAt 2 sbadri)
+          (bitAt 1 sbadri)
+          (bitAt 0 sbadri)
+          (bitAt 7 sbdati)
+          (bitAt 6 sbdati)
+          (bitAt 5 sbdati)
+          (bitAt 4 sbdati)
+          (bitAt 3 sbdati)
+          (bitAt 2 sbdati)
+          (bitAt 1 sbdati)
+          (bitAt 0 sbdati)
+          scli
+          sdai
+
+bitAt :: KnownNat n => Index n -> Signal dom (BitVector n) -> Signal dom Bit
+bitAt n = fmap (!n)
diff --git a/src/Ice40/IO.hs b/src/Ice40/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/IO.hs
@@ -0,0 +1,173 @@
+{-|
+Module      : Ice40.IO
+Description : Ice40 IO hard IP primitives
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+IO hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.IO where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN ioPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.IO.ioPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "ioPrim
+    :: BitVector 6         -- ARG[0]  pinType
+    -> Bit                 -- ARG[1]  pullup
+    -> Bit                 -- ARG[2]  negTrigger
+    -> String              -- ARG[3]  ioStandard
+    -> Signal domIn Bit    -- ARG[4]  latchInputValue
+    -> Signal domEn Bit    -- ARG[5]  clockEnable
+    -> Clock domIn         -- ARG[6]  inputClk
+    -> Clock domOut        -- ARG[7]  outputClk
+    -> Signal domOut Bit   -- ARG[8]  outputEnable
+    -> Signal domOut Bit   -- ARG[9]  dOut0
+    -> Signal domOut Bit   -- ARG[10] dOut1
+    -> ( Signal domPin Bit -- packagePin
+       , Signal domIn Bit  -- dIn0
+       , Signal domIn Bit  -- dIn1
+       )"
+       , "template" :
+  "//SB_IO begin
+  wire ~GENSYM[package_pin][0];
+  wire ~GENSYM[d_in_0][1];
+  wire ~GENSYM[d_in_1][2];
+
+  SB_IO #(
+    .PIN_TYPE         ( ~ARG[0]  ),
+    .PULLUP           ( ~ARG[1]  ),
+    .NEG_TRIGGER      ( ~ARG[2]  ),
+    .IO_STANDARD      ( ~ARG[3]  )
+  ) ~GENSYM[sb_io_inst][3] (
+    .PACKAGE_PIN      ( ~SYM[0]  ),
+    .LATCH_INPUT_VALUE( ~ARG[4]  ),
+    .CLOCK_ENABLE     ( ~ARG[5]  ),
+    .INPUT_CLK        ( ~ARG[6]  ),
+    .OUTPUT_CLK       ( ~ARG[7]  ),
+    .OUTPUT_ENABLE    ( ~ARG[8]  ),
+    .D_OUT_0          ( ~ARG[9]  ),
+    .D_OUT_1          ( ~ARG[10] ),
+    .D_IN_0           ( ~SYM[1]  ),
+    .D_IN_1           ( ~SYM[2]  )
+  );
+
+  assign ~RESULT = { ~SYM[0], ~SYM[1], ~SYM[2] };
+  //SB_IO end"
+       }
+     }
+  ]
+  |]) #-}
+
+-- | IO primitive, see io for wrapper
+{-# NOINLINE ioPrim #-}
+ioPrim
+  :: BitVector 6         -- ^ pinType
+  -> Bit                 -- ^ pullup
+  -> Bit                 -- ^ negTrigger
+  -> String              -- ^ ioStandard
+  -> Signal domIn Bit    -- ^ latchInputValue
+  -> Signal domEn Bit    -- ^ clockEnable
+  -> Clock domIn         -- ^ inputClk
+  -> Clock domOut        -- ^ outputClk
+  -> Signal domOut Bit   -- ^ outputEnable
+  -> Signal domOut Bit   -- ^ dOut0
+  -> Signal domOut Bit   -- ^ dOut1
+  -> ( Signal domPin Bit -- packagePin
+     , Signal domIn Bit  -- dIn0
+     , Signal domIn Bit  -- dIn1
+     ) -- ^ (packagePin, dIn0, dIn1)
+ioPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ = (pure 0, pure 0, pure 0)
+
+-- | Input pin configuration parameter
+data PinInput = PinInput -- ^ Simple Input pin dIn0
+              | PinInputLatch -- ^ Disables Internal data changes on the physical input pin by latching the value
+              | PinInputRegistered -- ^ Input data is registered in input cell
+              | PinInputRegisteredLatch -- ^ Disables internal data changes on the physical input pin by latching the value on the input register
+              | PinInputDDR -- ^ Input DDR data is clocked out on rising and falling clock edges. Use the dIn0 and dIn1 pins for DDR operation
+  deriving stock (Generic, Show, Read, Eq)
+  deriving anyclass NFDataX
+
+fromPinInput :: PinInput -> BitVector 2
+fromPinInput = \case
+  PinInput                -> 0b01
+  PinInputLatch           -> 0b11
+  PinInputRegistered      -> 0b00
+  PinInputRegisteredLatch -> 0b10
+  PinInputDDR             -> 0b00
+
+-- | Output pin configuration parameter
+data PinOutput = PinNoOutput -- ^Disables the output function
+               | PinOutput -- ^ Simple output pin (no enable)
+               | PinOutputTristate -- ^ The output pin may be tristated using the enable
+               | PinOutputEnableRegistered -- ^ The output pin may be tristated using a registered enable signal
+               | PinOutputRegistered -- ^ Output registered (no enable)
+               | PinOutputRegisteredEnable -- ^ Output registered with enable (the enable is not registered)
+               | PinOutputRegisteredEnableRegistered -- ^ Output registered and enable registered
+               | PinOutputDDR -- ^ Output DDR data is clocked out on rising and falling clock edges
+               | PinOutputDDREnable -- ^ Output data is clocked out on rising and falling clock edges
+               | PinOutputDDREnableRegistered -- ^ Output DDR data with registered enable signal
+               | PinOutputRegisteredInverted -- ^ Output registered signal is inverted
+               | PinOutputRegisteredEnableInverted -- ^ Output signal is registered and inverted (no enable function)
+               | PinOutputRegisteredEnableRegisteredInverted -- ^ Output signal is registered and inverted, the enable/tristate control is registered
+  deriving stock (Generic, Show, Read, Eq)
+  deriving anyclass NFDataX
+
+fromPinOutput :: PinOutput -> BitVector 4
+fromPinOutput = \case
+  PinNoOutput                                 -> 0b0000
+  PinOutput                                   -> 0b0110
+  PinOutputTristate                           -> 0b1010
+  PinOutputEnableRegistered                   -> 0b1110
+  PinOutputRegistered                         -> 0b0101
+  PinOutputRegisteredEnable                   -> 0b1001
+  PinOutputRegisteredEnableRegistered         -> 0b1101
+  PinOutputDDR                                -> 0b0100
+  PinOutputDDREnable                          -> 0b1000
+  PinOutputDDREnableRegistered                -> 0b1100
+  PinOutputRegisteredInverted                 -> 0b0111
+  PinOutputRegisteredEnableInverted           -> 0b1011
+  PinOutputRegisteredEnableRegisteredInverted -> 0b1111
+
+-- | Input-Output Standards
+data IOStandard = SBLVCMOS
+                | SBLVDSINPUT
+  deriving (Generic, Show, Read, Eq)
+
+fromIOStandard :: IOStandard -> String
+fromIOStandard = \case
+  SBLVCMOS    -> "SB_LVCMOS"
+  SBLVDSINPUT -> "SB_LVDS_INPUT"
+
+-- | IO primitive
+io
+  :: PinInput
+  -> PinOutput
+  -> Bit                 -- ^ pullUp
+  -> Bit                 -- ^ negTrigger
+  -> IOStandard
+  -> Signal domIn Bit    -- ^ latchInputValue
+  -> Signal domEn Bit    -- ^ clockEnable
+  -> Clock domIn         -- ^ inputClk
+  -> Clock domOut        -- ^ outputClk
+  -> Signal domOut Bit   -- ^ outputEnable
+  -> Signal domOut Bit   -- ^ dOut0
+  -> Signal domOut Bit   -- ^ dOut1
+  -> ( Signal domPin Bit -- packagePin
+     , Signal domIn Bit  -- dIn0
+     , Signal domIn Bit  -- dIn1
+     ) -- ^ (packagePin, dIn0, dIn1)
+io pinInput pinOutput pullUp negTrigger ioStandard
+  = ioPrim
+      (fromPinOutput pinOutput ++# fromPinInput pinInput)
+      pullUp
+      negTrigger
+      (fromIOStandard ioStandard)
+
diff --git a/src/Ice40/Led.hs b/src/Ice40/Led.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Led.hs
@@ -0,0 +1,140 @@
+{-|
+Module      : Ice40.Led
+Description : LED Ice40 hard IP primitive
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+LED hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Led ( led ) where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN ledPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Led.ledPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "ledPrim
+  :: Signal dom Bit       -- ARG[0]  leddcs
+  -> Clock dom            -- ARG[1]  leddclk
+  -> Signal dom Bit       -- ARG[2]  ledddat7
+  -> Signal dom Bit       -- ARG[3]  ledddat6
+  -> Signal dom Bit       -- ARG[4]  ledddat5
+  -> Signal dom Bit       -- ARG[5]  ledddat4
+  -> Signal dom Bit       -- ARG[6]  ledddat3
+  -> Signal dom Bit       -- ARG[7]  ledddat2
+  -> Signal dom Bit       -- ARG[8]  ledddat1
+  -> Signal dom Bit       -- ARG[9]  ledddat0
+  -> Signal dom Bit       -- ARG[10] leddaddr3
+  -> Signal dom Bit       -- ARG[11] leddaddr2
+  -> Signal dom Bit       -- ARG[12] leddaddr1
+  -> Signal dom Bit       -- ARG[13] leddaddr0
+  -> Signal dom Bool      -- ARG[14] leddden
+  -> Signal dom Bool      -- ARG[15] leddexe
+  -> Unbundled dom ( Bit  -- pwmOut0
+                   , Bit  -- pwmOut1
+                   , Bit  -- pwmOut2
+                   , Bool -- leddon
+                   )"
+      , "template" :
+  "//SB_LEDDA_IP begin
+  wire ~GENSYM[pwmOut0][0];
+  wire ~GENSYM[pwmOut1][1];
+  wire ~GENSYM[pwmOut2][2];
+  wire ~GENSYM[leddon][3];
+
+  SB_LEDDA_IP SB_LEDDA_IP_INST (
+    .LEDDCS    ( ~ARG[0]  ),
+    .LEDDCLK   ( ~ARG[1]  ),
+    .LEDDDAT7  ( ~ARG[2]  ),
+    .LEDDDAT6  ( ~ARG[3]  ),
+    .LEDDDAT5  ( ~ARG[4]  ),
+    .LEDDDAT4  ( ~ARG[5]  ),
+    .LEDDDAT3  ( ~ARG[6]  ),
+    .LEDDDAT2  ( ~ARG[7]  ),
+    .LEDDDAT1  ( ~ARG[8]  ),
+    .LEDDDAT0  ( ~ARG[9]  ),
+    .LEDDADDR3 ( ~ARG[10] ),
+    .LEDDADDR2 ( ~ARG[11] ),
+    .LEDDADDR1 ( ~ARG[12] ),
+    .LEDDADDR0 ( ~ARG[13] ),
+    .LEDDDEN   ( ~ARG[14] ),
+    .LEDDEXE   ( ~ARG[15] ),
+
+    .PWMOUT0   ( ~SYM[0]  ),
+    .PWMOUT1   ( ~SYM[1]  ),
+    .PWMOUT2   ( ~SYM[2]  ),
+    .LEDDON    ( ~SYM[3]  )
+  );
+
+  assign ~RESULT = {~SYM[0], ~SYM[1], ~SYM[2], ~SYM[3]};
+  //SB_LEDDA_IP end"
+      }
+    }
+  ]
+  |]) #-}
+
+{-# NOINLINE ledPrim #-}
+ledPrim
+  :: Signal dom Bit       -- ARG[0]  leddcs - CS to write LEDD IP registers
+  -> Clock dom            -- ARG[1]  leddclk - Clock to write LEDD IP registers
+  -> Signal dom Bit       -- ARG[2]  ledddat7 - bit 7 data to write into the LEDD IP registers
+  -> Signal dom Bit       -- ARG[3]  ledddat6 - bit 6 data to write into the LEDD IP registers
+  -> Signal dom Bit       -- ARG[4]  ledddat5 - bit 5 data to write into the LEDD IP registers
+  -> Signal dom Bit       -- ARG[5]  ledddat4 - bit 4 data to write into the LEDD IP registers
+  -> Signal dom Bit       -- ARG[6]  ledddat3 - bit 3 data to write into the LEDD IP registers
+  -> Signal dom Bit       -- ARG[7]  ledddat2
+  -> Signal dom Bit       -- ARG[8]  ledddat1
+  -> Signal dom Bit       -- ARG[9]  ledddat0
+  -> Signal dom Bit       -- ARG[10] leddaddr3
+  -> Signal dom Bit       -- ARG[11] leddaddr2
+  -> Signal dom Bit       -- ARG[12] leddaddr1
+  -> Signal dom Bit       -- ARG[13] leddaddr0
+  -> Signal dom Bool      -- ARG[14] leddden
+  -> Signal dom Bool      -- ARG[15] leddexe
+  -> Unbundled dom ( Bit  -- pwmOut0
+                   , Bit  -- pwmOut1
+                   , Bit  -- pwmOut2
+                   , Bool -- leddon
+                   )
+ledPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ = (pure 0, pure 0, pure 0, pure False)
+
+-- | SB_LEDDA_IP primitive generates the RGB PWM outputs for the RGB LED drivers. The IP contains registers that are programmed in byte the SCI bus interface signals.
+led
+  :: HiddenClock dom
+  => Signal dom Bit           -- ^ cs - CS to write LEDD IP registers
+  -> Signal dom (BitVector 8) -- ^ dat - data to write into the LEDD IP registers
+  -> Signal dom (BitVector 4) -- ^ addr - LEDD IP register address
+  -> Signal dom Bool          -- ^ en - data enable input to indicate data and address are stable
+  -> Signal dom Bool          -- ^ exe - enable to IP to run the blinking sequence. When it is LOW, the sequence stops at the nearest OFF state
+  -> Unbundled dom ( Bit 
+                   , Bit 
+                   , Bit 
+                   , Bool
+                   ) -- ^ (pwmOut0, pwmOut1, pwmOut2, LED on indicator)
+led cs dat addr en exe = (pwmOut0, pwmOut1, pwmOut2, on)
+  where
+    (pwmOut0, pwmOut1, pwmOut2, on) = ledPrim cs
+                                              hasClock
+                                              (bitAt 7 dat)
+                                              (bitAt 6 dat)
+                                              (bitAt 5 dat)
+                                              (bitAt 4 dat)
+                                              (bitAt 3 dat)
+                                              (bitAt 2 dat)
+                                              (bitAt 1 dat)
+                                              (bitAt 0 dat)
+                                              (bitAt 3 addr)
+                                              (bitAt 2 addr)
+                                              (bitAt 1 addr)
+                                              (bitAt 0 addr)
+                                              en
+                                              exe
+
+bitAt :: KnownNat n => Index n -> Signal dom (BitVector n) -> Signal dom Bit
+bitAt n = fmap (!n)
diff --git a/src/Ice40/Mac.hs b/src/Ice40/Mac.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Mac.hs
@@ -0,0 +1,187 @@
+{-|
+Module      : Ice40.Mac
+Description : Ice40 Multiply-Accumulate (DSP) hard IP primitive
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+Mac hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Mac ( macPrim ) where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN macPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Mac.macPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "macPrim
+  :: Bit                         -- ARG[0]  negTrigger
+  -> Bit                         -- ARG[1]  aReg
+  -> Bit                         -- ARG[2]  bReg
+  -> Bit                         -- ARG[3]  cReg
+  -> Bit                         -- ARG[4]  dReg
+  -> Bit                         -- ARG[5]  top8x8MultReg
+  -> Bit                         -- ARG[6]  bot8x8MultReg
+  -> Bit                         -- ARG[7]  pipeline16x16MultReg1
+  -> Bit                         -- ARG[8]  pipeline16x16MultReg2
+  -> BitVector 2                 -- ARG[9]  topOutputSelect
+  -> BitVector 2                 -- ARG[10] topAddSubLowerInput
+  -> Bit                         -- ARG[11] topAddSubUpperInput
+  -> BitVector 2                 -- ARG[12] topAddSubCarrySelect
+  -> BitVector 2                 -- ARG[13] botOutputSelect
+  -> BitVector 2                 -- ARG[14] botAddSubLowerInput
+  -> Bit                         -- ARG[15] botAddSubUpperInput
+  -> BitVector 2                 -- ARG[16] botAddSubCarrySelect
+  -> Bit                         -- ARG[17] mode8x8
+  -> Bit                         -- ARG[18] aSigned
+  -> Bit                         -- ARG[19] bSigned
+  -> Clock dom                   -- ARG[20] clk
+  -> Signal dom Bit              -- ARG[21] ce
+  -> Signal dom (BitVector 16)   -- ARG[22] c
+  -> Signal dom (BitVector 16)   -- ARG[23] a
+  -> Signal dom (BitVector 16)   -- ARG[24] b
+  -> Signal dom (BitVector 16)   -- ARG[25] d
+  -> Signal dom Bit              -- ARG[26] irsttop
+  -> Signal dom Bit              -- ARG[27] irstbot
+  -> Signal dom Bit              -- ARG[28] orsttop
+  -> Signal dom Bit              -- ARG[29] orstbot
+  -> Signal dom Bit              -- ARG[30] ahold
+  -> Signal dom Bit              -- ARG[31] bhold
+  -> Signal dom Bit              -- ARG[32] chold
+  -> Signal dom Bit              -- ARG[33] dhold
+  -> Signal dom Bit              -- ARG[34] oholdtop
+  -> Signal dom Bit              -- ARG[35] oholdbot
+  -> Signal dom Bit              -- ARG[36] addsubtop
+  -> Signal dom Bit              -- ARG[37] addsubbot
+  -> Signal dom Bit              -- ARG[38] oloadtop
+  -> Signal dom Bit              -- ARG[39] oloadbot
+  -> Signal dom Bit              -- ARG[40] accumci
+  -> Signal dom Bit              -- ARG[41] signextin
+  -> Signal dom Bit              -- ARG[42] ci
+  -> ( Signal dom (BitVector 32) -- o[31:0]
+     , Signal dom Bit            -- co
+     , Signal dom Bit            -- accumco
+     , Signal dom Bit            -- signextout
+     )"
+       , "template" :
+  "//SB_MAC16 begin
+  wire [31:0]  ~GENSYM[o][0];
+  wire         ~GENSYM[co][1];
+  wire         ~GENSYM[accumco][2];
+  wire         ~GENSYM[signextout][3];
+
+  SB_MAC16 #(
+    .NEG_TRIGGER(~ARG[0]),
+    .C_REG(~ARG[3]),
+    .A_REG(~ARG[1]),
+    .B_REG(~ARG[2]),
+    .D_REG(~ARG[4]),
+    .TOP_8x8_MULT_REG(~ARG[5]),
+    .BOT_8x8_MULT_REG(~ARG[6]),
+    .PIPELINE_16x16_MULT_REG1(~ARG[7]),
+    .PIPELINE_16x16_MULT_REG2(~ARG[8]),
+    .TOPOUTPUT_SELECT(~ARG[9]),
+    .TOPADDSUB_LOWERINPUT(~ARG[10]),
+    .TOPADDSUB_UPPERINPUT(~ARG[11]),
+    .TOPADDSUB_CARRYSELECT(~ARG[12]),
+    .BOTOUTPUT_SELECT(~ARG[13]),
+    .BOTADDSUB_LOWERINPUT(~ARG[14]),
+    .BOTADDSUB_UPPERINPUT(~ARG[15]),
+    .BOTADDSUB_CARRYSELECT(~ARG[16]),
+    .MODE_8x8(~ARG[17]),
+    .A_SIGNED(~ARG[18]),
+    .B_SIGNED(~ARG[19])
+  ) ~GENSYM[sb_mac16_inst][4] (
+    .CLK(~ARG[20]),
+    .CE(~ARG[21]),
+    .C(~ARG[22]),
+    .A(~ARG[23]),
+    .B(~ARG[24]),
+    .D(~ARG[25]),
+    .AHOLD(~ARG[30]),
+    .BHOLD(~ARG[31]),
+    .CHOLD(~ARG[32]),
+    .DHOLD(~ARG[33]),
+    .IRSTTOP(~ARG[26]),
+    .IRSTBOT(~ARG[27]),
+    .ORSTTOP(~ARG[28]),
+    .ORSTBOT(~ARG[29]),
+    .OLOADTOP(~ARG[38]),
+    .OLOADBOT(~ARG[39]),
+    .ADDSUBTOP(~ARG[36]),
+    .ADDSUBBOT(~ARG[37]),
+    .OHOLDTOP(~ARG[34]),
+    .OHOLDBOT(~ARG[35]),
+    .CI(~ARG[42]),
+    .ACCUMCI(~ARG[40]),
+    .SIGNEXTIN(~ARG[41]),
+    .O(~SYM[0]),
+    .CO(~SYM[1]),
+    .ACCUMCO(~SYM[2]),
+    .SIGNEXTOUT(~SYM[3])
+  );
+
+  assign ~RESULT = { ~SYM[0], ~SYM[1], ~SYM[2], ~SYM[3] };
+  // SB_MAC16 end"
+       }
+     }
+  ]
+  |]) #-}
+
+-- | Multiply-Accumulate primitive
+{-# NOINLINE macPrim #-}
+macPrim
+  :: Bit                         -- ^ negTrigger
+  -> Bit                         -- ^ aReg
+  -> Bit                         -- ^ bReg
+  -> Bit                         -- ^ cReg
+  -> Bit                         -- ^ dReg
+  -> Bit                         -- ^ top8x8MultReg
+  -> Bit                         -- ^ bot8x8MultReg
+  -> Bit                         -- ^ pipeline16x16MultReg1
+  -> Bit                         -- ^ pipeline16x16MultReg2
+  -> BitVector 2                 -- ^ topOutputSelect
+  -> BitVector 2                 -- ^ topAddSubLowerInput
+  -> Bit                         -- ^ topAddSubUpperInput
+  -> BitVector 2                 -- ^ topAddSubCarrySelect
+  -> BitVector 2                 -- ^ botOutputSelect
+  -> BitVector 2                 -- ^ botAddSubLowerInput
+  -> Bit                         -- ^ botAddSubUpperInput
+  -> BitVector 2                 -- ^ botAddSubCarrySelect
+  -> Bit                         -- ^ mode8x8
+  -> Bit                         -- ^ aSigned
+  -> Bit                         -- ^ bSigned
+  -> Clock dom                   -- ^ clk
+  -> Signal dom Bit              -- ^ ce
+  -> Signal dom (BitVector 16)   -- ^ c
+  -> Signal dom (BitVector 16)   -- ^ a
+  -> Signal dom (BitVector 16)   -- ^ b
+  -> Signal dom (BitVector 16)   -- ^ d
+  -> Signal dom Bit              -- ^ irsttop
+  -> Signal dom Bit              -- ^ irstbot
+  -> Signal dom Bit              -- ^ orsttop
+  -> Signal dom Bit              -- ^ orstbot
+  -> Signal dom Bit              -- ^ ahold
+  -> Signal dom Bit              -- ^ bhold
+  -> Signal dom Bit              -- ^ chold
+  -> Signal dom Bit              -- ^ dhold
+  -> Signal dom Bit              -- ^ oholdtop
+  -> Signal dom Bit              -- ^ oholdbot
+  -> Signal dom Bit              -- ^ addsubtop
+  -> Signal dom Bit              -- ^ addsubbot
+  -> Signal dom Bit              -- ^ oloadtop
+  -> Signal dom Bit              -- ^ oloadbot
+  -> Signal dom Bit              -- ^ accumci
+  -> Signal dom Bit              -- ^ signextin
+  -> Signal dom Bit              -- ^ ci
+  -> ( Signal dom (BitVector 32) -- o[31:0]
+     , Signal dom Bit            -- co
+     , Signal dom Bit            -- accumco
+     , Signal dom Bit            -- signextout
+     )                           -- ^ (o[31:0], co, accumco, signextout)
+macPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ = (0, 0, 0, 0)
diff --git a/src/Ice40/Osc.hs b/src/Ice40/Osc.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Osc.hs
@@ -0,0 +1,130 @@
+{-|
+Module      : Ice40.Osc
+Description : Ice40 oscillator hard IP primitives
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+LFOSC and HFOSC hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Osc
+  ( lf10kHz
+  , hf48Mhz
+  , hf24Mhz
+  , hf12Mhz
+  , hf6Mhz
+  ) where
+
+import Clash.Prelude
+import Clash.Signal.Internal
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+import Ice40.Clock
+
+------------------------------
+-- Low Frequency Oscillator --
+------------------------------
+
+{-# ANN lf10kHz (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Osc.lf10kHz"
+       , "kind" : "Declaration"
+       , "type" :
+  "lf10kHz
+  :: Signal dom Bool -- ARG[0] clkLfPu
+  -> Signal dom Bool -- ARG[1] clkLfEn
+  -> Clock Lattice10kHz"
+       , "template" :
+  "//SB_LFOSC begin
+  SB_LFOSC ~GENSYM[sb_lfosc_inst][0] (   
+  .CLKLFEN (~ARG[0]),
+  .CLKLFPU (~ARG[1]), 
+  .CLKLF   (~RESULT)
+  );
+  //SB_LFOSC end"
+      }
+    }
+  ]
+  |]) #-}
+
+-- | Low frequency oscillator 10 kHz
+{-# NOINLINE lf10kHz #-}
+lf10kHz
+  :: Signal dom Bool -- ^ CLKLFPU - Power up the LFOSC circuit. After power up, oscillator output will be stable after 100us. Active High.
+  -> Signal dom Bool -- ^ CLKLFEN - Enable the clock output. Enable should be low for the 100us power up period. Active High.
+  -> Clock Lattice10kHz -- ^ LF Oscillator output
+lf10kHz !_ !_ = Clock SSymbol
+
+-------------------------------
+-- High Frequency Oscillator --
+-------------------------------
+-- | SB_HFOSC primitive generates 48MHz nominal clock frequency within +/-10% variation, with user programmable divider value of 1, 2, 4, and 8. the HFOSC can drive either the global clock network or fabric routes directly based on the clock network selection.
+
+{-# ANN hfPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Osc.hfPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "hfPrim
+  :: KnownDomain dom            -- ARG[0]
+  => KnownDomain dom'           -- ARG[1]
+  => String                     -- ARG[2]  clkhfdiv
+  -> Signal dom Bool            -- ARG[3]  clkhfen
+  -> Signal dom Bool            -- ARG[4]  clkhfpu
+  -> Clock dom'                 -- clkhf"
+       , "template" :
+  "//SB_HFOSC begin
+  SB_HFOSC #( .CLKHF_DIV(~ARG[2]) ) sb_hfosc_inst (
+    .CLKHFEN (~ARG[3]),
+    .CLKHFPU (~ARG[4]),
+    .CLKHF   (~RESULT)
+  );                                                                                          
+  //SB_HFOSC end"
+      }
+    }
+  ]
+  |]) #-}
+
+{-# NOINLINE hfPrim #-}
+hfPrim
+  :: KnownDomain dom            -- ARG[0]
+  => KnownDomain dom'           -- ARG[1]
+  => String                     -- ARG[2]  clkhfdiv
+  -> Signal dom Bool            -- ARG[3]  clkhfen
+  -> Signal dom Bool            -- ARG[4]  clkhfpu
+  -> Clock dom'                 -- clkhf"
+hfPrim !_ !_ !_ = Clock SSymbol
+
+-- | High frequency oscillator 48 Mhz
+hf48Mhz
+  :: KnownDomain dom
+  => Signal dom Bool -- ^ CLKHFEN Enable the clock output. Enable should be low for the 100us power up period. Active High.
+  -> Signal dom Bool -- ^ CLKHFPU Power up the HFOSC circuit. After power up, oscillator output will be stable after 100us. Active High.
+  -> Clock Lattice48Mhz -- ^ HF Oscillator output
+hf48Mhz = hfPrim "0b00"
+
+-- | High frequency oscillator 24 Mhz
+hf24Mhz
+  :: KnownDomain dom
+  => Signal dom Bool -- ^ CLKHFEN Enable the clock output. Enable should be low for the 100us power up period. Active High.
+  -> Signal dom Bool -- ^ CLKHFPU Power up the HFOSC circuit. After power up, oscillator output will be stable after 100us. Active High.
+  -> Clock Lattice24Mhz -- ^ HF Oscillator output
+hf24Mhz = hfPrim "0b01"
+
+-- | High frequency oscillator 12 Mhz
+hf12Mhz
+  :: KnownDomain dom
+  => Signal dom Bool -- ^ CLKHFEN Enable the clock output. Enable should be low for the 100us power up period. Active High.
+  -> Signal dom Bool -- ^ CLKHFPU Power up the HFOSC circuit. After power up, oscillator output will be stable after 100us. Active High.
+  -> Clock Lattice12Mhz -- ^ HF Oscillator output
+hf12Mhz = hfPrim "0b10"
+
+-- | High frequency oscillator 6 Mhz
+hf6Mhz
+  :: KnownDomain dom
+  => Signal dom Bool -- ^ CLKHFEN Enable the clock output. Enable should be low for the 100us power up period. Active High.
+  -> Signal dom Bool -- ^ CLKHFPU Power up the HFOSC circuit. After power up, oscillator output will be stable after 100us. Active High.
+  -> Clock Lattice6Mhz -- ^ HF Oscillator output
+hf6Mhz = hfPrim "0b11"
+  
diff --git a/src/Ice40/Rgb.hs b/src/Ice40/Rgb.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Rgb.hs
@@ -0,0 +1,95 @@
+{-|
+Module : Ice40.Rgb
+Description : Ice40 RGB hard IP primitive
+Copyright : (c) David Cox, 2021
+License : BSD 3-Clause
+Maintainer : standardsemiconductor@gmail.com
+
+RGB hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Rgb where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN rgbPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [ { "BlackBox" :
+      { "name" : "Ice40.Rgb.rgbPrim"
+      , "kind" : "Declaration"
+      , "type" :
+  "rgbPrim
+  :: String           -- currentMode ARG[0]
+  -> String           -- rgb0Current ARG[1]
+  -> String           -- rgb1Current ARG[2]
+  -> String           -- rgb2Current ARG[3]
+  -> Signal dom Bit   -- curren      ARG[4]
+  -> Signal dom Bit   -- rgbleden    ARG[5]
+  -> Signal dom Bit   -- rgb0Pwm     ARG[6]
+  -> Signal dom Bit   -- rgb1Pwm     ARG[7]
+  -> Signal dom Bit   -- rgb2Pwm     ARG[8]
+  -> Signal dom ( Bit -- rgb0
+                , Bit -- rgb1
+                , Bit -- rgb2
+                )"
+      , "template" :
+  "//SB_RGBA_DRV begin
+  wire ~GENSYM[rgb0][0];
+  wire ~GENSYM[rgb1][1];
+  wire ~GENSYM[rgb2][2];
+
+  SB_RGBA_DRV #(
+     .CURRENT_MODE ( ~ARG[0] ),
+     .RGB0_CURRENT ( ~ARG[1] ),
+     .RGB1_CURRENT ( ~ARG[2] ),
+     .RGB2_CURRENT ( ~ARG[3] )
+  ) ~GENSYM[rgba_drv_inst][3] (
+     .CURREN       ( ~ARG[4] ),
+     .RGBLEDEN     ( ~ARG[5] ),
+     .RGB0PWM      ( ~ARG[6] ),
+     .RGB1PWM      ( ~ARG[7] ),
+     .RGB2PWM      ( ~ARG[8] ),
+     .RGB0         ( ~SYM[0] ),
+     .RGB1         ( ~SYM[1] ),
+     .RGB2         ( ~SYM[2] )
+  );
+ 
+  assign ~RESULT = {~SYM[0], ~SYM[1], ~SYM[2]};
+  //SB_RGBA_DRV end"
+      }
+    } 
+  ]
+  |]) #-}
+
+-- | SB_RGBA_DRV primitive is the RGB LED drive module which contains 3 
+--   dedicated open drain I/O pins for RGB LED outputs. Each of the RGB LED 
+--   output is bonded out together with an SB_IO_OD primitive to 
+--   the package pin. User can either use SB_RGB_DRV primitive or the 
+--   SB_IO_OD primitive to drive the package pin, but not both.
+-- 
+--   The primitive allows configuration of each of the 3 RGB LED 
+--   outputs individually. When the RGBx_CURRENT parameter of RGBx output is 
+--   set to "0b000000", then SB_IO_OD can be used to drive 
+--   the package pin. "0b000001" = 4mA for Full Mode; 2mA for 
+--   Half Mode. "0b000011" = 8mA for Full Mode; 4mA for Half Mode. 
+--   "0b000111" = 12mA for Full Mode; 6mA for Half Mode. 
+--   "0b001111" = 16mA for Full Mode; 8mA for Half Mode. 
+--   "0b011111" = 20mA for Full Mode; 10mA for Half Mode. 
+--   "0b111111" = 24mA for Full Mode; 12mA for Half Mode.
+{-# NOINLINE rgbPrim #-}
+rgbPrim
+  :: String           -- ^ currentMode - Parameter values: "0b0" = Full Current Mode (Default), "0b1" = Half Current Mode.
+  -> String           -- ^ rgb0Current
+  -> String           -- ^ rgb1Current
+  -> String           -- ^ rgb2Current
+  -> Signal dom Bit   -- ^ curren - enable the mixed signal control block to supply reference current to the IR drivers. When it is not enabled (CURREN=0), no current is supplied, and the IR drivers are powered down. Enabling the mixed signal control block takes 100us to reach a stable reference current value.
+  -> Signal dom Bit   -- ^ rgbleden - enable the SB_RGB_DRV primitive. Active High.
+  -> Signal dom Bit   -- ^ rgb0Pwm - input data to drive RGB0 LED pin. This input is usually driven from the SB_LEDD_IP.
+  -> Signal dom Bit   -- ^ rgb1Pwm - input data to drive RGB1 LED pin. This input is usually driven from the SB_LEDD_IP.
+  -> Signal dom Bit   -- ^ rgb2Pwm - input data to drive RGB2 LED pin. This input is usually driven from teh SB_LEDD_IP.
+  -> Signal dom ( Bit
+                , Bit
+                , Bit
+                ) -- ^ (RGB0 LED output, RGB1 LED output, RGB2 LED output)
+rgbPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ = pure (0, 0, 0)
diff --git a/src/Ice40/Spi.hs b/src/Ice40/Spi.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Spi.hs
@@ -0,0 +1,285 @@
+{-|
+Module      : Ice40.Spi
+Description : Ice40 SPI hard IP primitive
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+SPI hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Spi ( spi ) where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+                    
+-- | spi primitive wrapper
+spi
+  :: HiddenClock dom
+  => String                   -- ^ busAddr
+  -> Signal dom Bool          -- ^ sbrwi
+  -> Signal dom Bool          -- ^ sbstbi
+  -> Signal dom (BitVector 8) -- ^ sbadri
+  -> Signal dom (BitVector 8) -- ^ sbdati
+  -> Signal dom Bit           -- ^ bi
+  -> Signal dom Bit           -- ^ wi
+  -> Signal dom Bit           -- ^ wcki
+  -> Signal dom Bit           -- ^ wcsni
+  -> Unbundled dom 
+       ( BitVector 8 -- sbdato
+       , Bool        -- sbacko
+       , Bit         -- spiirq
+       , Bit         -- spiwkup
+       , Bit         -- wo
+       , Bit         -- woe
+       , Bit         -- bo
+       , Bit         -- boe
+       , Bit         -- wcko
+       , Bit         -- wckoe
+       , BitVector 4 -- bcsno
+       , BitVector 4 -- bcsnoe
+       ) -- ^ (sbdato, sbacko, spiirq, spiwkup, wo, woe, bo, boe, wcko, wckoe, bcsno, bcsnoe)
+spi busAddr sbrwi sbstbi sbadri sbdati bi wi wcki wcsni =
+  spiPrim busAddr
+          hasClock
+          sbrwi
+          sbstbi
+          (bitAt 7 sbadri)
+          (bitAt 6 sbadri)
+          (bitAt 5 sbadri)
+          (bitAt 4 sbadri)
+          (bitAt 3 sbadri)
+          (bitAt 2 sbadri)
+          (bitAt 1 sbadri)
+          (bitAt 0 sbadri)
+          (bitAt 7 sbdati)
+          (bitAt 6 sbdati)
+          (bitAt 5 sbdati)
+          (bitAt 4 sbdati)
+          (bitAt 3 sbdati)
+          (bitAt 2 sbdati)
+          (bitAt 1 sbdati)
+          (bitAt 0 sbdati)
+          bi
+          wi
+          wcki
+          wcsni
+
+bitAt :: KnownNat n => Index n -> Signal dom (BitVector n) -> Signal dom Bit
+bitAt n = fmap (!n)
+
+{-# ANN spiPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Spi.spiPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "spiPrim
+    :: String          -- ARG[0]  busAddr
+    -> Clock dom       -- ARG[1]  sbclki
+    -> Signal dom Bool -- ARG[2]  sbrwi
+    -> Signal dom Bool -- ARG[3]  sbstbi
+    -> Signal dom Bit  -- ARG[4]  sbadri7
+    -> Signal dom Bit  -- ARG[5]  sbadri6
+    -> Signal dom Bit  -- ARG[6]  sbadri5
+    -> Signal dom Bit  -- ARG[7]  sbadri4
+    -> Signal dom Bit  -- ARG[8]  sbadri3
+    -> Signal dom Bit  -- ARG[9]  sbadri2
+    -> Signal dom Bit  -- ARG[10] sbadri1
+    -> Signal dom Bit  -- ARG[11] sbadri0
+    -> Signal dom Bit  -- ARG[12] sbdati7
+    -> Signal dom Bit  -- ARG[13] sbdati6
+    -> Signal dom Bit  -- ARG[14] sbdati5
+    -> Signal dom Bit  -- ARG[15] sbdati4
+    -> Signal dom Bit  -- ARG[16] sbdati3
+    -> Signal dom Bit  -- ARG[17] sbdati2
+    -> Signal dom Bit  -- ARG[18] sbdati1
+    -> Signal dom Bit  -- ARG[19] sbdati0
+    -> Signal dom Bit  -- ARG[20] bi
+    -> Signal dom Bit  -- ARG[21] wi
+    -> Signal dom Bit  -- ARG[22] wcki
+    -> Signal dom Bit  -- ARG[23] wcsni
+    -> Unbundled dom 
+         ( BitVector 8 -- sbdato
+         , Bool        -- sbacko
+         , Bit         -- spiirq
+         , Bit         -- spiwkup
+         , Bit         -- wo
+         , Bit         -- woe
+         , Bit         -- bo
+         , Bit         -- boe
+         , Bit         -- wcko
+         , Bit         -- wckoe
+         , BitVector 4 -- bcsno
+         , BitVector 4 -- bcsnoe
+         )"
+        , "template" :
+  "//SB_SPI begin
+  wire ~GENSYM[sbdato7][0];
+  wire ~GENSYM[sbdato6][1];
+  wire ~GENSYM[sbdato5][2];
+  wire ~GENSYM[sbdato4][3];
+  wire ~GENSYM[sbdato3][4];
+  wire ~GENSYM[sbdato2][5];
+  wire ~GENSYM[sbdato1][6];
+  wire ~GENSYM[sbdato0][7];
+  wire ~GENSYM[sbacko][8];
+  wire ~GENSYM[irq][9];
+  wire ~GENSYM[wkup][10];
+  wire ~GENSYM[wo][11];
+  wire ~GENSYM[woe][12];
+  wire ~GENSYM[bo][13];
+  wire ~GENSYM[boe][14];
+  wire ~GENSYM[wcko][15];
+  wire ~GENSYM[wckoe][16];
+  wire ~GENSYM[bcsno3][17];
+  wire ~GENSYM[bcsno2][18];
+  wire ~GENSYM[bcsno1][19];
+  wire ~GENSYM[bcsno0][20];
+  wire ~GENSYM[bcsnoe3][21];
+  wire ~GENSYM[bcsnoe2][22];
+  wire ~GENSYM[bcsnoe1][23];
+  wire ~GENSYM[bcsnoe0][24];
+
+  SB_SPI #(
+    .BUS_ADDR74( ~ARG[0] )
+  ) ~GENSYM[sb_spi_inst][25] (
+    .SBCLKI  ( ~ARG[1]  ),
+    .SBRWI   ( ~ARG[2]  ),
+    .SBSTBI  ( ~ARG[3]  ),
+    .SBADRI7 ( ~ARG[4]  ),
+    .SBADRI6 ( ~ARG[5]  ),
+    .SBADRI5 ( ~ARG[6]  ),
+    .SBADRI4 ( ~ARG[7]  ),
+    .SBADRI3 ( ~ARG[8]  ),
+    .SBADRI2 ( ~ARG[9]  ),
+    .SBADRI1 ( ~ARG[10] ),
+    .SBADRI0 ( ~ARG[11] ),
+    .SBDATI7 ( ~ARG[12] ),
+    .SBDATI6 ( ~ARG[13] ),
+    .SBDATI5 ( ~ARG[14] ),
+    .SBDATI4 ( ~ARG[15] ),
+    .SBDATI3 ( ~ARG[16] ),
+    .SBDATI2 ( ~ARG[17] ),
+    .SBDATI1 ( ~ARG[18] ),
+    .SBDATI0 ( ~ARG[19] ),
+    .MI      ( ~ARG[20] ),
+    .SI      ( ~ARG[21] ),
+    .SCKI    ( ~ARG[22] ),
+    .SCSNI   ( ~ARG[23] ),
+
+    .SBDATO7 ( ~SYM[0]  ),
+    .SBDATO6 ( ~SYM[1]  ),
+    .SBDATO5 ( ~SYM[2]  ),
+    .SBDATO4 ( ~SYM[3]  ),
+    .SBDATO3 ( ~SYM[4]  ),
+    .SBDATO2 ( ~SYM[5]  ),
+    .SBDATO1 ( ~SYM[6] ),
+    .SBDATO0 ( ~SYM[7] ),
+    .SBACKO  ( ~SYM[8] ),
+    .SPIIRQ  ( ~SYM[9] ),
+    .SPIWKUP ( ~SYM[10] ),
+    .SO      ( ~SYM[11] ),
+    .SOE     ( ~SYM[12] ),
+    .MO      ( ~SYM[13] ),
+    .MOE     ( ~SYM[14] ),
+    .SCKO    ( ~SYM[15] ),
+    .SCKOE   ( ~SYM[16] ),
+    .MCSNO3  ( ~SYM[17] ),
+    .MCSNO2  ( ~SYM[18] ),
+    .MCSNO1  ( ~SYM[19] ),
+    .MCSNO0  ( ~SYM[20] ),
+    .MCSNOE3 ( ~SYM[21] ),
+    .MCSNOE2 ( ~SYM[22] ),
+    .MCSNOE1 ( ~SYM[23] ),
+    .MCSNOE0 ( ~SYM[24] )
+  );
+
+  assign ~RESULT = { ~SYM[0]  // sbdato7
+                   , ~SYM[1]  // sbdato6
+                   , ~SYM[2]  // sbdato5
+                   , ~SYM[3]  // sbdato4
+                   , ~SYM[4]  // sbdato3
+                   , ~SYM[5]  // sbdato2
+                   , ~SYM[6]  // sbdato1
+                   , ~SYM[7]  // sbdato0
+                   , ~SYM[8]  // sbacko
+                   , ~SYM[9]  // spiirq
+                   , ~SYM[10] // spiwkup
+                   , ~SYM[11] // wo
+                   , ~SYM[12] // woe
+                   , ~SYM[13] // bo
+                   , ~SYM[14] // boe
+                   , ~SYM[15] // wcko
+                   , ~SYM[16] // wckoe
+                   , ~SYM[17] // bcsno3
+                   , ~SYM[18] // bcsno2
+                   , ~SYM[19] // bcsno1
+                   , ~SYM[20] // bcsno0
+                   , ~SYM[21] // bcsnoe3
+                   , ~SYM[22] // bcsnoe2
+                   , ~SYM[23] // bcsnoe1
+                   , ~SYM[24] // bcsnoe0
+                   };
+  // SB_SPI end"
+       }
+     }
+  ]
+  |]) #-}
+
+{-# NOINLINE spiPrim #-}
+spiPrim
+  :: String          -- ARG[0]  busAddr
+  -> Clock dom       -- ARG[1]  sbclki
+  -> Signal dom Bool -- ARG[2]  sbrwi
+  -> Signal dom Bool -- ARG[3]  sbstbi
+  -> Signal dom Bit  -- ARG[4]  sbadri7
+  -> Signal dom Bit  -- ARG[5]  sbadri6
+  -> Signal dom Bit  -- ARG[6]  sbadri5
+  -> Signal dom Bit  -- ARG[7]  sbadri4
+  -> Signal dom Bit  -- ARG[8]  sbadri3
+  -> Signal dom Bit  -- ARG[9]  sbadri2
+  -> Signal dom Bit  -- ARG[10] sbadri1
+  -> Signal dom Bit  -- ARG[11] sbadri0
+  -> Signal dom Bit  -- ARG[12] sbdati7
+  -> Signal dom Bit  -- ARG[13] sbdati6
+  -> Signal dom Bit  -- ARG[14] sbdati5
+  -> Signal dom Bit  -- ARG[15] sbdati4
+  -> Signal dom Bit  -- ARG[16] sbdati3
+  -> Signal dom Bit  -- ARG[17] sbdati2
+  -> Signal dom Bit  -- ARG[18] sbdati1
+  -> Signal dom Bit  -- ARG[19] sbdati0
+  -> Signal dom Bit  -- ARG[20] bi
+  -> Signal dom Bit  -- ARG[21] wi
+  -> Signal dom Bit  -- ARG[22] wcki
+  -> Signal dom Bit  -- ARG[23] wcsni
+  -> Unbundled dom 
+       ( BitVector 8 -- sbdato
+       , Bool        -- sbacko
+       , Bit         -- spiirq
+       , Bit         -- spiwkup
+       , Bit         -- wo
+       , Bit         -- woe
+       , Bit         -- bo
+       , Bit         -- boe
+       , Bit         -- wcko
+       , Bit         -- wckoe
+       , BitVector 4 -- bcsno
+       , BitVector 4 -- bcsnoe
+       )
+spiPrim !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_ !_
+  = unbundle $ pure ( 0     -- sbdato
+                    , False -- sbacko
+                    , 0     -- spiirq
+                    , 0     -- spiwkup
+                    , 0     -- wo
+                    , 0     -- woe
+                    , 0     -- bo
+                    , 0     -- boe
+                    , 0     -- wcko
+                    , 0     -- wckoe
+                    , 0     -- bcsno
+                    , 0     -- bcsnoe
+                    )
+
+
diff --git a/src/Ice40/Spram.hs b/src/Ice40/Spram.hs
new file mode 100644
--- /dev/null
+++ b/src/Ice40/Spram.hs
@@ -0,0 +1,97 @@
+{-|
+Module      : Ice40.Spram
+Description : Ice40 single-port RAM hard IP primitive
+Copyright   : (c) David Cox, 2021
+License     : BSD 3-Clause
+Maintainer  : standardsemiconductor@gmail.com
+
+SPRAM hard IP primitive from Lattice Ice Technology Library https://github.com/standardsemiconductor/VELDT-info/blob/master/SBTICETechnologyLibrary201708.pdf
+-}
+module Ice40.Spram ( spramPrim ) where
+
+import Clash.Prelude
+import Clash.Annotations.Primitive
+import Data.String.Interpolate (i)
+import Data.String.Interpolate.Util (unindent)
+
+{-# ANN spramPrim (InlinePrimitive [Verilog] $ unindent [i|
+  [  { "BlackBox" :
+       { "name" : "Ice40.Spram.spramPrim"
+       , "kind" : "Declaration"
+       , "type" :
+  "spramPrim
+  :: KnownDomain dom            -- ARG[0] 
+  => Clock dom                  -- ARG[1]  clock
+  -> Signal dom (BitVector 14)  -- ARG[2]  address
+  -> Signal dom (BitVector 16)  -- ARG[3]  dataIn
+  -> Signal dom (BitVector 4)   -- ARG[4]  maskWrEn
+  -> Signal dom Bit             -- ARG[5]  wrEN
+  -> Signal dom Bit             -- ARG[6]  chipSelect
+  -> Signal dom Bit             -- ARG[7]  standBy
+  -> Signal dom Bit             -- ARG[8]  sleep
+  -> Signal dom Bit             -- ARG[9] powerOff
+  -> Signal dom (BitVector 16)  -- dataOut"
+       , "template" :
+  "//SB_SPRAM begin
+  SB_SPRAM256KA ~GENSYM[sb_spram_inst][0] (
+    .ADDRESS   (~ARG[2]),
+    .DATAIN    (~ARG[3]),
+    .MASKWREN  (~ARG[4]),
+    .WREN      (~ARG[5]),
+    .CHIPSELECT(~ARG[6]),
+    .CLOCK     (~ARG[1]),
+    .STANDBY   (~ARG[7]),
+    .SLEEP     (~ARG[8]),
+    .POWEROFF  (~ARG[9]),
+    .DATAOUT   (~RESULT)
+  );
+  //SB_SPRAM end"
+       }
+    }
+  ]
+  |]) #-}
+
+data Nyb = Nyb3 | Nyb2 | Nyb1 | Nyb0
+
+-- | Single port RAM primitive
+{-# NOINLINE spramPrim #-}
+spramPrim
+  :: KnownDomain dom            -- ARG[0] 
+  => Clock dom                  -- ^ clock
+  -> Signal dom (BitVector 14)  -- ^ address
+  -> Signal dom (BitVector 16)  -- ^ dataIn
+  -> Signal dom (BitVector 4)   -- ^ maskWrEn
+  -> Signal dom Bit             -- ^ wrEN
+  -> Signal dom Bit             -- ^ chipSelect
+  -> Signal dom Bit             -- ^ standBy
+  -> Signal dom Bit             -- ^ sleep
+  -> Signal dom Bit             -- ^ powerOff
+  -> Signal dom (BitVector 16)  -- ^ dataOut
+spramPrim clock address dataIn maskWrEn wrEn chipSelect !_ !_ !_
+  = concat4 <$> nyb3 <*> nyb2 <*> nyb1 <*> nyb0
+  where
+    addressU = unpack <$> address
+    concat4 a b c d = a ++# b ++# c ++# d
+    ramEn = toEnable $ bitToBool <$> chipSelect
+    nybRam = withClock clock $ withEnable ramEn (blockRamPow2 (repeat 0) addressU)
+    nyb3 = nybRam wrM3
+    nyb2 = nybRam wrM2
+    nyb1 = nybRam wrM1
+    nyb0 = nybRam wrM0
+    wrM3 = writeGuard Nyb3 <$> dataIn <*> addressU <*> wrEn <*> maskWrEn
+    wrM2 = writeGuard Nyb2 <$> dataIn <*> addressU <*> wrEn <*> maskWrEn
+    wrM1 = writeGuard Nyb1 <$> dataIn <*> addressU <*> wrEn <*> maskWrEn
+    wrM0 = writeGuard Nyb0 <$> dataIn <*> addressU <*> wrEn <*> maskWrEn
+    nybSlice = \case
+      Nyb3 -> slice d15 d12
+      Nyb2 -> slice d11 d8
+      Nyb1 -> slice d7  d4
+      Nyb0 -> slice d3  d0
+    nybMask = \case
+      Nyb3 -> (!(3 :: Index 4))
+      Nyb2 -> (!(2 :: Index 4))
+      Nyb1 -> (!(1 :: Index 4))
+      Nyb0 -> (!(0 :: Index 4))
+    writeGuard n dIn addr en mask
+      | bitToBool en && (not.bitToBool) (nybMask n mask) = Just (addr, nybSlice n dIn)
+      | otherwise = Nothing
