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

Add Layer Colors to Probe Types #3744

Merged
merged 3 commits into from
Jan 24, 2024
Merged

Add Layer Colors to Probe Types #3744

merged 3 commits into from
Jan 24, 2024

Conversation

seldridge
Copy link
Member

This adds Layer Colors to Probe types.

This is an existing part of the FIRRTL 3.2.0 "groups" feature which is being expanded/clarified in FIRRTL 4.0.0. This enables a probe to be associated/colored with a layer. This enables a layerblock to define probes of the same color.

This feature is used to construct simple interfaces of probes whose defines require complicated logic that does not exist inside the interface. Consider a situation where you have a simple single-cycle microprocessor. You'd like to expose a simple commit trace that is the last four instructions. However, the microprocessor does not actually record the last four instructions. It only keeps track of the current instruction. What do you do? If you put the logic outside the microprocessor, then your interface has changed. If you put the logic inside the microprocessor, you're adding junk that shouldn't be there.

Layer colored probes provide a way to hide this logic in a layer while still keeping a clean interface.

(This example is intentionally simple---a real world example is computing an in-order commit trace for an out-of-order processor where you need to assemble all the instructions committing out-of-order.)

The example below shows a realization of the basic example above:

//> using scala "2.13.12"
//> using lib "org.chipsalliance::chisel::6.0.0-M3+191-d3177dda-SNAPSHOT"
//> using plugin "org.chipsalliance:::chisel-plugin::6.0.0-M3+191-d3177dda-SNAPSHOT"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Xfatal-warnings", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"

import chisel3._
import chisel3.probe.{Probe, ProbeValue, define, read}
import chisel3.layer.{Layer, Convention, block}
import circt.stage.ChiselStage

object Verification extends Layer(Convention.Bind)

class DUT extends Module {
  val a = IO(Input(UInt(32.W)))
  val b = IO(Output(UInt(32.W)))
  val trace = IO(Output(Probe(Vec(4, UInt(32.W)), Verification)))

  val pc = Reg(UInt(32.W))
  pc :<>= a
  b :<>= pc

  block(Verification) {
    val committedInstructions = Reg(Vec(4, UInt(32.W)))
    committedInstructions(0) :<>= pc
    committedInstructions(1) :<>= committedInstructions(0)
    committedInstructions(2) :<>= committedInstructions(1)
    committedInstructions(3) :<>= committedInstructions(2)

    define(trace, ProbeValue(committedInstructions))
  }

}

class TestHarness extends Module {
  val a = IO(Input(UInt(32.W)))
  val b = IO(Output(UInt(32.W)))

  val dut = Module(new DUT)
  dut.a :<>= a
  b :<>= dut.b

  block(Verification) {
    printf("The last four PC's are: %x, %x, %x, %x", read(dut.trace(0)), read(dut.trace(1)), read(dut.trace(2)), read(dut.trace(3)))
  }
}

object Main extends App {
  println(ChiselStage.emitCHIRRTL(new TestHarness))
  println(
    ChiselStage.emitSystemVerilog(
      gen = new TestHarness,
      firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info")
    )
  )
}

Using a series of patches to enable this in CIRCT (llvm/circt#6552, llvm/circt#6574, and llvm/circt#6554) based on updates to the FIRRTL spec (chipsalliance/firrtl-spec#160), this produces the following FIRRTL and Verilog:

FIRRTL version 3.3.0
circuit TestHarness :
  declgroup Verification, bind :
  module DUT :
    input clock : Clock
    input reset : Reset
    input a : UInt<32>
    output b : UInt<32>
    output trace : Probe<UInt<32>[4], Verification>

    reg pc : UInt<32>, clock
    connect pc, a
    connect b, pc
    group Verification :
      reg committedInstructions : UInt<32>[4], clock
      connect committedInstructions[0], pc
      connect committedInstructions[1], committedInstructions[0]
      connect committedInstructions[2], committedInstructions[1]
      connect committedInstructions[3], committedInstructions[2]
      define trace = probe(committedInstructions)


  module TestHarness :
    input clock : Clock
    input reset : UInt<1>
    input a : UInt<32>
    output b : UInt<32>

    inst dut of DUT
    connect dut.clock, clock
    connect dut.reset, reset
    connect dut.a, a
    connect b, dut.b
    group Verification :
      node _T = eq(reset, UInt<1>(0h0))
      when _T :
        printf(clock, UInt<1>(0h1), "The last four PC's are: %x, %x, %x, %x", read(dut.trace[0]), read(dut.trace[1]), read(dut.trace[2]), read(dut.trace[3])) : printf
// Generated by CIRCT firtool-1.61.0-116-gd585c5b8a
// Standard header to adapt well known macros for prints and assertions.

// Users can define 'PRINTF_COND' to add an extra gate to prints.
`ifndef PRINTF_COND_
  `ifdef PRINTF_COND
    `define PRINTF_COND_ (`PRINTF_COND)
  `else  // PRINTF_COND
    `define PRINTF_COND_ 1
  `endif // PRINTF_COND
`endif // not def PRINTF_COND_

module DUT_Verification(
  input        _clock,
  input [31:0] _pc
);

  reg  [31:0] committedInstructions_0;
  wire [31:0] committedInstructions_0_probe = committedInstructions_0;
  reg  [31:0] committedInstructions_1;
  wire [31:0] committedInstructions_1_probe = committedInstructions_1;
  reg  [31:0] committedInstructions_2;
  wire [31:0] committedInstructions_2_probe = committedInstructions_2;
  reg  [31:0] committedInstructions_3;
  wire [31:0] committedInstructions_3_probe = committedInstructions_3;
  always
    committedInstructions_0 <= _pc;
    committedInstructions_1 <= committedInstructions_0;
    committedInstructions_2 <= committedInstructions_1;
    committedInstructions_3 <= committedInstructions_2;
  end // always
endmodule

module DUT(
  input         clock,
  input  [31:0] a,
  output [31:0] b
);

  reg [31:0] pc;
  always
    pc <= a;
  assign b = pc;
endmodule

module TestHarness_Verification(
  input        _reset,
  input [31:0] _dut_trace_0,
               _dut_trace_1,
               _dut_trace_2,
               _dut_trace_3,
  input        _clock
);

  `ifndef SYNTHESIS
    always
      if ((`PRINTF_COND_) & ~_reset)
        $fwrite(32'h80000002, "The last four PC's are: %x, %x, %x, %x", _dut_trace_0,
                _dut_trace_1, _dut_trace_2, _dut_trace_3);
    end // always
  `endif // not def SYNTHESIS
endmodule

module TestHarness(
  input         clock,
                reset,
  input  [31:0] a,
  output [31:0] b
);

  DUT dut (
    .clock (clock),
    .a     (a),
    .b     (b)
  );
endmodule


// ----- 8< ----- FILE "groups_TestHarness_Verification.sv" ----- 8< -----

// Generated by CIRCT firtool-1.61.0-116-gd585c5b8a
`ifndef groups_TestHarness_Verification
`define groups_TestHarness_Verification
bind DUT DUT_Verification dUT_Verification (
  ._clock (clock),
  ._pc    (pc)
);
bind TestHarness TestHarness_Verification testHarness_Verification (
  ._reset       (reset),
  ._dut_trace_0 (TestHarness.dut.dUT_Verification.committedInstructions_0_probe),
  ._dut_trace_1 (TestHarness.dut.dUT_Verification.committedInstructions_1_probe),
  ._dut_trace_2 (TestHarness.dut.dUT_Verification.committedInstructions_2_probe),
  ._dut_trace_3 (TestHarness.dut.dUT_Verification.committedInstructions_3_probe),
  ._clock       (clock)
);
`endif // groups_TestHarness_Verification

The resulting Verilog has a clean DUT (no junk!). There is collateral in a bind file that can be used to "enable" the Verification layer. This then uses Verilog hierarchical names to pass information from the bound module in the DUT (which computes all the logic that we don't want in the DUT) to a bound module in the TestHarness (which consumes this). The circuit is valid (no illegal hierarchical names) whether or not the Verification layer is enabled.

@seldridge seldridge requested a review from jackkoenig January 19, 2024 00:36
@seldridge seldridge added the Feature New feature, will be included in release notes label Jan 19, 2024
@seldridge seldridge force-pushed the dev/seldridge/layer-colors branch from c081aff to 87181a9 Compare January 19, 2024 05:00
@sequencer
Copy link
Member

Question:
Is

  val trace = IO(Output(Probe(Vec(4, UInt(32.W)), Verification)))

possible to be wrapped by layer?

@seldridge
Copy link
Member Author

@sequencer wrote:

possible to be wrapped by layer?

Yes, though there needs to be a way to get a reference to the port out of the layerblock so that it can be connected to. I've considered that it may make sense to allow returning any type from a layerblock to enable this. However, I'm not sure if this is entirely necessary.

The following is not advised, but works:

class DUT extends Module {
  val a = IO(Input(UInt(32.W)))
  val b = IO(Output(UInt(32.W)))

  val pc = Reg(UInt(32.W))
  pc :<>= a
  b :<>= pc

  var _trace: Vec[UInt] = null

  block(Verification) {
    val trace = IO(Output(Probe(Vec(4, UInt(32.W)), Verification)))
    _trace = trace

    val committedInstructions = Reg(Vec(4, UInt(32.W)))
    committedInstructions(0) :<>= pc
    committedInstructions(1) :<>= committedInstructions(0)
    committedInstructions(2) :<>= committedInstructions(1)
    committedInstructions(3) :<>= committedInstructions(2)

    define(trace, ProbeValue(committedInstructions))
  }

}

@sequencer
Copy link
Member

LOL, I just exactly thought about the same thing.

var _trace: Vec[UInt] = null

What if block(Verification)
provide a new return type?

@seldridge
Copy link
Member Author

@sequencer wrote:

What if block(Verification) provide a new return type?

That's the other approach. This probably makes sense to allow. There is a question of what you can return. Can you only return layer-colored ports? Can you return anything? I'd like to solve this in a follow-on PR. Any change to the return type of a block can be handled later without any deficiency in the use of the API as written in this PR.

@seldridge seldridge force-pushed the dev/seldridge/layer-colors branch from 8425259 to bc4ee58 Compare January 19, 2024 06:06
@jackkoenig jackkoenig added this to the 6.x milestone Jan 19, 2024
@@ -797,7 +797,7 @@ object Data {
// Needed for the `implicit def toConnectableDefault`
import scala.language.implicitConversions

private[chisel3] case class ProbeInfo(val writable: Boolean)
private[chisel3] case class ProbeInfo(val writable: Boolean, color: Option[layer.Layer])
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused. Binary compatibility should be complaining about this because it isn't smart enough to filter out package private things (it looks at the Java byte code which doesn't have package private). Why are we not having to waive this? If somehow they've made MiMa support package private Scala semantics--that's great--but I'd like to understand before we merge because this is surprising me.

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 still a mystery but we're just gonna leave it.

@sequencer
Copy link
Member

If using boring api. e.g. tapAndRead. The secret port should also be colored

@jackkoenig jackkoenig modified the milestones: 6.x, 7.0 Jan 23, 2024
Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

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

Overall looks good but a couple of suggestions. Also I need to disable binary compatibility checking so this will pass CI when we switch back to case classes.

firrtl/src/main/scala/firrtl/ir/IR.scala Outdated Show resolved Hide resolved
"define a = probe(in)",
"define b = probe(in)"
)()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have a negative test of what happens if you try to drive a layer-colored probe from the wrong layer or from no layer?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's currently no checking of this. firtool will error about it if you do this. It's probably better to duplicate the check of FIRRTL semantics in Chisel here. I'll see about adding this.

Copy link
Contributor

Choose a reason for hiding this comment

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

This logic was recently tweaked a bit, but here's the basic place where we check lexical scoping:

private[chisel3] def isVisible: Boolean = isVisibleFromModule && isVisibleFromWhen

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this to the checks of the legality of a define (and tests showing it works). This is included in bb5d02c. Test is added in f83f4b6.

core/src/main/scala/chisel3/Layer.scala Outdated Show resolved Hide resolved
@@ -797,7 +797,7 @@ object Data {
// Needed for the `implicit def toConnectableDefault`
import scala.language.implicitConversions

private[chisel3] case class ProbeInfo(val writable: Boolean)
private[chisel3] case class ProbeInfo(val writable: Boolean, color: Option[layer.Layer])
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 still a mystery but we're just gonna leave it.

@seldridge seldridge force-pushed the dev/seldridge/layer-colors branch from 3999ce1 to f83f4b6 Compare January 24, 2024 20:58
@seldridge seldridge requested a review from jackkoenig January 24, 2024 21:00
Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

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

LGTM, 1 minor suggestion.

core/src/main/scala/chisel3/internal/package.scala Outdated Show resolved Hide resolved
Add a memoized (via a `lazy val`) member to Layer that returns the FIRRTL
"full name" of that layer.  This includes all the parent layers prefixed
with interleaves periods.

This is memoized to avoid unnecessarily recomputing the partial prefixes
of parents.

Signed-off-by: Schuyler Eldridge <[email protected]>
Extend the probe modifier case class, `ProbeInfo`, to include information
about the optional layer color of this layer.  Extend emission to properly
write this out to FIRRTL text.

Signed-off-by: Schuyler Eldridge <[email protected]>
@seldridge seldridge force-pushed the dev/seldridge/layer-colors branch from 1bc9889 to f9d4c3b Compare January 24, 2024 22:31
@seldridge seldridge merged commit f9d4c3b into main Jan 24, 2024
14 checks passed
@seldridge seldridge deleted the dev/seldridge/layer-colors branch January 24, 2024 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New feature, will be included in release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants