An example shows that always blocks really aren’t needed much in SystemVerilog
virtual class C#(type T); static task ff(const ref logic clk, const ref T in, ref T out); forever @(posedge clk) out <= in; endtask endclass module test#(type T) (input logic clk, input T in, output T out); initial C#(T)::ff(clk, in, out); endmodule
Modules are useful for laying down static components like nets and registers, but their behavior should be described by methods and the rest of the OOP machinery.
Unfortunately the <= (NBA or nonblocking assignment) in that example still violates a rule that was added to 13.5.2 during the 2005 balloting, which says, “Because a variable passed by reference may be an automatic variable, a ref argument shall not be used in any context forbidden for automatic variables.” But in the long run that license for lazy analysis won’t stand. The LRM already is more nuanced elsewhere, such as in 16.11, which says, “An automatic variable shall not be passed by reference nor passed as a nonconstant input to a subroutine call from an assertion statement in procedural code.”
If you can do an NBA to a non-static class property (and of course you can), you should surely be able to do an NBA via a reference to a static module variable.
Aside: I suppose there must be some workaround to get the effect using a non-static class property, such as
@(posedge clk) begin out = prev; prev <= in; end
but whatever the “correct” hack might be, the tools and event scheduler are supposed to take care of stuff like this.