Showing posts with label e (IEEE 1647). Show all posts
Showing posts with label e (IEEE 1647). Show all posts

Wednesday, December 9, 2015

Fun and Games with CRV: Einstein's Puzzle (Revisited)

Two weeks ago, Aurelian from AMIQ published a post on how to solve the so-called Einstein's puzzle using e. At the end, he challenged us readers to try and improve on his solution. Not being one to shy away, I rolled up my sleeves and got to work.

He started out by defining a struct to hold the information about a resident:

<'
struct resident {
  nationality : nationality_t;
  house_color : house_color_t;
  cigarette : cigarette_t;
  pet : pet_t;
  drink : drink_t;
};
'>

He gave up on this idea after he tried to constrain the residents to have unique nationalities, house colors, etc. Here's what he tried:

<'
struct neighborhood {
  keep residents.nationality.is_a_permutation(all_values(nationality_t));
  // ...
};
'>

This looks reasonable, right? Why didn't it work then? That's because even though residents.nationality returns a list of all residents' nationalities, it isn't generative. I didn't find anything normative in the Language Reference or the Generation User Guide that explicitly states this, but this seems to be the case. The proper way of ensuring all nationalities are unique is to use the all_different(...) pseudo-method:

<'
struct neighborhood {
  keep residents.all_different(it.nationality);
  // ...
};
'>

Now that we've got our infrastructure set up, it's time to start converting the 15 facts into code. There are three types of facts given to us. The first type only involves individual residents. For example, we know that the one living in the red house is English. This can be represented as a constraint inside the resident class itself:

<'
extend resident {
  keep nationality == ENGLISH => house_color == RED; // #1
};
'>

There are seven more such constraints, but I won't show them here, since they are very similar to this one. You can find the complete code on SourceForge.

The second type of fact we know about our residents describes a characteristic of the resident living in a particular house. For example, we know that the resident in the first house is Norwegian. This kind of constraint needs to be added to the neighborhood:

<'
extend neighborhood {
  keep residents[0].nationality == NORWEGIAN; // #9
};
'>

The third type of fact gives hints about residents in relation to their neighbors. For example, we know that the green house is located to the left of the white house. Here we need to loop over all elements of the list. Per definition, the green house can't be the last one in the list, because then it wouldn't be located to the left of anything:

<'
extend neighborhood {
  // #4
  keep for each (resident) in residents {
    resident.house_color == GREEN =>
      index < 4 and residents[index+1].house_color == WHITE;
  };
};
'>

The fact that the Blend smoker lives next to the cat owner is a bit more involved, but it follows the same principle. This means that the Blend smoker is either located to the left (which is the same situation as before) or is located to the right, in which case the respective house can't be the first one:

<'
extend neighborhood {
  // #10
  keep for each (resident) in residents {
    resident.cigarette == BLEND =>
      index < 4 and residents[index+1].pet == CAT or
        index > 0 and residents[index-1].pet == CAT;
  };
};
'>

Since we have three more such constraints to write, we could save ourselves some typing by defining a macro:

<'
define <neighbors'statement> "<first'exp> neighbors <second'exp>" as {
  extend neighborhood {
    keep for each (resident) in residents {
      resident.<first'exp> =>
        index < 4 and residents[index+1].<second'exp> or
          index > 0 and residents[index-1].<second'exp>;
    };
  };
};
'>

The macro "call" would look like this:

<'
cigarette == BLEND neighbors pet == CAT; // #10
'>

With these three types of constraints we can model all fifteen facts and solve the puzzle. I was pretty surprised that it worked the first time and gave the right solution - the German keeps the fish. When I solved the zebra puzzle in SystemVerilog (which is essentially the same puzzle as this one, just with slightly different facts about the residents) I ran into problems because I used the implication operator. Basically, saying that the English resident lives in the red house doesn't just mean that nationality == ENGLISH => house_color == RED, but that at the same time house_color == RED => nationality == ENGLISH. There isn't any equivalence operator (also called double implication) in e, but this can be expressed using the equality operator, "==":

<'
extend resident {
  keep (nationality == ENGLISH) == (house_color == RED); // #1
};
'>

I've shown you my solution. Now it's time to pass the baton to you and challenge you to improve it even more.

Friday, January 30, 2015

vr_ad Twin Registers

In their quest to come up with ever more efficient architectures, concept engineers sometimes do crazy things. Twin registers (also called multiview registers) are one of them: when accessing an address location, it sometimes behaves like it's one register, while at other times it's like there's another register residing there.

While this may be fairly straightforward to implement in hardware (throw in a couple of multiplexers here and there), it's going to be a bit more involved for the verification engineer trying to check that it works as it should. Luckily vr_ad already provides an example of how to do this. "Then why are you writing this post?" you may be asking yourself. Well, the thing with that example is that it's not fully explained how the pieces fit together and there are also some mistakes in it.

There are two cases that come to mind where I've encountered twin registers. The first case is when the layout of the register is different depending on whether we're doing a read or a write. The second case is when the layout of the register depends on some external condition, such as the value of a certain signal, the state of the device or a specific configuration setting in some other register.

Let's start by looking at the first case. The code we'll look at is based on the vr_ad example. We have to define two different registers, one for each layout:

<'
reg_def STATUS {
reg_fld VALID : uint(bits : 1) : R : 0;
reg_fld DONE : uint(bits : 1) : R : 0;
};

reg_def CONTROL {
reg_fld SETVALID : uint(bits : 1) : W : 0;
reg_fld CLRVALID : uint(bits : 1) : W : 0;
reg_fld CLRDONE : uint(bits : 1) : W : 0;
reg_fld START : uint(bits : 1) : W : 0;
};
'>

Notice that we don't instantiate them in any register file, as that would lead to an error message because they would overlap. When reading the register the value we get will represent the STATUS layout, whereas any values we write will be interpreted according to the CONTROL layout.

Since we can't instantiate these two registers inside the register file, we'll need to define a dummy register to take their place:

<'
reg_def STATUS_CONTROL_PROXY {
reg_fld DATA : uint(bits : 32);

set_static_info() is also {
set_compare_mask(0);
};
};
'>

This register will only be used to forward the access operations to the appropriate twin, so we'll set its compare mask to 0. But to be able to forward these operations, we need to instantiate the twins somewhere. We'll do this inside a dummy register file:

<'
extend vr_ad_reg_file_kind : [ STATUS_CONTROL_TWINS ];
extend STATUS_CONTROL_TWINS vr_ad_reg_file {
status : STATUS vr_ad_reg;
control : CONTROL vr_ad_reg;

keep size == 8;

add_registers() is also {
add_with_offset(0x0, status);
add_with_offset(0x4, control);
};
};
'>

The offsets where we add the twins don't matter; they just need to be different. Whenever the address location where the twins reside in the hardware gets accessed, the proxy register will be accessed inside the vr_ad model. We can connect this to the dummy register file using indirect_access(...) and based on the access information we can access either STATUS or CONTROL:

<'
extend STATUS_CONTROL_TWINS vr_ad_reg_file {
indirect_access(direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
var data := ad_item.as_a(vr_ad_reg).get_access_data();
if direction == WRITE {
control.update(0, data, {});
}
else {
compute status.compare_and_update(0, data);
};
};
};
'>

When we're writing to the shared address we update the CONTROL register. When we're reading, we need to check that the data we got from the device matches the one from our model's STATUS register. In the main register file we just need to instantiate the proxy register and the dummy register file and connect them:

<'
extend EXAMPLE vr_ad_reg_file {
status_control_proxy : STATUS_CONTROL_PROXY vr_ad_reg;
status_control_twins : STATUS_CONTROL_TWINS vr_ad_reg_file;

add_registers() is also {
add_with_offset(0x0, status_control_proxy);
status_control_proxy.attach(status_control_twins);
};
};
'>

Notice that we didn't add the dummy register file to any specific offset inside the main register file. Doing that would imply that the twins are accessible at other offsets than the one where we've added the proxy register. The vr_ad example shows the dummy register file being instantiated outside of the main one (for whatever reason), but I chose to do it here since it makes everything better encapsulated.

We've figured out how to handle our register modeling, but we're not done yet. We need to handle the stimulus part. Since the twins' register file wasn't mapped anywhere inside the address space, vr_ad won't know what address to access if we want to write to the CONTROL register, for example. For this reason we have the indirect sequence mechanism. An indirect sequence is a sequence that gets started automatically when we try to access an unmapped element we've previously associated with it. In our case we'll associate an indirect sequence with the dummy register file. Whenever we try to access an element of this register file (i.e. the CONTROL or the STATUS register), the indirect sequence which gets called will convert this access to the unmapped item to an access to the proxy register (which is mapped inside the main register file):

<'
extend vr_ad_sequence_kind : [ACCESS_TWIN_REG];
extend ACCESS_TWIN_REG INDIRECT vr_ad_sequence {
!proxy_reg : STATUS_CONTROL_PROXY vr_ad_reg;

body() @driver.clock is only {
if direction == WRITE {
write_reg proxy_reg val reg.read_reg_rawval();
} else {
read_reg proxy_reg;
if not reg.in_model {
reg.write_reg_rawval(proxy_reg.read_reg_rawval());
};
};
};
};
'>

In the code snippet above, reg is a predefined field which will hold the sequence register (i.e. the register we call write_reg on). This register has been generated based on the constraints passed to the macro and contains the value we want to write. We pass it as the val argument to a write_reg call to the proxy register. In the vr_ad example, the value to be written is extracted using the read_reg_val() method, but this approach won't work in our case because the CONTROL register contains write-only fields. To extract the write value we have to use the read_reg_rawval() method to bypass the access mask. When doing a read, aside from calling the read_reg macro on the proxy reg we also want to update the sequence register so that we can query its value. The same comment related to the access mask applies here as well, hence the use of read_reg_rawval(). Also, in case the register we pass in to the initial macro is an instance from inside the model, we don't want to mess with the update mechanism, because this would cause side effects from the pre/post hooks to be applied and potentially ruin the modeled value.

Somewhere in the testbench we need to connect the indirect sequence to the dummy register file and tell the address map that we have some items that can't be accessed directly:

<'
extend sys {
connect_pointers() is also {
addr_map.add_unmapped_item(reg_file.status_control_twins);
reg_file.status_control_twins.set_indirect_seq_name(ACCESS_TWIN_REG);
};
};
'>

I don't get why the call to add_unmapped_item(...) has to be done on the address map and couldn't just be done inside the main register file. If I were to instantiate the EXAMPLE register file inside another register file, I'd have to call add_unmapped_item(...) again on the new map. If this call were encapsulated at the place I instantiate the dummy register file, doing vertical reuse would be easier. The problem is especially painful if I have a lot of twin registers from different register models I want to reuse.

For the test writer, accessing a twin register is no different than accessing a normal one:

<'
extend MAIN vr_ad_sequence {
!control : CONTROL vr_ad_reg;

body() @driver.clock is only {
write_reg_fields control { .SETVALID = 1 };
};
};
'>

The flow of execution is the following:

  1. control gets filled with the proper value, based on the argument block; in our case this value would be 0x8
  2. the indirect sequence gets called with control being passed in as its reg field
  3. a write_reg to the proxy register happens with the value stored in control
  4. the monitor notifies the register model of an access to address 0x0
  5. the model updates the proxy register (which is located at address 0x0)
  6. the proxy register notifies the dummy register file (via indirect_access(...)) that it has been accessed
  7. the dummy register file updates the value of the CONTROL register

As you can see, the indirect sequence and the indirect_access(...) method are complementary to each other.

Another case of twin registers I'd like us to look at is when the layout of a specific register depends on a configuration field in another register. Let's take as an example a register specifying what type of math operation we want to perform and another register that stores the arguments of that operation:

<'
type math_op_t : [NOP, INC, DEC, ADD, SUB];

reg_def MATH_OP EXAMPLE 0x4 {
reg_fld OP : math_op_t : RW : NOP;
};

reg_def UNARY_ARG {
reg_fld ARG : byte : RW : 0;
};

reg_def BINARY_ARGS {
reg_fld ARG0 : byte : RW : 0;
reg_fld ARG1 : byte : RW : 0;
};
'>

When we want to perform a increment or a decrement, we can only pass in one argument, so the UNARY_ARG layout applies. When we want to perform an addition or a subtraction, we need to pass in two arguments, so the BINARY_ARGS layout applies. When there is no operation set, the register isn't accessible.

As before, we'll need to define a proxy register:

<'
reg_def ARGS_PROXY {
reg_fld DATA : uint(bits : 32);

set_static_info() is also {
set_compare_mask(0);
};
};
'>

We also need to define a dummy register file to store the two layouts, together with its indirect_access(...) method:

<'
extend vr_ad_reg_file_kind : [ ARGS_TWINS ];
extend ARGS_TWINS vr_ad_reg_file {
unary_arg : UNARY_ARG vr_ad_reg;
binary_args : BINARY_ARGS vr_ad_reg;

keep size == 8;

add_registers() is also {
add_with_offset(0x0, unary_arg);
add_with_offset(0x4, binary_args);
};


!p_math_op : MATH_OP vr_ad_reg;

// this is called whenever the proxy reg is accessed
indirect_access(direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
var data := ad_item.as_a(vr_ad_reg).get_access_data();

if direction == WRITE {
unary_arg.update(0, data, {});
binary_args.update(0, data, {});
}
else {
case p_math_op.OP {
[INC, DEC] : { compute unary_arg.compare_and_update(0, data) };
[ADD, SUB] : { compute binary_args.compare_and_update(0, data) };
[NOP] : { check that %{data} == 0 };
};
};
};
};
'>

In order to know what operation is currently configured and what layout to apply, we need a reference to the MATH_OP register. Because the two twins also share their storage elements, we need to update both when doing a write. When doing a read, we select the appropriate register based on the math operation.

In the main register file we instantiate the one containing the twins and do the necessary connections:

<'
extend EXAMPLE vr_ad_reg_file {
args_proxy : ARGS_PROXY vr_ad_reg;
args_twins : ARGS_TWINS vr_ad_reg_file;

add_registers() is also {
add_with_offset(0x0, args_proxy);
args_proxy.attach(args_twins);
args_twins.p_math_op = math_op;
};
};
'>

The indirect sequence will look very similar to the one we defined in the first case:

<'
extend vr_ad_sequence_kind : [ACCESS_TWIN_REG];
extend ACCESS_TWIN_REG INDIRECT vr_ad_sequence {
!proxy_reg : ARGS_PROXY vr_ad_reg;

body() @driver.clock is only {
// a sanity check to make sure I'm not trying to access the wrong twin
var math_op := driver.addr_map.get_reg_by_kind(MATH_OP).as_a(MATH_OP vr_ad_reg);
assert(
(reg.kind == UNARY_ARG => math_op.OP in [INC, DEC]) and
(reg.kind == BINARY_ARGS => math_op.OP in [ADD, SUB])
)
else error(appendf("Cannot access %s when math_op is %s",
reg.kind.as_a(string), math_op.OP.as_a(string)));

if direction == WRITE {
write_reg proxy_reg val reg.read_reg_rawval();
} else {
read_reg proxy_reg;
if not reg.in_model {
reg.write_reg_rawval(proxy_reg.read_reg_rawval());
};
};
};
};
'>

What I've also added here are some sanity checks. It doesn't make much sense to access the BINARY_ARGS twin when doing an increment, for example, so the assert statement can warn the test writer that he may be doing something unintended.

The final piece of the puzzle is connecting the indirect sequence to the dummy register file:

<'
extend sys {
connect_pointers() is also {
addr_map.add_unmapped_item(reg_file.args_twins);
reg_file.args_twins.set_indirect_seq_name(ACCESS_TWIN_REG);
};
};
'>

Now we can access the twins like any other register:

<'
extend MAIN vr_ad_sequence {
!math_op : MATH_OP vr_ad_reg;
!unary_arg : UNARY_ARG vr_ad_reg;

body() @driver.clock is only {
write_reg_fields math_op { .OP = INC };
write_reg unary_arg { .ARG == 5 };
};
};
'>

As you can see, setting up the register model when we have twin register requires a bit more work. Luckily, vr_ad already provides a mechanism for handling this. Once we have all the plumbing in place, accessing twin registers is done in the same way as accessing any vanilla register. If you want to try it out yourselves, you can find the full code on SourceForge.

Do you have any other scenario where you've had to verify twin register? I'd love to hear about it in the comments section below.

Sunday, December 14, 2014

Experimental Cures for Flattened Register Definitions in vr_ad, Part 2

We've already talked about how to handle flattened register definitions from a modeling point of view in this post. This other post also showed us that accessing multiply instantiated registers is a bit of a challenge, even when they are defined properly. Let's add the missing piece of the puzzle now and have a look at how to easily access flattened registers.

Let's apply the same idea from the previous post and use macros. This post is actually less experimental as I've already used this approach on my current project.

Let's start out with the register definitions. We'll use our trusty graphics processing engine that can handle triangles:

<'
extend vr_ad_reg_file_kind : [ GRAPHICS ];
extend GRAPHICS vr_ad_reg_file {
  keep size == 256;
  post_generate() is also {
    reset();
  };
};

reg_def TRIANGLE0 GRAPHICS 0x00 {
  reg_fld SIDE0 : uint(bits : 8);
  reg_fld SIDE1 : uint(bits : 8);
  reg_fld SIDE2 : uint(bits : 8);
};

reg_def TRIANGLE1 GRAPHICS 0x4 {
  reg_fld SIDE0 : uint(bits : 8);
  reg_fld SIDE1 : uint(bits : 8);
  reg_fld SIDE2 : uint(bits : 8);
};

reg_def TRIANGLE2 GRAPHICS 0x8 {
  reg_fld SIDE0 : uint(bits : 8);
  reg_fld SIDE1 : uint(bits : 8);
  reg_fld SIDE2 : uint(bits : 8);
};
'>

Here's how we would access a single triangle:

<'
extend MAIN vr_ad_sequence {
  !triangle0 : TRIANGLE0 vr_ad_reg;
  
  body() @driver.clock is only {
    write_reg triangle0 {
      .SIDE0 == 1;
      .SIDE1 == 2;
      .SIDE2 == 3;
    };
  };
};
'>

If we would want to access TRIANGLE1 we would need to use a field of type TRIANGLE1 vr_ad_reg, but the code would otherwise stay the same. We don't need to use any static_item, because each register type is unique (which is exactly our problem). You can already see that creating a generic sequence that can handle any triangle is going to become a mess of case statements and doubled up code for the constraints.

We can fix that by using a macro on top of write_reg. We can call this macro on any field of type TRIANGLE0, TRIANGLE1, etc. and pass the desired instance as an argument. This is how we would write to TRIANGLE1:

<'
extend MAIN vr_ad_sequence {
  !triangle : TRIANGLE0 vr_ad_reg;
  
  body() @driver.clock is only {
    write_triangle_reg 1 triangle {
      .SIDE1 == 1;
    };
  };
};
'>

The macro would need to generate triangle with the appropriate constraint and execute an access to TRIANGLE1. Here's the macro body:

<'
define <write_triangle_reg'action>
  "write_triangle_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  var los : list of string;
  los.add(        "{");
  los.add(appendf("var temp_triangle : typeof(%s);", <reg'exp>));
  
  if <any> != "" {
    los.add(appendf("gen temp_triangle keeping %s;", <any>));
  }
  else {
    los.add(        "gen temp_triangle;");
  };
  
  los.add(        "var access_triangle : vr_ad_reg = new with {;");
  los.add( append(".kind = appendf(\"TRIANGLE%d\", ", <idx'exp>, ").as_a(vr_ad_reg_kind);"));
  los.add(        "};");
  los.add(        "write_reg access_triangle val temp_triangle.read_reg_rawval();");
  los.add(        "};");
  
  result = str_join(los, "\n");
};
'>

We generate a temporary register of the same type as the one we get passed in. Since all triangles have the same fields, it's irrelevant if the triangle field we passed in is of type TRIANGLE0, TRIANGLE1, etc. For the call to write_reg we need to use a variable of the appropriate type. We extract this type from the <idx'exp> argument by concatenating it to the string "TRIANGLE" and converting that to a vr_ad_reg_kind. As the write value we use the contents of the temporary field we just generated.

This should remind us that in this form our macro won't work in all cases, particularly when trying to use the val <val> syntax:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_triangle_reg 1 triangle val 0x010101;
  };
};
'>

This will give us a cryptic compile error. To do away with it we need to fix our macro body:

<'
define <write_triangle_reg'action>
  "write_triangle_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  var los : list of string;
  var is_val : bool = str_match(<any>, "/^val /");
  
  los.add(        "{");
  los.add(appendf("var temp_triangle : typeof(%s) = new;", <reg'exp>));
  
  if not is_val {
    if <any> != ""{
      los.add(appendf("gen temp_triangle keeping %s;", <any>));
    }
    else {
      los.add(        "gen temp_triangle;");
    };
  };
  
  los.add(        "var access_triangle : vr_ad_reg = new with {;");
  los.add( append(".kind = appendf(\"TRIANGLE%d\", ", <idx'exp>, ").as_a(vr_ad_reg_kind);"));
  los.add(        "};");
  if not is_val {
    los.add(        "write_reg access_triangle val temp_triangle.read_reg_rawval();");
  }
  else {
    los.add(appendf("write_reg access_triangle %s;", <any>));
  };
  los.add(        "};");
  
  result = str_join(los, "\n");
};
'>

We need to filter out the case when using the val <val> syntax. In that case we don't need to generate our temporary register to use it as the write value. "This macro is getting a bit too complicated", you might say and you would be right, but this is the price we pay for not doing things properly from the start (i.e. not having flattened register definitions).

As we've previously seen, the code for the write_reg_fields and read_reg flavors of the macro will be pretty similar, so it makes sense to encapsulate and generalize the macro code inside a global function:

<'
extend global {
  vgm__access_triangle_reg_body(operation : string,
    idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    var is_val : bool = str_match(block, "/^val /");
    
    los.add(        "{");
    los.add(appendf("var temp_triangle : typeof(%s) = new;", reg));
    
    if operation == "write_reg" {
      if not is_val {
        if block != ""{
          los.add(appendf("gen temp_triangle keeping %s;", block));
        }
        else {
          los.add(        "gen temp_triangle;");
        };
      };
    }
    else if operation == "write_reg_fields" {
      los.add(appendf("temp_triangle = new with %s;", block));
    };
    
    los.add(        "var access_triangle : vr_ad_reg = new with {;");
    los.add( append(".kind = appendf(\"TRIANGLE%d\", ", idx, ").as_a(vr_ad_reg_kind);"));
    los.add(        "};");
    
    // writing/reading
    if operation != "read_reg" {
      if operation != "write_reg" or not is_val {
        los.add(        "write_reg access_triangle val temp_triangle.read_reg_rawval();");
      }
      else {
        los.add(appendf("write_reg access_triangle %s;", block));
      };
    }
    else {
      los.add(        "read_reg access_triangle;");
      los.add(appendf("if %s == NULL {", reg));
      los.add(appendf("%s = new;", reg));
      los.add(        "};");
      los.add(appendf("%s.write_reg_rawval(access_triangle.read_reg_rawval());", reg));
    };
    
    los.add(        "};");
    
    result = str_join(los, "\n");
  };
};
'>

Our macro bodies will just contain calls to this function:

<'
define <write_triangle_reg'action>
  "write_triangle_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_triangle_reg_body("write_reg", <idx'exp>, <reg'exp>, <any>);
};

define <write_triangle_reg_fields'action>
  "write_triangle_reg_fields <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_triangle_reg_body("write_reg_fields", <idx'exp>, <reg'exp>, <any>);
};

define <read_triangle_reg'action>
  "read_triangle_reg <idx'exp> <reg'exp>" as computed
{
  result = vgm__access_triangle_reg_body("read_reg", <idx'exp>, <reg'exp>);
};
'>

Here's the write_reg_fields flavor in action:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_triangle_reg 1 triangle val 0x010101;
  };
};
'>

And here's the read_reg flavor in action:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    read_triangle_reg 2 triangle;
  };
};
'>

What we have up to now works fine for triangles, but as you can remember our graphics engine can also process circles:

<'
reg_def CIRCLE0 GRAPHICS 0x10 {
  reg_fld RADIUS : uint(bits : 8);
};

reg_def CIRCLE1 GRAPHICS 0x14 {
  reg_fld RADIUS : uint(bits : 8);
};

reg_def CIRCLE2 GRAPHICS 0x18 {
  reg_fld RADIUS : uint(bits : 8);
};
'>

We need to generalize our macro to handle any type of register. We can use string matching to separate the register type from its instance number. For example, CIRCLE0 is composed of CIRCLE and 0. Once we extract the 0 from the end we can append the appropriate index given as an input to the macro. Here's a snippet that does exactly this:

<'
var reg_kind_str := reg.kind.as_a(string);
assert str_match(reg_kind_str, "/(.*)(\\d+)$/");
reg_kind_str = appendf("%s%d", $1, idx);
'>

We match everything until the end of the string, where we expect to see at least one numeric character. The first match group is stored in $1 (a built-in variable), to which we append the desired index. We integrate this code into the global function that returns the macro body:

<'
extend global {
  vgm__access_graphics_reg_body(operation : string,
    idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    var is_val : bool = str_match(block, "/^val /");
    
    los.add(        "{");
    los.add(appendf("var temp_reg : typeof(%s) = new;", reg));
    
    if operation == "write_reg" {
      if not is_val {
        if block != ""{
          los.add(appendf("gen temp_reg keeping %s;", block));
        }
        else {
          los.add(        "gen temp_reg;");
        };
      };
    }
    else if operation == "write_reg_fields" {
      los.add(appendf("temp_reg = new with %s;", block));
    };
    
    los.add(appendf("if %s == NULL {", reg));
    los.add(appendf("%s = new;", reg));
    los.add(        "};");
    los.add(appendf("var reg_kind_str := %s.kind.as_a(string);", reg));
    los.add(        "assert str_match(reg_kind_str, \"/(.*)(\\d+)$/\");");
    los.add( append("reg_kind_str = appendf(\"%s%d\", $1, ", idx, ");"));
    los.add(        "var access_reg : vr_ad_reg = new with {;");
    los.add(        ".kind = reg_kind_str.as_a(vr_ad_reg_kind);");
    los.add(        "};");
    
    // writing/reading
    if operation != "read_reg" {
      if operation != "write_reg" or not is_val {
        los.add(        "write_reg access_reg val temp_reg.read_reg_rawval();");
      }
      else {
        los.add(appendf("write_reg access_reg %s;", block));
      };
    }
    else {
      los.add(        "read_reg access_reg;");
      los.add(appendf("%s.write_reg_rawval(access_reg.read_reg_rawval());", reg));
    };
    
    los.add(        "};");
    
    result = str_join(los, "\n");
    print result;
  };
};
'>

As before, the macros just call this function:

<'
define <write_graphics_reg'action>
  "write_graphics_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_graphics_reg_body("write_reg", <idx'exp>, <reg'exp>, <any>);
};

define <write_graphics_reg_fields'action>
  "write_graphics_reg_fields <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_graphics_reg_body("write_reg_fields", <idx'exp>, <reg'exp>, <any>);
};

define <read_graphics_reg'action>
  "read_graphics_reg <idx'exp> <reg'exp>" as computed
{
  result = vgm__access_graphics_reg_body("read_reg", <idx'exp>, <reg'exp>);
};
'>

Here's the macro being used to write to CIRCLE1:

<'
extend MAIN vr_ad_sequence {
  !circle : CIRCLE0 vr_ad_reg;
  
  body() @driver.clock is only {
    write_graphics_reg 1 circle { .RADIUS == 5 };
  };
};
'>

One of the main reasons why we wanted a generic way of accessing the registers is, of course, being able to do loops. Using the macro we can write all triangle registers:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    for i from 0 to 2 {
      write_graphics_reg i triangle {
        .SIDE0 == 3;
        .SIDE1 == 3;
        .SIDE2 == 3;
      };
    };
  };
};
'>

We can also easily read all circle registers:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    for i from 0 to 2 {
      read_graphics_reg i circle;
    };
  };
};
'>

The macros could also be converted to operate in multiple dimensions (i.e. to handle multiple instances of the GRAPHICS register file, etc.) by just updating the extraction of the vr_ad_reg_kind variable. We won't look at that here. If you want to get started with them, you can find the code on SourceForge, including the complete testing harness I used for development.

Using this approach we can easily access flattened registers in a generic way, allowing us to write portable sequences to complement the nice reference models we've learned to code last time. We pay for having flattened definitions with more complicated macro code, but the internal implementation of these macros is easy to change once we swap out the flattened model for a correctly defined one.

Happy register accesses and see you next time!

Tuesday, November 25, 2014

Working with Multiple Instances of vr_ad Registers

The devices that we verify often have multiple instances of a certain register type. These registers are instantiated in a regular structure inside the design. In our tests, we want to be able to access each of them.

In vr_ad, accessing a register is typically done using the write/read_reg family of macros. These macros encapsulate the very flexible access mechanisms that the package provides into one convenient call. For unique registers, they just work, without having to pass in any additional options, but things get a bit more involved if the register type we are trying to access is instantiated multiple times. There are multiple scenarios where this can be the case. Let's have a look at them!

The first scenario that comes to mind is when there are multiple instances of the same register inside a register file. Let's take the example from the last post, with the graphics processing device:

<'
extend vr_ad_reg_file_kind : [ GRAPHICS ];
extend GRAPHICS vr_ad_reg_file {
  keep size == 512;
  post_generate() is also {
    reset();
  };
};


reg_def TRIANGLE {
  reg_fld SIDE0 : uint(bits : 8);
  reg_fld SIDE1 : uint(bits : 8);
  reg_fld SIDE2 : uint(bits : 8);
};


extend GRAPHICS vr_ad_reg_file {
  triangles[3] : list of TRIANGLE vr_ad_reg;
  
  add_registers() is also {
    for each (triangle) in triangles {
      add_with_offset(index * 0x4, triangle);
    };
  };
};
'>

In this case, we have three TRIANGLE registers, each at a different offset. Let's try to use the write_reg macro to access a triangle:

<'
extend MAIN vr_ad_sequence {
  !triangle : TRIANGLE vr_ad_reg;
  
  body() @driver.clock is only {
    write_reg triangle;
  };
};
'>

Trying to use write_reg like this will result in an error message, stating that there are multiple TRIANGLE registers inside the address map. It's impossible for the macro to know exactly which instance we want. We can solve this ambiguity in two ways.

The first way is by using the less known operation generate block argument to the macro (which is optional). We get the exact instance of the model register we want and pass that in as the static_item:

<'
extend MAIN vr_ad_sequence {  
  body() @driver.clock is only {
    var static_triangle := driver.addr_map.get_regs_by_kind(TRIANGLE)[0];
    write_reg { .static_item == static_triangle } triangle;
  };
};
'>

This lets the macro know exactly which instance we want to access.

The other way we can do this is by using the static triangle as the macro argument:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    var static_triangle :=
      driver.addr_map.get_regs_by_kind(TRIANGLE)[1].as_a(TRIANGLE vr_ad_reg);
    write_reg static_triangle { .SIDE0 == 1 };
  };
};
'>

In this case, we don't have to pass a static_item anymore, but we have to cast the static triangle to be able to access the TRIANGLE subtype's fields. In both cases, though, we have to write quite a bit of code just to access the register we want.

Instead of always getting the static_item ourselves and using that, why not encapsulate the whole operation inside a macro of our own? Since macros are anyway used to access vr_ad registers, it shouldn't cause any confusion for anybody. What we need is an extra macro argument, so our macro should look something like this: write_triangle_reg <inst'idx> <reg'exp> <reg_gen'block> (let's ignore the <op_gen'block> argument for simplicity).

Such a macro would just fetch the appropriate static_item based on the <inst'idx> argument and forward the other arguments to write_reg. This is how such a macro would look like:

<'
define <write_triangle_reg'action>
  "write_triangle_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  var los : list of string;
  los.add(        "{");
  los.add(        "var static_triangle :=");
  los.add(appendf("driver.addr_map.get_regs_by_kind(TRIANGLE)[%s];", <idx'exp>));
  los.add(appendf("write_reg { .static_item == static_triangle } %s %s;", <reg'exp>, <any>));
  los.add(        "};");
  
  result = str_join(los, "\n");
};
'>

To access the last triangle, we would simply do:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_triangle_reg 2 triangle { .SIDE1 == 1 };
  };
};
'>

Now this is all fine and dandy, but what if we also want to read the registers? We would need a second macro, whose expansion would be very similar to the first one's. We could generalize the macro body code inside a function that can return the result for both access operations. The vr_ad package defines such functions inside the global singleton, so if it's good enough for Cadence, then it's good enough for us:

<'
extend global {
  vgm__access_triangle_reg_body(operation : string,
    idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    los.add(        "{");
    los.add(        "var static_triangles :=");
    los.add(        "driver.addr_map.get_regs_by_kind(TRIANGLE);");
    los.add(appendf("assert %s in [0..static_triangles.size() - 1];", idx));
    los.add(appendf("%s { .static_item == static_triangles[%s] } %s %s;", operation, idx, reg, block));
    los.add(        "};");
    
    result = str_join(los, "\n");
  };
};
'>

This function can take any access operation (write_reg, read_reg or write_reg_fields - a lesser know brother of the two), together with the macro arguments and return the macro body. Declaring the macros just means calling this function with the appropriate arguments:

<'
define <write_triangle_reg'action>
  "write_triangle_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_triangle_reg_body("write_reg", <idx'exp>, <reg'exp>, <any>);
};

define <write_triangle_reg_fields'action>
  "write_triangle_reg_fields <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_triangle_reg_body("write_reg_fields", <idx'exp>, <reg'exp>, <any>);
};

define <read_triangle_reg'action>
  "read_triangle_reg <idx'exp> <reg'exp>" as computed
{
  result = vgm__access_triangle_reg_body("read_reg", <idx'exp>, <reg'exp>);
};
'>

Now we can also easily read any TRIANGLE register. Here's a read of the first triangle:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    read_triangle_reg 0 triangle;
  };
};
'>

A pretty useful thing we can also do with the macros is use them inside loops:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    for i from 0 to 2 {
      write_triangle_reg_fields i triangle { .SIDE2 = 1 };
    };
  };
};
'>

What we have up to now is great and all. It works perfectly for triangles, but let's throw circles into the mix as well:

<'
reg_def CIRCLE {
  reg_fld RADIUS : uint(bits : 8);
};


extend GRAPHICS vr_ad_reg_file {
  circles[5] : list of CIRCLE vr_ad_reg;
  
  add_registers() is also {
    for each (circle) in circles {
      add_with_offset(0x20 + index * 0x4, circle);
    };
  };
};
'>

The *_triangle_reg macros won't work on these registers so we're back to where we started. It would be very silly to create a new macro that can handle only circles, because that would become really unmaintainable if we were to add more and more shapes. What we need is a macro that can work with any register, regardless of type.

We already have the information about the register's kind inside the register itself. We just need to use that when calling get_regs_by_kind(...) to get the static register. We'll call this new macro write_graphics_reg and define a new function that implements all three versions of it:

<'
extend global {
  vgm__access_graphics_reg_body(operation : string,
    idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    los.add(        "{");
    los.add(        "var kind : vr_ad_reg_kind;");
    los.add(appendf("if %s == NULL { %s = new };", reg, reg));
    los.add(appendf("kind = %s.kind;", reg));
    los.add(        "var static_regs :=");
    los.add(        "driver.addr_map.get_regs_by_kind(kind);");
    los.add(appendf("assert %s in [0..static_regs.size() - 1];", idx));
    los.add(appendf("%s { .static_item == static_regs[%s] } %s %s;", operation, idx, reg, block));
    los.add(        "};");
    
    result = str_join(los, "\n");
  };
};
'>

The three macros will be:

<'
define <write_graphics_reg'action>
  "write_graphics_reg <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_graphics_reg_body("write_reg", <idx'exp>, <reg'exp>, <any>);
};

define <write_graphics_reg_fields'action>
  "write_graphics_reg_fields <idx'exp> <reg'exp>[ <any>]" as computed
{
  result = vgm__access_graphics_reg_body("write_reg_fields", <idx'exp>, <reg'exp>, <any>);
};

define <read_graphics_reg'action>
  "read_graphics_reg <idx'exp> <reg'exp>" as computed
{
  result = vgm__access_graphics_reg_body("read_reg", <idx'exp>, <reg'exp>);
};
'>

These new macros can work with triangles, circles and any new register types we might add:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_graphics_reg 2 triangle;
    read_graphics_reg 4 circle;
  };
};
'>

Let's look at a different scenario now. Let's assume that our DUT is composed of multiple slices, each of which is able to do some graphics processing independently of the others. An individual slice can only process one triangle and one circle, but there are many such slices. In this case, the register definitions would look like this:

<'
extend vr_ad_reg_file_kind : [ SLICE ];
extend SLICE vr_ad_reg_file {
  keep size == 8;
  post_generate() is also {
    reset();
  };
};


reg_def TRIANGLE SLICE 0x0 {
  reg_fld SIDE0 : uint(bits : 8);
  reg_fld SIDE1 : uint(bits : 8);
  reg_fld SIDE2 : uint(bits : 8);
};

reg_def CIRCLE SLICE 0x4 {
  reg_fld RADIUS : uint(bits : 8);
};


extend vr_ad_reg_file_kind : [ GRAPHICS ];
extend GRAPHICS vr_ad_reg_file {
  slices[3] : list of SLICE vr_ad_reg_file;
  
  keep size == 64;
  post_generate() is also {
    reset();
  };
  
  add_registers() is also {
    for each (slice) in slices {
      add_with_offset(index * 0x10, slice);
    };
  };
};
'>

As before, we can access a certain triangle or circle by getting the appropriate register instance from the address map and using that with write_reg, like we did in the previous scenario. What we can also do is get a handle to the appropriate register file and use that as the static_item:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    var static_reg_file := driver.addr_map.get_reg_files_by_kind(SLICE)[0];
    write_reg { .static_item == static_reg_file } triangle;
    write_reg { .static_item == static_reg_file } circle;
  };
};
'>

The same comment as above still applies; we'd have to do this operation every time we want to access a register, which can become tedious. Why not write a new macro, then?

Writing a macro that expands to the new code is pretty straightforward. Here's how the global function for that would look like:

<'
extend global {
  vgm__access_slice_reg_body(operation : string,
    idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    los.add(        "{");
    los.add(        "var static_slices :=");
    los.add(        "driver.addr_map.get_reg_files_by_kind(SLICE);");
    los.add(appendf("assert %s in [0..static_slices.size() - 1];", idx));
    los.add(appendf("%s { .static_item == static_slices[%s] } %s %s;", operation, idx, reg, block));
    los.add(        "};");
    
    result = str_join(los, "\n");
  };
};
'>

We'll then wrap calls to this method inside the actual macro declarations, which I won't show here. Using these macros, we can easily access any slice register:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_slice_reg 1 triangle { .SIDE1 == 1 };
    read_slice_reg 1 triangle;
    
    write_slice_reg_fields 1 circle { .RADIUS = 1 };
    read_slice_reg 1 circle;
  };
};
'>

We can also, for example, loop over all triangles in the design:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    for i from 0 to 2 {
      write_slice_reg i triangle val 0x010203;
    };
  };
};
'>

Things start to get fun when the design contains a mixture of the two cases we've looked at above. Our registers are organized in slices, but within one slice some registers may be instantiated multiple times:

<'
// TRIANGLE and CIRCLE reg_defs
// ...

reg_def SQUARE SLICE 0x20 {
  reg_fld SIDE : uint(bits : 8);
};


extend SLICE vr_ad_reg_file {
  triangles[3] : list of TRIANGLE vr_ad_reg;
  circles[4] : list of CIRCLE vr_ad_reg;
  
  add_registers() is also {
    for each (triangle) in triangles {
      add_with_offset(index * 0x4, triangle);
    };
    
    for each (circle) in circles {
      add_with_offset(0x10 + index * 0x4, circle);
    };
  };
};


extend vr_ad_reg_file_kind : [ GRAPHICS ];
extend GRAPHICS vr_ad_reg_file {
  slices[3] : list of SLICE vr_ad_reg_file;
  
  keep size == 256;
  post_generate() is also {
    reset();
  };
  
  add_registers() is also {
    for each (slice) in slices {
      add_with_offset(index * 0x40, slice);
    };
  };
};
'>

To access a square it's enough to just pass in the register file as a static_item (this is the same situation as in scenario no. 2):

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    var static_slice := driver.addr_map.get_reg_files_by_kind(SLICE)[0];
    write_reg { .static_item == static_slice } square;
  };
};
'>

To access a triangle (or a circle), though, we need to get the appropriate register instance (similarly to how we did it in scenario no. 1):

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    var static_slice := driver.addr_map.get_reg_files_by_kind(SLICE)[0];
    var static_triangle := static_slice.get_regs_by_kind(TRIANGLE)[0];
    write_reg { .static_item == static_triangle } triangle;
  };
};
'>

This means that our macro has to be able to handle both the register file and register indices. We can only process one square at a time, though, so in that case it doesn't make sense to pass in a register index (seeing as how there is only one instance per register file). The register index argument must therefore be optional. Starting top-down, this is how the definition of the write_graphics_reg macro would look like:

<'
define <write_graphics_reg'action>
  "write_graphics_reg <rf_idx'exp>[ <reg_idx'exp>] <reg'exp>[ <any>]" as computed
{
  result = vgm__access_graphics_reg_body("write_reg", <rf_idx'exp>, <reg_idx'exp>, <reg'exp>, <any>);
};
'>

The global function would then take both of these arguments into account to determine the static_item that gets passed:

<'
extend global {
  vgm__access_graphics_reg_body(operation : string,
    rf_idx : string, reg_idx : string, reg : string, block : string = "") : string is
  {
    var los : list of string;
    los.add(        "{");
    los.add(        "var static_slices := driver.addr_map.get_reg_files_by_kind(SLICE);");
    los.add(appendf("assert %s in [0..static_slices.size() - 1];", rf_idx));
    
    // multiply instantiated reg
    if reg_idx != "" {
      los.add(        "var kind : vr_ad_reg_kind;");
      los.add(appendf("if %s == NULL { %s = new };", reg, reg));
      los.add(appendf("kind = %s.kind;", reg));
      los.add(        "var static_regs :=");
      los.add(appendf("static_slices[%s].get_regs_by_kind(kind);", rf_idx));
      los.add(appendf("assert %s in [0..static_regs.size() - 1];", reg_idx));
    };
    
    los.add(appendf("%s {", operation));
    if reg_idx == "" {
      los.add(appendf(".static_item == static_slices[%s];", rf_idx));
    }
    else {
      los.add(appendf(".static_item == static_regs[%s];", reg_idx));
    };
    los.add(appendf("} %s %s;", reg, block));
    los.add(        "};");
    
    result = str_join(los, "\n");
  };
};
'>

Using the extended version of the macro we can now access both triangles and squares:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    write_graphics_reg 1 1 triangle;
    read_graphics_reg 2 0 triangle;
    
    write_graphics_reg 1 square;
    read_graphics_reg 2 square;
  };
};
'>

We can also loop over all squares (not shown) or double-loop over all triangles:

<'
extend MAIN vr_ad_sequence {
  body() @driver.clock is only {
    for i from 0 to 2 {
      for j from 0 to 2 {
        read_graphics_reg i j triangle;
      };
    };
  };
};
'>

As we've seen, by encapsulating the write/read_reg macros inside our own we can easily select the register instance that we want to access. We save a lot of tedious typing and duplicated code to get the appropriate static_item every time. We pay a small price when using macros though, as it becomes more difficult to track down syntax errors, but with proper documentation the disadvantages can be reduced. For more examples and detailed code, check out the SourceForge repository.

If your next project involves a lot registers with multiple instances, why not try this approach out? See you next time!