Skip to content

Conversation

murphe67
Copy link
Collaborator

@murphe67 murphe67 commented Aug 4, 2025

The top-level join and blocker operations represent an awkward design:

  1. One operation is a super-set of the other operation
  2. The blocker operations operand names do not fully match its operation
  3. The type verification and backend generation is overly constrained.

This pull request adds the synchronizer operation, which represents the functionality of both units. Tokens of any types can be synchronized together: their values are unchanged by this, but their valid signals are passed through a join and eager fork so that tokens may only exit the unit when all inputs have arrived. The tokens do not have to leave the synchronizer together, as it is an eager fork.

This is a strong change, as removing the old join operation forces the fabric generation for elastic miter into the beta backend earlier than the rest of Dynamatic.

@murphe67 murphe67 requested review from AyaElAkhras, DanaKossaybati, Jiahui17 and shundroid and removed request for Jiahui17 August 4, 2025 14:51
@murphe67
Copy link
Collaborator Author

murphe67 commented Aug 4, 2025

This pull request is not fully finished (needs custom type verification so that inputs match outputs, and that bitwidths are properly extracted in rtl.cpp) but represents a simple solution to the oddness of the blocker operands. @Jiahui17 @shundroid @AyaElAkhras

Copy link
Collaborator Author

@murphe67 murphe67 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rough comments

std::vector<ProducerData> outsData;
};

// Never used but let it be
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is needed for synchronizer is easy to add, but this is a separate thing- we never added blocker to this

Comment on lines 516 to +529
// 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<handshake::JoinOp>(
auto synchronizerOp = rewriter.create<handshake::SynchronizerOp>(
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]);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

synchronizer can easily replace join, you just take the first output, second output is sinked

Comment on lines 491 to +497
if (lhsResult.getType().isa<handshake::ControlType>()) {
ValueRange joinInputs = {lhsEndBufferOp.getResult(),
ValueRange synchronizerInputs = {lhsEndBufferOp.getResult(),
rhsEndBufferOp.getResult()};
JoinOp joinOp =
builder.create<JoinOp>(builder.getUnknownLoc(), joinInputs);
setHandshakeAttributes(builder, joinOp, BB_OUT, eqName);
miterResultValues.push_back(joinOp.getResult());
SynchronizerOp synchronizerOp =
builder.create<SynchronizerOp>(builder.getUnknownLoc(), synchronizerInputs);
setHandshakeAttributes(builder, synchronizerOp, BB_OUT, eqName);
miterResultValues.push_back(synchronizerOp.getOuts()[0]);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

join replacement here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add comment of example output

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add custom naming to this so that it has operands of ins0, ins1, ins2 instead of ins_0, ins_1, ins_2.
when the underscore is present the netlist generator expects it to be a 2d signal.

sharing wrapper also uses this trick to have a variable number of operands but 1d port assignments.

ultimately we should make this be set in a sensible matter, but we are stuck with what we have for now

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add custom type verification to this to make sure each output has the bitwidth of its corresponding input

let arguments = (ins Variadic<ChannelType>:$data);
let results = (outs ChannelType:$result);
let assemblyFormat = "$data attr-dict `:` type($result)";
let assemblyFormat = "$ins `:` type($ins) attr-dict `->` type($outs)";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this is the best format, we don't have any other super varying input types like this, but apparently this is a common format?
need to research this a little bit more

Comment on lines 540 to 543
.Case<handshake::SynchronizerOp>([&](auto) {
// Number of input channels
addUnsigned("SIZE", op->getNumOperands());
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker op has a bug here since it doesnt register datatype which will break phase 2 matching @AyaElAkhras , will add this later also

})
.Case<handshake::BufferOp, handshake::ForkOp, handshake::LazyForkOp,
handshake::BranchOp, handshake::SinkOp>([&](auto) {
handshake::BranchOp, handshake::SinkOp, handshake::SynchronizerOp>([&](auto) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Carmine50 this operation does not have "a" bitwidth, it will have many- just returning the first bitwidth for now, not sure what our options for supporting this in-depth are

Comment on lines +610 to +617
let builders = [
OpBuilder<(ins "ValueRange":$ins), [{
SmallVector<Type> outTypes;
for (Value v : ins)
outTypes.push_back(v.getType());
build($_builder, $_state, outTypes, ins);
}]>
];
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shundroid custom builder to infer out types from in types

@murphe67
Copy link
Collaborator Author

murphe67 commented Aug 4, 2025

@Jiahui17 probably I make another PR after this to make the new python sharing wrapper call this instead of pulling from sharing wrapper support?

@shundroid
Copy link
Collaborator

ElasticMiter is still using the old dot2smv on the main branch. #391 updates it to use the new SMV backend.
It actually doesn't work currently on the main branch though...

@murphe67
Copy link
Collaborator Author

murphe67 commented Aug 7, 2025

That runs from Handshake right?

If we temporarily add the join back in to the tablegen and fabric generation.cpp, but leave it out of the backend/hardware stuff, this would be mergeable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants