Overview

Subroutines are sections of code that can be defined and reused to perform specific tasks. They provide a way to modularize your code, making it more organized, readable, and easier to maintain. Subroutines are especially useful when you have a piece of functionality that needs to be executed multiple times or from different parts of your design.

Function

Function is the basic block of code which performs the functionality that is programmed in it on every call. Verilog functions are similar to C/C++ or any other high level language functions in the sense

Support multiple arguments
Must define a return type or a default "void" is used
Can be invoked multiple times
Can be static or automatic ( automatic begin by default )
Primarily used in synthesis codes ( RTLs )

Task

Verilog tasks are similar to function but with a couple of modifications which are explicitly meant to work with HDL simulators. They are mentioned below

Arguments must be declared as inputs, outputs or inputs
There is no return type for tasks
Tasks ar temporal, which means delays can be used in them
Tasks can be static or automatic
Tasks can call functions, but functions cannot call tasks
Mostly used to model verification or non synthesizable blocks

Theory Questions
    Yes, it is possible to pass a datatype as ref. Although this makes sure any changes made to the variable in the task is immideately reflected everywhere else the varibale is being used.
    INOUT is allowed to be used in both tasks and functions.
    This type of fucntion is one which creates a local memory of its own on every fucntion call.
    A local variable is the one which has been created/declared inside the subroutine itself, while a formal variable is the one which is passed through a subroutine call. Usually, the local and formal variable concept comes into picture if the names of the variables are the same.
    Functions are preferred while wrting synthesizable codes. This is because, every call of the function is treated as an independent logic block.
    "Pass by value" refers to a way of passing arguments in subroutines, where a copy of the value of the varible that is used as an argument is provided inside the subroutine. Any change to the varibale inside the subroutine will not affect the actual variable outside.
    "Pass by reference" is when a variable is passed along with its pointer, which means any changes to the variable inside the subroutine will affect the varibales actual value everywhere else.
    Function with a static keyword before is not allowed.
    function integer rand_ret();
        fork
            begin
                return 5;
            end
            begin
                return 6;
            end
            begin
                return 7;
            end
        join
    endfunction
    
    It is valid, but which value is returned is simulator dependent.
Coding Questions
    module factorial_example;
      // Recursive function to calculate factorial
      function automatic integer factorial(input integer n);
        begin
          if (n == 0)
            factorial = 1;
          else
            factorial = n * factorial(n - 1);
        end
      endfunction
    
      initial begin
        $display("Factorial of 5 is: %0d", factorial(5));
      end
    endmodule
    
    module fibonacci_example;
      // Recursive function to calculate Fibonacci numbers
      function automatic integer fibonacci(input integer n);
        begin
          if (n == 0)
            fibonacci = 0;
          else if (n == 1)
            fibonacci = 1;
          else
            fibonacci = fibonacci(n - 1) + fibonacci(n - 2);
        end
      endfunction
    
      // Task to print Fibonacci series up to N-th element
      task print_fibonacci(input integer N);
        integer i;
        begin
          for (i = 0; i < N; i = i + 1)
            $display("Fibonacci(%0d) = %0d", i, fibonacci(i));
        end
      endtask
    
      initial begin
        // Print Fibonacci series up to 10th element
        print_fibonacci(10);
      end
    endmodule
    
    module top;
    
        function printF();
            $display("PRINT CALL");
        endfunction
    
        task print();
            $display("TASK CALL");
        endtask
    
        initial
        begin
            print(); // This works because there is no time being consumed in the task which is declared. Note that this is a LRM ambiguity, and so only some simulators allow it, for example older versions of QuestaSim.
        end
    
    
    endmodule
    
    task cal_sum( input integer ARR_IN [8], output reg [31:0] ARR_SUM );
        // Code
    endtask
    
    task cal_sum( input integer ARR_IN [8], output reg [31:0] ARR_SUM );
        integer i = 0;
    
        for( i = 0, ARR_SUM = 0; i < 8; i = i + 1 )
        begin
            ARR_SUM = ARR_SUM + ARR_IN[i];
        end
        return ARR_SUM;
    endtask
    
    module top;
    
        integer M[8] = '{ 1, 2, 3, 4, 5, 6, 7, 8 };
        integer OUT;
    
        initial begin
            cal_sum( M, OUT );
            $display( "Sum : %0d", OUT );        
        end
    
    endmodule
    
    task gen_clk( ref integer clk_period, ref reg clk );
        // Code
    endtask
    
    task gen_clk( ref integer clk_period, ref reg clk );
        clk = 0;
        forever
        begin
            #(clk_period/2) clk = ~clk;
        end
    endtask