## 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 ];
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:

```<'

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:

```<'

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;

if <any> != "" {
}
else {
};

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:

```<'
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(appendf("var temp_triangle : typeof(%s) = new;", <reg'exp>));

if not is_val {
if <any> != ""{
}
else {
};
};

if not is_val {
}
else {
};

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(appendf("var temp_triangle : typeof(%s) = new;", reg));

if operation == "write_reg" {
if not is_val {
if block != ""{
}
else {
};
};
}
else if operation == "write_reg_fields" {
los.add(appendf("temp_triangle = new with %s;", block));
};

if operation != "write_reg" or not is_val {
}
else {
};
}
else {
los.add(appendf("if %s == NULL {", reg));
};

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>);
};

{
};
'>
```

Here's the write_reg_fields flavor in action:

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

And here's the read_reg flavor in action:

```<'
body() @driver.clock is only {
};
};
'>
```

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(appendf("var temp_reg : typeof(%s) = new;", reg));

if operation == "write_reg" {
if not is_val {
if block != ""{
}
else {
};
};
}
else if operation == "write_reg_fields" {
los.add(appendf("temp_reg = new with %s;", block));
};

los.add(appendf("if %s == NULL {", reg));
los.add( append("reg_kind_str = appendf(\"%s%d\", \$1, ", idx, ");"));

if operation != "write_reg" or not is_val {
}
else {
};
}
else {
};

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>);
};

{
};
'>
```

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

```<'

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:

```<'
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:

```<'
body() @driver.clock is only {
for i from 0 to 2 {