Skip to content

[NFC] Improve L0 cache write port sampling time#34

Open
Scheremo wants to merge 1 commit into
pulp-platform:mainfrom
mosaic-soc:pr-latch-timing
Open

[NFC] Improve L0 cache write port sampling time#34
Scheremo wants to merge 1 commit into
pulp-platform:mainfrom
mosaic-soc:pr-latch-timing

Conversation

@Scheremo

Copy link
Copy Markdown
Contributor

This PR changes the latch-based snitch L0 cache implementation which is used when CFG.EARLY_LATCH is enabled. Up to now, the latch-memory often required (practically) infeasible timing constraints. This PR rewrites the latch cache to arrive at virtually the same timing constraints on the setup window as the flip-flop cache while using less area and causing significantly less leakage in all reasonable configurations.

The first issue with the current implementation is that it transparent-low latches to replace the posedge-triggered flip-flops used in the FF version of the L0 cache. Since the latches' gate pin is driven by a clock gate, this introduces a non-obvious critical timing condition: Since the latches' gate pin must be stable before the negative clock cycle, the clock gate's output must be calculated and latched within half of a clock cycle since its inputs (validate_strb[i]) depend on posedge-triggered flip flops and module inputs (out_rsp_id_i), which in turn depends on checking for a prefetch hit. This can lead to the path becoming critical for the entire cache.

The second issue with the current implementation is that the prefetch/refill path to the L0 storage elements (latches and flipflops) is oftentimes tight if not critical, especially in low-power implementation scenarios where the refilling memory (the L1 cache) has slow (e.g. more than half a clock period) CK -> Q timing. In contrast, the storage elements' read timing is usually much less critical as they are directly fed to a processor core. This forces the implementation to size all L0 storage elements accordingly, often leading to significant leakage and increased drive strength "creep" towards the cores.

Both of these issues are addressed in this pull request; instead of using transparent-low latches, the new implementation uses a posedge-triggered write port flip-flop to latch the refill/prefetch line, and selectively updates the new L0 cache which is implemented using transparent-high latches instead. This style of implementation achieves fundamentally the same cycle latency, and only adds the latches' setup and propagation delay to the cache's read path length. In practice, this is close to negligible.

The first issue of having the clock gate in a (sub-)critical path is fixed by the fact that the latches are transparent-high now; this directly implies we can use the whole clock cycle to set up the enable pin. The second issue of high drive strength on the main storage elements is mitigated as well. Since the refill/prefetch line is now stored in a write port flip-flop, the latches (being the main storage element) are no longer in a timing-relevant path.

Since typical implementations use 8 (or more) L0 cache lines, these leakage savings are quite noticeable. In our experiments this change alone has reduced the leakage of an entire cluster by over 15% without performance degradation compared to the flip-flop variant. Hold constraints are also no more critical than in the previous implementation as the storage latches hold condition is practically always met (the write port flip flop's output value on changes on a posedge) and next receiving storage element's hold condition edge is typically on the posedge of the next cycle.

@Scheremo
Scheremo requested a review from micprog as a code owner April 15, 2026 11:32
This PR changes the latch-based snitch L0 cache implementation which is used when CFG.EARLY_LATCH is enabled.
Up to now, the latch-memory often required (practically) infeasible timing constraints. This PR rewrites the latch cache to arrive at virtually the same timing constraints on the setup window as the flip-flop cache while using less area and causing significantly less leakage in all reasonable configurations.

The first issue with the current implementation is that it transparent-low latches to replace the posedge-triggered flip-flops used in the FF version of the L0 cache. Since the latches' gate pin is driven by a clock gate, this introduces a non-obvious critical timing condition: Since the latches' gate pin must be stable before the negative clock cycle, the clock gate's output must be calculated and latched within half of a clock cycle since its inputs (validate_strb[i]) depend on posedge-triggered flip flops and module inputs (out_rsp_id_i), which in turn depends on checking for a prefetch hit. This can lead to the path becoming critical for the entire cache.

The second issue with the current implementation is that the prefetch/refill path to the L0 storage elements (latches and flipflops) is oftentimes tight if not critical, especially in low-power implementation scenarios where the refilling memory (the L1 cache) has slow (e.g. more than half a clock period) CK -> Q timing. In contrast, the storage elements' read timing is usually much less critical as they are directly fed to a processor core. This forces the implementation to size all L0 storage elements accordingly, often leading to significant leakage and increased drive strength "creep" towards the cores.

Both of these issues are addressed in this pull request; instead of using transparent-low latches, the new implementation uses a posedge-triggered write port flip-flop to latch the refill/prefetch line, and selectively updates the new L0 cache which is implemented using transparent-high latches instead. This style of implementation achieves fundamentally the same cycle latency, and only adds the latches' setup and propagation delay to the cache's read path length. In practice, this is close to negligible.

The first issue of having the clock gate in a (sub-)critical path is fixed by the fact that the latches are transparent-high now; this directly implies we can use the whole clock cycle to set up the enable pin.
The second issue of high drive strength on the main storage elements is mitigated as well. Since the refill/prefetch line is now stored in a write port flip-flop, the latches (being the main storage element) are no longer in a timing-relevant path.

Since typical implementations use 8 (or more) L0 cache lines, these leakage savings are quite noticeable. In our experiments this change alone has reduced the leakage of an entire cluster by over 15% without performance degradation compared to the flip-flop variant. Hold constraints are also no more critical than in the previous implementation as the storage latches hold condition is practically always met (the write port flip flop's output value on changes on a posedge) and next receiving storage element's hold condition edge is typically on the posedge of the next cycle.
@colluca

colluca commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

I understand the first issue, and the change to transparent-high latches makes sense to me.

Regarding the second issue, I'm not particularly convinced by this statement:

storage elements' read timing is usually much less critical as they are directly fed to a processor core

In Snitch the read path combinationally goes all the way through the "decode", "read register" and "execute" stages and up to the "writeback" stage. So I would be a little surprised to see a large imbalance between the two paths . But if you say there are situations in which the refill and read paths are indeed imbalanced, justifying to cut the refill path, this sounds good to me.
However, I have a few concerns about the fix to this issue.

The first concern is that cutting the refill path is indeed a functional change. If I'm not mistaken, the latency of the latch-based implementation is no worse than that of the FF-based implementation, so architecturally this should not be a problem. However, I think it might be more correct to remove the [NFC] label.

The second, and more important, concern is that cutting the out_rsp_data_i signal, but not the out_rsp_valid_i (or the dependent validate_strb) signal leads to the two getting out of sync, and stale data being latched in the cache. Do you see this concern as valid, or did I miss something?

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