실제 보드를 다루다 보면, 외부에서 받아서 한 번만 저장해 두면 되는 설정값을 EEPROM에 보관했다가 전원 인가 후에는 온칩 리소스(BRAM/LUT)만 바라보고 싶다는 요구가 반복해서 나온다. 플래시에 비트스트림과 펌웨어를 두고, 모듈별 세부 파라미터는 EEPROM에 저장한 뒤, 부팅 후에는 BRAM에 올려 놓고 읽기 전용으로 사용하는 구조를 정리해 둘 필요가 있었다. 작성자: kiwanPark

여기서는 “외부에서 설정값을 수집→EEPROM에 저장→전원 인가 후에 FPGA가 EEPROM에서 BRAM/LUT로 설정을 로드→런타임에는 BRAM/LUT만 참조”라는 흐름을 기준으로, 아키텍처와 로직 설계 방식을 정리한다. 구체적인 디바이스는 Artix-7 계열과 일반적인 I²C/SPI EEPROM 조합을 가정하지만, 구조 자체는 다른 FPGA·EEPROM 조합에도 그대로 이식 가능하다.

문제 설정

먼저 정리해야 할 것은 이 설정값이 언제, 누가, 어떤 인터페이스를 통해 바꾸는가이다. 현장에서 주로 나오는 패턴은 다음과 같이 압축된다.

  • 초기 생산 단계에서는 공장 프로그램머나 JTAG, UART 부트로더 등이 EEPROM에 값을 쓴다.
  • 현장에서는 값이 자주 바뀌지 않고, 기기 재부팅 시점에만 반영되면 충분하다.
  • 런타임 중에는 설정 읽기가 압도적으로 많고, 쓰기는 거의 없거나 유지보수 모드에서만 발생한다.

이 전제를 놓고 보면, 런타임 경로에서 EEPROM을 직접 물고 가는 것은 굳이 필요하지 않다. 대신 부팅 직후 한 번만 읽어서 BRAM에 옮기고, 이후에는 BRAM을 사실상의 설정 ROM처럼 사용하는 구조로 정리하는 편이 타이밍과 디버깅 모두에서 유리하다.

전체 아키텍처

EEPROM→BRAM/LUT 로드 구조를 한 줄로 요약하면 다음과 같다.
EEPROM 인터페이스 블록 + 설정 로더 FSM + 설정 뱅크(BRAM/LUT) + 상태 모니터링/에러 플래그 조합으로 생각하면 정리가 쉽다.

  1. EEPROM 인터페이스 블록: I²C 또는 SPI 마스터로 동작하며, 지정된 주소 범위를 순차적으로 읽어 온다.
  2. 설정 로더 FSM: 전원 인가(또는 리셋 해제) 이후, EEPROM에서 특정 바이트 수만큼 읽어 BRAM에 써 넣는 상태 기계다.
  3. 설정 뱅크(BRAM/LUT): 읽기 전용 주소 공간처럼 사용하는 BRAM 또는 LUT ROM이다. 이 영역이 실제 연산 경로에서 참조하는 “진짜 설정값”이 된다.
  4. 상태/에러 플래그: 로딩 완료 플래그, 체크섬 검증 실패, EEPROM 응답 타임아웃 등 상태 정보를 레지스터로 남겨서 상위 소프트웨어나 디버깅 로직이 참조할 수 있게 한다.

이렇게 나누어 두면 설계 상에서 “언제까지 EEPROM을 바라봐야 하는지”가 명확해진다. 다음으로는 각 블록을 조금 더 세부적으로 본다.

EEPROM 인터페이스와 데이터 포맷

EEPROM 인터페이스는 이미 검증된 I²C/SPI 마스터 IP를 재사용하는 것이 합리적이다. 여기서 중요한 것은 데이터 포맷을 단순하게 유지하는 것이다. 로더 FSM이 해석해야 하는 포맷이 복잡해질수록 RTL 수준의 변경이 늘어나기 때문이다.

경험상 유지보수 관점에서 무리가 덜한 포맷은 다음과 같은 구조다.

  • [헤더 영역]
    • 매직 넘버 (예: 0xA5 0x5A)
    • 버전 (주/부 버전)
    • 전체 길이
    • 체크섬 (CRC16/CRC32 등)
  • [페이로드 영역]
    • 고정 길이 슬롯 배열 (예: 16비트 × N개)

이렇게 구성하면 로더 FSM은 “헤더를 읽어 유효성을 확인→길이만큼 순차 읽기→BRAM에 그대로 써 넣기” 정도의 단순한 흐름으로 유지할 수 있다. 슬롯마다 의미(예: 채널별 gain, offset, 임계값 등)는 상위 설계 문서에서 관리하고, RTL은 인덱스를 통해 접근하도록 만드는 것이 장기적으로 안전하다1.

BRAM/LUT 기반 설정 뱅크 설계

설정값을 담는 메모리는 두 가지로 나뉜다. 런타임에 덮어쓸 필요가 있는 값은 BRAM, 완전히 고정된 상수 테이블은 LUT ROM에 두는 패턴이 합리적이다.

  • BRAM 쪽은 true dual-port 구성으로 하나의 포트는 로더 FSM이 쓰기 전용으로 사용하고, 다른 포트는 시스템 로직이 읽기 전용으로 사용한다.
  • LUT ROM 쪽은 Vivado/Quartus의 초기화 파일(MIF/COE 등)을 통해 bitstream에 직접 박아 넣고, EEPROM에는 넣지 않는다.

이 구조를 쓰면 “EEPROM에서 읽어 오는 값”과 “설계 시점에 고정된 상수”를 물리적으로 분리할 수 있다. 나중에 필드 업데이트가 필요할 때는 EEPROM 이미지에만 패치를 적용하고, bitstream을 건드리지 않아도 되는 경우가 많아진다2.

로더 FSM의 상태 설계

로더 FSM은 다음과 같은 상태로 나누면 구현이 단순해진다.

  • IDLE: 리셋 상태, 상위에서 load_start가 들어오기를 기다린다.
  • SEND_CMD: EEPROM에 읽기 명령과 시작 주소를 전송한다.
  • READ_HEADER: 헤더 영역을 읽고 매직 넘버, 버전, 길이, 체크섬 등을 임시 레지스터에 저장한다.
  • CHECK_HEADER: 헤더 유효성 검증. 실패 시 ERROR로 진입하고, 성공 시 READ_PAYLOAD로 넘어간다.
  • READ_PAYLOAD: 정해진 길이만큼 바이트를 받아 BRAM에 순차적으로 기록한다.
  • VERIFY: 필요하다면 BRAM에 적힌 값을 기반으로 다시 체크섬을 검증한다.
  • DONE: 로딩 완료 플래그를 올리고, 이후에는 시스템 로직이 BRAM을 자유롭게 읽을 수 있다.
  • ERROR: 에러 코드를 레지스터에 남기고, 필요 시 재시도나 안전한 기본값 경로로 분기한다.

이 구조의 핵심은 EEPROM 인터페이스와 BRAM 쓰기를 느슨하게 결합하는 것이다. I²C/SPI 마스터로부터 “바이트 유효” 신호가 들어올 때마다 쓰기 포인터를 한 칸씩 증가시키는 방식으로 쌓아 올리면 된다. 이때 타임아웃, NACK 등의 예외 상황을 어떻게 처리할지는 상태 기계의 ERROR 분기에서 집중적으로 처리하는 편이 디버깅에 유리하다3.

초기값과 폴백 전략

실제 시스템에서는 EEPROM이 비어 있거나 손상된 상태로 전원이 들어오는 경우를 항상 고려해야 한다. 이때 BRAM/LUT 초기값 전략이 중요해진다.

  • BRAM에는 bitstream 생성 시점에 합리적인 디폴트값을 COE/MIF 형태로 초기화해 두고, EEPROM 로딩이 성공하면 그 위를 덮어쓴다.
  • EEPROM 헤더가 유효하지 않거나 체크섬이 틀리면, 로더 FSM은 ERROR 상태로 들어가고, BRAM은 디폴트값을 그대로 유지한다.
  • 상위 소프트웨어는 load_done, load_error 플래그를 읽어서 현재 설정이 EEPROM 기반인지, 디폴트 기반인지를 구분할 수 있다.

이렇게 하면 “EEPROM이 망가졌을 때 장비가 완전히 죽는” 상황을 피할 수 있고, 현장에서의 롤백 전략도 단순해진다. 설계 단계에서 디폴트값을 어떻게 정할 것인지는 별도의 문서로 관리하되, RTL에서는 “디폴트 → EEPROM 덮어쓰기”의 순서만 명확히 보장하면 된다.

상위 시스템과의 인터페이스

마지막으로, 이 로직이 상위 시스템(예: SoC의 ARM 코어, 외부 MCU, 호스트 PC)과 어떻게 연결되는지를 정리해야 한다. 일반적인 패턴은 다음과 같다.

  • 상위 소프트웨어는 부팅 시점에 load_start를 1로 올리고, load_done이 1이 될 때까지 폴링하거나 인터럽트를 기다린다.
  • load_error가 1이면, 에러 코드를 읽어 로그를 남기고, 필요 시 EEPROM 리프로그램 루틴을 태운다.
  • 평상시에는 BRAM에 맵핑된 설정 공간을 메모리 맵 레지스터처럼 읽기만 한다.

이 흐름을 지키면, 실제 연산 경로는 EEPROM의 존재를 거의 의식하지 않아도 된다. EEPROM은 사실상 “부팅 시 한 번 읽고 잊어버리는 설정 저장소”로만 동작하고, 런타임 타이밍은 온칩 메모리(BRAM/LUT)의 특성을 그대로 따른다.

마치며

EEPROM→BRAM/LUT 로딩 구조를 한 번 잡아두면, 이후 다른 프로젝트에서도 거의 같은 패턴을 재사용할 수 있다. 특히 부팅 시점의 상태 기계와 에러 처리, 디폴트값 전략을 처음부터 명시적으로 설계해 두면, 현장에서의 디버깅 시간이 눈에 띄게 줄어드는 느낌이 있다.
다음에는 EEPROM과 별도로, SPI 플래시나 외부 DRAM에서 대량의 룩업 테이블을 스트리밍으로 옮기는 구조까지 확장해 볼 계획이다.

References

  1. Xilinx 7 Series FPGAs Configuration User Guide (UG470) - 구성 메모리 및 부트 흐름 설명 

  2. Intel FPGA Memory Initialization Files - MIF/HEX 기반 메모리 초기화 방식 설명 

  3. Microchip 24xx Series I²C EEPROM Datasheet - I²C EEPROM 프로토콜과 타임아웃·에러 조건