mirror of https://github.com/llvm/circt.git
[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:
parent
a69675b743
commit
98263fed75
|
@ -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
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue