mattia
mattia

Reputation: 32

Fixed point arithmetic in SystemVerilog

I am struggling to find the best way to write some good code with arithmetic operations (sum, multiplications) in SystemVerilog. It is crucial that this code need to be synthesized for an ASIC, so no use of real numbers or classes.

My code eventually works, but it feels like I am re-inventing the wheel each time. Here it follows an example of the syntax I use, with fixed point signals, with integers aligned on bit at index [0].

    logic [4:-5] a, b;      //5 bits for integers, 5 bits for fractionals
    logic [9:-10] res;      //full result
    logic [9:-5] res_trunc; //result truncated with the same precisions as a,b
    always_comb res = a*b;
    always_comb res_trunc = res>>5;

Please notice how the last operation, in case of signed negative numbers (>>> operator needed) will have issues.

What I am looking for, would be some kind of standard way of expressing fractional numbers (with a typedef maybe?), with possible "automatic alignment" of integers, exploiting the fact that synthesis engine will simplify and get rid of what is not necessary.

Hereafter, I will write some pseudocode that is similar to what I would like to achieve.

    //parametric typedef? not synthesizable if done exploting classes...
    typedef signed logic [15,-16] fp_num_t(PRECISION); 

    fp_num_t(2) a; //e.g.    10.01  = 2.250, assigned in u_my_mod module
    fp_num_t(3) b; //e.g.    01.001 = 1.125, assigned in u_my_mod module

    my_mod u_my_mod (
        .....
        .o1 (a),
        .o2 (b)
    ); 

    fp_num_t(1) c; //result, 10.1   = 2.5
    always_comb c = a*b;

I hope I managed to explain what my target would be. I don't know if anything similar to this might be possible, or if some of you found a better way to handle this kind of operations without expliciting all the bits each time (awful code readability, error prone).

Upvotes: 1

Views: 1691

Answers (1)

nguthrie
nguthrie

Reputation: 2685

Well, I know you don't want macros but I couldn't resist trying. Definitely a bit complicated and doesn't support N=0:

// Create a variable NAME that is in Q format: QM.N
// This includes a sign bit. Also create a real number representation
// just for debug purposes
`define Q(NAME,M,N) \
\
struct packed signed \
       { \
          logic [ M    :0] m; \
          logic [ N - 1:0] n; \
       } NAME \
`ifndef SYNTHESIS \
;real ``NAME``_real; \
assign ``NAME``_real = real'( NAME ) / (2.0**$bits(NAME.n)) \
`endif


// Multiply 2 Q variables together
// First create the product variable. Each part of this
// variable is the sum of the bit widths.
//
// The second variable is for the truncated output which will have as many fractional bits
// as the input
`define Q_MUL( P, A, B ) \
\
`Q ( P, $bits(A.m) + $bits(B.m), $bits(A.n) + $bits(B.n) ); \
`Q ( ``P``_trunc, $bits(A.m) + $bits(B.m), ($bits(A.n) > $bits(B.n)) ? $bits(A.n) : $bits(B.n) ); \
assign P = A*B; \
assign ``P``_trunc = P >>> ($bits(P.n) - $bits(``P``_trunc.n))

A quick test:

module test;

   `Q( varA, 2, 1);
   `Q( varB, 3, 3);

   `Q_MUL( product, varA, varB );

   initial begin
      varA = 4'b0_10_0; // 2
      varB = 7'b0_111_001; // 7.125
      #10;
      $display ("%f x %f = %f", varA_real, varB_real, product_real);
      $display ("%f x %f = %f", varA_real, varB_real, product_trunc_real);
      varA = 4'b1_00_1; // -3.5
      varB = 7'b0_111_001; // 7.125
      #10;
      $display ("%f x %f = %f", varA_real, varB_real, product_real);
      $display ("%f x %f = %f", varA_real, varB_real, product_trunc_real);
      $finish();
   end
endmodule

Results in:

2.000000 x 7.125000 = 14.250000
2.000000 x 7.125000 = 14.250000
-3.500000 x 7.125000 = -24.937500
-3.500000 x 7.125000 = -25.000000

Upvotes: 1

Related Questions