diff --git a/clash-shake.cabal b/clash-shake.cabal
--- a/clash-shake.cabal
+++ b/clash-shake.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.35.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           clash-shake
-version:        0.3.1.2
+version:        0.3.2
 synopsis:       Shake rules for building Clash programs
 description:    Shake rules for building Clash programs and synthesizing FPGA
                 configuration. Contains build rules for Xilinx ISE, Xilinx Vivado,
@@ -36,6 +36,7 @@
       Clash.Shake
       Clash.Shake.Xilinx
       Clash.Shake.Intel
+      Clash.Shake.F4PGA
       Clash.Shake.SymbiFlow
   other-modules:
       Paths_clash_shake
diff --git a/src/Clash/Shake.hs b/src/Clash/Shake.hs
--- a/src/Clash/Shake.hs
+++ b/src/Clash/Shake.hs
@@ -141,7 +141,7 @@
     }
 
 nestedPhony :: String -> String -> Action () -> Rules ()
-nestedPhony root name = phony (root </> name)
+nestedPhony target name = phony (target <> ":" <> name)
 
 useConfig :: FilePath -> Rules ()
 useConfig file = do
@@ -155,7 +155,7 @@
             return mempty
 
     forM_ (HM.lookup "TARGET" cfg) $ \target ->
-      want [target </> "bitfile"]
+      want [target <> ":" <> "bitfile"]
 
 binImage :: Maybe Int -> FilePath -> FilePath -> Action ()
 binImage size src out = do
diff --git a/src/Clash/Shake/F4PGA.hs b/src/Clash/Shake/F4PGA.hs
new file mode 100644
--- /dev/null
+++ b/src/Clash/Shake/F4PGA.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE OverloadedStrings, RecordWildCards, TemplateHaskell #-}
+module Clash.Shake.F4PGA
+    ( xilinx7
+    , openFPGALoader
+    ) where
+
+import Clash.Shake
+import qualified Clash.Shake.Xilinx as Xilinx
+
+import Development.Shake
+import Development.Shake.Command
+import Development.Shake.FilePath
+import Development.Shake.Config
+
+xilinx7 :: Xilinx.Board -> ClashKit -> FilePath -> FilePath -> String -> Rules SynthKit
+xilinx7 Xilinx.Board{ boardTarget = target@Xilinx.Target{..} } kit@ClashKit{..} outDir srcDir topName = do
+    let rootDir = joinPath . map (const "..") . splitPath $ outDir
+
+    let symbiflow' :: String -> [String] -> Action ()
+        symbiflow' tool args = cmd_ (EchoStdout False) (Cwd outDir) =<< toolchain "SYMBIFLOW" tool args
+        symbiflow :: String -> [String] -> Action ()
+        symbiflow tool args = cmd_ (EchoStdout False) =<< toolchain "SYMBIFLOW" tool args
+
+    let getFiles dir pats = map (srcDir </>) <$> getDirectoryFiles srcDir [ dir </> pat | pat <- pats ]
+        verilogSrcs = getFiles "src-hdl" ["*.v"]
+        xdcSrcs = getFiles "src-hdl" ["*.xdc" ]
+
+    outDir </> topName <.> "eblif" %> \out -> do
+        srcs <- manifestSrcs
+        verilogs <- verilogSrcs
+        xdcs <- xdcSrcs
+        need $ srcs <> verilogs <> xdcs
+
+        symbiflow' "symbiflow_synth" $
+          [ "-d", targetFamily
+          , "-p", Xilinx.targetPart target
+          , "-t", topName
+          ] ++
+          [ "-v " <> rootDir </> src | src <- srcs <> verilogs ] ++
+          [ "-x " <> rootDir </> xdc | xdc <- xdcs ]
+
+    outDir <//> "*.net" %> \out -> do
+        let eblif = out -<.> "eblif"
+        need [eblif]
+        symbiflow' "symbiflow_pack" $
+          [ "-d", targetDevice <> "_test"
+          , "-e", takeFileName eblif
+          ]
+
+    outDir <//> "*.place" %> \out -> do
+        let eblif = out -<.> "eblif"
+            net = out -<.> "net"
+        need [eblif, net]
+        symbiflow' "symbiflow_place" $
+          [ "-d", targetDevice <> "_test"
+          , "-P", Xilinx.targetPart target
+          , "-e", takeFileName eblif
+          , "-n", takeFileName net
+          ]
+
+    outDir <//> "*.route" %> \out -> do
+        let eblif = out -<.> "eblif"
+            place = out -<.> "place"
+        need [eblif, place]
+        symbiflow' "symbiflow_route" $
+          [ "-d", targetDevice <> "_test"
+          , "-e", takeFileName eblif
+          ]
+
+    outDir <//> "*.fasm" %> \out -> do
+        let eblif = out -<.> "eblif"
+            route = out -<.> "route"
+        need [eblif, route]
+        symbiflow' "symbiflow_write_fasm" $
+          [ "-d", targetDevice <> "_test"
+          , "-e", takeFileName eblif
+          ]
+
+    outDir <//> "*.bit" %> \out -> do
+        let fasm = out -<.> "fasm"
+        need [fasm]
+        symbiflow "symbiflow_write_bitstream" $
+          [ "-d", targetFamily
+          , "-p", Xilinx.targetPart target
+          , "-f", fasm
+          , "-b", out
+          ]
+
+    let bitfile = outDir </> topName <.> "bit"
+
+    return SynthKit
+        { bitfile = bitfile
+        , phonies =
+            [ "upload" |> openFPGALoader ["-c", "digilent"] bitfile
+            ]
+        }
+
+openFPGALoader :: [String] -> FilePath -> Action ()
+openFPGALoader args bitfile = do
+    need [bitfile]
+    cmd_ =<< toolchain "OPENFPGALOADER" "openFPGALoader" (args ++ [bitfile])
diff --git a/src/Clash/Shake/Intel.hs b/src/Clash/Shake/Intel.hs
--- a/src/Clash/Shake/Intel.hs
+++ b/src/Clash/Shake/Intel.hs
@@ -31,9 +31,11 @@
     , "targetDevice"  .= T.pack targetDevice
     ]
 
+-- | Target definition for Terasic DE0-Nano
 de0Nano :: Target
 de0Nano = Target "Cyclone IV E" "EP4CE22F17C6"
 
+-- | Target definition for Arrow DECA
 arrowDeca :: Target
 arrowDeca = Target "MAX 10" "10M50DAF484C6GES"
 
diff --git a/src/Clash/Shake/SymbiFlow.hs b/src/Clash/Shake/SymbiFlow.hs
--- a/src/Clash/Shake/SymbiFlow.hs
+++ b/src/Clash/Shake/SymbiFlow.hs
@@ -1,100 +1,5 @@
-{-# LANGUAGE OverloadedStrings, RecordWildCards, TemplateHaskell #-}
-module Clash.Shake.SymbiFlow
-    ( xilinx7
+module Clash.Shake.SymbiFlow {-# DEPRECATED "Use module `Clash.Shake.F4PGA' instead" #-}
+    ( module Clash.Shake.F4PGA
     ) where
 
-import Clash.Shake
-import qualified Clash.Shake.Xilinx as Xilinx
-
-import Development.Shake
-import Development.Shake.Command
-import Development.Shake.FilePath
-import Development.Shake.Config
-
-xilinx7 :: Xilinx.Board -> ClashKit -> FilePath -> FilePath -> String -> Rules SynthKit
-xilinx7 Xilinx.Board{ boardTarget = target@Xilinx.Target{..} } kit@ClashKit{..} outDir srcDir topName = do
-    let rootDir = joinPath . map (const "..") . splitPath $ outDir
-
-    let symbiflow' :: String -> [String] -> Action ()
-        symbiflow' tool args = cmd_ (EchoStdout False) (Cwd outDir) =<< toolchain "SYMBIFLOW" tool args
-        symbiflow :: String -> [String] -> Action ()
-        symbiflow tool args = cmd_ (EchoStdout False) =<< toolchain "SYMBIFLOW" tool args
-
-    let getFiles dir pats = map (srcDir </>) <$> getDirectoryFiles srcDir [ dir </> pat | pat <- pats ]
-        verilogSrcs = getFiles "src-hdl" ["*.v"]
-        xdcSrcs = getFiles "src-hdl" ["*.xdc" ]
-
-    outDir </> topName <.> "eblif" %> \out -> do
-        srcs <- manifestSrcs
-        verilogs <- verilogSrcs
-        xdcs <- xdcSrcs
-        need $ srcs <> verilogs <> xdcs
-
-        symbiflow' "symbiflow_synth" $
-          [ "-d", targetFamily
-          , "-p", Xilinx.targetPart target
-          , "-t", topName
-          ] ++
-          [ "-v " <> rootDir </> src | src <- srcs <> verilogs ] ++
-          [ "-x " <> rootDir </> xdc | xdc <- xdcs ]
-
-    outDir <//> "*.net" %> \out -> do
-        let eblif = out -<.> "eblif"
-        need [eblif]
-        symbiflow' "symbiflow_pack" $
-          [ "-d", targetDevice <> "_test"
-          , "-e", takeFileName eblif
-          ]
-
-    outDir <//> "*.place" %> \out -> do
-        let eblif = out -<.> "eblif"
-            net = out -<.> "net"
-        need [eblif, net]
-        symbiflow' "symbiflow_place" $
-          [ "-d", targetDevice <> "_test"
-          , "-P", Xilinx.targetPart target
-          , "-e", takeFileName eblif
-          , "-n", takeFileName net
-          ]
-
-    outDir <//> "*.route" %> \out -> do
-        let eblif = out -<.> "eblif"
-            place = out -<.> "place"
-        need [eblif, place]
-        symbiflow' "symbiflow_route" $
-          [ "-d", targetDevice <> "_test"
-          , "-e", takeFileName eblif
-          ]
-
-    outDir <//> "*.fasm" %> \out -> do
-        let eblif = out -<.> "eblif"
-            route = out -<.> "route"
-        need [eblif, route]
-        symbiflow' "symbiflow_write_fasm" $
-          [ "-d", targetDevice <> "_test"
-          , "-e", takeFileName eblif
-          ]
-
-    outDir <//> "*.bit" %> \out -> do
-        let fasm = out -<.> "fasm"
-        need [fasm]
-        symbiflow "symbiflow_write_bitstream" $
-          [ "-d", targetFamily
-          , "-p", Xilinx.targetPart target
-          , "-f", fasm
-          , "-b", out
-          ]
-
-    let bitfile = outDir </> topName <.> "bit"
-
-    return SynthKit
-        { bitfile = bitfile
-        , phonies =
-            [ "upload" |> openFPGALoader ["-c", "digilent"] bitfile
-            ]
-        }
-
-openFPGALoader :: [String] -> FilePath -> Action ()
-openFPGALoader args bitfile = do
-    need [bitfile]
-    cmd_ =<< toolchain "OPENFPGALOADER" "openFPGALoader" (args ++ [bitfile])
+import Clash.Shake.F4PGA
diff --git a/src/Clash/Shake/Xilinx.hs b/src/Clash/Shake/Xilinx.hs
--- a/src/Clash/Shake/Xilinx.hs
+++ b/src/Clash/Shake/Xilinx.hs
@@ -5,7 +5,7 @@
     , ise
     , vivado
 
-    , papilioPro, papilioOne, nexysA750T, basys3
+    , papilioPro, papilioOne, nexysA750T, basys3, pynqZ2
     ) where
 
 import Clash.Shake
@@ -43,31 +43,44 @@
     , "part"          .= T.pack (targetPart target)
     ]
 
+-- | Target defintion for Papilio Pro
 papilioPro :: Target
 papilioPro = Target "Spartan6" "xc6slx9" "tqg144" 2
 
+-- | Target definition for Papilio One
 papilioOne :: Target
 papilioOne = Target "Spartan3E" "xc3s500e" "vq100" 5
 
 data Board = Board
     { boardSpec :: String -- TODO: what is the structure of this?
+    , boardDeviceIndex :: Word
     , boardTarget :: Target
     }
 
+boardDeviceName :: Board -> String
+boardDeviceName Board{ boardTarget = Target{..}, .. } = targetDevice <> "_" <> show boardDeviceIndex
+
 boardMustache :: Board -> [Aeson.Pair]
-boardMustache Board{..} =
-    [ "board" .= T.pack boardSpec
+boardMustache board@Board{..} =
+    [ "board"      .= T.pack boardSpec
+    , "deviceName" .= T.pack (boardDeviceName board)
     ] <>
     targetMustache boardTarget
 
+-- | Board definition for Digilent Nexys A7-50T
 nexysA750T :: Board
-nexysA750T = Board "digilentinc.com:nexys-a7-50t:part0:1.0" $
+nexysA750T = Board "digilentinc.com:nexys-a7-50t:part0:1.0" 0 $
     Target "artix7" "xc7a50t" "csg324" 1
 
+-- | Board definition for Digilent Basys 3
 basys3 :: Board
-basys3 = Board "digilentinc.com:basys3:part0:1.2" $
+basys3 = Board "digilentinc.com:basys3:part0:1.2" 0 $
     Target "artix7" "xc7a35t" "cpg236" 1
 
+-- | Board definition for TUL PYNQ-Z2
+pynqZ2 :: Board
+pynqZ2 = Board "tul.com.tw:pynq-z2:part0:1.0" 1 $
+    Target "zynq7000" "xc7z020" "clg400" 1
 
 ise :: Target -> ClashKit -> FilePath -> FilePath -> String -> Rules SynthKit
 ise fpga kit@ClashKit{..} outDir srcDir topName = do
diff --git a/template/xilinx-vivado/upload.tcl.mustache b/template/xilinx-vivado/upload.tcl.mustache
--- a/template/xilinx-vivado/upload.tcl.mustache
+++ b/template/xilinx-vivado/upload.tcl.mustache
@@ -2,7 +2,7 @@
 connect_hw_server
 open_hw_target
 
-set devs [get_hw_devices {{targetDevice}}_0]
+set devs [get_hw_devices {{deviceName}}]
 set dev [lindex $devs 0]
 
 current_hw_device $devs
