diff --git a/core/src/main/scala/chisel3/Intrinsic.scala b/core/src/main/scala/chisel3/Intrinsic.scala new file mode 100644 index 00000000000..2c922b68b66 --- /dev/null +++ b/core/src/main/scala/chisel3/Intrinsic.scala @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import chisel3._ +import chisel3.experimental.{requireIsChiselType, Param, SourceInfo} +import chisel3.internal.firrtl.ir._ +import chisel3.internal.{Builder, OpBinding} +import chisel3.internal.Builder.pushCommand + +object Intrinsic { + + /** Create an intrinsic statement. + * + * @param intrinsic name of the intrinsic + * @param params parameter name/value pairs, if any. Parameter names must be unique. + * @param data inputs + * + * @example {{{ + * Intrinsic("test", "Foo" -> 5)(f, g) + * }}} + */ + def apply(intrinsic: String, params: (String, Param)*)(data: Data*)(implicit sourceInfo: SourceInfo): Unit = { + require(params.map(_._1).distinct.size == params.size, "parameter names must be unique") + pushCommand(DefIntrinsic(sourceInfo, intrinsic, data.map(_.ref), params)) + } +} + +object IntrinsicExpr { + + /** Create an intrinsic expression. + * + * @param intrinsic name of the intrinsic + * @param ret return type of the expression + * @param params parameter name/value pairs, if any. Parameter names must be unique. + * @param data inputs + * @return intrinsic expression that returns the specified return type + * + * @example {{{ + * val test = IntrinsicExpr("test", UInt(32.W), "Foo" -> 5)(f, g) + 3.U + * }}} + */ + def apply[T <: Data]( + intrinsic: String, + ret: => T, + params: (String, Param)* + )(data: Data* + )( + implicit sourceInfo: SourceInfo + ): T = { + val prevId = Builder.idGen.value + val t = ret // evaluate once (passed by name) + requireIsChiselType(t, "intrinsic type") + val int = if (!t.mustClone(prevId)) t else t.cloneTypeFull + + int.bind(OpBinding(Builder.forcedUserModule, Builder.currentWhen)) + require(params.map(_._1).distinct.size == params.size, "parameter names must be unique") + pushCommand(DefIntrinsicExpr(sourceInfo, intrinsic, int, data.map(_.ref), params)) + int + } +} diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index 76aa9f6da2c..09bd8471257 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -254,6 +254,24 @@ private[chisel3] object Converter { e.name ) ) + case i @ DefIntrinsic(info, intrinsic, args, params) => + Some( + fir.IntrinsicStmt( + convert(info), + intrinsic, + args.map(a => convert(a, ctx, info)), + params.map { case (k, v) => convert(k, v) } + ) + ) + case i @ DefIntrinsicExpr(info, intrinsic, id, args, params) => + val tpe = extractType(id, info, typeAliases) + val expr = fir.IntrinsicExpr( + intrinsic, + args.map(a => convert(a, ctx, info)), + params.map { case (k, v) => convert(k, v) }, + tpe + ) + Some(fir.DefNode(convert(info), i.name, expr)) case _ => None } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index ef154ed0ee3..06c06384c0d 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -407,6 +407,17 @@ private[chisel3] object ir { params: Map[String, Param]) extends Component + case class DefIntrinsicExpr[T <: Data]( + sourceInfo: SourceInfo, + intrinsic: String, + id: T, + args: Seq[Arg], + params: Seq[(String, Param)]) + extends Definition + + case class DefIntrinsic(sourceInfo: SourceInfo, intrinsic: String, args: Seq[Arg], params: Seq[(String, Param)]) + extends Command + case class DefClass(id: Class, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component case class Circuit( diff --git a/docs/src/explanations/intrinsics.md b/docs/src/explanations/intrinsics.md index 2bab51c0c91..2435db893c4 100644 --- a/docs/src/explanations/intrinsics.md +++ b/docs/src/explanations/intrinsics.md @@ -6,26 +6,44 @@ section: "chisel3" # Intrinsics -Chisel *Intrinsics* are used to instantiate implementation defined functionality. +Chisel *Intrinsics* are used to express implementation defined functionality. Intrinsics provide a way for specific compilers to extend the capabilities of the language in ways which are not implementable with library code. +Intrinsics will be typechecked by the implementation. What intrinsics are +available is documented by an implementation. + +The `Intrinsic` and `IntrinsicExpr` can be used to create intrinsic statements +and expressions. + Modules defined as an `IntrinsicModule` will be instantiated as normal modules, but the intrinsic field communicates to the compiler what functionality to use to implement the module. Implementations may not be actual modules, the module nature of intrinsics is merely for instantiation purposes. -Intrinsics will be typechecked by the implementation. What intrinsics are -available is documented by an implementation. - ### Parameterization Parameters can be passed as an argument to the IntModule constructor. -### Example +### Intrinsic Expression Example + +This following creates an intrinsic for the intrinsic named "MyIntrinsic". +It takes a parameter named "STRING" and has several inputs. + +```scala mdoc:invisible +import chisel3._ +``` + +```scala mdoc:compile-only +class Foo extends RawModule { + val myresult = IntrinsicExpr("MyIntrinsic", UInt(32.W), "STRING" -> "test")(3.U, 5.U) +} +``` + +### IntrinsicModule Example -This following creates an intrinsic module for the intrinsic named -"OtherIntrinsic". It takes a parameter named "STRING" and has several ports. +This following creates an intrinsic module for the intrinsic named +"OtherIntrinsic". It takes a parameter named "STRING" and has one bundle port. ```scala mdoc:invisible import chisel3._ diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index d68d6514d10..eb53c4ca69d 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -445,6 +445,23 @@ case class LayerBlock(info: Info, layer: String, body: Statement) extends Statem case class DefOption(info: Info, name: String, cases: Seq[DefOptionCase]) case class DefOptionCase(info: Info, name: String) +case class IntrinsicExpr( + intrinsic: String, + args: Seq[Expression], + params: Seq[Param], + tpe: Type) + extends Expression + with UseSerializer + +case class IntrinsicStmt( + info: Info, + intrinsic: String, + args: Seq[Expression], + params: Seq[Param], + tpe: Option[Type] = None) + extends Statement + with UseSerializer + // formal object Formal extends Enumeration { val Assert = Value("assert") diff --git a/firrtl/src/main/scala/firrtl/ir/Serializer.scala b/firrtl/src/main/scala/firrtl/ir/Serializer.scala index ccb44c61ec5..8340474d644 100644 --- a/firrtl/src/main/scala/firrtl/ir/Serializer.scala +++ b/firrtl/src/main/scala/firrtl/ir/Serializer.scala @@ -127,10 +127,11 @@ object Serializer { if (idx != lastIdx) b ++= ", " } b += ')' - case ProbeExpr(expr, _) => b ++= "probe("; s(expr); b += ')' - case RWProbeExpr(expr, _) => b ++= "rwprobe("; s(expr); b += ')' - case ProbeRead(expr, _) => b ++= "read("; s(expr); b += ')' - case other => b ++= other.serialize // Handle user-defined nodes + case ProbeExpr(expr, _) => b ++= "probe("; s(expr); b += ')' + case RWProbeExpr(expr, _) => b ++= "rwprobe("; s(expr); b += ')' + case ProbeRead(expr, _) => b ++= "read("; s(expr); b += ')' + case IntrinsicExpr(intrinsic, args, params, tpe) => sIntrinsic(NoInfo, intrinsic, args, params, Some(tpe)) + case other => b ++= other.serialize // Handle user-defined nodes } // Helper for some not-real Statements that only exist for Serialization @@ -354,13 +355,48 @@ object Serializer { b ++= "force("; s(clock); b ++= ", "; s(cond); b ++= ", "; s(probe); b ++= ", "; s(value); b += ')'; s(info) case ProbeRelease(info, clock, cond, probe) => b ++= "release("; s(clock); b ++= ", "; s(cond); b ++= ", "; s(probe); b += ')'; s(info) - case other => b ++= other.serialize // Handle user-defined nodes + case IntrinsicStmt(info, intrinsic, args, params, tpe) => sIntrinsic(info, intrinsic, args, params, tpe) + case other => b ++= other.serialize // Handle user-defined nodes } private def sStmtName(lbl: String)(implicit b: StringBuilder): Unit = { if (lbl.nonEmpty) { b ++= s" : ${legalize(lbl)}" } } + private def sIntrinsic( + info: Info, + intrinsic: String, + args: Seq[Expression], + params: Seq[Param], + tpe: Option[Type] + )( + implicit b: StringBuilder, + indent: Int + ): Unit = { + b ++= "intrinsic(" + b ++= intrinsic + if (params.nonEmpty) { + b += '<'; + val lastIdx = params.size - 1 + params.zipWithIndex.foreach { + case (param, idx) => + s(param) + if (idx != lastIdx) b ++= ", " + } + b += '>' + } + if (tpe.nonEmpty) { + b ++= " : " + s(tpe.get) + } + if (args.nonEmpty) { + b ++= ", " + s(args, ", ") + } + b += ')' + s(info) + } + private def s(node: Width)(implicit b: StringBuilder, indent: Int): Unit = node match { case IntWidth(width) => b += '<'; b ++= width.toString(); b += '>' case UnknownWidth => // empty string @@ -437,11 +473,11 @@ object Serializer { } private def s(node: Param)(implicit b: StringBuilder, indent: Int): Unit = node match { - case IntParam(name, value) => b ++= "parameter "; b ++= name; b ++= " = "; b ++= value.toString - case DoubleParam(name, value) => b ++= "parameter "; b ++= name; b ++= " = "; b ++= value.toString - case StringParam(name, value) => b ++= "parameter "; b ++= name; b ++= " = "; b ++= value.escape + case IntParam(name, value) => b ++= name; b ++= " = "; b ++= value.toString + case DoubleParam(name, value) => b ++= name; b ++= " = "; b ++= value.toString + case StringParam(name, value) => b ++= name; b ++= " = "; b ++= value.escape case RawStringParam(name, value) => - b ++= "parameter "; b ++= name; b ++= " = " + b ++= name; b ++= " = " b += '\''; b ++= value.replace("'", "\\'"); b += '\'' case other => b ++= other.serialize // Handle user-defined nodes } @@ -467,14 +503,14 @@ object Serializer { doIndent(0); b ++= "extmodule "; b ++= legalize(name); b ++= " :"; s(info) ports.foreach { p => newLineAndIndent(1); s(p) } newLineAndIndent(1); b ++= "defname = "; b ++= defname - params.foreach { p => newLineAndIndent(1); s(p) } + params.foreach { p => newLineAndIndent(1); b ++= "parameter "; s(p) } Iterator(b.toString) case IntModule(info, name, ports, intrinsic, params) => implicit val b = new StringBuilder doIndent(0); b ++= "intmodule "; b ++= legalize(name); b ++= " :"; s(info) ports.foreach { p => newLineAndIndent(1); s(p) } newLineAndIndent(1); b ++= "intrinsic = "; b ++= intrinsic - params.foreach { p => newLineAndIndent(1); s(p) } + params.foreach { p => newLineAndIndent(1); b ++= "parameter "; s(p) } Iterator(b.toString) case DefClass(info, name, ports, body) => val start = { diff --git a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala index 4894be96c0d..25bdfd3e2d8 100644 --- a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala +++ b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala @@ -253,6 +253,22 @@ class SerializerSpec extends AnyFlatSpec with Matchers { Serializer.serialize(probeRelease) should be("release(clock, cond, outProbe)") } + it should "support emitting intrinsic expressions and statements" in { + val intrinsicNode = + DefNode(NoInfo, "foo", IntrinsicExpr("test", Seq(Reference("arg")), Seq(), UIntType(IntWidth(1)))) + Serializer.serialize(intrinsicNode) should be("node foo = intrinsic(test : UInt<1>, arg)") + + val paramsNode = + DefNode(NoInfo, "bar", IntrinsicExpr("test", Seq(), Seq(IntParam("foo", 5)), UIntType(IntWidth(1)))) + Serializer.serialize(paramsNode) should be("node bar = intrinsic(test : UInt<1>)") + + val intrinsicStmt = IntrinsicStmt(NoInfo, "stmt", Seq(), Seq(IntParam("foo", 5))) + Serializer.serialize(intrinsicStmt) should be("intrinsic(stmt)") + + val intrinsicStmtTpe = IntrinsicStmt(NoInfo, "expr", Seq(), Seq(), Some(UIntType(IntWidth(2)))) + Serializer.serialize(intrinsicStmtTpe) should be("intrinsic(expr : UInt<2>)") + } + it should "support lazy serialization" in { var stmtSerialized = false case class HackStmt(stmt: Statement) extends Statement { diff --git a/src/test/scala/chiselTests/IntrinsicSpec.scala b/src/test/scala/chiselTests/IntrinsicSpec.scala new file mode 100644 index 00000000000..a396c64c6c6 --- /dev/null +++ b/src/test/scala/chiselTests/IntrinsicSpec.scala @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chiselTests.{ChiselFlatSpec, MatchesAndOmits} +import circt.stage.ChiselStage + +import chisel3._ + +class IntrinsicSpec extends ChiselFlatSpec with MatchesAndOmits { + behavior.of("Intrinsics") + + it should "support a simple intrinsic statement" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + Intrinsic("test")() + }) + + matchesAndOmits(chirrtl)("intrinsic(test)")() + } + + it should "support intrinsic statements with arguments" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val f = IO(UInt(3.W)) + val g = IO(UInt(5.W)) + Intrinsic("test")(f, g) + }) + + matchesAndOmits(chirrtl)("intrinsic(test, f, g)")() + } + it should "support intrinsic statements with parameters and arguments" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val f = IO(UInt(3.W)) + val g = IO(UInt(5.W)) + Intrinsic("test", "Foo" -> 5)(f, g) + }) + + matchesAndOmits(chirrtl)("intrinsic(test, f, g)")() + } + + it should "support intrinsic expressions" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val f = IO(UInt(3.W)) + val g = IO(UInt(5.W)) + val test = IntrinsicExpr("test", UInt(32.W))(f, g) + 3.U + }) + + matchesAndOmits(chirrtl)(" = intrinsic(test : UInt<32>, f, g)")() + } + + it should "support intrinsic expressions with parameters and arguments" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val f = IO(UInt(3.W)) + val g = IO(UInt(5.W)) + val test = IntrinsicExpr("test", UInt(32.W), "foo" -> "bar", "x" -> 5)(f, g) + 3.U + }) + + matchesAndOmits(chirrtl)(" = intrinsic(test : UInt<32>, f, g)")() + } + + it should "preserve parameter order" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val test = IntrinsicExpr("test", UInt(32.W), "x" -> 5, "foo" -> 3)() + }) + + matchesAndOmits(chirrtl)(" = intrinsic(test : UInt<32>)")() + } + + it should "requite unique parameter names" in { + intercept[IllegalArgumentException] { + ChiselStage.emitCHIRRTL(new RawModule { + val test = IntrinsicExpr("test", UInt(32.W), "foo" -> 5, "foo" -> 3)() + }) + }.getMessage() should include( + "parameter names must be unique" + ) + } +}