산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Industrial programming >> VHDL

준비/유효한 핸드셰이크를 사용하여 블록 RAM에서 AXI FIFO를 만드는 방법

AXI 모듈을 인터페이스하기 위한 로직을 처음 생성해야 했을 때 AXI 인터페이스의 특성에 약간 짜증이 났습니다. 일반적인 사용 중/유효, 전체/유효 또는 비어 있음/유효 제어 신호 대신 AXI 인터페이스는 "준비" 및 "유효"라는 두 개의 제어 신호를 사용합니다. 나의 좌절은 곧 경외심으로 바뀌었습니다.

AXI 인터페이스에는 추가 제어 신호를 사용하지 않고 흐름 제어가 내장되어 있습니다. 규칙은 충분히 이해하기 쉽지만 FPGA에서 AXI 인터페이스를 구현할 때 고려해야 할 몇 가지 함정이 있습니다. 이 기사에서는 VHDL에서 AXI FIFO를 생성하는 방법을 보여줍니다.

AXI는 1주기 지연 문제를 해결합니다.

데이터 스트림 인터페이스를 생성할 때 덮어쓰기 및 덮어쓰기를 방지하는 것은 일반적인 문제입니다. 문제는 2개의 클록된 로직 모듈이 통신할 때 각 모듈이 1개의 클록 주기 지연으로 해당 모듈의 출력만 읽을 수 있다는 것입니다.

위의 이미지는 쓰기 활성화/전체를 사용하는 FIFO에 쓰는 순차 모듈의 타이밍 다이어그램을 보여줍니다. 신호 체계. 인터페이스 모듈은 wr_en를 어설션하여 FIFO에 데이터를 씁니다. 신호. FIFO는 full을 어설션합니다. 다른 데이터 요소를 위한 공간이 없을 때 신호를 보내 데이터 소스에 쓰기를 중지하라는 메시지를 표시합니다.

불행히도 인터페이스 모듈은 클럭 로직만 사용하는 한 제 시간에 멈출 방법이 없습니다. FIFO는 full를 올립니다. 클록의 상승 에지에서 정확히 플래그를 지정합니다. 동시에 인터페이스 모듈은 다음 데이터 요소를 쓰려고 시도합니다. full를 샘플링하고 반응할 수 없습니다. 너무 늦기 전에 신호를 보내세요.

한 가지 해결책은 추가 almost_empty을 포함하는 것입니다. 신호에 대해 VHDL에서 링 버퍼 FIFO를 만드는 방법 자습서에서 이 작업을 수행했습니다. 추가 신호는 empty보다 우선합니다. 신호를 보내 인터페이스 모듈에 반응할 시간을 줍니다.

준비/유효한 악수

AXI 프로토콜은 각 방향에서 ready이라고 하는 두 개의 제어 신호만을 사용하여 흐름 제어를 구현합니다. 다른 valid . ready 신호는 수신기에 의해 제어되며 논리적 '1' 이 신호의 값은 수신기가 새 데이터 항목을 수락할 준비가 되었음을 의미합니다. valid 반면에 신호는 발신자에 의해 제어됩니다. 발신자는 valid을 설정해야 합니다. '1'로 데이터 버스에 표시된 데이터가 샘플링에 유효한 경우

중요한 부분은 다음과 같습니다. 데이터 전송은 readyvalid '1' 동일한 클럭 주기에서. 수신자는 데이터를 받아들일 준비가 되었음을 알리고, 발신자는 전송할 데이터가 있을 때 단순히 데이터를 거기에 넣습니다. 전송은 발신자가 보낼 준비가 되어 있고 수신자가 수신할 준비가 되어 있을 때 둘 다 동의할 때 발생합니다.

위의 파형은 하나의 데이터 항목에 대한 트랜잭션의 예를 보여줍니다. 샘플링은 일반적으로 클록 로직의 경우와 같이 상승 클록 에지에서 발생합니다.

구현

VHDL에서 AXI FIFO를 구현하는 방법에는 여러 가지가 있습니다. 시프트 레지스터일 수 있지만 블록 RAM에서 FIFO를 생성하는 가장 간단한 방법이기 때문에 링 버퍼 구조를 사용할 것입니다. 변수와 신호를 사용하여 하나의 거대한 프로세스에서 모든 것을 생성하거나 기능을 여러 프로세스로 분할할 수 있습니다.

이 구현은 업데이트해야 하는 대부분의 신호에 대해 별도의 프로세스를 사용합니다. 동기가 필요한 프로세스만 시계에 민감하고 나머지는 조합 논리를 사용합니다.

개체

엔티티 선언에는 입력 및 출력 단어의 너비를 설정하는 데 사용되는 일반 포트와 RAM 공간을 예약할 슬롯 수가 포함됩니다. FIFO의 용량은 RAM 깊이에서 1을 뺀 것과 같습니다. 하나의 슬롯은 가득 찬 FIFO와 빈 FIFO를 구분하기 위해 항상 비어 있습니다.

entity axi_fifo is
  generic (
    ram_width : natural;
    ram_depth : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- AXI input interface
    in_ready : out std_logic;
    in_valid : in std_logic;
    in_data : in std_logic_vector(ram_width - 1 downto 0);

    -- AXI output interface
    out_ready : in std_logic;
    out_valid : out std_logic;
    out_data : out std_logic_vector(ram_width - 1 downto 0)
  );
end axi_fifo; 

포트 선언의 처음 두 신호는 클록 및 리셋 입력입니다. 이 구현은 동기식 리셋을 사용하며 클록의 상승 에지에 민감합니다.

준비/유효한 제어 신호와 일반 폭의 입력 데이터 신호를 사용하는 AXI 스타일 입력 인터페이스가 있습니다. 마지막으로 입력과 유사한 신호가 있는 AXI 출력 인터페이스가 제공되며 방향만 반대입니다. 입력 및 출력 인터페이스에 속하는 신호에는 in_ 접두사가 붙습니다. 또는 out_ .

한 AXI FIFO의 출력을 다른 입력에 직접 연결할 수 있으며 인터페이스가 완벽하게 맞습니다. 스택을 쌓는 것보다 더 나은 솔루션은 ram_depth 더 큰 FIFO를 원하는 경우 일반적입니다.

신호 선언

VHDL 파일의 선언적 영역에 있는 처음 두 명령문은 RAM 유형과 해당 신호를 선언합니다. RAM은 일반 입력에서 동적으로 크기 조정됩니다.

-- The FIFO is full when the RAM contains ram_depth - 1 elements
type ram_type is array (0 to ram_depth - 1)
  of std_logic_vector(in_data'range);
signal ram : ram_type;

코드의 두 번째 블록은 새로운 정수 하위 유형과 그로부터 4개의 신호를 선언합니다. index_type RAM의 깊이를 정확히 나타내는 크기입니다. head 신호는 항상 다음 쓰기 작업에 사용될 RAM 슬롯을 나타냅니다. tail 신호는 다음 읽기 작업에서 액세스할 슬롯을 가리킵니다. count 값 신호는 항상 FIFO에 현재 저장된 요소 수와 동일하며 count_p1 한 클럭 주기만큼 지연된 동일한 신호의 복사본입니다.

-- Newest element at head, oldest element at tail
subtype index_type is natural range ram_type'range;
signal head : index_type;
signal tail : index_type;
signal count : index_type;
signal count_p1 : index_type;

그런 다음 in_ready_i라는 두 개의 신호가 옵니다. 및 out_valid_i . 이는 엔터티 출력 in_ready의 복사본일 뿐입니다. 및 out_valid . _i 접미사는 내부를 의미합니다. , 제 코딩 스타일의 일부입니다.

-- Internal versions of entity signals with mode "out"
signal in_ready_i : std_logic;
signal out_valid_i : std_logic;

마지막으로 동시 읽기 및 쓰기를 나타내는 데 사용할 신호를 선언합니다. 이 기사의 뒷부분에서 그 목적을 설명하겠습니다.

-- True the clock cycle after a simultaneous read and write
signal read_while_write_p1 : std_logic;

하위 프로그램

신호 후에 사용자 정의 index_type를 증가시키는 함수를 선언합니다. . next_index 함수는 read을 살펴봅니다. 및 valid 진행 중인 읽기 또는 읽기/쓰기 트랜잭션이 있는지 확인하는 매개변수입니다. 이 경우 인덱스가 증가하거나 래핑됩니다. 그렇지 않으면 변경되지 않은 인덱스 값이 반환됩니다.

function next_index(
  index : index_type;
  ready : std_logic;
  valid : std_logic) return index_type is
begin
  if ready = '1' and valid = '1' then
    if index = index_type'high then
      return index_type'low;
    else
      return index + 1;
    end if;
  end if;

  return index;
end function;

반복적인 타이핑을 방지하기 위해 head 업데이트 로직을 생성합니다. 및 tail 두 개의 동일한 프로세스 대신 프로시저에서 신호를 보냅니다. update_index 절차는 index_type의 신호인 클록 및 재설정 신호를 취합니다. , ready 신호 및 valid 신호를 입력으로 사용합니다.

procedure index_proc(
  signal clk : in std_logic;
  signal rst : in std_logic;
  signal index : inout index_type;
  signal ready : in std_logic;
  signal valid : in std_logic) is
begin
    if rising_edge(clk) then
      if rst = '1' then
        index <= index_type'low;
      else
        index <= next_index(index, ready, valid);
      end if;
    end if;
end procedure;

이 완전 동기식 프로세스는 next_index index 업데이트 함수 모듈이 리셋에서 벗어났을 때 신호. 재설정 시 index 신호는 표현할 수 있는 가장 낮은 값으로 설정되며 index_typeram_type 선언됩니다. 초기화 값으로 0을 사용할 수도 있지만 하드코딩을 최대한 피하려고 합니다.

내부 신호를 출력으로 복사

이 두 개의 동시 명령문은 출력 신호의 내부 버전을 실제 출력으로 복사합니다. VHDL은 out 모드의 엔티티 신호를 읽을 수 없기 때문에 내부 복사본에서 작업해야 합니다. 모듈 내부. 대안은 in_ready를 선언하는 것이었습니다. 및 out_valid inout 모드 사용 , 그러나 대부분의 회사 코딩 표준은 inout의 사용을 제한합니다. 엔티티 신호.

in_ready <= in_ready_i;
out_valid <= out_valid_i;

머리와 꼬리 업데이트

우리는 이미 index_proc에 대해 논의했습니다. head 업데이트에 사용되는 절차 및 tail 신호. 이 서브프로그램의 매개변수에 적절한 신호를 매핑함으로써 우리는 FIFO 입력을 제어하는 ​​것과 출력을 위한 것의 두 개의 동일한 프로세스에 해당하는 것을 얻습니다.

-- Update head index on write
PROC_HEAD : index_proc(clk, rst, head, in_ready_i, in_valid);

-- Update tail index on read
PROC_TAIL : index_proc(clk, rst, tail, out_ready, out_valid_i);

headtail 재설정 논리에 의해 동일한 값으로 설정되면 FIFO는 처음에 비어 있습니다. 이것이 이 링 버퍼가 작동하는 방식입니다. 둘 다 동일한 인덱스를 가리키는 경우 FIFO가 비어 있음을 의미합니다.

블록 RAM 추론

대부분의 FPGA 아키텍처에서 블록 RAM 프리미티브는 완전히 동기식 구성 요소입니다. 즉, 합성 도구가 VHDL 코드에서 블록 RAM을 유추하려면 클럭 프로세스 내부에 읽기 및 쓰기 포트를 배치해야 합니다. 또한 블록 RAM과 관련된 재설정 값은 있을 수 없습니다.

PROC_RAM : process(clk)
begin
  if rising_edge(clk) then
    ram(head) <= in_data;
    out_data <= ram(next_index(tail, out_ready, out_valid_i));
  end if;
end process;

읽기 활성화가 없습니다. 또는 쓰기 활성화 여기에서 AXI에서는 너무 느릴 것입니다. 대신 head가 가리키는 RAM 슬롯에 계속해서 쓰고 있습니다. 인덱스. 그런 다음 쓰기 트랜잭션이 발생했다고 판단하면 head 기록된 값을 잠급니다.

마찬가지로 out_data 모든 클록 주기마다 업데이트됩니다. tail 포인터는 읽기가 발생할 때 단순히 다음 슬롯으로 이동합니다. next_index 함수는 읽기 포트의 주소를 계산하는 데 사용됩니다. RAM이 읽은 후 충분히 빠르게 반응하고 다음 값을 출력하기 시작하도록 하려면 이 작업을 수행해야 합니다.

FIFO의 요소 수 계산

RAM의 요소 수를 계산하는 것은 단순히 head을 빼는 문제입니다. tail에서 . head 래핑된 경우 RAM의 총 슬롯 수로 상쇄해야 합니다. ram_depth를 통해 이 정보에 액세스할 수 있습니다. 일반 입력에서 상수입니다.

PROC_COUNT : process(head, tail)
begin
  if head < tail then
    count <= head - tail + ram_depth;
  else
    count <= head - tail;
  end if;
end process;

또한 count의 이전 값을 추적해야 합니다. 신호. 아래 프로세스는 한 클럭 주기만큼 지연된 버전을 생성합니다. _p1 접미사는 이를 나타내는 명명 규칙입니다.

PROC_COUNT_P1 : process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      count_p1 <= 0;
    else
      count_p1 <= count;
    end if;
  end if;
end process;

업데이트 준비 출력

in_ready 신호는 '1'이어야 합니다. 이 모듈이 다른 데이터 항목을 받아들일 준비가 되었을 때. FIFO가 가득 차 있지 않은 한 이 경우에 해당해야 하며 이것이 바로 이 프로세스의 논리가 말하는 것과 같습니다.

PROC_IN_READY : process(count)
begin
  if count < ram_depth - 1 then
    in_ready_i <= '1';
  else
    in_ready_i <= '0';
  end if;
end process;

동시 읽기 및 쓰기 감지

다음 섹션에서 설명할 코너 케이스 때문에 동시 읽기 및 쓰기 작업을 식별할 수 있어야 합니다. 동일한 클럭 주기 동안 유효한 읽기 및 쓰기 트랜잭션이 있을 때마다 이 프로세스는 read_while_write_p1을 설정합니다. '1'에 신호 다음 클록 주기에서.

PROC_READ_WHILE_WRITE_P1: process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      read_while_write_p1 <= '0';

    else
      read_while_write_p1 <= '0';
      if in_ready_i = '1' and in_valid = '1' and
        out_ready = '1' and out_valid_i = '1' then
        read_while_write_p1 <= '1';
      end if;
    end if;
  end if;
end process;

유효한 업데이트 출력

out_valid 신호는 데이터가 out_data에 표시되었음을 다운스트림 모듈에 나타냅니다. 유효하며 언제든지 샘플링할 수 있습니다. out_data 신호는 RAM 출력에서 ​​직접 옵니다. out_valid 구현 블록 RAM 입력과 출력 사이의 추가 클럭 주기 지연으로 인해 신호가 약간 까다롭습니다.

로직은 조합 프로세스로 구현되어 변화하는 입력 신호에 지연 없이 반응할 수 있습니다. 프로세스의 첫 번째 줄은 out_valid를 설정하는 기본값입니다. '1'에 신호 . 두 개의 후속 If-문이 트리거되지 않은 경우 이 값이 우선 적용됩니다.

PROC_OUT_VALID : process(count, count_p1, read_while_write_p1)
begin
  out_valid_i <= '1';

  -- If the RAM is empty or was empty in the prev cycle
  if count = 0 or count_p1 = 0 then
    out_valid_i <= '0';
  end if;

  -- If simultaneous read and write when almost empty
  if count = 1 and read_while_write_p1 = '1' then
    out_valid_i <= '0';
  end if;

end process;

첫 번째 If 문은 FIFO가 비어 있거나 이전 클럭 주기에서 비어 있는지 확인합니다. 분명히 FIFO는 0개의 요소가 있을 때 비어 있지만 이전 클록 주기에서 FIFO의 채우기 수준도 검사해야 합니다.

아래 파형을 고려하십시오. 처음에 FIFO는 count로 표시된 것처럼 비어 있습니다. 0 신호 . 그런 다음 세 번째 클록 사이클에서 쓰기가 발생합니다. RAM 슬롯 0은 다음 클록 주기에서 업데이트되지만 데이터가 out_data에 나타나기 전에 추가 주기가 걸립니다. 산출. or count_p1 = 0의 목적 문은 out_valid 남아 '0' (빨간색 원) 값이 RAM을 통해 전파되는 동안.

마지막 If 문은 다른 코너 케이스를 보호합니다. 우리는 현재 및 이전 FIFO 채우기 수준을 확인하여 비어 있을 때 쓰기의 특별한 경우를 처리하는 방법에 대해 방금 이야기했습니다. 그러나 count일 때 읽기와 쓰기를 동시에 수행하면 어떻게 될까요? 이미 1입니다. ?

아래의 파형은 그러한 상황을 보여줍니다. 처음에는 FIFO에 하나의 데이터 항목 D0이 있습니다. 한동안 거기에 있었으므로 둘 다 countcount_p1 0 . 그런 다음 세 번째 클록 주기에서 동시 읽기 및 쓰기가 수행됩니다. 하나의 항목이 FIFO를 떠나고 새 항목이 입력되어 카운터가 변경되지 않은 상태로 렌더링됩니다.

읽고 쓰는 순간에는 RAM에 출력할 준비가 된 다음 값이 없습니다. 채우기 수준이 1보다 높으면 있었을 것입니다. 입력 값이 출력에 나타나기 전에 두 개의 클럭 사이클을 기다려야 합니다. 추가 정보가 없으면 이 모서리 케이스를 감지하는 것이 불가능하며 out_valid의 값은 다음 클록 주기(빨간색으로 표시됨)는 '1'로 잘못 설정됩니다. .

이것이 read_while_write_p1가 필요한 이유입니다. 신호. 동시 읽기 및 쓰기가 있음을 감지하고 out_valid을 설정하여 이를 고려할 수 있습니다. '0'로 그 시계 주기에서.

Vivado에서 합성

Xilinx Vivado에서 독립형 모듈로 설계를 구현하려면 먼저 일반 입력에 값을 제공해야 합니다. 이것은 설정을 사용하여 Vivado에서 달성할 수 있습니다. → 일반제네릭/매개변수 메뉴는 아래 이미지와 같습니다.

타겟 디바이스인 Xilinx Zynq 아키텍처의 RAMB36E1 프리미티브와 일치하도록 일반 값이 선택되었습니다. 구현 후 리소스 사용량은 아래 이미지와 같습니다. AXI FIFO는 하나의 블록 RAM과 적은 수의 LUT 및 플립플롭을 사용합니다.

AXI가 준비/유효함 이상입니다.

AXI는 Advanced eXtensible Interface의 약자로 ARM의 AMBA(Advanced Microcontroller Bus Architecture) 표준의 일부입니다. AXI 표준은 읽기/유효한 핸드셰이크 그 이상입니다. AXI에 대해 더 알고 싶으시다면 다음 자료를 읽어보시기를 권장합니다.


VHDL

  1. 클라우드와 클라우드가 IT 세계를 변화시키는 방식
  2. 데이터를 최대한 활용하는 방법
  3. TEXTIO를 사용하여 파일에서 RAM을 초기화하는 방법
  4. IoT를 사용하여 AI를 준비하는 방법
  5. 산업 인터넷이 자산 관리를 변화시키는 방식
  6. 자산 추적 모범 사례:힘들게 번 자산 데이터를 최대한 활용하는 방법
  7. IoT를 더 잘 이해하려면 어떻게 해야 합니까?
  8. 외식업에서 IoT를 최대한 활용하는 방법
  9. 데이터가 어떻게 미래의 공급망을 가능하게 하는지
  10. 공급망 데이터를 신뢰할 수 있게 만드는 방법