Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Circt intrinsic wrappers (IsX, PlusArgsTest, PlusArgsValue) #2958

Merged
merged 23 commits into from
Apr 8, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/main/scala/chisel3/util/circt/IsX.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.util.circt

import chisel3._
import chisel3.experimental.IntrinsicModule
import chisel3.internal.Builder

import circt.Intrinsic

/** Create a module with a parameterized type which returns whether the input
* is a verilog 'x'.
*/
private class IsXIntrinsic[T <: Data](gen: T) extends IntrinsicModule("circt_isX") {
val i = IO(Input(gen))
val found = IO(Output(UInt(1.W)))
}

object IsX {

/** Creates an intrinsic which returns whether the input is a verilog 'x'.
*
* @example {{{
* b := IsX(a)
* }}}
*/
def apply[T <: Data](gen: T): Data = {
val inst = Module(new IsXIntrinsic(chiselTypeOf(gen)))
inst.i := gen
inst.found
}
}
35 changes: 35 additions & 0 deletions src/main/scala/chisel3/util/circt/PlusArgsTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.util.circt

import chisel3._
import chisel3.experimental.IntrinsicModule
import chisel3.internal.Builder

import circt.Intrinsic

/** Create a module with a parameterized type which calls the verilog function
* \$test\$plusargs to test for the existence of the string str in the
* simulator command line.
*/
private class PlusArgsTestIntrinsic[T <: Data](gen: T, str: String)
extends IntrinsicModule("circt_plusargs_test", Map("FORMAT" -> str)) {
val found = IO(Output(Bool()))
}

object PlusArgsTest {

/** Creates an intrinsic which calls \$test\$plusargs.
*
* @example {{{
* b := PlusArgsTest(UInt<32.W>, "FOO")
* }}}
*/
def apply[T <: Data](gen: T, str: String): Data = {
Module(if (gen.isSynthesizable) {
new PlusArgsTestIntrinsic(chiselTypeOf(gen), str)
} else {
new PlusArgsTestIntrinsic(gen, str)
}).found
}
}
40 changes: 40 additions & 0 deletions src/main/scala/chisel3/util/circt/PlusArgsValue.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.util.circt

import chisel3._
import chisel3.experimental.{FlatIO, IntrinsicModule}
import chisel3.internal.Builder

import circt.Intrinsic

/** Create a module which generates a verilog \$plusargs\$value. This returns a
* value as indicated by the format string and a flag for whether the value
* was found.
*/
private class PlusArgsValueIntrinsic[T <: Data](gen: T, str: String)
extends IntrinsicModule("circt_plusargs_value", Map("FORMAT" -> str)) {
val io = FlatIO(new Bundle {
val found = Output(Bool())
val result = Output(gen)
})
}

object PlusArgsValue {

/** Creates an intrinsic which calls \$test\$plusargs.
*
* @example {{{
* b := PlusArgsValue(UInt(32.W), "FOO=%d")
* b.found
* b.value
* }}}
*/
def apply[T <: Data](gen: T, str: String) = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so is the intrinsic handling the unpacking into some complicated data type from the %d which is going to just be an integer? Is this useful vs making that happen explicitly in chisel and make this intrinsic just return a UInt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intrinsic returns any verilog type which can be parsed with format strings (and is supported from firrtl). The format string can be used in verilog to pick apart complex specification strings for subfields you are interested in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intrinsic is nothing more than a wrapper for verilog-spec functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I am echoing Schuyler's comment above that I'm unclear why this can return anything other than a UInt at this level. Users can easily cast from UInt to their appropriate data type in a safer way than this happening automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intrinsic isn't doing this, verilog is. What is safer in moving the casting point? It moves unsafe type conversion further into the user code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I understand that this is meaningful in verilog, I am OK with it. We might want to add something to the scaladoc that in terms of order / unpacking it's equivalent to using a UInt and then .asTypeOf(gen).

Module(if (gen.isSynthesizable) {
new PlusArgsValueIntrinsic(chiselTypeOf(gen), str)
} else {
new PlusArgsValueIntrinsic(gen, str)
}).io
}
}
32 changes: 6 additions & 26 deletions src/main/scala/chisel3/util/circt/SizeOf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,17 @@

package chisel3.util.circt

import scala.util.hashing.MurmurHash3

import chisel3._
import chisel3.experimental.{annotate, ChiselAnnotation, ExtModule}
import chisel3.experimental.IntrinsicModule
import chisel3.internal.{Builder, BuilderContextCache}

import circt.Intrinsic

// We have to unique designedName per type, be we can't due to name conflicts
// on bundles. Thus we use a globally unique id.
private object SizeOfGlobalIDGen {
private case object CacheKey extends BuilderContextCache.Key[Int]
def getID() = {
val oldID = Builder.contextCache.getOrElse(CacheKey, 0)
Builder.contextCache.put(CacheKey, oldID + 1)
oldID
}
}

/** Create a module with a parameterized type which returns the size of the type
* as a compile-time constant. This lets you write code which depends on the
* results of type inference.
*/
private class SizeOfIntrinsic[T <: Data](gen: T) extends ExtModule {
val i = IO(Input(gen));
private class SizeOfIntrinsic[T <: Data](gen: T) extends IntrinsicModule("circt_sizeof") {
val i = IO(Input(gen))
val size = IO(Output(UInt(32.W)))
annotate(new ChiselAnnotation {
override def toFirrtl =
Intrinsic(toTarget, "circt.sizeof")
})
override val desiredName = "SizeOf_" + SizeOfGlobalIDGen.getID()
}

object SizeOf {
Expand All @@ -47,8 +27,8 @@ object SizeOf {
* }}}
*/
def apply[T <: Data](gen: T): Data = {
val sizeOfInst = Module(new SizeOfIntrinsic(chiselTypeOf(gen)))
sizeOfInst.i := gen
sizeOfInst.size
val inst = Module(new SizeOfIntrinsic(chiselTypeOf(gen)))
inst.i := gen
inst.size
}
}
47 changes: 47 additions & 0 deletions src/test/scala/chiselTests/util/IsXSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests.util

import chisel3._
import chisel3.stage.ChiselGeneratorAnnotation
import chisel3.testers.BasicTester
import chisel3.util.circt.IsX
import circt.stage.ChiselStage

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.io.Source

private class IsXBundle extends Bundle {
val a = UInt()
val b = SInt()
}

private class IsXTop extends Module {
val w = IO(Input(UInt(65.W)))
val x = IO(Input(new IsXBundle))
val y = IO(Input(UInt(65.W)))
val outw = IO(Output(UInt(1.W)))
val outx = IO(Output(UInt(1.W)))
val outy = IO(Output(UInt(1.W)))
outw := IsX(w)
outx := IsX(x)
outy := IsX(y)
}

class IsXSpec extends AnyFlatSpec with Matchers {
it should "Should work for types" in {
val fir = ChiselStage.emitCHIRRTL(new IsXTop)
println(fir)
(
(fir.split('\n').map(_.trim) should contain).allOf(
"intmodule IsXIntrinsic :",
"input i : UInt<65>",
"output found : UInt<1>",
"intrinsic = circt_isX",
"input i : { a : UInt, b : SInt}"
)
)
}
}
35 changes: 35 additions & 0 deletions src/test/scala/chiselTests/util/PlusArgsTestSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests.util

import chisel3._
import chisel3.testers.BasicTester
import chisel3.util.circt.PlusArgsTest
import circt.stage.ChiselStage

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.io.Source

private class PlusArgsTestTop extends Module {
val w = IO(Output(UInt(1.W)))
val x = IO(Output(UInt(1.W)))
val z = IO(Input(UInt(32.W)))
w := PlusArgsTest(UInt(32.W), "FOO")
x := PlusArgsTest(z, "BAR")
}

class PlusArgsTestSpec extends AnyFlatSpec with Matchers {
it should "Should work for types" in {
val fir = ChiselStage.emitCHIRRTL(new PlusArgsTestTop)
println(fir)
(fir.split('\n').map(_.trim) should contain).allOf(
"intmodule PlusArgsTestIntrinsic :",
"output found : UInt<1>",
"intrinsic = circt_plusargs_test",
"parameter FORMAT = \"FOO\"",
"parameter FORMAT = \"BAR\""
)
}
}
41 changes: 41 additions & 0 deletions src/test/scala/chiselTests/util/PlusArgsValueSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests.util

import chisel3._
import chisel3.testers.BasicTester
import chisel3.util.circt.PlusArgsValue
import circt.stage.ChiselStage

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.io.Source

private class PlusArgsValueTop extends Module {
val wf = IO(Output(UInt(1.W)))
val wv = IO(Output(UInt(32.W)))
val xf = IO(Output(UInt(1.W)))
val xv = IO(Output(UInt(32.W)))
val tmpw = PlusArgsValue(UInt(32.W), "FOO=%d")
val tmpx = PlusArgsValue(xv, "BAR=%d")
wf := tmpw.found
wv := tmpw.result
xf := tmpx.found
xv := tmpx.result
}

class PlusArgsValueSpec extends AnyFlatSpec with Matchers {
it should "Should work for types" in {
val fir = ChiselStage.emitCHIRRTL(new PlusArgsValueTop)
println(fir)
(fir.split('\n').map(_.trim) should contain).inOrder(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR can do a takeWhile(_ != '@') to strip the source locators before the trim. (Generally it's bad to match on the source locator.)

"intmodule PlusArgsValueIntrinsic :",
"output found : UInt<1>",
"output result : UInt<32>",
"intrinsic = circt_plusargs_value",
"parameter FORMAT = \"FOO=%d\"",
"parameter FORMAT = \"BAR=%d\""
)
}
}
54 changes: 13 additions & 41 deletions src/test/scala/chiselTests/util/SizeOfSpec.scala
Original file line number Diff line number Diff line change
@@ -1,64 +1,36 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests.util

import chisel3._
import chisel3.stage.ChiselGeneratorAnnotation
import chisel3.testers.BasicTester
import chisel3.util.circt.SizeOf

import circt.stage.ChiselStage

import firrtl.stage.FirrtlCircuitAnnotation

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.io.Source

class MyBundle extends Bundle {
private class SizeOfBundle extends Bundle {
val a = UInt()
val b = SInt()
}

class SizeOfTop extends Module {
val io = IO(new Bundle {
val w = Input(UInt(65.W))
val x = Input(new MyBundle)
val outw = UInt(32.W)
val outx = UInt(32.W)
})
io.outw := SizeOf(io.w)
io.outx := SizeOf(io.x)
private class SizeOfTop extends Module {
val w = IO(Input(UInt(65.W)))
val x = IO(Input(new SizeOfBundle))
val outw = IO(Output(UInt(32.W)))
val outx = IO(Output(UInt(32.W)))
outw := SizeOf(w)
outx := SizeOf(x)
}

/** A test for intrinsics. Since chisel is producing intrinsics as tagged
* extmodules (for now), we explicitly test the chirrtl and annotations rather
* than the processed firrtl or verilog. It is worth noting that annotations
* are implemented (for now) in a way which makes the output valid for all
* firrtl compilers, hence we write a localized, not end-to-end test
*/
class SizeOfSpec extends AnyFlatSpec with Matchers {
it should "Should work for types" in {
val fir = ChiselStage.emitCHIRRTL(new SizeOfTop)
val a1 = """extmodule SizeOf_0""".r
(fir should include).regex(a1)
val b1 = """defname = SizeOf_0""".r
(fir should include).regex(b1)
val a2 = """extmodule SizeOf_1""".r
(fir should include).regex(a2)
val b2 = """defname = SizeOf_1""".r
(fir should include).regex(b2)

// The second elaboration uses a unique name since the Builder is reused (?)
val c = """Intrinsic\(~SizeOfTop\|SizeOf.*,circt.sizeof\)"""
((new ChiselStage)
.execute(
args = Array("--target", "chirrtl"),
annotations = Seq(chisel3.stage.ChiselGeneratorAnnotation(() => new SizeOfTop))
)
.flatMap {
case FirrtlCircuitAnnotation(circuit) => circuit.annotations
case _ => None
}
.mkString("\n") should include).regex(c)
println(fir)
(fir.split('\n').map(_.trim) should contain)
.allOf("intmodule SizeOfIntrinsic :", "input i : UInt<65>", "output size : UInt<32>", "intrinsic = circt_sizeof")
}
}