Skip to content

Commit a018a54

Browse files
committed
[core] Add Placeholder API
Add an advanced Chisel API which allows for generation of hardware at a remembered point in the design. This API allows a user to create an object of `class Placeholder` which, if used later via its `append` method, will create hardware at the location where the `Placeholder` object was created. This is intended to be used to support certain generator patterns where hardware must be created _before_ some other hardware, but only after the other hardware has been built. E.g., this can be used to support creating and defining layer-colored probes after constructing a layer block. Signed-off-by: Schuyler Eldridge <[email protected]>
1 parent 1b7f6e8 commit a018a54

File tree

6 files changed

+256
-1
lines changed

6 files changed

+256
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chisel3
4+
5+
import chisel3.experimental.SourceInfo
6+
import chisel3.internal.Builder
7+
import chisel3.internal.firrtl.ir
8+
9+
/** A [[Placeholder]] is an _advanced_ API for Chisel generators to generate
10+
* additional hardware at a different point in the design.
11+
*
12+
* For example, this can be used to add a wire _before_ another wire:
13+
* {{{
14+
* val placeholder = new Placeholder()
15+
* val a = Wire(Bool())
16+
* val b = placeholder.append {
17+
* Wire(Bool())
18+
* }
19+
* }}}
20+
*
21+
* This will generate the following FIRRTL where `b` is declared _before_ `a`:
22+
* {{{
23+
* wire b : UInt<1>
24+
* wire a : UInt<1>
25+
* }}}
26+
*/
27+
private[chisel3] class Placeholder()(implicit sourceInfo: SourceInfo) {
28+
29+
private val state = Builder.State.save
30+
31+
private val placeholder = Builder.pushCommand(new ir.Placeholder(sourceInfo = sourceInfo))
32+
33+
/** Generate the hardware of the `thunk` and append the result at the point
34+
* where this [[Placeholder]] was created. The return value will be what the
35+
* `thunk` returns which enables getting a reference to the generated
36+
* hardware.
37+
*
38+
* @param thunk the hardware to generate and append to the [[Placeholder]]
39+
* @return the return value of the `thunk`
40+
*/
41+
def append[A](thunk: => A): A = Builder.State.guard(state) {
42+
Builder.currentBlock.get.appendToPlaceholder(placeholder)(thunk)
43+
}
44+
45+
}

core/src/main/scala/chisel3/internal/firrtl/Converter.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
package chisel3.internal.firrtl
44

5-
import chisel3._
5+
import chisel3.{Placeholder => _, _}
66
import chisel3.experimental._
77
import chisel3.experimental.{NoSourceInfo, SourceInfo, SourceLine, UnlocatableSourceInfo}
88
import chisel3.properties.Property
@@ -266,6 +266,8 @@ private[chisel3] object Converter {
266266
)
267267
case LayerBlock(info, layer, region) =>
268268
fir.LayerBlock(convert(info), layer, convert(region, ctx, typeAliases))
269+
case Placeholder(info, block) =>
270+
convert(block, ctx, typeAliases)
269271
}
270272

271273
/** Convert Chisel IR Commands into FIRRTL Statements

core/src/main/scala/chisel3/internal/firrtl/IR.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,39 @@ private[chisel3] object ir {
337337
) extends Definition
338338
case class DefObject(sourceInfo: SourceInfo, id: HasId, className: String) extends Definition
339339

340+
class Placeholder(val sourceInfo: SourceInfo) extends Command {
341+
private var commandBuilder: mutable.Builder[Command, ArraySeq[Command]] = null
342+
343+
private var commands: Seq[Command] = null
344+
345+
private var closed: Boolean = false
346+
347+
private def close(): Unit = {
348+
if (commandBuilder == null)
349+
commands = Seq.empty
350+
else
351+
commands = commandBuilder.result()
352+
closed = true
353+
}
354+
355+
def getBuffer: mutable.Builder[Command, ArraySeq[Command]] = {
356+
require(!closed, "cannot add more commands to a closed Placeholder")
357+
if (commandBuilder == null)
358+
commandBuilder = ArraySeq.newBuilder[Command]
359+
commandBuilder
360+
}
361+
362+
}
363+
364+
object Placeholder {
365+
def unapply(placeholder: Placeholder): Option[(SourceInfo, Seq[Command])] = {
366+
placeholder.close()
367+
Some(
368+
(placeholder.sourceInfo, placeholder.commands)
369+
)
370+
}
371+
}
372+
340373
class Block(val sourceInfo: SourceInfo) {
341374
// While building block, commands go into _commandsBuilder.
342375
private var _commandsBuilder = ArraySeq.newBuilder[Command]
@@ -355,6 +388,14 @@ private[chisel3] object ir {
355388
_commandsBuilder += c
356389
}
357390

391+
def appendToPlaceholder[A](placeholder: Placeholder)(thunk: => A): A = {
392+
val oldPoint = _commandsBuilder
393+
_commandsBuilder = placeholder.getBuffer
394+
val result = thunk
395+
_commandsBuilder = oldPoint
396+
result
397+
}
398+
358399
def close() = {
359400
if (_commands == null) {
360401
_commands = _commandsBuilder.result()

src/main/scala-2/chisel3/aop/Select.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ object Select {
4444
case cmd @ LayerBlock(_, _, region) =>
4545
val head = f.lift(cmd).toSeq
4646
head ++ collect(region)(f)
47+
case cmd @ Placeholder(_, block) =>
48+
collect(block)(f)
4749
case cmd if f.isDefinedAt(cmd) => Some(f(cmd))
4850
case _ => None
4951
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chisel3
4+
5+
import chiselTests.{ChiselFlatSpec, FileCheck}
6+
import circt.stage.ChiselStage
7+
8+
class PlaceholderSpec extends ChiselFlatSpec with FileCheck {
9+
10+
"Placeholders" should "allow insertion of commands" in {
11+
12+
class Foo extends RawModule {
13+
14+
val placeholder = new Placeholder()
15+
16+
val a = Wire(UInt(1.W))
17+
18+
val b = placeholder.append {
19+
Wire(UInt(2.W))
20+
}
21+
22+
}
23+
24+
generateFirrtlAndFileCheck(new Foo) {
25+
s"""|CHECK: wire b : UInt<2>
26+
|CHECK-NEXT: wire a : UInt<1>
27+
|""".stripMargin
28+
}
29+
30+
}
31+
32+
they should "be capable of being nested" in {
33+
34+
class Foo extends RawModule {
35+
36+
val placeholder_1 = new Placeholder()
37+
val placeholder_2 = placeholder_1.append(new Placeholder())
38+
39+
val a = Wire(UInt(1.W))
40+
41+
val b = placeholder_1.append {
42+
Wire(UInt(2.W))
43+
}
44+
45+
val c = placeholder_2.append {
46+
Wire(UInt(3.W))
47+
}
48+
49+
val d = placeholder_1.append {
50+
Wire(UInt(4.W))
51+
}
52+
53+
}
54+
55+
generateFirrtlAndFileCheck(new Foo) {
56+
s"""|CHECK: wire c : UInt<3>
57+
|CHECK-NEXT: wire b : UInt<2>
58+
|CHECK-NEXT: wire d : UInt<4>
59+
|CHECK-NEXT: wire a : UInt<1>
60+
|""".stripMargin
61+
}
62+
63+
}
64+
65+
they should "emit no statements if empty" in {
66+
67+
class Foo extends RawModule {
68+
val a = new Placeholder()
69+
}
70+
71+
generateFirrtlAndFileCheck(new Foo) {
72+
"""|CHECK: public module Foo :
73+
|CHECK: skip
74+
|""".stripMargin
75+
}
76+
}
77+
78+
they should "allow constructing hardware in a parent" in {
79+
80+
class Bar(placeholder: Placeholder, a: Bool) extends RawModule {
81+
82+
placeholder.append {
83+
val b = Wire(Bool())
84+
b :<= a
85+
}
86+
87+
}
88+
89+
class Foo extends RawModule {
90+
91+
val a = Wire(Bool())
92+
val placeholder = new Placeholder
93+
94+
val bar = Module(new Bar(placeholder, a))
95+
96+
}
97+
98+
generateFirrtlAndFileCheck(new Foo) {
99+
"""|CHECK: module Bar :
100+
|CHECK-NOT: {{wire|connect}}
101+
|
102+
|CHECK: module Foo :
103+
|CHECK: wire a : UInt<1>
104+
|CHECK-NEXT: wire b : UInt<1>
105+
|CHECK-NEXT: connect b, a
106+
|CHECK-NEXT: inst bar of Bar
107+
|""".stripMargin
108+
}
109+
110+
}
111+
112+
// TODO: This test can be changed to pass in the future in support of advanced
113+
// APIs like Providers or an improved BoringUtils.
114+
they should "error if constructing hardware in a child" in {
115+
116+
class Bar extends RawModule {
117+
118+
val a = Wire(Bool())
119+
val placeholder = new Placeholder()
120+
121+
}
122+
123+
class Foo extends RawModule {
124+
125+
val bar = Module(new Bar)
126+
127+
bar.placeholder.append {
128+
val b = Wire(Bool())
129+
b :<= bar.a
130+
}
131+
132+
}
133+
134+
intercept[IllegalArgumentException] { ChiselStage.emitCHIRRTL(new Foo) }.getMessage() should include(
135+
"Can't write to module after module close"
136+
)
137+
138+
}
139+
140+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chisel3
4+
5+
import chisel3.aop.Select
6+
import chisel3.stage.{ChiselGeneratorAnnotation, DesignAnnotation}
7+
import chiselTests.ChiselFlatSpec
8+
9+
class SelectSpec extends ChiselFlatSpec {
10+
11+
"Placeholders" should "be examined" in {
12+
class Foo extends RawModule {
13+
val placeholder = new Placeholder()
14+
val a = Wire(Bool())
15+
val b = placeholder.append {
16+
Wire(Bool())
17+
}
18+
}
19+
val design = ChiselGeneratorAnnotation(() => {
20+
new Foo
21+
}).elaborate(1).asInstanceOf[DesignAnnotation[Foo]].design
22+
Select.wires(design).size should be(2)
23+
}
24+
25+
}

0 commit comments

Comments
 (0)