From d2428522ca4ffabcf9113b5ca60792d23524e297 Mon Sep 17 00:00:00 2001 From: Martijn Bastiaan Date: Mon, 4 Mar 2024 15:59:22 +0100 Subject: [PATCH 1/2] Eliminate need for custom `mealy`, fixing laziness The old `mealy'` was too strict in its input. By wrapping `IntMap` in a `newtype` we can define an `NFDataX` instance for it, allowing the use of `clash-prelude`'s `mealy`. --- clash-vexriscv-sim/src/Utils/Storage.hs | 85 ++++++++++++++++++------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/clash-vexriscv-sim/src/Utils/Storage.hs b/clash-vexriscv-sim/src/Utils/Storage.hs index fed99eb..83bf115 100644 --- a/clash-vexriscv-sim/src/Utils/Storage.hs +++ b/clash-vexriscv-sim/src/Utils/Storage.hs @@ -2,17 +2,39 @@ -- -- SPDX-License-Identifier: Apache-2.0 -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ApplicativeDo #-} -module Utils.Storage where +{-# LANGUAGE RecordWildCards #-} + +module Utils.Storage + ( storage + ) where import Clash.Prelude +import Protocols.Wishbone +import GHC.Stack (HasCallStack) + import qualified Data.List as L import qualified Data.IntMap.Strict as I -import Clash.Signal.Internal (Signal((:-))) -import Protocols.Wishbone +newtype MappedMemory = MappedMemory (I.IntMap (BitVector 8)) + +unMappedMemory :: MappedMemory -> I.IntMap (BitVector 8) +unMappedMemory (MappedMemory x) = x + +instance NFDataX MappedMemory where + -- Not a product type, so no spine + deepErrorX = errorX + + -- Keys are 'Int's and evaluated to WHNF because this is a strict map. For 'Int's, + -- WHNF ~ NF, so we only need to check the values. + hasUndefined = any hasUndefined . I.elems . unMappedMemory + + -- Not a product type, so no spine + ensureSpine = id + + -- This is a strict map, so we dont need to do anything + rnfX x = seq x () storage :: forall dom. @@ -23,26 +45,34 @@ storage :: -- ^ contents Signal dom (WishboneM2S 32 4 (BitVector 32)) -> Signal dom (WishboneS2M (BitVector 32)) -storage contents = mealy' go (I.fromAscList $ L.zip [0..] contents) +storage contents = mealy go (MappedMemory $ I.fromAscList $ L.zip [0..] contents) where size = L.length contents - -- Version of mealy that doesn't require NFDataX for the state. - -- This is needed because IntMap (Word8) does not implement NFDataX - mealy' fn st0 (i :- is) = o :- mealy' fn st1 is - where (st1, o) = fn st0 i - - go mem WishboneM2S{..} - | not (busCycle && strobe) = (mem, emptyWishboneS2M) - | addr >= fromIntegral size = (mem, emptyWishboneS2M { err = True }) + go (MappedMemory mem) WishboneM2S{..} + | not (busCycle && strobe) = (MappedMemory mem, emptyWishboneS2M) + | addr >= fromIntegral size = + (MappedMemory mem, emptyWishboneS2M { err = True }) | not writeEnable {- read -} = case readDataSel mem addr busSelect of - Nothing -> (mem, emptyWishboneS2M { err = True }) - Just x -> (mem, (emptyWishboneS2M @(BitVector 32)) { acknowledge = True, readData = x }) + Nothing -> (MappedMemory mem, emptyWishboneS2M { err = True }) + Just x -> (MappedMemory mem, (emptyWishboneS2M @(BitVector 32)) { acknowledge = True, readData = x }) | otherwise {- write -} = - (writeDataSel mem addr busSelect writeData, emptyWishboneS2M { acknowledge = True }) + ( MappedMemory (writeDataSel mem addr busSelect writeData) + , emptyWishboneS2M { acknowledge = True } + ) -readDataSel :: I.IntMap (BitVector 8) -> BitVector 32 -> BitVector 4 -> Maybe (BitVector 32) +readDataSel :: + HasCallStack => + -- | Memory + I.IntMap (BitVector 8) -> + -- | Address + BitVector 32 -> + -- | Byte enables (@SEL@) + BitVector 4 -> + -- | Read value, or 'Nothing' if the read is invalid due to an unsupported + -- value of @SEL@. + Maybe (BitVector 32) readDataSel mem addr sel = case sel of 0b0001 -> readByte (addr + 0) @@ -52,8 +82,8 @@ readDataSel mem addr sel = 0b0011 -> readWord (addr + 0) 0b1100 -> readWord (addr + 2) 0b1111 -> readDWord addr - _ -> Nothing - + _ -> Nothing + where readByte addr' = resize @_ @8 @32 <$> I.lookup (fromIntegral addr') mem readWord addr' = do @@ -65,7 +95,18 @@ readDataSel mem addr sel = h <- readWord (addr' + 0) pure $ h `shiftL` 16 .|. l -writeDataSel :: I.IntMap (BitVector 8) -> BitVector 32 -> BitVector 4 -> BitVector 32 -> I.IntMap (BitVector 8) +writeDataSel :: + HasCallStack => + -- | Memory + I.IntMap (BitVector 8) -> + -- | Address + BitVector 32 -> + -- | Byte enables (SEL) + BitVector 4 -> + -- | Value to write + BitVector 32 -> + -- | Updated memory + I.IntMap (BitVector 8) writeDataSel mem addr sel val = case sel of 0b0001 -> @@ -87,7 +128,7 @@ writeDataSel mem addr sel val = I.insert (fromIntegral $ addr + 2) lh $ I.insert (fromIntegral $ addr + 1) hl $ I.insert (fromIntegral $ addr + 0) hh mem - _ -> mem - + _ -> error $ "Got SEL = " <> show sel <> " which is unsupported" + where (hh :: BitVector 8, hl :: BitVector 8, lh :: BitVector 8, ll :: BitVector 8) = unpack val From 14620c859f618b1dd173d95409706fc1df3c7a38 Mon Sep 17 00:00:00 2001 From: Martijn Bastiaan Date: Mon, 4 Mar 2024 17:00:09 +0100 Subject: [PATCH 2/2] Handle inputs and outputs in correct order Eleminates the need for prepending singals with dummy elements Fixes #1 --- clash-vexriscv-sim/clash-vexriscv-sim.cabal | 2 + clash-vexriscv-sim/src/Utils/Cpu.hs | 11 - clash-vexriscv-sim/src/Utils/Storage.hs | 8 +- clash-vexriscv/src/VexRiscv.hs | 230 ++++++++++++-------- clash-vexriscv/src/VexRiscv/FFI.hsc | 80 ++++--- clash-vexriscv/src/ffi/impl.cpp | 154 +++++++++---- clash-vexriscv/src/ffi/interface.h | 39 +--- 7 files changed, 307 insertions(+), 217 deletions(-) diff --git a/clash-vexriscv-sim/clash-vexriscv-sim.cabal b/clash-vexriscv-sim/clash-vexriscv-sim.cabal index ee6504f..fe75768 100644 --- a/clash-vexriscv-sim/clash-vexriscv-sim.cabal +++ b/clash-vexriscv-sim/clash-vexriscv-sim.cabal @@ -120,6 +120,8 @@ test-suite unittests default-language: Haskell2010 hs-source-dirs: tests type: exitcode-stdio-1.0 + -- TODO: enable parallel tests: + -- ghc-options: -threaded -rtsopts -with-rtsopts=-N ghc-options: -threaded main-is: tests.hs build-depends: diff --git a/clash-vexriscv-sim/src/Utils/Cpu.hs b/clash-vexriscv-sim/src/Utils/Cpu.hs index e887d6c..325492c 100644 --- a/clash-vexriscv-sim/src/Utils/Cpu.hs +++ b/clash-vexriscv-sim/src/Utils/Cpu.hs @@ -18,17 +18,6 @@ import GHC.Stack (HasCallStack) import Utils.ProgramLoad (Memory) import Utils.Interconnect (interconnectTwo) -emptyInput :: Input -emptyInput = - Input - { timerInterrupt = low, - externalInterrupt = low, - softwareInterrupt = low, - iBusWbS2M = (emptyWishboneS2M @(BitVector 32)) {readData = 0}, - dBusWbS2M = (emptyWishboneS2M @(BitVector 32)) {readData = 0} - } - - {- Address space diff --git a/clash-vexriscv-sim/src/Utils/Storage.hs b/clash-vexriscv-sim/src/Utils/Storage.hs index 83bf115..b053779 100644 --- a/clash-vexriscv-sim/src/Utils/Storage.hs +++ b/clash-vexriscv-sim/src/Utils/Storage.hs @@ -11,6 +11,7 @@ module Utils.Storage import Clash.Prelude +import Data.Either (isLeft) import Protocols.Wishbone import GHC.Stack (HasCallStack) @@ -28,12 +29,15 @@ instance NFDataX MappedMemory where -- Keys are 'Int's and evaluated to WHNF because this is a strict map. For 'Int's, -- WHNF ~ NF, so we only need to check the values. - hasUndefined = any hasUndefined . I.elems . unMappedMemory + hasUndefined m = + isLeft (isX (unMappedMemory m)) + || (any hasUndefined $ I.elems $ unMappedMemory m) -- Not a product type, so no spine ensureSpine = id - -- This is a strict map, so we dont need to do anything + -- This is a strict map, so we dont need to do anything. Note that WHNF ~ NF for + -- 'BitVector'. rnfX x = seq x () storage :: diff --git a/clash-vexriscv/src/VexRiscv.hs b/clash-vexriscv/src/VexRiscv.hs index 24a7154..adf4ba7 100644 --- a/clash-vexriscv/src/VexRiscv.hs +++ b/clash-vexriscv/src/VexRiscv.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022-2023 Google LLC +-- SPDX-FileCopyrightText: 2022-2024 Google LLC -- -- SPDX-License-Identifier: Apache-2.0 @@ -16,17 +16,23 @@ import Clash.Prelude import Clash.Annotations.Primitive import Clash.Signal.Internal +import Data.Bifunctor (first) import Data.String.Interpolate (__i) +import Data.Word (Word64) +import Foreign (Ptr) import Foreign.Marshal (alloca) import Foreign.Storable -import GHC.IO (unsafePerformIO) +import GHC.IO (unsafePerformIO, unsafeInterleaveIO) import GHC.Stack (HasCallStack) import Language.Haskell.TH.Syntax import Protocols.Wishbone + +import VexRiscv.ClockTicks import VexRiscv.FFI import VexRiscv.TH import VexRiscv.VecToTuple +import qualified VexRiscv.FFI as FFI data Input = Input { timerInterrupt :: "TIMER_INTERRUPT" ::: Bit @@ -43,48 +49,6 @@ data Output = Output } deriving (Generic, NFDataX, ShowX, Eq, BitPack) -inputToFFI :: Bool -> Input -> INPUT -inputToFFI reset Input {..} = - INPUT - { reset = boolToBit reset - , timerInterrupt - , externalInterrupt - , softwareInterrupt - , iBusWishbone_ACK = boolToBit $ acknowledge iBusWbS2M - , iBusWishbone_DAT_MISO = unpack $ readData iBusWbS2M - , iBusWishbone_ERR = boolToBit $ err iBusWbS2M - , dBusWishbone_ACK = boolToBit $ acknowledge dBusWbS2M - , dBusWishbone_DAT_MISO = unpack $ readData dBusWbS2M - , dBusWishbone_ERR = boolToBit $ err dBusWbS2M - } - -outputFromFFI :: OUTPUT -> Output -outputFromFFI OUTPUT {..} = - Output - { iBusWbM2S = - (emptyWishboneM2S @30 @(BitVector 32)) - { busCycle = bitToBool iBusWishbone_CYC, - strobe = bitToBool iBusWishbone_STB, - writeEnable = bitToBool iBusWishbone_WE, - addr = truncateB $ pack iBusWishbone_ADR, - writeData = pack iBusWishbone_DAT_MOSI, - busSelect = resize $ pack iBusWishbone_SEL, - cycleTypeIdentifier = unpack $ resize $ pack iBusWishbone_CTI, - burstTypeExtension = unpack $ resize $ pack iBusWishbone_BTE - }, - dBusWbM2S = - (emptyWishboneM2S @30 @(BitVector 32)) - { busCycle = bitToBool dBusWishbone_CYC, - strobe = bitToBool dBusWishbone_STB, - writeEnable = bitToBool dBusWishbone_WE, - addr = truncateB $ pack dBusWishbone_ADR, - writeData = pack dBusWishbone_DAT_MOSI, - busSelect = resize $ pack dBusWishbone_SEL, - cycleTypeIdentifier = unpack $ resize $ pack dBusWishbone_CTI, - burstTypeExtension = unpack $ resize $ pack dBusWishbone_BTE - } - } - -- When passing S2M values from Haskell to VexRiscv over the FFI, undefined -- bits/values cause errors when forcing their evaluation to something that can -- be passed through the FFI. @@ -127,9 +91,7 @@ vexRiscv input = where (unbundle -> (timerInterrupt, externalInterrupt, softwareInterrupt, iBusS2M, dBusS2M)) - -- A hack that enables us to both generate synthesizable HDL and simulate vexRisc in Haskell/Clash - = (<$> if clashSimulation then unpack 0 :- input else input) - $ \(Input a b c d e) -> (a, b, c, d, e) + = (\(Input a b c d e) -> (a, b, c, d, e)) <$> input (unbundle -> (iBus_DAT_MISO, iBus_ACK, iBus_ERR)) = (\(WishboneS2M a b c _ _) -> (a, b, c)) @@ -222,7 +184,7 @@ vexRiscv# , Signal dom (BitVector 3) -- ^ dBus_CTI , Signal dom (BitVector 2) -- ^ dBus_BTE ) -vexRiscv# !_sourcePath !_clk rst0 +vexRiscv# !_sourcePath clk rst0 timerInterrupt externalInterrupt softwareInterrupt @@ -232,52 +194,99 @@ vexRiscv# !_sourcePath !_clk rst0 dBus_ACK dBus_ERR - dBus_DAT_MISO + dBus_DAT_MISO = unsafePerformIO $ do + (v, initStage1, initStage2, stepRising, stepFalling, _shutDown) <- vexCPU - = let - iBusS2M = WishboneS2M <$> iBus_DAT_MISO <*> iBus_ACK <*> iBus_ERR <*> pure False <*> pure False - dBusS2M = WishboneS2M <$> dBus_DAT_MISO <*> dBus_ACK <*> dBus_ERR <*> pure False <*> pure False - - input = Input <$> timerInterrupt <*> externalInterrupt <*> softwareInterrupt <*> iBusS2M <*> dBusS2M - - output = unsafePerformIO $ do - (step, _) <- vexCPU - pure $ go step (unsafeFromReset rst0) input - - (unbundle -> (iBusM2S, dBusM2S)) = (<$> output) $ \(Output iBus dBus) -> (iBus, dBus) + nonCombInput = NON_COMB_INPUT + <$> (boolToBit <$> unsafeToActiveHigh rst0) + <*> timerInterrupt + <*> externalInterrupt + <*> softwareInterrupt + + combInput = COMB_INPUT + <$> (boolToBit <$> iBus_ACK) + <*> (unpack <$> iBus_DAT_MISO) + <*> (boolToBit <$> iBus_ERR) + <*> (boolToBit <$> dBus_ACK) + <*> (unpack <$> dBus_DAT_MISO) + <*> (boolToBit <$> dBus_ERR) + + simInitThenCycles :: + Signal dom NON_COMB_INPUT -> + Signal dom COMB_INPUT -> + IO (Signal dom OUTPUT) + simInitThenCycles (cnc :- cncs) ~(cc :- ccs) = do + let + -- Note: we don't need @ticks@ for the initialization stages, because this + -- first cycle of a 'Signal' is meant to model what happens _before_ a + -- clock edge. + ticks = first fromIntegral <$> singleClockEdgesRelative clk + + out0 <- initStage1 v cnc + stage2Out <- unsafeInterleaveIO (initStage2 v cc) + out1 <- unsafeInterleaveIO (simCycles ticks cncs ccs) + + pure $ out0 :- (stage2Out `seq` out1) + + simCycles :: + [(Word64, ActiveEdge)] -> + Signal dom NON_COMB_INPUT -> + Signal dom COMB_INPUT -> + IO (Signal dom OUTPUT) + simCycles ((fsSinceLastEvent, Rising) : ts) (cnc :- cncs) ccs = do + out0 <- stepRising v fsSinceLastEvent cnc + out1 <- unsafeInterleaveIO (simCycles ts cncs ccs) + pure $ out0 :- out1 + + simCycles ((fsSinceLastEvent, Falling) : ts) cncs (cc :- ccs) = do + stepFalling v fsSinceLastEvent cc + simCycles ts cncs ccs + + simCycles [] _ _ = error "Empty ticks: should never happen" + + output <- simInitThenCycles nonCombInput combInput - (unbundle -> (iBus_ADR, iBus_DAT_MOSI, iBus_SEL, iBus_CYC, iBus_STB, iBus_WE, iBus_CTI, iBus_BTE)) = - (<$> iBusM2S) $ \(WishboneM2S a b c _ e f g h i) -> (a, b, c, e, f, g, h, i) - - (unbundle -> (dBus_ADR, dBus_DAT_MOSI, dBus_SEL, dBus_CYC, dBus_STB, dBus_WE, dBus_CTI, dBus_BTE)) = - (<$> dBusM2S) $ \(WishboneM2S a b c _ e f g h i) -> (a, b, c, e, f, g, h, i) - in + let + iBus_CYC = FFI.iBusWishbone_CYC <$> output + iBus_STB = FFI.iBusWishbone_STB <$> output + iBus_WE = FFI.iBusWishbone_WE <$> output + iBus_ADR = FFI.iBusWishbone_ADR <$> output + iBus_DAT_MOSI = FFI.iBusWishbone_DAT_MOSI <$> output + iBus_SEL = FFI.iBusWishbone_SEL <$> output + iBus_CTI = FFI.iBusWishbone_CTI <$> output + iBus_BTE = FFI.iBusWishbone_BTE <$> output + + dBus_CYC = FFI.dBusWishbone_CYC <$> output + dBus_STB = FFI.dBusWishbone_STB <$> output + dBus_WE = FFI.dBusWishbone_WE <$> output + dBus_ADR = FFI.dBusWishbone_ADR <$> output + dBus_DAT_MOSI = FFI.dBusWishbone_DAT_MOSI <$> output + dBus_SEL = FFI.dBusWishbone_SEL <$> output + dBus_CTI = FFI.dBusWishbone_CTI <$> output + dBus_BTE = FFI.dBusWishbone_BTE <$> output + + pure ( -- iBus - iBus_CYC - , iBus_STB - , iBus_WE - , iBus_ADR - , iBus_DAT_MOSI - , iBus_SEL - , pack <$> iBus_CTI - , pack <$> iBus_BTE + bitToBool <$> iBus_CYC + , bitToBool <$> iBus_STB + , bitToBool <$> iBus_WE + , truncateB . pack <$> iBus_ADR + , pack <$> iBus_DAT_MOSI + , truncateB . pack <$> iBus_SEL + , truncateB . pack <$> iBus_CTI + , truncateB . pack <$> iBus_BTE -- dBus - , dBus_CYC - , dBus_STB - , dBus_WE - , dBus_ADR - , dBus_DAT_MOSI - , dBus_SEL - , pack <$> dBus_CTI - , pack <$> dBus_BTE + , bitToBool <$> dBus_CYC + , bitToBool <$> dBus_STB + , bitToBool <$> dBus_WE + , truncateB . pack <$> dBus_ADR + , pack <$> dBus_DAT_MOSI + , truncateB . pack <$> dBus_SEL + , truncateB . pack <$> dBus_CTI + , truncateB . pack <$> dBus_BTE ) - where - {-# NOINLINE go #-} - go step (rst :- rsts) (input :- inputs) = unsafePerformIO $ do - out <- step rst input - pure $ out :- go step rsts inputs {-# NOINLINE vexRiscv# #-} {-# ANN vexRiscv# ( let @@ -401,16 +410,47 @@ vexRiscv# !_sourcePath !_clk rst0 |] ) #-} + -- | Return a function that performs an execution step and a function to free -- the internal CPU state -vexCPU :: IO (Bool -> Input -> IO Output, IO ()) +vexCPU :: IO + ( Ptr VexRiscv + , Ptr VexRiscv -> NON_COMB_INPUT -> IO OUTPUT -- initStage1 + , Ptr VexRiscv -> COMB_INPUT -> IO () -- initStage2 + , Ptr VexRiscv -> Word64 -> NON_COMB_INPUT -> IO OUTPUT -- rising + , Ptr VexRiscv -> Word64 -> COMB_INPUT -> IO () -- falling + , Ptr VexRiscv -> IO () + ) vexCPU = do v <- vexrInit + let - step reset input = alloca $ \inputFFI -> alloca $ \outputFFI -> do - poke inputFFI (inputToFFI reset input) - vexrStep v inputFFI outputFFI - outVal <- peek outputFFI - pure $ outputFromFFI outVal - shutDown = vexrShutdown v - pure (step, shutDown) + {-# NOINLINE initStage1 #-} + initStage1 vPtr nonCombInput = + alloca $ \nonCombInputFFI -> alloca $ \outputFFI -> do + poke nonCombInputFFI nonCombInput + vexrInitStage1 vPtr nonCombInputFFI outputFFI + peek outputFFI + + {-# NOINLINE initStage2 #-} + initStage2 vPtr combInput = + alloca $ \combInputFFI -> do + poke combInputFFI combInput + vexrInitStage2 vPtr combInputFFI + + {-# NOINLINE stepRising #-} + stepRising vPtr fsSinceLastEvent nonCombInput = + alloca $ \nonCombInputFFI -> alloca $ \outputFFI -> do + poke nonCombInputFFI nonCombInput + vexrStepRisingEdge vPtr fsSinceLastEvent nonCombInputFFI outputFFI + peek outputFFI + + {-# NOINLINE stepFalling #-} + stepFalling vPtr fsSinceLastEvent combInput = + alloca $ \combInputFFI -> do + poke combInputFFI combInput + vexrStepFallingEdge vPtr fsSinceLastEvent combInputFFI + + shutDown = vexrShutdown + + pure (v, initStage1, initStage2, stepRising, stepFalling, shutDown) diff --git a/clash-vexriscv/src/VexRiscv/FFI.hsc b/clash-vexriscv/src/VexRiscv/FFI.hsc index 47b154b..9fd456c 100644 --- a/clash-vexriscv/src/VexRiscv/FFI.hsc +++ b/clash-vexriscv/src/VexRiscv/FFI.hsc @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Google LLC +-- SPDX-FileCopyrightText: 2022-2024 Google LLC -- -- SPDX-License-Identifier: Apache-2.0 @@ -19,19 +19,27 @@ import Data.Word data VexRiscv foreign import ccall unsafe "vexr_init" vexrInit :: IO (Ptr VexRiscv) - foreign import ccall unsafe "vexr_shutdown" vexrShutdown :: Ptr VexRiscv -> IO () -foreign import ccall unsafe "vexr_step" vexrStep :: Ptr VexRiscv -> Ptr INPUT -> Ptr OUTPUT -> IO () +foreign import ccall unsafe "vexr_init_stage1" vexrInitStage1 :: Ptr VexRiscv -> Ptr NON_COMB_INPUT -> Ptr OUTPUT -> IO () +foreign import ccall unsafe "vexr_init_stage2" vexrInitStage2 :: Ptr VexRiscv -> Ptr COMB_INPUT -> IO () +foreign import ccall unsafe "vexr_step_rising_edge" vexrStepRisingEdge :: Ptr VexRiscv -> Word64 -> Ptr NON_COMB_INPUT -> Ptr OUTPUT -> IO () +foreign import ccall unsafe "vexr_step_falling_edge" vexrStepFallingEdge :: Ptr VexRiscv -> Word64 -> Ptr COMB_INPUT -> IO () -data INPUT = INPUT +-- | CPU input that cannot combinatorially depend on the CPU output +data NON_COMB_INPUT = NON_COMB_INPUT { reset :: Bit , timerInterrupt :: Bit , externalInterrupt :: Bit , softwareInterrupt :: Bit - , iBusWishbone_ACK :: Bit + } + +-- | CPU input that can combinatorially depend on the CPU output +data COMB_INPUT = COMB_INPUT + { iBusWishbone_ACK :: Bit , iBusWishbone_DAT_MISO :: Word32 , iBusWishbone_ERR :: Bit + , dBusWishbone_ACK :: Bit , dBusWishbone_DAT_MISO :: Word32 , dBusWishbone_ERR :: Bit @@ -47,6 +55,7 @@ data OUTPUT = OUTPUT , iBusWishbone_SEL :: Word8 , iBusWishbone_CTI :: Word8 , iBusWishbone_BTE :: Word8 + , dBusWishbone_CYC :: Bit , dBusWishbone_STB :: Bit , dBusWishbone_WE :: Bit @@ -64,34 +73,45 @@ instance Storable Bit where peek = fmap boolToBit . peek . castPtr poke ptr = poke (castPtr ptr) . bitToBool -instance Storable INPUT where - alignment _ = #alignment INPUT - sizeOf _ = #size INPUT +instance Storable NON_COMB_INPUT where + alignment _ = #alignment NON_COMB_INPUT + sizeOf _ = #size NON_COMB_INPUT + {-# INLINE peek #-} + peek ptr = const NON_COMB_INPUT <$> pure () + <*> (#peek NON_COMB_INPUT, reset) ptr + <*> (#peek NON_COMB_INPUT, timerInterrupt) ptr + <*> (#peek NON_COMB_INPUT, externalInterrupt) ptr + <*> (#peek NON_COMB_INPUT, softwareInterrupt) ptr + + {-# INLINE poke #-} + poke ptr this = do + (#poke NON_COMB_INPUT, reset) ptr (reset this) + (#poke NON_COMB_INPUT, timerInterrupt) ptr (timerInterrupt this) + (#poke NON_COMB_INPUT, externalInterrupt) ptr (externalInterrupt this) + (#poke NON_COMB_INPUT, softwareInterrupt) ptr (softwareInterrupt this) + return () + +instance Storable COMB_INPUT where + alignment _ = #alignment COMB_INPUT + sizeOf _ = #size COMB_INPUT {-# INLINE peek #-} - peek ptr = const INPUT <$> pure () - <*> (#peek INPUT, reset) ptr - <*> (#peek INPUT, timerInterrupt) ptr - <*> (#peek INPUT, externalInterrupt) ptr - <*> (#peek INPUT, softwareInterrupt) ptr - <*> (#peek INPUT, iBusWishbone_ACK) ptr - <*> (#peek INPUT, iBusWishbone_DAT_MISO) ptr - <*> (#peek INPUT, iBusWishbone_ERR) ptr - <*> (#peek INPUT, dBusWishbone_ACK) ptr - <*> (#peek INPUT, dBusWishbone_DAT_MISO) ptr - <*> (#peek INPUT, dBusWishbone_ERR) ptr + peek ptr = const COMB_INPUT <$> pure () + <*> (#peek COMB_INPUT, iBusWishbone_ACK) ptr + <*> (#peek COMB_INPUT, iBusWishbone_DAT_MISO) ptr + <*> (#peek COMB_INPUT, iBusWishbone_ERR) ptr + <*> (#peek COMB_INPUT, dBusWishbone_ACK) ptr + <*> (#peek COMB_INPUT, dBusWishbone_DAT_MISO) ptr + <*> (#peek COMB_INPUT, dBusWishbone_ERR) ptr {-# INLINE poke #-} poke ptr this = do - (#poke INPUT, reset) ptr (reset this) - (#poke INPUT, timerInterrupt) ptr (timerInterrupt this) - (#poke INPUT, externalInterrupt) ptr (externalInterrupt this) - (#poke INPUT, softwareInterrupt) ptr (softwareInterrupt this) - (#poke INPUT, iBusWishbone_ACK) ptr (iBusWishbone_ACK this) - (#poke INPUT, iBusWishbone_DAT_MISO) ptr (iBusWishbone_DAT_MISO this) - (#poke INPUT, iBusWishbone_ERR) ptr (iBusWishbone_ERR this) - (#poke INPUT, dBusWishbone_ACK) ptr (dBusWishbone_ACK this) - (#poke INPUT, dBusWishbone_DAT_MISO) ptr (dBusWishbone_DAT_MISO this) - (#poke INPUT, dBusWishbone_ERR) ptr (dBusWishbone_ERR this) + (#poke COMB_INPUT, iBusWishbone_ACK) ptr (iBusWishbone_ACK this) + (#poke COMB_INPUT, iBusWishbone_DAT_MISO) ptr (iBusWishbone_DAT_MISO this) + (#poke COMB_INPUT, iBusWishbone_ERR) ptr (iBusWishbone_ERR this) + + (#poke COMB_INPUT, dBusWishbone_ACK) ptr (dBusWishbone_ACK this) + (#poke COMB_INPUT, dBusWishbone_DAT_MISO) ptr (dBusWishbone_DAT_MISO this) + (#poke COMB_INPUT, dBusWishbone_ERR) ptr (dBusWishbone_ERR this) return () instance Storable OUTPUT where @@ -107,6 +127,7 @@ instance Storable OUTPUT where <*> (#peek OUTPUT, iBusWishbone_SEL) ptr <*> (#peek OUTPUT, iBusWishbone_CTI) ptr <*> (#peek OUTPUT, iBusWishbone_BTE) ptr + <*> (#peek OUTPUT, dBusWishbone_CYC) ptr <*> (#peek OUTPUT, dBusWishbone_STB) ptr <*> (#peek OUTPUT, dBusWishbone_WE) ptr @@ -126,6 +147,7 @@ instance Storable OUTPUT where (#poke OUTPUT, iBusWishbone_SEL) ptr (iBusWishbone_SEL this) (#poke OUTPUT, iBusWishbone_CTI) ptr (iBusWishbone_CTI this) (#poke OUTPUT, iBusWishbone_BTE) ptr (iBusWishbone_BTE this) + (#poke OUTPUT, dBusWishbone_CYC) ptr (dBusWishbone_CYC this) (#poke OUTPUT, dBusWishbone_STB) ptr (dBusWishbone_STB this) (#poke OUTPUT, dBusWishbone_WE) ptr (dBusWishbone_WE this) diff --git a/clash-vexriscv/src/ffi/impl.cpp b/clash-vexriscv/src/ffi/impl.cpp index a7bbba0..230e2c6 100644 --- a/clash-vexriscv/src/ffi/impl.cpp +++ b/clash-vexriscv/src/ffi/impl.cpp @@ -7,57 +7,123 @@ #include "interface.h" extern "C" { - VVexRiscv* vexr_init(); - void vexr_shutdown(VVexRiscv *top); - void vexr_step(VVexRiscv *top, const INPUT *input, OUTPUT *output); + VVexRiscv* vexr_init(); + void vexr_shutdown(VVexRiscv *top); + + void vexr_init_stage1(VVexRiscv *top, const NON_COMB_INPUT *input, OUTPUT *output); + void vexr_init_stage2(VVexRiscv *top, const COMB_INPUT *input); + void vexr_step_rising_edge(VVexRiscv *top, uint64_t time_add, const NON_COMB_INPUT *input, OUTPUT *output); + void vexr_step_falling_edge(VVexRiscv *top, uint64_t time_add, const COMB_INPUT *input); } +static VerilatedContext* contextp = 0; VVexRiscv* vexr_init() { - return new VVexRiscv(); + contextp = new VerilatedContext; + VVexRiscv *v = new VVexRiscv(contextp); + Verilated::traceEverOn(true); + v->clk = false; + return v; +} + +// Set all inputs that cannot combinationaly depend on outputs. I.e., all inputs +// except the Wishbone buses. +void set_non_comb_inputs(VVexRiscv *top, const NON_COMB_INPUT *input) +{ + top->reset = input->reset; + top->timerInterrupt = input->timerInterrupt; + top->externalInterrupt = input->externalInterrupt; + top->softwareInterrupt = input->softwareInterrupt; +} + +// Set all inputs that can combinationaly depend on outputs. I.e., the Wishbone +// buses. +void set_comb_inputs(VVexRiscv *top, const COMB_INPUT *input) +{ + top->iBusWishbone_ACK = input->iBusWishbone_ACK; + top->iBusWishbone_DAT_MISO = input->iBusWishbone_DAT_MISO; + top->iBusWishbone_ERR = input->iBusWishbone_ERR; + top->dBusWishbone_ACK = input->dBusWishbone_ACK; + top->dBusWishbone_DAT_MISO = input->dBusWishbone_DAT_MISO; + top->dBusWishbone_ERR = input->dBusWishbone_ERR; +} + +// Set all outputs +void set_ouputs(VVexRiscv *top, OUTPUT *output) +{ + output->iBusWishbone_CYC = top->iBusWishbone_CYC; + output->iBusWishbone_STB = top->iBusWishbone_STB; + output->iBusWishbone_WE = top->iBusWishbone_WE; + output->iBusWishbone_ADR = top->iBusWishbone_ADR; + output->iBusWishbone_DAT_MOSI = top->iBusWishbone_DAT_MOSI; + output->iBusWishbone_SEL = top->iBusWishbone_SEL; + output->iBusWishbone_CTI = top->iBusWishbone_CTI; + output->iBusWishbone_BTE = top->iBusWishbone_BTE; + output->dBusWishbone_CYC = top->dBusWishbone_CYC; + output->dBusWishbone_STB = top->dBusWishbone_STB; + output->dBusWishbone_WE = top->dBusWishbone_WE; + output->dBusWishbone_ADR = top->dBusWishbone_ADR; + output->dBusWishbone_DAT_MOSI = top->dBusWishbone_DAT_MOSI; + output->dBusWishbone_SEL = top->dBusWishbone_SEL; + output->dBusWishbone_CTI = top->dBusWishbone_CTI; + output->dBusWishbone_BTE = top->dBusWishbone_BTE; +} + +void vexr_init_stage1(VVexRiscv *top, const NON_COMB_INPUT *input, OUTPUT *output) +{ + // Set all inputs that cannot combinationaly depend on outputs. I.e., all inputs + // except the Wishbone buses. + set_non_comb_inputs(top, input); + + // Combinatorially respond to the inputs + top->eval(); + set_ouputs(top, output); + + // Advance time by 50 nanoseconds. This is an arbitrary value. Ideally, we would + // do something similar to Clash's template tag "~LONGESTPERIOD". + contextp->timeInc(50000); +} + +void vexr_init_stage2(VVexRiscv *top, const COMB_INPUT *input) +{ + set_comb_inputs(top, input); } void vexr_shutdown(VVexRiscv *top) { - delete top; -} - -void vexr_step(VVexRiscv *top, const INPUT *input, OUTPUT *output) -{ - // set inputs - top->reset = input->reset; - top->timerInterrupt = input->timerInterrupt; - top->externalInterrupt = input->externalInterrupt; - top->softwareInterrupt = input->softwareInterrupt; - top->iBusWishbone_ACK = input->iBusWishbone_ACK; - top->iBusWishbone_DAT_MISO = input->iBusWishbone_DAT_MISO; - top->iBusWishbone_ERR = input->iBusWishbone_ERR; - top->dBusWishbone_ACK = input->dBusWishbone_ACK; - top->dBusWishbone_DAT_MISO = input->dBusWishbone_DAT_MISO; - top->dBusWishbone_ERR = input->dBusWishbone_ERR; - - // run one cycle of the simulation - top->clk = true; - top->eval(); - top->clk = false; - top->eval(); - - // update outputs - output->iBusWishbone_CYC = top->iBusWishbone_CYC; - output->iBusWishbone_STB = top->iBusWishbone_STB; - output->iBusWishbone_WE = top->iBusWishbone_WE; - output->iBusWishbone_ADR = top->iBusWishbone_ADR; - output->iBusWishbone_DAT_MOSI = top->iBusWishbone_DAT_MOSI; - output->iBusWishbone_SEL = top->iBusWishbone_SEL; - output->iBusWishbone_CTI = top->iBusWishbone_CTI; - output->iBusWishbone_BTE = top->iBusWishbone_BTE; - output->dBusWishbone_CYC = top->dBusWishbone_CYC; - output->dBusWishbone_STB = top->dBusWishbone_STB; - output->dBusWishbone_WE = top->dBusWishbone_WE; - output->dBusWishbone_ADR = top->dBusWishbone_ADR; - output->dBusWishbone_DAT_MOSI = top->dBusWishbone_DAT_MOSI; - output->dBusWishbone_SEL = top->dBusWishbone_SEL; - output->dBusWishbone_CTI = top->dBusWishbone_CTI; - output->dBusWishbone_BTE = top->dBusWishbone_BTE; + delete top; + delete contextp; + contextp = 0; +} + + +void vexr_step_rising_edge(VVexRiscv *top, uint64_t time_add, const NON_COMB_INPUT *input, OUTPUT *output) +{ + // Advance time since last event. Note that this is 0 for the first call to + // this function. To get a sensisble waveform, vexr_init has already advanced + // time. + contextp->timeInc(time_add); // XXX: time_add is in femtoseconds, timeinc expects picoseconds + + // docssss + set_non_comb_inputs(top, input); + + top->clk = true; + top->eval(); + + // Set all outputs + set_ouputs(top, output); +} + +void vexr_step_falling_edge(VVexRiscv *top, uint64_t time_add, const COMB_INPUT *input) +{ + // advance time since last event + contextp->timeInc(time_add); // time_add is in femtoseconds, timeinc expects picoseconds + + // Update inputs + top->clk = false; + set_comb_inputs(top, input); + + // Evaluate the simulation + top->eval(); } diff --git a/clash-vexriscv/src/ffi/interface.h b/clash-vexriscv/src/ffi/interface.h index 312c68a..e82759a 100644 --- a/clash-vexriscv/src/ffi/interface.h +++ b/clash-vexriscv/src/ffi/interface.h @@ -14,7 +14,9 @@ typedef struct { bit timerInterrupt; bit externalInterrupt; bit softwareInterrupt; +} NON_COMB_INPUT; +typedef struct { bit iBusWishbone_ACK; uint32_t iBusWishbone_DAT_MISO; bit iBusWishbone_ERR; @@ -22,7 +24,7 @@ typedef struct { bit dBusWishbone_ACK; uint32_t dBusWishbone_DAT_MISO; bit dBusWishbone_ERR; -} INPUT; +} COMB_INPUT; typedef struct { bit iBusWishbone_CYC; @@ -44,39 +46,4 @@ typedef struct { uint8_t dBusWishbone_BTE; } OUTPUT; - #endif - -/* - input reset - input timerInterrupt, - input externalInterrupt, - input softwareInterrupt, - - input iBusWishbone_ACK, - input [31:0] iBusWishbone_DAT_MISO, - input iBusWishbone_ERR, - - input dBusWishbone_ACK, - input [31:0] dBusWishbone_DAT_MISO, - input dBusWishbone_ERR, - - - output iBusWishbone_CYC, - output iBusWishbone_STB, - output iBusWishbone_WE, - output [29:0] iBusWishbone_ADR, - output [31:0] iBusWishbone_DAT_MOSI, - output [3:0] iBusWishbone_SEL, - output [2:0] iBusWishbone_CTI, - output [1:0] iBusWishbone_BTE, - - output dBusWishbone_CYC, - output dBusWishbone_STB, - output dBusWishbone_WE, - output [29:0] dBusWishbone_ADR, - output [31:0] dBusWishbone_DAT_MOSI, - output reg [3:0] dBusWishbone_SEL, - output [2:0] dBusWishbone_CTI, - output [1:0] dBusWishbone_BTE, -*/