Skip to content

Commit 0530bdb

Browse files
Improve Builder.forceName collision errors (#3012)
1 parent 79765dc commit 0530bdb

File tree

5 files changed

+114
-15
lines changed

5 files changed

+114
-15
lines changed

core/src/main/scala/chisel3/Module.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,14 @@ package experimental {
381381
if (_namespace.contains(name)) {
382382
Builder.error(
383383
s"""Unable to name port $port to "$name" in $this,""" +
384-
s" name is already taken by another port! ${source}"
384+
s" name is already taken by another port! ${source.makeMessage(x => x)}"
385385
)(UnlocatableSourceInfo)
386386
}
387387
port.setRef(ModuleIO(this, _namespace.name(name)))
388388
case None =>
389389
Builder.error(
390390
s"Unable to name port $port in $this, " +
391-
s"try making it a public field of the Module $source"
391+
s"try making it a public field of the Module ${source.makeMessage(x => x)}"
392392
)(UnlocatableSourceInfo)
393393
port.setRef(ModuleIO(this, "<UNNAMED>"))
394394
}

core/src/main/scala/chisel3/RawModule.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends
4848
if (port._computeName(None).isEmpty) {
4949
Builder.error(
5050
s"Unable to name port $port in $this, " +
51-
s"try making it a public field of the Module $source"
51+
s"try making it a public field of the Module ${source.makeMessage(x => x)}"
5252
)(UnlocatableSourceInfo)
5353
}
5454
}

core/src/main/scala/chisel3/internal/Builder.scala

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,6 @@ private[chisel3] class Namespace(keywords: Set[String]) {
4444
}
4545
}
4646

47-
private def sanitize(s: String, leadingDigitOk: Boolean = false): String = {
48-
// TODO what character set does FIRRTL truly support? using ANSI C for now
49-
def legalStart(c: Char) = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
50-
def legal(c: Char) = legalStart(c) || (c >= '0' && c <= '9')
51-
val res = if (s.forall(legal)) s else s.filter(legal)
52-
val headOk = (!res.isEmpty) && (leadingDigitOk || legalStart(res.head))
53-
if (headOk) res else s"_$res"
54-
}
55-
5647
/** Checks if `n` ends in `_\d+` and returns the substring before `_` if so, null otherwise */
5748
// TODO can and should this be folded in to sanitize? Same iteration as the forall?
5849
private def prefix(n: String): Int = {
@@ -241,11 +232,31 @@ private[chisel3] trait HasId extends chisel3.InstanceId {
241232
refBuilder: String => Arg = Ref(_)
242233
): Unit =
243234
if (_ref.isEmpty) {
244-
val candidate_name = _computeName(Some(default).filterNot(_ => errorIfDup)).get
235+
val candidate_name = _computeName(Some(default).filterNot(_ => errorIfDup)).getOrElse {
236+
throwException(
237+
s"Attempted to name a nameless IO port ($this): this is usually caused by instantiating an IO but not assigning it to a val.\n" +
238+
s"Assign $this to a val, or explicitly call suggestName to seed a unique name"
239+
)
240+
}
241+
242+
val sanitized = sanitize(candidate_name)
245243
val available_name = namespace.name(candidate_name)
246-
if (errorIfDup && (available_name != candidate_name)) {
247-
Builder.error(s"Cannot have duplicate names $available_name and $candidate_name")(UnlocatableSourceInfo)
244+
245+
// Check for both cases of name duplication
246+
if (errorIfDup && (available_name != sanitized)) {
247+
// If sanitization occurred, then the sanitized name duplicate an existing name
248+
if ((candidate_name != sanitized)) {
249+
Builder.error(
250+
s"Attempted to name $this with an unsanitary name '$candidate_name': sanitization results in a duplicated name '$sanitized'. Please seed a more unique name"
251+
)(UnlocatableSourceInfo)
252+
} else {
253+
// Otherwise the candidate name duplicates an existing name
254+
Builder.error(
255+
s"Attempted to name $this with a duplicated name '$candidate_name'. Use suggestName to seed a unique name"
256+
)(UnlocatableSourceInfo)
257+
}
248258
}
259+
249260
setRef(refBuilder(available_name))
250261
// Clear naming prefix to free memory
251262
naming_prefix = Nil

core/src/main/scala/chisel3/internal/package.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ package object internal {
6464
}
6565
}
6666

67+
// Sanitizes a name, e.g. from a `HasId`, by stripping all non ANSI-C characters
68+
private[chisel3] def sanitize(s: String, leadingDigitOk: Boolean = false): String = {
69+
// TODO what character set does FIRRTL truly support? using ANSI C for now
70+
def legalStart(c: Char) = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
71+
def legal(c: Char) = legalStart(c) || (c >= '0' && c <= '9')
72+
val res = if (s.forall(legal)) s else s.filter(legal)
73+
val headOk = (!res.isEmpty) && (leadingDigitOk || legalStart(res.head))
74+
if (headOk) res else s"_$res"
75+
}
76+
6777
// Private reflective version of "val io" to maintain Chisel.Module semantics without having
6878
// io as a virtual method. See https://github.com/freechipsproject/chisel3/pull/1550 for more
6979
// information about the removal of "val io"
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chiselTests
4+
5+
import chisel3._
6+
import circt.stage.ChiselStage
7+
8+
class NameCollisionSpec extends ChiselFlatSpec with Utils {
9+
behavior.of("Builder")
10+
11+
it should "error on duplicated names with a correct message" in {
12+
(the[ChiselException] thrownBy extractCause[ChiselException] {
13+
ChiselStage.elaborate(
14+
new Module {
15+
val foo, bar = IO(Input(UInt(8.W)))
16+
val out = IO(Output(UInt(8.W)))
17+
18+
// Rename both inputs to the same name
19+
foo.suggestName("same")
20+
bar.suggestName("same")
21+
22+
out := foo + bar
23+
},
24+
Array("--throw-on-first-error")
25+
)
26+
}).getMessage should include(
27+
"Attempted to name NameCollisionSpec_Anon.same: IO[UInt<8>] with a duplicated name 'same'."
28+
)
29+
}
30+
31+
it should "error on sanitization resulting in duplicated names with a helpful message" in {
32+
// Case one: An unsanitary name that results in a collision with an existing name once sanitized
33+
(the[ChiselException] thrownBy extractCause[ChiselException] {
34+
ChiselStage.elaborate(
35+
new Module {
36+
val foo, bar = IO(Input(UInt(8.W)))
37+
val out = IO(Output(UInt(8.W)))
38+
39+
// Seed an initial name with no necessary sanitization
40+
foo.suggestName("unsanitary")
41+
// Seed an additional name which, when sanitized, collides with the previous name
42+
bar.suggestName("unsanitary-")
43+
44+
out := foo + bar
45+
},
46+
Array("--throw-on-first-error")
47+
)
48+
}).getMessage should include(
49+
"Attempted to name NameCollisionSpec_Anon.unsanitary-: IO[UInt<8>] with an unsanitary name 'unsanitary-'"
50+
)
51+
52+
// Case two: An unsanitary name which does not collide with any names once sanitized. No error is raised
53+
ChiselStage.elaborate(
54+
new Module {
55+
val foo, bar = IO(Input(UInt(8.W)))
56+
val out = IO(Output(UInt(8.W)))
57+
58+
out.suggestName("unsanitary-")
59+
60+
out := foo + bar
61+
},
62+
Array("--throw-on-first-error")
63+
)
64+
}
65+
66+
it should "error on nameless ports being assigned default names" in {
67+
68+
(the[ChiselException] thrownBy extractCause[ChiselException] {
69+
ChiselStage.elaborate(
70+
new Module {
71+
// Write to an output port that isn't assigned to a val, and so doesn't get prefixed
72+
IO(Output(UInt(8.W))) := 123.U
73+
},
74+
Array("--throw-on-first-error")
75+
)
76+
}).getMessage should include("try making it a public field of the Module")
77+
}
78+
}

0 commit comments

Comments
 (0)