diff --git a/experimental/include/experimental/Support/HandshakeSimulator.h b/experimental/include/experimental/Support/HandshakeSimulator.h index ee0c9ca0d3..b78fe35f12 100644 --- a/experimental/include/experimental/Support/HandshakeSimulator.h +++ b/experimental/include/experimental/Support/HandshakeSimulator.h @@ -582,27 +582,6 @@ class ForkModel : public OpExecutionModel { std::vector outsData; }; -// Never used but let it be -class JoinModel : public OpExecutionModel { -public: - using OpExecutionModel::OpExecutionModel; - JoinModel(handshake::JoinOp joinOp, mlir::DenseMap &subset); - - void reset() override; - - void exec(bool isClkRisingEdge) override; - - void printStates() override; - -private: - // ports - std::vector ins; - ProducerRW *outs; - - // internal components - JoinSupport join; -}; - class LazyForkModel : public OpExecutionModel { public: using OpExecutionModel::OpExecutionModel; diff --git a/experimental/lib/Support/FtdImplementation.cpp b/experimental/lib/Support/FtdImplementation.cpp index 0f57a8d20c..6952e9dd06 100644 --- a/experimental/lib/Support/FtdImplementation.cpp +++ b/experimental/lib/Support/FtdImplementation.cpp @@ -506,7 +506,7 @@ LogicalResult ftd::createPhiNetworkDeps( continue; } - // If the operand has one dependency only, there is no need for a join. + // If the operand has one dependency only, there is no need for a synchronizer. if (dependencies.size() == 1) { if (failed(connect(operand, dependencies[0]))) return failure(); @@ -514,19 +514,19 @@ LogicalResult ftd::createPhiNetworkDeps( } // If the operand has many dependencies, then each of them is singularly - // connected with an SSA network, and then everything is joined. + // connected with an SSA network, and then everything is synchronized. ValueRange operands = dependencies; rewriter.setInsertionPointToStart(operand->getOwner()->getBlock()); - auto joinOp = rewriter.create( + auto synchronizerOp = rewriter.create( operand->getOwner()->getLoc(), operands); - joinOp->moveBefore(operandOwner); + synchronizerOp->moveBefore(operandOwner); for (unsigned i = 0; i < dependencies.size(); i++) { - if (failed(connect(&joinOp->getOpOperand(i), dependencies[i]))) + if (failed(connect(&synchronizerOp->getOpOperand(i), dependencies[i]))) return failure(); } - operand->set(joinOp.getResult()); + operand->set(synchronizerOp.getOuts()[0]); } return success(); diff --git a/experimental/lib/Support/HandshakeSimulator.cpp b/experimental/lib/Support/HandshakeSimulator.cpp index 557b23db60..e5c768745c 100644 --- a/experimental/lib/Support/HandshakeSimulator.cpp +++ b/experimental/lib/Support/HandshakeSimulator.cpp @@ -680,25 +680,6 @@ void ForkModel::printStates() { printValue("outs", outs[i], outsData[i].data); } -JoinModel::JoinModel(handshake::JoinOp joinOp, - mlir::DenseMap &subset) - : OpExecutionModel(joinOp), - outs(getState(joinOp.getResult(), subset)), - join(joinOp->getNumOperands()) { - for (auto oper : joinOp->getOperands()) - ins.push_back(getState(oper, subset)); -} - -void JoinModel::reset() { join.exec(ins, outs); } - -void JoinModel::exec(bool isClkRisingEdge) { reset(); } - -void JoinModel::printStates() { - for (auto *in : ins) - llvm::outs() << "Ins: " << in->valid << " " << in->ready << "\n"; - llvm::outs() << "Outs: " << outs->valid << " " << outs->ready << "\n"; -} - LazyForkModel::LazyForkModel(handshake::LazyForkOp lazyForkOp, mlir::DenseMap &subset) : OpExecutionModel(lazyForkOp), @@ -1384,9 +1365,6 @@ void Simulator::associateModel(Operation *op) { .Case([&](handshake::ForkOp forkOp) { registerModel(forkOp); }) - .Case([&](handshake::JoinOp joinOp) { - registerModel(joinOp); - }) .Case([&](handshake::LazyForkOp lForkOp) { registerModel(lForkOp); }) diff --git a/experimental/tools/elastic-miter/FabricGeneration.cpp b/experimental/tools/elastic-miter/FabricGeneration.cpp index 246ae3320e..97f8504831 100644 --- a/experimental/tools/elastic-miter/FabricGeneration.cpp +++ b/experimental/tools/elastic-miter/FabricGeneration.cpp @@ -489,12 +489,12 @@ createElasticMiter(MLIRContext &context, ModuleOp lhsModule, ModuleOp rhsModule, setHandshakeAttributes(builder, rhsEndBufferOp, BB_OUT, rhsBufName); if (lhsResult.getType().isa()) { - ValueRange joinInputs = {lhsEndBufferOp.getResult(), + ValueRange synchronizerInputs = {lhsEndBufferOp.getResult(), rhsEndBufferOp.getResult()}; - JoinOp joinOp = - builder.create(builder.getUnknownLoc(), joinInputs); - setHandshakeAttributes(builder, joinOp, BB_OUT, eqName); - miterResultValues.push_back(joinOp.getResult()); + SynchronizerOp synchronizerOp = + builder.create(builder.getUnknownLoc(), synchronizerInputs); + setHandshakeAttributes(builder, synchronizerOp, BB_OUT, eqName); + miterResultValues.push_back(synchronizerOp.getOuts()[0]); } else { CmpIOp compOp = builder.create( builder.getUnknownLoc(), CmpIPredicate::eq, diff --git a/experimental/tools/unit-generators/vhdl/generators/handshake/synchronizer.py b/experimental/tools/unit-generators/vhdl/generators/handshake/synchronizer.py new file mode 100644 index 0000000000..7485fc8d44 --- /dev/null +++ b/experimental/tools/unit-generators/vhdl/generators/handshake/synchronizer.py @@ -0,0 +1,118 @@ +from generators.handshake.join import generate_join +from generators.handshake.fork import generate_fork +from generators.support.utils import data + + +def generate_synchronizer(name, params): + size = params["size"] + + bitwidths = [] + for i in range(size): + bitwidths.append(params[f"bitwidth_{i}"]) + + input_ports = "" + for i, bitwidth in enumerate(bitwidths): + input_ports += input_port(i, bitwidth) + input_ports = input_ports.lstrip() + + output_ports = "" + for i, bitwidth in enumerate(bitwidths): + output_ports += output_port(i, bitwidth) + output_ports = output_ports.lstrip().rpartition(";")[0].rstrip() + + join_name = f"{name}_join" + fork_name = f"{name}_fork" + + dependencies = generate_join(join_name, {"size": size}) + \ + generate_fork(fork_name, {"size": size, "bitwidth": 0}) + + + entity = f""" + library ieee; + use ieee.std_logic_1164.all; + + -- Entity of synchronizer + entity {name} is + port ( + {input_ports} + {output_ports} + ); + end entity; + """ + + valid_instance_assignments = "" + for i in range(size): + valid_instance_assignments += f" ins_valid({i}) => ins{i}_valid,\n" + + for i in range(size): + valid_instance_assignments += (f" ins_ready({i}) => ins{i}_ready,\n") + + valid_instance_assignments = valid_instance_assignments.lstrip() + + + fork_instance_assignments = "" + for i in range(size): + fork_instance_assignments += f" outs_valid({i}) => outs{i}_valid,\n" + + for i in range(size): + fork_instance_assignments += (f" outs_ready({i}) => outs{i}_ready,\n") + + fork_instance_assignments = fork_instance_assignments.lstrip() + + assignments = "" + for i, bitwidth in enumerate(bitwidths): + potential_assignment = f" outs{i} <= ins{i};\n" + assignments += data(potential_assignment, bitwidth) + assignments = assignments.lstrip() + + + architecture = f""" + -- Architecture of synchronizer + architecture arch of {name} is + signal join_valid : std_logic; + signal fork_ready : std_logic; + begin + join : entity work.{join_name} + port map( + -- input channels + {valid_instance_assignments} + -- output channel to eager fork + outs_valid => join_valid, + outs_ready => fork_ready + ); + + fork : entity work.{fork_name} + port map( + -- output channels + {fork_instance_assignments} + -- input channel from fork + ins_valid => join_valid, + ins_ready => fork_ready + ); + + {assignments} + + end architecture; + """ + + + return dependencies + entity + architecture + + +def input_port(i, bitwidth): + potential_port_declaration = f"ins{i} : in std_logic_vector({bitwidth} - 1 downto 0);" + return f""" -- input port {i} + {data(potential_port_declaration, bitwidth)} + ins{i}_valid : in std_logic; + ins{i}_ready : out std_logic; + +""" + +def output_port(i, bitwidth): + potential_port_declaration = f"outs{i} : out std_logic_vector({bitwidth} - 1 downto 0);" + return f""" -- output port {i} + {data(potential_port_declaration, bitwidth)} + outs{i}_valid : out std_logic; + outs{i}_ready : in std_logic; + +""" \ No newline at end of file diff --git a/experimental/tools/unit-generators/vhdl/generators/support/utils.py b/experimental/tools/unit-generators/vhdl/generators/support/utils.py index b1f54fcc95..157a39b916 100644 --- a/experimental/tools/unit-generators/vhdl/generators/support/utils.py +++ b/experimental/tools/unit-generators/vhdl/generators/support/utils.py @@ -1,7 +1,6 @@ from typing import Type, Union from enum import Enum - def data(code: str, bitwidth: int) -> str: return code if bitwidth else "" diff --git a/experimental/tools/unit-generators/vhdl/vhdl-unit-generator.py b/experimental/tools/unit-generators/vhdl/vhdl-unit-generator.py index 5ed05c4a08..5f3902c73f 100644 --- a/experimental/tools/unit-generators/vhdl/vhdl-unit-generator.py +++ b/experimental/tools/unit-generators/vhdl/vhdl-unit-generator.py @@ -39,6 +39,7 @@ import generators.handshake.fptosi as fptosi import generators.handshake.ready_remover as ready_remover import generators.handshake.valid_merger as valid_merger +import generators.handshake.synchronizer as synchronizer def generate_code(name, mod_type, parameters): @@ -117,6 +118,8 @@ def generate_code(name, mod_type, parameters): return ready_remover.generate_ready_remover(name, parameters) case "valid_merger": return valid_merger.generate_valid_merger(name, parameters) + case "synchronizer": + return synchronizer.generate_synchronizer(name, parameters) case _: raise ValueError(f"Module type {mod_type} not found") diff --git a/include/dynamatic/Dialect/Handshake/HandshakeOps.td b/include/dynamatic/Dialect/Handshake/HandshakeOps.td index a68af2eb0b..a25e32e15d 100644 --- a/include/dynamatic/Dialect/Handshake/HandshakeOps.td +++ b/include/dynamatic/Dialect/Handshake/HandshakeOps.td @@ -591,47 +591,62 @@ def SourceOp : Handshake_Op<"source", [Pure]> { let assemblyFormat = "attr-dict `:` type($result)"; } -def JoinOp : Handshake_Op<"join", [ - SameOperandsAndResultType, - DeclareOpInterfaceMethods +def SynchronizerOp : Handshake_Op<"synchronizer", [ + Pure, + OutputsSameTypeAsInputs<"ins", "outs">, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods ]> { - let summary = "join operation"; + let summary = "Synchronization operation"; let description = [{ - A control-only synchronizer. Produces a valid output when all - inputs become available. + Token synchronizer. + Outputs only become valid when all inputs are valid. Example: ```mlir - %0 = join %a, %b, %c : !handshake.control + %0, %1, %2 = handshake.synchronizer %a, %b, %c : !handshake.channel, !handshake.channel, !handshake.control -> !handshake.channel, !handshake.channel, !handshake.control ``` }]; - let arguments = (ins Variadic:$data); - let results = (outs ControlType:$result); - let assemblyFormat = "$data attr-dict `:` type($result)"; -} + let arguments = (ins Variadic:$ins); + let results = (outs Variadic:$outs); -// TODO: Split the input into two parts and explicitly mark the forwarded input, -// once the backend supports variadic channels with zero or one item correctly. -def BlockerOp : Handshake_Op<"blocker", [ - SameOperandsAndResultType -]> { - let summary = "blocker operation"; - let description = [{ - A data synchronizer. Blocks handshakes until all inputs are valid, - then forwards the first input. + let builders = [ + OpBuilder<(ins "ValueRange":$ins), [{ + SmallVector outTypes; + for (Value v : ins) + outTypes.push_back(v.getType()); + build($_builder, $_state, outTypes, ins); + }]> + ]; - Example: + let extraClassDeclaration= [{ + // "size" refers to both num operands + // and num results, so utility function is + // provided to abstract which is chosen to get value + unsigned getSize() { + return getNumOperands(); + } + }]; - ```mlir - %0 = blocker %a, %b, %c : !handshake.channel - ``` + let extraClassDefinition = [{ + // use custom getOperandName to prevent + // conversion to data array + std::string $cppClass::getOperandName(unsigned idx) { + assert(idx < getNumOperands() && "index too high"); + return "ins" + std::to_string(idx); + } + + // use custom getResultName to prevent + // conversion to data array + std::string $cppClass::getResultName(unsigned idx) { + assert(idx < getNumResults() && "index too high"); + return "outs" + std::to_string(idx); + } }]; - let arguments = (ins Variadic:$data); - let results = (outs ChannelType:$result); - let assemblyFormat = "$data attr-dict `:` type($result)"; + let assemblyFormat = "$ins `:` type($ins) attr-dict `->` type($outs)"; } def NotOp : Handshake_Op<"not", [Pure, SameOperandsAndResultType]> { diff --git a/include/dynamatic/Dialect/Handshake/HandshakeTypes.td b/include/dynamatic/Dialect/Handshake/HandshakeTypes.td index 4485827359..8b0d1b6ad0 100644 --- a/include/dynamatic/Dialect/Handshake/HandshakeTypes.td +++ b/include/dynamatic/Dialect/Handshake/HandshakeTypes.td @@ -418,4 +418,19 @@ class HasSpecTagIfPresentIn : PredOpTrait< "$" # output # ".getType().cast())"> >; +class OutputsSameTypeAsInputs : PredOpTrait< + "outputs must match inputs in number and type (incl. extra signals)", + CPred<[{ + [](OperandRange ins, ResultRange outs) { + if (ins.size() != outs.size()) + return false; + for (auto [in, out] : llvm::zip(ins, outs)) { + if (in.getType() != out.getType()) + return false; + } + return true; + } + }] # "($" # inputVariadic # ", $" # outputVariadic # ")"> +>; + #endif // DYNAMATIC_DIALECT_HANDSHAKE_HANDSHAKE_TYPES_TD diff --git a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp index 2093537ded..b0c1708a2d 100644 --- a/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp +++ b/lib/Conversion/HandshakeToHW/HandshakeToHW.cpp @@ -537,9 +537,13 @@ ModuleDiscriminator::ModuleDiscriminator(Operation *op) { addUnsigned("SIZE", op->getNumOperands()); addType("DATA_TYPE", op->getResult(0)); }) - .Case([&](auto) { + .Case([&](handshake::SynchronizerOp synchronizerOp) { // Number of input channels - addUnsigned("SIZE", op->getNumOperands()); + addUnsigned("SIZE", synchronizerOp.getSize()); + + for(unsigned i = 0; i < synchronizerOp.getSize(); i++){ + addType("DATA_TYPE_" + std::to_string(i), op->getResult(i)); + } }) .Case( [&](auto) { @@ -1821,8 +1825,7 @@ class HandshakeToHWPass ConvertToHWInstance, ConvertToHWInstance, ConvertToHWInstance, - ConvertToHWInstance, - ConvertToHWInstance, + ConvertToHWInstance, ConvertToHWInstance, ConvertToHWInstance, ConvertToHWInstance, diff --git a/lib/Dialect/Handshake/HandshakeOps.cpp b/lib/Dialect/Handshake/HandshakeOps.cpp index c0e5facdeb..162d20d745 100644 --- a/lib/Dialect/Handshake/HandshakeOps.cpp +++ b/lib/Dialect/Handshake/HandshakeOps.cpp @@ -584,8 +584,6 @@ LogicalResult ConstantOp::verify() { return success(); } -bool JoinOp::isControl() { return true; } - /// Based on mlir::func::CallOp::verifySymbolUses LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) { // Check that the module attribute was specified. diff --git a/lib/Support/TimingModels.cpp b/lib/Support/TimingModels.cpp index 7fbd83d2af..558105e912 100644 --- a/lib/Support/TimingModels.cpp +++ b/lib/Support/TimingModels.cpp @@ -52,7 +52,7 @@ unsigned dynamatic::getOpDatawidth(Operation *op) { mergeLikeOp.getDataOperands().front().getType()); }) .Case([&](auto) { + handshake::BranchOp, handshake::SinkOp, handshake::SynchronizerOp>([&](auto) { return getHandshakeTypeBitWidth(op->getOperand(0).getType()); }) .Case( @@ -62,7 +62,7 @@ unsigned dynamatic::getOpDatawidth(Operation *op) { .Case([&](auto) { return getHandshakeTypeBitWidth(op->getResult(0).getType()); }) - .Case( + .Case( [&](auto) { if (op->getNumOperands() == 0) return 0u; diff --git a/tools/export-dot/export-dot.cpp b/tools/export-dot/export-dot.cpp index c70fb6bc4e..55f3a8fa63 100644 --- a/tools/export-dot/export-dot.cpp +++ b/tools/export-dot/export-dot.cpp @@ -250,9 +250,9 @@ static std::string getPrettyNodeLabel(Operation *op) { static StringRef getNodeColor(Operation *op) { return llvm::TypeSwitch(op) - .Case( + .Case( [&](auto) { return "lavender"; }) - .Case([&](auto) { return "cyan"; }) + .Case([&](auto) { return "cyan"; }) .Case([&](auto) { return "palegreen"; }) .Case([&](auto) { return "gold"; }) .Case(