Skip to content

Commit

Permalink
[ESI] Add specification to build signals into SV interface or not (#4653
Browse files Browse the repository at this point in the history
)

Support both modes wherein external modules' signaling are bundled in SV interfaces and just appended with `_valid` and `_ready`. Default to not bundled since that seems to be far more common. 95% of this was just updating to use the op interface rather than just hwmodule directly.
  • Loading branch information
teqdruid authored Feb 14, 2023
1 parent b16d03f commit 1149618
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 117 deletions.
4 changes: 4 additions & 0 deletions include/circt/Dialect/ESI/ESIDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ struct ESIPortValidReadyMapping {
hw::PortInfo data, valid, ready;
};

/// Name of dialect attribute which governs whether or not to bundle (i.e. use
/// SystemVerilog interfaces) channel signal wires on external modules.
constexpr StringRef extModBundleSignalsAttrName = "esi.bundle";

/// Find all the port triples on a module which fit the
/// <name>/<name>_valid/<name>_ready pattern. Ready must be the opposite
/// direction of the other two.
Expand Down
6 changes: 3 additions & 3 deletions integration_test/ESI/cosim/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
// PY: rpc.testVectorSum(25)
// PY: rpc.testCrypto(25)

hw.module.extern @IntAccNoBP(%clk: i1, %rst: i1, %ints: !esi.channel<i32>) -> (totalOut: !esi.channel<i32>)
hw.module.extern @IntArrSum(%clk: i1, %rst: i1, %arr: !esi.channel<!hw.array<4 x si13>>) -> (totalOut: !esi.channel<!hw.array<2 x ui24>>)
hw.module.extern @IntAccNoBP(%clk: i1, %rst: i1, %ints: !esi.channel<i32>) -> (totalOut: !esi.channel<i32>) attributes {esi.bundle}
hw.module.extern @IntArrSum(%clk: i1, %rst: i1, %arr: !esi.channel<!hw.array<4 x si13>>) -> (totalOut: !esi.channel<!hw.array<2 x ui24>>) attributes {esi.bundle}

hw.module @ints(%clk: i1, %rst: i1) {
%intsIn = esi.cosim %clk, %rst, %intsTotalBuffered, "TestEP" : !esi.channel<i32> -> !esi.channel<i32>
Expand All @@ -31,7 +31,7 @@ hw.module @array(%clk: i1, %rst: i1) {
!Config = !hw.struct<encrypt: i1, otp: !hw.array<32 x i8>>
!cfgChan = !esi.channel<!Config>

hw.module.extern @Encryptor(%clk: i1, %rst: i1, %in: !pktChan, %cfg: !cfgChan) -> (x: !pktChan)
hw.module.extern @Encryptor(%clk: i1, %rst: i1, %in: !pktChan, %cfg: !cfgChan) -> (x: !pktChan) attributes {esi.bundle}

hw.module @structs(%clk:i1, %rst:i1) -> () {
%compressedData = hw.instance "otpCryptor" @Encryptor(clk: %clk: i1, rst: %rst: i1, in: %inputData: !pktChan, cfg: %cfg: !cfgChan) -> (x: !pktChan)
Expand Down
4 changes: 2 additions & 2 deletions integration_test/ESI/system/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// RUN: circt-opt %t1.mlir -export-verilog -verify-diagnostics -o t3.mlir > %t2.sv
// RUN: circt-rtl-sim.py %t2.sv %BININC%/circt/Dialect/ESI/ESIPrimitives.sv %S/../supplements/integers.sv --cycles 150 | FileCheck %s

hw.module.extern @IntCountProd(%clk: i1, %rst: i1) -> (ints: !esi.channel<i32>)
hw.module.extern @IntAcc(%clk: i1, %rst: i1, %ints: !esi.channel<i32>) -> (totalOut: i32)
hw.module.extern @IntCountProd(%clk: i1, %rst: i1) -> (ints: !esi.channel<i32>) attributes {esi.bundle}
hw.module.extern @IntAcc(%clk: i1, %rst: i1, %ints: !esi.channel<i32>) -> (totalOut: i32) attributes {esi.bundle}
hw.module @top(%clk: i1, %rst: i1) -> (totalOut: i32) {
%intStream = hw.instance "prod" @IntCountProd(clk: %clk: i1, rst: %rst: i1) -> (ints: !esi.channel<i32>)
%intStreamBuffered = esi.buffer %clk, %rst, %intStream {stages=2, name="intChan"} : i32
Expand Down
227 changes: 129 additions & 98 deletions lib/Dialect/ESI/ESIPasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ struct ESIPortsPass : public LowerESIPortsBase<ESIPortsPass> {
void runOnOperation() override;

private:
bool updateFunc(HWModuleOp mod);
void updateInstance(HWModuleOp mod, InstanceOp inst);
bool updateFunc(HWMutableModuleLike mod);
void updateInstance(HWMutableModuleLike mod, InstanceOp inst);

bool updateFunc(HWModuleExternOp mod);
void updateInstance(HWModuleExternOp mod, InstanceOp inst);
Expand All @@ -385,7 +385,8 @@ void ESIPortsPass::runOnOperation() {
// Find all externmodules and try to modify them. Remember the modified ones.
DenseMap<StringRef, HWModuleExternOp> externModsMutated;
for (auto mod : top.getOps<HWModuleExternOp>())
if (updateFunc(mod))
if (mod->hasAttrOfType<UnitAttr>(extModBundleSignalsAttrName) &&
updateFunc(mod))
externModsMutated[mod.getName()] = mod;

// Find all instances and update them.
Expand All @@ -397,14 +398,14 @@ void ESIPortsPass::runOnOperation() {

// Find all modules and try to modify them to have wires with valid/ready
// semantics. Remember the modified ones.
DenseMap<StringRef, HWModuleOp> modsMutated;
for (auto mod : top.getOps<HWModuleOp>())
DenseMap<SymbolRefAttr, HWMutableModuleLike> modsMutated;
for (auto mod : top.getOps<HWMutableModuleLike>())
if (updateFunc(mod))
modsMutated[mod.getName()] = mod;
modsMutated[FlatSymbolRefAttr::get(mod)] = mod;

// Find all instances and update them.
top.walk([&modsMutated, this](InstanceOp inst) {
auto mapIter = modsMutated.find(inst.getModuleName());
auto mapIter = modsMutated.find(inst.getModuleNameAttr());
if (mapIter != modsMutated.end())
updateInstance(mapIter->second, inst);
});
Expand All @@ -419,16 +420,20 @@ static StringAttr appendToRtlName(StringAttr base, StringRef suffix) {
}

/// Convert all input and output ChannelTypes into valid/ready wires. Try not to
/// change the order and materialize ops in reasonably intuitive locations.
bool ESIPortsPass::updateFunc(HWModuleOp mod) {
auto funcType = mod.getFunctionType();
/// change the order and materialize ops in reasonably intuitive locations. Will
/// modify the body only if one exists.
bool ESIPortsPass::updateFunc(HWMutableModuleLike mod) {
Block *body = nullptr;
if (mod->getNumRegions() == 1 && mod->getRegion(0).getBlocks().size() == 1)
body = &mod->getRegion(0).front();

// Build ops in the module.
ImplicitLocOpBuilder modBuilder(mod.getLoc(), mod.getBody());
ImplicitLocOpBuilder modBuilder(mod.getLoc(), mod);
Type i1 = modBuilder.getI1Type();
if (body)
modBuilder.setInsertionPointToStart(body);

// Get information to be used later on.
hw::OutputOp outOp = cast<hw::OutputOp>(mod.getBodyBlock()->getTerminator());

ModulePortInfo ports = mod.getPorts();
bool updated = false;

// Reconstruct the list of operand types, changing the type whenever an ESI
Expand All @@ -438,125 +443,150 @@ bool ESIPortsPass::updateFunc(HWModuleOp mod) {

// 'Ready' signals are outputs. Remember them for later when we deal with the
// returns.
SmallVector<std::tuple<Value, StringAttr, Location>, 8> newReadySignals;
SmallVector<Attribute> newArgNames;
SmallVector<Attribute> newArgLocs;
SmallVector<std::tuple<Value, PortInfo>, 8> newReadySignals;
SmallVector<std::pair<unsigned, PortInfo>> inputsToInsert;
SmallVector<unsigned> inputsToErase;

for (size_t argNum = 0, blockArgNum = 0, e = mod.getNumArguments();
argNum < e; ++argNum, ++blockArgNum) {
Type argTy = funcType.getInput(argNum);
auto argNameAttr = getModuleArgumentNameAttr(mod, argNum);
auto argLocAttr = getModuleArgumentLocAttr(mod, argNum);
for (size_t argNum = 0, e = ports.inputs.size(), blockArgNum = 0; argNum < e;
++argNum, ++blockArgNum) {
PortInfo &port = ports.inputs[argNum];

auto chanTy = argTy.dyn_cast<ChannelType>();
if (!chanTy) {
auto chanTy = port.type.dyn_cast<ChannelType>();
if (!chanTy)
// If not ESI, pass through.
newArgTypes.push_back(argTy);
newArgNames.push_back(argNameAttr);
newArgLocs.push_back(argLocAttr);
continue;
}

// When we find one, add a data and valid signal to the new args.
Value data;
newArgTypes.push_back(chanTy.getInner());
newArgNames.push_back(argNameAttr);
newArgLocs.push_back(argLocAttr);
data =
mod.front().insertArgument(blockArgNum, chanTy.getInner(), argLocAttr);
++blockArgNum;

newArgTypes.push_back(i1);
newArgNames.push_back(appendToRtlName(argNameAttr, "_valid"));
newArgLocs.push_back(argLocAttr);
Value valid = mod.front().insertArgument(blockArgNum, i1, argLocAttr);
++blockArgNum;
// Build the ESI wrap operation to translate the lowered signals to what
// they were. (A later pass takes care of eliminating the ESI ops.)
auto wrap = modBuilder.create<WrapValidReadyOp>(data, valid);
// Replace uses of the old ESI port argument with the new one from the wrap.
mod.front()
.getArgument(blockArgNum)
.replaceAllUsesWith(wrap.getChanOutput());
// Delete the ESI port block argument.
mod.front().eraseArgument(blockArgNum);
--blockArgNum;
newReadySignals.emplace_back(
wrap.getReady(), appendToRtlName(argNameAttr, "_ready"), argLocAttr);
PortInfo dataPort = {
port.name, PortDirection::INPUT, chanTy.getInner(), ~0U, {}, port.loc};
inputsToInsert.emplace_back(argNum, dataPort);

PortInfo validPort = {appendToRtlName(port.name, "_valid"),
PortDirection::INPUT,
i1,
~0U,
{},
port.loc};
inputsToInsert.emplace_back(argNum, validPort);

Value data, valid;
if (body) {
valid = body->insertArgument(blockArgNum, i1, port.loc);
data = body->insertArgument(blockArgNum, chanTy.getInner(), port.loc);
blockArgNum += 2;
}

// And add the corresponding output ready signal.
PortInfo readyPort = {appendToRtlName(port.name, "_ready"),
PortDirection::OUTPUT,
i1,
~0U,
{},
port.loc};

Value ready;
if (body) {
// Build the ESI wrap operation to translate the lowered signals to what
// they were. (A later pass takes care of eliminating the ESI ops.)
auto wrap = modBuilder.create<WrapValidReadyOp>(data, valid);
ready = wrap.getReady();
// Replace uses of the old ESI port argument with the new one from the
// wrap.
body->getArgument(blockArgNum).replaceAllUsesWith(wrap.getChanOutput());
// Delete the ESI port block argument.
body->eraseArgument(blockArgNum);
--blockArgNum;
}
newReadySignals.emplace_back(ready, readyPort);

inputsToErase.push_back(argNum);
updated = true;
}

// Iterate through the outputs, appending to all of the next three lists.
// Lower the ESI ports.
SmallVector<Type, 8> newResultTypes;
SmallVector<std::pair<unsigned, PortInfo>> outputsToInsert;
SmallVector<unsigned> outputsToErase;
SmallVector<Value, 8> newOutputOperands;
SmallVector<Attribute> newResultNames;
SmallVector<Attribute> newResultLocs;
unsigned oldNumInputs = ports.inputs.size();

modBuilder.setInsertionPointToEnd(mod.getBodyBlock());
for (size_t resNum = 0, numRes = funcType.getNumResults(); resNum < numRes;
Operation *outOp = nullptr;
if (body) {
outOp = body->getTerminator();
modBuilder.setInsertionPoint(outOp);
}
for (size_t resNum = 0, numRes = ports.outputs.size(); resNum < numRes;
++resNum) {
Type resTy = funcType.getResult(resNum);
auto chanTy = resTy.dyn_cast<ChannelType>();
Value oldOutputValue = outOp.getOperand(resNum);
auto oldResultName = getModuleResultNameAttr(mod, resNum);
auto oldResultLoc = getModuleResultLocAttr(mod, resNum);
PortInfo &port = ports.outputs[resNum];
auto chanTy = port.type.dyn_cast<ChannelType>();
Value oldOutputValue;
if (outOp)
oldOutputValue = outOp->getOperand(resNum);
if (!chanTy) {
// If not ESI, pass through.
newResultTypes.push_back(resTy);
newResultNames.push_back(oldResultName);
newResultLocs.push_back(oldResultLoc);
newOutputOperands.push_back(oldOutputValue);
continue;
}

// Lower the output, adding ready signals directly to the arg list.
Value ready = mod.front().addArgument(i1, oldResultLoc); // Ready block arg.
auto unwrap = modBuilder.create<UnwrapValidReadyOp>(oldOutputValue, ready);
newOutputOperands.push_back(unwrap.getRawOutput());
newResultTypes.push_back(chanTy.getInner()); // Raw data.
newResultNames.push_back(oldResultName);
newResultLocs.push_back(oldResultLoc);

newResultTypes.push_back(i1); // Valid.
newOutputOperands.push_back(unwrap.getValid());
newResultNames.push_back(appendToRtlName(oldResultName, "_valid"));
newResultLocs.push_back(oldResultLoc);

newArgTypes.push_back(i1); // Ready func arg.
newArgNames.push_back(appendToRtlName(oldResultName, "_ready"));
newArgLocs.push_back(oldResultLoc);
Value ready;
if (body) {
ready = body->addArgument(i1, port.loc); // Ready block arg.
auto unwrap =
modBuilder.create<UnwrapValidReadyOp>(oldOutputValue, ready);
newOutputOperands.push_back(unwrap.getRawOutput());
newOutputOperands.push_back(unwrap.getValid());
}
// New outputs.
// When we find one, add a data and valid signal to the new args.
PortInfo dataPort = {
port.name, PortDirection::OUTPUT, chanTy.getInner(), ~0U, {}, port.loc};
outputsToInsert.emplace_back(resNum, dataPort);

PortInfo validPort = {appendToRtlName(port.name, "_valid"),
PortDirection::OUTPUT,
i1,
~0U,
{},
port.loc};
outputsToInsert.emplace_back(resNum, validPort);

// New 'ready' input port.
PortInfo readyPort = {appendToRtlName(port.name, "_ready"),
PortDirection::INPUT,
i1,
~0U,
{},
port.loc};
inputsToInsert.emplace_back(oldNumInputs, readyPort);

outputsToErase.push_back(resNum);
updated = true;
}

// Append the ready list signals we remembered above.
for (auto [value, name, location] : newReadySignals) {
newResultTypes.push_back(i1);
newOutputOperands.push_back(value);
newResultNames.push_back(name);
newResultLocs.push_back(location);
unsigned oldNumOutputs = ports.outputs.size();
for (auto [value, port] : newReadySignals) {
if (body)
newOutputOperands.push_back(value);
outputsToInsert.emplace_back(oldNumOutputs, port);
}

if (!updated)
return false;

// A new output op is necessary.
outOp.erase();
modBuilder.create<hw::OutputOp>(newOutputOperands);

// Set the new types.
auto newFuncType = modBuilder.getFunctionType(newArgTypes, newResultTypes);
mod.setType(newFuncType);
setModuleArgumentNames(mod, newArgNames);
setModuleArgumentLocs(mod, newArgLocs);
setModuleResultNames(mod, newResultNames);
setModuleResultLocs(mod, newResultLocs);
// A new set of output values is needed.
if (outOp)
outOp->setOperands(newOutputOperands);
mod.modifyPorts(inputsToInsert, outputsToInsert, inputsToErase,
outputsToErase);
return true;
}

/// Update an instance of an updated module by adding `esi.[un]wrap.vr`
/// ops around the instance. Lowering or folding away `[un]wrap` ops is another
/// pass.
void ESIPortsPass::updateInstance(HWModuleOp mod, InstanceOp inst) {
void ESIPortsPass::updateInstance(HWMutableModuleLike mod, InstanceOp inst) {
ImplicitLocOpBuilder b(inst.getLoc(), inst);
BackedgeBuilder beb(b, inst.getLoc());
Type i1 = b.getI1Type();
Expand Down Expand Up @@ -704,6 +734,7 @@ bool ESIPortsPass::updateFunc(HWModuleExternOp mod) {
updated = true;
}

mod->removeAttr(extModBundleSignalsAttrName);
if (!updated)
return false;

Expand Down
6 changes: 3 additions & 3 deletions test/Dialect/ESI/cosim_structs.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ hw.module @top(%clk:i1, %rst:i1) -> () {
// CAPNP-NEXT: compressionLevel @1 :UInt8;
// CAPNP-NEXT: blob @2 :List(UInt8);

// COSIM: hw.instance "encodeStruct{{.+}}Inst" @encodeStruct{{.+}}(clk: %clk: i1, valid: %[[#]]: i1, unencodedInput: %[[#]]: !hw.struct<encrypted: i1, compressionLevel: ui4, blob: !hw.array<32xi8>>) -> (encoded: !hw.array<448xi1>)
// COSIM: hw.instance "Compressor" @Cosim_Endpoint<ENDPOINT_ID_EXT: none = "", SEND_TYPE_ID: ui64 = 11116741711825659895, SEND_TYPE_SIZE_BITS: i32 = 448, RECV_TYPE_ID: ui64 = 17519082812652290511, RECV_TYPE_SIZE_BITS: i32 = 128>(clk: %clk: i1, rst: %rst: i1, {{.+}}, {{.+}}, {{.+}}) -> ({{.+}})
// COSIM: hw.module @encode{{.+}}(%clk: i1, %valid: i1, %unencodedInput: !hw.struct<encrypted: i1, compressionLevel: ui4, blob: !hw.array<32xi8>>) -> (encoded: !hw.array<448xi1>)
// COSIM-DAG: hw.instance "encodeStruct{{.+}}Inst" @encodeStruct{{.+}}(clk: %clk: i1, valid: %{{.+}}: i1, unencodedInput: %{{.+}}: !hw.struct<encrypted: i1, compressionLevel: ui4, blob: !hw.array<32xi8>>) -> (encoded: !hw.array<448xi1>)
// COSIM-DAG: hw.instance "Compressor" @Cosim_Endpoint<ENDPOINT_ID_EXT: none = "", SEND_TYPE_ID: ui64 = 11116741711825659895, SEND_TYPE_SIZE_BITS: i32 = 448, RECV_TYPE_ID: ui64 = 17519082812652290511, RECV_TYPE_SIZE_BITS: i32 = 128>(clk: %clk: i1, rst: %rst: i1, {{.+}}, {{.+}}, {{.+}}) -> ({{.+}})
// COSIM-DAG: hw.module @encode{{.+}}(%clk: i1, %valid: i1, %unencodedInput: !hw.struct<encrypted: i1, compressionLevel: ui4, blob: !hw.array<32xi8>>) -> (encoded: !hw.array<448xi1>)
Loading

0 comments on commit 1149618

Please sign in to comment.