Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No inlining let-bound global vars with clock types #2846

Merged
merged 1 commit into from
Dec 3, 2024
Merged

Conversation

christiaanb
Copy link
Member

The global vars are usually backed by a clock generator that are not work-free.

In addition, when these global vars are recursively defined, they can mess up the post-normalization flattening stage which then violates certain invariants of the netlist generation stage. This then causes the netlist generation stage to generate bad Verilog names.

Fixes #2845

Still TODO:

  • Write a changelog entry (see changelog/README.md)
  • Check copyright notices are up to date in edited files

Copy link
Member

@DigitalBrains1 DigitalBrains1 left a comment

Choose a reason for hiding this comment

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

I think we should backport this to 1.8?

Furthermore, it wouldn't surprise me if the reproducer in clash-testsuite that is added here no longer reproduces the issue once issue #2570 is truly fixed. This is just something I realised and wanted to point out.

Copy link
Member

@leonschoorl leonschoorl left a comment

Choose a reason for hiding this comment

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

Are you aware that bindConstantVar already checks for (vars with) local ids?

test _ (i,stripTicks -> e) = case isLocalVar e of
-- Don't inline `let x = x in x`, it throws us in an infinite loop
True -> return (i `notElemFreeVars` e)

I think that means that your (isLocalId v) will be False, except possibly when we recurse inside isWorkFreeIsh.

Comment on lines +158 to +160
-- Only local variables with a clock type are work-free. When it is a global
-- variable, it is probably backed by a clock generator, which is definitely
-- not work-free.
Copy link
Member

Choose a reason for hiding this comment

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

The local variable distinction feel arbitrary to me.

Given:

clk = clockGen

clkGlobal = clk

f =
 let
   clkLocal = clk
   clkA = clkLocal
   clkB = clkGlobal
 in [...]

As I understand it, you're saying here it is ok for bindConstantVar to replace clkA inside of [...] with clkLocal, but not clkB with clkGlobal.
Is that what you're saying?
And if so, why?
They seem equivalent to me.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that's what I'm saying. Inlining clkA everywhere will not make the circuit f any larger (perform work). Inlining clkB everywhere will make the circuit f larger, because it will have duplicated calls to what will ultimately be clockGen.

--
-- Inlining let-bindings referencing a global variable with a clock type
-- can sometimes lead to the post-normalization flattening stage to generate
-- code that violates the invariants of the netlist generation stage.
Copy link
Member

Choose a reason for hiding this comment

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

Do you remember which invariants?
And are these invariants actually documented anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot have let-expressions appear in the argument position of an application.

@christiaanb
Copy link
Member Author

Are you aware that bindConstantVar already checks for (vars with) local ids?

test _ (i,stripTicks -> e) = case isLocalVar e of
-- Don't inline `let x = x in x`, it throws us in an infinite loop
True -> return (i `notElemFreeVars` e)

I think that means that your (isLocalId v) will be False, except possibly when we recurse inside isWorkFreeIsh.

I am aware. But prior to this commit/PR, the compiler would inline let-bindings binding global variables with a clock type, which is bad because it duplicates work/increases the size of the circuit..

Do not inline let-bound recursive calls
@christiaanb
Copy link
Member Author

I think @leonschoorl comments correctly identify a missed CSE oppertunity in that the current implementation will, for:

clk = clockGen

clkGlobal = clk

f =
 let
   clkLocal = clk
   clkA = clkLocal
   clkB = clkGlobal
 in [...]

not transform f such that ultimately there is only one application/call of the clockGen primitive. While it is most likely desirable in increase sharing in this case, that has never been a guarantee/must of the Clash compiler. That's because CSE is not always desirable as it increases fanout. Decreasing sharing should however be considered a bug, which this PR actually fixes.

That being said, once this PR is merged, we should probably open an "enhancement" issue that Clash is missing a desirable CSE opportunity.

@DigitalBrains1
Copy link
Member

I'd like to call your attention to the point that there can ever only be one clock primitive in one domain.¹ Two clock primitives might generate the same frequency, but they are probably not perfectly phase-aligned, making them separate clock domains. So with constructions like clkGlobal = clk, you have to wonder: is this ever actually going to occur in a valid design? Or is this already user error since they are now putting things in the same domain even though they are not synchronised to the same clock? If I want stuff to be in the same clock domain, I pass them all the exact same bind of the clock variable, not clk to one and clkGlobal to another.

¹ The other way around is fine: you could have one clock primitive which outputs multiple clocks.

@christiaanb christiaanb merged commit 47b8fa7 into master Dec 3, 2024
13 checks passed
@christiaanb christiaanb deleted the fix_2845 branch December 3, 2024 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clash generates invalid verilog names
4 participants