diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c149c457..6320efe0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,15 @@ jobs: - name: Run Turnt tests for monitor run: turnt --env monitor $(find . -type f -name '*.prot') + roundtrip: + name: Roundtrip + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-test-environment + - name: Run Turnt roundtrip tests + run: turnt --env roundtrip $(find . -type f -name '*.tx') --diff + msrv: name: Check Minimum Rust Version for protocols library runs-on: ubuntu-24.04 diff --git a/examples/picorv32/unsigned_mul.rt b/examples/picorv32/unsigned_mul.rt new file mode 100644 index 00000000..4eee5fe0 --- /dev/null +++ b/examples/picorv32/unsigned_mul.rt @@ -0,0 +1,22 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + pcpi_mul_reset(); + pcpi_mul(1, 1, 1); + pcpi_mul(1, 100, 100); + pcpi_mul(100, 1, 100); + pcpi_mul(33554483, 200, 2415929304); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + pcpi_mul_reset(); + pcpi_mul(1, 1, 1); + pcpi_mul(1, 100, 100); + pcpi_mul(100, 1, 100); + pcpi_mul(33554483, 200, 2415929304); +} + diff --git a/examples/serv/serv_regfile.rt b/examples/serv/serv_regfile.rt new file mode 100644 index 00000000..fbfb87f6 --- /dev/null +++ b/examples/serv/serv_regfile.rt @@ -0,0 +1,16 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + read_write(0, 0, 0, 0, 1, 5, 3735928559); + read_write(5, 3735928559, 0, 0, 0, 0, 0); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + read_write(0, 0, 0, 0, 1, 5, 3735928559); + read_write(5, 3735928559, 0, 0, 0, 0, 0); +} + diff --git a/examples/tinyaes128/aes128.rt b/examples/tinyaes128/aes128.rt new file mode 100644 index 00000000..a6a84132 --- /dev/null +++ b/examples/tinyaes128/aes128.rt @@ -0,0 +1,16 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + aes128(5233100606242806050955395731361295, 88962710306127702866241727433142015, 140591190147677442632770771134392354138); + aes128(0, 0, 136792598789324718765670228683992083246); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + aes128(5233100606242806050955395731361295, 88962710306127702866241727433142015, 140591190147677442632770771134392354138); + aes128(0, 0, 136792598789324718765670228683992083246); +} + diff --git a/examples/turnt.toml b/examples/turnt.toml index 8c08bf47..e0d5fd55 100644 --- a/examples/turnt.toml +++ b/examples/turnt.toml @@ -1,2 +1,7 @@ [envs.interp] command = "cargo run --package protocols-interp -- --color never --transactions {filename} {args}" + +[envs.roundtrip] +command = "uv run ../scripts/roundtrip_case.py {filename} {args}" +binary = true +output.rt = "-" diff --git a/interp/src/main.rs b/interp/src/main.rs index 4947d11d..71c3a6c4 100644 --- a/interp/src/main.rs +++ b/interp/src/main.rs @@ -54,6 +54,11 @@ struct Cli { /// of cycles specified with this option. #[arg(long)] max_steps: Option, + + /// Mark this test as allowed to fail (ignored by interpreter, used only + /// round-trip test harness) + #[arg(long)] + allow_round_trip_failure: bool, } /// Examples (enables all tracing logs): diff --git a/justfile b/justfile index 09c83629..79a06f13 100644 --- a/justfile +++ b/justfile @@ -6,6 +6,10 @@ interp: monitor: turnt --env monitor $(find . -type f -name '*.prot') +# Run roundtrip checks (interp -> fst -> monitor) for all transactions +roundtrip: + turnt --env roundtrip $(find . -type f -name '*.tx') + # Run Turnt tests for the monitor based on the Brave New World artifacts bnw_monitor: turnt --env monitor $(find monitor/tests/fpga-debugging -type f -name '*.prot') diff --git a/protocols/src/scheduler.rs b/protocols/src/scheduler.rs index bb87d0c0..a8eb7789 100644 --- a/protocols/src/scheduler.rs +++ b/protocols/src/scheduler.rs @@ -403,6 +403,9 @@ impl<'a> Scheduler<'a> { } } + // Emit one trailing empty cycle so all generated .fst files having timing information + self.evaluator.sim_step(); + // Emit diagnostics for all errors after execution is complete self.emit_all_diagnostics(); self.results.clone() diff --git a/protocols/tests/adders/adder_d0/add_combinational.rt b/protocols/tests/adders/adder_d0/add_combinational.rt new file mode 100644 index 00000000..c92ee58d --- /dev/null +++ b/protocols/tests/adders/adder_d0/add_combinational.rt @@ -0,0 +1,13 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: monitor_error +interpreter_trace: +trace { + add_combinational(100, 200, 300); +} + +monitor_error: +Transaction `add_combinational_illegal_observation_in_conditional`, cycle 0: Unable to find value for b (symbol5) in args_mapping, which is { (symbol4) a: 100 +(symbol6) s: 300 } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + diff --git a/protocols/tests/adders/adder_d0/illegal_assignment.rt b/protocols/tests/adders/adder_d0/illegal_assignment.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d0/illegal_assignment.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d0/illegal_observation_assertion.rt b/protocols/tests/adders/adder_d0/illegal_observation_assertion.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d0/illegal_observation_assertion.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d0/illegal_observation_conditional.rt b/protocols/tests/adders/adder_d0/illegal_observation_conditional.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d0/illegal_observation_conditional.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/add_incorrect.rt b/protocols/tests/adders/adder_d1/add_incorrect.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/add_incorrect.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/add_incorrect_implicit.rt b/protocols/tests/adders/adder_d1/add_incorrect_implicit.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/add_incorrect_implicit.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/add_multitrace.rt b/protocols/tests/adders/adder_d1/add_multitrace.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/add_multitrace.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/both_threads_fail.rt b/protocols/tests/adders/adder_d1/both_threads_fail.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/both_threads_fail.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/both_threads_pass.rt b/protocols/tests/adders/adder_d1/both_threads_pass.rt new file mode 100644 index 00000000..ce76cd61 --- /dev/null +++ b/protocols/tests/adders/adder_d1/both_threads_pass.rt @@ -0,0 +1,15 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: monitor_error +interpreter_trace: +trace { + add(1, 2, 3); + add(4, 5, 9); +} + +monitor_error: +All schedulers failed: No transactions match the waveform for DUT `Adder` +Failure at cycle 1: No transactions match the waveform in `.roundtrip_tmp/adders-adder_d1-both_threads_pass_0.fst`. +Possible transactions: [add, add_fork_early, add_doesnt_end_in_step, add_incorrect, add_incorrect_implicit, wait_and_add] +Error: Monitor failed + diff --git a/protocols/tests/adders/adder_d1/busy_wait_fail.rt b/protocols/tests/adders/adder_d1/busy_wait_fail.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/adders/adder_d1/busy_wait_fail.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/adders/adder_d1/busy_wait_fail.tx b/protocols/tests/adders/adder_d1/busy_wait_fail.tx index 3024a150..689c0497 100644 --- a/protocols/tests/adders/adder_d1/busy_wait_fail.tx +++ b/protocols/tests/adders/adder_d1/busy_wait_fail.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/busy_wait.prot +// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/busy_wait.prot --allow-round-trip-failure // RETURN: 101 trace { add_busy_wait(1, 2, 2, 999); // Wrong output value, this transaction fails diff --git a/protocols/tests/adders/adder_d1/busy_wait_pass.rt b/protocols/tests/adders/adder_d1/busy_wait_pass.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/adders/adder_d1/busy_wait_pass.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/adders/adder_d1/busy_wait_pass.tx b/protocols/tests/adders/adder_d1/busy_wait_pass.tx index df87db84..4caca794 100644 --- a/protocols/tests/adders/adder_d1/busy_wait_pass.tx +++ b/protocols/tests/adders/adder_d1/busy_wait_pass.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/busy_wait.prot +// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/busy_wait.prot --allow-round-trip-failure trace { add_busy_wait(1, 2, 1, 3); // Runs busy-waiting loop for 1 iteration add_busy_wait(4, 5, 3, 9); // Runs busy-waiting loop for 3 iterations diff --git a/protocols/tests/adders/adder_d1/didnt_end_in_step.rt b/protocols/tests/adders/adder_d1/didnt_end_in_step.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/didnt_end_in_step.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/double_fork_error.rt b/protocols/tests/adders/adder_d1/double_fork_error.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/double_fork_error.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/first_fail_second_norun.rt b/protocols/tests/adders/adder_d1/first_fail_second_norun.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/first_fail_second_norun.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/first_thread_fails.rt b/protocols/tests/adders/adder_d1/first_thread_fails.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/first_thread_fails.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/fork_before_step_error.rt b/protocols/tests/adders/adder_d1/fork_before_step_error.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/fork_before_step_error.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/loop_with_assigns.rt b/protocols/tests/adders/adder_d1/loop_with_assigns.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/adders/adder_d1/loop_with_assigns.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/adders/adder_d1/loop_with_assigns.tx b/protocols/tests/adders/adder_d1/loop_with_assigns.tx index 14ec302a..998b2568 100644 --- a/protocols/tests/adders/adder_d1/loop_with_assigns.tx +++ b/protocols/tests/adders/adder_d1/loop_with_assigns.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/loop_with_assigns.prot +// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/loop_with_assigns.prot --allow-round-trip-failure trace { loop_add(1, 2, 3, 3); // assert 1+2=3 on each of 3 iterations loop_add(10, 20, 1, 30); // assert 10+20=30 on 1 iteration diff --git a/protocols/tests/adders/adder_d1/nested_busy_wait.rt b/protocols/tests/adders/adder_d1/nested_busy_wait.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/adders/adder_d1/nested_busy_wait.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/adders/adder_d1/nested_busy_wait.tx b/protocols/tests/adders/adder_d1/nested_busy_wait.tx index 6315bb9f..d1f304ee 100644 --- a/protocols/tests/adders/adder_d1/nested_busy_wait.tx +++ b/protocols/tests/adders/adder_d1/nested_busy_wait.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/nested_busy_wait.prot +// ARGS: --verilog adders/adder_d1/add_d1.v --protocol adders/adder_d1/nested_busy_wait.prot --allow-round-trip-failure trace { nested_busy_wait(1, 2, 2, 3, 3); // 6 inner steps + 2 outer steps = 8 cycles nested_busy_wait(10, 20, 3, 2, 30); // 6 inner steps + 3 outer steps = 9 cycles diff --git a/protocols/tests/adders/adder_d1/second_thread_fails.rt b/protocols/tests/adders/adder_d1/second_thread_fails.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/second_thread_fails.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d1/wait_and_add_correct.rt b/protocols/tests/adders/adder_d1/wait_and_add_correct.rt new file mode 100644 index 00000000..1c23412b --- /dev/null +++ b/protocols/tests/adders/adder_d1/wait_and_add_correct.rt @@ -0,0 +1,15 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: monitor_error +interpreter_trace: +trace { + wait_and_add(1, 2, 3); + add(4, 5, 9); +} + +monitor_error: +All schedulers failed: No transactions match the waveform for DUT `Adder` +Failure at cycle 1: No transactions match the waveform in `.roundtrip_tmp/adders-adder_d1-wait_and_add_correct_0.fst`. +Possible transactions: [add, add_fork_early, add_doesnt_end_in_step, add_incorrect, add_incorrect_implicit, wait_and_add] +Error: Monitor failed + diff --git a/protocols/tests/adders/adder_d1/wait_and_add_incorrect_implicit.rt b/protocols/tests/adders/adder_d1/wait_and_add_incorrect_implicit.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d1/wait_and_add_incorrect_implicit.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/adders/adder_d2/both_threads_pass.rt b/protocols/tests/adders/adder_d2/both_threads_pass.rt new file mode 100644 index 00000000..5528f243 --- /dev/null +++ b/protocols/tests/adders/adder_d2/both_threads_pass.rt @@ -0,0 +1,16 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + add(1, 2, 3); + add(4, 5, 9); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + add(1, 2, 3); + add(4, 5, 9); +} + diff --git a/protocols/tests/adders/adder_d2/no_dontcare_conflict.rt b/protocols/tests/adders/adder_d2/no_dontcare_conflict.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/adders/adder_d2/no_dontcare_conflict.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/alus/alu_d1.rt b/protocols/tests/alus/alu_d1.rt new file mode 100644 index 00000000..d5dcd0d8 --- /dev/null +++ b/protocols/tests/alus/alu_d1.rt @@ -0,0 +1,22 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + add(1, 2, 3); + add(123, 245, 368); + sub(200, 200, 0); + and(100, 100, 100); + or(0, 230, 230); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + add(1, 2, 3); + add(123, 245, 368); + sub(200, 200, 0); + and(100, 100, 100); + or(0, 230, 230); +} + diff --git a/protocols/tests/alus/alu_d2.rt b/protocols/tests/alus/alu_d2.rt new file mode 100644 index 00000000..d5dcd0d8 --- /dev/null +++ b/protocols/tests/alus/alu_d2.rt @@ -0,0 +1,22 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + add(1, 2, 3); + add(123, 245, 368); + sub(200, 200, 0); + and(100, 100, 100); + or(0, 230, 230); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + add(1, 2, 3); + add(123, 245, 368); + sub(200, 200, 0); + and(100, 100, 100); + or(0, 230, 230); +} + diff --git a/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_bug.rt b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_bug.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_bug.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_fix.rt b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_fix.rt new file mode 100644 index 00000000..ba2a4639 --- /dev/null +++ b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_fft_fix.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + bit_truncation_fft(8386560, 2047); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + bit_truncation_fft(8386560, 2047); +} + diff --git a/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_bug.rt b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_bug.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_bug.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_fix.rt b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_fix.rt new file mode 100644 index 00000000..396fedb5 --- /dev/null +++ b/protocols/tests/brave_new_world/bit_truncation/bit_truncation_sha_fix.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + bit_truncation_sha(4398046511104, 68719476736); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + bit_truncation_sha(4398046511104, 68719476736); +} + diff --git a/protocols/tests/brave_new_world/failure_to_update/ftu_sha_bug.rt b/protocols/tests/brave_new_world/failure_to_update/ftu_sha_bug.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/brave_new_world/failure_to_update/ftu_sha_bug.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/brave_new_world/failure_to_update/ftu_sha_fix.rt b/protocols/tests/brave_new_world/failure_to_update/ftu_sha_fix.rt new file mode 100644 index 00000000..43b6d3b2 --- /dev/null +++ b/protocols/tests/brave_new_world/failure_to_update/ftu_sha_fix.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + failure_to_update_sha(); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + failure_to_update_sha(); +} + diff --git a/protocols/tests/brave_new_world/signal_asynchrony/signal_async_bug.rt b/protocols/tests/brave_new_world/signal_asynchrony/signal_async_bug.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/brave_new_world/signal_asynchrony/signal_async_bug.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/brave_new_world/signal_asynchrony/signal_async_fix.rt b/protocols/tests/brave_new_world/signal_asynchrony/signal_async_fix.rt new file mode 100644 index 00000000..828f45bf --- /dev/null +++ b/protocols/tests/brave_new_world/signal_asynchrony/signal_async_fix.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + signal_async(6, 7); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + signal_async(6, 7); +} + diff --git a/protocols/tests/brave_new_world/use_without_valid/use_without_valid_bug.rt b/protocols/tests/brave_new_world/use_without_valid/use_without_valid_bug.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/brave_new_world/use_without_valid/use_without_valid_bug.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/brave_new_world/use_without_valid/use_without_valid_fix.rt b/protocols/tests/brave_new_world/use_without_valid/use_without_valid_fix.rt new file mode 100644 index 00000000..93c8a6ec --- /dev/null +++ b/protocols/tests/brave_new_world/use_without_valid/use_without_valid_fix.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + use_without_valid(5, 5); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + use_without_valid(5, 5); +} + diff --git a/protocols/tests/counters/counter.rt b/protocols/tests/counters/counter.rt new file mode 100644 index 00000000..63761847 --- /dev/null +++ b/protocols/tests/counters/counter.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + count_up(10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + count_up(10); +} + diff --git a/protocols/tests/fifo/fifo.rt b/protocols/tests/fifo/fifo.rt new file mode 100644 index 00000000..5700f7a8 --- /dev/null +++ b/protocols/tests/fifo/fifo.rt @@ -0,0 +1,24 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + reset(); + push(3); + push(4); + idle(); + pop(3); + pop(4); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + reset(); + push(3); + push(4); + idle(); + pop(3); + pop(4); +} + diff --git a/protocols/tests/fifo/pop_before_push_fail.rt b/protocols/tests/fifo/pop_before_push_fail.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/fifo/pop_before_push_fail.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/fifo/pop_empty_fail.rt b/protocols/tests/fifo/pop_empty_fail.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/fifo/pop_empty_fail.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/fifo/push_pop_conflict.rt b/protocols/tests/fifo/push_pop_conflict.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/fifo/push_pop_conflict.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/fifo/push_pop_identity_ok.rt b/protocols/tests/fifo/push_pop_identity_ok.rt new file mode 100644 index 00000000..16cc7776 --- /dev/null +++ b/protocols/tests/fifo/push_pop_identity_ok.rt @@ -0,0 +1,21 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: trace_mismatch +interpreter_trace: +trace { + reset(); + push(2); + pop(2); + idle(); + push(3); + pop(3); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + reset(); + push(2); + pop(2); +} + diff --git a/protocols/tests/fifo/push_pop_loop_empty.rt b/protocols/tests/fifo/push_pop_loop_empty.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/fifo/push_pop_loop_empty.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/fifo/push_pop_loop_empty.tx b/protocols/tests/fifo/push_pop_loop_empty.tx index e58825b4..f3d8d51e 100644 --- a/protocols/tests/fifo/push_pop_loop_empty.tx +++ b/protocols/tests/fifo/push_pop_loop_empty.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog fifo/bsg_mem_1rw_sync.v fifo/bsg_mem_1rw_sync_synth.v fifo/bsg_circular_ptr.v fifo/bsg_fifo_1rw_large.v fifo/fifo_wrapper.v --protocol=fifo/fifo_bounded_loop.prot --module fifo_wrapper +// ARGS: --verilog fifo/bsg_mem_1rw_sync.v fifo/bsg_mem_1rw_sync_synth.v fifo/bsg_circular_ptr.v fifo/bsg_fifo_1rw_large.v fifo/fifo_wrapper.v --protocol=fifo/fifo_bounded_loop.prot --module fifo_wrapper --allow-round-trip-failure trace { reset(); push_n_times(42, 4); // Push 42 four times diff --git a/protocols/tests/fifo/push_pop_loop_not_empty.rt b/protocols/tests/fifo/push_pop_loop_not_empty.rt new file mode 100644 index 00000000..e49c98d6 --- /dev/null +++ b/protocols/tests/fifo/push_pop_loop_not_empty.rt @@ -0,0 +1 @@ +SKIP: allowed to fail round trip diff --git a/protocols/tests/fifo/push_pop_loop_not_empty.tx b/protocols/tests/fifo/push_pop_loop_not_empty.tx index 91d75f18..bf8f2d9f 100644 --- a/protocols/tests/fifo/push_pop_loop_not_empty.tx +++ b/protocols/tests/fifo/push_pop_loop_not_empty.tx @@ -1,4 +1,4 @@ -// ARGS: --verilog fifo/bsg_mem_1rw_sync.v fifo/bsg_mem_1rw_sync_synth.v fifo/bsg_circular_ptr.v fifo/bsg_fifo_1rw_large.v fifo/fifo_wrapper.v --protocol=fifo/fifo_bounded_loop.prot --module fifo_wrapper +// ARGS: --verilog fifo/bsg_mem_1rw_sync.v fifo/bsg_mem_1rw_sync_synth.v fifo/bsg_circular_ptr.v fifo/bsg_fifo_1rw_large.v fifo/fifo_wrapper.v --protocol=fifo/fifo_bounded_loop.prot --module fifo_wrapper --allow-round-trip-failure trace { reset(); push_n_times(7, 3); diff --git a/protocols/tests/identities/dual_identity_d0/dual_identity_d0_combdep.rt b/protocols/tests/identities/dual_identity_d0/dual_identity_d0_combdep.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/dual_identity_d0/dual_identity_d0_combdep.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/dual_identity_d1/dual_identity_d1.rt b/protocols/tests/identities/dual_identity_d1/dual_identity_d1.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/dual_identity_d1/dual_identity_d1.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d0/passthrough_combdep.rt b/protocols/tests/identities/identity_d0/passthrough_combdep.rt new file mode 100644 index 00000000..57134e31 --- /dev/null +++ b/protocols/tests/identities/identity_d0/passthrough_combdep.rt @@ -0,0 +1,15 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: trace_mismatch +interpreter_trace: +trace { + passthrough(); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + passthrough(); + passthrough(); +} + diff --git a/protocols/tests/identities/identity_d1/explicit_fork.rt b/protocols/tests/identities/identity_d1/explicit_fork.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d1/explicit_fork.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d1/identity_d1.prot b/protocols/tests/identities/identity_d1/identity_d1.prot index 9293a221..fcaec1ee 100644 --- a/protocols/tests/identities/identity_d1/identity_d1.prot +++ b/protocols/tests/identities/identity_d1/identity_d1.prot @@ -20,8 +20,12 @@ prot slicing_ok(in a: u32, out s: u32) { DUT.a := a; step(); + + assert_eq(s[31:15], DUT.s[31:15]); - assert_eq(s[31:15], DUT.s[31:15]); + // TODO for Ernest: + // uncomment this line and see if it works with the monitor (if the round-trip test passes) + // assert_eq(s[14:0], DUT.s[14:0]); fork(); step(); diff --git a/protocols/tests/identities/identity_d1/slicing_err.rt b/protocols/tests/identities/identity_d1/slicing_err.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d1/slicing_err.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d1/slicing_invalid.rt b/protocols/tests/identities/identity_d1/slicing_invalid.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d1/slicing_invalid.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d1/slicing_ok.rt b/protocols/tests/identities/identity_d1/slicing_ok.rt new file mode 100644 index 00000000..7e380628 --- /dev/null +++ b/protocols/tests/identities/identity_d1/slicing_ok.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: FAIL +failure_kind: trace_mismatch +interpreter_trace: +trace { + slicing_ok(1, 1); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + explicit_fork(1, 1); +} + diff --git a/protocols/tests/identities/identity_d2/single_thread_passes.rt b/protocols/tests/identities/identity_d2/single_thread_passes.rt new file mode 100644 index 00000000..3cc34532 --- /dev/null +++ b/protocols/tests/identities/identity_d2/single_thread_passes.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multiple_assign(1, 1); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multiple_assign(1, 1); +} + diff --git a/protocols/tests/identities/identity_d2/two_assignments_same_value.rt b/protocols/tests/identities/identity_d2/two_assignments_same_value.rt new file mode 100644 index 00000000..f188a082 --- /dev/null +++ b/protocols/tests/identities/identity_d2/two_assignments_same_value.rt @@ -0,0 +1,22 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multiple_assign(1, 1); + multiple_assign(1, 1); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multiple_assign(1, 1); + multiple_assign(1, 1); +} + +candidate_monitor_trace: 1 +trace { + multiple_assign(1, 1); + two_fork_err(1, 1); +} + diff --git a/protocols/tests/identities/identity_d2/two_different_assignments_error.rt b/protocols/tests/identities/identity_d2/two_different_assignments_error.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d2/two_different_assignments_error.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d2/two_fork_err.rt b/protocols/tests/identities/identity_d2/two_fork_err.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d2/two_fork_err.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/identities/identity_d2/two_fork_ill_formed.rt b/protocols/tests/identities/identity_d2/two_fork_ill_formed.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/identities/identity_d2/two_fork_ill_formed.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/inverters/inverter_d0.rt b/protocols/tests/inverters/inverter_d0.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/inverters/inverter_d0.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/multi/multi0/multi0.rt b/protocols/tests/multi/multi0/multi0.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi0/multi0.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multi/multi0keep/multi0keep.rt b/protocols/tests/multi/multi0keep/multi0keep.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi0keep/multi0keep.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multi/multi0keep2const/multi0keep2const.rt b/protocols/tests/multi/multi0keep2const/multi0keep2const.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi0keep2const/multi0keep2const.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multi/multi2const/multi2const.rt b/protocols/tests/multi/multi2const/multi2const.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi2const/multi2const.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multi/multi2multi/multi2multi.rt b/protocols/tests/multi/multi2multi/multi2multi.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi2multi/multi2multi.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multi/multi_data/multi_data.rt b/protocols/tests/multi/multi_data/multi_data.rt new file mode 100644 index 00000000..ce778b6c --- /dev/null +++ b/protocols/tests/multi/multi_data/multi_data.rt @@ -0,0 +1,14 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + multi(10, 10); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + multi(10, 10); +} + diff --git a/protocols/tests/multipliers/mult_d2/both_threads_fail.rt b/protocols/tests/multipliers/mult_d2/both_threads_fail.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/multipliers/mult_d2/both_threads_fail.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/multipliers/mult_d2/both_threads_pass.rt b/protocols/tests/multipliers/mult_d2/both_threads_pass.rt new file mode 100644 index 00000000..537a9166 --- /dev/null +++ b/protocols/tests/multipliers/mult_d2/both_threads_pass.rt @@ -0,0 +1,16 @@ +trace_block: 0 +trace_result: PASS +matched_monitor_trace_index: 0 +interpreter_trace: +trace { + mul(1, 2, 2); + mul(6, 8, 48); +} + +parsed_monitor_trace_candidates: +candidate_monitor_trace: 0 +trace { + mul(1, 2, 2); + mul(6, 8, 48); +} + diff --git a/protocols/tests/multipliers/mult_d2/first_thread_fails.rt b/protocols/tests/multipliers/mult_d2/first_thread_fails.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/multipliers/mult_d2/first_thread_fails.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/multipliers/mult_d2/second_thread_fails.rt b/protocols/tests/multipliers/mult_d2/second_thread_fails.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/multipliers/mult_d2/second_thread_fails.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/picorv32/unsigned_mul_no_reset.rt b/protocols/tests/picorv32/unsigned_mul_no_reset.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/picorv32/unsigned_mul_no_reset.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/picorv32/unsigned_mul_no_reset_thread_assignment_persistence.rt b/protocols/tests/picorv32/unsigned_mul_no_reset_thread_assignment_persistence.rt new file mode 100644 index 00000000..d79ced8c --- /dev/null +++ b/protocols/tests/picorv32/unsigned_mul_no_reset_thread_assignment_persistence.rt @@ -0,0 +1 @@ +SKIP: non-zero // RETURN diff --git a/protocols/tests/turnt.toml b/protocols/tests/turnt.toml index 8c08bf47..fab701a1 100644 --- a/protocols/tests/turnt.toml +++ b/protocols/tests/turnt.toml @@ -1,2 +1,7 @@ [envs.interp] command = "cargo run --package protocols-interp -- --color never --transactions {filename} {args}" + +[envs.roundtrip] +command = "uv run ../../scripts/roundtrip_case.py {filename} {args}" +binary = true +output.rt = "-" diff --git a/scripts/roundtrip_case.py b/scripts/roundtrip_case.py new file mode 100644 index 00000000..6dea7c42 --- /dev/null +++ b/scripts/roundtrip_case.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +"""Run one roundtrip check for a single .tx file. + +Usage: + uv run scripts/roundtrip_case.py path/to/test.tx +""" + +import argparse +import re +import subprocess +import traceback +from pathlib import Path +from typing import Optional + + +def parse_arg(args: str, flag: str) -> Optional[str]: + """Extract a CLI flag value from a // ARGS line.""" + m = re.search(rf"--{flag}[= ](\S+)", args) + return m.group(1) if m else None + + +def relpath_str(path: Path, base_dir: Path) -> str: + """Return a path string relative to base_dir when possible.""" + try: + return str(path.relative_to(base_dir)) + except ValueError: + return str(path) + + +def sanitize_error_output(text: str) -> str: + """Remove unstable runtime panic location lines from stderr/stdout text.""" + lines = [] + for line in text.splitlines(): + if "panicked at " in line: + continue + lines.append(line) + sanitized = "\n".join(lines).strip() + return sanitized + + +def extract_struct_name(prot_path: Path) -> Optional[str]: + """Return the first struct name declared in a .prot file.""" + m = re.search(r"^struct\s+([A-Za-z_]\w*)", prot_path.read_text(), re.MULTILINE) + return m.group(1) if m else None + + +def normalize_trace_line(line: str) -> str: + """Normalize one trace statement line for stable expected/actual comparison.""" + line = line.strip() + if "//" in line: + line = line.split("//", 1)[0].rstrip() + if not line: + return line + + m = re.fullmatch(r"([A-Za-z_]\w*)\s*\((.*)\)\s*;?", line) + if not m: + return line + + fn_name = m.group(1) + args_blob = m.group(2).strip() + if not args_blob: + return f"{fn_name}();" + + normalized_args = [] + for raw_arg in args_blob.split(","): + arg = raw_arg.strip().replace("_", "") + if re.fullmatch(r"0[bB][01]+", arg): + normalized_args.append(str(int(arg[2:], 2))) + elif re.fullmatch(r"0[xX][0-9a-fA-F]+", arg): + normalized_args.append(str(int(arg[2:], 16))) + elif re.fullmatch(r"\d+", arg): + normalized_args.append(str(int(arg, 10))) + else: + normalized_args.append(arg) + + return f"{fn_name}({', '.join(normalized_args)});" + + +def parse_trace_blocks(text: str) -> list[list[str]]: + """Parse `trace { ... }` blocks and return normalized statements per trace.""" + traces: list[list[str]] = [] + in_trace = False + current: list[str] = [] + + for raw_line in text.splitlines(): + line = raw_line.strip() + if not in_trace: + if line == "trace {": + in_trace = True + current = [] + continue + + if line == "}": + traces.append(current) + in_trace = False + current = [] + continue + + normalized = normalize_trace_line(raw_line) + if normalized: + current.append(normalized) + + return traces + + +def collect_generated_fsts(base_fst: Path) -> list[Path]: + """Collect generated FSTs for a run, preferring indexed multi-trace outputs.""" + indexed: list[tuple[int, Path]] = [] + for path in base_fst.parent.glob(f"{base_fst.stem}_*{base_fst.suffix}"): + suffix = path.stem[len(base_fst.stem) + 1 :] + if suffix.isdigit(): + indexed.append((int(suffix), path)) + + if indexed: + return [path for _, path in sorted(indexed, key=lambda item: item[0])] + if base_fst.exists(): + return [base_fst] + return [] + + +def cleanup_generated_fsts(base_fst: Path) -> None: + """Delete base and indexed temporary waveform files created for a test case.""" + for path in base_fst.parent.glob(f"{base_fst.stem}_*{base_fst.suffix}"): + try: + path.unlink() + except OSError: + pass + try: + base_fst.unlink() + except OSError: + pass + + +def fail(msg: str) -> int: + """Print a failure message and return success for Turnt.""" + print(msg) + return 0 + + +def format_trace(trace: list[str]) -> str: + """Format one trace block for output.""" + if not trace: + return "" + return "\n".join(trace) + + +def format_trace_block(trace: list[str]) -> str: + """Format one trace using `trace { ... }` syntax with statement indentation.""" + if not trace: + return "trace {\n}" + indented = "\n".join(f" {stmt}" for stmt in trace) + return f"trace {{\n{indented}\n}}" + + +def tx_path_to_wave_stem(tx_file: Path, base_dir: Path) -> str: + """Build a deterministic wave stem from the tx path.""" + try: + rel = tx_file.relative_to(base_dir) + rel_no_suffix = rel.with_suffix("") + stem = str(rel_no_suffix).replace("/", "-").replace("\\", "-") + except ValueError: + stem = tx_file.stem + # Keep filenames portable and deterministic. + return re.sub(r"[^A-Za-z0-9._-]", "-", stem) + + +def format_monitor_trace_candidates(traces: list[list[str]]) -> str: + """Format parsed monitor traces as indexed candidates.""" + if not traces: + return "" + chunks = [] + for i, t in enumerate(traces): + chunks.append(f"candidate_monitor_trace: {i}\n{format_trace_block(t)}") + return "\n\n".join(chunks) + + +def main() -> int: + """Execute one roundtrip check for a single `.tx` file. If the + interpreter fails with a non-zero exit code, the test is skipped.""" + parser = argparse.ArgumentParser( + description="Run one roundtrip check for a single .tx file." + ) + parser.add_argument("tx_file", help="Path to the .tx file to check") + parser.add_argument( + "--keep-fst", + action="store_true", + help="Do not delete the intermediate .fst waveform file after the check", + ) + parser.add_argument( + "--allow-round-trip-failure", + action="store_true", + help="Mark this test as allowed to fail (will be skipped)", + ) + args_ns = parser.parse_args() + + tx_file = Path(args_ns.tx_file).resolve() + if not tx_file.exists(): + return fail(f"Missing tx file: {tx_file}") + + tx_text = tx_file.read_text() + args_match = re.search(r"^// ARGS:\s*(.+)$", tx_text, re.MULTILINE) + if not args_match: + print("SKIP: missing // ARGS") + return 0 + args = args_match.group(1) + + # Check if --allow-round-trip-failure is present in the file's // ARGS line + if "--allow-round-trip-failure" in args: + print("SKIP: allowed to fail round trip") + return 0 + + return_match = re.search(r"^// RETURN:\s*(\d+)", tx_text, re.MULTILINE) + if return_match and int(return_match.group(1)) != 0: + print("SKIP: non-zero // RETURN") + return 0 + + prot_rel = parse_arg(args, "protocol") + verilog_rel = parse_arg(args, "verilog") + if not prot_rel or not verilog_rel: + print("SKIP: missing --protocol or --verilog in // ARGS") + return 0 + + base_dir = tx_file.parent + while base_dir != base_dir.parent and not (base_dir / "turnt.toml").exists(): + base_dir = base_dir.parent + if not (base_dir / "turnt.toml").exists(): + return fail(f"No turnt.toml found for {tx_file}") + + prot_file = (base_dir / prot_rel).resolve() + if not prot_file.exists(): + return fail(f"Missing protocol file: {prot_file}") + + struct_name = extract_struct_name(prot_file) + if not struct_name: + return fail(f"Could not find struct in protocol: {prot_file}") + + tx_file_rel = relpath_str(tx_file, base_dir) + prot_file_rel = relpath_str(prot_file, base_dir) + + module_name = parse_arg(args, "module") + instance_name = module_name if module_name else Path(verilog_rel).stem + expected_traces = parse_trace_blocks(tx_text) + wave_stem = tx_path_to_wave_stem(tx_file, base_dir) + roundtrip_tmp_dir = base_dir / ".roundtrip_tmp" + roundtrip_tmp_dir.mkdir(parents=True, exist_ok=True) + fst_path = roundtrip_tmp_dir / f"{wave_stem}.fst" + fst_path_rel = relpath_str(fst_path, base_dir) + cleanup_generated_fsts(fst_path) + + try: + interp_cmd = ( + "cargo run --quiet --package protocols-interp -- " + f"--color never --transactions {tx_file_rel} {args} --fst {fst_path_rel}" + ) + interp = subprocess.run( + interp_cmd, + shell=True, + cwd=base_dir, + capture_output=True, + text=True, + ) + if interp.returncode != 0: + output = sanitize_error_output((interp.stdout + interp.stderr).strip()) + return fail( + f"interpreter_error:\n{output if output else ''}" + ) + + generated_fsts = collect_generated_fsts(fst_path) + if not generated_fsts: + return fail("interpreter_error:\nNo waveform file generated by interpreter") + + printed_blocks = 0 + + for trace_idx, generated_fst in enumerate(generated_fsts): + if printed_blocks > 0: + print("") + print("---") + print("") + print(f"trace_block: {trace_idx}") + if args_ns.keep_fst: + print(f"fst_file: {relpath_str(generated_fst, base_dir)}") + printed_blocks += 1 + if trace_idx >= len(expected_traces): + print("trace_result: FAIL") + print("failure_kind: extra_interpreter_trace") + print("message:") + print( + f"Interpreter generated unexpected extra trace {trace_idx} for {tx_file}" + ) + print("") + continue + + monitor_cmd = ( + "cargo run --quiet --package protocols-monitor -- " + f"-p {prot_file_rel} --wave {relpath_str(generated_fst, base_dir)} " + f"--instances {instance_name}:{struct_name}" + ) + monitor = subprocess.run( + monitor_cmd, + shell=True, + cwd=base_dir, + capture_output=True, + text=True, + ) + if monitor.returncode != 0: + output = sanitize_error_output( + (monitor.stdout + monitor.stderr).strip() + ) + print("trace_result: FAIL") + print("failure_kind: monitor_error") + print("interpreter_trace:") + print(format_trace_block(expected_traces[trace_idx])) + print("") + print("monitor_error:") + print(output if output else "") + print("") + continue + + monitor_traces = parse_trace_blocks(monitor.stdout) + expected = expected_traces[trace_idx] + matched_idx = next( + ( + i + for i, candidate in enumerate(monitor_traces) + if candidate == expected + ), + None, + ) + if matched_idx is None: + print("trace_result: FAIL") + print("failure_kind: trace_mismatch") + print("interpreter_trace:") + print(format_trace_block(expected)) + print("") + print("parsed_monitor_trace_candidates:") + print(format_monitor_trace_candidates(monitor_traces)) + print("") + continue + + print("trace_result: PASS") + print(f"matched_monitor_trace_index: {matched_idx}") + print("interpreter_trace:") + print(format_trace_block(expected)) + print("") + print("parsed_monitor_trace_candidates:") + print(format_monitor_trace_candidates(monitor_traces)) + print("") + + if len(generated_fsts) < len(expected_traces): + for trace_idx in range(len(generated_fsts), len(expected_traces)): + if printed_blocks > 0: + print("") + print("---") + print("") + print(f"trace_block: {trace_idx}") + printed_blocks += 1 + print("trace_result: FAIL") + print("failure_kind: missing_interpreter_trace") + print("interpreter_trace:") + print(format_trace_block(expected_traces[trace_idx])) + print("") + + return 0 + finally: + if not args_ns.keep_fst: + cleanup_generated_fsts(fst_path) + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception: + print(f"roundtrip_tester_error:\n{traceback.format_exc().strip()}") + raise SystemExit(0) diff --git a/turnt.toml b/turnt.toml index 70a5c157..ccf06cff 100644 --- a/turnt.toml +++ b/turnt.toml @@ -1 +1,6 @@ command = "cargo run --package protocols-interp -- --color never --transactions {filename} {args}" + +[envs.roundtrip] +command = "uv run scripts/roundtrip_case.py {filename} {args}" +binary = true +output.rt = "-"