• Skip to main content
  • Skip to search
  • Skip to footer
Cadence Home
  • This search text may be transcribed, used, stored, or accessed by our third-party service providers per our Cookie Policy and Privacy Policy.

  1. Blogs
  2. Verification
  3. Tips on Using e Macros to Raise Abstraction and Facilitate…
teamspecman
teamspecman

Community Member

Blog Activity
Options
  • Subscribe by email
  • More
  • Cancel
IEEE 1647
Specman
Functional Verification
tech tips
e
team specman
macros
Incisive Enterprise Simulator (IES)
IES-XL

Tips on Using e Macros to Raise Abstraction and Facilitate Reuse

15 Jul 2009 • 5 minute read

[Please welcome Yuri Tsoglin of Specman R&D to the guest blogging rostrum.]

As my colleague Hilmar van der Kooij noted in a previous post, e's "defined as computed" macro capability is a great way to condense repetitive blocks of code into a few easy to read, parameterized lines. Building on Hilmar’s practical introduction, I’m going to ask you to take a step back and look at macros in broader context: specifically, consider that macros allow you to effectively introduce your own language constructs in an almost unlimited way. Whether you stick with a purely practical view of macros, or accept this proposed conceptual view, the benefits are the same: macros are a very powerful tool to help to raise the level of abstraction and facilitate reuse of your testbench.

Of course, using macros in a haphazard way can be more harmful than helpful since a macro defined improperly, or used in a situation that does not really require a macro, can cause the code to become obscure and less readable. Thus, allow me to give some useful tips on when macros should or should not be used, and when the “define-as-computed” macro capability referenced in Hilmar’s post is more appropriate than e’s “define-as” macro capability

A macro introduces a new syntactic construct to the language, and that's the primary reason they should be added to your code. For example, if you have a reused syntactic pattern in your code, such as a group of struct members related to each other in a certain way:

<'

struct my_struct_s {

 

   private a: uint;

   get_a(): uint is {return a};

   set_a(value: uint) is {a = value};

 

   private b: uint;

   get_b(): uint is {return b};

   set_b(value: uint) is {b = value};

 

   // same code for defining c,d,e …

};

'>

A macro such as the following would be the best thing to do:

 

<'

define <my_priv_field'struct_member> "priv_field <name>[ ]:[ ]<type>" as {

      private <name>: <type>;

      get_<name>(): <type> is { return <name> };

      set_<name>(value: <type>) is { <name> = value };

};

'>

as this would allow you to implement a new language construct to represent a private field definition as well as the two required methods:

 

<'

struct my_struct_s {   

    priv_field a: uint;

    priv_field b: uint;

    …

  };

'>

However, it is not a good idea to use a macro where other basic language tools will do. For example, don't use a macro to make up a "pseudo-method" that multiplies the field “f” in a certain struct by a given factor:

 

<'

-- This is an example for what should NOT be done

define <print_hex'exp> "multiply_f\(<factor'exp>\)" as {

       ( f * <factor'exp> )

};

'>

Although the macro works, it is preferable to use a regular method in this case. Also notice that although this macro is expected to be called only in the context of the struct, nothing prevents it from being called outside that context.

Special Macro Constructs: “Define As” and “Define As Computed”
The e language supports two unique constructs for creating macros: “define as” and “define as computed”. The difference between the two: a “define-as” macro directly defines the textual pattern for the replacement code, whereas a “define-as-computed macro” contains procedural code that is executed at compile time and computes the replacement code string.

Define-as macros are more simple and readable, so use a define-as macro whenever the general replacement code pattern is fixed and does not depend on the macro arguments. In more complicated cases, when the replacement code can be different and depends on the macro arguments in a non-trivial way, a define-as-computed macro is the way to go. To illustrate, let me start with an example where a define-as macro seems to be fine at first glance, but actually it isn't.

Assume your struct “packet” has a boolean field “flag”, and you want to introduce a new action construct of the form "switch p on/off", which will modify that field accordingly.

 

<'

define <switch_packet'action> "switch <packet'exp> (<HOW>on|off)" as {

      if "<HOW>"=="on" then {

            <packet'exp>.flag = TRUE;

      } else {

            <packet'exp>.flag = FALSE;

      };

};

'>

This macro, as such, works fine. But let's see what in fact happens here. Whenever the macro is used in the code, it is replaced at compile time with the specified replacement code as is, and the replacement code is executed as is at the run time. So, the following code: "switch p off" is in fact replaced with this code:

 

if "off"=="on" then {

              p.flag = TRUE;

       } else {

              p.flag = FALSE;

       };

 

and this is what will be executed every time the action is reached by the flow of the program. This obviously does not make sense: the condition checks the syntax of the macro call itself (namely, whether it was used with "on" or with "off"), rather than something in its run-time context, thus it could have been checked and applied already at compile time. If the condition had been checked at the compile time, the replacement code (to be executed at run time) could have become simply:

p.flag = FALSE;

Furthermore, in more realistic cases (than this trivial example) this can also avoid unneeded performance overhead.

Alternatively, a define-as-computed macro would be the right choice in this case. In the example below, notice the check becomes part of the define-as-computed executable code, rather than part of the replacement code string it returns.

 

<'

define <switch_packet'action> "switch <packet'exp> (<HOW>on|off)" as computed {

      var res_string: string;

      if <HOW>=="on" then {

            res_string = "TRUE";

      } else {

            res_string = "FALSE";

      };

      // return the assembled code string

      return append(<packet'exp>, ".flag = ", res_string);

};

'>

To summarize, the following simple tips should help you make powerful macros:

  1. Don’t use a macro when there is no real need to do so (eg. Don't "simulate" methods with macros)
  2. Use define-as macros when appropriate. They are less powerful than define-as-computed, but more simple and readable.
  3. Don't perform syntactic checks on the macro call at run time; and if a define-as macro would cause this, use define-as-computed instead.

 

Yuri Tsoglin
Member of Consulting Staff
Specman R&D
Cadence Israel Development Center, Rosh Ha'ain

© 2025 Cadence Design Systems, Inc. All Rights Reserved.

  • Terms of Use
  • Privacy
  • Cookie Policy
  • US Trademarks
  • Do Not Sell or Share My Personal Information