diff --git a/docs/RationaleHW.md b/docs/RationaleHW.md index ee55b6f76d..0eb961a53a 100644 --- a/docs/RationaleHW.md +++ b/docs/RationaleHW.md @@ -260,7 +260,23 @@ This set includes: ### Using parameters in the body of a module Parameters are not SSA values, so they cannot directly be used within the body -of the module. To project them with a specific name, you can use the +of the module. Just like you use `hw.constant` to project a constant integer +value into the SSA domain, you can use the `hw.param.value` to project an +parameter expression, like so: + +```mlir +hw.module @M1(%clock : i1, ...) { + ... + %param1 = sv.param.value i1 = #hw.param.decl.ref<"param1"> + ... + sv.if %param1 { // Compile-time conditional on parameter. + sv.fwrite "Only happens when the parameter is set\n" + } + ... +} +``` + +Alternately, you can project them with a specific name, you can use the `sv.localparam` declaration like so: ```mlir @@ -275,8 +291,9 @@ hw.module @M1(%clock : i1, ...) { } ``` -Alternatively, if you don't want to introduce a local name, you can use a -**TODO**: yet-to-be-implemented new op. +Using `sv.localparam` is helpful when you're looking to produce specifically +pretty Verilog for human consumption. The optimizer won't fold aggressively +around these names. ### Parameterized Types diff --git a/include/circt/Dialect/HW/HWMiscOps.td b/include/circt/Dialect/HW/HWMiscOps.td index ddf1a5f5ca..c9f342cf74 100644 --- a/include/circt/Dialect/HW/HWMiscOps.td +++ b/include/circt/Dialect/HW/HWMiscOps.td @@ -57,12 +57,8 @@ def KnownBitWidthType : Type, def BitcastOp: HWOp<"bitcast", [NoSideEffect]> { let summary = [{ Reinterpret one value to another value of the same size and - potentially different type. - }]; - - let description = [{ - See the HW-SV rationale document for a longer description, including an - example. + potentially different type. See the `hw` dialect rationale document for + more details. }]; let arguments = (ins KnownBitWidthType:$input); @@ -74,7 +70,17 @@ def BitcastOp: HWOp<"bitcast", [NoSideEffect]> { return success(); }]; - let assemblyFormat = [{ - $input attr-dict `:` functional-type($input, $result) - }]; + let assemblyFormat = "$input attr-dict `:` functional-type($input, $result)"; +} + +def ParamValueOp : HWOp<"param.value", + [FirstAttrDerivedResultType, NoSideEffect]> { + let summary = [{ + Return the value of a parameter expression as an SSA value that may be used + by other ops. + }]; + + let arguments = (ins AnyAttr:$value); + let results = (outs HWValueType:$result); + let assemblyFormat = "custom($value, type($result)) attr-dict"; } diff --git a/include/circt/Dialect/HW/HWVisitors.h b/include/circt/Dialect/HW/HWVisitors.h index 6b1fd0eae9..f597caffbd 100644 --- a/include/circt/Dialect/HW/HWVisitors.h +++ b/include/circt/Dialect/HW/HWVisitors.h @@ -33,7 +33,7 @@ public: // Struct operations StructCreateOp, StructExtractOp, StructInjectOp, // Cast operation - BitcastOp>([&](auto expr) -> ResultType { + BitcastOp, ParamValueOp>([&](auto expr) -> ResultType { return thisCast->visitTypeOp(expr, args...); }) .Default([&](auto expr) -> ResultType { @@ -61,6 +61,7 @@ public: HANDLE(ConstantOp, Unhandled); HANDLE(BitcastOp, Unhandled); + HANDLE(ParamValueOp, Unhandled); HANDLE(StructCreateOp, Unhandled); HANDLE(StructExtractOp, Unhandled); HANDLE(StructInjectOp, Unhandled); diff --git a/lib/Dialect/HW/HWOps.cpp b/lib/Dialect/HW/HWOps.cpp index ea77e8e101..86624d74bf 100644 --- a/lib/Dialect/HW/HWOps.cpp +++ b/lib/Dialect/HW/HWOps.cpp @@ -179,6 +179,24 @@ OpFoldResult ConstantOp::fold(ArrayRef constants) { return valueAttr(); } +//===----------------------------------------------------------------------===// +// ConstantOp +//===----------------------------------------------------------------------===// + +static ParseResult parseParamValue(OpAsmParser &p, Attribute &value, + Type &resultType) { + if (p.parseType(resultType) || p.parseEqual() || + p.parseAttribute(value, resultType)) + return failure(); + return success(); +} + +static void printParamValue(OpAsmPrinter &p, Operation *, Attribute value, + Type resultType) { + p << resultType << " = "; + p.printAttributeWithoutType(value); +} + //===----------------------------------------------------------------------===// // HWModuleOp //===----------------------------------------------------------------------===/ @@ -1174,7 +1192,7 @@ static ParseResult parseArrayConcatTypes(OpAsmParser &p, auto parseElement = [&]() -> ParseResult { Type ty; if (p.parseType(ty)) - return p.emitError(p.getCurrentLocation(), "Expected type"); + return failure(); auto arrTy = type_dyn_cast(ty); if (!arrTy) return p.emitError(p.getCurrentLocation(), "Expected !hw.array type"); diff --git a/lib/Dialect/SV/Transforms/HWLegalizeNames.cpp b/lib/Dialect/SV/Transforms/HWLegalizeNames.cpp index b69ec89f42..fd181778d8 100644 --- a/lib/Dialect/SV/Transforms/HWLegalizeNames.cpp +++ b/lib/Dialect/SV/Transforms/HWLegalizeNames.cpp @@ -233,8 +233,6 @@ remapRenamedParameters(Attribute value, HWModuleOp module, newRHS, value.getType()); } - // TODO: Handle nested expressions when we support them. - // Otherwise this must be a parameter reference. auto parameterRef = value.dyn_cast(); assert(parameterRef && "Unknown kind of parameter expression"); @@ -344,6 +342,16 @@ rewriteModuleBody(Block &block, NameCollisionResolver &nameResolver, continue; } + if (auto paramValue = dyn_cast(op)) { + // If the initializer value in the local param was renamed then update it. + if (moduleHasRenamedInterface) { + auto curModule = op.getParentOfType(); + paramValue.valueAttr(remapRenamedParameters( + paramValue.value(), curModule, renamedParameterInfo)); + } + continue; + } + // If this operation has regions, then we recursively process them if they // can contain things that need to be renamed. We don't walk the module // in the common case. diff --git a/lib/Dialect/SV/Transforms/PrettifyVerilog.cpp b/lib/Dialect/SV/Transforms/PrettifyVerilog.cpp index 435f211014..794c8a41a6 100644 --- a/lib/Dialect/SV/Transforms/PrettifyVerilog.cpp +++ b/lib/Dialect/SV/Transforms/PrettifyVerilog.cpp @@ -260,7 +260,7 @@ void PrettifyVerilogPass::processPostOrder(Block &body) { // block as their use. This will allow the verilog emitter to inline // constant expressions and avoids ReadInOutOp from preventing motion. if (matchPattern(&op, mlir::m_Constant()) || - isa(op)) { + isa(op)) { sinkOrCloneOpToUses(&op); continue; } diff --git a/lib/Translation/ExportVerilog/ExportVerilog.cpp b/lib/Translation/ExportVerilog/ExportVerilog.cpp index 49ce9e729d..ac2eec1d61 100644 --- a/lib/Translation/ExportVerilog/ExportVerilog.cpp +++ b/lib/Translation/ExportVerilog/ExportVerilog.cpp @@ -75,9 +75,11 @@ enum VerilogPrecedence { //===----------------------------------------------------------------------===// /// Helper that prints a parameter constant value in a Verilog compatible way. -static void printParamValue(Attribute value, raw_ostream &os, - VerilogPrecedence parenthesizeIfLooserThan, - function_ref emitError) { +/// This returns the precedence of the generated string. +static VerilogPrecedence +printParamValue(Attribute value, raw_ostream &os, + VerilogPrecedence parenthesizeIfLooserThan, + function_ref emitError) { if (auto intAttr = value.dyn_cast()) { IntegerType intTy = intAttr.getType().cast(); APInt value = intAttr.getValue(); @@ -96,26 +98,26 @@ static void printParamValue(Attribute value, raw_ostream &os, os << intTy.getWidth() << "'d"; } value.print(os, intTy.isSigned()); - return; + return Symbol; } if (auto strAttr = value.dyn_cast()) { os << '"'; os.write_escaped(strAttr.getValue()); os << '"'; - return; + return Symbol; } if (auto fpAttr = value.dyn_cast()) { // TODO: relying on float printing to be precise is not a good idea. os << fpAttr.getValueAsDouble(); - return; + return Symbol; } if (auto verbatimParam = value.dyn_cast()) { os << verbatimParam.getValue().getValue(); - return; + return Symbol; } if (auto parameterRef = value.dyn_cast()) { os << parameterRef.getName().getValue(); - return; + return Symbol; } if (auto paramBinOp = value.dyn_cast()) { @@ -139,19 +141,25 @@ static void printParamValue(Attribute value, raw_ostream &os, printParamValue(paramBinOp.getLhs(), os, subprecedence, emitError); os << operatorStr; printParamValue(paramBinOp.getRhs(), os, subprecedence, emitError); - if (subprecedence > parenthesizeIfLooserThan) + if (subprecedence > parenthesizeIfLooserThan) { os << ')'; - return; + return Symbol; + } + return subprecedence; } os << "<>"; emitError() << " = " << value; + return LowestPrecedence; } /// Prints a parameter attribute expression in a Verilog compatible way. -static void printParamValue(Attribute value, raw_ostream &os, - function_ref emitError) { - printParamValue(value, os, VerilogPrecedence::LowestPrecedence, emitError); +/// This returns the precedence of the generated string. +static VerilogPrecedence +printParamValue(Attribute value, raw_ostream &os, + function_ref emitError) { + return printParamValue(value, os, VerilogPrecedence::LowestPrecedence, + emitError); } /// Return true for nullary operations that are better emitted multiple @@ -204,7 +212,7 @@ static StringRef getSymOpName(Operation *symOp) { /// MemoryEffects should be checked if a client cares. bool ExportVerilog::isVerilogExpression(Operation *op) { // These are SV dialect expressions. - if (isa(op) || isa(op)) + if (isa(op)) return true; // All HW combinatorial logic ops and SV expression ops are Verilog @@ -441,7 +449,7 @@ static StringRef getVerilogDeclWord(Operation *op, } if (isa(op)) return "wire"; - if (isa(op)) + if (isa(op)) return "localparam"; // Interfaces instances use the name of the declared interface. @@ -991,6 +999,7 @@ private: using TypeOpVisitor::visitTypeOp; SubExprInfo visitTypeOp(ConstantOp op); SubExprInfo visitTypeOp(BitcastOp op); + SubExprInfo visitTypeOp(ParamValueOp op); SubExprInfo visitTypeOp(ArraySliceOp op); SubExprInfo visitTypeOp(ArrayGetOp op); SubExprInfo visitTypeOp(ArrayCreateOp op); @@ -1455,6 +1464,13 @@ SubExprInfo ExprEmitter::visitTypeOp(ConstantOp op) { return {Unary, signPreference == RequireSigned ? IsSigned : IsUnsigned}; } +SubExprInfo ExprEmitter::visitTypeOp(ParamValueOp op) { + auto prec = printParamValue(op.value(), os, [&]() { + return op->emitOpError("invalid parameter use"); + }); + return {prec, IsUnsigned}; +} + // 11.5.1 "Vector bit-select and part-select addressing" allows a '+:' syntax // for slicing operations. SubExprInfo ExprEmitter::visitTypeOp(ArraySliceOp op) { diff --git a/test/Dialect/SV/hw-legalize-names.mlir b/test/Dialect/SV/hw-legalize-names.mlir index 555ba83424..844e29751f 100644 --- a/test/Dialect/SV/hw-legalize-names.mlir +++ b/test/Dialect/SV/hw-legalize-names.mlir @@ -116,6 +116,9 @@ hw.module @parameters(%p1: i8) { sv.ifdef "SOMEMACRO" { // CHECK: %local = sv.localparam : i1 {value = #hw.param.decl.ref<"wire_1">} %local = sv.localparam : i1 { value = #hw.param.decl.ref<"wire">: i1 } + + // CHECK: = hw.param.value i1 = #hw.param.decl.ref<"wire_1"> + %0 = hw.param.value i1 = #hw.param.decl.ref<"wire"> } // "wire" param getting updated should update in this instance. diff --git a/test/ExportVerilog/hw-dialect.mlir b/test/ExportVerilog/hw-dialect.mlir index bfe72a36f4..d62132a543 100644 --- a/test/ExportVerilog/hw-dialect.mlir +++ b/test/ExportVerilog/hw-dialect.mlir @@ -861,7 +861,7 @@ hw.module @UseParameterized(%a: i8) -> (ww: i8, xx: i8, yy: i8, zz: i8) { } // CHECK-LABEL: module UseParameterValue -hw.module @UseParameterValue(%arg0: i8) -> (out1: i8, out2: i8) { +hw.module @UseParameterValue(%arg0: i8) -> (out1: i8, out2: i8, out3: i8) { // CHECK-NEXT: #(parameter [41:0] xx) ( // CHECK: parameters2 #( @@ -878,7 +878,14 @@ hw.module @UseParameterValue(%arg0: i8) -> (out1: i8, out2: i8) { // CHECK-NEXT: .p1((xx + 42'd17) * yy) // CHECK-NEXT: ) inst3 ( %c = hw.instance "inst3" @parameters2, 17>, #hw.param.verbatim<"yy">>, p2: i1 = 0>(arg0: %arg0: i8) -> (out: i8) - - hw.output %a, %b : i8, i8 + + // CHECK: localparam [41:0] _T = xx + 42'd17; + // CHECK-NEXT: wire [7:0] _T_0 = _T[7:0]; + // CHECK-NEXT: assign out3 = _T_0 + _T_0; + %d = hw.param.value i42 = #hw.param.binary, 17> + %e = comb.extract %d from 0 : (i42) -> i8 + %f = comb.add %e, %e : i8 + + hw.output %a, %b, %f : i8, i8, i8 }