[HW Parameters] add the hw.param.value op.

This projects an parameter expression into an SSA value, just like
hw.constant does for simple integer constants.
This commit is contained in:
Chris Lattner 2021-09-26 17:54:11 -07:00
parent a69675b743
commit 98263fed75
9 changed files with 111 additions and 35 deletions

View File

@ -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<param1: i1>(%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<param1: i1>(%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

View File

@ -57,12 +57,8 @@ def KnownBitWidthType : Type<CPred<[{getBitWidth($_self) != -1}]>,
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<ParamValue>($value, type($result)) attr-dict";
}

View File

@ -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);

View File

@ -179,6 +179,24 @@ OpFoldResult ConstantOp::fold(ArrayRef<Attribute> 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<ArrayType>(ty);
if (!arrTy)
return p.emitError(p.getCurrentLocation(), "Expected !hw.array type");

View File

@ -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<ParamDeclRefAttr>();
assert(parameterRef && "Unknown kind of parameter expression");
@ -344,6 +342,16 @@ rewriteModuleBody(Block &block, NameCollisionResolver &nameResolver,
continue;
}
if (auto paramValue = dyn_cast<ParamValueOp>(op)) {
// If the initializer value in the local param was renamed then update it.
if (moduleHasRenamedInterface) {
auto curModule = op.getParentOfType<HWModuleOp>();
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.

View File

@ -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<sv::ReadInOutOp, sv::ArrayIndexInOutOp>(op)) {
isa<sv::ReadInOutOp, sv::ArrayIndexInOutOp, hw::ParamValueOp>(op)) {
sinkOrCloneOpToUses(&op);
continue;
}

View File

@ -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<InFlightDiagnostic()> emitError) {
/// This returns the precedence of the generated string.
static VerilogPrecedence
printParamValue(Attribute value, raw_ostream &os,
VerilogPrecedence parenthesizeIfLooserThan,
function_ref<InFlightDiagnostic()> emitError) {
if (auto intAttr = value.dyn_cast<IntegerAttr>()) {
IntegerType intTy = intAttr.getType().cast<IntegerType>();
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<StringAttr>()) {
os << '"';
os.write_escaped(strAttr.getValue());
os << '"';
return;
return Symbol;
}
if (auto fpAttr = value.dyn_cast<FloatAttr>()) {
// 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<ParamVerbatimAttr>()) {
os << verbatimParam.getValue().getValue();
return;
return Symbol;
}
if (auto parameterRef = value.dyn_cast<ParamDeclRefAttr>()) {
os << parameterRef.getName().getValue();
return;
return Symbol;
}
if (auto paramBinOp = value.dyn_cast<ParamBinaryAttr>()) {
@ -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 << "<<UNKNOWN MLIRATTR: " << value << ">>";
emitError() << " = " << value;
return LowestPrecedence;
}
/// Prints a parameter attribute expression in a Verilog compatible way.
static void printParamValue(Attribute value, raw_ostream &os,
function_ref<InFlightDiagnostic()> 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<InFlightDiagnostic()> 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<ReadInOutOp>(op) || isa<ArrayIndexInOutOp>(op))
if (isa<ReadInOutOp, ArrayIndexInOutOp, ParamValueOp>(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<WireOp>(op))
return "wire";
if (isa<ConstantOp, LocalParamOp>(op))
if (isa<ConstantOp, LocalParamOp, ParamValueOp>(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) {

View File

@ -116,6 +116,9 @@ hw.module @parameters<p1: i42 = 17, wire: i1>(%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.

View File

@ -861,7 +861,7 @@ hw.module @UseParameterized(%a: i8) -> (ww: i8, xx: i8, yy: i8, zz: i8) {
}
// CHECK-LABEL: module UseParameterValue
hw.module @UseParameterValue<xx: i42>(%arg0: i8) -> (out1: i8, out2: i8) {
hw.module @UseParameterValue<xx: i42>(%arg0: i8) -> (out1: i8, out2: i8, out3: i8) {
// CHECK-NEXT: #(parameter [41:0] xx) (
// CHECK: parameters2 #(
@ -878,7 +878,14 @@ hw.module @UseParameterValue<xx: i42>(%arg0: i8) -> (out1: i8, out2: i8) {
// CHECK-NEXT: .p1((xx + 42'd17) * yy)
// CHECK-NEXT: ) inst3 (
%c = hw.instance "inst3" @parameters2<p1: i42 = #hw.param.binary<mul #hw.param.binary<add #hw.param.verbatim<"xx">, 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<add #hw.param.verbatim<"xx">, 17>
%e = comb.extract %d from 0 : (i42) -> i8
%f = comb.add %e, %e : i8
hw.output %a, %b, %f : i8, i8, i8
}