[Zynq FIR IP 개발 #1: Block Design부터 비트스트림·XSA까지]

YOUNGWOONG HAN·2026년 5월 6일

Zynq FIR IP 개발

목록 보기
1/5
post-thumbnail

Zynq FIR IP의 구조 이해

개발 환경: Vivado 2024.2 / Zybo Z7-20 (xc7z020clg400-1) / Ubuntu
대상 IP: fir_decimator_n43_axis (N=43 transposed FIR + M=2 데시메이터, 16-bit AXI-Stream)
입력 8192 샘플 → 출력 4096 샘플

시뮬레이션 골든은 4117샘플이다. 테스트벤치는 마지막 입력 뒤에도 0을 밀어 넣어 파이프라인에 남은 42샘플까지 전부 flush하므로 FIR 출력이 8192+43−1 = 8234, 여기서 M=2 데시메이션하면 4117이 된다. 보드는 DMA가 8192샘플만 밀어 넣고 끝내므로 테일이 나오지 않고 정확히 8192/2 = 4096이다. 이 글부터는 보드 기준 4096을 쓴다.

  ① PS(C코드): 멀티톤 신호 합성
         ↓
  ② PS: DDR에 입력 배열 저장
         ↓
  ③ PS: DMA에 전송 명령 (시작 주소, 길이)
         ↓
  ④ DMA MM2S: DDR → AXI-Stream → FIR S_AXIS (입력)
         ↓
  ⑤ FIR IP: 필터링 + 데시메이션 (M=2)
         ↓
  ⑥ DMA S2MM: FIR M_AXIS → AXI-Stream → DDR (출력 배열 저장)
         ↓
  ⑦ PS(C코드): DDR에서 결과 읽기 → UART 출력

③~⑥ 구간에서 PS는 명령만 내리고, 실제 데이터 이동은 DMA가 CPU 개입 없이 처리한다. 이것이 DMA를 쓰는 이유다.

DMA는 채널이 2개

  • MM2S (Memory-Mapped to Stream): DDR → FIR 방향
  • S2MM (Stream to Memory-Mapped): FIR → DDR 방향

생성하려는 block design

bd_fir_dma: custom fir ip와 PS ip, DMA ip를 붙인 block design

Block Design: bd_fir_dma
├── ZYNQ7 Processing System   ← IP 1
├── AXI DMA                   ← IP 2
└── fir_decimator_n43_axis    ← IP 3 (우리 것)
#IP 이름출처역할
1ZYNQ7 Processing SystemXilinx 기본 제공ARM 코어 + DDR + HP0 포트
2AXI Direct Memory AccessXilinx 기본 제공DDR ↔ AXI-Stream 브리지
3fir_decimator_n43_axis우리가 만든 RTLFIR 필터 + 데시메이터

Vivado가 "Run Connection Automation" 시 자동으로 추가하는 IP 3개:

#IP 이름역할
4Processor System ResetPS 리셋 신호 동기화
5AXI SmartConnectPS GP0 → DMA AXI-Lite 연결 중계
6AXI InterconnectDMA MM2S/S2MM → PS HP0 메모리 경로 병합

큰 그림 1: Block Design 생성

GUI에서 IP들을 배치하고 연결하는 것:

  • Zynq PS7 IP — ARM 코어, HP0 포트 활성화
  • AXI DMA IP — DDR ↔ AXI-Stream 브리지
  • Custom FIR IP (fir_decimator_n43_axis) — Module Reference로 삽입

연결:

AXI DMA MM2S → (AXI-Stream) → FIR S_AXIS (입력)
AXI DMA S2MM ← (AXI-Stream) ← FIR M_AXIS (출력)
AXI DMA ↔ PS7 HP0 (DDR 직접 접근)

큰 그림 2: 비트스트림 생성 + XSA 내보내기

Block Design → Synthesize → Implement → Generate Bitstream → Export Hardware (.xsa)

Vivado GUI로 Block Design 완성하기

진행 순서
1. 새 프로젝트 생성 (Board: Zybo Z7-20)
2. Create Block Design
3. IP 추가: ZYNQ7 Processing System
4. IP 추가: AXI DMA
5. IP 추가: 우리 FIR (Module Reference)
6. Create HDL Wrapper
7. Generate Bitstream
8. Export Hardware

1. 새 프로젝트 생성

  1. Create Project 클릭
  2. Project name: fir_decimator_trans_n43
  3. Project location: 원하는 경로 설정
  4. Project type: RTL Project
    Sources 추가할 때 RTL 파일들 넣기:
    • rtl/transposed_form/n43/fir_decimator_n43_axis.v ← 최상위 top
    • rtl/transposed_form/decimator_m2_phase0.v
    • rtl/transposed_form/n43/fir_decimator_n43.v
    • rtl/transposed_form/n43/fir_n43.v
  5. constraint file은 포함하지 말기! 나는 실수로 포함시킴
  6. Default Part에서 Board 탭 → Zybo Z7-20 선택
  7. finish

2. Create Block Design

Vivado 좌측 Flow Navigator에서:

  1. IP INTEGRATORCreate Block Design 클릭
  2. Design name: bd_fir_dma

3. IP 추가: ZYNQ7 Processing System

  1. 캔버스 우클릭 → Add IP → 검색창에 zynq 입력 → ZYNQ7 Processing System 더블클릭

  2. PS 설정하기
    블록 더블클릭 이후 수정할 두 가지 항목 확인

1. 포트 활성화
PS-PL ConfigurationHP Slave AXI InterfaceS AXI HP0 Interface 체크

  • HP0: PL(DMA)이 PS(CPU)의 DDR에 접근하는 유일한 경로, 기본 비활성화이므로 활성화 해주기
  • GP 포트는 PS→PL 방향 마스터 포트라서 이 용도로 쓸 수 없다.

2. 클럭 확인
Clock ConfigurationPL Fabric ClocksFCLK_CLK0 가 100MHz인지 확인

4. IP 추가: AXI DMA

  1. 캔버스 우클릭 → Add IP → 검색창에 axi dma 입력 → AXI Direct Memory Access 더블클릭

  1. DMA 설정하기

블록 더블클릭 이후 확인/수정할 항목 확인

상단

항목설명
Enable Scatter Gather Engine해제 ✓Simple DMA 모드. 연속 배열 하나를 옮기는 구조라 시작 주소와 길이만 넘기는 Simple DMA로 충분
Enable Micro DMA해제기능 제한된 경량 DMA. 해당 없음
Enable Multi Channel Support비활성Scatter Gather 전용. 해당 없음
Enable Control/Status Stream비활성Scatter Gather 전용. 해당 없음
Width of Buffer Length Register14 bits최대 전송 크기 2¹⁴ -1 = 16383 bytes. 출력 4117샘플 × 2bytes ≈ 8KB이므로 충분
Address Width32 bitsZynq-7000은 32비트 주소 체계. 맞음

⚠️ 이 판단이 나중에 MM2S timeout의 원인이 된다. 여기서 내가 계산에 쓴 건 출력 쪽 길이였다. 그런데 이 설정은 채널별이 아니라 MM2S와 S2MM에 함께 적용되는 파라미터다. 실제 제약은 둘 중 큰 쪽이 건다.

S2MM (출력) : 4096샘플 × 2B =  8192 bytes  →  16383 이내, 문제 없음
MM2S (입력) : 8192샘플 × 2B = 16384 bytes  →  16383을 정확히 1 byte 초과

FIR는 M=2 데시메이터라 입력이 출력의 두 배인데, 하필 여유를 확인한 쪽이 작은 출력이었다.
이 시점의 나는 몰랐고, 보드를 올린 뒤에야 알게 된다. 상세는 3편.


Read Channel (MM2S) — DDR → FIR 방향

항목설명
Number of Channels1채널 하나면 충분
Memory Map Data Width32DDR(HP0) 쪽 버스 폭. 32비트 표준
Stream Data Width16FIR s_axis_tdata 폭과 일치
Max Burst Size16DDR 읽기 시 한 번에 16 beat. 기본값으로 충분
Allow Unaligned Transfers해제16비트 샘플은 정렬 보장됨

Write Channel (S2MM) — FIR → DDR 방향

항목설명
Number of Channels1채널 하나면 충분
Memory Map Data Width32 (AUTO)DDR 쪽 버스 폭. 동일
Stream Data Width16 (MANUAL)FIR m_axis_tdata 폭과 일치
Max Burst Size16DDR 쓰기 시 한 번에 16 beat
Allow Unaligned Transfers해제정렬 보장됨

하단

항목설명
Enable Single AXI4 Data InterfaceAUTO/해제MM2S와 S2MM이 DDR 포트를 각각 따로 씀. 동시 읽기+쓰기 가능

5. IP 추가: Custom FIR

커스텀 RTL을 BD에 넣는 방법은 IP Packaging과 Module Reference 두 가지다. IP Packaging 방식은 재사용할 때 이득이 있다. 해당 FIR IP는 이 Block Design 하나에서만 쓰이고 AXI-Stream 인터페이스도 이미 RTL에 있어서, Module Reference 방식을 사용했다.

  1. 캔버스 우클릭 → Add Modulefir_decimator_n43_axis 선택
  1. 캔버스 상단 초록색 배너 "Run Connection Automation" 클릭

  2. 모든 항목 체크 : Vivado가 SmartConnect, Processor System Reset, AXI Interconnect를 자동 생성하고 연결한다.

항목연결되는 경로설명
axi_dma_0 → S_AXI_LITEPS GP0 → SmartConnect → DMA S_AXI_LITEPS가 DMA 레지스터에 명령을 쓰는 제어 경로
processing_system7_0 → S_AXI_HP0DMA M_AXI_MM2S/S2MM → PS HP0DMA가 DDR을 읽고 쓰는 데이터 경로

FIR의 AXI-Stream 포트(s_axis, m_axis)는 이 목록에 나오지 않는다. Vivado가 DMA의 어느 채널을 FIR의 어느 포트에 연결할지 판단하지 못하기 때문이다. 아래의 7번에서 수동 연결한다.

⚠️ Connection Automation이 DMA 클럭을 다 연결해주지는 않는다.

Validate Design에서 이런 에러가 났다.

[BD 41-758] /axi_dma_0/m_axi_s2mm_aclk is not connected to a valid clock source

DMA의 클럭 포트는 s_axi_lite_aclk, m_axi_mm2s_aclk, m_axi_s2mm_aclk 세 개인데, Run Connection Automation이 이걸 전부 연결해주지는 않는다. 셋 다 연결돼 있는지 확인하고, 빠진 것은 PS의 FCLK_CLK0에 수동으로 연결한다.

6. 자동 생성된 IP:AXI Interconnect 확인

"Run Connection Automation"으로 자동 생성된 axi_mem_intercon(AXI Interconnect) 더블클릭

  • Number of Slave Interfaces → 2

DMA는 DDR 쪽으로 M_AXI_MM2S(읽기)와 M_AXI_S2MM(쓰기) 두 개의 마스터 포트를 낸다.
이 둘이 PS의 HP0 하나로 들어가야 하므로 Interconnect의 slave interface가 2개 필요하다.
MM2S/S2MM을 한 번에 자동 연결했다면 Vivado가 이미 2로 잡아준다.

7. FIR 모듈 수동 연결

포트 연결 요약:

PS GP0 → SmartConnect → DMA S_AXI_LITE     (PS가 DMA 제어)
DMA M_AXI_MM2S → PS HP0                    (DMA가 DDR 읽기)
DMA M_AXI_S2MM → PS HP0                    (DMA가 DDR 쓰기)
DMA M_AXIS_MM2S → FIR S_AXIS               (DDR → FIR)
FIR M_AXIS → DMA S_AXIS_S2MM               (FIR → DDR)

배선 전에 알아야 할 두 가지

1. AXI-Stream은 번들을 통째로 연결한다.
BD에서 AXI-Stream 포트는 wire 묶음이 아니라 인터페이스로 관리된다. 펼쳐서 tdata/tvalid/tready/tlast를 하나씩 연결하면 Vivado가 AXI-Stream 프로토콜 연결로 인식하지 못하고, DMA도 S_AXIS_S2MM 인터페이스 단위로만 인식해서 핸드셰이크가 보장되지 않는다.

2. 빈 캔버스에 drop하면 외부 포트가 생긴다.
대상 포트 위가 아닌 곳에 놓으면 Vivado가 Make External을 실행해 FPGA 핀으로 노출시키고, 비트스트림 단계에서 [DRC NSTD-1] 39 out of 39 logical ports use I/O standard 'DEFAULT'로 터진다. get_bd_ports *로 확인·삭제하고 초록 체크가 뜰 때 놓아 재연결한다.

첫 번째: aclk 연결

FIR 블록의 aclk 포트 → PS 블록의 FCLK_CLK0 포트로 드래그

두 번째: aresetn 연결

FIR 블록의 aresetn 포트 → rst_ps7_0_100M 블록의 peripheral_aresetn 포트로 드래그

aresetn : FIR IP의 active-low 리셋 포트

세 번째: DMA → FIR 입력 연결

axi_dma_0 블록의 M_AXIS_MM2S 포트 → FIR 블록의 s_axis 포트로 드래그

네 번째: FIR → DMA 출력 연결

FIR 블록의 m_axis 포트 → axi_dma_0 블록의 S_AXIS_S2MM 포트로 드래그

다섯 번째: Run Block Automation 클릭
상단 녹색 배너의 Run Block Automation 클릭

※ Block Automation은 PS7 IP를 추가한 직후(3번)에 실행하는 게 일반적이다. DDR / FIXED_IO를 외부 포트로 빼주는 단계라 언제 눌러도 결과는 같지만, 따라 하는 입장이라면 PS 추가 직후에 눌러도 된다. 나는 배선을 다 한 뒤에 눌렀다.


여섯 번째: 검증하기
단축키 F6를 눌러 Validate Design 진행

Digilent 보드 파일을 쓰는 Zybo 설계에서는 흔히 뜨는 경고라고 한다.

다만 넘기기 전에 DDR 파트는 확인해야 한다. 이 시점의 BD에는 PS7 DDR 파트가 Zybo Z7-20이 아니라 Z7-10용 파트로 들어가 있었다. 보드 파일이 제대로 적용되지 않은 채로 BD가 만들어졌기 때문이고, 그대로 아래 11번에서 TCL로 추출까지 됐다. ps7_init은 DDR 파트를 기준으로 CAS latency, tRCD, tRP 같은 타이밍 레지스터를 설정하므로, 파트가 틀리면 DDR이 불안정해지고 DMA 전송 실패로 이어질 수 있다.

get_property CONFIG.PCW_UIPARAM_DDR_PARTNO [get_bd_cells /processing_system7_0]
# MT41K256M16 RE-125 → 정상 (Z7-20)
# MT41J128M8 JP-125  → 잘못됨 (Z7-10). BD 상단 녹색 배너에서 보드 파일 재적용

8. Create HDL Wrapper

Sources 탭에서 Block Design 파일(bd_fir_dma) 우클릭 → Create HDL WrapperLet Vivado manage wrapper and auto-update 선택

9. Generate Bitstream

SynthesisImplementationGenerate Bitstream

error 1: 비트스트림 생성 실패

로그 첫 줄: synth_design -top fir_decimator_n43_axis

error 1 해결

두 가지 문제가 동시에 존재했다.

문제 1: 합성 Top 모듈 오설정

합성 Top 모듈이 bd_fir_dma_wrapper가 아닌 fir_decimator_n43_axis로 설정되어 있었다.
이는 Block Design 생성 이전에 FIR IP 단독 검증을 위해 fir_decimator_n43_axis를 Top으로 설정해 두었던 상태가 그대로 남아 있었기 때문이다. Block Design Wrapper가 아닌 FIR 모듈 단독으로 합성을 시도하니 DMA, PS 등 나머지 연결이 전혀 없는 불완전한 설계가 합성 대상이 되었다.

→ Sources 패널에서 bd_fir_dma_wrapper를 우클릭 → Set as Top으로 변경

문제 2: 불필요한 XDC 파일 활성화

로그에서 zybo_n43.xdc가 포함되어 있었다. 이 XDC 파일은 FIR IP를 단독으로 합성할 때 사용하던 핀 제약 파일로, fir_decimator_n43_axis의 포트를 물리 핀에 직접 매핑하는 내용이 담겨있었다. Block Design 기반 설계에서는 FIR 포트가 내부적으로 DMA에 연결되므로 물리 핀 매핑이 필요 없는데, 이 XDC가 여전히 활성화되어 있어 잘못된 핀 제약이 적용되고 있었다.

→ Sources 패널 → Constraints 폴더 → zybo_n43.xdc 우클릭 → Disable File

두 문제를 모두 수정한 뒤 다시 시도했다.

9-1. Generate Bitstream 다시 시도

드디어 성공

IMPLEMENTATIONOpen Implemented DesignReport Timing Summary

  • WNS = +1.332ns
  • TNS = 0.000ns (타이밍 위반 없음)
  • Failing Endpoints = 0

Block Design을 만들기 전, FIR 코어만 단독으로 합성해 100MHz 타이밍 클로저를 확인했을 때는 WNS가 +0.278ns였다. 여유가 더 커진 건 조건이 달라서다. 단독 합성 때는 FIR 포트가 zybo_n43.xdc로 물리 핀에 묶여 I/O 경로가 크리티컬 패스에 들어갔지만, Block Design에서는 같은 포트가 DMA로 가는 내부 연결이라 그 경로가 사라진다.

10. Export Hardware

FileExportExport HardwareInclude bitstream 체크
지정한 디렉토리에 xsa 파일을 export하는 과정입니다.
XSA (Xilinx Support Archive):
Vivado가 만드는 하드웨어 설명 패키지 파일

11. TCL 스크립트 추출

Block Design은 Vivado 프로젝트 파일(.xpr) 안에만 존재합니다. 프로젝트가 유실되거나 다른 환경에서 재현해야 할 때를 대비해 write_bd_tcl로 Block Design 전체를 TCL 파일로 추출해 레포에 보관합니다.

추출 명령어

Block Design이 열린 상태에서 Vivado TCL Console에서 실행:

write_bd_tcl -force <repo>/vivado/bd_fir_dma.tcl

추출 결과: <repo>/vivado 디렉토리안에 bd_fir_dma.tcl 스크립트 저장

현재 레포에서는 이 파일이 vivado/fir_n43/bd_fir_dma.tcl로 옮겨져 있고, 2편에서 수정하는 c_sg_length_width {23}과 위의 DDR 파트 수정(MT41K256M16 RE-125)이 이미 반영돼 있다. 지금 레포의 TCL은 이 글 시점의 파일이 아니다.

포함 내용

write_bd_tcl이 추출하는 정보:

  • 모든 IP 정보 (processing_system7:5.5, axi_dma:7.1, smartconnect:1.0, axi_interconnect:2.1, proc_sys_reset:5.0)
  • IP 파라미터 설정 전체 (stream width=16 등)
  • 모든 배선 연결 (AXI, AXI-Stream, 클럭, 리셋)
  • 주소 할당 (assign_bd_address)
  • 마지막에 validate_bd_design 자동 호출

재현 방법

다른 머신에서 Block Design을 재현하려면:

  1. Vivado 2024.2 (Zybo Z7-20 보드 파일은 레포 boards/ 사용 가능)
  2. RTL 소스 4개가 포함된 Vivado 프로젝트 생성 (1 참고)
  3. Vivado TCL Console에서:
source vivado/fir_n43/bd_fir_dma.tcl

RTL 소스 없이 sourcing하면 fir_decimator_n43_axis 모듈을 찾지 못해 실패한다.
지금 레포에는 프로젝트 생성부터 BD 구성, 비트스트림, XSA까지 한 번에 도는 배치 스크립트도 있다. 보드 파일도 레포 boards/에 포함돼 별도 설치가 필요 없다.

vivado -mode batch -source vivado/fir_n43/build_bd_fir_dma.tcl

error 정리

#증상원인해결
1[BD 41-758] m_axi_s2mm_aclk is not connected to a valid clock sourceRun Connection Automation이 DMA 클럭 포트를 전부 연결하지 않음s_axi_lite_aclk / m_axi_mm2s_aclk / m_axi_s2mm_aclkFCLK_CLK0에 수동 연결
2[DRC NSTD-1] 39 out of 39 logical ports use I/O standard 'DEFAULT'포트를 빈 캔버스에 drop → Vivado가 Make External 실행, AXI-Stream 포트가 FPGA 물리 핀으로 노출get_bd_ports *로 확인 후 외부 포트 삭제, 대상 포트 위에서 초록 체크가 뜰 때 재연결
3비트스트림 생성 실패, 로그에 synth_design -top fir_decimator_n43_axis합성 Top이 bd_fir_dma_wrapper가 아니고, 단독 합성용 zybo_n43.xdc가 활성화되어 잘못된 핀 제약 적용Top을 wrapper로 변경 + XDC Disable File
4(이후에 발견) PS7 DDR 파트가 MT41J128M8 JP-125 (Zybo Z7-10용)BD 생성 시 보드 파일이 제대로 적용되지 않음보드 파일 재적용 후 PCW_UIPARAM_DDR_PARTNOMT41K256M16 RE-125인지 확인

마무리하며

Validate Design 성공과 비트스트림 생성 성공은 다르다는 걸 이번 작업에서 체감했습니다. Vivado가 "연결에 문제없다"고 판정해도, 합성 Top이 잘못 설정되어 있거나 프로젝트 초기에 넣어둔 XDC 파일이 남아있으면 비트스트림 단계에서 막힙니다. 에러 메세지를 읽고 원인을 역추적하는 과정이 번거롭긴 했지만, 덕분에 Block Design의 외부 포트 개념과 Vivado 프로젝트 상태 관리 방식을 제대로 이해하게 됐습니다. 다음 단계는 생성된 .xsa를 Vitis에서 열어 DMA를 제어하는 C 코드를 작성하는 것입니다. 코드는 금방 나왔는데, 정작 그걸 보드에 올리는 데서 16일이 갔습니다. JTAG로 ELF를 DDR에 올리면 32비트 워드의 상위 1바이트가 계속 오염됐고, 끝내 원인을 못 밝힌 채 부팅 경로를 바꿔야 했습니다. 다음 글은 그 이야기입니다.

profile
electronic engineering student

0개의 댓글