이 연속 게시글의 주된 목표는 마이크로컨트롤러(uC)와 필드 프로그래머블 게이트 어레이(FPGA) 간에 데이터를 빠르고 신뢰성 있게 전송하기 위한 메커니즘을 개발하는 것입니다. 이번 글에서는 통신 프로토콜 및 관련된 프레임의 특성을 설명합니다.
uC와 FPGA 간 인터페이스를 설계하는 방법에는 여러 가지가 있습니다. 논의를 시작하기에 앞서, 일반적인 설계 요구 사항을 우선 설정해 보겠습니다:
- uC와 FPGA 간 배선 수 최소화
- uC와 FPGA 간 타겟팅 가능한(주소 지정이 가능한) 정보 교환 지원
- 수천 개의 주소 지정이 가능한 FPGA 레지스터 수용
- 메시지 길이 최적화
- FPGA의 복잡도를 과도하게 증가시키지 않으면서 uC에서 쉽게 프로그래밍
- 고속
- 데이터 무결성을 보장하는 수단 제공
- 플랫폼 간 호환 가능(예를 들어 Xilinx 전용이 아닌)
FPGA 자원의 최소 활용은 설계 요구사항에 포함되지 않습니다. 이렇게 떳떳하게 생략한 이유는 본 연속 게시글의 1부에서 다룹니다. 모든 FPGA 모듈 출력은 레지스터를 통해 처리되고 클럭 도메인 내에서 동기화된다는 점을 상기하십시오. 이 설계 규정은 FPGA 자원의 소비와 설계 안정성 및 문제 해결 시간 감소 간에 균형을 유지하는 무난한 절충안으로 판단됩니다. 이 게시글에서 설명하는 RTL은 최적화되지 않았지만, Digilent Basys 3의 Xilinx Artix-7 XC7A35T-1CPG236C FPGA에서 사용 가능한 전체 자원의 일부만 소모합니다.
프로토콜 컨셉
설계와 그 요구 사항은 하드웨어 및 데이터 프로토콜에서 가장 잘 구체화됩니다. 이는 OSI(Open Systems Interconnect, 개방형 시스템 간 상호 접속) 모델의 1, 2계층에 해당합니다. 1계층인 물리 계층은 전기적 사양을 설명합니다. 이 설계에서는 적은 배선 수를 요구하므로 I2C, SPI, 듀얼 SPI 또는 쿼드 SPI를 사용할 수 있습니다. 이 중에서 SPI는 전이중 통신이 가능하고 uC에서 쉽게 프로그래밍할 수 있다는 점에서 두드러집니다. 참고로, FPGA가 uC보다 데이터를 더 빨리 처리할 수 있으므로 흐름 제어 메커니즘은 필요하지 않다고 가정합니다.
2계층의 데이터 링크 수준은 데이터 교환에 필요한 프로토콜을 정의해야 합니다. 이 계층은 데이터를 적절한 헤더와 오류 검출을 포함하는 적절한 크기의 블록으로 프레이밍하는 것과 관련이 있습니다. 2계층은 1계층에 정의된 전기적 인터페이스와는 직접적으로 관련이 없기 때문에 2계층과 1계층은 서로 독립적입니다. 이러한 독립성으로, 레이어간 바이트 단위 인터페이스가 유지되면 향후 SPI 인터페이스를 쿼드 SPI 또는 심지어 옥타 SPI로 교체할 수도 있습니다.
제가 아는 한, SPI를 사용해 데이터를 교환할 수 있는 기존 프로토콜은 없습니다. 장치별로 구현하는 것이 일반적입니다. 예를 들어, 센서에는 데이터를 읽거나 쓰기 위한 제조업체 또는 장치에 특화된 프로토콜이 있을 수 있습니다. 여기에는 단일 또는 블록 레지스터의 데이터 교환을 위한 규칙도 포함될 것입니다. 일부 장치는 읽기 및 쓰기 작업에 대한 데이터 무결성을 보장하는 수단으로 CRC(Cyclic Redundancy Check, 순환 중복 검사)를 포함할 수도 있습니다. 마지막으로, 대부분의 SPI 장치들은 반이중 프로토콜로 동작합니다. 일반적인 SPI 전송은 읽기/쓰기 플래그가 포함된 바이트로 시작합니다. 그런 다음 장치는 다음 클럭 옥텟에서 적절한 작업을 수행할 것입니다. MOSI와 MISO 라인에서 동시 전송이 가능한 전이중을 찾는 것은 어렵습니다.
uC의 관점에서 전이중 SPI 마스터는 쉽게 구현할 수 있습니다. 아래는 칩 선택 신호 내에 SPI 전송을 프레이밍하는 것이 포함된 아두이노 구현 예제입니다.
// prepare buf for SPI transmission
// append CRC
digitalWrite(CS_PIN, LOW);
SPI.transfer(buffer, bufferSize); // full-duplex: each uC byte in buff is replaced with an FPGA byte
digitalWrite(CS_PIN, HIGH);
// verify and handle CRC
// unpack and handle FPGA data
SPI에 맞게 802.3 프레임을 조정
계속하기 전에, 802.3 이더넷 프레임이 SPI 프로토콜의 시작점 역할을 하므로 이를 살펴보는 것이 좋겠습니다. 그림 1에서 볼 수 있듯이, 이더넷 프레임은 프리앰블, 목적지 및 출발지 MAC 주소, 그리고 길이 필드를 포함하는 헤더로 시작합니다. 그다음에는 페이로드로 46바이트에서 1500바이트까지 달라질 수 있습니다. 그 뒤로 32비트 CRC 코드가 따라옵니다.
그림 1: 단순화한 802.3 이더넷 프레임.
uC가 FPGA 인터페이스에 더 잘 맞도록 이더넷 프레임의 크기를 줄일 수 있습니다. SPI를 사용하면, 프리앰플과 주소 지정이 필요 없습니다. SPI의 CS_not 제어 라인과 SPI의 일대일 특성은 이 필드를 불필요하게 만듭니다. 크기 필드와 길이가 가변적인 페이로드는 유지해야 할 필요가 있는 특성입니다. 이를 통해 uC는 단일 FPGA 레지스터를 읽거나 쓸 수 있으며, 또한 데이터 블록을 전송할 수도 있습니다. 마지막으로 CRC는 데이터 검증을 보장하는 수단이 될 것입니다.
우리가 목적하는 바의 경우, 필드의 크기를 줄일 수 있습니다. 합리적인 절충안은 페이로드의 길이를 나타내는 데 단일 바이트를 사용하는 것입니다. 이는 자연스럽게 프레임의 길이를 헤더와 CRC를 포함해 256 바이트로 제한합니다. 프레임 길이 감소를 고려하면, 16비트 CRC가 합리적입니다.
# 메시지 유형 요구 사항의 규형 맞추기
SPI는 전이중 통신 채널을 제공한다는 점을 상기하십시오. 앞서 설펴본 바와 같이, uC 프로그래밍 관점에서는 그리 복잡하지 않습니다. 그러나 이 전이중 모드를 활용하려면 이제는 FPGA 내부에 있는 특정 하드웨어의 주소를 어떻게 지정할 것인지 결정해야 합니다.
이 방법을 사용하려면, FPGA 하드웨어는 주소 지정이 가능해야 합니다. 예를 들어, FPGA 평가 기판의 LED를 베이스 주소에 연결할 수 있습니다 - Basys 3에 사용된 16개 LED의 경우 2바이트 필요. 기판의 16개 슬라이드 스위치는 또 다른 2바이트 주소에 연결할 수 있을 것입니다. 이런 구조에서는 FPGA는 마치 uC의 또 다른 주변 장치처럼 동작합니다. uC는 SPI를 통한 프레임 프로토콜을 사용하여 FPGA 하드웨어를 읽거나 씁니다.
이 글에서 앞으로 FPGA 하드웨어를 주변 장치라고 부르겠습니다. 이는 uC가 SPI 인터페이스를 통해 FPGA를 사실상 고성능의 주변 장치로 사용하고 있는 설계를 반영한 것입니다.
설계 요구 사항 중 하나는 몇 개에서 수천 개에 이르는 FPGA 레지스터에 주소를 지정할 수 있는 확장성입니다. 이러한 요구 사항을 고려할 때, 256개로 제한하는 1바이트 폭의 주소는 너무 열악합니다. 그러나 2바이트 폭 인터페이스는 uC가 FPGA 내부에 최대 65536개의 주소를 지정할 수 있기 때문에 적합합니다.
이쯤에서 멈추고 SPI 프레임의 헤더를 정의해 보겠습니다. SPI 프레임에는 명령 유형(읽기 또는 쓰기)을 나타내는 1바이트, 페이로드 길이를 나타내는 1바이트, 주소를 위한 2바이트, 가변 길이의 페이로드와 CRC가 포함됩니다. 결과적으로 헤더는 4바이트 길이이며, 페이로드는 최대 250바이트이고, CRC는 2바이트입니다.
이 정도면 대부분의 설계에서는 충분할 수 있습니다. 그러나 이 방법은 암묵적으로 반이중이라는 것을 가정하고 있습니다. 그 이유를 이해하기 위해 SPI 전송과 관련된 인과관계를 생각해 보겠습니다. 예를 들어, 기판의 16개 LED를 제어하는 레지스터가 베이스 주소 0x0200에 위치해 있다고 가정해 보겠습니다. 이 레지스터에서 SPI 전송을 시작하면, 사실상 FPGA 레지스터를 읽은 다음에 쓸 것입니다. 읽기 작업은 지난 데이터를 포함하기 때문에 무의미합니다. 여기서 우리는 멀티바이트 레지스터의 업데이트와 관련된 불안정성을 방지하기 위해 모든 FPGA 레지스터에 더블 버퍼링이 구현되어 있다고 가정하겠습니다.
전이중 통신을 위해서는 또 하나의 주소 지정 필드를 추가해야 합니다. 예를 들어, FPGA 기판의 스위치 상태를 저장하는 레지스터가 0x0202 주소에 위치해 있다고 가정해 보겠습니다. 프레임에 또 하나의 주소 지정 필드를 추가하면, 우리는 이제 읽기 및 쓰기 작업을 동시에 수행할 수 있습니다.
이제 우리는 LED를 업데이트하는 동시에 스위치 상태를 읽을 수 있습니다.
새로운 헤더는 5바이트 길이로, 페이로드는 최대 249바이트가 될 수 있으며, CRC는 2바이트입니다. 명령 바이트는 더 이상 필요하지 않습니다.
- 페이로드 길이: 1바이트, 헤더와 페이로드의 총 바이트 수
- 목적지 주소 필드: 2바이트, 써야 할 FPGA 하드웨어를 나타냄
- 출발지 주소 필드: 2바이트, 읽어야 할 FPGA 하드웨어 나타냄
- 페이로드: 1에서 249바이트
- CRC: 2바이트
두 옵션을 면밀히 비교해 보면, 1바이트의 차이가 있는 것을 알 수 있습니다. 읽기/쓰기 명령 바이트가 있는 반이중 프레임은 FPGA 기판의 16개 LED에 데이터를 쓰기 위해 8바이트를 전송할 필요가 있습니다. 이에 상응하는 전이중 프레임은 9바이트가 필요하지만, LED에 데이터를 쓰는 동시에 스위치를 읽을 수 있습니다. 이를 어떻게 바라보느냐에 따라, 처리에 1바이트를 낭비했다고 볼 수 있지만, 한편으로는 해당 트랜잭션에 소요되는 8바이트와 시간 오버헤드를 절약했다고 볼 수도 있습니다.
이러한 설계 상의 반대급부를 고려하여, 전이중 옵션에만 집중할 것입니다. uC의 오버헤드와 FPGA 프로그래밍 오버헤드는 중요하지 않습니다. 작은 페이로드의 전송에 걸리는 속도 차이는 아주 작으며, 큰 페이로드의 경우 이러한 변화가 중요하지 않습니다.
이러한 전이중 프로토콜에는 단점이 하나 있습니다. 모든 데이터 전송이 이제는 쓰기 작업을 포함한다는 사실입니다. 어떤 경우에는 이렇게 하는 것이 불편할 수 있습니다. 쓰기 작업을 구별하는 대신, FPGA 주소 중 비어 있거나 기능하지 않는 블록인 0xFF00에서 0xFFFF까지를 따로 잡아 둘 것입니다. 읽기 전용 메모리 작업이 필요한 경우, uC는 이 비어 있는 곳에 "쓰기"를 할 것입니다. 이러한 간단한 수정으로 전이중 방식을 반이중 읽기 방식으로 전환할 수 있습니다.
FPGA에 쓰기 및 읽기
uC와 FPGA 간 전이중 프로토콜에 대한 명령 및 응답 프레임이 그림 2에 나와 있습니다. 헤더 내용은 프레임 간에 서로 밀접하게 관련되어 있습니다. 응답에 읽기 주소와 길이 헤더 정보가 반향되어 있는 것을 알 수 있습니다. 응답과 관련된 인과관계 문제가 있다는 점에 주의해야 합니다. 필드는 동시에 바이트 단위로 스트리밍 되기 때문에 응답이 지연됩니다. 예를 들어, (명령 프레임에서) 첫 번째로 전송되는 길이 필드는 FPGA가 전송할 첫 번째 바이트를 결정할 때 아직 수신되지 않았기 때문에 응답에서 첫 번째 필드가 될 수 없습니다. 쓰기 주소는 응답 프레임에서 중요하지 않기 때문에 문제가 되지 않습니다. 대신, uC가 첫 번째 바이트에 길이를 스트리밍하는 동안 FPGA는 프로그램 전용 플래그를 스트리밍합니다.
프레임의 나머지 필드들도 비슷합니다. FPGA 응답은 베이스 읽기 주소를 시작으로 데이터를 스트리밍합니다. 그 사이 FPGA는 uC 데이터를 임시 버퍼로 동시에 이동시킵니다. 각각의 CRC는 계산되어 프레임에 추가됩니다.
그림 2: 명령 프레임과 응답 프레임 간의 관계.
명령 프레임의 데이터 무결성
CRC는 데이터 무결성을 보장하는 수단을 제공합니다. CRC는 프레임 내용에 적용된다는 점을 상기하십시오. uC 관점에의 세 단계 과정을 생각해 보면, 먼저 uC는 관련 데이터가 포함된 프레임 버퍼를 준비합니다. 그런 다음, CRC를 계산하고 버퍼에 추가하는 함수를 호출합니다. 마지막으로, uC는 SPI를 통해 데이터를 전송하는 함수를 호출합니다.
FPGA는 스트리밍 데이터를 버퍼에 저장하는 동시에 명령 프레임의 CRC를 계산합니다. 프레임이 종료되면, FPGA는 수신된 명령 프레임 CRC와 계산된 CRC를 비교합니다. 두 값이 일치하면, FPGA는 데이터를 버퍼에서 관련 하드웨어 레지스터로 빠르게 전송합니다. 전체 251바이트의 페이로드 처리에 대략 5us가 필요한 비교적 빠른 작업입니다. 그러나 수신된 CRC와 계산된 CRC가 다를 경우 FPGA는 프레임을 폐기하고 프레임 오류 메커니즘을 작동시킵니다.
그림 2의 응답 프레임에서 볼 수 있듯이, 문제 해결을 위해 프레임 오류 카운터가 포함되어 있습니다. FPGA에서 uC로 연결되는 물리적 신호선에 대한 프로비젼을 추가할 수도 있습니다. 이를 통해 uC는 프레임 오류를 빠르게 감지하고 대응할 수 있습니다.
FPGA 임시 프레임 버퍼의 중요성은 아무리 강조해도 지나치지 않습니다. CRC에 의해 검증되기 전까지 FPGA는 새 데이터에 아무런 조치를 취하지 않습니다. 이 중요한 주제는 다음 게시글에서 보다 자세히 다룰 예정입니다.
응답 프레임의 데이터 무결성
응답 프레임의 무결성을 위해서는 uC에서 신중하게 프로그래밍해야 합니다. 우선, 명령 프레임이 확인되지 훨씬 전부터 FPGA는 uC의 읽기 요청에 즉시 응답한다는 점을 우선 인지하고 있어야 합니다. 결과적으로, FPGA가 생성한 데이터의 무결성뿐만 아니라 명령에 대한 FPGA의 이해를 검증하는 것은 uC의 몫입니다. 이를 위해 응답 프레임에 프레임 길이와 베이스 읽기 주소를 반향합니다. uC는 응답 프레임을 명령 프레임의 데이터와 비교하여 오류를 식별할 수 있습니다.
예를 들어, uC에서 FPGA에 전송될 때 명령 프레임의 읽기 주소 필드에 비트 오류가 발생했다고 가정해 보겠습니다. 오류는 FPGA의 CRC 기술에 의해 매우 높은 확률로 탐지될 것이므로 uC에서 FPGA로의 쓰기 작업에서는 높은 수준의 데이터 무결성이 보장됩니다. FPGA는 해당 프레임을 무시할 것입니다. 동시에 우리는 FPGA의 병렬 구조와 관련된 인과 관계도 고려해야만 합니다.
FPGA는 명령 프레임에 오류가 발생했다는 것을 알기 훨씬 전부터 읽기 데이터를 스트리밍합니다. 사실상, FPGA는 오류가 있는 읽기 주소로부터 충실하게 데이터 스트리밍을 시작합니다. 심지어 프레임에 유효한 CRC를 추가할 수도 있습니다.
트랜잭션이 완료되면 uC는 응답 프레임 전체의 사본을 가지게 됩니다. 작업을 수행하기 전 데이터를 검증해야만 하며, 이는 두 단계로 이루어집니다:
- uC는 전송된 필드와 수신된 필드의 길이 및 읽기 주소가 일치하는 지 우선 확인해야 합니다.
- 그 다음, 응답 프레임의 CRC를 검증해야 합니다.
두 작업이 모두 완료되면, uC는 통신 오류를 처리하기 위해 적절한 조치를 취할 수 있습니다.
여러분의 의견과 제안을 환영합니다. 특히 고급 RTL 시스템 설계 방법론에 대한 추가 논의를 환영합니다.
감사합니다,
APDahlen
영문 원본: Implementing a Robust Microcontroller to FPGA SPI Interface: Part 2 – Protocol Definition