From bd2d6d7eeef7ff017a0ac547403b05e1e41f4c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Kondi=C4=87?= Date: Mon, 17 Jul 2023 17:20:40 +0200 Subject: [PATCH 1/3] Use fixedpoint library and bump Scala version to 2.13.10 --- .github/workflows/test.yml | 2 ++ .gitmodules | 3 ++ build.sbt | 30 +++++++++++++--- doc/Example.md | 4 +-- fixedpoint | 1 + .../main/scala/jtag2mm/TestMultiplexer.scala | 5 +-- .../numbers/chisel_concrete/DspComplex.scala | 9 +++-- .../chisel_types/DspComplexTypeClass.scala | 4 +-- .../chisel_types/DspRealTypeClass.scala | 5 ++- .../chisel_types/FixedPointTypeClass.scala | 35 +++++++++---------- .../numbers/chisel_types/SIntTypeClass.scala | 4 +-- .../numbers/chisel_types/UIntTypeClass.scala | 4 +-- .../ChiselConvertableFrom.scala | 2 +- .../dsptools/numbers/implicits/AllOps.scala | 5 ++- .../numbers/implicits/ImplicitsTop.scala | 2 -- .../dsptools/numbers/rounding/Saturate.scala | 5 +-- 16 files changed, 73 insertions(+), 47 deletions(-) create mode 100644 .gitmodules create mode 160000 fixedpoint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index de8a105f..3ec9ac17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + with: + submodules: 'true' - name: Setup Scala uses: olafurpg/setup-scala@v10 - name: Cache diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e60bee65 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "fixedpoint"] + path = fixedpoint + url = https://github.com/ucb-bar/fixedpoint diff --git a/build.sbt b/build.sbt index 90fed8d4..aa2850b0 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,6 @@ enablePlugins(SiteScaladocPlugin) enablePlugins(GhpagesPlugin) val defaultVersions = Map( - "chisel-iotesters" -> "2.5-SNAPSHOT", "chisel3" -> "3.5-SNAPSHOT", ) @@ -16,9 +15,8 @@ val commonSettings = Seq( version := "1.5-SNAPSHOT", git.remoteRepo := "git@github.com:ucb-bar/dsptools.git", autoAPIMappings := true, - scalaVersion := "2.12.14", - crossScalaVersions := Seq("2.13.6", "2.12.14"), - scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-language:reflectiveCalls"), + scalaVersion := "2.13.10", + scalacOptions ++= Seq("-encoding", "UTF-8", "-unchecked", "-deprecation", "-feature", "-language:reflectiveCalls", "-Ymacro-annotations"), javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), pomExtra := (http://chisel.eecs.berkeley.edu/ @@ -65,7 +63,7 @@ val commonSettings = Seq( case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.3") } }, - libraryDependencies ++= Seq("chisel-iotesters").map { dep: String => + libraryDependencies ++= Seq("chisel3").map { dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) }, addCompilerPlugin("edu.berkeley.cs" %% "chisel3-plugin" % defaultVersions("chisel3") cross CrossVersion.full), @@ -80,12 +78,34 @@ val dsptoolsSettings = Seq( ), ) +val fixedpointSettings = Seq( + libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.2.+" % "test", + "org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % "test", + ) +) + publishMavenStyle := true publishArtifact in Test := false pomIncludeRepository := { x => false } +def freshProject(name: String, dir: File): Project = { + Project(id = name, base = dir / "src") + .settings( + Compile / scalaSource := baseDirectory.value / "main" / "scala", + Compile / resourceDirectory := baseDirectory.value / "main" / "resources" + ) +} + +lazy val fixedpoint = freshProject("fixedpoint", file("fixedpoint")) + .settings( + commonSettings, + fixedpointSettings + ) + val dsptools = (project in file(".")) + .dependsOn(fixedpoint) //.enablePlugins(BuildInfoPlugin) .enablePlugins(ScalaUnidocPlugin) .settings(commonSettings: _*) diff --git a/doc/Example.md b/doc/Example.md index f839a48b..64cdf21b 100644 --- a/doc/Example.md +++ b/doc/Example.md @@ -6,9 +6,9 @@ A basic DSP Module + Tester might look like this: package SimpleDsp // Allows you to use Chisel Module, Bundle, etc. -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} // Allows you to use FixedPoint -import chisel3.experimental.FixedPoint +import fixedpoint._ // If you want to take advantage of type classes >> Data:RealBits (i.e. pass in FixedPoint or DspReal) // Required for you to use operators defined via type classes (+ has special Dsp overflow behavior, etc.) import dsptools.numbers._ diff --git a/fixedpoint b/fixedpoint new file mode 160000 index 00000000..4e69127e --- /dev/null +++ b/fixedpoint @@ -0,0 +1 @@ +Subproject commit 4e69127e1897c8e3ca809748cc9491597cdfcc06 diff --git a/rocket/src/main/scala/jtag2mm/TestMultiplexer.scala b/rocket/src/main/scala/jtag2mm/TestMultiplexer.scala index d2204db6..d2f19b4c 100644 --- a/rocket/src/main/scala/jtag2mm/TestMultiplexer.scala +++ b/rocket/src/main/scala/jtag2mm/TestMultiplexer.scala @@ -2,9 +2,10 @@ package freechips.rocketchip.jtag2mm -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} import chisel3.util._ -import chisel3.experimental._ +import chisel3.experimental.{FixedPoint => _, _} +import fixedpoint._ //import chisel3.experimental.{withClockAndReset} import dsptools._ diff --git a/src/main/scala/dsptools/numbers/chisel_concrete/DspComplex.scala b/src/main/scala/dsptools/numbers/chisel_concrete/DspComplex.scala index a8fe4a7e..924beece 100644 --- a/src/main/scala/dsptools/numbers/chisel_concrete/DspComplex.scala +++ b/src/main/scala/dsptools/numbers/chisel_concrete/DspComplex.scala @@ -2,12 +2,14 @@ package dsptools.numbers -import chisel3._ -import chisel3.experimental.FixedPoint +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ import dsptools.DspException import breeze.math.Complex import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor +import scala.reflect.ClassTag + object DspComplex { def apply[T <: Data:Ring](gen: T): DspComplex[T] = { @@ -60,7 +62,8 @@ object DspComplex { } -class DspComplex[T <: Data:Ring](val real: T, val imag: T) extends Bundle { +class DspComplex[T <: Data:Ring](val real: T, val imag: T)(implicit val ct: ClassTag[DspComplex[T]]) extends Bundle + with ForceElementwiseConnect[DspComplex[T]] { // So old DSP code doesn't break def imaginary(dummy: Int = 0): T = imag diff --git a/src/main/scala/dsptools/numbers/chisel_types/DspComplexTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/DspComplexTypeClass.scala index 10c0285a..1449863b 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/DspComplexTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/DspComplexTypeClass.scala @@ -2,8 +2,8 @@ package dsptools.numbers -import chisel3._ -import chisel3.experimental.FixedPoint +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ import dsptools.hasContext import implicits._ import chisel3.util.ShiftRegister diff --git a/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala index a7380ff6..c2d48946 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/DspRealTypeClass.scala @@ -2,11 +2,10 @@ package dsptools.numbers -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} import chisel3.util.{Cat, ShiftRegister} import dsptools.{DspContext, NoTrim, hasContext} -import chisel3.experimental.FixedPoint -import chisel3.internal.firrtl.KnownBinaryPoint +import fixedpoint._ import scala.language.implicitConversions diff --git a/src/main/scala/dsptools/numbers/chisel_types/FixedPointTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/FixedPointTypeClass.scala index 63b10ed3..726ca6b5 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/FixedPointTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/FixedPointTypeClass.scala @@ -2,9 +2,8 @@ package dsptools.numbers -import chisel3._ -import chisel3.experimental.FixedPoint -import chisel3.internal.firrtl.KnownBinaryPoint +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ import chisel3.util.ShiftRegister import dsptools._ @@ -70,7 +69,7 @@ trait FixedPointIsReal extends Any with IsReal[FixedPoint] with FixedPointOrder def isWhole(a: FixedPoint): Bool = a === floor(a) // Truncate = round towards zero (integer part without fractional bits) def truncate(a: FixedPoint): FixedPoint = { - Mux(isSignNegative(ShiftRegister(a, context.numAddPipes)), + shadow.Mux(isSignNegative(ShiftRegister(a, context.numAddPipes)), ceil(a), floor(ShiftRegister(a, context.numAddPipes)) ) @@ -156,22 +155,22 @@ trait FixedPointReal extends FixedPointRing with FixedPointIsReal with Convertab case RoundDown => a.setBinaryPoint(b) case RoundUp => { val addAmt = math.pow(2, -b).F(b.BP) // shr(1.0.F(b.BP),b) - Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) + shadow.Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) } case RoundTowardsZero => { val addAmt = math.pow(2, -b).F(b.BP) // shr(1.0.F(b.BP),b) - val valueForNegativeNum = Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) - Mux(isSignNegative(a), valueForNegativeNum, a.setBinaryPoint(b)) + val valueForNegativeNum = shadow.Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) + shadow.Mux(isSignNegative(a), valueForNegativeNum, a.setBinaryPoint(b)) } case RoundTowardsInfinity => { val addAmt = math.pow(2, -b).F(b.BP) // shr(1.0.F(b.BP),b) - val valueForPositiveNum = Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) - Mux(isSignNegative(a), a.setBinaryPoint(b), valueForPositiveNum) + val valueForPositiveNum = shadow.Mux((a === a.setBinaryPoint(b)), a.setBinaryPoint(b), plus(a.setBinaryPoint(b), addAmt)) + shadow.Mux(isSignNegative(a), a.setBinaryPoint(b), valueForPositiveNum) } case RoundHalfDown => { val addAmt1 = math.pow(2, -b).F(b.BP) // shr(1.0.F(b.BP),b) val addAmt2 = math.pow(2, -(b+1)).F((b+1).BP) // shr(1.0.F((b+1).BP),(b+1)) - Mux((a > plus(a.setBinaryPoint(b), addAmt2)), plus(a.setBinaryPoint(b), addAmt1), a.setBinaryPoint(b)) + shadow.Mux((a > plus(a.setBinaryPoint(b), addAmt2)), plus(a.setBinaryPoint(b), addAmt1), a.setBinaryPoint(b)) } case RoundHalfUp => { val roundBp = b + 1 @@ -181,27 +180,27 @@ trait FixedPointReal extends FixedPointRing with FixedPointIsReal with Convertab case RoundHalfTowardsZero => { val addAmt1 = math.pow(2, -b).F(b.BP) // shr(1.0.F(b.BP),b) val addAmt2 = math.pow(2, -(b+1)).F((b+1).BP) // shr(1.0.F((b+1).BP),(b+1)) - val valueForPositiveNum = Mux((a > plus(a.setBinaryPoint(b), addAmt2)), plus(a.setBinaryPoint(b), addAmt1), a.setBinaryPoint(b)) - Mux(isSignNegative(a), plus(a, addAmt2).setBinaryPoint(b), valueForPositiveNum) + val valueForPositiveNum = shadow.Mux((a > plus(a.setBinaryPoint(b), addAmt2)), plus(a.setBinaryPoint(b), addAmt1), a.setBinaryPoint(b)) + shadow.Mux(isSignNegative(a), plus(a, addAmt2).setBinaryPoint(b), valueForPositiveNum) } case RoundHalfTowardsInfinity => { val roundBp = b + 1 val addAmt = math.pow(2, -roundBp).F(roundBp.BP) - Mux(isSignNegative(a) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) + shadow.Mux(isSignNegative(a) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) } case RoundHalfToEven => { require(b > 0, "Binary point of input fixed point number must be larger than zero when trimming") val roundBp = b + 1 val checkIfEvenBp = b - 1 val addAmt = math.pow(2, -roundBp).F(roundBp.BP) - Mux((a.setBinaryPoint(checkIfEvenBp) === a.setBinaryPoint(b)) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) + shadow.Mux((a.setBinaryPoint(checkIfEvenBp) === a.setBinaryPoint(b)) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) } case RoundHalfToOdd => { require(b > 0, "Binary point of input fixed point number must be larger than zero when trimming") val roundBp = b + 1 val checkIfOddBp = b - 1 val addAmt = math.pow(2, -roundBp).F(roundBp.BP) - Mux((a.setBinaryPoint(checkIfOddBp) =/= a.setBinaryPoint(b)) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) + shadow.Mux((a.setBinaryPoint(checkIfOddBp) =/= a.setBinaryPoint(b)) && (a === a.setBinaryPoint(roundBp)), a.setBinaryPoint(b), plus(a, addAmt).setBinaryPoint(b)) } case _ => throw DspException("Desired trim type not implemented!") } @@ -230,7 +229,7 @@ trait FixedPointReal extends FixedPointRing with FixedPointIsReal with Convertab // Can potentially overflow def ceil(a: FixedPoint): FixedPoint = { - Mux( + shadow.Mux( isWhole(ShiftRegister(a, context.numAddPipes)), floor(ShiftRegister(a, context.numAddPipes)), plusContext(floor(a), one)) @@ -247,10 +246,10 @@ trait FixedPointReal extends FixedPointRing with FixedPointIsReal with Convertab override def fromBigInt(n: BigInt): FixedPoint = super[ConvertableToFixedPoint].fromBigInt(n) // Overflow only on most negative def abs(a: FixedPoint): FixedPoint = { - Mux(isSignNegative(a), super[FixedPointRing].minus(zero, a), a) + shadow.Mux(isSignNegative(a), super[FixedPointRing].minus(zero, a), a) } def context_abs(a: FixedPoint): FixedPoint = { - Mux( + shadow.Mux( isSignNegative(ShiftRegister(a, context.numAddPipes)), super[FixedPointRing].minusContext(zero, a), ShiftRegister(a, context.numAddPipes)) diff --git a/src/main/scala/dsptools/numbers/chisel_types/SIntTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/SIntTypeClass.scala index 09057f86..1db6ee7f 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/SIntTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/SIntTypeClass.scala @@ -2,10 +2,10 @@ package dsptools.numbers -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} import chisel3.util.{Cat, ShiftRegister} import dsptools.{DspContext, DspException, Grow, NoTrim, Saturate, Wrap, hasContext} -import chisel3.experimental.FixedPoint +import fixedpoint._ import scala.language.implicitConversions diff --git a/src/main/scala/dsptools/numbers/chisel_types/UIntTypeClass.scala b/src/main/scala/dsptools/numbers/chisel_types/UIntTypeClass.scala index d354c359..ebfebbdc 100644 --- a/src/main/scala/dsptools/numbers/chisel_types/UIntTypeClass.scala +++ b/src/main/scala/dsptools/numbers/chisel_types/UIntTypeClass.scala @@ -2,10 +2,10 @@ package dsptools.numbers -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} import chisel3.util.{Cat, ShiftRegister} import dsptools.{DspContext, DspException, Grow, Saturate, Wrap, hasContext} -import chisel3.experimental.FixedPoint +import fixedpoint._ import scala.language.implicitConversions diff --git a/src/main/scala/dsptools/numbers/convertible_types/ChiselConvertableFrom.scala b/src/main/scala/dsptools/numbers/convertible_types/ChiselConvertableFrom.scala index 56cb5a11..e9c5146d 100644 --- a/src/main/scala/dsptools/numbers/convertible_types/ChiselConvertableFrom.scala +++ b/src/main/scala/dsptools/numbers/convertible_types/ChiselConvertableFrom.scala @@ -2,7 +2,7 @@ package dsptools.numbers -import chisel3.experimental.FixedPoint +import fixedpoint._ import chisel3.{Data, SInt} import dsptools.DspException diff --git a/src/main/scala/dsptools/numbers/implicits/AllOps.scala b/src/main/scala/dsptools/numbers/implicits/AllOps.scala index bd797fd3..5f1487ad 100644 --- a/src/main/scala/dsptools/numbers/implicits/AllOps.scala +++ b/src/main/scala/dsptools/numbers/implicits/AllOps.scala @@ -2,12 +2,11 @@ package dsptools.numbers -import chisel3._ +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} import spire.macros.Ops import scala.language.experimental.macros - -import chisel3.experimental.FixedPoint +import fixedpoint._ /** * Much of this is drawn from non/spire, but using Chisel Bools instead of diff --git a/src/main/scala/dsptools/numbers/implicits/ImplicitsTop.scala b/src/main/scala/dsptools/numbers/implicits/ImplicitsTop.scala index a90897c0..2590ac78 100644 --- a/src/main/scala/dsptools/numbers/implicits/ImplicitsTop.scala +++ b/src/main/scala/dsptools/numbers/implicits/ImplicitsTop.scala @@ -2,8 +2,6 @@ package dsptools.numbers -import chisel3.experimental.FixedPoint - trait AllSyntax extends EqSyntax with PartialOrderSyntax with OrderSyntax with SignedSyntax with IsRealSyntax with IsIntegerSyntax with ConvertableToSyntax with ChiselConvertableFromSyntax with BinaryRepresentationSyntax with ContextualRingSyntax diff --git a/src/main/scala/dsptools/numbers/rounding/Saturate.scala b/src/main/scala/dsptools/numbers/rounding/Saturate.scala index 53f1a4ca..2f42334c 100644 --- a/src/main/scala/dsptools/numbers/rounding/Saturate.scala +++ b/src/main/scala/dsptools/numbers/rounding/Saturate.scala @@ -2,8 +2,9 @@ package dsptools.numbers.rounding -import chisel3._ -import chisel3.experimental.{ChiselAnnotation, FixedPoint, RunFirrtlTransform, annotate, requireIsHardware} +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import chisel3.experimental.{ChiselAnnotation, RunFirrtlTransform, annotate, requireIsHardware} +import fixedpoint._ import chisel3.stage.ChiselStage import firrtl.{CircuitForm, CircuitState, HighForm, MidForm, Transform} import firrtl.annotations.{ModuleName, SingleTargetAnnotation, Target} From a21c46873cded7cf0d14e72deb8fbac84b4f52b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Kondi=C4=87?= Date: Wed, 26 Jul 2023 20:16:23 +0200 Subject: [PATCH 2/3] Add example FixedPoint tests using chiseltest Add trait PeekPokeDspExtensions that is to be mixed in with chiseltest's PeekPokeTester to allow peek, poke, and expect operations with dsptools' types. --- build.sbt | 28 ++- .../dsptools/misc/DspTesterUtilities.scala | 124 +++++++++++ .../dsptools/misc/PeekPokeDspExtensions.scala | 192 ++++++++++++++++++ .../examples/ParameterizedAdderSpec.scala | 64 ++++++ src/test/scala/examples/SimpleAdderSpec.scala | 45 ++++ 5 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 src/main/scala/dsptools/misc/DspTesterUtilities.scala create mode 100644 src/main/scala/dsptools/misc/PeekPokeDspExtensions.scala create mode 100644 src/test/scala/examples/ParameterizedAdderSpec.scala create mode 100644 src/test/scala/examples/SimpleAdderSpec.scala diff --git a/build.sbt b/build.sbt index aa2850b0..e51367ec 100644 --- a/build.sbt +++ b/build.sbt @@ -6,6 +6,7 @@ enablePlugins(GhpagesPlugin) val defaultVersions = Map( "chisel3" -> "3.5-SNAPSHOT", + "chiseltest" -> "0.5-SNAPSHOT" ) name := "dsptools" @@ -16,7 +17,13 @@ val commonSettings = Seq( git.remoteRepo := "git@github.com:ucb-bar/dsptools.git", autoAPIMappings := true, scalaVersion := "2.13.10", - scalacOptions ++= Seq("-encoding", "UTF-8", "-unchecked", "-deprecation", "-feature", "-language:reflectiveCalls", "-Ymacro-annotations"), + scalacOptions ++= Seq("-encoding", + "UTF-8", + "-unchecked", + "-deprecation", + "-feature", + "-language:reflectiveCalls", + "-Ymacro-annotations"), javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), pomExtra := (http://chisel.eecs.berkeley.edu/ @@ -47,26 +54,25 @@ val commonSettings = Seq( val v = version.value val nexus = "https://oss.sonatype.org/" if (v.trim.endsWith("SNAPSHOT")) { - Some("snapshots" at nexus + "content/repositories/snapshots") - } - else { - Some("releases" at nexus + "service/local/staging/deploy/maven2") + Some("snapshots".at(nexus + "content/repositories/snapshots")) + } else { + Some("releases".at(nexus + "service/local/staging/deploy/maven2")) } }, - resolvers ++= Seq ( + resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases") ), libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, major)) if major <= 12 => Seq() - case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.3") + case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.3") } }, - libraryDependencies ++= Seq("chisel3").map { dep: String => + libraryDependencies ++= Seq("chisel3", "chiseltest").map { dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) }, - addCompilerPlugin("edu.berkeley.cs" %% "chisel3-plugin" % defaultVersions("chisel3") cross CrossVersion.full), + addCompilerPlugin(("edu.berkeley.cs" %% "chisel3-plugin" % defaultVersions("chisel3")).cross(CrossVersion.full)), ) val dsptoolsSettings = Seq( @@ -88,7 +94,9 @@ val fixedpointSettings = Seq( publishMavenStyle := true publishArtifact in Test := false -pomIncludeRepository := { x => false } +pomIncludeRepository := { x => + false +} def freshProject(name: String, dir: File): Project = { Project(id = name, base = dir / "src") diff --git a/src/main/scala/dsptools/misc/DspTesterUtilities.scala b/src/main/scala/dsptools/misc/DspTesterUtilities.scala new file mode 100644 index 00000000..e8f330ba --- /dev/null +++ b/src/main/scala/dsptools/misc/DspTesterUtilities.scala @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.misc + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import dsptools.DspException +import dsptools.numbers.{DspComplex, DspReal} +import chisel3.internal.InstanceId + +//scalastyle:off cyclomatic.complexity method.length +object DspTesterUtilities { + + // Converts signed Double's to their 2's complement BigInt equivalents (unsigned) + // (totalWidth, fractionalWidth of some FixedPoint) + def signedToBigIntUnsigned(x: Double, totalWidth: Int, fractionalWidth: Int): BigInt = { + val bi = FixedPoint.toBigInt(x, fractionalWidth) + val neg = bi < 0 + val neededWidth = bi.bitLength + 1 + require(neededWidth <= totalWidth, "Double -> BigInt width larger than total width allocated!") + if (neg) { + (BigInt(1) << totalWidth) + bi + } else { + bi + } + } + + // Redundant from chisel-testers + // Converts unsigned BigInt to signed BigInt (width = width of Chisel data type) + def signConvert(bigInt: BigInt, width: Int): BigInt = { + require(bigInt >= 0, "signConvert assumes bigInt is >= 0!") + // Since the bigInt is always unsigned, bitLength always gets the max # of bits required to represent bigInt + val w = bigInt.bitLength.max(width) + // Negative if MSB is set or in this case, ex: 3 bit wide: negative if >= 4 + if (bigInt >= (BigInt(1) << (w - 1))) (bigInt - (BigInt(1) << w)) else bigInt + } + + // Converts a positive 2's complement BigInt to a Double - used for FixedPoint + def toDoubleFromUnsigned(i: BigInt, totalWidth: Int, fractionalWidth: Int): Double = { + val signedBigInt = signConvert(i, totalWidth) + FixedPoint.toDouble(signedBigInt, fractionalWidth) + } + + // For DspReal represented as BigInt from Double (unsigned) + def doubleToBigIntBits(double: Double): BigInt = { + val ret = BigInt(java.lang.Double.doubleToLongBits(double)) + if (ret >= 0) ret + else (BigInt(1) << DspReal.underlyingWidth) + ret + } + + // For DspReal represented as BigInt back to Double + def bigIntBitsToDouble(bigInt: BigInt): Double = { + java.lang.Double.longBitsToDouble(bigInt.toLong) + } + + // Used to get signal name for printing to console + private[dsptools] def getName(signal: InstanceId): String = { + s"${signal.pathName}" + } + + // Note: DspReal underlying is UInt + // Checks if a basic number is signed or unsigned + def isSigned(e: Data): Boolean = { + e match { + case _: SInt | _: FixedPoint => true + case _: DspReal | _: Bool | _: UInt => false + // Clock isn't a number, but it's still valid IO (should be treated as a Bool) + case _: Clock => false + case _ => throw DspException("Not a basic number/clock type! " + e) + } + } + + // For printing to Verilog testbench (signed) + private[dsptools] def signPrefix(e: Element): String = { + def signed = isSigned(e) + if (signed) " signed " + else "" + } + + // Determines if peek/poke data fits in bit width + def validRangeTest(signal: Data, value: BigInt): Unit = { + val len = value.bitLength + val neededLen = if (isSigned(signal)) len + 1 else len + require(signal.widthOption.nonEmpty, "Cannot check range of node with unknown width!") + if (neededLen > signal.getWidth) + throw DspException(s"Value: $value is not in node ${getName(signal)} range") + if (!isSigned(signal) && value < 0) + throw DspException("Negative value can't be used with unsigned") + } + + // Gets information on bitwidth, binarypoint for printing in console + def bitInfo(signal: Data): String = signal.widthOption match { + case Some(width) => { + signal match { + case f: FixedPoint => + f.binaryPoint match { + // Q integer . fractional bits + case KnownBinaryPoint(bp) => s"Q${width - 1 - bp}.$bp" + case _ => s"${width}-bit F" + } + case r: DspReal => "R" + case u: UInt => s"${width}-bit U" + case s: SInt => s"${width}-bit S" + case c: DspComplex[_] => { + val realInfo = bitInfo(c.real.asInstanceOf[Data]) + val imagInfo = bitInfo(c.imag.asInstanceOf[Data]) + s"[$realInfo, $imagInfo]" + } + case _ => throw DspException("Can't get bit info! Invalid type!") + } + } + case None => "" + } + + // Round value if data type is integer + def roundData(data: Data, value: Double): Double = { + data match { + case _: SInt | _: UInt => value.round.toDouble + case _: DspReal | _: FixedPoint => value + case _ => throw DspException("Invalid data type for rounding determination") + } + } + +} diff --git a/src/main/scala/dsptools/misc/PeekPokeDspExtensions.scala b/src/main/scala/dsptools/misc/PeekPokeDspExtensions.scala new file mode 100644 index 00000000..68970b50 --- /dev/null +++ b/src/main/scala/dsptools/misc/PeekPokeDspExtensions.scala @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: Apache-2.0 + +package dsptools.misc + +import breeze.math.Complex +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest.iotesters.PeekPokeTester +import dsptools.DspException +import dsptools.misc.DspTesterUtilities.{getName, roundData, validRangeTest} +import dsptools.numbers._ + +trait PeekPokeDspExtensions { + this: PeekPokeTester[_] => + + private def fixedName(node: FixedPoint): String = + node.instanceName.replace('.', '_') + + private def dspPeek(node: Data): (Double, BigInt) = { + val bi: BigInt = node match { + // Unsigned bigint + case r: DspReal => peek(r.node.asInstanceOf[Bits]) + case b: Bits => peek(b.asInstanceOf[Bits]) + case f: FixedPoint => peek(fixedName(f)) + } + val (dblOut, bigIntOut) = node match { + case _: DspReal => (DspTesterUtilities.bigIntBitsToDouble(bi), bi) + case f: FixedPoint => + f.binaryPoint match { + case KnownBinaryPoint(bp) => (FixedPoint.toDouble(bi, bp), bi) + case _ => throw DspException("Cannot peek FixedPoint with unknown binary point location") + } + // UInt + SInt = Bits + case _: Bits => (bi.doubleValue, bi) + case _ => throw DspException(s"Peeked node ${getName(node)} has incorrect type ${node.getClass.getName}") + } + (dblOut, bigIntOut) + } + + def peek(node: FixedPoint): Double = dspPeek(node)._1 + + // Takes precedence over Aggregate + def peek(node: DspReal): Double = dspPeek(node)._1 + + // General type returns Double + def peek(node: Data): Double = dspPeek(node)._1 + + def peek(c: DspComplex[_]): Complex = { + Complex(dspPeek(c.real.asInstanceOf[Data])._1, dspPeek(c.imag.asInstanceOf[Data])._1) + } + + def poke(signal: FixedPoint, value: Int): Unit = poke(signal, value.toDouble) + + def poke(signal: FixedPoint, value: Double): Unit = poke(signal.asInstanceOf[Data], value) + + // DspReal extends Bundle extends Aggregate extends Data + // If poking DspReal with Double, can only go here + // Type classes are all Data:RealBits + //scalastyle:off cyclomatic.complexity + def poke(signal: Data, value: Double): Unit = { + signal match { + case f: FixedPoint => + f.binaryPoint match { + case KnownBinaryPoint(bp) => + poke(fixedName(f) /*f.asSInt.asInstanceOf[Bits]*/, FixedPoint.toBigInt(value, bp)) + case _ => throw DspException("Must poke FixedPoint with known binary point") + } + case r: DspReal => poke(r.node.asInstanceOf[Bits], DspTesterUtilities.doubleToBigIntBits(value)) + // UInt + SInt + case b: Bits => poke(b.asInstanceOf[Bits], BigInt(value.round.toInt)) + case _ => throw DspException("Illegal poke value for node of type Data and value of type Double") + } + } + + def poke(signal: Data, value: BigDecimal): Unit = { + assert(value <= Double.MaxValue, s"poking ${signal} with a value $value bigger than Double.MaxValue") + poke(signal, value.toDouble) + } + + def poke(c: DspComplex[_], value: Complex): Unit = { + poke(c.real.asInstanceOf[Data], value.real) + poke(c.imag.asInstanceOf[Data], value.imag) + } + + def pokeFixedPoint(signal: FixedPoint, value: Double): Unit = { + poke(signal, value) + } + + def pokeFixedPointBig(signal: FixedPoint, value: BigDecimal): Unit = { + poke(signal, value) + } + + def checkDecimal(data: Data, expected: Double, dblVal: Double, bitVal: BigInt): (Boolean, Double) = { + def toMax(w: Int): BigInt = (BigInt(1) << w) - 1 + + // <= + val fixTol = 0 + val realTol = 8 + val fixTolInt = toMax(fixTol) + val floTolDec = math.pow(10, -realTol) + // Error checking does a bad job of handling really small numbers, + // so let's just force the really small numbers to 0 + val expected0 = if (math.abs(expected) < floTolDec / 100) 0.0 else expected + val dblVal0 = if (math.abs(dblVal) < floTolDec / 100) 0.0 else dblVal + val expectedBits = data match { + case _: DspReal => DspTesterUtilities.doubleToBigIntBits(expected0) // unsigned BigInt + case f: FixedPoint => + f.binaryPoint match { + case KnownBinaryPoint(bp) => FixedPoint.toBigInt(expected0, bp) + case _ => throw DspException("Unknown binary point in FixedPoint on expect") + } + case _: Bits => BigInt(expected0.round.toInt) + } + + validRangeTest(data, expectedBits) + + // Allow for some tolerance in error checking + val (tolerance, tolDec) = data match { + case f: FixedPoint => + f.binaryPoint match { + case KnownBinaryPoint(bp) => (fixTolInt, FixedPoint.toDouble(fixTolInt, bp)) + case _ => throw DspException("Unknown binary point!") + } + case _: SInt | _: UInt => (fixTolInt, fixTolInt.toDouble) + case _: DspReal => (DspTesterUtilities.doubleToBigIntBits(floTolDec), floTolDec) + } + val good = { + if (dblVal0 != expected0) { + val gotDiffDbl = math.abs(dblVal0 - expected0) + val gotDiffBits = (bitVal - expectedBits).abs + val passDbl = gotDiffDbl <= tolDec + val passBits = gotDiffBits <= tolerance + passDbl && passBits + } else { + true + } + } + (good, tolDec) + } + + // Expect on DspReal goes straight to here + def expect(data: Data, expected: Double): Boolean = expect(data, expected, msg = "") + + def expectWithoutFailure(data: Data, expected: Double, msg: String = ""): Boolean = { + val expectedNew = roundData(data, expected) + val path = getName(data) + val (dblVal, bitVal) = dspPeek(data) + val (good, tolerance) = checkDecimal(data, expectedNew, dblVal, bitVal) + good + } + + def expect(data: Data, expected: Double, msg: String): Boolean = { + val good = expectWithoutFailure(data, expected, msg) + expect(good, msg) + } + + def expect(signal: FixedPoint, expected: Int): Boolean = expect(signal, expected, "") + + def expect(signal: FixedPoint, expected: Int, msg: String): Boolean = expect(signal, expected.toDouble, msg) + + def expect(signal: FixedPoint, expected: Double): Boolean = expect(signal, expected, "") + + def expect(signal: FixedPoint, expected: Double, msg: String): Boolean = { + expect(signal.asInstanceOf[Data], expected, msg) + } + + def expect(data: Data, expected: BigDecimal): Boolean = expect(data, expected, "") + + def expect(data: Data, expected: BigDecimal, msg: String): Boolean = { + assert(expected <= Double.MaxValue, s"expecting from ${data} a value $expected that is bigger than Double.MaxValue") + val good = expectWithoutFailure(data, expected.toDouble, msg) + expect(good, msg) + } + + def expect(data: DspComplex[_], expected: Complex): Boolean = expect(data, expected, msg = "") + + def expect(data: DspComplex[_], expected: Complex, msg: String): Boolean = { + val dataReal = data.real.asInstanceOf[Data] + val dataImag = data.imag.asInstanceOf[Data] + val expectedNewR = roundData(dataReal, expected.real) + val expectedNewI = roundData(dataImag, expected.imag) + val path = getName(data) + val (good, dblValR, dblValI, toleranceR) = { + val (dblValR, bitValR) = dspPeek(dataReal) + val (dblValI, bitValI) = dspPeek(dataImag) + val (goodR, toleranceR) = checkDecimal(dataReal, expectedNewR, dblValR, bitValR) + val (goodI, _) = checkDecimal(dataImag, expectedNewI, dblValI, bitValI) + (goodR & goodI, dblValR, dblValI, toleranceR) + } + expect(good, msg) + } +} diff --git a/src/test/scala/examples/ParameterizedAdderSpec.scala b/src/test/scala/examples/ParameterizedAdderSpec.scala new file mode 100644 index 00000000..bca1d720 --- /dev/null +++ b/src/test/scala/examples/ParameterizedAdderSpec.scala @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters.PeekPokeTester +import dsptools.misc.PeekPokeDspExtensions +import dsptools.numbers._ +import org.scalatest.flatspec.AnyFlatSpec + +//noinspection TypeAnnotation +class ParameterizedAdder[T <: Data: Ring](gen: () => T) extends Module { + val a1: T = IO(Input(gen().cloneType)) + val a2: T = IO(Input(gen().cloneType)) + val c = IO(Output(gen().cloneType)) + + val register1 = Reg(gen().cloneType) + + register1 := a1 + a2 + + c := register1 +} + +class ParameterizedAdderTester[T <: Data: Ring](c: ParameterizedAdder[T]) + extends PeekPokeTester(c) + with PeekPokeDspExtensions { + for { + i <- (BigDecimal(-2.0) to 1.0 by 0.25).map(_.toDouble) + j <- (BigDecimal(-2.0) to 4.0 by 0.5).map(_.toDouble) + } { + poke(c.a1, i) + poke(c.a2, j) + step(1) + + val result = peek(c.c) + + expect(c.c, i + j, s"parameterize adder tester $i + $j => $result should have been ${i + j}") + } +} + +class ParameterizedAdderSpec extends AnyFlatSpec with ChiselScalatestTester { + + behavior.of("parameterized adder circuit on blackbox real") + + ignore should "allow registers to be declared that infer widths" in { + def getReal: DspReal = new DspReal + + test(new ParameterizedAdder(() => getReal)).runPeekPoke(new ParameterizedAdderTester(_)) + } + + behavior.of("parameterized adder circuit on fixed point") + + it should "allow registers to be declared that infer widths" in { + def getFixed: FixedPoint = FixedPoint(32.W, 16.BP) + + test(new ParameterizedAdder(() => getFixed)).runPeekPoke(new ParameterizedAdderTester(_)) + + test(new ParameterizedAdder(() => getFixed)) + .withAnnotations(Seq(VerilatorBackendAnnotation)) + .runPeekPoke(new ParameterizedAdderTester(_)) + } +} diff --git a/src/test/scala/examples/SimpleAdderSpec.scala b/src/test/scala/examples/SimpleAdderSpec.scala new file mode 100644 index 00000000..e8209d36 --- /dev/null +++ b/src/test/scala/examples/SimpleAdderSpec.scala @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 + +package examples + +import chisel3.{fromDoubleToLiteral => _, fromIntToBinaryPoint => _, _} +import fixedpoint._ +import chiseltest._ +import chiseltest.iotesters.PeekPokeTester +import org.scalatest.flatspec.AnyFlatSpec +import dsptools.misc.PeekPokeDspExtensions + +//noinspection TypeAnnotation +class SimpleAdder extends Module { + val a1 = IO(Input(FixedPoint(6.W, 4.BP))) + val a2 = IO(Input(FixedPoint(8.W, 1.BP))) + val c = IO(Output(FixedPoint(12.W, 5.BP))) + + val register1 = Reg(FixedPoint()) + + register1 := a1 + a2 + + c := register1 +} + +class SimpleAdderTester(c: SimpleAdder) extends PeekPokeTester(c) with PeekPokeDspExtensions { + for { + i <- BigDecimal(0.0) to 1.0 by 0.25 + j <- BigDecimal(0.0) to 4.0 by 0.5 + } { + val expected = i + j + + poke(c.a1, i) + poke(c.a2, j) + step(1) + expect(c.c, expected, s"SimpleAdder: $i + $j should make $expected got ${peek(c.c)}") + } +} +class SimpleAdderSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior.of("SimpleAdder") + + it should "add to numbers excellently" in { + test(new SimpleAdder) //(new SimpleAdderTester(_)) + .runPeekPoke(new SimpleAdderTester(_)) + } +} From 49e63ddfb4e12ed003b4b65134d21628d353e06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20Kondi=C4=87?= <55934296+konda-x1@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:39:58 +0200 Subject: [PATCH 3/3] Update test.yml * Remove chisel3-tools docker image * Install Verilator from system repositories * Use coursier's setup-action for setting up Scala and sbt * Update coursier's cache-action to v6 due to deprecation warnings --- .github/workflows/test.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ec9ac17..575cbfbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,21 +13,18 @@ jobs: test: name: test runs-on: ubuntu-latest - container: - image: ucbbar/chisel3-tools - options: --user github --entrypoint /bin/bash - env: - CONTAINER_HOME: /home/github steps: - name: Checkout uses: actions/checkout@v2 with: submodules: 'true' + - name: Install Verilator + run: sudo apt-get update -y && sudo apt-get install -y verilator - name: Setup Scala - uses: olafurpg/setup-scala@v10 + uses: coursier/setup-action@v1 - name: Cache - uses: coursier/cache-action@v5 + uses: coursier/cache-action@v6 - name: Documentation id: doc run: sbt doc