Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ad0ff22
Add LoopArgState definition
ngernest Feb 20, 2026
d968619
Handle speculative loop exeuction
ngernest Feb 20, 2026
e0075a0
Exit loop when LoopArg becomes Known(n)
ngernest Feb 20, 2026
8458992
Formatting
ngernest Feb 20, 2026
0996bda
Add loop_arg to args_mapping when forking exit_thread
ngernest Feb 20, 2026
da7824d
Refactor two cases of the RepeatLoop forking logic into a helper func…
ngernest Feb 20, 2026
4ff13e6
Handle case when multiple loops use the same loop arg
ngernest Feb 20, 2026
4af2f99
Disallow nested repeat loops that use the same loop arg
ngernest Feb 20, 2026
cd0bc93
Run Cargo in Quiet mode when running Snapshot Tests
ngernest Feb 20, 2026
8c296ef
Add Cargo aliases
ngernest Feb 20, 2026
bca2aa1
Update various Turnt.toml files to use Cargo aliases
ngernest Feb 20, 2026
59974b4
Merge remote-tracking branch 'origin/cargo_quiet_mode' into monitor_r…
ngernest Feb 21, 2026
7a70e62
Add adder_d1 repeat loop tests
ngernest Feb 21, 2026
2c7009d
Add adder_d1 loop waveform files
ngernest Feb 21, 2026
c8a42e0
Add Turnt args to adder_d1 loop tests
ngernest Feb 21, 2026
c261d2a
Make Interpreter::to_protocol_application return an option
ngernest Feb 21, 2026
68d44ba
Add expected output for two adder_d1 tests with loops
ngernest Feb 21, 2026
d903d7a
Optimize Interpreter::to_protocol_application
ngernest Feb 21, 2026
1667082
Intercept fork before handling other statements
ngernest Feb 21, 2026
943d665
Revert "Intercept fork before handling other statements"
ngernest Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[alias]
interp = "run --quiet --package protocols-interp --"
monitor = "run --quiet --package protocols-monitor --"
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ Cargo.lock
test_run/
*.btor
**/.DS_Store
.vscode/*
.surfer/
monitor/target
monitor/tests/axi_output.txt
*.json
Expand All @@ -18,6 +16,7 @@ monitor/tests/axi_output.txt
# But include the .github directory (CI files)
!.github/
!.github/**/*
# And also include .gitignore + .python-version
# And also include .gitignore + .python-version + Cargo config
!.gitignore
!.python-version
!.python-version
!.cargo
2 changes: 1 addition & 1 deletion examples/turnt.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[envs.interp]
command = "cargo run --package protocols-interp -- --color never --transactions {filename} {args}"
command = "cargo interp --color never --transactions {filename} {args}"
34 changes: 31 additions & 3 deletions monitor/src/global_scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ fn process_group_cycles(
match scheduler.process_current_queue(trace, ctx) {
Ok(CycleResult::Done) => processed_schedulers.push_back(scheduler),
Ok(CycleResult::Fork { parent }) => {
// Iterate over all possible candidate protocols
// When there is an explicit/implicit fork,
// we need to iterate over all possible candidate protocols
// and for each candidate protocol, spawn a scheduler that runs it
for (transaction, symbol_table) in &scheduler.possible_transactions {
let mut cloned_scheduler = scheduler.clone();

// If there was an explicit fork, we have to add the
// parent thread to the cloned scheduler
// parent thread to the cloned scheduler's `current` queue.
if let Some(ref thread) = *parent {
cloned_scheduler.current.push_front(thread.clone());
cloned_scheduler.current.push_back(thread.clone());
}

// Create a new thread for the candidate protocol
Expand All @@ -75,6 +77,32 @@ fn process_group_cycles(
schedulers_to_process.push_back(cloned_scheduler);
}
}
Ok(CycleResult::RepeatLoopFork {
exited_thread,
speculative_thread,
}) => {
// For forks that arise due to `repeat` loops,
// create 2 schedulers that each execute a different thread:
// - One which executes the `exited_thread`, i.e. the thread
// which exits the loop with the `LoopArg` set to
// `Known(n)` for some `n >= 0`
// - One which executes the `speculative_thread`, i.e.
// the thread which speculatively executes the loop body
// for another iteration, with the `LoopArg`
// set to `Speculative(n + 1)`

let mut scheduler_with_exited_thread = scheduler.clone();
scheduler_with_exited_thread
.current
.push_back(*exited_thread);
schedulers_to_process.push_back(scheduler_with_exited_thread);

let mut scheduler_with_speculative_thread = scheduler.clone();
scheduler_with_speculative_thread
.current
.push_back(*speculative_thread);
schedulers_to_process.push_back(scheduler_with_speculative_thread);
}
Err(SchedulerError::NoTransactionsMatch {
struct_name,
error_context,
Expand Down
44 changes: 31 additions & 13 deletions monitor/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ use crate::{
global_context::GlobalContext,
signal_trace::{PortKey, SignalTrace, WaveSignalTrace},
thread::Thread,
types::ProtocolApplication,
types::{LoopArgState, ProtocolApplication},
};

#[allow(dead_code)]
#[derive(Clone)]
pub struct Interpreter {
pub transaction: Transaction,
pub symbol_table: SymbolTable,
pub next_stmt_map: NextStmtMap,

/// Maps protocol arguments (input/output parameters) to their values
pub args_mapping: FxHashMap<SymbolId, BitVecValue>,

/// Tracks which bits of a `SymbolId` are known
pub known_bits: FxHashMap<SymbolId, BitVecValue>,

/// Tracks the state of arguments for `repeat` loops
pub loop_args: FxHashMap<SymbolId, LoopArgState>,

/// Constraints on DUT input port values that must hold after stepping.
/// These are established by assignments like `D.m_axis_tvalid := 1'b1`
/// and verified after each `step()` to ensure the constraint still holds.
Expand All @@ -43,41 +51,47 @@ pub struct Interpreter {
}

impl Interpreter {
/// Builds a `ProtocolApplication` from the current interpreter state.
/// Attempts to builds a `ProtocolApplication` from the current interpreter state.
/// `struct_name` is left as `None` — the caller (Scheduler) is responsible
/// for setting it, since the Interpreter doesn't know the struct name.
/// If this return function returns `None` (e.g. because an argument
/// wasn't present in `args_mapping`), the caller
/// (`Scheduler::run_thread_till_next_step`) treats the corresponding thread
/// as having failed.
pub fn to_protocol_application(
&self,
struct_name: Option<String>,
ctx: &GlobalContext,
trace: &WaveSignalTrace,
) -> ProtocolApplication {
) -> Option<ProtocolApplication> {
let mut serialized_args = vec![];
for arg in &self.transaction.args {
let symbol_id = arg.symbol();
let name = self.symbol_table[symbol_id].full_name(&self.symbol_table);
let value = self.args_mapping.get(&symbol_id).unwrap_or_else(|| {
if let Some(value) = self.args_mapping.get(&symbol_id) {
serialized_args.push(serialize_bitvec(value, ctx.display_hex));
} else {
let name = self.symbol_table[symbol_id].full_name(&self.symbol_table);
let time_str = if ctx.show_waveform_time {
trace.format_time(trace.time_step(), ctx.time_unit)
} else {
format!("cycle {}", self.trace_cycle_count)
};
panic!(
info!(
"Transaction `{}`, {}: Unable to find value for {} ({}) in args_mapping, which is {{ {} }}",
self.transaction.name,
time_str,
name,
symbol_id,
serialize_args_mapping(&self.args_mapping, &self.symbol_table, ctx.display_hex)
)
});
serialized_args.push(serialize_bitvec(value, ctx.display_hex));
);
return None;
}
}
ProtocolApplication {
Some(ProtocolApplication {
struct_name,
protocol_name: self.transaction.name.clone(),
serialized_args,
}
})
}

/// Performs a context switch in the `Interpreter` by setting its
Expand All @@ -91,6 +105,7 @@ impl Interpreter {
self.known_bits = thread.known_bits.clone();
self.constraints = thread.constraints.clone();
self.args_to_pins = thread.args_to_pins.clone();
self.loop_args = thread.loop_args_state.clone();
}

/// Pretty-prints a `Statement` identified by its `StmtId`
Expand Down Expand Up @@ -157,6 +172,7 @@ impl Interpreter {
trace_cycle_count,
args_to_pins: FxHashMap::default(),
dut_symbol_id,
loop_args: FxHashMap::default(),
}
}

Expand Down Expand Up @@ -475,8 +491,10 @@ impl Interpreter {
Stmt::While(loop_guard_id, do_block_id) => {
self.evaluate_while(loop_guard_id, stmt_id, do_block_id, ctx, trace)
}
Stmt::BoundedLoop(_, _) => {
todo!("Bounded loops is not yet implemented in the monitor")
Stmt::RepeatLoop(_, _) => {
// Repeat loops are intercepted in `run_thread_till_next_step`
// in `scheduler.rs`, so this case is unreachable
unreachable!("Repeat loops handled in `scheduler.rs` already")
}
Stmt::Step | Stmt::Fork => {
// The scheduler handles `step`s and `fork`s.
Expand Down
Loading
Loading