Skip to content

Commit

Permalink
simulator: attempt to extract source line.
Browse files Browse the repository at this point in the history
  • Loading branch information
kivikakk committed Jun 5, 2024
1 parent 2d58c7e commit f507170
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 29 deletions.
46 changes: 23 additions & 23 deletions core/src/main/scala/chisel3/internal/Error.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ object ExceptionHelpers {
def ellipsis(message: Option[String] = None): StackTraceElement =
new StackTraceElement("..", " ", message.getOrElse(""), -1)

private[chisel3] def getErrorLineInFile(sourceRoots: Seq[File], sl: SourceLine): List[String] = {
def tryFileInSourceRoot(sourceRoot: File): Option[List[String]] = {
try {
val file = new File(sourceRoot, sl.filename)
val lines = Source.fromFile(file).getLines()
var i = 0
while (i < (sl.line - 1) && lines.hasNext) {
lines.next()
i += 1
}
val line = lines.next()
val caretLine = (" " * (sl.col - 1)) + "^"
Some(line :: caretLine :: Nil)
} catch {
case scala.util.control.NonFatal(_) => None
}
}
val sourceRootsWithDefault = if (sourceRoots.nonEmpty) sourceRoots else Seq(new File("."))
// View allows us to search the directories one at a time and early out
sourceRootsWithDefault.view.map(tryFileInSourceRoot(_)).collectFirst { case Some(value) => value }.getOrElse(Nil)
}

/** Utility methods that can be added to exceptions.
*/
implicit class ThrowableHelpers(throwable: Throwable) {
Expand Down Expand Up @@ -254,28 +276,6 @@ private[chisel3] class ErrorLog(
throwOnFirstError: Boolean) {
import ErrorLog.withColor

private def getErrorLineInFile(sl: SourceLine): List[String] = {
def tryFileInSourceRoot(sourceRoot: File): Option[List[String]] = {
try {
val file = new File(sourceRoot, sl.filename)
val lines = Source.fromFile(file).getLines()
var i = 0
while (i < (sl.line - 1) && lines.hasNext) {
lines.next()
i += 1
}
val line = lines.next()
val caretLine = (" " * (sl.col - 1)) + "^"
Some(line :: caretLine :: Nil)
} catch {
case scala.util.control.NonFatal(_) => None
}
}
val sourceRootsWithDefault = if (sourceRoots.nonEmpty) sourceRoots else Seq(new File("."))
// View allows us to search the directories one at a time and early out
sourceRootsWithDefault.view.map(tryFileInSourceRoot(_)).collectFirst { case Some(value) => value }.getOrElse(Nil)
}

/** Returns an appropriate location string for the provided source info.
* If the source info is of `NoSourceInfo` type, the source location is looked up via stack trace.
* If the source info is `None`, an empty string is returned.
Expand All @@ -292,7 +292,7 @@ private[chisel3] class ErrorLog(
// id is optional because it has only been applied to warnings, TODO apply to errors
private def logWarningOrError(msg: String, si: Option[SourceInfo], isFatal: Boolean): Unit = {
val location = errorLocationString(si)
val sourceLineAndCaret = si.collect { case sl: SourceLine => getErrorLineInFile(sl) }.getOrElse(Nil)
val sourceLineAndCaret = si.collect { case sl: SourceLine => ExceptionHelpers.getErrorLineInFile(sourceRoots, sl) }.getOrElse(Nil)
val fullMessage = if (location.isEmpty) msg else s"$location: $msg"
val errorLines = fullMessage :: sourceLineAndCaret
val entry = ErrorEntry(errorLines, isFatal)
Expand Down
24 changes: 19 additions & 5 deletions src/main/scala/chisel3/simulator/PeekPokeAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package chisel3.simulator
import svsim._
import chisel3._

import chisel3.experimental.SourceInfo
import chisel3.experimental.{SourceInfo, SourceLine}
import chisel3.internal.ExceptionHelpers

object PeekPokeAPI extends PeekPokeAPI

trait PeekPokeAPI {
case class FailedExpectationException[T](sourceInfo: SourceInfo, observed: T, expected: T, message: String)
extends Exception(s"Failed Expectation: Observed value '$observed' != $expected. $message ${sourceInfo.makeMessage(x => x)}")
case class FailedExpectationException[T](sourceInfo: SourceInfo, extraContext: Seq[String], observed: T, expected: T, message: String)
extends Exception(
s"Failed Expectation: Observed value '$observed' != $expected. " +
s"$message ${sourceInfo.makeMessage(x => x)}" +
(if (extraContext.nonEmpty) s"\n${extraContext.mkString("\n")}" else "")
)

implicit class testableClock(clock: Clock) {
def step(cycles: Int = 1): Unit = {
Expand Down Expand Up @@ -134,10 +139,19 @@ trait PeekPokeAPI {
val module = AnySimulatedModule.current
module.willPeek()
val simulationPort = module.port(data)

simulationPort.check(isSigned = isSigned) { observedValue =>
val observed = encode(observedValue)
if (observed != expected)
throw FailedExpectationException(sourceInfo, observed, expected, buildMessage(observed, expected))
if (observed != expected) {
val extraContext =
sourceInfo match {
case sl: SourceLine =>
ExceptionHelpers.getErrorLineInFile(Seq(), sl)
case _ =>
Seq()
}
throw FailedExpectationException(sourceInfo, extraContext, observed, expected, buildMessage(observed, expected))
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/test/scala/chiselTests/simulator/SimulatorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class SimulatorSpec extends AnyFunSpec with Matchers {

it("reports failed expects correctly") {
val simulator = new VerilatorSimulator("test_run_dir/simulator/GCDSimulator")
a [PeekPokeAPI.FailedExpectationException[_]] must be thrownBy {
val thrown = the [PeekPokeAPI.FailedExpectationException[_]] thrownBy {
simulator
.simulate(new GCD()) { module =>
import PeekPokeAPI._
Expand All @@ -87,6 +87,11 @@ class SimulatorSpec extends AnyFunSpec with Matchers {
gcd.io.result.expect(5)
}.result
}
thrown.getMessage must include regex "Observed value '12' != 5\\."
thrown.getMessage must include regex " @\\[src/test/scala/chiselTests/simulator/SimulatorSpec\\.scala \\d+:\\d+\\]"
thrown.getMessage must include regex "gcd\\.io\\.result\\.expect\\(5\\)"
// Not actually anchoring this so it's not extremely brittle, only somewhat.
thrown.getMessage must include regex " \\^"
}

it("runs a design that includes an external module") {
Expand Down

0 comments on commit f507170

Please sign in to comment.