How To Code Efficiently In SystemVerilog Without AOP

How To Code Efficiently In SystemVerilog Without AOP

Posted by

One too many times I’ve seen great verification engineers, experts in ‘e’ language programming, afraid of moving towards SystemVerilog. Their fears covered the full spectrum: from online myths to extremely well founded arguments.
Regardless of how well or not is the argument, times are changing and we need to keep up with the technologies.



One idea which I’ve encountered over and over again is this:

I will have to write tones of code because in SystemVerilog I do not have the Aspect Oriented Programming capabilities of ‘e’

Well …. this is not 100% true! Or, better said, is not that dramatic!

Let’s look at a simple example in ‘e’ language and how we can translate it efficiently in SystemVerilog using something called very fancy: the Strategy Pattern.

We need to build a calculator which can perform two types of operations: with one parameter and with two parameters.
Simple enough!

‘e’ Language

You can get the entire code from EDA Playground!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct calculator {

   op1_kind : [MODULO, POW2];
   
   op2_kind : [ADD, SUB];
   
   op1(a : int) : int is undefined;
   
   op2(a : int, b : int) : int is undefined;
   
   when MODULO'op1_kind {
      op1(a : int) : int is {
         return ((a >= 0) ? a : (-a));
      };
   };
   
   when POW2'op1_kind {
      op1(a : int) : int is {
         return a * a;
      };
   };
   
   when ADD'op2_kind {
      op2(a : int, b : int) : int is {
         return (a + b);
      };
   };
   
   when SUB'op2_kind {
      op2(a : int, b : int) : int is {
         return (a - b);
      };
   };
};

extend sys {
   run() is also {
      var calc : calculator = new;
      calc.op1_kind = POW2;
      messagef(LOW, "power of 2 result: %d", calc.op1(5));
     
      calc.op2_kind = ADD;
      messagef(LOW, "add result - %d", calc.op2(3, 4));
   };
};

I guess the code needs no explanations! If it does then you’re in the wrong place 🙂

When it comes to writing this code in SystemVerilog most of us will jump in creating either one class with lots of ifs inside or four classes for each possible combination. But we will see that there is a better approach.

SystemVerilog Version #1: lots of ifs

You can get the entire code from EDA Playground!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
typedef enum {MODULO, POW2} op1_kind_e;
   
typedef enum {ADD, SUB} op2_kind_e;
   
class calculator ;
  op1_kind_e op1_kind;
 
  op2_kind_e op2_kind;
 
  virtual function int op1(int a);
    if(op1_kind == MODULO) begin
      return ((a >= 0) ? a : (-a));
    end
    else if(op1_kind == POW2) begin
      return a * a;
    end
    else begin
      $error("You added values to op1_kind_e but you did not updated function op1()");
    end
  endfunction
   
  virtual function int op2(int a, int b);
     if(op2_kind == ADD) begin
       return (a + b);
    end
     else if(op2_kind == SUB) begin
       return (a - b);
    end
    else begin
      $error("You added values to op2_kind_e but you did not updated function op2()");
    end
   endfunction
endclass

module test;
   initial begin
      automatic calculator calc = new();
      calc.op1_kind = POW2;
      $display("power of 2 result: %0d", calc.op1(5));
     
      calc.op2_kind = ADD;
     $display("add result - %0d", calc.op2(3, 4));
  end
endmodule

This approach is not flexible because if I need to add new operation kinds then I will have to modify the two functions: op1() and op2().
So we need to find a way to be able to add new functionality without modifying the original code!

SystemVerilog Version #2: four classes

You can get the entire code from EDA Playground!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
virtual class calculator;
   pure virtual function int op1(int a);
   
   pure virtual function int op2(int a, int b);
endclass

class calculator_modulo_add extends calculator;
   virtual function int op1(int a);
     return ((a >= 0) ? a : (-a));
   endfunction
   
   virtual function int op2(int a, int b);
     return (a + b);
   endfunction
endclass
   
class calculator_modulo_sub extends calculator;
   virtual function int op1(int a);
     return ((a >= 0) ? a : (-a));
   endfunction
   
   virtual function int op2(int a, int b);
     return (a - b);
   endfunction
endclass
     
class calculator_pow2_add extends calculator;
   virtual function int op1(int a);
     return (a * a);
   endfunction
   
   virtual function int op2(int a, int b);
     return (a + b);
   endfunction
endclass
     
class calculator_pow2_sub extends calculator;
   virtual function int op1(int a);
     return (a * a);
   endfunction
   
   virtual function int op2(int a, int b);
     return (a - b);
   endfunction
endclass
   
module test;
   initial begin
      automatic calculator_pow2_add calc = new();
      $display("power of 2 result: %0d", calc.op1(5));
      $display("add result - %0d", calc.op2(3, 4));
  end
endmodule

This approach is flexible, in the sense that that I do not have to modify the original code in order to add new operation kinds.
The pitfall of this approach is that the number of classes I have to write grows exponentially with every new operation kind added. Plus, I have to write tones of redundant code.

SystemVerilog Version #3: the Strategy Pattern

You can get the entire code from EDA Playground!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
virtual class op1_handler;
  pure virtual function int op(int a);
endclass
   
class op1_modulo_handler extends op1_handler;
   virtual function int op(int a);
      return ((a >= 0) ? a : (-a));
   endfunction
endclass
 
class op1_pow2_handler extends op1_handler;
   virtual function int op(int a);
     return (a * a);
   endfunction
endclass

virtual class op2_handler;
  pure virtual function int op(int a, int b);
endclass
   
class op2_add_handler extends op2_handler;
   virtual function int op(int a, int b);
     return (a + b);
   endfunction
endclass

class op2_sub_handler extends op2_handler;
   virtual function int op(int a, int b);
     return (a - b);
   endfunction
endclass

class calculator;
   op1_handler op1_h;
 
   op2_handler op2_h;
 
   function int op1(int a);
      return op1_h.op(a);
   endfunction
   
   function int op2(int a, int b);
     return op2_h.op(a, b);
   endfunction
endclass
   
module test;
   initial begin
      automatic calculator calc = new();
      automatic op1_pow2_handler op1_h = new();
      automatic op2_add_handler op2_h = new();
     
      calc.op1_h = op1_h;
      calc.op2_h = op2_h;
     
      $display("power of 2 result: %0d", calc.op1(5));
      $display("add result - %0d", calc.op2(3, 4));
  end
endmodule

The big advantage of this approach is that it is flexible enough so I will not have to modify the original code if I need to add new kinds of operations. And also, the number of classes grows linearly with every new kind added (e.g. just have to implement one op2_handler class for a new kind of two arguments operation).



Cristian Slav

One Comment

  • tudor.timi@gmail.com' Tudor Timi says:

    I would make one slight change to your code:

    class calculator;
    function new(op1_handles op1, op2_handler op2);
    // …
    endfunction
    // …
    endclass

    This way you enforce that you need handlers supplied. It also makes it easy to instantiate a calculate in one line:

    calc = new(op1_pow2_handler::new(), op2_add_handler::new());

Leave a Reply

Your email address will not be published.