Skip to content

Commit

Permalink
Merge pull request #56 from clash-lang/in-reset-behavior-tests
Browse files Browse the repository at this point in the history
Add test for vexriscv reset behavior
  • Loading branch information
martijnbastiaan authored Feb 19, 2025
2 parents da3b01a + 6783596 commit 638080b
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 52 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
- run: nix-shell --run "cabal build all"
- run: nix-shell --run "cabal run clash-vexriscv:unittests"
- run: nix-shell --run "cabal run clash-vexriscv-sim:unittests -- -j1"
- run: nix-shell --run "cabal run clash-vexriscv-sim:unittests -- -j1 --jtag-debug"
- run: nix-shell --run "cabal run clash-vexriscv-sim:hdl-test"

license-check:
Expand Down Expand Up @@ -266,7 +266,7 @@ jobs:
- name: Run `clash-vexriscv-sim` unittests
run: |
# Can't run the unit tests with multiple threads because of the common use of port 7894.
cabal run clash-vexriscv-sim:unittests -- -j1
cabal run clash-vexriscv-sim:unittests -- -j1 --jtag-debug
- name: Run `clash-vexriscv-sim` HDL test
run: |
Expand Down
96 changes: 76 additions & 20 deletions clash-vexriscv-sim/app/VexRiscvChainSimulation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Clash.Prelude
import Control.Monad (forM_, when)
import GHC.Char (chr)
import GHC.IO.Handle (Handle, hFlush, hPutStr)
import Options.Applicative (Parser, execParser, fullDesc, header, help, helper, info, long, progDesc, short, strOption)
import Options.Applicative (Parser, auto, execParser, fullDesc, header, help, helper, info, long, option, progDesc, short, showDefault, strOption, switch, value)
import Protocols.Wishbone
import System.Exit (exitFailure)
import System.IO (IOMode (WriteMode), hPutChar, hPutStrLn, openFile, stdout)
Expand Down Expand Up @@ -44,10 +44,15 @@ debugConfig =
--------------------------------------

data RunOpts = RunOpts
{ execPathA :: FilePath
, execPathB :: FilePath
, logPathA :: FilePath
, logPathB :: FilePath
{ a_execPath :: FilePath
, b_execPath :: FilePath
, a_logPath :: FilePath
, b_logPath :: FilePath
, a_assertResetFor :: Int
, b_assertResetFor :: Int
, a_assertResetAfter :: Int
, b_assertResetAfter :: Int
, printClockCycles :: Bool
}

getRunOpts :: Parser RunOpts
Expand All @@ -73,6 +78,38 @@ getRunOpts =
<> long "b-log"
<> help "Path to the log file for CPU B"
)
<*> option
auto
( long "assert-cpu-a-reset-for"
<> help "Assert CPU A in reset for N cycles. Must be at least 2 for a proper reset. Note that this reset gets asserted after 'assert-cpu-reset-after' cycles. Simulation will always start with a 2-cycle reset."
<> value 0
<> showDefault
)
<*> option
auto
( long "assert-cpu-b-reset-for"
<> help "Assert CPU B in reset for N cycles. Must be at least 2 for a proper reset. Note that this reset gets asserted after 'assert-cpu-reset-after' cycles. Simulation will always start with a 2-cycle reset."
<> value 0
<> showDefault
)
<*> option
auto
( long "assert-cpu-a-reset-after"
<> help "Assert CPU A reset after N cycles"
<> value 0
<> showDefault
)
<*> option
auto
( long "assert-cpu-b-reset-after"
<> help "Assert CPU B reset after N cycles"
<> value 0
<> showDefault
)
<*> switch
( long "print-clock-cycles"
<> help "Print number of clock cycles passed after each printed"
)

jtagDaisyChain :: JtagIn -> JtagOut -> JtagIn
jtagDaisyChain (JtagIn tc ms _) (JtagOut to) = JtagIn tc ms to
Expand All @@ -85,20 +122,28 @@ type CpuSignals =
, WishboneS2M (BitVector 32)
)

toReset :: (KnownDomain dom) => Int -> Int -> Reset dom
toReset assertForN assertAfterN =
unsafeFromActiveHigh $ fromList (asserted0 <> deasserted <> asserted1 <> L.repeat False)
where
asserted0 = L.replicate 2 True
deasserted = L.replicate assertAfterN False
asserted1 = L.replicate assertForN True

main :: IO ()
main = do
RunOpts{..} <- execParser opts

(iMemA, dMemA) <-
withClockResetEnable @System clockGen resetGen enableGen
$ loadProgramDmem @System execPathA
$ loadProgramDmem @System a_execPath

(iMemB, dMemB) <-
withClockResetEnable @System clockGen resetGen enableGen
$ loadProgramDmem @System execPathB
$ loadProgramDmem @System b_execPath

logFileA <- openFile logPathA WriteMode
logFileB <- openFile logPathB WriteMode
logFileA <- openFile a_logPath WriteMode
logFileB <- openFile b_logPath WriteMode

let portNr = 7894
jtagBridge <- vexrJtagBridge portNr
Expand All @@ -107,21 +152,24 @@ main = do

let
jtagInA = jtagBridge jtagOutB
resetA = toReset a_assertResetFor a_assertResetAfter
cpuOutA@(unbundle -> (_circuitA, jtagOutA, _, _iBusA, _dBusA)) =
withClock @System clockGen
$ withReset @System (resetGenN (SNat @2))
$ withReset @System resetA
$ let (circ, jto, writes1, iBus, dBus) = cpu NoDumpVcd (Just jtagInA) iMemA dMemA
in bundle (circ, jto, writes1, iBus, dBus)

jtagInB = liftA2 jtagDaisyChain jtagInA jtagOutA
resetB = toReset b_assertResetFor b_assertResetAfter
cpuOutB@(unbundle -> (_circuitB, jtagOutB, _, _iBusB, _dBusB)) =
withClock @System clockGen
$ withReset @System (resetGenN (SNat @2))
$ withReset @System resetB
$ let (circ, jto, writes1, iBus, dBus) = cpu NoDumpVcd (Just jtagInB) iMemB dMemB
in bundle (circ, jto, writes1, iBus, dBus)
cpuOut = bundle (cpuOutA, cpuOutB)

runSampling
printClockCycles
debugConfig
(logFileA, logFileB)
cpuOut
Expand All @@ -135,18 +183,20 @@ main = do
)

runSampling ::
-- | Print clock cycles whenever a line is printed
Bool ->
DebugConfiguration ->
(Handle, Handle) ->
Signal System (CpuSignals, CpuSignals) ->
IO ()
runSampling dbg (handleA, handleB) cpusOutputs = do
runSampling printClockCycles dbg (handleA, handleB) cpusOutputs = do
case dbg of
RunCharacterDevice ->
forM_
(sample_lazy @System (bundle (register @System (unpack 0) cpusOutputs, cpusOutputs)))
$ \((a1, b1), (a0, b0)) -> do
runCharacterDevice handleA a1 a0
runCharacterDevice handleB b1 b0
(L.zip [(0 :: Int) ..] (sample_lazy @System (bundle (register @System (unpack 0) cpusOutputs, cpusOutputs))))
$ \(nCycle, ((a1, b1), (a0, b0))) -> do
runCharacterDevice printClockCycles nCycle handleA a1 a0
runCharacterDevice printClockCycles nCycle handleB b1 b0
InspectBusses initCycles uninteresting interesting iEnabled dEnabled -> do
let
skipTotal = initCycles + uninteresting
Expand All @@ -171,11 +221,14 @@ runSampling dbg (handleA, handleB) cpusOutputs = do
_ -> pure ()

runCharacterDevice ::
-- | Print clock cycles whenever a line is printed
Bool ->
Int ->
Handle ->
CpuSignals ->
CpuSignals ->
IO ()
runCharacterDevice logFile (_, _, write, dS2M, iS2M) (out1, _, _, _, _) = do
runCharacterDevice printClockCycles nCycle logFile (_, _, write, dS2M, iS2M) (out1, _, _, _, _) = do
when (err dS2M) $ do
let dBusM2S = dBusWbM2S out1
let dAddr = toInteger (addr dBusM2S) -- `shiftL` 2
Expand All @@ -198,12 +251,15 @@ runCharacterDevice logFile (_, _, write, dS2M, iS2M) (out1, _, _, _, _) = do
exitFailure

case write of
Just (address, value) | address == 0x0000_1000 -> do
Just (address, val) | address == 0x0000_1000 -> do
let
(_ :: BitVector 24, b :: BitVector 8) = unpack value
(_ :: BitVector 24, b :: BitVector 8) = unpack val
char = chr (fromEnum b)
hPutChar logFile char
when (char == '\n') (hFlush logFile)
when (char == '\n') $ do
when printClockCycles $ do
hPutStrLn logFile ("[CPU] clock cycle: " <> show nCycle)
hFlush logFile
_ -> pure ()

runInspectBusses ::
Expand Down
6 changes: 3 additions & 3 deletions clash-vexriscv-sim/data/vexriscv_chain_gdbb.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# SPDX-License-Identifier: CC0-1.0

# Assume "print_a" is running on the CPU
# Assume "print_b" is running on the CPU
file "target/riscv32imc-unknown-none-elf/debug/print_b"

# Work around issues where simulation is too slow to respond to keep-alive messages,
Expand All @@ -27,7 +27,7 @@ break main
# Jump to start address, should run until it hits main
jump _start

# Run until we hit function "done", meaning it should have printed "a"
# Run until we hit function "done", meaning it should have printed "b"
disable 1
break print_b::done
continue
Expand All @@ -37,7 +37,7 @@ disable 2
file "target/riscv32imc-unknown-none-elf/debug/print_a"
load

# Jump to start address. Should now output "b".
# Jump to start address. Should now output "a".
break print_a::done
jump _start

Expand Down
29 changes: 24 additions & 5 deletions clash-vexriscv-sim/tests/Tests/Jtag.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ getGdb = do
Nothing -> fail "Neither gdb-multiarch nor gdb found in PATH"
Just x -> pure x

bold :: String -> String
bold s = "\ESC[1m" <> s <> "\ESC[0m"

green :: String -> String
green s = "\ESC[32m" <> s <> "\ESC[0m"

expectLineOrTimeout ::
(HasCallStack) =>
-- | Number of microseconds to wait. I.e., 1_000_000 is 1 second.
Expand All @@ -72,6 +78,8 @@ expectLineOrTimeout ::
String ->
Assertion
expectLineOrTimeout us debug h expected = do
when debug $
hPutStrLn stderr (bold (">>> Expecting: " <> expected))
result <- timeout us go
case result of
Just () -> pure ()
Expand All @@ -81,8 +89,10 @@ expectLineOrTimeout us debug h expected = do
line <- hGetLine h

when debug $ do
hPutStr stderr "> "
hPutStrLn stderr line
hPutStrLn stderr $
if line == expected
then bold $ green $ "[✓] " <> line
else "[ ] " <> line

ifM
(pure $ null line)
Expand All @@ -101,6 +111,8 @@ waitForLineOrTimeout ::
String ->
Assertion
waitForLineOrTimeout us debug h expected = do
when debug $
hPutStrLn stderr (bold (">>> Waiting for: " <> expected))
result <- timeout us go
case result of
Just () -> pure ()
Expand All @@ -109,8 +121,10 @@ waitForLineOrTimeout us debug h expected = do
go = do
line <- hGetLine h
when debug $ do
hPutStr stderr "> "
hPutStrLn stderr line
hPutStrLn stderr $
if line == expected
then bold $ green $ "[✓] " <> line
else "[ ] " <> line
if line == expected
then pure ()
else go
Expand Down Expand Up @@ -139,8 +153,12 @@ test debug = do
gdb <- getGdb

let
-- Timeout after 60 seconds
-- Timeout after 60 seconds. Warning: removing the type signature breaks
-- stack traces.
expectLine :: (HasCallStack) => Bool -> Handle -> String -> Assertion
expectLine = expectLineOrTimeout 60_000_000

waitForLine :: (HasCallStack) => Bool -> Handle -> String -> Assertion
waitForLine = waitForLineOrTimeout 60_000_000

vexRiscvProc =
Expand Down Expand Up @@ -172,6 +190,7 @@ test debug = do
withCreateProcess openOcdProc $ \_ _ (fromJust -> openOcdStdErr) _ -> do
hSetBuffering openOcdStdErr LineBuffering
putStrLn "Waiting for \"Halting processor\" on openOcdStdErr"
waitForLine debug openOcdStdErr "[riscv.cpu0] Target successfully examined."
waitForLine debug openOcdStdErr "Halting processor"

-- OpenOCD has started, so we can start GDB
Expand Down
Loading

0 comments on commit 638080b

Please sign in to comment.