Eriks answer fits quite well if you want to put the access restriction logic in the main bank, in his case this is bank z
, and then just add meta data in bank x
and y
describing access path.
However, an alternative solution would be to do the restrictive logic on bank x
and y
and only forward the accesses to bank z if it is allowed on the specific interface. That way, the logic in bank z
remains clean and does not have to consider any special access interfaces.
The example code below handles restrictive access on register level and not on field level. Next example will expand to also include field level.
template register_accessor is register {
param configuration = "none";
method write_register(uint64 value, uint64 enabled_bytes, void *aux) {
try {
z.write(offset, value, enabled_bytes, NULL);
} catch {
}
}
method read_register(uint64 enabled_bytes, void *aux) -> (uint64) {
try {
return z.read(offset, enabled_bytes, NULL);
} catch {
return 0;
}
}
}
bank x {
register reg0 @ 0x0 size 2 is register_accessor;
}
bank y {
register reg1 @ 0x2 size 2 is register_accessor;
}
bank z {
register reg0 @ 0x0 size 2;
register reg1 @ 0x2 size 2;
}
In the example above, an access on interface x
only has access to reg0
in bank z
and an access on interface y
only has access to reg1
in bank z
.
But the question also includes access restrictions on field level which makes it a bit more complicated but perfectly doable.
Here is an example of how that could be done with the same design pattern.
template field_accessor is (field) {
shared method read_only() -> (bool);
}
template read_only_access is (field_accessor) {
shared method read_only() -> (bool) { return true; }
}
template register_accessor is register {
param configuration = "none";
method write_register(uint64 value, uint64 enabled_bytes, void *aux) {
foreach f in (each field_accessor in (this)) {
if (f.read_only())
enabled_bytes[f.lsb + f.bitsize - 1:f.lsb] = 0;
}
try {
z.write(offset, value, enabled_bytes, NULL);
} catch {
}
}
method read_register(uint64 enabled_bytes, void *aux) -> (uint64) {
try {
return z.read(offset, enabled_bytes, NULL);
} catch {
return 0;
}
}
}
bank x {
register regX @ 0x0 size 4 is register_accessor {
field a @ [15:0] is read_only_access;
field b @ [31:16];
}
}
bank y {
register regX @ 0x0 size 4 is register_accessor {
field a @ [15:0];
field b @ [31:16] is read_only_access;
}
}
bank z {
register regX @ 0x0 size 4 {
field a @ [15:0];
field b @ [31:16];
}
}
In this example a read access of register regX
is allowed on both interfaces x
and y
. However, on interface x
it is not possible to write to field a
and on interface y
it is not possible to write to field b
. The template register_accessor
masks the bits corresponding to the read-only field before forwarding the write to bank z
. You can create as many interfaces as you want without having to modify any logic in the storage bank (bank z
in this example).