Skip to content

[FIRRTL][Seq] Add explicit clock edge - #10879

Open
Clo91eaf wants to merge 3 commits into
llvm:mainfrom
xinpian-tech:firrtl-reg-clock-edge
Open

[FIRRTL][Seq] Add explicit clock edge#10879
Clo91eaf wants to merge 3 commits into
llvm:mainfrom
xinpian-tech:firrtl-reg-clock-edge

Conversation

@Clo91eaf

Copy link
Copy Markdown
Contributor

Add explicit positive-, negative-, and dual-edge clock attributes to FIRRTL registers and seq.firreg, preserving them through transformations and lowering.

Assisted-by: Codex:gpt-5.6-Sol

@Clo91eaf Clo91eaf changed the title Firrtl reg clock edge [FIRRTL][Seq] Add explicit clock edge Jul 21, 2026
@circt-bot

circt-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Results of circt-tests run for 2fefefa compared to results for f853892: no change to test results.

@seldridge seldridge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Generally looks good. Thank you for getting this going!

High level: the optionality of the edgedness is unnecessary. It is fine to have a default valued attribute, though. However, you can also make a case that the default valued attribute is not really that useful over just making this mandatory and unset.

Comment thread include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td Outdated
Comment on lines +46 to +52
// Register clock edge. Mirrors the FIRRTL/SV event-control concept so that a
// non-default (negedge or dual-edge) register clock survives lowering to SV.
def ClockEdgePos : I32EnumAttrCase<"Pos", 0, "pos">;
def ClockEdgeNeg : I32EnumAttrCase<"Neg", 1, "neg">;
def ClockEdgeBoth : I32EnumAttrCase<"Both", 2, "both">;
def ClockEdgeAttr : I32EnumAttr<"ClockEdge", "register clock edge",
[ClockEdgePos, ClockEdgeNeg, ClockEdgeBoth]>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is consistent with how we've modeled this in the past. It occurred to me that this might be more compact as BitEnumAttr which would then allow a more compact representation for "both" which is just Pos | Neg. This is probably future work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Additionally, we're starting to accumulate these enums in multiple dialects. Maybe there is eventually a common home in core for them which is sufficient for other dialects to just use. Not blocking or something that needs to be figured out now.

Comment thread include/circt/Dialect/Seq/SeqOps.td Outdated
Optional<I1>:$reset, Optional<AnyType>:$resetValue,
UnitAttr:$isAsync
UnitAttr:$isAsync,
OptionalAttr<ClockEdgeAttr>:$clockEdge

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment about optional. At this point, it's fine if this is mandatory. However, it's not a good design to make it optional and then assume a default.

Comment thread lib/Conversion/FIRRTLToHW/LowerToHW.cpp Outdated
case EventControl::AtEdge:
return seq::ClockEdgeAttr::get(context, seq::ClockEdge::Both);
}
return {};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks unnecessary given that the case is fully covered.

Comment thread lib/Conversion/FIRRTLToHW/LowerToHW.cpp Outdated
Comment on lines +3802 to +3803
if (!edge)
return {};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In purging the optional this should go away.

Comment thread lib/Conversion/HWToBTOR2/HWToBTOR2.cpp Outdated
Comment on lines +625 to +634
// The transitions emitted below model a posedge-clocked register. Reject a
// non-default clock edge rather than silently emit an incorrect model.
if (auto firReg = dyn_cast<seq::FirRegOp>(op)) {
if (firReg.getClockEdge().value_or(seq::ClockEdge::Pos) !=
seq::ClockEdge::Pos) {
op->emitError("non-posedge clock edges are not supported by the BTOR2 "
"conversion");
return;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should go away with mandatory edgedness.

return sv::EventControl::AtEdge;
}
return sv::EventControl::AtPosEdge;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comments about default.

Comment thread lib/Dialect/Arc/Transforms/StripSV.cpp Outdated
Comment on lines +157 to +162
if (reg.getClockEdge().value_or(seq::ClockEdge::Pos) !=
seq::ClockEdge::Pos) {
reg.emitOpError("only positive-edge registers are currently "
"supported");
return signalPassFailure();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comments about edgedness.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This PR is very close to going the distance and adding a syntax. Do you want to do that now?

Alternatively, we can delay that for later. However, please propose some syntax. What do you think would make sense here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I’d prefer to defer the textual FIRRTL syntax to a follow-up PR

My proposed syntax is an optional edge qualifier immediately before the clock expression:

reg r : UInt<8>, clock
reg r : UInt<8>, posedge clock
reg r : UInt<8>, negedge clock
reg r : UInt<8>, edge clock

regreset r : UInt<8>, negedge clock, reset, UInt<8>(0)

The omitted qualifier would continue to mean posedge

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This sounds good to me. I'm fine to defer. I only wanted to make sure that there was a plan for this. Thank you.

@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch 2 times, most recently from 86fc858 to 161c820 Compare July 22, 2026 12:31
@circt-bot

circt-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

Results of circt-tests run for 161c820 compared to results for f853892: no change to test results.

@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch from 161c820 to 874c78e Compare July 22, 2026 13:35
@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch from 874c78e to a95574b Compare July 22, 2026 14:22
@Clo91eaf
Clo91eaf requested a review from seldridge July 22, 2026 14:39
@circt-bot

circt-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

Results of circt-tests run for a95574b compared to results for f853892: no change to test results.

@seldridge seldridge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This continues to shape up great. I'm good to defer the FIRRTL syntax changes until later. The proposed syntax looks good.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This sounds good to me. I'm fine to defer. I only wanted to make sure that there was a plan for this. Thank you.

Comment on lines +1364 to +1367
auto newReg =
RegOp::create(*builder, field.type, op.getClockVal(), "",
NameKindEnum::DroppableName, attrs, StringAttr{});
newReg.setClockEdgeAttr(op.getClockEdgeAttr());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this cleaner if done directly in the builder asopposed to two statements?

Comment thread lib/Dialect/FIRRTL/FIRRTLOps.cpp Outdated
Comment on lines +6464 to +6467
if (isa<RegOp, RegResetOp>(op) &&
cast<EventControlAttr>(op->getAttr("clockEdge")).getValue() ==
EventControl::AtPosEdge)
elides.push_back("clockEdge");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doing this specialization based on a runtime type check of the operation is weird. Can this instead be moved into the RegOp/RegResetOp printer/parser?

Comment on lines 84 to +91
bool RegOfVecToMemPass::analyzeMemoryPattern(FirRegOp reg,
MemoryPattern &pattern) {
LLVM_DEBUG(llvm::dbgs() << "Analyzing register: " << reg << "\n");

// FirMem ports are positive-edge triggered.
if (reg.getClockEdge() != ClockEdge::Pos)
return false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What are the implications of this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

seq.firmem does not carry the clockEdge attribute, so if we do not check it, the pass will convert the negedge-trigged into posedge-trigged memory, chaging the update timing semantic. Maybe we can add the clockEdge semantic in the seq.firmem later.

Comment on lines +16 to +22

// -----

hw.module @NegEdgeReg(in %clk : !seq.clock, in %arg0: i8) {
// expected-error @below {{only positive-edge registers are currently supported}}
%reg = seq.firreg %arg0 clock %clk {clockEdge = 1 : i32} : i8
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for including a test of this!

Comment on lines +4190 to +4191
// CHECK: %reg = firrtl.reg %clock {clockEdge = 1 : i32}
%reg = firrtl.regreset %clock, %zero, %dummy {clockEdge = 1 : i32} : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This printer/parser syntax isn't ideal. Can this instead switch to something that either: used an attribute with a real name:

    %reg = firrtl.regreset %clock, %zero, %dummy {clockEdge = negedge} : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>

Or:

    %reg = firrtl.regreset negedge %clock, %zero, %dummy : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>

The latter is what I would go with.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Now it represented as:

firrtl.reg %clock
firrtl.reg negedge %clock
firrtl.regreset negedge %clock, %reset, %value

@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch from a95574b to 0c31fe0 Compare July 24, 2026 08:55
@circt-bot

circt-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

Results of circt-tests run for 0c31fe0 compared to results for f853892: no change to test results.

@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch from 0c31fe0 to 72ee42a Compare July 24, 2026 10:13
@circt-bot

circt-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

Results of circt-tests run for 72ee42a compared to results for f853892: no change to test results.

@Clo91eaf
Clo91eaf force-pushed the firrtl-reg-clock-edge branch from 72ee42a to 3e8cf11 Compare July 24, 2026 13:43
@circt-bot

circt-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

Results of circt-tests run for 3e8cf11 compared to results for f853892: no change to test results.

@Clo91eaf

Copy link
Copy Markdown
Contributor Author

@seldridge cc

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