Passing the (initial) value of a shared variable to a generic during component instantiation

2.4k views Asked by At

I am trying to structure a testbench such, that each test case is represented by a record which holds all the parameters for the test case, e.g. input file names, generics to be used for DUT instantiation &cetera. The idea is that only one assignment needs to be changed to switch between different test cases.

type string_ptr is access string;
type test_case_t is record  
    input_file : string_ptr;  
    ...  
end record;  

shared variable test_case_1 : test_case_t := (  
    input_file => new string'("path to input file 1")
    ...  
);  
shared variable TEST_CASE : test_case_t := test_case_1;

dut: my_module
    generic map (
        file_name => TEST_CASE.input_file.all
    );
        ...

A string pointer is used for input_file since no unconstraint arrays are allowed inside a record type declaration (in this case, pre-VHDL-2008).
Due to the string pointer in test_case_t I have to use a shared variable for test_case_1.
However, when I try to simulate this testbench in Xilinx ISE/ISim 14.4 the simulator keeps using the default value of the generic and not the value I pass to it in the testbench (I assume this is a bug).

I tried to work around the issue with

constant INPUT_FILE : string := TEST_CASE.input_file.all;

dut: my_module
    generic map (
        file_name => INPUT_FILE
    );
    ...

This is basically using the initial value of a shared variable as the initial value for a constant. However, this assignment will crash the Xilinx compiler before simulation even started (another bug I assume).

At this point I no longer trust the Xilinx tools at all.
My question is if a shared variable can be used to pass generic values to a module as shown above (first code snippet)?
Also, is this usage of shared variable advisable for testbenches? I would have used a constant or signal for test_case_1 but due to the use string_ptr type in test_case_t this seems not to be allowed.

3

There are 3 answers

1
AudioBubble On BEST ANSWER

You can associate any fixed length string expression as the value of a generic constant as an actual in a generic map to a formal of type string with an unbound subtype indication.

To understand why this works we turn to elaboration of generics.

IEEE Std 1076-2008 (the LRM):

14.3 Elaboration of a block, package, or subprogram header

14.3.2 Generic clause

Elaboration of a generic clause consists of the elaboration of each of the equivalent single generic declarations contained in the clause, in the order given. The elaboration of a generic declaration establishes that the generic can subsequently be referenced.

6.5.6.2 Generic clauses (para 5)

The subtype denoted by a generic type is specified by the corresponding actual in a generic association list. It is an error if no such actual is specified for a given formal generic type (either because the formal generic is unassociated or because the actual is open).

(And note the actual of a generic is an expression and the association list is the generic map. Also note this doesn't match Brian's expectations on how the subtype indication for a string as a generic is determined from his Ada background.)

So that means that this:

entity my_module is
    generic ( constant file_name: string := "default_string");
end entity;

architecture foo of my_module is
begin
UNLABELED:
    process
    begin
        report "generic file_name = " & file_name;
        wait;
    end process;
end architecture;

entity foo is
end entity;

architecture fum of foo is

    type string_ptr is access string;
    type test_case_t is 
        record  
            input_file : string_ptr;  
        end record;  
    shared variable test_case_1 : test_case_t := 
            (input_file => new string'("""path to input file 1"""));  
    shared variable TEST_CASE : test_case_t := test_case_1;

    component my_module is
        generic (
            constant file_name: string := "default string"
        );
    end component;
begin
dut: my_module
    generic map (
        file_name => TEST_CASE.input_file.all
    );
end architecture;

is legal VHDL:

ghdl -a my_module.vhdl
ghdl -e foo
ghdl -r foo
my_module.vhdl:10:9:@0ms:(report note): generic file_name = "path to input file 1"

As well as this:

architecture fuu of foo is

    -- type string_ptr is access string;
    -- type test_case_t is
    --     record
    --         input_file : string_ptr;
    --     end record;
    -- shared variable test_case_1 : test_case_t :=
    --         (input_file => new string'("""path to input file 1"""));
    -- shared variable TEST_CASE : test_case_t := test_case_1;

    component my_module is
        generic (
            constant file_name: string := "default string"
        );
    end component;
begin
dut: my_module
    generic map (
        file_name => """some other string""" -- TEST_CASE.input_file.all
    );
end architecture;

Which gives:

ghdl -a my_module.vhdl
ghdl -e foo
ghdl -r foo
my_module.vhdl:10:9:@0ms:(report note): generic file_name = "some other string"

So this tells us a couple of things.

ISIM is not quite standard compliant in implementing generics. From the difference in the two architectures we can see that your particular ISIM version likely is not determining the subtype from the actual as per 6.5.6.2 quoted above noting you're trying to deal with string subtype (length). (You'd expect if someone were to point it out Xilinx would fix it).

You could try not providing a default expression in the generic clause in the entity declaration (noting component declarations should match). See 6.5.6.2 para 4:

The value of a generic constant may be specified by the corresponding actual in a generic association list. If no such actual is specified for a given formal generic constant (either because the formal generic is unassociated or because the actual is open), and if a default expression is specified for that generic, the value of this expression is the value of the generic. It is an error if no actual is specified for a given formal generic constant and no default expression is present in the corresponding interface element. It is an error if some of the subelements of a composite formal generic constant are connected and others are either unconnected or unassociated.

It's an either or situation, if neither it's an error.

There's also propagating a top level constant or generic (where supported by the tool implementation) requires passing every unique value through generics of each successive hierarchical block element. This would require the implementation supports 6.5.6.2 properly. (See the bit about 6.5.6.2 para 4 above).

And if you were really clever and the simulation and synthesis implementations supported configuration declarations properly there's likely a way to do something through configuration (providing two different use clauses with two different packages providing component declarations with two different actuals in generic clauses - this also requires the VHDL implementation also support 6.5.6.2, also see para 4 above). This method would allow you to simulate different configurations for different sets of actual values.

0
AudioBubble On

(Summarising and expanding from comments...) Give up trying to map generics to variables (or signals). At runtime, generics are constants. Their values are finalised at elaboration time.

You are correct that constants can't hold access types, and you can't hold variable length strings in records. This is one area where VHDL simplified too far from Ada - The latter has discriminated records, which handle variable length strings seamlessly, with no need of access types.

One approach would be to make a string table (array of strings - or if you need variable length strings, array of access string) and hold the index in the record.

Avoiding indirections is good. Abusing variables where constants are needed .. not so good. My quirk is a tendency to avoid access types unless there is a good reason to use them.

For example, here I might store space-padded fixed-length strings directly in the record, a "pad" function to populate the record member from an arbitrary string, and a "trim" function at their point of use. Compared with the string table, that would remove two levels of indirection.

subtype filename is string(1 to 100);

type test_case_t is record  
    input_file : filename;  
    ...  
end record;  

function pad (name : string ) return filename;
function trim (name : filename ) return string;

constant test_case_1 : test_case_t := (  
    input_file => pad("path to input file 1"),
    ...  
);  

file_open(f, trim(test_case_1.input_file));

And the generics can be of (sub)type filename. So, most of the declarations above would typically be in a package, used both in the main testbench and any entity that uses such a generic.

0
Jim Lewis On

Like Brian said, with generics you are stuck with one file per simulation run. This is ok for output files, however, for input files, you may want to read one, then read another, and so on.

I like using a shared variable/protected type object in the model, such as shown below:

library osvvm ;    
architecture Test1 of Model1 is 
  use osvvm.NamePkg.all ; 
  shared variable FileName : NamePType ; 
  file TestFile : TEXT ;
begin

  Functionality : process
  begin
    WaitForTransaction(. . .) ; 
    case ModelRec.Operation is
      . . . 
      when OPEN_NEW_FILE => 
        file_close(TestFile) ;
        file_open(Status, TestFile, FileName.Get, READ_MODE);

Then in the part of the testbench that is handing off files to this model, I use an external name to access the file:

architecture Test_Model1_1 of TestCtrl is
  alias Model1FileName is <<variable 
    .tbmemio.U_Model1.FileName : NamePType>> ;
begin
  . . . 
  Model1TestProc : process
  begin 
    . . .
    Model1FileName.set("Test1.txt") ; 
    . . . 

NamePkg is in the OSVVM library which can be downloaded from http://osvvm.org/.