From d26eb4efec62f9fb55fbc351acdf4a9af43ccb42 Mon Sep 17 00:00:00 2001 From: Philip Axer Date: Sat, 7 Nov 2026 13:37:21 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Keep=20a=20subroutine=20with=20no=20written?= =?UTF-8?q?=20lifetime=20static=20on=20the=20compiled=20tier=20(=C2=A76.21?= =?UTF-8?q?,=20=C2=A713.3.1,=20=C2=A713.3.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compiled tier no longer inlines a task or void function with no written lifetime when it has locals or non-input formals. Such a subroutine is static, so its variables keep their values between calls. The call stays interpreted. --- src/compiler/bytecode.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/bytecode.rs b/src/compiler/bytecode.rs index 7c215100a..04f4b0659 100644 --- a/src/compiler/bytecode.rs +++ b/src/compiler/bytecode.rs @@ -4224,8 +4224,10 @@ impl<'a> BytecodeCompiler<'a> { // §13.3.1: a STATIC subroutine's variables — its locals and output // formals — keep their values between calls; an inline gives each // call fresh registers. Only an input-only static body with no - // declarations behaves the same either way. - if matches!(lifetime, Some(crate::ast::types::Lifetime::Static)) + // declarations behaves the same either way. No written lifetime is + // static; elaboration marks a subroutine of an `automatic` module + // automatic (§13.3.1). + if !matches!(lifetime, Some(crate::ast::types::Lifetime::Automatic)) && (ports .iter() .any(|p| !matches!(p.direction, PortDirection::Input)) From e00f0384172a6e51e2ef22e2246e0e97003c794e Mon Sep 17 00:00:00 2001 From: Philip Axer Date: Sat, 7 Nov 2026 13:37:21 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Test=20static=20variables=20of=20subroutine?= =?UTF-8?q?s=20with=20no=20written=20lifetime=20(=C2=A76.21,=20=C2=A713.3.?= =?UTF-8?q?1,=20=C2=A713.3.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static locals and output formals under a clocked process: module tasks, void functions, block tasks bare and by path, generate-for and nested blocks in an instance. Automatic tasks and automatic modules are the contrast. A function with a result and a task without locals already worked and only hold that. --- tests/lrm.rs | 2 + ...ines_keep_their_variables_when_compiled.rs | 166 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 tests/lrm/implicit_static_subroutines_keep_their_variables_when_compiled.rs diff --git a/tests/lrm.rs b/tests/lrm.rs index 518f82951..74fb07c17 100644 --- a/tests/lrm.rs +++ b/tests/lrm.rs @@ -83,3 +83,5 @@ mod ch36_vpi; mod constraint_real_bounds; #[path = "lrm/harness.rs"] mod harness; +#[path = "lrm/implicit_static_subroutines_keep_their_variables_when_compiled.rs"] +mod implicit_static_subroutines_keep_their_variables_when_compiled; diff --git a/tests/lrm/implicit_static_subroutines_keep_their_variables_when_compiled.rs b/tests/lrm/implicit_static_subroutines_keep_their_variables_when_compiled.rs new file mode 100644 index 000000000..5b5662d2e --- /dev/null +++ b/tests/lrm/implicit_static_subroutines_keep_their_variables_when_compiled.rs @@ -0,0 +1,166 @@ +//! §6.21, §13.3.1, §13.3.2: a subroutine with no written lifetime in a static definition +//! is static. Its variables, formals included, retain their values between +//! invocations, also when a clocked process runs on the compiled tier. + +use xezim::simulate; + +fn lines(src: &str) -> Vec { + let sim = simulate(src, 1000).expect("simulate failed"); + sim.output.iter().map(|o| o.message.clone()).collect() +} + +/// `clk` rises at 5, 15, ... 95: ten calls by time 100. +const CLK: &str = "logic clk = 0; always #5 clk = ~clk;"; + +#[test] +fn a_module_task_with_no_written_lifetime_keeps_its_static_local() { + let src = format!( + r#"module top; {CLK} int acc = 0; + task tk(); static int w = 0; w++; acc += w; endtask + always @(posedge clk) tk(); + initial begin #100 $display("%0d", acc); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["55"]); +} + +#[test] +fn only_an_automatic_task_gets_fresh_locals() { + let src = format!( + r#"module top; {CLK} int a = 0, b = 0, c = 0; + task automatic ta(); int w = 0; w++; a += w; endtask + task static ts(); static int w = 0; w++; b += w; endtask + task ti(); static int w = 0; w++; c += w; endtask + always @(posedge clk) begin ta(); ts(); ti(); end + initial begin #100 $display("%0d %0d %0d", a, b, c); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["10 55 55"]); +} + +#[test] +fn a_top_level_block_task_keeps_its_static_local_bare_and_by_path() { + let src = format!( + r#"module top; {CLK} + if (1) begin : g + int bare = 0, path = 0; + task tb(); static int w = 0; w++; bare += w; endtask + task tp(); static int w = 0; w++; path += w; endtask + always @(posedge clk) begin tb(); g.tp(); end + end + initial begin #100 $display("%0d %0d", g.bare, g.path); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["55 55"]); +} + +#[test] +fn block_tasks_in_an_instance_keep_one_static_local_per_generate_iteration() { + let src = format!( + r#"module m; {CLK} + for (genvar i = 0; i < 2; i++) begin : h + int acc = 0; + task t(); static int w = 0; w++; acc += w; endtask + always @(posedge clk) if (i == 0 || $time < 50) t(); + if (1) begin : n + int deep = 0; + task d(); static int w = 0; w++; deep += w; endtask + always @(posedge clk) d(); + end + end + initial #100 $display("%0d %0d %0d %0d", h[0].acc, h[1].acc, h[0].n.deep, h[1].n.deep); +endmodule +module top; m u(); initial #101 $finish; endmodule"# + ); + assert_eq!(lines(&src), ["55 15 55 55"]); +} + +#[test] +fn a_void_function_with_no_written_lifetime_keeps_its_static_local() { + let src = format!( + r#"module top; {CLK} int acc = 0; + function void f(); static int w = 0; w++; acc += w; endfunction + always @(posedge clk) f(); + initial begin #100 $display("%0d", acc); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["55"]); +} + +#[test] +fn a_function_with_a_result_and_no_written_lifetime_keeps_its_static_local() { + let src = format!( + r#"module top; {CLK} int acc = 0; + function int f(); static int w = 0; w++; return w; endfunction + always @(posedge clk) acc += f(); + initial begin #100 $display("%0d", acc); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["55"]); +} + +#[test] +fn a_local_without_a_qualifier_is_static_in_a_static_task() { + let src = format!( + r#"module top; {CLK} int a = 0, b = 0, c = 0; + task top_level(); int w; w++; a = w; endtask + task nested(); begin int w; w++; b += w; end endtask + task looped(int n); for (int i = 0; i < n; i++) begin int w; w++; c += w; end endtask + always @(posedge clk) begin top_level(); nested(); looped(1); end + initial begin #100 $display("%0d %0d %0d", a, b, c); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["10 55 55"]); +} + +#[test] +fn an_output_formal_of_a_static_task_retains_its_value_between_calls() { + let src = format!( + r#"module top; {CLK} int r = 0; + task t(output int o); o = o + 1; endtask + always @(posedge clk) t(r); + initial begin #100 $display("%0d", r); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["10"]); +} + +#[test] +fn a_static_local_is_readable_by_path_after_compiled_calls() { + let src = format!( + r#"module top; {CLK} + task t(); static int w = 0; w++; endtask + always @(posedge clk) t(); + initial begin #100 $display("%0d", t.w); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["10"]); +} + +#[test] +fn a_task_with_no_locals_still_runs_in_a_clocked_process() { + let src = format!( + r#"module top; {CLK} int a = 0; + task t(int x); a += x; endtask + always @(posedge clk) t(2); + initial begin #100 $display("%0d", a); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["20"]); +} + +#[test] +fn subroutines_of_an_automatic_module_get_fresh_locals_also_in_a_block() { + let src = format!( + r#"module automatic top; {CLK} int a = 0, b = 0; + task t(); int w; w++; a += w; endtask + if (1) begin : g + task n(); int w; w++; b += w; endtask + always @(posedge clk) n(); + end + always @(posedge clk) t(); + initial begin #100 $display("%0d %0d", a, b); $finish; end +endmodule"# + ); + assert_eq!(lines(&src), ["10 10"]); +}