2

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).

5
  • where is your strange declaration syntax came from? logic [4:-5] a, b; declares two 10-bit vars, not 5 bit. You need [4:0] for 5 bits. As for the rest, look into packed structs and text macros. Commented Aug 31, 2022 at 1:54
  • Yes, I know it is 10 bits, 5 for integers + 5 for fractionals (2**4+2**3+2**2+2**1+2**0+2**-1+2**-2+2**-3+2**-4+2**-5). It is an implementation of the Q notation, where for simplicity I used only unsigned instead of signed numbers. Please check also this answer from @dave_59. Commented Aug 31, 2022 at 8:54
  • I have thought about his same problem in the past and not come up with anything satisfactory. I think you need a parameterized class which contains the multiplication function. What you really want is to overload the * operator but I don’t think that is supported. Commented Sep 30, 2022 at 10:58
  • We might be able to do something simple with macros. I am going to try to come up with something that works. Commented Sep 30, 2022 at 11:03
  • @nguthrie I thought as well about parametrized classes, but I will have to synthesize this thus it is not an option. Macros on the other hand are quite powerful but also dangerous and very difficult to debug, thus my company's policy (which I support) is to avoid macros as much as possible. Probably the only thing left is to create custom parametrized modules that perform given operations. Commented Sep 30, 2022 at 14:58

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.