Skip to content

Commit be2a0ce

Browse files
committed
[core] Enable returning values from layer blocks
Add support for a limited form of returning values from layer blocks. With this patch, when a `Data` is returned from a layer block, this will now cause the creation of a wire of layer-colored probe type that is driven by a probe of the return value. There is additional handling to directly return a probe type as well (which enables returning from an inner layer block up through an outer layer block). If a non-`Data` is returned, then this will be converted to a `Unit`. This latter behavior is the current behavior. Signed-off-by: Schuyler Eldridge <[email protected]>
1 parent d59874a commit be2a0ce

File tree

2 files changed

+165
-9
lines changed

2 files changed

+165
-9
lines changed

core/src/main/scala/chisel3/Layer.scala

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,15 @@ object layer {
311311
* be the current layer which causes no layer block to be created. (This is
312312
* not a _proper_ ancestor requirement.)
313313
*
314+
* By default, the return of the layer block will be either a subtype of
315+
* [[Data]] or [[Unit]], depending on the `thunk` provided. If the `thunk`
316+
* (the hardware that should be constructed inside the layer block) returns a
317+
* [[Data]], then this will either return a [[Wire]] of layer-colored
318+
* [[Probe]] type if a layer block was created or the underlying [[Data]] if
319+
* no layer block was created. If the `thunk` returns anything else, this
320+
* will return [[Unit]]. This is controlled by the implicit argument `tc`
321+
* and may be customized by advanced users to do other things.
322+
*
314323
* @param layer the layer this block is associated with
315324
* @param skipIfAlreadyInBlock if true, then this will not create a layer if
316325
* this `block` is already inside another layerblock
@@ -321,21 +330,23 @@ object layer {
321330
* @param sourceInfo a source locator
322331
* @throws java.lang.IllegalArgumentException if the layer of the currnet
323332
* layerblock is not an ancestor of the desired layer
333+
* @return either a subtype of [[Data]] or [[Unit]] depending on the `thunk`
334+
* return type
324335
*/
325336
def block[A](
326337
layer: Layer,
327338
skipIfAlreadyInBlock: Boolean = false,
328339
skipIfLayersEnabled: Boolean = false
329340
)(thunk: => A)(
330-
implicit sourceInfo: SourceInfo
331-
): Unit = {
341+
implicit tc: BlockReturnHandler[A] = BlockReturnHandler.unit[A],
342+
sourceInfo: SourceInfo
343+
): tc.R = {
332344
// Do nothing if we are already in a layer block and are not supposed to
333345
// create new layer blocks.
334346
if (
335347
skipIfAlreadyInBlock && Builder.layerStack.size > 1 || skipIfLayersEnabled && Builder.enabledLayers.nonEmpty || Builder.elideLayerBlocks
336348
) {
337-
thunk
338-
return
349+
return tc.identity(thunk)
339350
}
340351

341352
val _layer = Builder.layerMap.getOrElse(layer, layer)
@@ -350,20 +361,37 @@ object layer {
350361
s"a layerblock associated with layer '${_layer.fullName}' cannot be created under a layerblock of non-ancestor layer '${Builder.layerStack.head.fullName}'"
351362
)
352363

364+
if (layersToCreate.isEmpty)
365+
return tc.identity(thunk)
366+
353367
addLayer(_layer)
354368

369+
// Save the append point _before_ the layer block so that we can insert a
370+
// layer-colored wire once the `thunk` executes.
371+
val beforeLayerBlock = new Placeholder
372+
373+
// Track the current layer block. When this is used, this will be the
374+
// innermost layer block that will be created. This is guaranteed to be
375+
// non-null as long as `layersToCreate` is not empty.
376+
var layerBlock: LayerBlock = null
377+
378+
// Recursively create any necessary layers. There are two cases:
379+
//
380+
// 1. There are no layers left to create. Run the thunk, create the
381+
// layer-colored wire, and define it.
382+
// 2. There are layers left to create. Create the next layer and recurse.
355383
def createLayers(layers: List[Layer])(thunk: => A): A = layers match {
356384
case Nil => thunk
357385
case head :: tail =>
358-
val layerBlock = new LayerBlock(sourceInfo, head)
359-
Builder.pushCommand(layerBlock)
386+
layerBlock = Builder.pushCommand(new LayerBlock(sourceInfo, head))
360387
Builder.layerStack = head :: Builder.layerStack
361388
val result = Builder.forcedUserModule.withRegion(layerBlock.region)(createLayers(tail)(thunk))
362389
Builder.layerStack = Builder.layerStack.tail
363390
result
364391
}
365392

366-
createLayers(layersToCreate)(thunk)
393+
val result = createLayers(layersToCreate)(thunk)
394+
return tc.apply(beforeLayerBlock, layerBlock, result)
367395
}
368396

369397
/** API that will cause any calls to `block` in the `thunk` to not create new

src/test/scala/chiselTests/LayerSpec.scala

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import chisel3.probe.{define, Probe, ProbeValue}
1010
import chiselTests.{ChiselFlatSpec, FileCheck, Utils}
1111
import java.nio.file.{FileSystems, Paths}
1212
import _root_.circt.stage.ChiselStage
13+
import chisel3.reflect.DataMirror.internal.chiselTypeClone
1314

1415
class LayerSpec extends ChiselFlatSpec with Utils with FileCheck {
1516

@@ -157,10 +158,10 @@ class LayerSpec extends ChiselFlatSpec with Utils with FileCheck {
157158
"""|CHECK: module Foo :
158159
|CHECK: define x = probe(a)
159160
|CHECK-NEXT: define y = probe(b)
160-
|CHECK-NEXT: layerblock A :
161+
|CHECK: layerblock A :
161162
|CHECK-NEXT: define x = probe(c)
162163
|CHECK-NEXT: define y = probe(d)
163-
|CHECK-NEXT: layerblock B :
164+
|CHECK: layerblock B :
164165
|CHECK-NEXT: define y = probe(e)
165166
|""".stripMargin
166167
}
@@ -306,6 +307,133 @@ class LayerSpec extends ChiselFlatSpec with Utils with FileCheck {
306307
ChiselStage.emitCHIRRTL(new Foo) should include("""layer ExpensiveAsserts, bind, "verification/ExpensiveAsserts"""")
307308
}
308309

310+
"Values returned by layer blocks" should "be layer-colored wires" in {
311+
class Foo extends RawModule {
312+
val out = IO {
313+
Output {
314+
new Bundle {
315+
val a = Probe(UInt(1.W), A)
316+
val b = Probe(UInt(2.W), A.B)
317+
val c = Probe(UInt(3.W), A.B)
318+
val d = Probe(UInt(4.W), A)
319+
}
320+
}
321+
}
322+
323+
// A single layer block
324+
val a = layer.block(A) {
325+
Wire(UInt(1.W))
326+
}
327+
define(out.a, a)
328+
329+
// Auto-creation of parent layer blocks
330+
val b = layer.block(A.B) {
331+
Wire(UInt(2.W))
332+
}
333+
define(out.b, b)
334+
335+
// Nested layer blocks
336+
val c = layer.block(A) {
337+
layer.block(A.B) {
338+
Wire(UInt(3.W))
339+
}
340+
}
341+
define(out.c, c)
342+
343+
// Return of ProbeValue
344+
val d = layer.block(A) {
345+
ProbeValue(Wire(UInt(4.W)))
346+
}
347+
define(out.d, d)
348+
349+
// No layers created. Check all generator code paths.
350+
layer.block(A) {
351+
// Path 1: `skipIfAlreadyInBlock` set
352+
val e = layer.block(C, skipIfAlreadyInBlock = true) {
353+
Wire(UInt(5.W))
354+
}
355+
// Path 2: requested layer is the current layer
356+
val f = layer.block(A) {
357+
Wire(UInt(6.W))
358+
}
359+
}
360+
// Path 3: `skipIfLayersEnabled` is set and a layer is enabled
361+
layer.enable(C)
362+
val g = layer.block(C, skipIfLayersEnabled = true) {
363+
Wire(UInt(7.W))
364+
}
365+
// Path 4: the `elideBlocks` API is used
366+
val h = layer.elideBlocks {
367+
layer.block(A) {
368+
Wire(UInt(8.W))
369+
}
370+
}
371+
372+
// Return non-`Data` as `Unit`.
373+
val i = layer.block(A) {
374+
42
375+
}
376+
assert(i.getClass() == classOf[Unit])
377+
378+
// Return `Unit` as `Unit`.
379+
val j = layer.block(A) { () }
380+
assert(j.getClass() == classOf[Unit])
381+
}
382+
383+
generateFirrtlAndFileCheck(new Foo) {
384+
s"""|CHECK: module Foo
385+
|CHECK: wire a : Probe<UInt<1>, A>
386+
|CHECK-NEXT: layerblock A :
387+
|CHECK-NEXT: wire [[a:.*]] : UInt<1>
388+
|CHECK-NEXT: define a = probe([[a]])
389+
|CHECK: define out.a = a
390+
|
391+
|CHECK: wire b : Probe<UInt<2>, A.B>
392+
|CHECK-NEXT: layerblock A :
393+
|CHECK-NEXT: layerblock B :
394+
|CHECK-NEXT: wire [[b:.*]] : UInt<2>
395+
|CHECK-NEXT: define b = probe([[b]])
396+
|CHECK: define out.b = b
397+
|
398+
|CHECK: wire c : Probe<UInt<3>, A.B>
399+
|CHECK-NEXT: layerblock A :
400+
|CHECK-NEXT: wire [[c0:.*]] : Probe<UInt<3>, A.B>
401+
|CHECK-NEXT: layerblock B :
402+
|CHECK-NEXT: wire [[c:.*]] : UInt<3>
403+
|CHECK-NEXT: define [[c0]] = probe([[c]])
404+
|CHECK: define c = [[c0]]
405+
|CHECK: define out.c = c
406+
|
407+
|CHECK: wire d : Probe<UInt<4>, A>
408+
|CHECK-NEXT: layerblock A :
409+
|CHECK-NEXT: wire [[d:.*]] : UInt<4>
410+
|CHECK-NEXT: define d = probe([[d]])
411+
|CHECK: define out.d = d
412+
|
413+
|CHECK: layerblock A :
414+
|CHECK-NEXT: wire e : UInt<5>
415+
|CHECK-NEXT: wire f : UInt<6>
416+
|CHECK-NOT: layerblock
417+
|CHECK: wire g : UInt<7>
418+
|CHECK-NOT: layerblock
419+
|CHECK: wire h : UInt<8>
420+
|""".stripMargin
421+
}
422+
}
423+
424+
they should "require compatible color if returning a layer-colored wire" in {
425+
class Foo extends RawModule {
426+
427+
val a = layer.block(A) {
428+
Wire(Probe(Bool(), C))
429+
}
430+
431+
}
432+
433+
intercept[ChiselException] { ChiselStage.convert(new Foo, Array("--throw-on-first-error")) }
434+
.getMessage() should include("cannot return probe of color 'C' from a layer block associated with layer 'A'")
435+
}
436+
309437
"addLayer API" should "add a layer to the output CHIRRTL even if no layer block references that layer" in {
310438
class Foo extends RawModule {
311439
layer.addLayer(A)

0 commit comments

Comments
 (0)