2
votes

Thesis:
Xilinx XST reverses the direction of vectors after concatenating these.

I have a SATAController and a PicoBlaze soft core CPU. This CPU uses a register interface + cross-clocking to read/write test data. The CPU writes to six 8-bit registers, which store a 24-bit offset and a 24-bit length field for a read request. These address and length fields are expanded to 48 bits (keyword: LBA-48 addressing mode).

How does it look like?

  -- register values after cross clocking with double FFs
  signal sync_Offset     : T_SLV_24;   -- std_logic_vector(23 downto 0)
  signal sync_Length     : T_SLV_24;
  -- output signals for LBA-48 addressing
  signal Address_LB      : T_SLV_48;   -- std_logic_vector(47 downto 0)
  signal BlockCount_LB   : T_SLV_48;
  -- [...]  
begin
  -- [...]
  -- SLL 7 -> granularity for logical blocks is 1 MiB
  Address_LB    <= resize(sync_Offset & "0000000", Address_LB'length);
  BlockCount_LB <= resize(sync_Length & "0000000", BlockCount_LB'length);

Normally one would guess that resize results in
Address_LB == "0000_0000_0000_0000_0" & sync_Offset & "000_0000"
BlockCount_LB == "0000_0000_0000_0000_0" & sync_Length & "000_0000"

But, it's not. After three hours of debugging I found out that the synthesis result is as following:
Address_LB = "0000_000" & sync_Offset & "0_0000_0000_0000_0000"
BlockCount_LB = "0000_000" & sync_Length & "0_0000_0000_0000_0000"

So what happens?
The signals sync_* are descending vectors. After concatenation with operator & the vector is in ascending direction, which causes resize to align sync_* at the other boundary of the vector.

So I searched the web for this phenomenon and I couldn't find anything. I also searched through std_logic_1164 but the concatenation operator isn't explicitly defined for std_logic(_vector).

So here are my questions:

  • Is this a language feature or tool specific?
    (I used Xilinx XST for these tests.)
  • Where is & defined?
  • What are the resulting vector directions after concatenating two:
    • equal directed vectors or two
    • reverse directed vectors?

Solutions:

  1. One could use an intermediate signal of known direction.
  2. One can write a function to force a certain direction. See function descend(..).

Fixed example from above:

Address_LB    <= resize(descend(sync_Offset & "0000000"), Address_LB'length);
BlockCount_LB <= resize(descend(sync_Length & "0000000"), BlockCount_LB'length);

Appendix - Function Declarations:

subtype T_SLV_24  is STD_LOGIC_VECTOR(23 downto 0);
subtype T_SLV_48  is STD_LOGIC_VECTOR(47 downto 0);

resize function:

-- Resizes the vector to the specified length. The adjustment is make on
-- on the 'high end of the vector. The 'low index remains as in the argument.
-- If the result vector is larger, the extension uses the provided fill value
-- (default: '0').
-- Use the resize functions of the numeric_std package for value-preserving
-- resizes of the signed and unsigned data types.
function resize(vec : std_logic_vector; length : natural; fill : std_logic := '0') return std_logic_vector is
  constant  high2b : natural := vec'low+length-1;
  constant  highcp : natural := imin(vec'high, high2b);
  variable  res_up : std_logic_vector(vec'low to high2b);
  variable  res_dn : std_logic_vector(high2b downto vec'low);
begin
  if vec'ascending then
    res_up := (others => fill);
    res_up(vec'low to highcp) := vec(vec'low to highcp);
    return  res_up;
  else
    res_dn := (others => fill);
    res_dn(highcp downto vec'low) := vec(highcp downto vec'low);
    return  res_dn;
  end if;
end function;

function imin(arg1 : integer; arg2 : integer) return integer is
begin
  if arg1 < arg2 then return arg1; end if;
  return arg2;
end function;

function descend(vec : std_logic_vector) return std_logic_vector is
  variable res : std_logic_vector(vec'high downto vec'low);
begin
  res := vec;
  return  res;
end function;

Edit 1:

Involved packages:

library ieee;
use     ieee.std_logic_1164.all;
use     ieee.numeric_std.all;

library PoC;
use     PoC.utils.all;    -- here are resize, imin and descend declared

T_SLV_<n> stands for "type; std_logic_vector; n bits -> (n-1 downto 0)"

2
For completeness, can you include the types T_SLV_24 and of Address_LB?Josh
Also, what libraries and packages are in play here?Josh
@Josh T_SLV_24 is a 24 bit std_logic_vector (range 23..0). Address_LB is a T_SLV_48 -> I removed the SATA_ prefix in signal declaration. Involved packages are listed in section 'Edit 1'. I also fixed resize, because I copied the bit_vector version. This example uses the std_logic_vector version. The code is the same, besides the used types.Paebbels
How about a Minimal, Complete, and Verifiable example? The proper way to deal with ascending versus descending ranges in your functions might be to use aliases (which allows you to define it and subsequent assignment is left right associative without regard to ascending or descending).user1155120

2 Answers

2
votes

With this as an MCVe:

library ieee;
use ieee.std_logic_1164.all;

entity sync is
end entity;
architecture mcve of sync is

    subtype T_SLV_24 is std_logic_vector(23 downto 0);
    subtype T_SLV_48 is std_logic_vector(47 downto 0);
    -- register values after cross clocking with double FFs
    signal sync_Offset     : T_SLV_24 := x"deadbe";
    signal sync_Length     : T_SLV_24 := x"feedfa";
    -- output signals for LBA-48 addressing
    signal Address_LB      : T_SLV_48;   -- std_logic_vector(47 downto 0)
    signal BlockCount_LB   : T_SLV_48;

    function MINIMUM (l,r: integer) return integer is
        begin
            if l > r then
                return r;
            else
                return l;
            end if;
        end function;
  -- [...]  
  -- Resizes the vector to the specified length. The adjustment is make on
  -- on the 'high end of the vector. The 'low index remains as in the argument.
  -- If the result vector is larger, the extension uses the provided fill value
  -- (default: '0').
  -- Use the resize functions of the numeric_std package for value-preserving
  -- resizes of the signed and unsigned data types.
  function resize(vec : std_logic_vector; length : natural; fill : std_logic := '0') return std_logic_vector is
    constant  high2b : natural := vec'low+length-1;
    constant  highcp : natural := MINIMUM(vec'high, high2b);  -- imin
    variable  res_up : std_logic_vector(vec'low to high2b);
    variable  res_dn : std_logic_vector(high2b downto vec'low);
  begin
    if vec'ascending then
      res_up := (others => fill);
      res_up(vec'low to highcp) := vec(vec'low to highcp);
      return  res_up;
    else
      res_dn := (others => fill);
      res_dn(highcp downto vec'low) := vec(highcp downto vec'low);
      return  res_dn;
    end if;
  end function;
  function descend(vec : std_logic_vector) return std_logic_vector is
    variable res : std_logic_vector(vec'high downto vec'low);
  begin
    res := vec;
    return  res;
  end function;

  function to_string(inp: std_logic_vector) return string is
      variable image_str: string (1 to inp'length);
      alias input_str:  std_logic_vector (1 to inp'length) is inp;
  begin
      for i in input_str'range loop
          image_str(i) := character'VALUE(std_ulogic'IMAGE(input_str(i)));
      end loop;
      return image_str;
  end;

begin
  -- [...]
  -- SLL 7 -> granularity for logical blocks is 1 MiB
    -- Address_LB    <= resize(sync_Offset & "0000000", Address_LB'length);
    -- BlockCount_LB <= resize(sync_Length & "0000000", BlockCount_LB'length);
    Address_LB    <= resize(descend(sync_Offset & "0000000"), Address_LB'length);
    BlockCount_LB <= resize(descend(sync_Length & "0000000"), BlockCount_LB'length);

    Process (Address_LB, BlockCount_LB)
    begin
        report "Address_LB = " & to_string(Address_LB) Severity NOTE;
        report "BlockCount_LB = " & to_string(BlockCount_LB) Severity NOTE;
    end process;
end architecture;

With two initial values:

    signal sync_Offset     : T_SLV_24 := x"deadbe";
    signal sync_Length     : T_SLV_24 := x"feedfa";

Note the local function MINIMUM instead of imin (not provided) and a local copy of to_string (this ghdl is -1993 compliant). Also note the minimal context clause with a local MINIMUM

That gave:

ghdl -a sync.vhdl
ghdl -e sync
ghdl -r sync
sync.vhdl:75:9:@0ms:(report note): Address_LB = uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
sync.vhdl:76:9:@0ms:(report note): BlockCount_LB = uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
sync.vhdl:75:9:@0ms:(report note): Address_LB = 000000000000000001101111010101101101111100000000
sync.vhdl:76:9:@0ms:(report note): BlockCount_LB = 000000000000000001111111011101101111110100000000

0000 0000 0000 0000 0 1101 1110 1010 1101 1011 1110 000 0000
                        D    E    A    D    B    E

Address_LB looks right.

0000 0000 0000 0000 0 1111 1110 1110 1101 1111 1010 000 0000
                        F    E    E    D    F    A

And so does BlockCount_LB.

Without the descent call (the original assignments):

  Address_LB    <= resize(sync_Offset & "0000000", Address_LB'length);
  BlockCount_LB <= resize(sync_Length & "0000000", BlockCount_LB'length);

sync.vhdl:75:9:@0ms:(report note): Address_LB = 110111101010110110111110000000000000000000000000
sync.vhdl:76:9:@0ms:(report note): BlockCount_LB = 111111101110110111111010000000000000000000000000

Which is

D  E   A   D   B   E  0000 0000 0000 0000 0000 0000

and

F  E   E   D   F   A  0000 0000 0000 0000 0000 0000

And doesn't look like the question, which really says you should supply an MCVe. (These do look like what you'd expect from reading your resize function).

  • Is this a language feature or tool specific? (I used Xilinx XST for these tests.)

Worked in ghdl. Tool specific. I'd also look carefully at VHDL constructs supported by XST.

  • Where is & defined?

It's define as soon as the type is defined, along with other predefined operators for a single dimensional array type and basic operations. Note I used a subtype, so in this case these implicit declarations occur in package std_logic_1164, following the declaration for std_logic_vector (std_ulogic_vector, -2008).

  • What are the resulting vector directions of equal directed vectors and reverse directed vectors?

Huh? Try this one again paebbels. Assignment is left to right associative, left element to left element, ... right element to right_element). No reserved data, which would require a loop in descend (or a subtype indication in the interface list Formal, but then it wouldn't work with any length).

Personally I don't see the point of writing functions to do this. Simply using concatenation should be synthesis safe.

2
votes

The concatenation operator (&) is defined in the VHDL standard, e.g. VHDL-2008 section "9.2.5 Adding operators", where the relevant information about direction and range is:

a) ... Let S be the index subtype of the base type of the result. The direction of the result of the concatenation is the direction of S, and the left bound of the result is S'LEFT.

For TYPE std_logic_vector IS ARRAY (NATURAL RANGE <>) OF std_logic; (or similar), the index subtype S is NATURAL which has ascending direction (NATURAL'ASCENDING = TRUE) and left bound 0 (NATURAL'LEFT = 0).

Concatenation result for std_logic_vector is thus ascending with left bound of 0, which is also the case for sync_Offset & "0000000". (Only exception is for concatenation of two zero-length arrays, which returns right argument.)

The resize function you have written will place the vec argument to the left in the result, and fill with fill, so I would expect the result from:

resize(sync_Offset & "0000000", Address_LB'length);

to be:

sync_Offset & "000...000"

If there are any 0's to the left of the sync_Offset value in the result, then it sounds like a problem in either your result interpretation or XST (less likely).

Btw. reusing the resize name for a function with other operation than resize of an unsigned is likely to lead to confusion.