이벤트 카메라에서 발생한 좌표·극성 정보를 64×64×2 Event Tensor로 누적하고, FPGA에 구현한 Dense INT8 Tiny CNN이 표적 위치를 추론하는 Zynq-7000 기반 객체 추적 SoC입니다. NPU가 출력한 좌표는 카메라용 Pan/Tilt와 레이저용 Pan/Tilt 제어기로 전달되며, Dead Zone·Slew Limit·Safe Limit·Watchdog·Emergency Stop을 포함한 안전 제어 경로를 거칩니다.
ARM Cortex-A9 Processing System은 AXI4-Lite로 NPU 입력과 설정 레지스터를 관리하고, Programmable Logic은 이벤트 전처리·Tensor 생성·CNN 추론·좌표 복원·서보 및 레이저 인터록을 하드웨어로 수행합니다. Python Integer Golden Model, Verilog self-checking testbench, PS 드라이버 호스트 시험, Vivado 배치·배선 결과를 하나의 재현 흐름으로 구성했습니다.
대상 소자는 Xilinx Zynq-7000
xc7z020clg400-1이며, 전체 설계는 100 MHz 단일 PL 클럭 도메인에서 동작합니다. 최종 모델의 기준 결과는target_x=36,target_y=28,target_score=43, 추론 지연은 125,845 cycles입니다.
- 동작 시나리오
- 핵심 기술 요약
- 프로젝트 개요
- 주요 기능
- 시스템 구성
- 아키텍처
- Tensor와 CNN 데이터 형식
- AXI4-Lite 인터페이스
- 추적·서보·안전 제어
- 처리 순서와 상태 전이
- 실행 구조와 소스 구성
- 핵심 설계 포인트
- Troubleshooting
- 검증 및 성능 분석
- 빌드와 소프트웨어 연동
| 단계 | 입력과 처리 | 출력 |
|---|---|---|
| Event 수신 | 원본 좌표·극성·Window 경계를 표준 Event Stream으로 변환 | 64×64 좌표, Positive/Negative polarity |
| Tensor 생성 | 동일 픽셀 이벤트를 포화 누적하고 Ping-Pong 버퍼로 Window를 분리 | 8,192-byte CHW Tensor |
| NPU 추론 | Conv1~Conv4, Requantize, Activation, Argmax 실행 | 8×8 Heatmap과 최대 응답 cell |
| 좌표 복원 | Heatmap cell을 원본 64×64 좌표 중심으로 변환 | target_valid, target_x/y, target_score |
| 카메라 추적 | 중심 오차에 비례한 이동량을 Dead Zone과 Slew Limit로 제한 | PT#1 Pan/Tilt position |
| 레이저 정렬 | 카메라 위치와 잔여 좌표 오차, 보정값을 결합 | PT#2 Pan/Tilt position |
| 안전 출력 | Arm, E-stop, score, 위치, freshness, lock 연속성을 검사 | Servo PWM 4채널, fail-closed laser_en |
Event Window 하나가 확정되면 해당 버퍼를 NPU 입력으로 전송하면서 동시에 초기화합니다. 반대쪽 버퍼는 다음 Window를 누적하므로 입력 수집과 이전 Tensor 전달을 분리할 수 있습니다.
| 분류 | 핵심 기술 |
|---|---|
| Event 전처리 | 원본 센서 좌표 → 64×64 spatial binning, 범위 검사, polarity 보존 |
| Tensor | 64×64×2 CHW, Positive/Negative 채널, INT8 0~127 포화 누적 |
| Buffer | 8 KiB × 2 Ping-Pong BRAM, 연속 주소 전송과 동시 clear |
| CNN | Conv1~Conv3 3×3 stride 2, Conv4 1×1, 8×8 Heatmap |
| NPU | 8개 PE, output-stationary, INT8 곱셈, INT32 누산 |
| Quantization | 채널별 multiplier, ties-away-from-zero rounding, INT8 clamp |
| Result | Signed INT8 Heatmap, raster first-max Argmax, 64×64 좌표 복원 |
| SoC | Zynq PS7, M_AXI_GP0, AXI SmartConnect, 32-bit AXI4-Lite |
| Control | P Control, Dead Zone, Slew Limit, 카메라/레이저 Dual Pan-Tilt |
| Safety | Safe/Lock Zone, score 검사, E-stop, freshness watchdog, 수동 재무장 |
| 검증 | Python Integer Golden, Verilog self-checking XSim, C host mock |
| 구현 | Vivado 2024.2 synthesis·implementation·bitstream·XSA 자동 생성 |
| 항목 | 내용 |
|---|---|
| 프로젝트명 | Project06_EventCamera |
| 대상 보드 | Digilent Zybo Z7-20 |
| SoC | Zynq-7000 XC7Z020-1CLG400C |
| CPU | Dual-core ARM Cortex-A9 Processing System |
| 목적 | Event Tensor 기반 표적 좌표 추론과 Pan/Tilt 추적 제어 |
| NPU 입력 | 2×64×64 INT8 Event Tensor, 8,192 bytes |
| NPU 출력 | 1×8×8 signed INT8 Heatmap |
| 추론 결과 | target_valid, 6-bit X/Y, signed INT8 score |
| 연산 구조 | Dense INT8 CNN, 8 PE, output-stationary |
| 기준 클럭 | 100 MHz |
| PS/PL 연결 | 32-bit AXI4-Lite, base 0x4000_0000, range 4 KiB |
| 개발 도구 | Vivado 2024.2, XSim, Vitis, Python 3 |
PL 데이터 경로는 CPU 개입 없이 Event Window부터 NPU Direct START와 추적 제어까지 연결됩니다. PS는 같은 입력 버퍼를 AXI로 적재하는 시험 경로와 하드웨어 Event 경로를 선택할 수 있으며, 결과·상태·서보 위치·안전 상태를 레지스터로 조회합니다.
- Spatial Binning : 센서 좌표를 나눗셈 없는 역수 곱셈 방식으로 64×64 좌표에 대응시킵니다.
- Event Window : 내부 주기 또는 외부 Window pulse를 이벤트와 동일한 파이프라인 깊이로 정렬합니다.
- 포화 누적 : 동일 좌표의 이벤트를 127까지 누적하고 overflow wrap을 방지합니다.
- Ping-Pong Tensor : 다음 Window를 수집하는 동안 직전 8,192-byte Tensor를 NPU로 전달합니다.
- Dense INT8 CNN : 4개 Convolution 계층을 8개 PE로 수행하고 Golden Model과 bit-exact 결과를 생성합니다.
- AXI 입력 선택 : PS 입력과 Event 하드웨어 입력을 하나의 NPU input buffer 경계에서 선택합니다.
- Direct START : Tensor 전송 완료 pulse가 NPU 추론을 직접 시작하며 PS START와의 충돌을 중재합니다.
- 좌표 복원 : 8×8 Heatmap의 first-max cell을 64×64 공간의 cell center 좌표로 변환합니다.
- Dual Pan-Tilt : 카메라 헤드와 레이저 헤드를 독립적으로 제어하고 위치를 AXI에서 관측합니다.
- Fail-Closed Interlock : 안전 조건 하나라도 해제되면 레이저 출력을 즉시 차단합니다.
- 상태 진단 : busy/done/error, event count, overrun, servo position, control state와 IRQ를 제공합니다.
flowchart LR
SENSOR["Event Camera<br/>x · y · polarity · window"]
subgraph PL["Zynq-7000 Programmable Logic"]
ADAPTER["Event Adapter<br/>Spatial Binning"]
ACC["Event Accumulator<br/>64×64×2 Ping-Pong"]
MUX["Input Source MUX<br/>Event HW / PS AXI"]
NPU["Dense INT8 NPU<br/>Conv1 → Conv4"]
RESULT["Argmax Decoder<br/>target x · y · score"]
TRACK["Tracking Controller<br/>PT#1 Camera"]
AIM["Laser Head Controller<br/>PT#2 Laser"]
SAFE["Laser Interlock<br/>Fail-Closed Safety"]
AXI["NPU AXI4-Lite<br/>Register Bridge"]
end
subgraph PS["ARM Cortex-A9 Processing System"]
APP["Bare-Metal Software<br/>Configure · Start · Monitor"]
end
SERVO["Servo PWM ×4"]
LASER["Laser Enable"]
SENSOR --> ADAPTER --> ACC --> MUX --> NPU --> RESULT
RESULT --> TRACK --> AIM
TRACK --> SERVO
AIM --> SERVO
RESULT --> SAFE
TRACK --> SAFE
AIM --> SAFE --> LASER
APP <-->|"M_AXI_GP0 / AXI4-Lite"| AXI
AXI --> MUX
AXI <--> NPU
AXI <--> TRACK
AXI <--> AIM
AXI <--> SAFE
Event 데이터와 NPU, 제어기는 FCLK_CLK0=100 MHz를 공유합니다. PS/PL AXI와 NPU 내부 클럭이 같으므로 데이터 경계에 별도 CDC가 없으며, npu_axi가 software-visible 상태를 sticky bit와 IRQ로 변환합니다.
아래 이미지는 top_system_c를 포함한 A+C 전체 구성을 Vivado IP Integrator에서 직접 연 실제 Block Design입니다. ZYNQ7 Processing System의 M_AXI_GP0가 AXI SmartConnect를 거쳐 npu_0의 AXI4-Lite slave에 연결되고, FCLK_CLK0와 Processor System Reset이 전체 PL 데이터 경로의 100 MHz clock/reset을 공급합니다. 이미지를 클릭하면 원본 크기로 확인할 수 있습니다.
Block Design 오른쪽의 npu_0는 Event Adapter·Tensor Accumulator·Dense INT8 NPU·Dual Pan-Tilt·Laser Interlock을 포함한 top_system_c Module Reference입니다. 외부에는 Servo PWM 4채널, Laser Enable, Status LED, Hardware Arm과 Emergency Stop이 연결됩니다.
flowchart TB
DDR["DDR3"] <--> PS7["ZYNQ7 Processing System<br/>ARM Cortex-A9"]
FIXED["MIO · Clock · Reset"] <--> PS7
PS7 -->|"FCLK_CLK0 100 MHz"| RST["Processor System Reset"]
PS7 <-->|"M_AXI_GP0"| SC["AXI SmartConnect"]
SC <-->|"0x4000_0000 / 4 KiB"| TOP["top_system_c"]
TOP -->|irq| PS7
TOP --> LED["Status LED"]
TOP --> PWM["Servo PWM ×4"]
TOP --> LSR["Laser Enable"]
ARM["Hardware Arm"] --> TOP
ESTOP["Emergency Stop"] --> TOP
top_system_c 안에는 AXI bridge, NPU core, Event pipeline, Dual Pan-Tilt controller와 Laser interlock이 모두 포함됩니다. AXI 주소 공간은 하나의 4 KiB segment로 고정하고, 세부 기능은 32-bit register offset으로 구분합니다.
flowchart LR
RAW["Raw Event<br/>x[10:0] · y[10:0] · pol"]
BIN["2-stage Binning<br/>64×64"]
WIN["Window Boundary<br/>Event-aligned"]
RMW["Saturating RMW<br/>Forwarding"]
B0["Tensor Buffer 0<br/>8 KiB"]
B1["Tensor Buffer 1<br/>8 KiB"]
XFER["Sequential Transfer<br/>1 byte / cycle"]
START["tensor_start<br/>1-cycle pulse"]
RAW --> BIN --> WIN --> RMW
RMW --> B0
RMW --> B1
B0 --> XFER
B1 --> XFER
XFER --> START
연속된 동일 주소 이벤트는 BRAM write 결과를 한 단계 forwarding해 누적 손실을 막습니다. Window 종료 시 RMW pipeline을 비운 뒤 버퍼 역할을 교대하고, 전송 대상 버퍼는 0번부터 8,191번까지 순차 출력하면서 동시에 clear합니다.
flowchart LR
INPUT["Event Tensor<br/>2×64×64"]
C1["Conv1<br/>8×32×32<br/>3×3 / S2 / P1"]
C2["Conv2<br/>16×16×16<br/>3×3 / S2 / P1"]
C3["Conv3<br/>32×8×8<br/>3×3 / S2 / P1"]
C4["Conv4<br/>1×8×8<br/>1×1 / S1"]
ARG["Signed Argmax<br/>Raster First-Max"]
XY["Cell Center Mapping<br/>x,y = index×8+4"]
INPUT --> C1 --> C2 --> C3 --> C4 --> ARG --> XY
각 convolution은 output-stationary 방식으로 INT32 partial sum을 유지합니다. 연산이 끝난 값은 채널별 requantization multiplier와 고정 rounding 규칙을 거쳐 다음 INT8 activation buffer로 전달됩니다. Conv4 출력은 signed Heatmap으로 유지하고 raster 순서의 첫 최대값을 선택합니다.
flowchart LR
TARGET["target_valid<br/>x · y · score"]
PCTRL["P Control<br/>Dead Zone · Slew Limit"]
PT1["PT#1 Camera Position"]
RESIDUAL["Camera Pose + Residual Error<br/>Calibration Offset"]
PT2["PT#2 Laser Position"]
QUAL["Lock Qualification<br/>3 consecutive updates"]
WATCH["Freshness Watchdog<br/>Max-on Timer"]
INTERLOCK["Fail-Closed Interlock"]
TARGET --> PCTRL --> PT1 --> RESIDUAL --> PT2
TARGET --> QUAL
PT1 --> QUAL
PT2 --> QUAL
QUAL --> WATCH --> INTERLOCK
카메라 헤드는 화면 중심 (32,32)과의 오차를 줄이는 방향으로 이동합니다. 레이저 헤드는 카메라의 현재 자세와 남은 화면 오차, 보정 offset을 결합해 별도로 정렬합니다. 레이저 출력은 추적 결과뿐 아니라 물리 Arm, E-stop, Safe Limit, aim 상태와 watchdog을 모두 통과해야 활성화됩니다.
| 항목 | 값 |
|---|---|
| Shape | 2×64×64 |
| 데이터 형식 | signed INT8 저장, 유효 누적 범위 0~127 |
| Channel 0 | Positive event |
| Channel 1 | Negative event |
| Memory order | CHW |
| 주소식 | (polarity << 12) | (y << 6) | x |
| 총 크기 | 8,192 bytes |
원본 좌표는 floor(raw × 64 / sensor_size)와 동일한 결과를 내도록 고정소수점 역수 곱셈으로 변환합니다. 좌표 범위를 벗어난 이벤트는 설정된 OOR 정책에 따라 drop 또는 clamp됩니다.
| Layer | Kernel / Stride / Padding | 입력 | 출력 | 출력 크기 |
|---|---|---|---|---|
| Conv1 | 3×3 / 2 / 1 | 2×64×64 | 8×32×32 | 8,192 B |
| Conv2 | 3×3 / 2 / 1 | 8×32×32 | 16×16×16 | 4,096 B |
| Conv3 | 3×3 / 2 / 1 | 16×16×16 | 32×8×8 | 2,048 B |
| Conv4 | 1×1 / 1 / 0 | 32×8×8 | 1×8×8 | 64 B |
Weight는 OIHW 순서의 signed INT8이며 8개 bank에 output_channel % 8 기준으로 배치합니다. Bias는 사용하지 않고, Conv1~3은 requantization 이후 ReLU/INT8 clamp를 거칩니다.
heatmap_addr = y_cell * 8 + x_cell
target_x = x_cell * 8 + 4
target_y = y_cell * 8 + 4
target_valid = target_score > SCORE_TH
동일한 최대 score가 여러 cell에 존재하면 raster scan에서 먼저 나타난 cell을 선택합니다. RTL 비교 연산은 strict >를 사용하므로 Python numpy.argmax()의 flatten first-max 규칙과 일치합니다.
| 주소 범위 | 대상 |
|---|---|
0x4000_0000 ~ 0x4000_0FFF |
NPU·Event·Control 통합 AXI4-Lite register |
AXI 데이터 폭은 32-bit이고 register는 4-byte 정렬입니다. AW와 W 채널을 독립적으로 수신하며, response는 master가 handshake할 때까지 유지합니다.
| Offset | 이름 | 접근 | 기능 |
|---|---|---|---|
0x00 |
CTRL | RW / Pulse | START, SOFT_RESET, INPUT_SRC, IRQ_EN, HW_START_EN |
0x04 |
STATUS | RO / W1C | DONE sticky, BUSY, ERROR sticky, TARGET_VALID |
0x08 |
EVENT_CFG | RW | Event enable, polarity와 입력 설정 |
0x0C |
INPUT_STAT | RO | accumulator ready, tensor ready, overrun, event/drop count |
0x10 |
CYCLE_CNT | RO | 마지막 NPU 추론 cycle 수 |
0x14 |
RESULT_X | RO | target X coordinate |
0x18 |
RESULT_Y | RO | target Y coordinate |
0x1C |
RESULT_SCORE | RW / RO | signed score threshold와 결과 score |
0x20 |
PAN_CMD | RW | PT#1 카메라 Pan 설정 |
0x24 |
TILT_CMD | RW | PT#1 카메라 Tilt 설정 |
0x28 |
LASER_CTRL | RW | software arm 및 레이저 제어 설정 |
0x2C |
SAFE_LIMIT | RW | PT#1 runtime safe limit |
0x30 |
TRACK_ERR_X | RW | X축 추적 보정 설정 |
0x34 |
TRACK_ERR_Y | RW | Y축 추적 보정 설정 |
0x38 |
VERSION | RO | IP version 0x4E50_0101 |
0x3C |
INBUF_ADDR | RW | PS Tensor write pointer |
0x40 |
INBUF_DATA | WO | 32-bit write → INT8 4-byte 순차 확장 |
0x44 |
SCRATCH | RW | AXI 연결 점검 register |
0x48 |
PAN2_CMD | RW | PT#2 레이저 Pan 설정 |
0x4C |
TILT2_CMD | RW | PT#2 레이저 Tilt 설정 |
0x50 |
SAFE_LIMIT2 | RW | PT#2 runtime safe limit |
0x54 |
LASER_CAL | RW | 레이저 헤드 calibration offset |
0x58 |
SERVO_POS_STAT | RO | {tilt2, pan2, tilt1, pan1} |
0x5C |
CONTROL_STAT | RO | arm, E-stop, lock, watchdog, laser 상태 |
| 모드 | INPUT_SRC |
HW_START_EN |
추론 시작 |
|---|---|---|---|
| PS Tensor 시험 | 0 | 0 | PS가 INBUF_DATA 적재 후 CTRL.START |
| Event + PS 관리 | 1 | 0 | Tensor ready 확인 후 PS가 CTRL.START |
| Event Direct | 1 | 1 | tensor_start가 직접 NPU START |
PS START와 hardware START가 같은 cycle에 요청되더라도 한 번의 pulse만 NPU에 전달합니다. 소유권이 충돌하거나 busy/reset 중 요청이 발생하면 ERROR sticky bit로 기록하므로 frame loss를 software에서 확인할 수 있습니다.
error_x = target_x - 32
error_y = target_y - 32
step = clamp((abs(error) × P_GAIN) >> P_GAIN_SHIFT, 1, SLEW_LIMIT)
기본 Dead Zone은 중심 기준 ±4이고, 위치는 50 Hz servo frame tick에서만 변경됩니다. target_valid=0이면 현재 위치를 유지하며, 모든 position command는 설정된 최소·최대 범위로 clamp됩니다.
| 항목 | 기본값 |
|---|---|
| PWM frame | 20 ms / 50 Hz |
| Position range | 0~255 command |
| Neutral | 128 |
| Safe position | 32~224 |
| Pulse range | 1,125~1,875 μs |
4개 출력은 카메라 Pan/Tilt와 레이저 Pan/Tilt에 각각 대응합니다. 두 헤드는 독립 position을 갖고 동일한 PWM frame 기준으로 출력됩니다.
레이저 출력은 다음 조건이 모두 참일 때만 허용됩니다.
- Software Arm과 Hardware Arm 활성
- Emergency Stop 해제 및 수동 재무장 완료
- 유효한 target과 score threshold 만족
- Target Safe Zone 및 Lock Zone 만족
- PT#1·PT#2 위치가 각각 Safe Limit 내부
- 레이저 헤드가 목표 위치에 도달
- 연속 3회 lock 확인 및 target freshness 유지
- Watchdog와 최대 연속 출력 시간 조건 만족
전원 인가, E-stop, target loss, 위치 범위 이탈 또는 freshness timeout에서는 laser_en이 0으로 유지됩니다.
sequenceDiagram
participant CAM as Event Source
participant EVT as Event Pipeline
participant NPU as INT8 NPU
participant AXI as AXI Register
participant CTRL as Tracking / Safety
CAM->>EVT: x, y, polarity events
CAM->>EVT: window_end
EVT->>EVT: pipeline drain and buffer swap
EVT->>NPU: 8,192-byte Tensor transfer
EVT->>NPU: tensor_start pulse
NPU->>NPU: Conv1 → Conv2 → Conv3 → Conv4 → Argmax
NPU->>AXI: done, cycle count, target x/y/score
NPU->>CTRL: target_valid, target x/y/score
CTRL->>CTRL: Pan/Tilt update and safety qualification
stateDiagram-v2
[*] --> IDLE
IDLE --> CONV1 : start
CONV1 --> CONV2 : layer_done
CONV2 --> CONV3 : layer_done
CONV3 --> CONV4 : layer_done
CONV4 --> ARGMAX : layer_done
ARGMAX --> DONE : scan_complete
DONE --> IDLE : done pulse
Activation buffer는 계층마다 source/destination 역할을 교대합니다. cycle_cnt는 start부터 최종 done까지 측정하고, 결과 register와 target_valid는 다음 실행 전까지 유지됩니다.
| 영역 | 모듈 | 역할 |
|---|---|---|
| NPU | npu_pe |
INT8 multiply와 INT32 accumulate |
| NPU | npu_requant |
고정소수점 requantization과 rounding |
| NPU | npu_conv_dense |
Dense convolution address·PE 제어 |
| NPU | npu_act_buf |
Ping-Pong activation memory |
| NPU | npu_weight_rom |
8-bank INT8 weight memory |
| NPU | npu_controller |
Conv1~4 layer sequence |
| NPU | argmax_decoder |
8×8 Heatmap first-max 좌표 복원 |
| NPU | npu_core |
NPU top-level과 외부 input write port |
| Event | event_adapter |
좌표 binning과 Window 정렬 |
| Event | event_accumulator |
포화 누적, Ping-Pong Tensor 생성 |
| Control | tracking_controller |
카메라 Pan/Tilt 추적 |
| Control | laser_head_controller |
레이저 Pan/Tilt 보정·정렬 |
| Control | servo_pwm |
4채널 servo pulse 생성 |
| Control | laser_interlock |
fail-closed 안전 조건 검사 |
| Integration | npu_axi |
AXI4-Lite register bridge와 START 중재 |
| Integration | top_system_c |
Event·NPU·Control 전체 통합 Top |
Project06_EventCamera/
├── README.md
├── ai/ # Dataset, training, quantization, Integer Golden
├── constraints/ # Zybo Z7-20 XDC와 OOC timing constraint
├── docs/ # 최종 규격, 인터페이스, 검증·운용 문서
├── handoff/ # 통합에 사용한 최종 전달 문서
├── rtl/
│ ├── npu/ # Dense INT8 NPU
│ ├── event/ # Event adapter와 accumulator
│ ├── control/ # Tracking, dual head, servo, interlock
│ └── integration/ # AXI bridge와 system top
├── tb/ # Verilog self-checking testbench
├── test_vectors/ # case00/01/02 Integer Golden tensors
├── weights/ # 최종 INT8 weight와 requant parameter
├── sw/ # Cortex-A9 driver, self-test, host mock
├── sim/ # XSim·Vivado build script
├── tools/ # Weight pack, vector 생성, waveform capture
├── results/ # Bitstream, XSA, ELF, timing·utilization report
└── NPU_Pr_vivado/ # Vivado GUI project
Tensor 순서, padding, weight layout, signedness, rounding, clamp와 Argmax tie-break를 명시적으로 고정했습니다. 각 계층의 전체 output byte를 Python Integer Golden dump와 비교하여 최종 좌표만 같은 경우가 아니라 중간 연산까지 일치하는지 검사합니다.
BRAM 기반 누적기는 read latency 때문에 같은 주소 이벤트가 연속으로 들어오면 갱신 전 값을 다시 읽을 수 있습니다. 직전 write 주소와 값을 forwarding해 동일 픽셀의 연속 이벤트도 한 건씩 정확하게 누적합니다.
Window 종료 순간 pipeline에 남아 있는 이벤트까지 원래 버퍼 선택 정보를 함께 전달합니다. pipeline drain 이후 역할을 바꾸므로 직전 Window의 마지막 이벤트가 다음 Window로 넘어가지 않습니다.
AXI 입력 경로는 test vector·software bring-up에 사용하고, Event Direct 경로는 Tensor 완료 즉시 추론을 시작합니다. 두 경로는 같은 input buffer와 NPU를 공유하지만 source selection과 START arbitration으로 동시에 구동되지 않습니다.
레이저 enable은 여러 안전 조건의 AND 결과로만 생성됩니다. 모호하거나 오래된 target, 범위 밖 position, E-stop, Arm 절차 위반이 발생하면 기본 출력은 OFF이며, fault 이후에는 명시적 재무장 절차를 거칩니다.
- 확인 :
input_event.hex가 CHW 순서인지, weight가 OIHW인지,weights/w_bank*.mem을 다시 pack했는지 확인합니다. - 해결 :
tools/pack_weights.py와 vector 생성 도구를 순서대로 실행한 뒤tb_npu_conv_dense부터 비교합니다.
- 원인 : START 다음 cycle까지 BUSY가 반영되는 동안 software가 BUSY만 polling할 수 있습니다.
- 해결 :
STATUS.DONEsticky bit를 기준으로npu_wait_done()을 사용합니다.
- 확인 :
CTRL.INPUT_SRC,EVENT_CFG.ENABLE, accumulator ready와npu_busy를 확인합니다. - 해결 : PS 입력은
INPUT_SRC=0, hardware Event 입력은INPUT_SRC=1로 선택하고 한 번에 한 START 소유자만 활성화합니다.
- 확인 :
CONTROL_STAT의 Hardware Arm, Software Arm, E-stop, aim, freshness, safe/lock 상태를 확인합니다. - 해결 : Arm을 LOW로 내려 재무장 latch를 해제한 뒤 안전 조건과 calibration 범위를 다시 설정합니다.
- 확인 : Zybo Z7-20 board part
digilentinc.com:zybo-z7-20:part0:1.2가 XHub board store에 설치되어 있는지 확인합니다. - 해결 : Vivado 2024.2의 board repository 경로를 등록한 뒤 GUI project setup script를 다시 실행합니다.
Vivado 2024.2 XSim self-checking simulation과 배치·배선 보고서를 함께 사용했습니다. NPU 단독 결과와 Event·Control을 포함한 전체 PL top, Zynq Block Design 결과를 구분해 기록합니다.
자동 회귀 결과는 16 / 16 testbench PASS입니다. 동일한 test suite를 case00, case01, case02 Golden vector에 적용한 A+C 통합 회귀도 48 / 48 PASS입니다.
| 검증 그룹 | Testbench | 핵심 검증 |
|---|---|---|
| NPU 수치 | tb_npu_requant, tb_npu_pe |
3,150 requant case, 300 MAC case |
| NPU 계층 | tb_npu_conv_dense, tb_npu_full |
Conv1 8,192 B, Conv1~4 전체 Golden |
| AXI / SoC | tb_npu_axi, tb_top_system |
120 AXI checks, PS/PL end-to-end |
| Event | tb_event_adapter, tb_event_accumulator, tb_event_pipeline |
좌표 전수 binning, 8,192 B Tensor, Ping-Pong |
| Servo / Tracking | tb_servo_pwm, tb_tracking_controller, tb_laser_head_controller |
PWM, P control, limit, calibration |
| Safety | tb_laser_interlock, tb_dual_head_control, tb_c_event_control_top |
E-stop, rearm, watchdog, dual head |
| 전체 통합 | tb_top_system_c |
3개 연속 frame, 37 checks, fail-closed 출력 |
PS driver는 동일한 AXI 동작을 복제한 host mock에서 76 / 76 checks PASS하며, input byte ordering, DONE sticky, START arbitration과 두 Pan/Tilt head register 독립성을 검사합니다.
아래 이미지는 합성된 그림이 아니라 Vivado XSim에서 self-checking testbench를 실행한 WDB를 직접 열어 캡처한 실제 파형입니다. 이미지를 클릭하면 원본 크기로 확인할 수 있습니다.
(1) NPU Conv1~Conv4 전체 추론
busy구간에서 controller state와 layer가 Conv1 → Conv2 → Conv3 → Conv4 순서로 전환됩니다.- 계층 완료마다
conv_done이 발생하고 activation buffer 선택이 교대합니다. - 125,845 cycles 뒤
done=1,target_valid=1이 되며 최종 결과는 X=36, Y=28, score=43입니다.
(2) Event 입력부터 NPU·제어까지 End-to-End
- 3개 Event Window에서
window_end, 8,192-byteevt_we,tensor_start,npu_busy,done의 반복 순서를 확인합니다. - 각 frame 결과가 X=36, Y=28, score=43으로 일치하고 IRQ·상태 register까지 전달됩니다.
- Hardware Arm이 비활성인 시험 조건에서
laser_en은 전체 구간 0을 유지해 fail-closed 동작을 검증합니다.
100 MHz 제약으로 route까지 완료한 Vivado Report Utilization 결과입니다.
위 화면은 npu_bd_wrapper routed design에서 직접 연 Utilization 보고서입니다. 상단 Hierarchy 표에서 Slice LUT 2,324, Slice Register 2,188, Block RAM Tile 12, DSP 18을 확인할 수 있습니다.
| 구현 범위 | LUT | FF | BRAM Tile | DSP |
|---|---|---|---|---|
| NPU Core OOC | 573 / 53,200 | 278 / 106,400 | 8 / 140 | 12 / 220 |
| Event+NPU+Control Top OOC | 1,975 / 53,200 | 1,693 / 106,400 | 12 / 140 | 18 / 220 |
| Zynq A+C Full Block Design | 2,324 / 53,200 | 2,188 / 106,400 | 12 / 140 | 18 / 220 |
전체 Block Design 기준 사용률은 LUT 4.37%, FF 2.06%, BRAM 8.57%, DSP 8.18% 입니다. NPU activation memory와 Event Ping-Pong Tensor가 BRAM을 사용하고, PE multiplier와 spatial binning 연산이 DSP에 매핑됩니다.
위 화면은 같은 routed design의 Design Timing Summary입니다. Setup WNS +0.618 ns, Hold WHS +0.043 ns, Pulse Width WPWS +4.020 ns와 각 항목의 failing endpoint 0을 Vivado 화면에서 직접 확인할 수 있습니다.
| 구현 범위 | Clock | WNS | WHS | 실패 Endpoint |
|---|---|---|---|---|
| NPU Core OOC | 100 MHz | +0.752 ns | +0.122 ns | 0 |
| Event+NPU+Control Top OOC | 100 MHz | +0.197 ns | +0.106 ns | 0 |
| Zynq A+C Full Block Design | 100 MHz | +0.618 ns | +0.043 ns | 0 |
세 구현 범위 모두 setup·hold·pulse-width 조건을 만족합니다. 전체 Block Design의 TNS와 THS는 0.000 ns이며, 6,182개 timing endpoint에서 실패가 없습니다.
| 항목 | 값 |
|---|---|
| 기준 클럭 | 100 MHz / 10 ns |
| PE 수 | 8 |
| 전체 추론 | 125,845 cycles |
| 추론 시간 | 1.258 ms |
| 이론적 단일 추론 상한 | 약 794 inference/s |
| Event Tensor 전송 | 8,192 cycles / 81.92 μs |
| 최종 Heatmap | 8×8 signed INT8 |
단일 추론 상한은 NPU core의 연속 실행 지연만으로 계산한 값입니다. 실제 시스템 update rate는 Event Window, Tensor 준비, 서보 frame과 입력 source의 event rate를 함께 따릅니다.
| 확인 항목 | 결과 |
|---|---|
| Setup failing endpoint | 0 |
| Hold failing endpoint | 0 |
| Pulse-width failing endpoint | 0 |
| Combinational latch loop | 0 |
| Tensor 주소 순서 위반 | 0 |
| NPU busy 중 Event write | 0 |
| Event accumulator overrun | 0 |
| 전체 통합 laser fail-closed 위반 | 0 |
RTL simulation은 protocol assertion 성격의 self-check와 전체 byte compare를 함께 사용합니다. Implementation 결과는 synthesis 추정치가 아니라 route 완료 후 생성한 timing/utilization report를 기준으로 합니다.
| 산출물 | 용도 |
|---|---|
npu_soc_cfull.bit |
Event·NPU·Control 전체 Bitstream |
npu_soc_cfull.xsa |
Vitis Hardware Platform |
npu_test.elf |
PS hardware self-test |
live_tracker.elf |
Live tracking application |
bd_timing_cfull.rpt |
전체 Block Design timing |
bd_util_cfull.rpt |
전체 Block Design utilization |
portfolio_waveforms |
실제 XSim waveform capture |
# Verilog self-checking testbench 16종
./sim/run_sim.sh
# 대표 파형 WDB 생성 및 캡처
./sim/run_wave.sh tb_npu_full
./tools/capture_portfolio_waves.sh full topc
# NPU Core / 전체 통합 Top 배치·배선
(cd build && vivado -mode batch -source ../sim/run_impl.tcl)
(cd build && vivado -mode batch -source ../sim/run_impl_cfull.tcl)
# Zynq A+C 전체 Block Design, Bitstream, XSA
(cd build && vivado -mode batch -source ../sim/run_bd.tcl -tclargs -cfull)저장소 루트에서 GUI project를 동기화한 뒤 엽니다.
vivado -mode batch -source sim/setup_gui_project.tcl
vivado NPU_Pr_vivado/NPU_Pr_vivado.xprSimulation Sources의 기본 top은 tb_npu_full입니다. 전체 통합 파형은 tb_top_system_c를 Set as Top으로 지정하고 Run Behavioral Simulation을 실행합니다.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
PYTHONPATH=ai ./.venv/bin/python ai/train.py --mode self-test
PYTHONPATH=ai ./.venv/bin/python ai/test_v04_retrain_pipeline.py
PYTHONPATH=ai ./.venv/bin/python ai/test_v06_pipeline.py최종 적용 모델은 model_v04_demo_masked_radius1_x1, 실행 profile은 DEMO_BLUE_01입니다. Weight, requantization parameter와 case00/01/02 Golden Tensor는 weights/와 test_vectors/에 함께 저장됩니다.
# Host mock 기반 driver·CPU baseline·protocol test
make -C sw all
# Cortex-A9 cross build
make -C sw arm
# Vitis hardware application
vitis -s sw/build_vitis_unified.py
# Live tracking application
NPU_APP=live_tracker vitis -s sw/build_vitis_unified.pyresults/npu_soc_cfull.bit를 Zynq PL에 program합니다.results/npu_test.elf를 실행해 VERSION, AXI scratch, input buffer, NPU 결과와 IRQ를 검사합니다.- 결과가
valid=1, X=36, Y=28, score=43, cycle=125845인지 확인합니다. - Live 동작에서는 동일 Bitstream에
results/live_tracker.elf를 실행합니다. - Servo·레이저 구동 전에 Hardware Arm과 Emergency Stop 입력을 확인합니다.
상세 software register 사용법과 실행 절차는 sw/README.md, 전체 규격과 산출물 색인은 docs/00_DOCUMENT_INDEX.md에서 확인할 수 있습니다.
Project06_EventCamera는 Event 입력부터 INT8 CNN 추론, 좌표 복원, Dual Pan-Tilt 추적과 fail-closed 레이저 제어까지 하나의 Zynq PS/PL SoC로 통합한 FPGA 객체 추적 프로젝트입니다.





