Hey @Aquaticfuller @DiyouS,
found when running some kernels on the 64 tile variant.
Every ISS instantiated with htif=True polls the 8-byte tohost word every 1000 cycles (Htif::htif_handler re-arming via htif_event.enqueue(1000)). The poll goes through Syscalls::user_access, which issues byte-granular requests on the core's data port — i.e., through the fully timed memory model. tohost is an ordinary .data word, so in shared-L1 targets all pollers converge on one cache bank.
Impact. With N cores the aggregate is N × 8 / 1000 bank accesses/cycle at a single bank. On the CachePool insitu-cache target this crosses the bank's 1-op/cycle accept capacity at 256 cores (~2 req/cycle measured: 63 M debug reads in 61.5 M cycles, all on tohost's home bank), the bank's accept backlog diverges, and any core whose data shares that bank starves — presenting as a program deadlock.
Fix (attached). Event-driven tohost: the LSU compares each store's address against the config-known htif_tohost and wakes the Htif handler directly (notify_tohost_store(), one-cycle-later enqueue, guarded by is_enqueued()); the periodic re-arm is removed (the single reset-time check remains). The store itself proceeds into memory unchanged, so tohost keeps ordinary memory semantics for the target. Host-side steady-state traffic through the modeled fabric drops to zero — equivalent to an RTL testbench's non-intrusive watch — and the staleness window disappears (the handler reads immediately after the store, line guaranteed present). Cost: one address compare per store.
Note: Found by AI, I have not verified the diagnosis and patch below completely and unfortunately don't have time to clean it up now. But I wanted to report before you run into this issue and have to debug it again :-)
Note: I'm not sure if this AI solution is completely correct when the request can resolve asynchronously or something. Better double-check the patch below.
From a68ed5866b19bf7b295baba2df10546d29abda9f Mon Sep 17 00:00:00 2001
From: Johannes Pfau <johannes.pfau@h-partners.com>
Date: Wed, 26 Aug 2026 19:04:33 +0200
Subject: [PATCH] =?UTF-8?q?iss/htif:=20event-driven=20tohost=20=E2=80=94?=
=?UTF-8?q?=20store-side=20interception=20replaces=20the=201000-cycle=20po?=
=?UTF-8?q?ll?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The per-core HTIF pollers read tohost through the timed cache fabric
every 1000 cycles; at 256 cores that saturated the word's home bank
(the original runaway), and the bank-side debug-transparency repair
left a read-miss staleness hole: under heavy traffic the tohost line
is evicted between the target's store and the host poll, the debug
read misses, and the dropped syscall wedges the target mid-printf
(mu_peak@64 froze deterministically at the same TTI).
tohost is an ordinary memory word by HTIF design, so the target's
store must stay real memory traffic — but the HOST side never needed
to poll: the ISS already knows the address. The LSU now compares each
store against it and wakes the Htif handler directly (next cycle, when
the store has completed and its line is guaranteed present); the
periodic re-arm is gone. Host-side traffic through the modeled memory
system in steady state: zero — matching the RTL testbench's
non-intrusive watch.
Validation at 64 tiles / 256 cores: interval-only 3/3 + printf path
clean; mu_peak FULL ladder completes for the first time (70%
sustained, rungs 40-70 at 28-44% worst-interval budget; 80/90 show the
genuine backlog knee) through both historical freeze points.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
models/cpu/iss/include/htif.hpp | 6 ++++++
models/cpu/iss/src/htif.cpp | 13 ++++++++++++-
models/cpu/iss/src/lsu.cpp | 11 +++++++++++
3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/models/cpu/iss/include/htif.hpp b/models/cpu/iss/include/htif.hpp
index c311ec31..0ca023f3 100644
--- a/models/cpu/iss/include/htif.hpp
+++ b/models/cpu/iss/include/htif.hpp
@@ -104,6 +104,12 @@ class Htif
{
public:
+ // Store-side interception (event-driven HTIF): the LSU calls this when the target
+ // stores to the tohost word; the handler then runs once, next cycle, instead of a
+ // periodic poll. Keeps the host side out of the modeled memory system entirely.
+ void notify_tohost_store();
+ inline iss_reg_t get_tohost_addr() { return this->tohost_addr; }
+
Htif(IssWrapper &top, Iss &iss);
void build();
diff --git a/models/cpu/iss/src/htif.cpp b/models/cpu/iss/src/htif.cpp
index 86abaad8..1ed6c29b 100644
--- a/models/cpu/iss/src/htif.cpp
+++ b/models/cpu/iss/src/htif.cpp
@@ -449,6 +449,16 @@ int fds_t::lookup(iss_reg_t fd)
return fd >= fds.size() ? -1 : fds[fd];
}
+void Htif::notify_tohost_store()
+{
+#ifdef CONFIG_GVSOC_ISS_HTIF
+ if (this->tohost_addr != 0 && !this->htif_event.is_enqueued())
+ {
+ this->htif_event.enqueue(1);
+ }
+#endif
+}
+
void Htif::reset(bool active)
{
if (active)
@@ -479,5 +489,6 @@ void Htif::htif_handler(vp::Block *__this, vp::ClockEvent *event)
}
}
- iss->syscalls.htif.htif_event.enqueue(1000);
+ // No periodic re-arm: event-driven via notify_tohost_store() (a per-1000-cycle
+ // poll through the timed cache fabric saturated the tohost home bank at 256 cores).
}
diff --git a/models/cpu/iss/src/lsu.cpp b/models/cpu/iss/src/lsu.cpp
index 188c33aa..6d5faedf 100644
--- a/models/cpu/iss/src/lsu.cpp
+++ b/models/cpu/iss/src/lsu.cpp
@@ -163,6 +163,17 @@ void Lsu::data_response(vp::Block *__this, vp::IoReq *req)
int Lsu::data_req_aligned(iss_addr_t addr, uint8_t *data_ptr, uint8_t *memcheck_data, int size, bool is_write, int64_t &latency, int &req_id)
{
+ // Event-driven HTIF: a store touching the tohost word wakes the host-side handler
+ // directly (next cycle, when this store has completed). The store itself proceeds
+ // into memory unchanged - tohost stays an ordinary memory word for the target.
+ if (is_write)
+ {
+ iss_reg_t th = this->iss.syscalls.htif.get_tohost_addr();
+ if (th != 0 && addr <= th && th < addr + (iss_addr_t)size)
+ {
+ this->iss.syscalls.htif.notify_tohost_store();
+ }
+ }
this->trace.msg("Data request (addr: 0x%lx, size: 0x%x, is_write: %d)\n", addr, size, is_write);
vp::IoReq *req = this->get_req();
if (req == NULL)
--
2.50.1
Hey @Aquaticfuller @DiyouS,
found when running some kernels on the 64 tile variant.
Note: Found by AI, I have not verified the diagnosis and patch below completely and unfortunately don't have time to clean it up now. But I wanted to report before you run into this issue and have to debug it again :-)
Note: I'm not sure if this AI solution is completely correct when the request can resolve asynchronously or something. Better double-check the patch below.