[Moore] Support unconnected behavior (#7202)

This commit is contained in:
cepheus 2024-06-20 11:12:31 +08:00 committed by GitHub
parent 1f6c29fb64
commit 560257cd4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 10 deletions

View File

@ -11,6 +11,35 @@ The main goal of the `moore` dialect is to provide a set of operations and types
In contrast, the `sv` dialect is geared towards emission of SystemVerilog text, and is focused on providing a good lowering target to allow for emission. The `moore` and `sv` dialect may eventually converge into a single dialect. As we are building out the Verilog frontend capabilities of CIRCT it is valuable to have a separate ingestion dialect, such that we do not have to make disruptive changes to the load-bearing `sv` dialect used in production.
## LRM Rules
### Unconnection rules
The SystemVerilog LRM defines unconnected behavior while leaving ports unconnected.
| Port Type | Unconnected Behavior |
| ---------------- | -------------------------- |
| Input (Net) | High-impedance value ('Z) |
| Input (Variable) | Default initial value |
| Output | No effect on Simulation |
| Inout (Net) | High-impedance value ('Z) |
| Inout (Variable) | Default initial value |
| Ref | Cannot be left unconnected |
| Interconnect | Cannot be left unconnected |
| Interface | Cannot be left unconnected |
For variables that do not have a specified initializer. It has a default rule to initialize data value according to its type:
| Type | Default initial value |
| --------------------------- | ------------------------------- |
| 4-state integral | 'X |
| 2-state integral | '0 |
| **real**, **shortreal** | 0.0 |
| Enumeration | Base type default initial value |
| **string** | "" (empty string) |
| **event** | New event |
| **class** | **null** |
| **interface class** | **null** |
| **chandle (Opaque handle)** | **null** |
| **virtual interface** | **null** |
## Types
@ -19,7 +48,7 @@ In contrast, the `sv` dialect is geared towards emission of SystemVerilog text,
The `moore.iN` and `moore.lN` types represent a two-valued or four-valued simple bit vector of width `N`.
| Verilog | Moore Dialect |
|------------|---------------|
| ---------- | ------------- |
| `bit` | `!moore.i1` |
| `logic` | `!moore.l1` |
| `reg` | `!moore.l1` |

View File

@ -120,9 +120,53 @@ struct MemberVisitor {
for (const auto *con : instNode.getPortConnections()) {
const auto *expr = con->getExpression();
if (!expr)
return mlir::emitError(loc)
<< "unconnected port `" << con->port.name << "` not supported";
// Handle unconnected behavior. The expression is null if it have no
// connection for the port.
if (!expr) {
auto *port = con->port.as_if<PortSymbol>();
switch (port->direction) {
case slang::ast::ArgumentDirection::In: {
auto refType = moore::RefType::get(
cast<moore::UnpackedType>(context.convertType(port->getType())));
if (const auto *net =
port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
auto netOp = builder.create<moore::NetOp>(
loc, refType, StringAttr::get(builder.getContext(), net->name),
convertNetKind(net->netType.netKind), nullptr);
auto readOp = builder.create<moore::ReadOp>(
loc, refType.getNestedType(), netOp);
portValues.insert({port, readOp});
} else if (const auto *var =
port->internalSymbol
->as_if<slang::ast::VariableSymbol>()) {
auto varOp = builder.create<moore::VariableOp>(
loc, refType, StringAttr::get(builder.getContext(), var->name),
nullptr);
auto readOp = builder.create<moore::ReadOp>(
loc, refType.getNestedType(), varOp);
portValues.insert({port, readOp});
} else {
return mlir::emitError(loc)
<< "unsupported internal symbol for unconnected port `"
<< port->name << "`";
}
continue;
}
// No need to express unconnected behavior for output port, skip to the
// next iteration of the loop.
case slang::ast::ArgumentDirection::Out:
continue;
// TODO: Mark Inout port as unsupported and it will be supported later.
default:
return mlir::emitError(loc)
<< "unsupported port `" << port->name << "` ("
<< slang::ast::toString(port->kind) << ")";
}
}
// Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
// output and inout ports.
@ -185,7 +229,6 @@ struct MemberVisitor {
for (auto &port : moduleLowering->ports) {
auto value = portValues.lookup(&port.ast);
assert(value && "no prepared value for port");
if (port.ast.direction == ArgumentDirection::Out)
outputValues.push_back(value);
else
@ -202,7 +245,8 @@ struct MemberVisitor {
// Assign output values from the instance to the connected expression.
for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs()))
builder.create<moore::ContinuousAssignOp>(loc, lvalue, output);
if (lvalue)
builder.create<moore::ContinuousAssignOp>(loc, lvalue, output);
return success();
}

View File

@ -1054,6 +1054,23 @@ module PortsTop;
// CHECK-NEXT: moore.assign [[V1]], [[V1_VALUE]]
// CHECK-NEXT: moore.assign [[C1]], [[C1_VALUE]]
MultiPorts p3(x3, y3, z3, w3);
wire x4, y4;
// CHECK: %a = moore.net wire : <l1>
// CHECK: [[A_VALUE:%.+]] = moore.read %a : l1
// CHECK: [[X4:%.+]] = moore.read %x4 : l1
// CHECK: %c = moore.variable : <l1>
// CHECK: [[C_VALUE:%.+]] = moore.read %c : l1
// CHECK: [[D_VALUE:%.+]], [[E_VALUE:%.+]] = moore.instance "p4" @PortsUnconnected(
// CHECK-SAME: a: [[A_VALUE]]: !moore.l1
// CHECK-SAME: b: [[X4]]: !moore.l1
// CHECK-SAME: c: [[C_VALUE]]: !moore.l1
// CHECK-SAME: ) -> (
// CHECK-SAME: d: !moore.l1
// CHECK-SAME: e: !moore.l1
// CHECK-SAME: )
// CHECK: moore.assign %y4, [[D_VALUE]] : l1
PortsUnconnected p4(.a(), .b(x4), .c(), .d(y4), .e());
endmodule
// CHECK-LABEL: moore.module @PortsAnsi
@ -1084,14 +1101,14 @@ module PortsAnsi(
endmodule
// CHECK-LABEL: moore.module @PortsNonAnsi
// CHECK-SAME: in %a : !moore.l1
// CHECK-SAME: out b : !moore.l1
// CHECK-SAME: in %c : !moore.ref<l1>
// CHECK-SAME: in %d : !moore.ref<l1>
module PortsNonAnsi(a, b, c, d);
// CHECK-SAME: in %a : !moore.l1
input a;
// CHECK-SAME: out b : !moore.l1
output b;
// CHECK-SAME: in %c : !moore.ref<l1>
inout c;
// CHECK-SAME: in %d : !moore.ref<l1>
ref logic d;
endmodule
@ -1167,6 +1184,34 @@ module MultiPorts(
// CHECK: moore.output [[V1_READ]], [[C1_READ]]
endmodule
// CHECK-LABEL: moore.module @PortsUnconnected
module PortsUnconnected(
// CHECK-SAME: in %a : !moore.l1
input a,
// CHECK-SAME: in %b : !moore.l1
input b,
// CHECK-SAME: in %c : !moore.l1
input logic c,
// CHECK-SAME: out d : !moore.l1
output d,
// CHECK-SAME: out e : !moore.l1
output e
);
// Internal nets and variables created by Slang for each port.
// CHECK: [[A_INT:%.+]] = moore.net name "a" wire : <l1>
// CHECK: [[B_INT:%.+]] = moore.net name "b" wire : <l1>
// CHECK: [[C_INT:%.+]] = moore.variable name "c" : <l1>
// CHECK: [[D_INT:%.+]] = moore.net wire : <l1>
// CHECK: [[E_INT:%.+]] = moore.net wire : <l1>
// Mapping ports to local declarations.
// CHECK: moore.assign [[A_INT]], %a : l1
// CHECK: moore.assign [[B_INT]], %b : l1
// CHECK: [[D_READ:%.+]] = moore.read [[D_INT]] : l1
// CHECK: [[E_READ:%.+]] = moore.read [[E_INT]] : l1
// CHECK: moore.output [[D_READ]], [[E_READ]] : !moore.l1, !moore.l1
endmodule
// CHECK-LABEL: moore.module @EventControl(in %clk : !moore.l1)
module EventControl(input clk);
// CHECK: %clk_0 = moore.net name "clk" wire : <l1>