Search code examples
verilogsystem-verilog

Function returning logically incorrect values


I am currently learning to write Verilog and as an exercise trying to implement an algorithm, but a function I have written for it returns unexpected, incorrect values.

I managed to reduce it to this minimal example:

function [0:1] test (
    input a0,
    input a1,
    input r
);                                                                                                         
    begin
        test = {(a0 ^ 1) & r, (a1 ^ 1) & r};
    end
endfunction

which I then call inside a module:

assign out[1:0] = test(0, 0, 1);

where out is the output register of the module.

To my understanding both out[1] and out[0] should then have the same value, namely 1 (since (0 ^ 1) & 1 = 1).

I wrote a testbench for this and if I simply call this module there once, the wave output of my IDE shows me that out[1] and out[0] are now 0 and 1, respectively, as can be seen in the wave in the screenshot.

wave output of testbench

Logically, this does not make sense, so there must be another issue I am missing, but I am at a complete loss what it could be. Any help would be greatly appreciated.

The entire code I'm using to reproduce this error:

issue.v

module issue(
    input [63:0] in,
    input [63:0] mask,
    input [63:0] r,
    output reg [63:0] out
);

    function [0:1] test (
        input a0,
        input a1,
        input r
    );
        begin
            test = {(a0 ^ 1) & r, (a1 ^ 1) & r};
        end
    endfunction

    assign out[1:0] = test(0, 0, 1);

endmodule

issue_tb.v

`timescale 1 ns/10 ps

module issue_tb;
    localparam period = 20;

    reg [63:0] in, mask, r;
    wire [63:0] out;

    issue UUT  (.in(in), .mask(mask), .r(r), .out(out));

    reg clk;

    always begin
        clk = 1'b1; 
        #20; // high for 20 * timescale = 20 ns

        clk = 1'b0;
        #20; // low for 20 * timescale = 20 ns
    end

    always @(posedge clk)
    begin
        // test 0
        //in = 63'h0;
        //mask = 63'h0;
        //r = 63'h1;
        #period;
        if(!(out == 63'h0)) begin
            $display("Test 0 failed");
            $stop;
        end
        $display("All tests passed");
        $stop;
    end
endmodule

Solution

  • The problem is that the constant 1 is not 1-bit wide, as you require.

    Change:

        test = {(a0 ^ 1) & r, (a1 ^ 1) & r};
    

    to:

        test = {(a0 ^ 1'b1) & r, (a1 ^ 1'b1) & r};
    

    There are subtleties associated with using the concatenation operator ({}) regarding signal bit widths. Refer to IEEE Std 1800-2017, Table 11-21— Bit lengths resulting from self-determined expressions. The problem with your code is that items in a concatenation are self-determined. I believe 1 is 32 bits wide. So, each of the 2 expressions in the concatenation is 32 bits wide, thereby truncating bits for test, which is only 2 bits wide. Using 1'b1 sets the bit width of the constant to 1.