Skip to content

Commit

Permalink
[ImportVerilog] Add conditional operator. (#6950)
Browse files Browse the repository at this point in the history
  • Loading branch information
angelzzzzz authored Jun 5, 2024
1 parent 2189f25 commit c76eb12
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 54 deletions.
175 changes: 121 additions & 54 deletions include/circt/Dialect/Moore/MooreOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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".
}];
Expand Down Expand Up @@ -961,64 +962,130 @@ 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);
let assemblyFormat = [{
$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);
let assemblyFormat = [{
$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
35 changes: 35 additions & 0 deletions lib/Conversion/ImportVerilog/Expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<moore::ConditionalOp>(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<moore::YieldOp>(loc, trueValue);

// Handle right expression.
builder.setInsertionPointToStart(conditionalOp.getBody(1));
auto falseValue = context.convertExpression(expr.right());
if (!falseValue)
return {};
builder.create<moore::YieldOp>(loc, falseValue);

return conditionalOp.getResult();
}

/// Emit an error for all other expressions.
template <typename T>
Value visit(T &&node) {
Expand Down
24 changes: 24 additions & 0 deletions lib/Dialect/Moore/MooreOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConditionalOp>(*(*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
//===----------------------------------------------------------------------===//
Expand Down
38 changes: 38 additions & 0 deletions test/Conversion/ImportVerilog/basic.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions test/Dialect/Moore/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
22 changes: 22 additions & 0 deletions test/Dialect/Moore/errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit c76eb12

Please sign in to comment.