Procedures can be useful for more than just simulation.
I’ve used procedures in the past for simulations. They provide a convenient way to handle bus protocols. More recently, I’ve been looking into using them for synthesizable code. At the core, procedures can be snippets of code with an alias. Even when a piece of code is only used once, it can still be useful to make a procedure.
This was an interesting realization. The reason is that it moves specifics of some operation outside of the state machine logic. This makes the code more readable, as there is some amount of self-commenting. More importantly, it removes clutter. For example:
-- inside of a process:
set_defaults;
detect_events;
case state is
when SOF => -- expecting SOF
if start_event and not overflow then
write_increment_logic;
next_state <= DATA_EOF;
end if;
when DATA_EOF => -- expecting data or EOF
if start_event or overflow then
write_revert_logic;
next_state <= SOF;
elsif end_event then
write_commit_logic;
next_state <= SOF;
elsif data_event then
write_increment_logic;
end if;
end case;
This is a simple example, but hopefully shows the point. This code segment’s intent is clear, even if some of the details are left out. It provides different levels of resolution into the code. For simple cases, the above is about as complex as just writing out all of the details. But the same structure might also be used for more complicated cases. For example, in the above, there may be gray coding of pointers, variable amounts of data valid per cycle, multiple fifos to be controlled, etc…
One disadvantage of the above is that it isn’t clear what the inputs or outputs of the procedures are. This can be important if two procedures need to be referenced in a specific order because they both assign to the same signals, or because they both make use of variables. You can add the inputs/outputs, but it begins to become cluttered again.