3

I am having some trouble using aggregates in my VHDL test bench (short hand shown below).

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all    

entity TB is 
end entity;

architecture RTL of TB is
    -- constant(s)
    constant  CLK_PERIOD    : time                         := 10 ns; -- 100 MHz
    -- signal(s)
    signal    CLK           : std_logic                    := '0';
    signal    nRST          : std_logic                    := '1';
    signal    CONFIG_REG    : std_logic_vector(7 downto 0) := (others => '0');
begin
    -- clock driver
    CLK  <= NOT CLK after (CLK_PERIOD / 2.0);

    -- main process
    process
    begin
        -- reset driver...
        nRST  <= 
            '1', 
            '0' after (CLK_PERIOD * 1);

        -- set initial configuration...
        CONFIG_REG <= (
            6           =>  '1',     
            3 downto 2  => "01", 
            7 | 0       =>  '1', 
            others      =>  '0'
        );

        -- do test-bench stuff...

        -- update configuration...
        CONFIG_REG <= (
            6           =>  '0',     
            3 downto 2  => "10", 
            7 | 0       =>  '1', 
            others      =>  '0'
        );

        -- do more test-bench stuff...
    end process;
end architecture;

I really want to 'name' the parts of the configuration register so that it actually reads well.

So I want to say:

Bit[6]   = ENABLE
Bit[3:2] = MODE
Bit[7|0] = READY_DONE

I know that I can use a constant for the 6:

constant  ENABLE : integer := 6;

and then my code looks like this:

CONFIG_REG <= (
    ENABLE      =>  '1',     
    3 downto 2  => "01", 
    7 | 0       =>  '1',
    others      =>  '0'
);

But I've been stumped to try and get the range 3 downto 2 and the 7|0 named so that the code looks like:

CONFIG_REG <= (
    ENABLE      =>  '1',     
    MODE        => "01", 
    READY_DONE  =>  '1',
    others      =>  '0'
);

I thought I might be able to accomplish this using aliasing and I've been looking at the VHDL Golden Reference Guide (p.15) which has been pretty helpful as far as understanding aliasing and ranges go, but I still cannot figure out how to name a range itself or an 'or'(|) of values.

Currently I have the below 'hack', which I'm not really fond of...

constant ENABLE :  integer := 6;
alias    MODE   is CONFIG_REG(3 downto 2);
-- what to do about the OR though???

CONFIG_REG <= (
    ENABLE      =>  '1',     
    MODE'RANGE  => "01", 
    7 | 0       =>  '1',
    others      =>  '0'
);

I really want to make my test-bench readable so that when I look at it 6 mo. from now, I'll know what it's doing without having to go and figure out "Now what was bit[6] again???" or in the event I have to hand off my code to another developer, they can easily get an idea of what I was trying to accomplish.

Any help / advice would be appreciated on how to do this.

Thanks for reading.


EDIT: Fixed the 7 | 0 to be valid:

Invalid:

7 | 0       =>  "10",

Valid:

7 | 0       =>  '1',
bgarisn
  • 31
  • 4
  • In addition to the answer of @user1155120, you can also use arrays, whose index type is an enumeration type. – Paebbels Aug 02 '17 at 22:39
  • 2
    Is there a reason why you don't use record types? They have named fields. In case you also need a vectorized version of the data, you could very easily write conversion functions between vector and record types. – Renaud Pacalet Aug 03 '17 at 06:24
  • @RenaudPacalet I didn't use a record because it didn't make sense to me as far as what a register is (i.e. a collection of bits). Do you typically use records to represent registers in your designs instead of `std_logic_vector`? – bgarisn Aug 03 '17 at 13:03
  • 1
    Absolutely, everywhere. They are a wonderful tool to turn an obscure VHDL code into something that anybody can read and understand. The synthesizer will take care of transforming it into bits, don't worry. This is a very common practice. An AXI bus interface, for instance, can be entirely described using two records with meaningful names and field names: master-to-slave and slave-to-master. Remember that VHDL is a **high** level language, meaning that programmers should not be responsible for low-level details. – Renaud Pacalet Aug 03 '17 at 13:19

2 Answers2

3

Beware, your code is not valid: the aggregate notation 7 | 0 stands for groups of indices, not vectors. It should be associated std_logic values, not std_logic_vector. Moreover, in VHDL versions prior 2008, the aggregate notation 3 downto 2 should also be associated std_logic values:

-- set initial configuration...
CONFIG_REG <= (
    6           => '1',     
    3 downto 2  => '1', 
    7 | 0       => '0', 
    others      => '0'
);

In VHDL 2008 the association between choices that are discrete ranges and expressions of the type of the aggregate are now supported. So, the 3 downto 2 => "01" is OK in VHDL 2008. But as VHDL 2008 is still not fully supported by many synthesizers, you should probably be careful, unless this code is not supposed to be synthesized, of course.

Anyway, using records instead of vectors could be an option for your problem. And in case you also need a vector version of the data, you could very easily write conversion functions between vector and record types. Example:

package foo is
  type config_type is record
    ready:    std_ulogic;
    enable:   std_ulogic;
    foobar:   std_ulogic_vector(1 downto 0);
    mode:     std_ulogic_vector(1 downto 0);
    reserved: std_ulogic;
    done:     std_ulogic;
  end record;

  function rec2vec(v: config_type) return std_ulogic_vector;
  function vec2rec(v: std_ulogic_vector) return config_type;
end package foo;

package body foo is
  function rec2vec(v: config_type) return std_ulogic_vector is
  begin
    return v.ready & v.enable & v.foobar & v.mode & v.reserved & v.done;
  end function rec2vec;

  function vec2rec(v: std_ulogic_vector) return config_type is
    constant vv: std_ulogic_vector(7 downto 0) := v;
  begin
    return (ready => vv(7), enable => vv(6), foobar => vv(5 downto 4),
            mode => vv(3 downto 2), reserved => vv(1), done => vv(0));
  end function vec2rec;
end package body foo;

You could then use the aggregate notation to assign records:

signal config_reg: config_type;
...
config_reg <= (
  ready  => '1',
  enable => '1',
  foobar => "--",
  mode   => "01",
  others => '0'
);

And convert to-from vectors:

signal config_reg_v: std_ulogic_vector(7 downto 0);
...
config_reg_v <= rec2vec(config_reg);
...
config_reg <= vec2rec(config_reg_v);
...
config_reg <= vec2rec(X"ff");

Note: I used std_ulogic and std_ulogic_vector instead of the resolved std_logic and std_logic_vector. There are good reasons for that but it is another question.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • Thanks for the detailed response. I messed up with the `7|0` one that's true. I do think that it is valid to say that `3 downto 2 => "01" though isn't it? I didn't use a record because that seemed like a complicated way to get a name for a bit in my register, but I'm relatively novice/intermediate at VHDL so I was not sure exactly how other developers go about that. – bgarisn Aug 03 '17 at 12:48
  • Sorry, but `3 downto 2 => "01"`, in this context, is **not** valid. Any VHDL compiler/simulator will raise an error. Records are not complicated, they are just a very useful feature of most decent programming languages. Do yourself a favor: use them everywhere they make sense. You will improve your productivity by a significant amount, mainly thanks to the number of silly errors that they will avoid. – Renaud Pacalet Aug 03 '17 at 13:23
  • Don't be sorry. I'm just trying to understand why it's not valid because Modelsim 10.5c (the simulator I'm using to run my test-bench) seems to think it's fine -- that is, the value changes to the "01" in the simulation as expected. I appreciate you pointing me towards records. I'll make sure to check them out and learn how to use them in a more productive manner. – bgarisn Aug 03 '17 at 13:33
  • @bgarisn I just tried to compile your code with Modelsim 10.5 (after adding the missing library and package declarations): `** Error: foo.vhd(29): String literal found where non-array type ieee.std_logic_1164.STD_LOGIC was expected.` Line 29 is `3 downto 2 => "01",`. – Renaud Pacalet Aug 03 '17 at 14:00
  • I got it: you compiled in VHDL2008 mode, I compiled in the default (2002, probably). And the 9.3.3.3 clause of the LRM changed in 2008, adding this new aggregate form. Sorry, I should have checked that. I'll edit my answer accordingly. – Renaud Pacalet Aug 03 '17 at 14:09
  • How is the support of records by synthesis tools nowadays? In the past I remember many bugs when for instance assigning different values in a record in different processes or using records as entity ports, etc. – JHBonarius Aug 04 '17 at 10:46
  • 1
    @JHBonarius I have been using records for years and never seen anything wrong in synthesis (Altera Quartus, Xilinx Vivado, Synopsys Design Compiler, Cadence RTL compiler). And, honestly, if I was told to stop using records, I would have to redesign so many lines of code that I would probably try to find another job ;-). – Renaud Pacalet Aug 04 '17 at 10:55
  • @RenaudPacalet I once tried to connect many components using a single inout port that was a record. E.g. an axi bus. Some would be used for uplink and some for downlink data. The synthesis tool did not like that. – JHBonarius Aug 04 '17 at 15:21
  • @JHBonarius If you try to use a single record with inout, your synthesis tool will not be happy - OTOH, I do that all the time in a verification environment. If you use separate records for RTL (one for in and one for out), you should be ok. – Jim Lewis Aug 04 '17 at 16:43
  • @RenaudPacalet you note "Note: I used std_ulogic and std_ulogic_vector instead of the resolved std_logic and std_logic_vector. There are good reasons for that but it is another question." -- what are the good reasons? – akohlsmith Aug 21 '19 at 16:36
  • 1
    @akohlsmith `std_logic` is a resolved type, dedicated to situations where you have several drivers for the same signal (tri-states bus, bidirectional data bus...). `std_ulogic` is unresolved. Having several drivers on a `std_ulogic` signal is forbidden and causes an error, it is much safer as it prevents you from creating a short-circuit by accident. – Renaud Pacalet Aug 21 '19 at 16:47
0

I prefer to define full width constants in my package:

subtype ConfigRegType is std_logic_vector(7 downto 0) ; 
constant CFG_ENABLE : ConfigRegType := B"0_1_000000" ; 
constant CFG_MODE0  : ConfigRegType := B"0000_00_00" ; 
constant CFG_MODE1  : ConfigRegType := B"0000_00_00" ; 
constant CFG_MODE2  : ConfigRegType := B"0000_10_00" ; 
constant CFG_MODE3  : ConfigRegType := B"0000_11_00" ; 
constant CFG_READY  : ConfigRegType := B"1_0000000" ;
constant CFG_DONE   : ConfigRegType := B"0000000_1" ;
. . . 

Now when you are ready to write to it, simply "or" the value set you want:

CONFIG_REG <= CFG_ENABLE or CFG_MODE1 or CFG_READY or CFG_DONE ; 

I have played with other ways, however, like you are noting they seem to require knowledge of details of the implementation.

Jim Lewis
  • 3,601
  • 10
  • 20
  • Thanks for the advice. Do you find that this approach is flexible when you have to change things down the road? – bgarisn Aug 03 '17 at 12:56
  • When things change, simply update the subtype and constants in the package and you are good to go. – Jim Lewis Aug 04 '17 at 15:00
  • Imho that's not so simple. Just imagine having to edit 50+ lines of code of you decide to add another bit to the configuration register. With regards to software design the maintainance cost of this code is quite high. – JHBonarius Aug 06 '17 at 17:12
  • @JHBonarius It might be hard if you use VI, OTOH, if you use Notepad++ or Sigasi, it is simple as a column edit to add a 0 to each of the constant declarations in the location where you add another definition. Of course, this does require alignment WRT spacing and such, but I tend to do that anyway. Also I typically do not put the "_" in the values. – Jim Lewis Aug 07 '17 at 18:42