Skip to content

Conversation

jackkoenig
Copy link
Contributor

@jackkoenig jackkoenig commented Jun 15, 2022

Note, I've rendered the updated docs here.

Chisel and FIRRTL have long used signals with names beginning with an
underscore as an API to specify that the name does not really matter.
Tools like Verilator follow a similar convention and exclude signals
with underscore names from waveform dumps by default. With the
introduction of compiler-plugin prefixing in Chisel 3.4, the convention
remained but was hard for users to use unless the unnnamed signal
existed outside of any prefix domain. Notably, unnamed signals are most
useful when creating wires inside of utility methods which almost always
results in the signal ending up with a prefix.

With this commit, Chisel explicitly recognizes signals whos val names
start with an underscore and preserve that underscore regardless of any
prefixing. Chisel will also ignore such underscores when generating
prefixes based on the temporary signal, preventing accidental double
underscores in the names of signals that are prefixed by the temporary.

Here's a specific example of the change this makes, Given:

class Example extends Module {
  val foo, bar = IO(Input(UInt(8.W)))
  val out = {
    val port = IO(Output(UInt(8.W)))
    val _sum = foo + bar
    val _sum2 = Wire(UInt(8.W))
    _sum2 := foo + 1.U
    port := _sum + _sum2
    port
  }
}

Chisel master (and 3.4.3) currently compile this to:

module Example :
  input clock : Clock
  input reset : UInt<1>
  input foo : UInt<8>
  input bar : UInt<8>
  output out : UInt<8>

  node _out__sum_T = add(foo, bar)
  node out__sum = tail(_out__sum_T, 1)
  wire out__sum2 : UInt<8>
  node _out__sum2_T = add(foo, UInt<1>("h1"))
  node _out__sum2_T_1 = tail(_out__sum2_T, 1)
  out__sum2 <= _out__sum2_T_1
  node _out_port_T = add(out__sum, out__sum2)
  node _out_port_T_1 = tail(_out_port_T, 1)
  out <= _out_port_T_1

Now this is compiled to:

  module Example :
    input clock : Clock
    input reset : UInt<1>
    input foo : UInt<8>
    input bar : UInt<8>
    output out : UInt<8>

    node _out_sum_T = add(foo, bar)
    node _out_sum = tail(_out_sum_T, 1)
    wire _out_sum2 : UInt<8>
    node _out_sum2_T = add(foo, UInt<1>("h1"))
    node _out_sum2_T_1 = tail(_out_sum2_T, 1)
    _out_sum2 <= _out_sum2_T_1
    node _out_port_T = add(_out_sum, _out_sum2)
    node _out_port_T_1 = tail(_out_port_T, 1)
    out <= _out_port_T_1 

Note that out__sum and out__sum2 become _out_sum and _out_sum2. This change comes from Chisel effectively recognizing the leading _ is not actually part of the name, but is the signifier of a temporary so it is stripped from the "seed" and moved to the front of the final name.

Also note that _out__sum2_T becomes _out_sum2_T. This functionality looks similar to the previous paragraph, but is actually a totally different change where Chisel, again recognizing that _ is the signifier of a temporary and not really part of the name, actually ignores the leading _ for generating prefixes from signals.

Between these two changes, I think the naming behavior is a pretty nice improvement over the current behavior.

Contributor Checklist

  • Did you add Scaladoc to every public function/method?
  • Did you add at least one test demonstrating the PR?
  • Did you delete any extraneous printlns/debugging code?
  • Did you specify the type of improvement?
  • Did you add appropriate documentation in docs/src?
  • Did you state the API impact?
  • Did you specify the code generation impact?
  • Did you request a desired merge strategy?
  • Did you add text to be included in the Release Notes for this change?

Type of Improvement

  • new feature/API

API Impact

This adds a new API to let users creating "temporary" signals. I will note that this might have a s

Backend Code Generation Impact

This only changes names when there are vals in user's Chisel code which names that start with _. This is an extremely uncommon pattern so given the power this unlocks and the improved user API, I think this is appropriate for release in v3.5.4.

Desired Merge Strategy

  • Squash

Release Notes

Define leading '_' as API for creating temporaries. Chisel will now preserve the leading _ for any val-named signal which signifies to FIRRTL and other backend tools that the name does not matter. Chisel also now ignores the leading _ in prefixes generated from such "unnamed signals" which reduces the number of cases of double __ in the middle of signal names.

Reviewer Checklist (only modified by reviewer)

  • Did you add the appropriate labels?
  • Did you mark the proper milestone (Bug fix: 3.4.x, [small] API extension: 3.5.x, API modification or big change: 3.6.0)?
  • Did you review?
  • Did you check whether all relevant Contributor checkboxes have been checked?
  • Did you do one of the following when ready to merge:
    • Squash: You/ the contributor Enable auto-merge (squash), clean up the commit message, and label with Please Merge.
    • Merge: Ensure that contributor has cleaned up their commit history, then merge with Create a merge commit.

@jackkoenig jackkoenig added this to the 3.6.0 milestone Jun 15, 2022
@jackkoenig
Copy link
Contributor Author

I've marked this 3.6 because it has a fairly large impact on code gen stability when bumping and I am not sure that's a great thing to do in a minor release. That being said I could be convinced that this makes sense in 3.5.4.

@@ -66,15 +66,15 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends
if (id.isSynthesizable) {
id.topBinding match {
case OpBinding(_, _) =>
id.forceName(Some(""), default = "T", _namespace)
id.forceName(None, default = "_T", _namespace)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized that my changes here make the "defaultPrefix" argument of _computeName redundant so I need to further clean that up.

@@ -186,7 +186,7 @@ private[chisel3] trait HasId extends InstanceId {
} else {
defaultSeed.map { default =>
defaultPrefix match {
case Some(p) => buildName(default, p :: naming_prefix.reverse)
case Some(p) => buildName(default, naming_prefix.reverse)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code can get a lot simpler now, TODO fix.

@jackkoenig jackkoenig requested review from azidar and mwachs5 June 15, 2022 00:27
@seldridge
Copy link
Member

Define leading '_' as API for creating temporaries. Chisel will now preserve the leading _ for any val-named signal which signifies to FIRRTL and other backend tools that the name does not matter. Chisel also now ignores the leading _ in prefixes generated from such "unnamed signals" which reduces the number of cases of double __ in the middle of signal names.

This release notes info is great.

WDYT about codifying some of this into a Chisel spec?

Possibly useful, but the way this is evolving on the FIRRTL Dialect side is that ops have a name kind enum that qualify the utility of keeping a name around and let the compiler reason about this for different optimization levels. Currently there are two values: (1) interesting and (2) droppable. This matches the existing non-underscore (droppable) and named (interesting) convention, but allows for further enumeration going forward and avoids permanently encoding this information in the leading character of a string.

@jackkoenig jackkoenig force-pushed the temporary-naming-api branch from 9bce4d1 to 9204d39 Compare June 15, 2022 18:13
@jackkoenig jackkoenig force-pushed the temporary-naming-api branch from 9204d39 to 0c23a9c Compare June 15, 2022 18:18
Copy link
Contributor

@mwachs5 mwachs5 left a comment

Choose a reason for hiding this comment

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

forgot to hit "submit review" maybe you already addressed this

@@ -202,6 +225,84 @@ class Example8 extends Module {
ChiselStage.emitVerilog(new Example8)
```

Note that using `.suggestName` does *not* affect prefixes derived from val names;
Copy link
Contributor

Choose a reason for hiding this comment

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

can you give an example of this val name case not affecting suggestName results?

Maybe this whole section should be warning-ed with " this is how suggestName works today, its behavior of is subject to change..."

Copy link
Contributor Author

@jackkoenig jackkoenig Jun 15, 2022

Choose a reason for hiding this comment

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

The example is right below this line 😉, pasting from the mdoc-output of this section:

EDIT: See updated version in comment below


Note that using .suggestName does not affect prefixes derived from val names; however, it can affect prefixes derived from connections (eg. :=):

class ConnectionPrefixExample extends Module {
  val in0 = IO(Input(UInt(2.W)))
  val in1 = IO(Input(UInt(2.W)))

  val out0 = IO(Output(UInt()))
  val out1 = IO(Output(UInt()))

  out0 := {
    // out0_sum
    val sum = in0 + in1
    sum + 1.U
  }
  // Comes after so does *not* affect prefix above
  out0.suggestName("foo")

  // Comes before so *does* affect prefix below
  out1.suggestName("bar")
  out1 := {
    // bar_diff
    val diff = in0 - in1
    diff + 1.U
  }
}
module ConnectionPrefixExample(
  input        clock,
  input        reset,
  input  [1:0] in0,
  input  [1:0] in1,
  output [1:0] foo,
  output [1:0] bar
);
  wire [1:0] out0_sum = in0 + in1; // @[naming.md 235:19]
  wire [1:0] bar_diff = in0 - in1; // @[naming.md 245:20]
  assign foo = out0_sum + 2'h1; // @[naming.md 236:9]
  assign bar = bar_diff + 2'h1; // @[naming.md 246:10]
endmodule

Copy link
Contributor

Choose a reason for hiding this comment

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

but neither of these examples is showing the suggestName on diff itself

Copy link
Contributor

Choose a reason for hiding this comment

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

but i think you covered that with the link to the "already covered" case about suggestName

Copy link
Contributor

Choose a reason for hiding this comment

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

Wait sorry my response was to the wrong point, be back in a sec

Copy link
Contributor

Choose a reason for hiding this comment

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

just looking for an example to demonstrate what you are saying in " Note that using .suggestName does not affect prefixes derived from val names"

Copy link
Contributor Author

@jackkoenig jackkoenig Jun 15, 2022

Choose a reason for hiding this comment

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

I've added another example to that case, here's the updated text:


Note that using .suggestName does not affect prefixes derived from val names;
however, it can affect prefixes derived from connections (eg. :=):

class ConnectionPrefixExample extends Module {
  val in0 = IO(Input(UInt(2.W)))
  val in1 = IO(Input(UInt(2.W)))

  val out0 = {
    val port = IO(Output(UInt()))
    // Even though this suggestName is before mul, the prefix used in this scope
    // is derived from `val out0`, so this does not affect the name of mul
    port.suggestName("foo")
    // out0_mul
    val mul = in0 * in1
    port := mul + 1.U
    port
  }

  val out1 = IO(Output(UInt()))
  val out2 = IO(Output(UInt()))

  out1 := {
    // out1_sum
    val sum = in0 + in1
    sum + 1.U
  }
  // Comes after so does *not* affect prefix above
  out1.suggestName("bar")

  // Comes before so *does* affect prefix below
  out2.suggestName("fizz")
  out2 := {
    // fizz_diff
    val diff = in0 - in1
    diff + 1.U
  }
}
module ConnectionPrefixExample(
  input        clock,
  input        reset,
  input  [1:0] in0,
  input  [1:0] in1,
  output [3:0] foo,
  output [1:0] bar,
  output [1:0] fizz
);
  wire [3:0] out0_mul = in0 * in1; // @[naming.md 235:19]
  wire [1:0] out1_sum = in0 + in1; // @[naming.md 245:19]
  wire [1:0] fizz_diff = in0 - in1; // @[naming.md 255:20]
  assign foo = out0_mul + 4'h1; // @[naming.md 236:17]
  assign bar = out1_sum + 2'h1; // @[naming.md 246:9]
  assign fizz = fizz_diff + 2'h1; // @[naming.md 256:10]
endmodule

As this example illustrates, this behavior is slightly inconsistent so is subject to change in a future version of Chisel.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I'm going to tweak it to make it even more obvious by putting the .suggestName on port.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay further updated

@jackkoenig
Copy link
Contributor Author

WDYT about codifying some of this into a Chisel spec?

@seldridge I think that would be a good thing to do. It would be fairly straightforward to specify the semantics/rules for emitting FIRRTL at least. It's a bit more difficult for defining down to Verilog until we have stronger guarantees on how things lower from FIRRTL to Verilog but perhaps it's time to at least get the ball rolling.

Chisel and FIRRTL have long used signals with names beginning with an
underscore as an API to specify that the name does not really matter.
Tools like Verilator follow a similar convention and exclude signals
with underscore names from waveform dumps by default. With the
introduction of compiler-plugin prefixing in Chisel 3.4, the convention
remained but was hard for users to use unless the unnnamed signal
existed outside of any prefix domain. Notably, unnamed signals are most
useful when creating wires inside of utility methods which almost always
results in the signal ending up with a prefix.

With this commit, Chisel explicitly recognizes signals whos val names
start with an underscore and preserve that underscore regardless of any
prefixing. Chisel will also ignore such underscores when generating
prefixes based on the temporary signal, preventing accidental double
underscores in the names of signals that are prefixed by the temporary.
@jackkoenig jackkoenig force-pushed the temporary-naming-api branch from 0c23a9c to ec178aa Compare June 15, 2022 23:04
@jackkoenig
Copy link
Contributor Author

@mwachs5 I think this is ready to :shipit:

@jackkoenig
Copy link
Contributor Author

Note this is 3.6.0 though, might be nice to backport some of the docs changes to 3.5 so it shows up on the website

Prefixing can also be derived from the name of signals on the left-hand side of a connection.
While this is not implemented via the compiler plugin, the behavior should feel similar:

```scala mdoc
Copy link
Contributor

Choose a reason for hiding this comment

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

this is very helpful in an explanation doc!

Copy link
Contributor

@mwachs5 mwachs5 left a comment

Choose a reason for hiding this comment

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

This is all crazy complicated but happy to approve the better docs of the status quo if we are open to changing them!

@mwachs5
Copy link
Contributor

mwachs5 commented Jun 15, 2022

Note this is 3.6.0 though, might be nice to backport some of the docs changes to 3.5 so it shows up on the website

I agree -- if we can backport the docs and tests without the _ change that would be good

@jackkoenig
Copy link
Contributor Author

Before we merge this, let me check if this actually impacts names that much. If the impact is very minor, we can backport the full PR. If the impact is huge, then I'll just backport the docs improvements.

@jackkoenig
Copy link
Contributor Author

Okay this actually has zero impact on naming unless you intentionally name things with leading _. And since apparently that is not used at all in SiFive internal codebases at least, it seems that this is a reasonable thing to include in a minor release.

@jackkoenig jackkoenig modified the milestones: 3.6.0, 3.5.x Jun 16, 2022
@jackkoenig jackkoenig merged commit 7b6d2e4 into master Jun 16, 2022
@jackkoenig jackkoenig deleted the temporary-naming-api branch June 16, 2022 00:05
@mergify mergify bot added the Backported This PR has been backported label Jun 16, 2022
mergify bot added a commit that referenced this pull request Jun 16, 2022
…2581)

* Define leading '_' as API for creating temporaries

Chisel and FIRRTL have long used signals with names beginning with an
underscore as an API to specify that the name does not really matter.
Tools like Verilator follow a similar convention and exclude signals
with underscore names from waveform dumps by default. With the
introduction of compiler-plugin prefixing in Chisel 3.4, the convention
remained but was hard for users to use unless the unnnamed signal
existed outside of any prefix domain. Notably, unnamed signals are most
useful when creating wires inside of utility methods which almost always
results in the signal ending up with a prefix.

With this commit, Chisel explicitly recognizes signals whos val names
start with an underscore and preserve that underscore regardless of any
prefixing. Chisel will also ignore such underscores when generating
prefixes based on the temporary signal, preventing accidental double
underscores in the names of signals that are prefixed by the temporary.

(cherry picked from commit bd94366)

* Remove unused defaultPrefix argument from _computeName

(cherry picked from commit ec178aa)

# Conflicts:
#	core/src/main/scala/chisel3/Module.scala
#	core/src/main/scala/chisel3/experimental/hierarchy/ModuleClone.scala

* Resolve backport conflicts

* Waive false positive binary compatibility errors

Co-authored-by: Jack Koenig <koenig@sifive.com>
@jackkoenig jackkoenig mentioned this pull request Jul 27, 2022
16 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backported This PR has been backported
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants