Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
fault locators working
Browse files Browse the repository at this point in the history
  • Loading branch information
ekiwi committed Sep 14, 2023
1 parent 3b00782 commit 15f987d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 30 deletions.
15 changes: 7 additions & 8 deletions src/main/scala/chiseltest/internal/AccessCheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ private class AccessCheck(design: DesignInfo, topFileName: Option[String], teste
}
)

private def orderError(message: String): Nothing = {
throw ExceptionUtils.createThreadOrderDependentException(topFileName, message)
private def orderError(threadInfo: ThreadInfoProvider, message: String): Nothing = {
throw ExceptionUtils.createThreadOrderDependentException(threadInfo, topFileName, message)
}

def pokeBits(threadInfo: ThreadInfoProvider, signal: Data, value: BigInt): Unit = {
Expand All @@ -75,18 +75,16 @@ private class AccessCheck(design: DesignInfo, topFileName: Option[String], teste
// check for conflicting pokes
if (hasConflictingAccess(info, threadInfo)) {
if (info.lastAccessWasPoke) {
orderError("Conflicting pokes!") // TODO: better message
orderError(threadInfo, "Conflicting pokes!") // TODO: better message
} else {
orderError("Conflicting peek!") // TODO: better message
orderError(threadInfo, "Conflicting peek!") // TODO: better message
}
}
// have any of the signals that are influences by this input been peeked?
info.dependedOnBy.foreach { id =>
val info = idToSignal(id)
if (hasConflictingAccess(info, threadInfo) && !info.lastAccessWasPoke) {
orderError(
"Conflicting peek of a signal that may change with this poke!"
) // TODO: better message
orderError(threadInfo, "Conflicting peek of a signal that may change with this poke!") // TODO: better message
}
}

Expand Down Expand Up @@ -118,6 +116,7 @@ private class AccessCheck(design: DesignInfo, topFileName: Option[String], teste
// has this signal been poked?
if (hasConflictingAccess(info, threadInfo) && info.lastAccessWasPoke) {
orderError(
threadInfo,
s"Conflicting poke on signal `${info.chiselName}` that is being peeked!"
) // TODO: better message
}
Expand All @@ -126,7 +125,7 @@ private class AccessCheck(design: DesignInfo, topFileName: Option[String], teste
info.dependsOn.foreach { id =>
val info = idToSignal(id)
if (hasConflictingAccess(info, threadInfo) && info.lastAccessWasPoke) {
orderError("Conflicting poke that influences peek!") // TODO: better message
orderError(threadInfo, "Conflicting poke that influences peek!") // TODO: better message
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/main/scala/chiseltest/internal/Scheduler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ private trait ThreadInfoProvider {
def getActiveThreadId: Int
def getActiveThreadPriority: Int
def getStepCount: Int
def isParentOf(id: Int, childId: Int): Boolean
def isParentOf(id: Int, childId: Int): Boolean
def getParent(id: Int): Option[Int]
def getThreadStackTrace(id: Int): Seq[StackTraceElement]
}

/** Manages multiple Java threads that all interact with the same simulation and step synchronously. Currently only
Expand Down Expand Up @@ -89,12 +91,14 @@ private class Scheduler(simulationStep: (Int, Int) => Int) extends ThreadInfoPro

/** all threads */
private val threads = new mutable.ArrayBuffer[ThreadInfo]()
threads.addOne(new ThreadInfo(MainThreadId, "main", 0, None, ThreadActive, new Semaphore(0)))
threads.addOne(new ThreadInfo(MainThreadId, "main", 0, Some(Thread.currentThread()), ThreadActive, new Semaphore(0)))

/** order in which threads are scheduled */
private val threadOrder = new ThreadOrder
private def threadsInSchedulerOrder = threadOrder.getOrder.map(threads(_))
override def isParentOf(id: Int, childId: Int) = threadOrder.isParentOf(id, childId)
override def isParentOf(id: Int, childId: Int) = threadOrder.isParentOf(id, childId)
override def getParent(id: Int): Option[Int] = threadOrder.getParent(id)
override def getThreadStackTrace(id: Int): Seq[StackTraceElement] = threads(id).underlying.get.getStackTrace.toSeq

/** Keep track of global simulation time. */
private var currentStep: Int = 0
Expand Down
55 changes: 36 additions & 19 deletions src/main/scala/chiseltest/internal/SimController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package chiseltest.internal

import chisel3.{Clock, Data, Module}
import chiseltest._
import chiseltest.{FailedExpectException, _}
import chiseltest.coverage.TestCoverage
import chiseltest.simulator.SimulatorContext
import firrtl2.AnnotationSeq
Expand All @@ -26,7 +26,7 @@ private[chiseltest] class SimController[T <: Module](

/** Used by package.scala to communicate a failed `expect` */
def failedExpect(message: String): Unit = {
throw ExceptionUtils.createExpectFailureException(topFileName, message)
throw ExceptionUtils.createExpectFailureException(scheduler, topFileName, message)
}

private val scheduler = new Scheduler(ioAccess.simulationStep)
Expand Down Expand Up @@ -87,42 +87,59 @@ private[chiseltest] class SimController[T <: Module](
}

private object ExceptionUtils {
private def getExpectDetailedTrace(trace: Seq[StackTraceElement], inFile: String): String = {
val lineNumbers = trace.collect {
case ste if ste.getFileName == inFile => ste.getLineNumber
}.mkString(", ")
if (lineNumbers.isEmpty) {
s" (no lines in $inFile)"
} else {
s" (lines in $inFile: $lineNumbers)"
private def getFaultLocation(topFileName: Option[String], trace: Seq[StackTraceElement]): Option[String] =
topFileName.flatMap { inFile =>
trace.collectFirst {
case ste if ste.getFileName == inFile =>
s" at ($inFile:${ste.getLineNumber})"
}
}

/** Searches through all parent threads to find a fault location. Returns the first one from the bottom. */
private def getParentFaultLocation(threadInfo: ThreadInfoProvider, topFileName: Option[String], id: Int)
: Option[String] = {
threadInfo.getParent(id) match {
case Some(parentId) =>
val trace = threadInfo.getThreadStackTrace(parentId)
getFaultLocation(topFileName, trace)
.map(Some(_))
.getOrElse(getParentFaultLocation(threadInfo, topFileName, parentId))
case None => None
}

}

private def findFailureLocationAndStackIndex(topFileName: Option[String], entryPoints: Set[String]): (String, Int) = {
val trace = (new Throwable).getStackTrace
private def findFailureLocationAndStackIndex(
threadInfo: ThreadInfoProvider,
topFileName: Option[String],
entryPoints: Set[String]
): (String, Int) = {
val trace = Thread.currentThread().getStackTrace.toSeq
val entryStackDepth = trace
.indexWhere(ste => ste.getClassName.startsWith("chiseltest.package$") && entryPoints.contains(ste.getMethodName))
require(
entryStackDepth != -1,
s"Failed to find $entryPoints in stack trace:\r\n${trace.mkString("\r\n")}"
)

val trimmedTrace = trace.drop(entryStackDepth)
val failureLocation: String = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("")
val stackIndex = entryStackDepth + 1
val failureLocation: String = getFaultLocation(topFileName, trace)
.getOrElse(getParentFaultLocation(threadInfo, topFileName, threadInfo.getActiveThreadId).getOrElse(""))
val stackIndex = entryStackDepth - 1
(failureLocation, stackIndex)
}

/** Creates a FailedExpectException with correct stack trace to the failure. */
def createExpectFailureException(topFileName: Option[String], message: String): FailedExpectException = {
val (failureLocation, stackIndex) = findFailureLocationAndStackIndex(topFileName, ExpectEntryPoint)
def createExpectFailureException(threadInfo: ThreadInfoProvider, topFileName: Option[String], message: String)
: FailedExpectException = {
val (failureLocation, stackIndex) = findFailureLocationAndStackIndex(threadInfo, topFileName, ExpectEntryPoint)
new FailedExpectException(message + failureLocation, stackIndex)
}
private val ExpectEntryPoint = Set("expect", "expectPartial")

def createThreadOrderDependentException(topFileName: Option[String], message: String)
def createThreadOrderDependentException(threadInfo: ThreadInfoProvider, topFileName: Option[String], message: String)
: ThreadOrderDependentException = {
val (failureLocation, stackIndex) = findFailureLocationAndStackIndex(topFileName, ExpectPeekPokeEntryPoint)
val (failureLocation, stackIndex) =
findFailureLocationAndStackIndex(threadInfo, topFileName, ExpectPeekPokeEntryPoint)
new ThreadOrderDependentException(message + failureLocation, stackIndex)
}
private val ExpectPeekPokeEntryPoint = Set("expect", "expectPartial", "peek", "peekInt", "peekBoolean", "poke")
Expand Down

0 comments on commit 15f987d

Please sign in to comment.