‘e’ language is a very powerful language which allows us to make incredible things with its native syntax. It is difficult to reach a point in which you want to do something not supported by the language. But, if you do, ‘e’ language has another trick up its sleeves: define … as computed.
In this post I will show you a way in which you can use define … as computed to hyper boost the capabilities of ‘e’ language.
Problem
Let’s imagine that you have an I2C environment with to relevant fields inside. The unit looks something like this:
unit cfs_i2c_env {
//Environment name
name : cfs_i2c_env_name;
//Environment kind
kind : cfs_i2c_env_kind;
run() is also {
messagef(LOW, "do something");
};
};
This environment is instantiated many times, and the code might look something like this:
extend sys {
env_0 : ENV_0 BAROMETER cfs_i2c_env is instance;
env_1 : ENV_1 MOTOR cfs_i2c_env is instance;
env_2 : ENV_2 THERMOMETER cfs_i2c_env is instance;
env_3 : ENV_3 SWITCH cfs_i2c_env is instance;
env_4 : ENV_4 BAROMETER cfs_i2c_env is instance;
env_5 : ENV_5 MOTOR cfs_i2c_env is instance;
env_6 : ENV_6 THERMOMETER cfs_i2c_env is instance;
env_7 : ENV_7 SWITCH cfs_i2c_env is instance;
env_8 : ENV_8 BAROMETER cfs_i2c_env is instance;
env_9 : ENV_9 MOTOR cfs_i2c_env is instance;
};
What we want is to extend some of those instances and change the behavior of their run() method based on the following rule:
Environments with names ENV_4, ENV_5, ENV_6, ENV_7 and ENV_8 must print from their run() method the string “Do something else”. However, if any of these environments are of kind BAROMETER or THERMOMETER then their run() method must print “Do nothing”.
The Manual Solution
The obvious solution is to write extensions in which we implement the requirement. The problem is that this will leave us with a lot of repetitive code. Bellow there is the extension for ENV_4. Keep in mind that we should not assume a fixed kind associated with a particular environment name, so we need to handle both kinds: BAROMETER and THERMOMETER. The same code must be repeated 4 other times for ENV_5, ENV_6, ENV_7 and ENV_8. That’s a lot of code!
extend ENV_4 cfs_i2c_env {
run() is only {
messagef(LOW, "Do something else");
};
when BAROMETER {
run() is only {
messagef(LOW, "Do nothing");
};
};
when THERMOMETER {
run() is only {
messagef(LOW, "Do nothing");
};
};
};
The “define … as computed” Solution
Because the above solution involves a lot of repetitive code we can make use of “define … as computed” to generate that code in some for loops. The solution would look like this:
my_for <ENV_INDEX> from 4 to 8 {
extend ENV_<ENV_INDEX> cfs_i2c_env {
run() is only {
messagef(LOW, "Do something else");
};
my_for <KIND_INDEX> in BAROMETER, THERMOMETER {
when <KIND_INDEX> {
run() is only {
messagef(LOW, "Do nothing");
};
};
};
};
};
Notice that we have two types of for loops:
- one for which its index (e.g. <ENV_INDEX>) goes from 4 to 8
- another for which its index (e.g. <KIND_INDEX>) goes through a list of possible values: {BAROMETER, THERMOMETER}
The code is pretty compact!
To create these “define … as computed” macros is very simple.
Define “for … from … to …” Macro
The code bellow shows the implementation of the “for … from … to …” style of for loop:
define <from_to_statement'statement> "my_for <iterator'any> from <start_index'any> to <end_index'any> <code'block>" as computed {
result = "";
var start_index := str_expand_dots(<start_index'any>).as_a(uint);
var end_index := str_expand_dots(<end_index'any>).as_a(uint);
var code := str_expand_dots(<code'block>);
var iterator := str_expand_dots(<iterator'any>);
for i from start_index to end_index {
result = appendf("%0s %0s;", result, str_replace(code, iterator, appendf("%d", i)));
};
return appendf("%s;", result);
};
Notice that its syntactic category is “statement” because with it we already know that we will do with it a class extension.
In it we have a simple for loop in which we replace the provided iterator (e.g. <ENV_INDEX>) with all the values from our for loop (notice <start_index’any> and <end_index’any> arguments of the macro).
To be able to call this macro also in other parts of our code we can duplicate it with different syntactic categories (e.g. “struct_member”).
Define “for … in …” Macro
The code bellow shows the implementation of the “for … in …” style of for loop:
define <for_in_struct_member'struct_member> "my_for <iterator'any> in <values'exp>,... <code'block>" as computed {
result = "";
var values := <values'exps>;
var code := str_expand_dots(<code'block>);
var iterator := str_expand_dots(<iterator'any>);
for each (value) in values {
result = appendf("%0s %0s;", result, str_replace(code, iterator, str_expand_dots(value)));
};
return appendf("%s;", result);
};
Notice that its syntactic category is “struct_member” because with it we already know that we will use it to define a “when” aspect of our class.
In it we have a simple for loop in which we replace the provided iterator (e.g. <KIND_INDEX>) with all the values from our list of possible values (notice <values’exp> argument of the macro).
To be able to call this macro also in other parts of our code we can duplicate it with different syntactic categories (e.g. “statement”).
That’s it!
You can imagine that such macros can give us a lot of freedom in our code!
If you want to see this in action I’ve created an EDA Playground project showcasing this idea.
Hope it helps!
If you want to gain an in-depth knowledge on how to do module level verification using SystemVerilog and UVM language then checkout my Udemy course called “Design Verification with SystemVerilog/UVM“






Hi Cristian,
Great post, thank you for sharing!
Are you familiar with anything similar to “define as… compute” in System-Verliog?
I find the macros in System-Verilog to be very simple and not that powerful.
Hi Michael,
It would be great to have this also in SystemVerilog but I am not aware of something similar.