[FIRRTL][Seq] Add explicit clock edge - #10879
Conversation
seldridge
left a comment
There was a problem hiding this comment.
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.
| // 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]>; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| Optional<I1>:$reset, Optional<AnyType>:$resetValue, | ||
| UnitAttr:$isAsync | ||
| UnitAttr:$isAsync, | ||
| OptionalAttr<ClockEdgeAttr>:$clockEdge |
There was a problem hiding this comment.
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.
| case EventControl::AtEdge: | ||
| return seq::ClockEdgeAttr::get(context, seq::ClockEdge::Both); | ||
| } | ||
| return {}; |
There was a problem hiding this comment.
This looks unnecessary given that the case is fully covered.
| if (!edge) | ||
| return {}; |
There was a problem hiding this comment.
In purging the optional this should go away.
| // 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
This should go away with mandatory edgedness.
| return sv::EventControl::AtEdge; | ||
| } | ||
| return sv::EventControl::AtPosEdge; | ||
| } |
| if (reg.getClockEdge().value_or(seq::ClockEdge::Pos) != | ||
| seq::ClockEdge::Pos) { | ||
| reg.emitOpError("only positive-edge registers are currently " | ||
| "supported"); | ||
| return signalPassFailure(); | ||
| } |
There was a problem hiding this comment.
Same comments about edgedness.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
86fc858 to
161c820
Compare
161c820 to
874c78e
Compare
874c78e to
a95574b
Compare
seldridge
left a comment
There was a problem hiding this comment.
This continues to shape up great. I'm good to defer the FIRRTL syntax changes until later. The proposed syntax looks good.
There was a problem hiding this comment.
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.
| auto newReg = | ||
| RegOp::create(*builder, field.type, op.getClockVal(), "", | ||
| NameKindEnum::DroppableName, attrs, StringAttr{}); | ||
| newReg.setClockEdgeAttr(op.getClockEdgeAttr()); |
There was a problem hiding this comment.
Is this cleaner if done directly in the builder asopposed to two statements?
| if (isa<RegOp, RegResetOp>(op) && | ||
| cast<EventControlAttr>(op->getAttr("clockEdge")).getValue() == | ||
| EventControl::AtPosEdge) | ||
| elides.push_back("clockEdge"); |
There was a problem hiding this comment.
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?
| 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; | ||
|
|
There was a problem hiding this comment.
What are the implications of this?
There was a problem hiding this comment.
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.
|
|
||
| // ----- | ||
|
|
||
| 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 | ||
| } |
There was a problem hiding this comment.
Thank you for including a test of this!
| // 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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Agreed. Now it represented as:
firrtl.reg %clock
firrtl.reg negedge %clock
firrtl.regreset negedge %clock, %reset, %value
a95574b to
0c31fe0
Compare
0c31fe0 to
72ee42a
Compare
72ee42a to
3e8cf11
Compare
|
@seldridge cc |
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