Skip to content

Commit

Permalink
[SV] Added DPI import and call ops
Browse files Browse the repository at this point in the history
  • Loading branch information
nandor committed Mar 20, 2023
1 parent ea58dfa commit 0a63652
Show file tree
Hide file tree
Showing 11 changed files with 510 additions and 18 deletions.
1 change: 1 addition & 0 deletions include/circt/Dialect/SV/SV.td
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ include "circt/Dialect/SV/SVInOutOps.td"
include "circt/Dialect/SV/SVGenerate.td"
include "circt/Dialect/SV/SVStatements.td"
include "circt/Dialect/SV/SVVerification.td"
include "circt/Dialect/SV/SVDPI.td"
include "circt/Dialect/SV/SVTypeDecl.td"

#endif // CIRCT_DIALECT_SV_SV
73 changes: 73 additions & 0 deletions include/circt/Dialect/SV/SVDPI.td
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//===- SVDPI.td - SV DPI Interface Ops ---------------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This describes the ops for SystemVerilog verification statements and
// declarations.
//
//===----------------------------------------------------------------------===//

include "mlir/IR/FunctionInterfaces.td"

def DPIImportOp : SVOp<"dpi.import", [
FunctionOpInterface,
Symbol,
HasParent<"mlir::ModuleOp">]> {
let summary = "Import a DPI-C function";
let description = [{
Represents a SV DPI-C import statement. Imports a C function for use.

```
import "DPI-C" function void some_function(
input bit[31:0] value_in,
output bit[31:0] value_out
)
```
}];

// TODO: Add support for the `pure` attribute.
// TODO: Add support for return values.

let arguments = (ins TypeAttrOf<FunctionType>:$function_type,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs,
StrArrayAttr:$argNames,
StrArrayAttr:$resultNames);

let regions = (region AnyRegion:$body);

let extraClassDeclaration = [{
/// Returns the argument types of this function.
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }
/// Returns the result types of this function.
ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); }
}];

let hasCustomAssemblyFormat = 1;
}

def DPICallOp : SVOp<"dpi.call", [
ProceduralOp,
DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
let summary = "Call a DPI-C function";
let description = [{
Invokes a DPI-C function from a procedural block.

```
test(counter + 1, result)
```
}];

// TODO: turn this into an arbitrary SV expression and support return types.

let arguments = (ins FlatSymbolRefAttr:$callee, Variadic<AnyType>:$args);
let results = (outs Variadic<AnyType>);

let assemblyFormat = [{
$callee `(` $args `)` attr-dict`:` functional-type($args, results)
}];
}
2 changes: 2 additions & 0 deletions include/circt/Dialect/SV/SVOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
#define CIRCT_DIALECT_SV_OPS_H

#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/SV/SVAttributes.h"
#include "circt/Dialect/SV/SVDialect.h"
#include "circt/Dialect/SV/SVTypes.h"
#include "mlir/IR/FunctionInterfaces.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/Interfaces/InferTypeOpInterface.h"
Expand Down
6 changes: 6 additions & 0 deletions include/circt/Dialect/SV/SVVisitors.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class Visitor {
InterfaceOp, InterfaceSignalOp, InterfaceModportOp,
InterfaceInstanceOp, GetModportOp, AssignInterfaceSignalOp,
ReadInterfaceSignalOp,
// DPI ops.
DPIImportOp, DPICallOp,
// Verification statements.
AssertOp, AssumeOp, CoverOp, AssertConcurrentOp, AssumeConcurrentOp,
CoverConcurrentOp,
Expand Down Expand Up @@ -136,6 +138,10 @@ class Visitor {
HANDLE(AssignInterfaceSignalOp, Unhandled);
HANDLE(ReadInterfaceSignalOp, Unhandled);

// DPI ops.
HANDLE(DPIImportOp, Unhandled);
HANDLE(DPICallOp, Unhandled);

// Verification statements.
HANDLE(AssertOp, Unhandled);
HANDLE(AssumeOp, Unhandled);
Expand Down
9 changes: 9 additions & 0 deletions integration_test/EmitVerilog/lint.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,12 @@ hw.module @UniformArrayCreate() -> (arr: !hw.array<5xi8>) {
%arr = hw.array_create %c0_i8, %c0_i8, %c0_i8, %c0_i8, %c0_i8 : i8
hw.output %arr : !hw.array<5xi8>
}

sv.dpi.import @test(%arg0: i32) -> (res0: i5, res1: i6)

hw.module @top(%clk: i1, %arg1: i64) -> () {
%arg0 = hw.constant 0 : i32
sv.alwaysff(posedge %clk) {
%res0, %res1 = sv.dpi.call @test(%arg0) : (i32) -> (i5, i6)
}
}
86 changes: 84 additions & 2 deletions lib/Conversion/ExportVerilog/ExportVerilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3012,6 +3012,9 @@ class StmtEmitter : public EmitterBase,
const SmallPtrSetImpl<Operation *> &locationOps,
StringRef multiLineComment = StringRef());

LogicalResult visitSV(DPIImportOp op);
LogicalResult visitSV(DPICallOp op);

public:
ModuleEmitter &emitter;

Expand Down Expand Up @@ -3091,6 +3094,9 @@ LogicalResult StmtEmitter::visitSV(AssignOp op) {
}

LogicalResult StmtEmitter::visitSV(BPAssignOp op) {
if (dyn_cast_or_null<DPICallOp>(op.getSrc().getDefiningOp()))
return success();

// If the assign is emitted into logic declaration, we must not emit again.
if (emitter.assignsInlined.count(op))
return success();
Expand Down Expand Up @@ -4239,6 +4245,82 @@ LogicalResult StmtEmitter::visitSV(AssignInterfaceSignalOp op) {
return success();
}

LogicalResult StmtEmitter::visitSV(DPIImportOp op) {
startStatement();

ps << "import \"DPI-C\" function void ";
ps << PPExtString(getSymOpName(op));
ps << "(";

ps.scopedBox(PP::bbox2, [&]() {
bool needsComma = false;
auto printArg = [&](StringRef kind, Attribute name, Type ty) {
if (needsComma)
ps << ",";
ps << PP::newline << kind << " ";

// Emit the type.
{
SmallString<8> typeString;
llvm::raw_svector_ostream stringStream(typeString);
emitter.printPackedType(stripUnpackedTypes(ty), stringStream,
op->getLoc());
if (!typeString.empty())
ps << typeString;
}

ps << " " << PPExtString(name.cast<StringAttr>().getValue());

// Print out any array subscripts or other post-name stuff.
ps.invokeWithStringOS(
[&](auto &os) { emitter.printUnpackedTypePostfix(ty, os); });
needsComma = true;
};

for (const auto &[name, ty] :
llvm::zip(op.getArgNames(), op.getArgumentTypes()))
printArg("input ", name, ty);
for (const auto &[name, ty] :
llvm::zip(op.getResultNames(), op.getResultTypes()))
printArg("output", name, ty);
});

ps << PP::newline << ");" << PP::newline;
setPendingNewline();
return success();
}

LogicalResult StmtEmitter::visitSV(DPICallOp op) {
startStatement();

auto topModuleOp = op->getParentOfType<ModuleOp>();
Operation *callee = topModuleOp.lookupSymbol(op.getCallee());

ps << PPExtString(getSymOpName(callee)) << "(";

SmallPtrSet<Operation *, 8> ops;
ops.insert(op);

bool needsComma = false;
auto printArg = [&](Value value) {
if (needsComma)
ps << "," << PP::space;
emitExpression(value, ops);
needsComma = true;
};

ps.scopedBox(PP::ibox0, [&] {
for (Value arg : op.getArgs())
printArg(arg);
for (Value result : op.getResults())
printArg(result.getUsers().begin()->getOperand(0));
});

ps << ");";
emitLocationInfoAndNewLine(ops);
return success();
}

void StmtEmitter::emitStatement(Operation *op) {
// Expressions may either be ignored or emitted as an expression statements.
if (isVerilogExpression(op))
Expand Down Expand Up @@ -5095,7 +5177,7 @@ void SharedEmitterState::gatherFiles(bool separateModules) {
else
rootFile.ops.push_back(info);
})
.Case<VerbatimOp, IfDefOp>([&](Operation *op) {
.Case<VerbatimOp, IfDefOp, DPIImportOp>([&](Operation *op) {
// Emit into a separate file using the specified file name or
// replicate the operation in each outputfile.
if (!attr) {
Expand Down Expand Up @@ -5192,7 +5274,7 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) {
.Case<BindOp>([&](auto op) { ModuleEmitter(state).emitBind(op); })
.Case<BindInterfaceOp>(
[&](auto op) { ModuleEmitter(state).emitBindInterface(op); })
.Case<InterfaceOp, VerbatimOp, IfDefOp>(
.Case<InterfaceOp, VerbatimOp, IfDefOp, DPIImportOp>(
[&](auto op) { ModuleEmitter(state).emitStatement(op); })
.Case<TypeScopeOp>([&](auto typedecls) {
ModuleEmitter(state).emitStatement(typedecls);
Expand Down
83 changes: 67 additions & 16 deletions lib/Conversion/ExportVerilog/PrepareForEmission.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,38 @@ static void spillWiresForInstanceInputs(InstanceOp op) {
}
}

// Introduces a wire to replace an output port SSA wire. If the operation
// is in a procedural region, it creates a temporary register, otherwise it
// places a wire. The connecting op is inserted in the op's region.
static void replacePortWithWire(ImplicitLocOpBuilder &builder, Operation *op,
Value result, StringRef name) {

bool isProcedural = op->getParentOp()->hasTrait<ProceduralRegion>();

Value newTarget;
if (isProcedural) {
newTarget = builder.create<sv::RegOp>(result.getType(),
builder.getStringAttr(name));
} else {
newTarget = builder.create<sv::WireOp>(result.getType(), name);
}

while (!result.use_empty()) {
auto newRead = builder.create<sv::ReadInOutOp>(newTarget);
OpOperand &use = *result.getUses().begin();
use.set(newRead);
newRead->moveBefore(use.getOwner());
}

Operation *connect;
if (isProcedural) {
connect = builder.create<sv::BPAssignOp>(newTarget, result);
} else {
connect = builder.create<sv::AssignOp>(newTarget, result);
}
connect->moveAfter(op);
}

// Ensure that each output of an instance are used only by a wire
static void lowerInstanceResults(InstanceOp op) {
Block *block = op->getParentOfType<HWModuleOp>().getBodyBlock();
Expand All @@ -100,16 +132,17 @@ static void lowerInstanceResults(InstanceOp op) {
SmallString<32> nameTmp{"_", op.instanceName(), "_"};
auto namePrefixSize = nameTmp.size();

size_t nextResultNo = 0;
for (auto &port : getModulePortInfo(op).outputs) {
auto result = op.getResult(nextResultNo);
++nextResultNo;
for (auto &[i, port] : llvm::enumerate(getModulePortInfo(op).outputs)) {
Value result = op.getResult(i);

if (result.hasOneUse()) {
OpOperand &use = *result.getUses().begin();
if (dyn_cast_or_null<OutputOp>(use.getOwner()))
Operation *user = *result.getUsers().begin();
if (dyn_cast_or_null<OutputOp>(user)) {
// Output op users of the instance are handled during the lowering
// of the instance op itself.
continue;
if (auto assign = dyn_cast_or_null<AssignOp>(use.getOwner())) {
}
if (auto assign = dyn_cast_or_null<AssignOp>(user)) {
// Move assign op after instance to resolve cyclic dependencies.
assign->moveAfter(op);
continue;
Expand All @@ -120,18 +153,32 @@ static void lowerInstanceResults(InstanceOp op) {
if (port.name)
nameTmp += port.name.getValue().str();
else
nameTmp += std::to_string(nextResultNo - 1);
Value newWire = builder.create<sv::WireOp>(result.getType(), nameTmp);
nameTmp += std::to_string(i);
replacePortWithWire(builder, op, result, nameTmp);
}
}

while (!result.use_empty()) {
auto newWireRead = builder.create<ReadInOutOp>(newWire);
OpOperand &use = *result.getUses().begin();
use.set(newWireRead);
newWireRead->moveBefore(use.getOwner());
// Ensure that each output of a DPI call is used only by a wire.
static void lowerDPICallResults(DPICallOp op) {
Block *block = op->getParentOfType<HWModuleOp>().getBodyBlock();
auto builder = ImplicitLocOpBuilder::atBlockBegin(op.getLoc(), block);

SmallString<32> nameTmp{"_", op.getCallee(), "_"};
auto namePrefixSize = nameTmp.size();

for (auto &[i, result] : llvm::enumerate(op.getResults())) {
if (result.hasOneUse()) {
Operation *user = *result.getUsers().begin();
if (isa<BPAssignOp, PAssignOp>(user)) {
// Move assign op after instance to resolve cyclic dependencies.
user->moveAfter(op);
continue;
}
}

auto connect = builder.create<AssignOp>(newWire, result);
connect->moveAfter(op);
nameTmp.resize(namePrefixSize);
nameTmp += std::to_string(i);
replacePortWithWire(builder, op, result, nameTmp);
}
}

Expand Down Expand Up @@ -825,6 +872,10 @@ static LogicalResult legalizeHWModule(Block &block,
spillWiresForInstanceInputs(instance);
}

if (auto call = dyn_cast<DPICallOp>(op)) {
lowerDPICallResults(call);
}

// If logic op is located in a procedural region, we have to move the logic
// op declaration to a valid program point.
if (isProceduralRegion && isa<LogicOp>(op)) {
Expand Down
Loading

0 comments on commit 0a63652

Please sign in to comment.