// SPDX-License-Identifier: Apache-2.0 package chiselTests.stage import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.TesterDriver.createTestDirectory import chiselTests.Utils import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import firrtl.options.Dependency object ChiselStageSpec { class Bar extends Module { val in = IO(Input(UInt(4.W))) val out = IO(Output(UInt(4.W))) out := ~in } class Foo extends Module { val addr = IO(Input(UInt(4.W))) val out = IO(Output(Bool())) val memory = SyncReadMem(8, Bool()) val bar = Module(new Bar) bar.in := addr out := memory(bar.out) } class UserExceptionModule extends RawModule { assert(false, "User threw an exception") } class UserExceptionNoStackTrace extends RawModule { throw new Exception("Something bad happened") with scala.util.control.NoStackTrace } class RecoverableError extends RawModule { 3.U >> -1 } } class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { import ChiselStageSpec._ private trait ChiselStageFixture { val stage = new ChiselStage } behavior.of("ChiselStage$.emitChirrtl") it should "return a CHIRRTL string" in { ChiselStage.emitChirrtl(new Foo) should include("infer mport") } behavior.of("ChiselStage$.emitFirrtl") it should "return a High FIRRTL string" in { ChiselStage.emitFirrtl(new Foo) should include("mem memory") } it should "return a flattened FIRRTL string with '-e high'" in { val args = Array("-e", "high", "-td", createTestDirectory(this.getClass.getSimpleName).toString) (new ChiselStage) .emitFirrtl(new Foo, args) should include("module Bar") } behavior.of("ChiselStage$.emitVerilog") it should "return a Verilog string" in { ChiselStage.emitVerilog(new Foo) should include("endmodule") } it should "return a flattened Verilog string with '-e verilog'" in { val args = Array("-e", "verilog", "-td", createTestDirectory(this.getClass.getSimpleName).toString) (new ChiselStage) .emitVerilog(new Foo, args) should include("module Bar") } behavior.of("ChiselStage$.elaborate") ignore should "generate a Chisel circuit from a Chisel module" in { info("no files were written") catchWrites { ChiselStage.elaborate(new Foo) } shouldBe a[Right[_, _]] } behavior.of("ChiselStage$.convert") ignore should "generate a CHIRRTL circuit from a Chisel module" in { info("no files were written") catchWrites { ChiselStage.convert(new Foo) } shouldBe a[Right[_, _]] } ignore should "generate a FIRRTL circuit from a CHIRRTL circuit" in { info("no files were written") catchWrites { ChiselStage.convert(ChiselStage.elaborate(new Foo)) } shouldBe a[Right[_, _]] } behavior.of("ChiselStage$.emitChirrtl") ignore should "generate a CHIRRTL string from a Chisel module" in { val wrapped = catchWrites { ChiselStage.emitChirrtl(new Foo) } info("no files were written") wrapped shouldBe a[Right[_, _]] info("returned string looks like FIRRTL") wrapped.right.get should include("circuit") } behavior.of("ChiselStage$.emitFirrtl") ignore should "generate a FIRRTL string from a Chisel module" in { val wrapped = catchWrites { ChiselStage.emitFirrtl(new Foo) } info("no files were written") wrapped shouldBe a[Right[_, _]] info("returned string looks like FIRRTL") wrapped.right.get should include("circuit") } behavior.of("ChiselStage$.emitVerilog") ignore should "generate a Verilog string from a Chisel module" in { val wrapped = catchWrites { ChiselStage.emitVerilog(new Foo) } info("no files were written") wrapped shouldBe a[Right[_, _]] info("returned string looks like Verilog") wrapped.right.get should include("endmodule") } behavior.of("ChiselStage$.emitSystemVerilog") ignore should "generate a SystemvVerilog string from a Chisel module" in { val wrapped = catchWrites { ChiselStage.emitSystemVerilog(new Foo) } info("no files were written") wrapped shouldBe a[Right[_, _]] info("returned string looks like Verilog") wrapped.right.get should include("endmodule") } behavior.of("ChiselStage phase ordering") it should "only run elaboration once" in new ChiselStageFixture { info("Phase order is:\n" + stage.phaseManager.prettyPrint(" ")) val order = stage.phaseManager.flattenedTransformOrder.map(Dependency.fromTransform) info("Elaborate only runs once") exactly(1, order) should be(Dependency[chisel3.stage.phases.Elaborate]) } behavior.of("ChiselStage$ exception handling") it should "truncate a user exception" in { info("The user's java.lang.AssertionError was thrown") val exception = intercept[java.lang.AssertionError] { ChiselStage.emitChirrtl(new UserExceptionModule) } val message = exception.getMessage info("The exception includes the user's message") message should include("User threw an exception") info("The stack trace is trimmed") (exception.getStackTrace.mkString("\n") should not).include("java") } it should "NOT add a stack trace to an exception with no stack trace" in { val exception = intercept[java.lang.Exception] { ChiselStage.emitChirrtl(new UserExceptionNoStackTrace) } val message = exception.getMessage info("The exception includes the user's message") message should include("Something bad happened") info("The exception should not contain a stack trace") exception.getStackTrace should be(Array()) } it should "NOT include a stack trace for recoverable errors" in { val exception = intercept[java.lang.Exception] { ChiselStage.emitChirrtl(new RecoverableError) } val message = exception.getMessage info("The exception includes the standard error message") message should include("Fatal errors during hardware elaboration. Look above for error list.") info("The exception should not contain a stack trace") exception.getStackTrace should be(Array()) } behavior.of("ChiselStage exception handling") it should "truncate a user exception" in { info("The user's java.lang.AssertionError was thrown") val exception = intercept[java.lang.AssertionError] { (new ChiselStage).emitChirrtl(new UserExceptionModule) } info(s""" - Exception was a ${exception.getClass.getName}""") val message = exception.getMessage info("The exception includes the user's message") message should include("User threw an exception") val stackTrace = exception.getStackTrace.mkString("\n") info("The stack trace is trimmed") (stackTrace should not).include("java") info("The stack trace include information about running --full-stacktrace") stackTrace should include("--full-stacktrace") } it should """not truncate a user exception with "--full-stacktrace"""" in { info("The user's java.lang.AssertionError was thrown") val exception = intercept[java.lang.AssertionError] { (new ChiselStage).emitChirrtl(new UserExceptionModule, Array("--full-stacktrace")) } info(s""" - Exception was a ${exception.getClass.getName}""") val message = exception.getMessage info("The exception includes the user's message") message should include("User threw an exception") info("The stack trace is not trimmed") exception.getStackTrace.mkString("\n") should include("java") } it should "NOT add a stack trace to an exception with no stack trace" in { val exception = intercept[java.lang.Exception] { (new ChiselStage).emitChirrtl(new UserExceptionNoStackTrace) } val message = exception.getMessage info("The exception includes the user's message") message should include("Something bad happened") info("The exception should not contain a stack trace") exception.getStackTrace should be(Array()) } it should "NOT include a stack trace for recoverable errors" in { val exception = intercept[java.lang.Exception] { (new ChiselStage).emitChirrtl(new RecoverableError) } val message = exception.getMessage info("The exception includes the standard error message") message should include("Fatal errors during hardware elaboration. Look above for error list.") info("The exception should not contain a stack trace") exception.getStackTrace should be(Array()) } it should "include a stack trace for recoverable errors with '--throw-on-first-error'" in { val exception = intercept[java.lang.Exception] { (new ChiselStage).emitChirrtl(new RecoverableError, Array("--throw-on-first-error")) } val stackTrace = exception.getStackTrace.mkString("\n") info("The exception should contain a truncated stack trace") stackTrace shouldNot include("java") info("The stack trace include information about running --full-stacktrace") stackTrace should include("--full-stacktrace") } it should "include an untruncated stack trace for recoverable errors when given both '--throw-on-first-error' and '--full-stacktrace'" in { val exception = intercept[java.lang.Exception] { val args = Array("--throw-on-first-error", "--full-stacktrace") (new ChiselStage).emitChirrtl(new RecoverableError, args) } val stackTrace = exception.getStackTrace.mkString("\n") info("The exception should contain a truncated stack trace") stackTrace should include("java") } }