From c76eb120d675fc093ca114d288ca25548be85e7a Mon Sep 17 00:00:00 2001 From: Anqi Yu Date: Wed, 5 Jun 2024 10:44:55 +0800 Subject: [PATCH] [ImportVerilog] Add conditional operator. (#6950) --- include/circt/Dialect/Moore/MooreOps.td | 175 +++++++++++++------ lib/Conversion/ImportVerilog/Expressions.cpp | 35 ++++ lib/Dialect/Moore/MooreOps.cpp | 24 +++ test/Conversion/ImportVerilog/basic.sv | 38 ++++ test/Dialect/Moore/basic.mlir | 11 ++ test/Dialect/Moore/errors.mlir | 22 +++ 6 files changed, 251 insertions(+), 54 deletions(-) diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index a993bf2425c5..b567d351f46c 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -223,17 +223,18 @@ def NetOp : MooreOp<"net", [ ]> { let summary = "A net declaration"; let description = [{ - The `moore.net' operation is a net declaration. Net types defines different types - of net connection in SV. There are twelve built-in net types defined in the official - standard construct of the operation: - `supply0`, `supply1`, `tri`, `triand`, `trior`, `trireg`, `tri0`, `tri1`, `uwire`, - `wire`, `wand`, `wor`. - Optional assignment argument allows net operation to be initialized with specific - values as soon as it is created. Only one net declaration assignment can be made for - a particular net. See IEEE 1800-2017 § 10.3.1 "The net declaration assignment" for - the differences between net declaration assignments and continuous assign statements. - It has some features that are not supported: declaring an interconnect net and using - user-defined types in the net operation. + The `moore.net' operation is a net declaration. Net types defines different + types of net connection in SV. There are twelve built-in net types defined + in the official standard construct of the operation: + `supply0`, `supply1`, `tri`, `triand`, `trior`, `trireg`, `tri0`, `tri1`, + `uwire`, `wire`, `wand`, `wor`. + Optional assignment argument allows net operation to be initialized with + specific values as soon as it is created. Only one net declaration + assignment can be made for a particular net. See IEEE 1800-2017 § 10.3.1 + "The net declaration assignment" for the differences between net declaration + assignments and continuous assign statements. It has some features that are + not supported: declaring an interconnect net and using user-defined types in + the net operation. See IEEE 1800-2017 § 6.7 "Net declarations". }]; @@ -961,27 +962,27 @@ def StructInjectOp : MooreOp<"struct_inject"> { def UnionCreateOp : MooreOp<"union_create"> { let summary = "Union Create operation"; let description = [{ - A union is a data type that represents a single piece - of storage that can be accessed using one of - the named member data types. Only one of the - data types in the union can be used at a time. - By default, a union is unpacked, meaning there - is no required representation for how members - of the union are stored. Dynamic types and chandle - types can only be used in tagged unions. - See IEEE 1800-2017 § 7.3 "Unions" - - Example: - ``` - typedef union { int i; shortreal f; } num; // named union type - num n; - n.f = 0.0; // set n in floating point format - typedef struct { - bit isfloat; - union { int i; shortreal f; } n; // anonymous union type - } tagged_st; // named structure - ``` - See IEEE 1800-2017 § 7.3 "Unions" + A union is a data type that represents a single piece + of storage that can be accessed using one of + the named member data types. Only one of the + data types in the union can be used at a time. + By default, a union is unpacked, meaning there + is no required representation for how members + of the union are stored. Dynamic types and chandle + types can only be used in tagged unions. + See IEEE 1800-2017 § 7.3 "Unions" + + Example: + ``` + typedef union { int i; shortreal f; } num; // named union type + num n; + n.f = 0.0; // set n in floating point format + typedef struct { + bit isfloat; + union { int i; shortreal f; } n; // anonymous union type + } tagged_st; // named structure + ``` + See IEEE 1800-2017 § 7.3 "Unions" }]; let arguments = (ins StrAttr:$unionName, UnpackedType:$input); let results = (outs UnpackedType:$result); @@ -989,30 +990,30 @@ def UnionCreateOp : MooreOp<"union_create"> { $unionName `,` $input attr-dict `:` type($input) `->` type($result) }]; - } +} - def UnionExtractOp : MooreOp<"union_extract"> { +def UnionExtractOp : MooreOp<"union_extract"> { let summary = "Union Extract operation"; let description = [{ - With packed unions, writing one member and reading another is - independent of the byte ordering of the machine, - unlike an unpacked union of unpacked structures, - which are C-compatible and have members in ascending address order. - See IEEE 1800-2017 § 7.3.1 "Packed unions" - - Example: - ``` - typedef union packed { // default unsigned - s_atmcell acell; - bit [423:0] bit_slice; - bit [52:0][7:0] byte_slice; - } u_atmcell; - u_atmcell u1; - byte b; bit [3:0] nib; - b = u1.bit_slice[415:408]; // same as b = u1.byte_slice[51]; - nib = u1.bit_slice [423:420]; - ``` - See IEEE 1800-2017 § 7.3.1 "Packed unions" + With packed unions, writing one member and reading another is + independent of the byte ordering of the machine, + unlike an unpacked union of unpacked structures, + which are C-compatible and have members in ascending address order. + See IEEE 1800-2017 § 7.3.1 "Packed unions" + + Example: + ``` + typedef union packed { // default unsigned + s_atmcell acell; + bit [423:0] bit_slice; + bit [52:0][7:0] byte_slice; + } u_atmcell; + u_atmcell u1; + byte b; bit [3:0] nib; + b = u1.bit_slice[415:408]; // same as b = u1.byte_slice[51]; + nib = u1.bit_slice [423:420]; + ``` + See IEEE 1800-2017 § 7.3.1 "Packed unions" }]; let arguments = (ins StrAttr:$memberName, UnpackedType:$input); let results = (outs UnpackedType:$result); @@ -1020,5 +1021,71 @@ def UnionCreateOp : MooreOp<"union_create"> { $input `,` $memberName attr-dict `:` type($input) `->` type($result) }]; - } +} + +def ConditionalOp : MooreOp<"conditional",[ + RecursiveMemoryEffects, + NoRegionArguments, + SingleBlockImplicitTerminator<"moore::YieldOp"> +]> { + let summary = "Conditional operation"; + let description = [{ + If cond_predicate is true, the operator returns the value of the first + expression without evaluating the second expression; if false, it returns + the value of the second expression without evaluating the first expression. + If cond_predicate evaluates to an ambiguous value (x or z), then both the + first expression and the second expression shall be evaluated, and compared + for logical equivalence. If that comparison is true (1), the operator shall + return either the first or second expression. Otherwise the operator returns + a result based on the data types of the expressions. + + When both the first and second expressions are of integral types, if the + cond_predicate evaluates to an ambiguous value and the expressions are not + logically equivalent, their results shall be combined bit by bit using the + table below to calculate the final result. The first and second expressions + are extended to the same width. + + |?: | 0 | 1 | X | Z | + |---|---|---|---|---| + | 0 | 0 | X | X | X | + | 1 | X | 1 | X | X | + | X | X | X | X | X | + | Z | X | X | X | X | + + See IEEE 1800-2017 § 11.4.11 "Conditional operator". + }]; + let arguments = (ins AnySingleBitType:$condition); + let results = (outs UnpackedType:$result); + let regions = (region SizedRegion<1>:$trueRegion, + SizedRegion<1>:$falseRegion); + let assemblyFormat = [{ + $condition attr-dict `:` type($condition) `->` type($result) + $trueRegion $falseRegion + }]; +} + +def YieldOp : MooreOp<"yield", [ + Pure, + Terminator, + HasParent<"ConditionalOp"> +]> { + let summary = "conditional yield and termination operation"; + let description = [{ + "moore.yield" yields an SSA value from the Moore dialect op region and + terminates the regions. The semantics of how the values are yielded is + defined by the parent operation. + If "moore.yield" has any operands, the operands must match the parent + operation's results. + If the parent operation defines no values, then the "moore.yield" may be + left out in the custom syntax and the builders will insert one implicitly. + Otherwise, it has to be present in the syntax to indicate which values are + yielded. + }]; + let arguments = (ins UnpackedType:$result); + let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; + let assemblyFormat = [{ + attr-dict $result `:` type($result) + }]; + let hasVerifier = 1; +} #endif // CIRCT_DIALECT_MOORE_MOOREOPS diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index e013287df3ee..a6feb849e450 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -514,6 +514,41 @@ struct ExprVisitor { return result; } + // Handle conditional operator `?:`. + Value visit(const slang::ast::ConditionalExpression &expr) { + auto type = context.convertType(*expr.type); + + // Handle condition. + Value cond = convertToSimpleBitVector( + context.convertExpression(*expr.conditions.begin()->expr)); + cond = convertToBool(cond); + if (!cond) + return {}; + auto conditionalOp = builder.create(loc, type, cond); + + // Create blocks for true region and false region. + conditionalOp.getTrueRegion().emplaceBlock(); + conditionalOp.getFalseRegion().emplaceBlock(); + + OpBuilder::InsertionGuard g(builder); + + // Handle left expression. + builder.setInsertionPointToStart(conditionalOp.getBody(0)); + auto trueValue = context.convertExpression(expr.left()); + if (!trueValue) + return {}; + builder.create(loc, trueValue); + + // Handle right expression. + builder.setInsertionPointToStart(conditionalOp.getBody(1)); + auto falseValue = context.convertExpression(expr.right()); + if (!falseValue) + return {}; + builder.create(loc, falseValue); + + return conditionalOp.getResult(); + } + /// Emit an error for all other expressions. template Value visit(T &&node) { diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 77ee83d08426..61990918d5c7 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -353,6 +353,30 @@ LogicalResult ConcatOp::inferReturnTypes( return success(); } +//===----------------------------------------------------------------------===// +// YieldOp +//===----------------------------------------------------------------------===// + +LogicalResult YieldOp::verify() { + // Check that YieldOp's parent operation is ConditionalOp. + auto cond = dyn_cast(*(*this).getParentOp()); + if (!cond) { + emitOpError("must have a conditional parent"); + return failure(); + } + + // Check that the operand matches the parent operation's result. + auto condType = cond.getType(); + auto yieldType = getOperand().getType(); + if (condType != yieldType) { + emitOpError("yield type must match conditional. Expected ") + << condType << ", but got " << yieldType << "."; + return failure(); + } + + return success(); +} + //===----------------------------------------------------------------------===// // ConversionOp //===----------------------------------------------------------------------===// diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 457bbb447f13..2950db4e29a2 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -350,6 +350,9 @@ module Expressions; int a, b; } c, d; } union1; + // CHECK: %r1 = moore.variable : real + // CHECK: %r2 = moore.variable : real + real r1,r2; initial begin // CHECK: moore.constant 0 : i32 @@ -600,6 +603,41 @@ module Expressions; // CHECK: moore.or [[TMP1]], [[TMP6]] : i1 c = a inside { a, b, [a:b] }; + //===------------------------------------------------------------------===// + // Conditional operator + + // CHECK: moore.conditional %x : i1 -> i32 { + // CHECK: moore.yield %a : i32 + // CHECK: } { + // CHECK: moore.yield %b : i32 + // CHECK: } + c = x ? a : b; + + // CHECK: moore.conditional %x : i1 -> real { + // CHECK: moore.yield %r1 : real + // CHECK: } { + // CHECK: moore.yield %r2 : real + // CHECK: } + r1 = x ? r1 : r2; + + // CHECK: [[TMP1:%.+]] = moore.bool_cast %a : i32 -> i1 + // CHECK: moore.conditional [[TMP1]] : i1 -> i32 { + // CHECK: moore.yield %a : i32 + // CHECK: } { + // CHECK: moore.yield %b : i32 + // CHECK: } + c = a ? a : b; + + // CHECK: [[TMP1:%.+]] = moore.sgt %a, %b : i32 -> i1 + // CHECK: moore.conditional [[TMP1]] : i1 -> i32 { + // CHECK: [[TMP2:%.+]] = moore.add %a, %b : i32 + // CHECK: moore.yield [[TMP2]] : i32 + // CHECK: } { + // CHECK: [[TMP2:%.+]] = moore.sub %a, %b : i32 + // CHECK: moore.yield [[TMP2]] : i32 + // CHECK: } + c = (a > b) ? (a + b) : (a - b); + //===------------------------------------------------------------------===// // Assign operators diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index deb302b68748..9b459794983e 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -217,4 +217,15 @@ moore.module @Expressions() { // CHECK: moore.extract [[VAL4]] from [[VAL5]] : i8, i32 -> i5 %5 = moore.constant 2 : i32 moore.extract %4 from %5 : i8, i32 -> i5 + + // CHECK: moore.conditional %b1 : i1 -> i32 { + // CHECK: moore.yield %int : i32 + // CHECK: } { + // CHECK: moore.yield %int2 : i32 + // CHECK: } + moore.conditional %b1 : i1 -> i32 { + moore.yield %int : i32 + } { + moore.yield %int2 : i32 + } } diff --git a/test/Dialect/Moore/errors.mlir b/test/Dialect/Moore/errors.mlir index 1bcdfec6c485..2a3c7882eab3 100644 --- a/test/Dialect/Moore/errors.mlir +++ b/test/Dialect/Moore/errors.mlir @@ -65,3 +65,25 @@ moore.constant -2 : !moore.i1 // expected-error @below {{attribute width 9 does not match return type's width 8}} "moore.constant" () {value = 42 : i9} : () -> !moore.i8 + +// ----- + +%y = moore.variable : i8 + +moore.module @Cond() { + // expected-error @below {{'moore.yield' op expects parent op 'moore.conditional'}} + moore.yield %y : i8 +} + +// ----- + +%x = moore.variable : i1 +%t = moore.variable : i8 +%f = moore.variable : i8 + +moore.conditional %x : i1 -> i32 { + // expected-error @below {{yield type must match conditional. Expected '!moore.i32', but got '!moore.i8'}} + moore.yield %t : i8 +} { + moore.yield %f : i8 +}