From 718b347b9cabec1547f945dc69585a687e77c162 Mon Sep 17 00:00:00 2001 From: Lux Date: Wed, 14 Oct 2026 00:13:04 -0700 Subject: [PATCH 1/5] Bump tacit stack to lossy tracing (dev-lossy) - tacit: merge origin/dev-lossy (lossy Pause/Resume mode, trap from/to privilege fixes, lossless stall-reserve +1 fix, extra perf counters, sim-only backpressure sink); strip BOOM arms again for iris; keep the DMA sink's 0xFFF page alignment. - rocket-chip: cherry-pick lossy control + perf bundle and the extended encoder controller registers from riscv-tacit/trace-lossy onto iris-tacit (ASID/TLB work intentionally left out). - shuttle: cherry-pick perf bundle routing (a989d13) onto iris-tacit-on-master (ASID/TLB work left out). - tacit_decoder: bump to origin/lossy (decodes Pause/Resume). Encoder stall parameters (stallQuiescenceCycles / maxPacketsPerCycle) in src/TacitConfigs.scala are unchanged here; to follow separately. Co-Authored-By: Claude Opus 5.5 (1M context) --- rocket-chip | 2 +- shuttle | 2 +- tacit | 2 +- tacit_decoder | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rocket-chip b/rocket-chip index bca5b27..da8dff1 160000 --- a/rocket-chip +++ b/rocket-chip @@ -1 +1 @@ -Subproject commit bca5b27bc9ee96af5f1f04c7aa8163e3f2aa6af6 +Subproject commit da8dff1138537750151e552665ec41908c2bc0cf diff --git a/shuttle b/shuttle index 7f6bfe7..86d7847 160000 --- a/shuttle +++ b/shuttle @@ -1 +1 @@ -Subproject commit 7f6bfe7babe1ad5fc0fb6c915ccce2867db5d00f +Subproject commit 86d784777fcbe86100332903021717aa5e29ec2a diff --git a/tacit b/tacit index 8e9db5b..0fd43ec 160000 --- a/tacit +++ b/tacit @@ -1 +1 @@ -Subproject commit 8e9db5bc4d7b07f727368055115e57f192dbc798 +Subproject commit 0fd43ec0e8800a7ff6e950549718426a2eca875f diff --git a/tacit_decoder b/tacit_decoder index bc46393..dd0a2bb 160000 --- a/tacit_decoder +++ b/tacit_decoder @@ -1 +1 @@ -Subproject commit bc463936aa599f9987b7c579ba0ee25227ae0322 +Subproject commit dd0a2bb44fd75814aa540f0de81841bb6afae518 From d5bc5ee2b1cf814ef6eb420900b0e1b335f764ec Mon Sep 17 00:00:00 2001 From: Lux Date: Thu, 15 Oct 2026 13:02:40 -0700 Subject: [PATCH 2/5] Point rocket-chip submodule at ucb-bar/rocket-chip Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 1692d29..1ef3032 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = git@github.com:ucb-bar/saturn-vectors.git [submodule "rocket-chip"] path = rocket-chip - url = https://github.com/chipsalliance/rocket-chip.git + url = git@github.com:ucb-bar/rocket-chip.git [submodule "testchipip"] path = testchipip url = git@github.com:ucb-bar/testchipip.git From a0eece8a33bb83b08315b0d8cb687ecc390c44c4 Mon Sep 17 00:00:00 2001 From: Lux Date: Thu, 15 Oct 2026 21:00:27 -0700 Subject: [PATCH 3/5] Tacit: size encoder stall reserve for Iris cores; fix Shuttle phantom traps - shuttle: gate the trace ingress exception term to lane 0. ecall/ebreak commit with retire=1, so the CSR trace port also flagged lane 1 and every ecall/ebreak emitted a phantom trap packet from lane 1's stale uop. - TacitConfigs: the SRAM queue sizes its stall reserve from stallQuiescenceCycles (coreStages is unused there) and was taking the BOOM default of 3. Shuttle: 5 (3 retire groups past the register-read gate + 2 margin) with maxPacketsPerCycle = Some(1), which the lane-0 fix makes true. Rocket: 6 (3 past decode + 1 interrupt bypass + 2 margin). Shuttle now stalls at 11/16 entries instead of 9. Validated in Iris sim with the minimum reserve (Shuttle 3, Rocket 4) on the DMA sink: branch and ecall storms holding the queue at the stall point, an ecall/ebreak/illegal/load-fault/interrupt trap mix, a cross-core IPI storm and Embench (crc32, slre, statemate, nsichneu, edn, tarfind, md5sum), with no lossless-loss, enqueue-rate or lane-0 asserts and clean decodes. Standard tacit always/dma tests pass with the final values. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016hkQa6XQJC6FcnvJ9ScQ9b --- shuttle | 2 +- src/TacitConfigs.scala | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/shuttle b/shuttle index 86d7847..871e60f 160000 --- a/shuttle +++ b/shuttle @@ -1 +1 @@ -Subproject commit 86d784777fcbe86100332903021717aa5e29ec2a +Subproject commit 871e60fba46699d08a23d5ea35f0acd5388d714b diff --git a/src/TacitConfigs.scala b/src/TacitConfigs.scala index 51a64af..3dff3a0 100644 --- a/src/TacitConfigs.scala +++ b/src/TacitConfigs.scala @@ -65,7 +65,11 @@ class WithTacitParallelEncoder( new TraceCoreParams(nGroups = 1, xlen = xlen, iaddrWidth = xlen), bufferDepth = bufferDepth, coreStages = 5, - queueImpl = queueImpl)(p)), + queueImpl = queueImpl, + // Retire groups still reaching the queue after io.stall: ex/mem/wb + // past the decode gate, plus one interrupt that bypasses it (4), + // plus 2 cycles of margin. coreStages is unused by the SRAM queue. + stallQuiescenceCycles = 6)(p)), useArbiterMonitor = false, buildSinks = tp.tileParams.traceParams.map(_.buildSinks).getOrElse(Seq.empty) )), @@ -83,7 +87,14 @@ class WithTacitParallelEncoder( iaddrWidth = xlen), bufferDepth = bufferDepth, coreStages = 7, - queueImpl = queueImpl)(p)), + queueImpl = queueImpl, + // Retire groups still reaching the queue after io.stall: ex/mem/com + // past the register-read gate (3), plus 2 cycles of margin. + stallQuiescenceCycles = 5, + // At most one control-flow packet per commit group: one brjmp per + // issue group, traps and xRET only on lane 0 (needs the shuttle + // lane-0 trace exception gate). + maxPacketsPerCycle = Some(1))(p)), useArbiterMonitor = false, buildSinks = tp.tileParams.traceParams.map(_.buildSinks).getOrElse(Seq.empty) )), From c70d71e0c9af56ea85bec3b1d2c26ae570d47506 Mon Sep 17 00:00:00 2001 From: Lux Date: Mon, 19 Oct 2026 12:10:04 -0700 Subject: [PATCH 4/5] Tacit DMA: FBUS/MBUS ports with 64B bursts; tapeout serial-TL test; experiments Tapeout config: - WithIrisTacitTrace: one trace DMA per tile with a port on the FBUS and the MBUS (tacit 3a42024), chosen by its bus_select register. 0 (reset) writes through the FBUS into the SBUS, so the trace can go to the on-chip SBUS scratchpad or to DRAM through the L2; 1 writes DRAM below the L2 over serial TL. Soft-reset the DMA before switching. - software/tacit-dma.c: wait 4096 cycles after disabling tracing, past the DMA's partial-line flush. Tests (no RTL change): - TestHarness/SimTop/Utils: optional serialTLFreqMHz runs the serial-TL link, the harness memory and TSI on their own clock. - IrisSpec: "tacit dma trace workload on the tapeout serial TL" simulates the tapeout RTL (IrisConfig(sim = false): 16b serial TL, no TL monitors) with the link at 100 MHz. - IrisExperiments.scala: experiment-only overrides (DMA source count, shrunk L2, L2 outer latency) and a sim-only memory traffic monitor, built into build/Experiment_*. Nothing there is part of the tapeout configuration. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016hkQa6XQJC6FcnvJ9ScQ9b --- software/tacit-dma.c | 4 +- src/Iris.scala | 6 +- tacit | 2 +- test/src/IrisExperiments.scala | 100 +++++++++++++++++++++++++++++++++ test/src/IrisSpec.scala | 76 ++++++++++++++++++------- test/src/Utils.scala | 6 +- 6 files changed, 170 insertions(+), 24 deletions(-) create mode 100644 test/src/IrisExperiments.scala diff --git a/software/tacit-dma.c b/software/tacit-dma.c index 5112e20..a59019b 100644 --- a/software/tacit-dma.c +++ b/software/tacit-dma.c @@ -51,7 +51,9 @@ static void tacit_stop_trace(unsigned tile) { uintptr_t ctrl = TRACE_CTRL_BASE(tile); reg_write32(ctrl + TRACE_CTRL_CONTROL, 1); - delay_cycles(1024); + // The DMA writes 64-byte bursts and only flushes a partial one after 4096 idle + // cycles, so wait at least that long before reading the byte count. + delay_cycles(4096); __asm__ volatile ("fence" ::: "memory"); } diff --git a/src/Iris.scala b/src/Iris.scala index 85abb95..aa55fc1 100644 --- a/src/Iris.scala +++ b/src/Iris.scala @@ -207,7 +207,11 @@ object IrisNoCParams { /** Tacit trace encoder and trace sinks. */ class WithIrisTacitTrace extends Config( - new tacit.WithTraceSinkDMA(1) ++ + // One DMA sink per tile with a port on each bus, chosen by its bus_select + // register (soft-reset the DMA before changing it): + // 0 (reset): FBUS -> SBUS, reaches the SBUS scratchpad, or DRAM through the L2 + // 1: MBUS -> DRAM below the L2 (bypasses it) + new tacit.WithTraceSinkDMA(1, buses = Some(Seq(FBUS, MBUS))) ++ new tacit.WithTraceSinkAlways(0) ++ new WithTacitTraceArbiterMonitor ++ new WithTacitParallelEncoder diff --git a/tacit b/tacit index 0fd43ec..3a42024 160000 --- a/tacit +++ b/tacit @@ -1 +1 @@ -Subproject commit 0fd43ec0e8800a7ff6e950549718426a2eca875f +Subproject commit 3a4202410e60c6511c303bc6833bfe6c8065d8b1 diff --git a/test/src/IrisExperiments.scala b/test/src/IrisExperiments.scala new file mode 100644 index 0000000..5dd31bb --- /dev/null +++ b/test/src/IrisExperiments.scala @@ -0,0 +1,100 @@ +package edu.berkeley.cs.iris + +import chisel3._ +import org.chipsalliance.cde.config.{Config, Parameters} +import org.scalatest.funspec.AnyFunSpec +import freechips.rocketchip.subsystem._ + +// ============================================================================ +// EXPERIMENTS ONLY. Nothing in this file is part of the tapeout configuration. +// +// The tapeout configuration is whatever IrisConfig (src/Iris.scala) builds; the +// IrisSpec tests elaborate it. Everything below layers sim-only overrides on top +// of IrisConfig(sim = false) to answer a performance question, and every +// experiment builds into a build/Experiment_* directory so its RTL and results +// cannot be mistaken for the tapeout ones. +// ============================================================================ + +/** Trace DMA sinks get `n` TileLink source IDs (outstanding writes) instead of 16. */ +class WithExperimentTraceDMASources(n: Int) extends Config((site, here, up) => { + case tacit.TraceSinkDMASourcesKey => n +}) + +/** Shrinks the L2 by `divisor` (fewer sets; ways and banks unchanged) so a trace + * outgrows it in a short simulation. */ +class WithExperimentSmallL2(divisor: Int) extends Config((site, here, up) => { + case InclusiveCacheKey => up(InclusiveCacheKey).copy(sets = up(InclusiveCacheKey).sets / divisor) +}) + +/** Sizes the L2's outer MSHRs for an `cycles`-cycle memory round trip instead of + * the tapeout value. */ +class WithExperimentL2OuterLatency(cycles: Int) extends Config((site, here, up) => { + case InclusiveCacheKey => up(InclusiveCacheKey).copy(memCycles = cycles) +}) + +/** Sim-only probe of the harness memory (FastRAM's TL->AXI4 port, i.e. below + * serial TL): counts AXI read/write requests and data beats and prints them at + * the end of simulation. Uses hierarchical references, so it only works in the + * single-chip fast harness. */ +class MemTrafficMonitor extends chisel3.experimental.ExtModule { + setInline( + "MemTrafficMonitor.sv", + """module MemTrafficMonitor; + |`ifndef SYNTHESIS + | `define MTM_P SimTop.harness.chips_success_ram.tl2axi4 + | longint unsigned ar = 0, aw = 0, r_beats = 0, w_beats = 0; + | always @(posedge `MTM_P.clock) begin + | if (`MTM_P.auto_out_ar_valid && `MTM_P.auto_out_ar_ready) ar <= ar + 1; + | if (`MTM_P.auto_out_aw_valid && `MTM_P.auto_out_aw_ready) aw <= aw + 1; + | if (`MTM_P.auto_out_r_valid && `MTM_P.auto_out_r_ready) r_beats <= r_beats + 1; + | if (`MTM_P.auto_out_w_valid && `MTM_P.auto_out_w_ready) w_beats <= w_beats + 1; + | end + | final $display("MEMTRAFFIC reads=%0d read_beats=%0d writes=%0d write_beats=%0d", ar, r_beats, aw, w_beats); + | `undef MTM_P + |`endif + |endmodule + |""".stripMargin + ) +} + +class IrisExperimentsSpec extends AnyFunSpec { + // Tapeout RTL (16b serial TL, no TL monitors) with the link at 100 MHz, plus the + // given experimental overrides. + def tapeoutWith(overrides: Config*): Parameters = + overrides.foldRight(new IrisConfig(sim = false): Config)((o, acc) => new Config(o ++ acc)) + + def run(name: String, p: Parameters): Unit = { + implicit val params: Parameters = p + Utils.simulateTopWithBinaries( + Utils.buildRoot / s"Experiment_$name", + nChips = 1, + binaryPaths = Seq(Utils.root / "software/tacit-dma.riscv"), + fast = true, + serialTLFreqMHz = Some(100), + memTrafficMonitor = true + ) + } + + describe("Experiment (not tapeout)") { + it("tapeout config, memory traffic monitor only") { + run("tapeout_memmon", tapeoutWith()) + } + it("64 trace DMA sources") { + run("dma_src64_memmon", tapeoutWith(new WithExperimentTraceDMASources(64))) + } + it("32KB L2") { + run("l2_32k_memmon", tapeoutWith(new WithExperimentSmallL2(16))) + } + it("32KB L2 and 64 trace DMA sources") { + run("l2_32k_dma_src64_memmon", tapeoutWith(new WithExperimentSmallL2(16), new WithExperimentTraceDMASources(64))) + } + // Informational only: the L2 is frozen for tapeout. Sizes the L2's outer MSHRs + // for a 64-cycle memory round trip instead of the configured 4 (128 needs more outer source IDs than the 8-bit serial-TL source field allows). + it("L2 sized for a 64-cycle memory") { + run("l2_mem64_memmon", tapeoutWith(new WithExperimentL2OuterLatency(64))) + } + it("32KB L2 sized for a 64-cycle memory") { + run("l2_32k_mem64_memmon", tapeoutWith(new WithExperimentSmallL2(16), new WithExperimentL2OuterLatency(64))) + } + } +} diff --git a/test/src/IrisSpec.scala b/test/src/IrisSpec.scala index 01ca0c0..f67a895 100644 --- a/test/src/IrisSpec.scala +++ b/test/src/IrisSpec.scala @@ -62,11 +62,19 @@ class ClockSourceAtFreqMHz(val freqMHz: Double) ) } -class SimTop(nChips: Int, binaryPaths: Seq[Path], plusArgs: Seq[Seq[String]] = Seq.empty, fast: Boolean = false)(implicit +class SimTop( + nChips: Int, + binaryPaths: Seq[Path], + plusArgs: Seq[Seq[String]] = Seq.empty, + fast: Boolean = false, + serialTLFreqMHz: Option[Double] = None, + memTrafficMonitor: Boolean = false // experiments only, see IrisExperiments.scala +)(implicit p: Parameters ) extends RawModule { val driver = Module(new TestDriver) - val harness = Module(new TestHarness(nChips, binaryPaths, plusArgs, fast)) + val harness = Module(new TestHarness(nChips, binaryPaths, plusArgs, fast, serialTLFreqMHz)) + if (memTrafficMonitor) Module(new MemTrafficMonitor) harness.io.reset := driver.reset driver.success := harness.io.success } @@ -120,7 +128,18 @@ class TestDriver extends ExtModule { ) } -class TestHarness(nChips: Int, binaryPaths: Seq[Path], plusArgs: Seq[Seq[String]] = Seq.empty, fast: Boolean = false)(implicit +/** @param serialTLFreqMHz + * clock for the serial-TL link and the off-chip memory model behind it. None + * keeps the link on the 500 MHz chip clock; on silicon it is an external + * clock, typically much slower than the core. + */ +class TestHarness( + nChips: Int, + binaryPaths: Seq[Path], + plusArgs: Seq[Seq[String]] = Seq.empty, + fast: Boolean = false, + serialTLFreqMHz: Option[Double] = None +)(implicit p: Parameters ) extends RawModule { require(nChips >= 1, s"nChips must be at least 1, got $nChips") @@ -156,6 +175,7 @@ class TestHarness(nChips: Int, binaryPaths: Seq[Path], plusArgs: Seq[Seq[String] val ucieSidebandBypassClock = freeRunningClock(800) // The clocking tile's PLL reference, as in the UCIe repo's own harness. val ucieRefClock = freeRunningClock(100) + val serialTLClock = serialTLFreqMHz.map(freeRunningClock).getOrElse(digitalClock) implicit def view[A <: Data, B <: Data] : DataView[testchipip.tsi.TSIIO, TSIIO] = @@ -195,22 +215,26 @@ class TestHarness(nChips: Int, binaryPaths: Seq[Path], plusArgs: Seq[Seq[String] dtm_success ) - chiptop.serial_tl.clock_in := digitalClock - - val success = if (fast) { - val ram = Module(LazyModule(new FastRAM(chiptop_lazy.system.serdessers(0), p(SerialTLKey)(0), chipId = chipId)( - chiptop_lazy.system.serdessers(0).p - )).module) - ram.io.ser.in <> chiptop.serial_tl.out - chiptop.serial_tl.in <> ram.io.ser.out - SimTSI.connect(ram.io.tsi.map(_.viewAs[TSIIO]), digitalClock, io.reset, binaryPath, allPlusArgs) - } else { - val ram = Module(LazyModule(new SerialRAM(chiptop_lazy.system.serdessers(0), p(SerialTLKey)(0))( - chiptop_lazy.system.serdessers(0).p - )).module) - ram.io.ser.in <> chiptop.serial_tl.out - chiptop.serial_tl.in <> ram.io.ser.out - SimTSI.connect(ram.io.tsi.map(_.viewAs[TSIIO]), digitalClock, io.reset, binaryPath, allPlusArgs) + chiptop.serial_tl.clock_in := serialTLClock + + // The memory model and TSI bridge sit on the far side of the link, so they + // run on the link clock. + val success = withClockAndReset(serialTLClock, io.reset) { + if (fast) { + val ram = Module(LazyModule(new FastRAM(chiptop_lazy.system.serdessers(0), p(SerialTLKey)(0), chipId = chipId)( + chiptop_lazy.system.serdessers(0).p + )).module) + ram.io.ser.in <> chiptop.serial_tl.out + chiptop.serial_tl.in <> ram.io.ser.out + SimTSI.connect(ram.io.tsi.map(_.viewAs[TSIIO]), serialTLClock, io.reset, binaryPath, allPlusArgs) + } else { + val ram = Module(LazyModule(new SerialRAM(chiptop_lazy.system.serdessers(0), p(SerialTLKey)(0))( + chiptop_lazy.system.serdessers(0).p + )).module) + ram.io.ser.in <> chiptop.serial_tl.out + chiptop.serial_tl.in <> ram.io.ser.out + SimTSI.connect(ram.io.tsi.map(_.viewAs[TSIIO]), serialTLClock, io.reset, binaryPath, allPlusArgs) + } } val chipSuccessReg = withReset(io.reset.asAsyncReset)(RegInit(false.B)) when(dtm_success || success) { chipSuccessReg := true.B } @@ -482,6 +506,20 @@ class IrisSpec extends AnyFunSpec { fast = true ) } + + it("should run tacit dma trace workload on the tapeout serial TL") { + // Tapeout config (16b serial TL, no TL monitors) with the link at 100 MHz. + implicit val p = new IrisConfig(sim = false) + val workDir = Utils.buildRoot / "Iris_should_run_tacit_dma_trace_tapeout_serial_tl" + + Utils.simulateTopWithBinaries( + workDir, + nChips = 1, + binaryPaths = Seq(Utils.root / "software/tacit-dma.riscv"), + fast = true, + serialTLFreqMHz = Some(100) + ) + } } describe("IrisDryRun") { diff --git a/test/src/Utils.scala b/test/src/Utils.scala index c14f89f..673492e 100644 --- a/test/src/Utils.scala +++ b/test/src/Utils.scala @@ -114,7 +114,9 @@ script -e -f -c "./simulation +permissive +vcs+thread+16 +dramsim +dramsim_ini_d binaryPaths: Seq[Path], plusArgs: Seq[Seq[String]] = Seq.empty, fast: Boolean = false, - debug: Boolean = false + debug: Boolean = false, + serialTLFreqMHz: Option[Double] = None, + memTrafficMonitor: Boolean = false )(implicit p: Parameters) = { binaryPaths.zipWithIndex.foreach { case (path, i) => assert( @@ -133,7 +135,7 @@ script -e -f -c "./simulation +permissive +vcs+thread+16 +dramsim +dramsim_ini_d os.makeDir.all(artifactsDir) ChiselStage.emitSystemVerilogFile( - new SimTop(nChips, binaryPaths, plusArgs, fast), + new SimTop(nChips, binaryPaths, plusArgs, fast, serialTLFreqMHz, memTrafficMonitor), args = Array( "--target-dir", sourceDir.toString From 6ee73353bc9f2654282151ef3a5c772107df5984 Mon Sep 17 00:00:00 2001 From: Lux Date: Mon, 19 Oct 2026 16:35:25 -0700 Subject: [PATCH 5/5] Bump trace stack: Sync Periodic checkpoints - rocket-chip: sync_interval controller register (0x60, bytes, 0 = off). - tacit: TacitParallelEncoder emits Sync Periodic checkpoints. - tacit_decoder: decodes them and resyncs wrapped ring-buffer dumps. Off by default (sync_interval resets to 0); software enables it by writing the controller's 0x60 before enabling tracing. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016hkQa6XQJC6FcnvJ9ScQ9b --- rocket-chip | 2 +- tacit | 2 +- tacit_decoder | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rocket-chip b/rocket-chip index da8dff1..f06cb25 160000 --- a/rocket-chip +++ b/rocket-chip @@ -1 +1 @@ -Subproject commit da8dff1138537750151e552665ec41908c2bc0cf +Subproject commit f06cb254714736cf75eccde9566da25cb29de5c4 diff --git a/tacit b/tacit index 3a42024..6a0ca09 160000 --- a/tacit +++ b/tacit @@ -1 +1 @@ -Subproject commit 3a4202410e60c6511c303bc6833bfe6c8065d8b1 +Subproject commit 6a0ca0949f9b5ec4726e056a18322537dd32dfa3 diff --git a/tacit_decoder b/tacit_decoder index dd0a2bb..20945d0 160000 --- a/tacit_decoder +++ b/tacit_decoder @@ -1 +1 @@ -Subproject commit dd0a2bb44fd75814aa540f0de81841bb6afae518 +Subproject commit 20945d0394be4a519cba5361bd37d984524d40e9