Skip to content

Commit

Permalink
Test CPU chain access with one CPU in reset
Browse files Browse the repository at this point in the history
Turns out, the chain works just fine with one CPU in reset, which is the
behavior we want and expect.

This commit also factors out some of the shared code between the tests
to keep it a bit more manageable.
  • Loading branch information
martijnbastiaan committed Feb 14, 2025
1 parent 42b22c6 commit 85cbaeb
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 27 deletions.
22 changes: 19 additions & 3 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, execParser, fullDesc, header, help, helper, info, long, progDesc, short, strOption, switch)
import Protocols.Wishbone
import System.Exit (exitFailure)
import System.IO (IOMode (WriteMode), hPutChar, hPutStrLn, openFile, stdout)
Expand Down Expand Up @@ -48,6 +48,8 @@ data RunOpts = RunOpts
, execPathB :: FilePath
, logPathA :: FilePath
, logPathB :: FilePath
, keepInResetA :: Bool
, keepInResetB :: Bool
}

getRunOpts :: Parser RunOpts
Expand All @@ -73,6 +75,14 @@ getRunOpts =
<> long "b-log"
<> help "Path to the log file for CPU B"
)
<*> switch
( long "keep-cpu-a-in-reset"
<> help "Keep CPU A in reset"
)
<*> switch
( long "keep-cpu-b-in-reset"
<> help "Keep CPU B in reset"
)

jtagDaisyChain :: JtagIn -> JtagOut -> JtagIn
jtagDaisyChain (JtagIn tc ms _) (JtagOut to) = JtagIn tc ms to
Expand Down Expand Up @@ -107,16 +117,22 @@ main = do

let
jtagInA = jtagBridge jtagOutB
resetA
| keepInResetA = unsafeFromActiveHigh (pure True)
| otherwise = resetGenN d2
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
| keepInResetB = unsafeFromActiveHigh (pure True)
| otherwise = resetGenN d2
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)
Expand Down
1 change: 1 addition & 0 deletions clash-vexriscv-sim/tests/Tests/Jtag.hs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,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
122 changes: 98 additions & 24 deletions clash-vexriscv-sim/tests/Tests/JtagChain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
--
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE NamedFieldPuns #-}

module Tests.JtagChain where

Expand Down Expand Up @@ -32,42 +33,36 @@ getSimulateExecPath = cabalListBin "clash-vexriscv-sim:clash-vexriscv-chain-bin"
getProjectRoot :: IO FilePath
getProjectRoot = findParentContaining cabalProject

test ::
(HasCallStack) =>
-- | Print debug output of subprocesses
Bool ->
Assertion
test debug = do
data Args = Args
{ vexRiscvProc :: CreateProcess
, openOcdProc :: CreateProcess
, gdbProcA :: CreateProcess
, gdbProcB :: CreateProcess
, logPathA :: FilePath
, logPathB :: FilePath
}

createArgs :: IO Args
createArgs = do
simulateExecPath <- getSimulateExecPath
projectRoot <- getProjectRoot
gdb <- getGdb

let
rBD = rustBinsDir projectRoot "riscv32imc-unknown-none-elf" Debug
printAElfPath = rBD </> "print_a"
logAPath = projectRoot </> "cpu_a.log"
logPathA = projectRoot </> "cpu_a.log"
printBElfPath = rBD </> "print_b"
logBPath = projectRoot </> "cpu_b.log"
logPathB = projectRoot </> "cpu_b.log"
simDataDir = projectRoot </> "clash-vexriscv-sim" </> "data"
openocdCfgPath = simDataDir </> "vexriscv_chain_sim.cfg"
gdbCmdPathA = simDataDir </> "vexriscv_chain_gdba.cfg"
gdbCmdPathB = simDataDir </> "vexriscv_chain_gdbb.cfg"
gdb <- getGdb

ensureExists logAPath
ensureExists logBPath

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

waitForLine :: HasCallStack => Bool -> Handle -> String -> Assertion
waitForLine = waitForLineOrTimeout 120_000_000

vexRiscvProc =
( proc
simulateExecPath
["-a", printAElfPath, "-b", printBElfPath, "-A", logAPath, "-B", logBPath]
["-a", printAElfPath, "-b", printBElfPath, "-A", logPathA, "-B", logPathB]
)
{ std_out = CreatePipe
, cwd = Just projectRoot
Expand All @@ -91,14 +86,46 @@ test debug = do
, std_out = CreatePipe
}

withStreamingFiles (logAPath :> logBPath :> Nil) $ \(vecToTuple -> (logA, logB)) -> do
ensureExists logPathA
ensureExists logPathB

pure $ Args
{ vexRiscvProc
, openOcdProc
, gdbProcA
, gdbProcB
, logPathA
, logPathB
}

testBoth ::
(HasCallStack) =>
-- | Print debug output of subprocesses
Bool ->
Assertion
testBoth debug = do
let
-- Timeout after 120 seconds. Warning: removing the type signature breaks
-- stack traces.
expectLine :: HasCallStack => Bool -> Handle -> String -> Assertion
expectLine = expectLineOrTimeout 120_000_000

waitForLine :: HasCallStack => Bool -> Handle -> String -> Assertion
waitForLine = waitForLineOrTimeout 120_000_000

Args{vexRiscvProc, openOcdProc, gdbProcA, gdbProcB, logPathA, logPathB} <- createArgs

withStreamingFiles (logPathA :> logPathB :> Nil) $ \(vecToTuple -> (logA, logB)) -> do
withCreateProcess vexRiscvProc $ \_ (fromJust -> simStdOut) _ _ -> do
waitForLine debug simStdOut "JTAG bridge ready at port 7894"

expectLine debug logA "[CPU] a"
expectLine debug logB "[CPU] b"

withCreateProcess openOcdProc $ \_ _ (fromJust -> openOcdStdErr) _ -> do

waitForLine debug openOcdStdErr "[riscv.cpu0] Target successfully examined."
waitForLine debug openOcdStdErr "[riscv.cpu1] Target successfully examined."
waitForLine debug openOcdStdErr "Halting processor"

withCreateProcess gdbProcA $ \_ _ _ gdbProcHandleA -> do
Expand All @@ -124,6 +151,52 @@ withStreamingFiles paths f = go (toList paths) []
go [] hs = f (unsafeFromList (reverse hs))
go (p : ps) hs = withStreamingFile p (\h -> go ps (h : hs))

addArgs :: CreateProcess -> [String] -> CreateProcess
addArgs cp newArgs = cp{cmdspec = addArgsCmdSpec (cmdspec cp)}
where
addArgsCmdSpec (RawCommand cmd args) = RawCommand cmd (args <> newArgs)
addArgsCmdSpec (ShellCommand cmd) = ShellCommand (cmd <> " " <> unwords newArgs)


testInResetA ::
HasCallStack =>
-- | Print debug output of subprocesses
Bool ->
Assertion
testInResetA debug = do
Args{vexRiscvProc, openOcdProc, gdbProcB, logPathB} <- createArgs

let
-- Timeout after 240 seconds. These tests are extremely slow, because a lot
-- of bandwidth is reserved to keep examining the CPU in reset (?).
--
-- Warning: removing the type signature breaks stack traces.
expectLine :: HasCallStack => Bool -> Handle -> String -> Assertion
expectLine = expectLineOrTimeout 240_000_000

waitForLine :: HasCallStack => Bool -> Handle -> String -> Assertion
waitForLine = waitForLineOrTimeout 240_000_000

let vexRiscvProc1 = addArgs vexRiscvProc ["--keep-cpu-a-in-reset"]

withStreamingFile logPathB $ \logB -> do
withCreateProcess vexRiscvProc1 $ \_ (fromJust -> simStdOut) _ _ -> do
waitForLine debug simStdOut "JTAG bridge ready at port 7894"

expectLine debug logB "[CPU] b"

withCreateProcess openOcdProc $ \_ _ (fromJust -> openOcdStdErr) _ -> do
waitForLine debug openOcdStdErr "Error: [riscv.cpu0] Examination failed"
waitForLine debug openOcdStdErr "[riscv.cpu1] Target successfully examined."
waitForLine debug openOcdStdErr "Halting processor"

withCreateProcess gdbProcB $ \_ _ _ gdbProcHandleB -> do
expectLine debug logB "[CPU] b"
expectLine debug logB "[CPU] a"

gdbBExitCode <- waitForProcess gdbProcHandleB
ExitSuccess @=? gdbBExitCode

ensureExists :: (HasCallStack) => FilePath -> IO ()
ensureExists path = unlessM (doesPathExist path) (withFile path WriteMode (\_ -> pure ()))

Expand All @@ -134,7 +207,8 @@ tests :: (HasCallStack) => TestTree
tests = askOption $ \(JtagDebug debug) ->
testGroup
"JTAG chaining"
[ testCase "Basic GDB commands, breakpoints, and program loading" (test debug)
[ testCase "Basic GDB commands, breakpoints, and program loading" (testBoth debug)
, testCase "Program loading with CPU A held in reset" (testInResetA debug)
]

main :: IO ()
Expand Down

0 comments on commit 85cbaeb

Please sign in to comment.