Skip to content

Commit

Permalink
First clash-testbench prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinreact committed Apr 27, 2023
1 parent da1746a commit f34b0f8
Show file tree
Hide file tree
Showing 19 changed files with 1,548 additions and 0 deletions.
1 change: 1 addition & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ packages:
./clash-prelude
./clash-prelude-hedgehog
./clash-cores
./clash-testbench
./tests

write-ghc-environment-files: always
Expand Down
22 changes: 22 additions & 0 deletions clash-testbench/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2023 QBayLogic B.V.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.
55 changes: 55 additions & 0 deletions clash-testbench/clash-testbench.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
cabal-version: 2.2

name: clash-testbench
version: 0.1.0.0
synopsis: Design your TestBenches in Clash
description: Design your TestBenches in Clash
bug-reports: https://github.com/clash-lang/clash-compiler/issues
license: BSD-2-Clause
license-file: LICENSE
author: QBayLogic B.V.
maintainer: devops@qbaylogic.com
copyright: Copyright © 2023, QBayLogic B.V.
category: Hardware

library
default-language: Haskell2010
default-extensions:
DataKinds
FlexibleContexts
FlexibleInstances
GADTs
ImplicitParams
LambdaCase
MagicHash
MultiWayIf
NamedFieldPuns
RankNTypes
RecordWildCards
ScopedTypeVariables
TupleSections
TypeApplications
TypeFamilies
ViewPatterns
ghc-options:
-Wall -Wcompat
exposed-modules:
Clash.Testbench
Clash.Testbench.Signal
Clash.Testbench.Input
Clash.Testbench.Output
Clash.Testbench.Simulate
other-modules:
Clash.Testbench.Internal.ID
Clash.Testbench.Internal.Signal
Clash.Testbench.Internal.Monad
Clash.Testbench.Internal.Auto
build-depends:
base,
mtl,
array,
containers,
bytestring,
clash-ffi,
clash-prelude,
hs-source-dirs: src
56 changes: 56 additions & 0 deletions clash-testbench/example/Calculator.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeFamilies #-}
module Calculator where

import Clash.Prelude hiding (Word)

type Word = Signed 4
data OPC a = ADD | MUL | Imm a | Pop | Push
deriving (Lift, Generic, BitPack, NFDataX, Show)

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) a b = f (g a b)

infixr 9 .:

alu :: Num a => OPC a -> a -> a -> Maybe a
alu ADD = Just .: (+)
alu MUL = Just .: (*)
alu (Imm i) = const . const (Just i)
alu _ = const . const Nothing

pu :: (Num a, Num b)
=> (OPC a -> a -> a -> Maybe a)
-> (a, a, b) -- Current state
-> (a, OPC a) -- Input
-> ( (a, a, b) -- New state
, (b, Maybe a) -- Output
)
pu _ (op1, _, cnt) (dmem, Pop) = ((dmem, op1, cnt - 1), (cnt, Nothing) )
pu _ (op1, op2, cnt) ( _, Push) = ((op1, op2, cnt + 1) , (cnt, Nothing) )
pu a (op1, op2, cnt) ( _, opc) = ((op1, op2, cnt) , (cnt, a opc op1 op2))

datamem :: (KnownNat n, Integral i)
=> Vec n a -- Current state
-> (i, Maybe a) -- Input
-> (Vec n a, a) -- (New state, Output)
datamem mem (addr,Nothing) = (mem ,mem !! addr)
datamem mem (addr,Just val) = (replace addr val mem,mem !! addr)

topEntity
:: Clock System
-> Reset System
-> Enable System
-> Signal System (OPC Word)
-> Signal System (Maybe Word)
topEntity = exposeClockResetEnable go where
go i = val where
(addr,val) = (pu alu <^> (0,0,0 :: Unsigned 3)) (mem,i)
mem = (datamem <^> initMem) (addr,val)
initMem = replicate d8 0
{-# NOINLINE topEntity #-}
22 changes: 22 additions & 0 deletions clash-testbench/example/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2023 QBayLogic B.V.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.
24 changes: 24 additions & 0 deletions clash-testbench/example/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{-# LANGUAGE RecursiveDo #-}
module Main where

import Clash.Testbench

import Calculator (OPC(..))
import qualified Calculator (topEntity)

myTestbench
:: TB ()
myTestbench = mdo
input <- inputFromList Pop [Imm 1, Push, Imm 2, Push, Pop, Pop, Pop, ADD]
output <- ("topEntity" @@ Calculator.topEntity) auto auto auto input
watch input
watch output

main :: IO ()
main = simulate 10 myTestbench

foreign export ccall "clash_ffi_main"
ffiMain :: IO ()

ffiMain :: IO ()
ffiMain = simulateFFI 10 myTestbench
91 changes: 91 additions & 0 deletions clash-testbench/example/Setup.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module Main where

import Control.Monad
import Data.Maybe
import Distribution.PackageDescription.Utils
import Distribution.Simple
import Distribution.Simple.Build
import Distribution.Simple.BuildPaths
import Distribution.Simple.Setup
import Distribution.Simple.Utils
import Distribution.System
import Distribution.Types.ForeignLib
import Distribution.Types.ForeignLibType
import Distribution.Types.GenericPackageDescription
import Distribution.Types.HookedBuildInfo
import Distribution.Types.LocalBuildInfo
import Distribution.Types.PackageDescription
import Distribution.Types.UnqualComponentName
import Distribution.Verbosity
import System.Directory
import System.FilePath

main :: IO ()
main =
defaultMainWithHooks simpleUserHooks
{ postBuild = ffiPostBuild }

ffiPostBuild
:: Args
-> BuildFlags
-> PackageDescription
-> LocalBuildInfo
-> IO ()
ffiPostBuild args flags desc info = do
-- Create lib/ in the project directory
let outPath = takeDirectory (fromJust $ pkgDescrFile info) </> "lib"
createDirectoryIfMissing True outPath

-- Copy each foreign library to lib/
forM_ (foreignLibs desc) $ \flib ->
let name = unUnqualComponentName (foreignLibName flib)
dLib = buildDir info </> name </> flibTargetName info flib
in copySoAsVpl outPath dLib

-- Do the normal post-build hook action
postBuild simpleUserHooks args flags desc info

-- | Get the name of the library that will be written to disk when building
-- the library. Lifted from `Distribution.Simple.GHC`.
--
flibTargetName :: LocalBuildInfo -> ForeignLib -> String
flibTargetName lbi flib =
case (os, foreignLibType flib) of
(Windows, ForeignLibNativeShared) -> nm <.> "dll"
(Windows, ForeignLibNativeStatic) -> nm <.> "lib"
(Linux, ForeignLibNativeShared) -> "lib" ++ nm <.> versionedExt
(_other, ForeignLibNativeShared) ->
"lib" ++ nm <.> dllExtension (hostPlatform lbi)
(_other, ForeignLibNativeStatic) ->
"lib" ++ nm <.> staticLibExtension (hostPlatform lbi)
(_any, ForeignLibTypeUnknown) -> cabalBug "unknown foreign lib type"
where
nm :: String
nm = unUnqualComponentName $ foreignLibName flib

os :: OS
os = let (Platform _ os') = hostPlatform lbi
in os'

-- If a foreign lib foo has lib-version-info 5:1:2 or
-- lib-version-linux 3.2.1, it should be built as libfoo.so.3.2.1
-- Libtool's version-info data is translated into library versions in a
-- nontrivial way: so refer to libtool documentation.
versionedExt :: String
versionedExt =
let nums = foreignLibVersion flib os
in foldl (<.>) "so" (map show nums)

-- | Copy a file to the same directory, but change the extension to .vpl. This
-- is needed for iverilog, as it will not load VPI modules which do not have
-- either a .vpi or .vpl extension, unlike other simulators which will load
-- the .so file that cabal normally produces.
--
copySoAsVpl :: FilePath -> FilePath -> IO ()
copySoAsVpl outDir so =
-- We use installMaybeExecutable file because it preserves the permissions
-- of the original file. On my machine, just using installExecutableFile
-- meant the permissions were *slightly* different.
let outPath = replaceDirectory (replaceExtensions so "vpl") outDir
in installMaybeExecutableFile verbose so outPath

3 changes: 3 additions & 0 deletions clash-testbench/example/cabal.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
packages: . .. ../../clash-ghc ../../clash-lib ../../clash-prelude ../../clash-ffi

write-ghc-environment-files: always
49 changes: 49 additions & 0 deletions clash-testbench/example/clash-testbench-example.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
cabal-version: 2.4
name: clash-testbench-example
version: 0.1.0.0
synopsis: Exmaple for using clash-testbench
description: Exmaple for using clash-testbench
bug-reports: https://github.com/clash-lang/clash-compiler/issues
license: BSD-2-Clause
license-file: LICENSE
author: QBayLogic B.V.
maintainer: devops@qbaylogic.com
copyright: Copyright © 2023, QBayLogic B.V.
category: Hardware

common basic-config
default-language: Haskell2010
ghc-options:
-Wall -Wcompat
-fplugin GHC.TypeLits.Extra.Solver
-fplugin GHC.TypeLits.Normalise
-fplugin GHC.TypeLits.KnownNat.Solver
build-depends:
base,
clash-prelude,
clash-testbench,
ghc-typelits-extra,
ghc-typelits-knownnat,
ghc-typelits-natnormalise,

custom-setup
setup-depends:
base >= 4.11 && < 5,
Cabal >= 2.4 && < 3.7,
directory >= 1.3.6 && < 1.4,
filepath >= 1.4.2 && < 1.5,

executable simulate
import: basic-config
main-is: Main.hs
other-modules: Calculator
-- this option is required, since clash-ffi and clash-testbench come
-- with unresovled symbols for the VPI interface
ghc-options: -optl -Wl,--unresolved-symbols=ignore-in-object-files

foreign-library simulate-ffi
import: basic-config
other-modules: Main
Calculator
type: native-shared
lib-version-info: 0:1:0
34 changes: 34 additions & 0 deletions clash-testbench/example/run-iverilog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh

# This is just a minimalistic script for demonstrating the process of
# running the clash-testbench example using the Icarus Verilog VVP
# runtime engine. The script is not designed to work in any possible
# system environment and may not work immediately for you. It is
# intended to serve as an easy starter instead. Adapt it to your needs
# if it's not working out-of-the-box for you.

###############################

# Adjust these variables if the tools are not in your PATH already

# Cabal
# https://www.haskell.org/cabal
CABAL=cabal
# Clash
# https://github.com/clash-lang/clash-compiler
CLASH="${CABAL} run clash --"
# Icarus Verilog VVP runtime engine
# http://iverilog.icarus.com
IVERILOG=iverilog
VVP=vvp

###############################

${CABAL} build clash-testbench-example || exit $?
${CLASH} --verilog Calculator.hs || exit $?
${IVERILOG} verilog/Calculator.topEntity/topEntity.v -o Calculator.vvp \
|| exit $?
echo ""
echo "Running Icarus Verilog VVP runtime engine:"
echo ""
${VVP} -Mlib -mlibsimulate-ffi Calculator.vvp
18 changes: 18 additions & 0 deletions clash-testbench/src/Clash/Testbench.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{-|
Copyright: (C) 2023 Google Inc.
License: BSD2 (see the file LICENSE)
Maintainer: QBayLogic B.V. <devops@qbaylogic.com>
Design your TestBenches in Clash
-}
module Clash.Testbench
( module Clash.Testbench.Signal
, module Clash.Testbench.Input
, module Clash.Testbench.Output
, module Clash.Testbench.Simulate
) where

import Clash.Testbench.Signal
import Clash.Testbench.Input
import Clash.Testbench.Output
import Clash.Testbench.Simulate
Loading

0 comments on commit f34b0f8

Please sign in to comment.