VECTOR 표현과 INTEGER를 사용하는 것이 언제 더 좋을까요?


11

이 질문에 대한 답변에 대한 의견 스레드 에서 VHDL 엔티티의 잘못된 출력 은 다음과 같습니다.

"정수를 사용하면 FPGA의 내부 논리 표현에 대한 제어 또는 액세스 권한이 없지만 SLV를 통해 캐리 체인을 효율적으로 활용하는 것과 같은 트릭을 수행 할 수 있습니다."

그렇다면 어떤 상황 에서 내부 표현에 액세스하기 위해 정수를 사용하는 것보다 비트 표현 벡터를 사용하여 코딩하는 것이 더 좋은지 알았 습니까? 그리고 칩 영역, 클럭 주파수, 지연 또는 기타 측면에서 어떤 장점을 측정 했 습니까?


저수준 구현에 대한 제어 문제이기 때문에 측정하기가 어렵다고 생각합니다.
clabacchio

답변:


5

나는 두 가지 다른 포스터가 제안한 코드를 양식 vectorinteger양식으로 작성했으며 두 버전이 가능한 한 비슷한 방식으로 작동하도록주의를 기울였습니다.

시뮬레이션 결과를 비교 한 후 Xilinx Spartan 6을 대상으로하는 Synplify Pro를 사용하여 합성했습니다. 아래 코드 샘플은 작업 코드에서 붙여 넣었으므로 선호하는 신시사이저와 함께 사용하여 동일한 동작을하는지 확인할 수 있습니다.


다운 카운터

먼저 데이비드 케스 너가 제안한 다운 카운터 :

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

entity downcounter is
    generic (top : integer);
    port (clk, reset, enable : in  std_logic; 
         tick   : out std_logic);
end entity downcounter;

벡터 아키텍처 :

architecture vec of downcounter is
begin
    count: process (clk) is
        variable c : unsigned(32 downto 0);  -- don't inadvertently not allocate enough bits here... eg if "integer" becomes 64 bits wide
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := to_unsigned(top-1, c'length);
            elsif enable = '1' then
                if c(c'high) = '1' then
                    tick <= '1';
                    c := to_unsigned(top-1, c'length);
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture vec;

정수 아키텍처

architecture int of downcounter is
begin
    count: process (clk) is
        variable c : integer;
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := top-1;
            elsif enable = '1' then
                if c < 0 then
                    tick <= '1';
                    c := top-1;
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture int;

결과

코드 측면에서 정수는 to_unsigned()호출을 피하기 때문에 나에게 바람직 합니다. 그렇지 않으면 선택하지 않아도됩니다.

Synplify Pro를 통해이를 실행 하면 버전에 대해 66 개의 LUT 와 버전에 대해 64 개의 LUTtop := 16#7fff_fffe#생성 됩니다. 두 버전 모두 캐리 체인을 많이 사용합니다. 두 가지 모두 클럭 속도가 280MHz 를 초과 합니다. 신시사이저는 캐리 체인을 잘 사용할 수 있습니다. RTL 뷰어를 통해 시각적으로 유사한 로직이 두 가지 방식으로 생성되는지 확인했습니다. 분명히 비교기가있는 상위 카운터는 더 커지지 만 정수와 벡터 모두 다시 동일합니다.vectorinteger


2 ** n 카운터로 나누기

ajs410에서 제안한 내용 :

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clkdiv is
    port (clk, reset : in     std_logic;
        clk_2, clk_4, clk_8, clk_16  : buffer std_logic);
end entity clkdiv;

벡터 아키텍처

architecture vec of clkdiv is

begin  -- architecture a1

    process (clk) is
        variable count : unsigned(4 downto 0);
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := (others => '0');
            else
                count := count + 1;
            end if;
        end if;
        clk_2 <= count(0);
        clk_4 <= count(1);
        clk_8 <= count(2);
        clk_16 <= count(3);
    end process;

end architecture vec;

정수 아키텍처

to_unsigned비트를 사용 하고 따로 피하는 것을 피하려면 위와 같은 효과를내는 것을 피하기 위해 일부 후프를 뛰어 넘어야합니다 .

architecture int of clkdiv is
begin
    process (clk) is
        variable count : integer := 0;
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := 0;
                clk_2  <= '0';
                clk_4  <= '0';
                clk_8  <= '0';
                clk_16 <= '0';
            else
                if count < 15 then
                    count := count + 1;
                else
                    count := 0;
                end if;
                clk_2 <= not clk_2;
                for c4 in 0 to 7 loop
                    if count = 2*c4+1 then
                        clk_4 <= not clk_4;
                    end if;
                end loop; 
                for c8 in 0 to 3 loop
                    if count = 4*c8+1 then
                        clk_8 <= not clk_8;
                    end if;
                end loop; 
                for c16 in 0 to 1 loop
                    if count = 8*c16+1 then
                        clk_16 <= not clk_16;
                    end if;
                end loop; 
            end if;
        end if;
    end process;
end architecture int;

결과

이 경우 코드 방식으로 vector버전이 더 좋습니다!

이 작은 예제의 경우 합성 결과 측면에서 정수 버전 (ajs410이 예측 한대로)은 비교기의 일부로 3 개의 추가 LUT를 생성하지만, 신디사이저에 대해 너무 낙관적입니다.


다른 용도

산술을 랩 어라운드하려는 경우 벡터가 확실한 승리입니다 (카운터는 단일 행으로도 수행 할 수 있음).

vec <= vec + 1 when rising_edge(clk);

vs

if int < int'high then 
   int := int + 1;
else
   int := 0;
end if;

적어도 그 코드에서 저자가 랩을 의도 한 것은 분명합니다.


내가 실제 코드에서 사용하지 않았지만 고민 한 것 :

"자연적으로 줄 바꿈"기능은 "오버플로를 통한 계산"에도 사용할 수 있습니다. 덧셈 / 뺄셈 및 곱셈의 체인의 출력이 제한되어 있음을 알면 중간 계산의 높은 비트를 (2-s 보수로) "세척 중"으로 나올 필요가 없습니다. 출력에 도달 할 때까지 나는이 논문이 이것에 대한 증거를 포함하고 있다고 들었지만, 빠른 평가를하기에는 약간 조밀하게 보였다! 컴퓨터 추가 및 오버플로 이론-HL Garner

integer이 상황에서 s를 사용하면 랩이 풀릴 때 시뮬레이션 랩 오류가 발생할 수 있습니다.


필립이 지적한 것처럼 2 ** 31보다 큰 숫자가 필요할 때는 벡터를 사용할 수밖에 없습니다.


두 번째 코드 블록에는 variable c : unsigned(32 downto 0);... c33 비트 변수가 아닌가?
clabacchio

@clabacchio : 예, 랩 어라운드를보기 위해 '캐리 비트'에 액세스 할 수 있습니다.
Martin Thompson

5

VHDL을 작성할 때 SIGNALS 에 정수 (int) 대신 std_logic_vector (slv)를 사용하는 것이 좋습니다 . 반면에 제네릭, 정수 및 일부 변수에 int를 사용하면 유용 할 수 있습니다. int 유형의 신호를 선언하거나 정수의 범위를 지정해야하는 경우 간단히 수행 할 수 있습니다. 뭔가 잘못.

int의 문제점은 VHDL 프로그래머가 int의 내부 논리 표현이 무엇인지 알지 못하므로이를 활용할 수 없다는 것입니다. 예를 들어 1에서 10 범위의 정수를 정의하면 컴파일러가 해당 값을 어떻게 인코딩하는지 전혀 모릅니다. 잘만되면 그것은 4 비트로 인코딩 될 것이지만, 우리는 그 이상을 잘 모릅니다. FPGA 내부의 신호를 프로브 할 수 있으면 "0001"에서 "1010"으로 인코딩되거나 "0000"에서 "1001"로 인코딩 될 수 있습니다. 그것은 우리 인간에게는 절대적으로 의미가없는 방식으로 인코딩 될 수도 있습니다.

대신 int 대신 slv를 사용해야합니다. 왜냐하면 인코딩을 제어하고 개별 비트에 직접 액세스 할 수 있기 때문입니다. 나중에 볼 수 있듯이 직접 액세스하는 것이 중요합니다.

개별 비트에 액세스해야 할 때마다 int를 slv에 캐스트 할 수는 있지만 정말 지저분하고 빠릅니다. 그것은 두 세계의 최고 대신 두 세계의 최악을 얻는 것과 같습니다. 컴파일러는 최적화하기 어렵고 읽을 수없는 코드가 될 것입니다. 나는 이것을 권장하지 않습니다.

내가 말했듯이 slv를 사용하면 비트 인코딩을 제어하고 비트에 직접 액세스 할 수 있습니다. 그래서 당신은 이것으로 무엇을 할 수 있습니까? 몇 가지 예를 보여 드리겠습니다. 4,294,000,000 클럭마다 한 번씩 펄스를 출력해야한다고 가정 해 봅시다. int로 이것을 수행하는 방법은 다음과 같습니다.

signal count :integer range 0 to 4293999999;  -- a 32 bit integer

process (clk)
begin
  if rising_edge(clk) then
    if count = 4293999999 then  -- The important line!
      count <= 0;
      pulse <= '1';
    else
      count <= count + 1;
      pulse <= '0';
    end if;
  end if;
end process;

그리고 slv를 사용하는 동일한 코드 :

use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0);  -- a 33 bit integer, one extra bit!

process (clk)
begin
  if rising_edge(clk) then
    if count(count'high)='1' then   -- The important line!
      count <= std_logic_vector(4293999999-1,count'length);
      pulse <= '1';
    else
      count <= count - 1;
      pulse <= '0';
    end if;
  end if;
end process;

이 코드의 대부분은 최소한 결과 논리의 크기와 속도의 관점에서 int와 slv간에 동일합니다. 물론 하나는 카운트 업하고 다른 하나는 카운트 다운하지만이 예제에서는 중요하지 않습니다.

차이점은 "중요한 선"에 있습니다.

int 예제를 사용하면 32 입력 비교기가됩니다. Xilinx Spartan-3이 사용하는 4 입력 LUT를 사용하려면 11 개의 LUT와 3 단계의 로직이 필요합니다. 일부 컴파일러는 이것을 캐리 체인을 사용하여 32 LUT에 해당하는 뺄셈으로 변환하지만 3 레벨의 논리보다 빠르게 실행될 수 있습니다.

slv 예제에서는 32 비트 비교가 없으므로 "제로 LUT, 제로 레벨 논리"입니다. 유일한 페널티는 카운터가 하나의 추가 비트라는 것입니다. 이 여분의 비트 비트에 대한 추가 타이밍이 모두 캐리 체인에 있기 때문에 "거의 0"추가 타이밍 지연이 있습니다.

물론 이것은 대부분의 사람들이 이런 식으로 32 비트 카운터를 사용하지 않기 때문에 극단적 인 예입니다. 더 작은 카운터에는 적용되지만 그 차이는 여전히 크지 만 덜 극적인 것입니다.

이것은 빠른 타이밍을 얻기 위해 int를 통해 slv를 사용하는 방법의 한 가지 예일뿐입니다. slv를 활용하는 다른 많은 방법이 있습니다. 약간의 상상력 만 있으면됩니다.

업데이트 : "if (count-1) <0"으로 int를 사용하는 것에 대한 Martin Thompson의 의견을 다루는 내용이 추가되었습니다.

(참고 : 나는 당신이 "if count <0"을 의미한다고 가정합니다. 왜냐하면 그것은 내 slv 버전과 더 동등하고 그 추가 빼기의 필요성을 제거하기 때문입니다.)

경우에 따라 의도 된 논리 구현이 생성 될 수 있지만 항상 작동하는 것은 아닙니다. 코드와 컴파일러가 int 값을 인코딩하는 방법에 따라 다릅니다.

컴파일러와 int 범위를 지정하는 방법에 따라 int 값이 0 인 경우 FPGA 로직으로 만들 때 "0000 ... 0000"의 비트 벡터로 인코딩되지 않을 수 있습니다. 변형이 작동하려면 "0000 ... 0000"으로 인코딩해야합니다.

예를 들어 int를 -5에서 +5의 범위로 정의한다고 가정 해 봅시다. 값 0은 "0000"과 같은 4 비트로, +5는 "0101", -5는 "1011"로 인코딩 될 것으로 예상합니다. 이것이 전형적인 2 보완 인코딩 체계입니다.

그러나 컴파일러가 twos-complement를 사용한다고 가정하지 마십시오. 드문 일이지만, 보완은 "더 나은"논리를 초래할 수 있습니다. 또는 컴파일러는 일종의 "바이어스 드"인코딩을 사용할 수 있습니다. 여기서 -5는 "0000", 0은 "0101", +5는 "1010"으로 인코딩됩니다.

int의 인코딩이 "정확한"경우 컴파일러는 캐리 비트로 수행 할 작업을 유추 할 수 있습니다. 그러나 그것이 틀렸다면 결과 논리는 끔찍할 것입니다.

이런 식으로 int를 사용하면 논리 크기와 속도가 합리적 일 수 있지만 보장 할 수는 없습니다. 다른 컴파일러 (예 : XST에서 시놉시스로)로 전환하거나 다른 FPGA 아키텍처로 전환하면 정확히 잘못된 일이 발생할 수 있습니다.

Unsigned / Signed vs. slv는 또 다른 논쟁입니다. VHDL에 많은 옵션을 제공 한 미국 정부위원회에 감사 할 수 있습니다. :) 모듈과 코어 사이의 인터페이스 표준이기 때문에 slv를 사용합니다. 그 외에도 시뮬레이션의 다른 경우에는 서명 / 서명되지 않은 것보다 slv를 사용하면 큰 이점이 없다고 생각합니다. 서명 / 서명되지 않은 신호가 삼중 신호를 지원하는지 확실하지 않습니다.


4
David, 그 코드 조각들은 동일하지 않습니다. 하나는 0에서 임의의 숫자로 계산됩니다 (고가의 비교 연산자 사용). 다른 하나는 임의의 숫자에서 0으로 카운트 다운됩니다. 정수 또는 벡터를 사용하여 두 알고리즘을 모두 작성할 수 있으며 임의의 숫자를 세면 좋은 결과가 나오고 0을 세는 좋은 결과가 나타납니다. 소프트웨어 엔지니어는 것을 참고 또한 그들은 뜨거운 루프에서 좀 더 성능을 짜내해야하는 경우 제로 카운트 다운.
Philippe

1
필립과 마찬가지로 나는 이것이 유효한 비교라고 확신하지 않습니다. 정수 예제가 카운트 다운되고 사용 if (count-1) < 0되면 신시사이저가 캐리 비트를 유추하고 slv 예제와 거의 동일한 회로를 생성한다고 생각합니다. 또한, 우리는 unsigned요즘 유형을 사용해서는 안됩니다 :)
Martin Thompson

2
@DavidKessner 당신은 확실히 THOROUGH와 합리적인 추론을 제공했으며, 당신은 나의 +1을 얻었습니다. 그래도 물어봐야합니다 ... 왜 디자인 전반에 걸쳐 최적화가 걱정됩니까? 코드 영역이 필요한 코드 영역에 집중하거나 호환성을 위해 인터페이스 포인트 (엔티티 포트)의 SLV에 집중하는 것이 더 좋지 않습니까? 나는 대부분의 디자인에서 타이밍을 충족하고 부품에 맞는 LUT 사용이 최소화되는 것을 신경 쓰지 않는다는 것을 알고 있습니다. 특히 엄격한 제약 조건이 있다면 최적의 디자인을 더 의식하지만 일반적인 규칙은 아닙니다.
akohlsmith

2
이 답변에 대한 많은 투표율로 인해 약간 비싸졌습니다. @ bit_vector @는 마이크로 아키텍처를 모델링하고 최적화하기위한 올바른 추상화 수준이지만, 신호 및 포트에 대해 @ integer @와 같은 "높은 수준"유형을 다시 권장하는 것이 좋습니다. 추상화가 부족하여 이러한 기능이 제공하는 가치를 알기 때문에 충분히 복잡하고 읽을 수없는 코드를 보았습니다.
trondd

2
@david 우수 비고. 우리가 여러면에서 소프트웨어 개발과 비교할 때 여전히 중세 시대에 있다는 것은 사실이지만, Quartus 통합 합성 및 Synplify에 대한 경험을 통해 상황이 그렇게 나쁘지 않다고 생각합니다. 레지스터 리 타이밍 및 가독성을 유지하면서 성능을 향상시키는 기타 최적화와 같은 많은 것을 처리 할 수 ​​있습니다. 나는 대다수가 여러 툴체인과 장치를 목표로하고 있다고 의심하지만, 귀하의 경우 가장 일반적인 분모에 대한 요구 사항을 이해합니다 :-).
trondd

2

제 조언은 두 가지를 모두 시도한 다음 종합,지도 및 장소 및 경로 보고서를 살펴 보는 것입니다. 이 보고서는 각 접근법이 소비하는 LUT의 수를 정확하게 알려주고 로직이 작동 할 수있는 최대 속도도 알려줍니다.

David Kessner는 귀하가 툴체인의 자비에 있으며 "올바른"답변이 없다는 데 동의합니다. 합성은 흑 마법이며 어떤 일이 발생했는지 아는 가장 좋은 방법은 생성 된 보고서를주의 깊고 철저히 읽는 것입니다. Xilinx 툴을 사용하면 각 LUT가 프로그래밍되는 방식, 캐리 체인이 연결되는 방식, 스위치 패브릭이 모든 LUT를 연결하는 방식 등 FPGA 내부를 볼 수 있습니다.

Kessner의 접근 방식의 또 다른 극적인 예를 들어 1/2, 1/4, 1/8, 1/16 등에서 여러 클록 주파수를 원한다고 가정 해보십시오. 모든 사이클을 지속적으로 카운트하는 정수를 사용할 수 있습니다. 그런 다음 정수 값에 대해 여러 개의 비교기를 가지며 각 비교기 출력은 다른 클럭 분할을 형성합니다. 비교기의 수에 따라 팬 아웃이 불합리하게 커져 버퍼링을 위해 추가 LUT를 소비하기 시작할 수 있습니다. SLV 방식은 벡터의 각 개별 비트를 출력으로 사용합니다.


1

명백한 이유 중 하나는 부호있는 부호와 부호없는 부호가 32 비트 정수보다 큰 값을 허용하기 때문입니다. 그것은 VHDL 언어 디자인의 결함이며, 필수적인 것은 아닙니다. VHDL의 새로운 버전은이를 수정하여 임의의 크기 (Java의 BigInt와 유사)를 지원하는 정수 값이 필요합니다.

그 외에는 벡터와 비교하여 정수에 대해 다르게 수행되는 벤치 마크에 대해 듣고 싶습니다.

BTW, Jan Decaluwe는 이것에 대한 좋은 에세이를 썼습니다 : 이 Ints는 Countin '을 위해 만들어졌습니다.


감사합니다 Philippe ( "내부 표현에 대한 액세스를 통해 더 나은 것은 아니지만", 이것은 내가 실제로 추구하는 것입니다.)
Martin Thompson

이 에세이는 훌륭하지만 기본 구현과 결과 논리 속도 및 크기를 완전히 무시합니다. Decaluwe가 말한 대부분의 내용에 동의하지만 합성 결과에 대해서는 아무 말도하지 않습니다. 때로는 합성 결과가 중요하지 않고 때로는 결과가 중요합니다. 그래서 그것은 판단 요청입니다.

1
@David, Jan은 합성 툴이 정수에 어떻게 반응하는지에 대해 자세히 설명하지 않는다는 데 동의합니다. 그러나 그것은 판결 요청이 아닙니다. 합성 결과를 측정하고 주어진 합성 도구의 결과를 결정할 수 있습니다. OP는 그의 질문이 성능의 차이 (있는 경우)를 보여주는 코드 조각과 합성 결과를 생성하는 데있어 어려운 문제라고 생각합니다.
Philippe

@Philippe 아니오, 합성 결과에 대해 전혀 신경 쓰지 않는다면 판단이 필요합니다. 합성 결과 그 자체가 판단을 요구하는 것은 아닙니다.

@DavidKessner OK. 나는 오해했다.
Philippe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.