RTL 설계와 SystemVerilog/UVM 기반 검증을 중심으로, FPGA 시스템 구현과 온디바이스 AI까지 경험의 범위를 넓혀가고 있습니다.
광운대학교 전자공학과 졸업 · 인공지능반도체연계전공 이수
연계전공 필수과목인 인공지능반도체설계프로젝트 A+
RTL 설계뿐만 아니라 설계한 하드웨어가 의도한 대로 동작하는지를 검증하는 과정에 관심이 있습니다. SystemVerilog/UVM 기반 Testbench를 구성하고, Scoreboard와 Functional Coverage를 활용해 기능을 검증하며 FPGA 보드에서 실제 동작까지 확인하는 프로젝트를 수행해왔습니다.
디지털 설계·검증 경험을 기반으로 임베디드 시스템과 온디바이스 AI까지 이해의 범위를 넓혀가며, 시스템 전체를 이해하는 Design Verification 엔지니어를 목표로 하고 있습니다.
프로젝트를 클릭하면 상세 내용을 볼 수 있습니다.
ToF 센서(VL53L1X)로 앞차와의 거리를, 로터리 엔코더(AS5600)로 속도를 측정하고, PID 제어로 DC 모터 출력을 조절해 목표 거리(30cm ± 5cm)를 유지하는 ACC 시스템을 DE10-Standard(Cyclone V SoC) 보드로 구현한 4인 팀 프로젝트.
앞차와의 거리를 자동으로 유지하며 속도를 조절하는 Adaptive Cruise Control(ACC)을, 실제 차량이 아닌 RC카 + FPGA 조합으로 구현한 4인 팀 프로젝트입니다. DE10-Standard 보드(Cyclone V SoC)의 HPS(ARM 리눅스)와 FPGA 로직을 함께 활용해, 센서 처리는 HPS가, 실시간 PWM 제어는 FPGA가 담당하는 역할 분리 구조로 설계했습니다. HPS와 FPGA라는 이질적인 두 영역을 I2C와 AXI라는 서로 다른 버스로 엮어낸 것이 이 프로젝트의 핵심입니다.
정량적 목표
기능적 목표
ToF 센서(VL53L1X)가 측정한 거리와 로터리 엔코더(AS5600)가 측정한 속도는 HPS의 I2C2 인터페이스를 통해 HPS로 전달되고, HPS는 이 값을 AXI(Advanced eXtensible Interface) 기반 Lightweight AXI Bridge를 통해 FPGA 내부 레지스터에 write합니다. 즉 센서 쪽은 I2C, HPS-FPGA 경계는 AXI로 나뉘는 2단 버스 구조입니다. FPGA 내부에서는 Sensor Interface FSM → PID Controller FSM → PWM Generator 순서로 데이터가 흘러가며, 최종 PWM 신호가 모터드라이버(DRV8833)를 거쳐 DC 모터 속도를 제어하는 피드백 루프를 구성합니다. 제어 주기는 10ms입니다.
DE10-Standard(Cyclone V SoC)가 기본 제공하는 GHRD(Golden Hardware Reference Design)는 단순 입출력 테스트 수준이라 센서 제어나 실시간 제어 로직이 전혀 없었습니다. 이를 기반으로 Platform Designer에서 HPS I2C2 인터페이스를 추가 구성하고, Lightweight AXI Bridge를 활성화해 센서 데이터를 HPS→FPGA로 실시간 전달할 수 있는 경로를 새로 만들었습니다. 여기에 FPGA 내부 FSM 3종(Sensor Interface / PID Controller / PWM Generator)을 신규 설계해, 단순 데모 수준이던 GHRD를 실제 센서 기반 실시간 제어 시스템으로 확장했습니다.
개발 중 I2C로 연결된 VL53L1X 센서가 VL53L1_ERROR_CONTROL_INTERFACE, VL53L1_ERROR_TIME_OUT 같은 오류를 내며 거리값이 0으로 고정되는 문제를 겪었습니다.
브레드보드 접촉 불량이 원인 중 하나였고, 근본적으로는 소프트웨어 차원의 복구 로직이 없었던 게 문제였습니다.
이를 해결하기 위해 오류 감지 시 센서를 PlatformDeInit → 200ms 대기 → PlatformInit 순서로 재초기화하는 루틴을 넣어, 사용자 개입 없이도 약 200ms 이내에 자동 복구되도록 만들었습니다.
거리값과 에러코드를 32비트 하나에 패킹해서(상위 16비트 에러코드, 하위 16비트 거리) /dev/mem 기반 mmap으로, Lightweight AXI Bridge가 매핑한 FPGA 주소 공간(0x00040000)에 직접 write하는 방식을 택했습니다.
FPGA 쪽에서는 이 32비트 값을 다시 상/하위로 분리해 거리는 7-Segment에 표시하고, 에러코드는 LED 상태 표시나 에러 핸들링에 사용하도록 구성했습니다.
처음엔 흔히 쓰이는 L298N을 사용했지만, BJT 기반이라 전압 강하가 커서 채널당 최대 피크 전류를 안정적으로 보장하기 어려웠습니다. RC카 구동에 필요한 전류(정격 700~900mA, 급가속 시 최대 2.3A)를 계산해보니 L298N으로는 부족할 수 있다고 판단해, MOSFET 기반이라 전력 손실이 적고 채널당 2.4A까지 지원하는 DRV8833로 교체했습니다.
senser 제어 소프트웨어를 매번 수동 실행하지 않도록 systemd 서비스로 등록해, 보드 전원이 켜지면 센서 측정 프로그램이 자동으로 실행되고 곧바로 FPGA로 데이터가 흘러가도록 구성했습니다.
FPGA(Basys-3) 한 대로 완결되는 셀프 네컷사진 부스입니다. OV7670 카메라로 촬영한 영상을 보드 내부에서 실시간으로 필터링·마커 인식·스티커/드로잉 편집까지 처리하고, 완성된 사진을 UART로 PC에 전송해 Python UI에서 QR코드로 다운로드할 수 있게 만들었습니다. 7명 팀 프로젝트이며, 저는 Edit Engine(편집 엔진)을 권동오 님과 함께 설계·구현했습니다.
| 플랫폼 | Digilent Basys-3 (FPGA) |
|---|---|
| 카메라 | OV7670 (320×240, I2C 레지스터 설정) |
| 출력 | VGA Monitor (640×480), UART → PC(Python) |
| UART 속도 | 1 Mbps, 8N1 |
| 편집 기능 | 필터 4종, 스티커, Drawing/Mosaic, Color Picker |
| PC UI | Python 기반 촬영 가이드 / 필터·프레임 선택 / QR코드 생성 |
버튼·스위치 입력을 동기화해 System Controller로 전달하고, System Controller가 상태에 따라 각 모듈의 동작을 제어합니다. OV7670의 픽셀 데이터는 Camera Interface로 수신된 뒤 Down Scaler와 Filter를 거쳐 Capture&Filter 블록에 저장되고, Marker 좌표를 기반으로 편집한 결과를 VGA Monitor에 실시간 출력하는 동시에 UART로 Python UI에 전송합니다.
카메라로 촬영 → 보드에서 실시간 필터 적용 및 편집 → 완성된 4컷 사진까지 이어지는 전체 플로우입니다. Python UI는 촬영 가이드부터 필터/스티커 선택, 최종 사진 확인, QR코드 다운로드까지의 흐름을 담당합니다.
| 담당 | 역할 |
|---|---|
| 최민영 | Edit Engine(편집 엔진) 설계 및 구현 — Memory/Memory Writer/Memory Address Generator/Marker Overlay/Image Export 통합, VGA 출력 경로와 UART 전송 경로의 픽셀 데이터 흐름 설계, Critical Path Timing Closure(파이프라인 레지스터 삽입) |
| 공동 | System Controller, Camera Interface & Marker Detector, Capture & Filter, UART Interface, Python PC UI 등 나머지 모듈 |
Edit Engine은 VGA Controller가 생성하는 좌표(VGA Cntl)와 Capture&Filter에서 넘어온 촬영 이미지(Memory), Marker Detection이 넘겨주는 마커 좌표(Marker Overlay)를 하나로 합쳐 최종 픽셀 데이터를 만들고, 이를 Monitor와 UART(Python 전송용) 양쪽으로 동시에 출력하는 블록입니다. Down Scaler로 축소한 프리뷰 영상과 Capture로 저장된 원본 이미지를 pixel mux로 전환하고, Memory Address Generator가 계산한 주소로 Memory에서 픽셀을 읽어 Marker Overlay에서 스티커·드로잉·컬러피커 결과를 덮어씌운 뒤 MEM Writer로 다시 저장합니다.
Sticker는 Marker 좌표 근처에 저장된 스티커 이미지를 그대로 덮어쓰고, Drawing은 Marker 궤적을 따라 지정 색상을 기록하며, Color Picker는 Marker 좌표의 Memory 값을 읽어와(Memory Read) 해당 색상을 UI에 반영합니다. 이 세 경로 모두 최종적으로 Memory Write 단계로 모여 하나의 프레임 버퍼에 반영됩니다. VGA 좌표와 Marker 좌표를 비교해 겹치는 위치인지 판단한 뒤, 겹치면 Control Signal에 따라 오버레이 색상으로 덮어쓰고 겹치지 않으면 메모리 원본 값을 그대로 출력합니다.
System Controller의 전송 요청을 받으면 (0,0)부터 (639,479)까지 좌표를 순차 생성하며 Memory Read → UART 전달(valid/ready 핸드셰이크)을 반복하고, 전체 프레임 전송이 끝나면 System Controller에 완료 신호를 보냅니다.
OPEN(시작 대기) → SHOOT(프리뷰·필터 선택) → CAPTURE(4장 순차 촬영) → STICKER(스티커 편집) → DRAW(그리기·모자이크) → FINAL_EXPORT(이미지·UART 전송 완료 대기) → RESULT(결과 표시) 순서로 전이하며, 이미지 송신 완료 후 20초 경과 또는 외부 버튼 입력 시 자동으로 OPEN 상태로 복귀합니다. 현재 상태와 설정값을 32bit Status Data로 구성해 UART로 Python UI에 함께 전달합니다.
Cam_IF는 top_setup(I2C 기반 OV7670 레지스터 초기화)과 ov7670_mem_controller(픽셀 카운터 기반 좌표 생성)로 구성됩니다. Marker Detector는 색상 LUT(target_lut) 기반으로 목표 색상을 판별한 뒤, Line 단위가 아닌 Blob(덩어리) 단위 검출을 채택했습니다 — 시뮬레이션 결과 Line 방식이 첫 출력까지 32.2μs로 더 빨랐지만 valid 횟수가 10회로 출력이 불안정했던 반면, Blob 방식은 160.2μs로 약 128μs 느린 대신 valid 1회로 좌표가 안정적이어서, 사용자 체감에 미치는 영향이 적은 응답속도보다 좌표 안정성을 우선해 Blob 방식을 채택했습니다.
Capture_Controller는 IDLE → WAIT_FRAME_START → CAPTURE → CAPTURE_DONE → CAPTURE_WAIT → ALL_DONE 상태를 거치며 프레임 시작 시점(x=0, y=0)부터 마지막 픽셀(x=159, y=119)까지 다운스케일된 이미지를 순차로 Memory에 기록하고, 4장 촬영이 끝나면(cap_count==3) ALL_DONE으로 전이합니다. Filter_top은 Grayscale/Sepia/Softfocus/Filmlook 4개 필터를 병렬로 두고 i_filter_sel로 mux 선택하는 구조이며, 필름룩 필터는 채널별 게인 조정(R×1.12, G×1.043, B×0.88)을 Q8 고정소수점(256배 정수화)으로 구현했습니다.
Send Control이 System Controller의 32bit Status Data와 Edit Engine의 픽셀 데이터를 Byte 단위로 분할해 UART TX로 전달하고, Baud Generator가 1Mbps 기준 타이밍을 생성합니다. UART TX는 8N1 방식으로 IDLE → START → DATA(LSB부터 8bit) → STOP 순서로 직렬 전송하며, PC(Python)에서 수신 데이터를 재조립해 상태 확인 및 최종 이미지를 복원합니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| Vivado Timing Analysis에서 Negative Slack 대량 발생 (WNS -5.156ns, TNS -2616.955ns, Failing Endpoint 648개, Timing constraints 불충족) | 화면 좌표를 메모리 주소로 변환하는 곱셈 계산 경로(mem_addr_gen의 VGA 픽셀 주소 계산, mem_writer의 Sticker/Color Picker Write 주소 계산)에서 좌표 변환과 곱셈·덧셈 연산이 한 사이클에 몰려 조합 논리 경로 지연이 크게 증가 | Critical Path에 파이프라인 레지스터를 2단계로 삽입 — 1단계: 다운스케일 적용된 x_pixel/y_pixel을 레지스터에 저장, 2단계: 저장된 좌표로 주소 계산(곱셈) 수행 후 결과를 다시 출력 레지스터에 저장. 주소 계산 경로를 분리해 조합 논리 지연을 줄인 결과 WNS -5.156ns → +0.463ns, TNS 0ns, Failing Endpoint 0개로 Timing Constraint를 충족 |
Edit Engine을 대상으로 NONE passthrough, STICKER(하트 4×4 아이콘 투명/채움 경계), DRAW(3×3 박스 그리기 및 영역 밖 미변경), Color Picker(5×5 블록 코너·중심 픽셀) 4가지 시나리오를 SystemVerilog Testbench로 검증했으며, 총 16개 항목 모두 PASS(ALL TESTS PASSED)를 확인했습니다. UART 경로에서는 32bit Status Data(0x12345678 → 78→56→34→12 LSB Byte First 순서)와 RGB444 Pixel(0xABC → BC→0A) 분할 전송이 설계한 순서대로 정상 동작함을 시뮬레이션으로 확인했습니다.
AXI4-Lite와 APB 프로토콜의 UVM VIP(Verification IP)를 재사용 가능한 구조로 직접 설계하고, 이를 이용해 RV32I 5단 파이프라인 CPU와 AXI-APB 프로토콜 변환 브리지까지 검증한 개인 프로젝트입니다. 팹리스 SoC 설계/검증 직무 포트폴리오로 진행했으며, VIP 설계(Phase 1) → CPU 설계·검증(Phase 2) → 프로토콜 변환 브리지 검증(Phase 3) 순서로 난이도를 확장해 나갔습니다.
| 검증 방법론 | UVM (Universal Verification Methodology) |
|---|---|
| 시뮬레이터 / 디버거 | Synopsys VCS / Synopsys Verdi |
| 언어 | SystemVerilog, UVM 1.1d |
| 진행 단계 | Phase 1(AXI4 VIP) · Phase 2(RV32I 5-stage CPU) · Phase 3(APB VIP + AXI-APB 브리지) 전체 완료 |
재사용 가능한 AXI4-Lite UVM VIP(interface, transaction, sequence, sequencer, driver, monitor, agent, scoreboard, coverage)를 직접 설계하고, 예제 AXI4-Lite 레지스터 슬레이브 DUT를 대상으로 write-read 데이터 무결성을 검증했습니다.
| 항목 | 결과 |
|---|---|
| 총 트랜잭션 | write 13 / read 13 (랜덤 5쌍 + directed 8쌍) |
| Scoreboard | PASS 13 / FAIL 0 |
| Functional Coverage | 100% |
| UVM_ERROR / UVM_FATAL | 0 / 0 |
Coverage 항목: write/read 여부(cp_rw), 레지스터별 접근(cp_addr), 데이터 패턴(cp_data: zero/all_one/others), 레지스터×R/W cross coverage(cx_addr_rw).
| 파일 | 설명 |
|---|---|
vip/axi4/axi4_interface.sv | AXI4-Lite Interface 정의 |
vip/axi4/axi4_transaction.sv | Transaction (uvm_sequence_item) |
vip/axi4/axi4_sequencer.sv / axi4_sequence.sv | Sequencer / Sequence |
vip/axi4/axi4_driver.sv | Reset 대기, AW/W 병렬 handshake, timeout 처리 |
vip/axi4/axi4_monitor.sv | AW/W 병렬 캡처 |
vip/axi4/axi4_agent.sv / axi4_coverage.sv | Agent / Functional Coverage |
rtl/example_dut/axi_lite_reg_slave.sv | 검증 대상 예제 DUT |
tb/vip_standalone_tb/axi4_scoreboard.sv | Write-Read 데이터 무결성 Scoreboard |
| 문제 | 원인 | 해결 |
|---|---|---|
| AXI AW/W 채널 독립 handshake 문제 — W가 AW보다 먼저 handshake되면 driver/monitor가 이를 놓치고 무한 대기 | Driver/Monitor가 AW handshake를 먼저 확인한 뒤 W를 확인하는 순차 구조였는데, AXI 스펙상 AW/W는 완전히 독립된 채널 | fork...join 기반으로 AW watch와 W watch를 병렬 프로세스로 분리해 각자 독립적으로 handshake를 감지하도록 수정 |
| Reset/Timeout 처리 부재 — DUT 이상 시 시뮬레이션이 무한 대기할 수 있는 구조 | 리셋 해제 대기 로직과 handshake timeout이 없었음 | wait (vif.rst_n === 1'b1)로 리셋 대기 추가, 각 채널 handshake에 1000사이클 timeout 카운터 추가 후 초과 시 uvm_fatal로 명확히 실패 처리 |
| Coverage Closure — 완전 랜덤 시퀀스만으로는 Functional Coverage 62.5%에 그침 | 32비트 완전 랜덤 값이 정확히 0x00000000/0xFFFFFFFF로 나올 확률이 극히 낮고, 5쌍으로는 레지스터×R/W 8개 조합을 다 채우지 못함 | 4개 레지스터를 순회하며 데이터 zero/all_one으로 write+read하는 directed 시퀀스를 추가해 Coverage 100% 달성 |
싱글사이클 RV32I CPU 설계를 기반으로 IF-ID-EX-MEM-WB 5단 파이프라인 CPU를 새로 설계하고, Forwarding/Hazard 처리와 Golden Model(싱글사이클 CPU 재사용) 기반 Scoreboard로 명령어 실행 결과를 검증했습니다.
| 항목 | 결과 |
|---|---|
| 검증 방식 | Golden Model(싱글사이클 CPU) vs DUT(5-stage 파이프라인) 레지스터 write 이벤트 순서 비교 |
| 총 비교 이벤트 | 532건 |
| Scoreboard | PASS 532 / FAIL 0 |
| 발견 후 수정한 버그 | 2건 |
| 파일 | 설명 |
|---|---|
rtl/cpu/if_stage.sv ~ wb_stage.sv | IF/ID/EX/MEM/WB 5단 파이프라인 스테이지 |
rtl/cpu/forwarding_unit.sv | EX/MEM, MEM/WB 단계 Data Forwarding |
rtl/cpu/hazard_unit.sv | Load-use Hazard Stall, Control Hazard Flush |
rtl/cpu/register_file.sv | 파이프라인용 Register File (write-through bypass 포함) |
rtl/cpu/golden_register_file.sv / golden_datapath.sv / golden_top.sv | Golden Model 전용 싱글사이클 CPU (bypass 없음) |
rtl/cpu/rv32i_pipeline_top.sv | 파이프라인 CPU 최상위 |
tb/cpu_pipeline_tb/cpu_scoreboard.sv | Golden Model vs DUT 레지스터 write 이벤트 비교 |
| 문제 | 원인 | 해결 |
|---|---|---|
| Load-use Hazard | Load 명령어 바로 다음 명령어가 그 결과를 쓰려는 경우, Load 데이터는 MEM 단계가 끝나야 나오는데 Forwarding은 EX 단계 시작 시점에 필요해 Forwarding만으로 해결 불가 | hazard_unit이 EX 단계 명령어가 Load인지, 그 목적 레지스터가 ID 단계 명령어의 소스 레지스터와 같은지 감지해 PC/IF-ID 레지스터를 1사이클 정지시키고 ID/EX에 Bubble 삽입 |
| Control Hazard | Branch/Jump 여부가 EX 단계에서야 확정되는데, 그 사이 IF/ID 단계에 이미 Wrong-path 명령어 2개가 들어와 있음 | EX 단계의 pc_sel(Branch taken 또는 Jump)이 뜨면 같은 사이클에 IF/ID, ID/EX 레지스터를 동시에 Flush해 Wrong-path 명령어 제거 |
| [버그] Golden Model 조합논리 무한루프로 시뮬레이션 Hang — heartbeat조차 한 번도 안 찍힘 | 파이프라인용 Register File에 추가한 Write-through Bypass 로직을 Golden Model(싱글사이클)에도 그대로 재사용 — 파이프라인은 Read/Write 사이에 레지스터가 여러 개 껴 있어 안전하지만, 싱글사이클은 Read/Write가 같은 사이클에 조합적으로 연결돼 진짜 Combinational Loop 발생 | heartbeat 신호와 단독 모듈 테스트로 원인을 좁힌 뒤, Bypass 로직이 없는 Golden Model 전용 Register File을 별도로 분리해 해결 |
[버그] EX/MEM 단계 Forwarding이 LUI/AUIPC/JAL/JALR 값을 잘못 전달 — 532건 중 1건 실패(LUI x15,... ; ADDI x15,x15,... 패턴에서 x15 값이 틀림) |
EX 단계 Forwarding 로직이 EX/MEM 단계(1칸 Forwarding)에서 무조건 mem_alu_result(ALU 결과)만 전달 — 그러나 LUI/AUIPC/JAL/JALR의 실제 Write-back 값은 ALU 결과가 아니라 각각 immediate 원본/pc+imm/pc+4 |
WB 단계와 동일한 5-way Mux(mem_fwd_value)를 EX 단계에도 추가해 mem_rf_src_sel에 따라 올바른 값을 선택하도록 수정 |
AXI4-Lite VIP와 구조는 같지만 프로토콜은 더 단순한(SETUP→ACCESS 2단계, 단일 채널) APB UVM VIP를 새로 만들고, AXI4-Lite ↔ APB 프로토콜 변환 브리지를 설계해서 Phase 1의 AXI4 VIP로 브리지 너머의 APB 슬레이브까지 End-to-End로 검증했습니다.
| 항목 | 결과 |
|---|---|
| APB VIP 단독 검증 | PASS 13 / FAIL 0, Coverage 100% |
| AXI-APB 브리지 통합 검증 (AXI4 VIP → 브리지 → APB 슬레이브) | PASS 13 / FAIL 0, Coverage 100%, UVM_ERROR/FATAL 0 |
| 파일 | 설명 |
|---|---|
vip/apb/apb_driver.sv | SETUP→ACCESS, PREADY 대기, Timeout 처리 |
vip/apb/apb_monitor.sv / apb_agent.sv / apb_coverage.sv | APB Monitor / Agent / Functional Coverage |
rtl/example_dut/apb_reg_slave.sv | APB VIP 단독 검증용 예제 슬레이브 |
rtl/bridge/axi_apb_bridge.sv | AXI4-Lite 슬레이브 ↔ APB 마스터 변환 FSM |
tb/axi_apb_bridge_tb/axi_apb_bridge_test.sv | AXI4 VIP(Phase 1) → 브리지 → apb_reg_slave 통합 검증 |
APB VIP를 Phase 1 AXI4 VIP와 동일한 구조(Driver/Monitor/Agent/Sequence/Scoreboard/Coverage)로 구축해 예제 APB 슬레이브로 단독 검증한 뒤, AXI4-Lite ↔ APB 변환 브리지를 별도 RTL로 설계했습니다(AXI4-Lite 슬레이브 포트로 AW/W를 캡처해 APB 마스터 포트의 SETUP→ACCESS 시퀀스로 변환하는 FSM 구조). 최종적으로는 Phase 1의 AXI4 VIP·Sequence·Scoreboard를 그대로 재사용해 "AXI4 VIP → 브리지 → 실제 APB 슬레이브"까지 한 번에 검증했습니다 — 이 단계에서는 APB VIP(Driver/Monitor)를 쓰지 않고 브리지 자체가 APB 마스터 역할을 합니다.
| 문제 | 원인 | 해결 |
|---|---|---|
VS Code `timescale 지시어와 UVM 패키지 파싱 충돌 — Error-[ITSFM] Illegal `timescale for module 발생 후 뒤이은 파일 파싱까지 연쇄적으로 깨짐 |
VCS가 UVM 패키지(`timescale 없음)를 먼저 파싱하는데, 그 뒤에 `timescale이 있는 UVM 클래스 파일이 나오면 timescale 사용 여부 불일치로 에러 발생 |
UVM 클래스를 담은 파일에서는 `timescale을 전부 제거하고, 순수 RTL 모듈에만 유지하는 것으로 통일 |
UVM 클래스(uvm_sequence_item 등) 인식 불가 |
-ntb_opts uvm 옵션은 UVM 패키지 자체를 컴파일에 포함시켜주지만, 각 소스 파일에서 실제로 쓰려면 import uvm_pkg::*;와 `include "uvm_macros.svh"를 파일마다 명시적으로 선언해야 함 |
UVM 클래스를 쓰는 모든 파일 맨 위에 두 줄을 명시적으로 추가 |
NVIDIA Jetson Orin Nano 위에서 Webcam 3대 입력을 실시간으로 처리해, 운전자 졸음, 차량 내 잔류 아동/반려동물, 차량 주변 비인가 접근을 하나의 On-Device AI 파이프라인으로 감지하는 3인 팀 프로젝트입니다. 모든 추론과 위험 판단을 외부 서버 없이 Jetson 내부에서 수행하고, 최종 상태와 이벤트 정보만 Web UI/Telegram으로 전달합니다. 저는 이 중 운전자 졸음 감지(System1)를 전담 설계·구현했고, 잔류 탑승자 감지(System2)의 Jetson 전력 소비 측정 및 분석을 함께 담당했습니다.
| 핵심 플랫폼 | NVIDIA Jetson Orin Nano Developer Kit (Ampere GPU, 최대 67 INT8 TOPS) |
|---|---|
| 입력 | USB Webcam × 3 (운전자 / 뒷좌석 / 차량 주변) |
| OS / SDK | JetPack 6.2.2 (Jetson Linux), CUDA 12.6 |
| AI Framework | PyTorch, TensorRT FP16, TorchScript |
| 모델 학습 환경 | Google Colab (Tesla T4 GPU) |
| 결과 전달 | Web UI (FastAPI / Flask), Telegram Bot Alert |
| 서브시스템 | System1 졸음 감지 · System2 잔류 탑승자 감지 · Security System 보안카메라 |
3대의 Webcam(운전자·뒷좌석·차량 주변)이 Jetson Orin Nano에 각각 연결되고, Jetson 내부에서 3개 서브시스템이 각자의 AI 파이프라인으로 실시간 추론을 수행합니다. 최종 상태와 이벤트 정보만 Web UI와 Telegram Bot으로 전달되어, 영상 데이터 자체는 외부로 나가지 않습니다.
| 서브시스템 | 감지 대상 | 적용 모델 | 최종 출력 |
|---|---|---|---|
| System1 (졸음 감지) | 눈 감김 · 하품 · 고개 자세 | MobileViT-XXS 기반 Multi-task | NORMAL / WARNING / DANGER |
| System2 (잔류 탑승자 감지) | 7세 이하 아동 · 반려동물 잔류 | Fine-tuned YOLO11n + MiVOLO V2 | CHILD / ADULT / ANIMAL, Stage 0~3 |
| Security System (보안카메라) | 등록/미등록 사용자, 위조 얼굴 | YuNet + MiniFASNetV2 + SFace | OWNER / GUEST / UNKNOWN / SPOOF |
| 담당 | 역할 |
|---|---|
| 최민영 | System1(졸음 감지) 개발 및 성능 분석 전담 — Eye/Yawn/Head Pose 데이터셋 구축, ResNet18·MobileNetV2·MobileViT-XXS 학습 및 성능(정확도·모델크기·Jetson Latency) 비교, Grad-CAM 기반 판단 근거 분석, System2용 YOLO11n/s/m Jetson 전력 소비(Energy/Frame) 측정 및 분석, System1 최종보고서·발표자료 작성, 최종 발표 진행 |
| 팀원 | System2(잔류 탑승자 감지) 개발 — YOLO11 정확도 평가, Fine-tuning, Pipeline Latency/FPS 분석 |
| 팀원 | Security System(보안카메라) 구현, System1 데이터셋 학습 보조 |
운전자의 눈 감김(Eye), 하품(Yawn), 고개 자세(Head Pose)를 하나의 Multi-task 모델로 동시에 추론하고, 단일 Frame이 아닌 시간에 따른 상태 변화를 누적해 졸음 위험도(Risk Score)를 실시간으로 판단합니다. ResNet18 / MobileNetV2 / MobileViT-XXS 3종 Backbone을 직접 학습·비교하고, Jetson Orin Nano 실측 성능과 Grad-CAM 분석까지 거쳐 최종 모델을 선정했습니다.
Shared Backbone에서 추출한 특징을 Eye Head(2-class), Yawn Head(2-class), Pose Head(3축 회귀)로 분기하는 구조이며, Eye/Yawn/Head Pose 데이터셋마다 라벨이 달라 Batch마다 해당 Task의 Loss만 계산하되 Shared Backbone은 항상 업데이트하는 Partial-label Multi-task Learning으로 학습했습니다. 최근 3초 구간의 Eye Closed Ratio, 60초 구간의 Yawn Score, 3초 구간의 Head-down Ratio를 가중합해 Risk Score를 산출합니다.
단일 Task 기준으로는 ResNet18이 가장 높은 정확도(96.83%)를 보였지만, Multi-task 학습 결과에서는 가장 가벼운 MobileViT-XXS가 Eye Accuracy(97.31%)·Pose MAE(10.44°) 모두에서 가장 우수했습니다. Latency는 7.13ms로 세 모델 중 가장 길었지만, 30FPS 영상의 Frame당 처리 제한시간(약 33.3ms) 대비 충분히 짧아 실시간 처리에 문제가 없다고 판단했습니다.
| 모델 | Eye Accuracy | Pose MAE | Parameters | ONNX 크기 | Jetson Latency |
|---|---|---|---|---|---|
| ResNet18 | 93.09% | 11.94° | 11.18M | 42.65MB | 5.72ms |
| MobileNetV2 | 93.78% | 10.73° | 2.23M | 8.54MB | 5.90ms |
| MobileViT-XXS ✅ | 97.31% | 10.44° | 0.95M | 4.08MB | 7.13ms |
수치 비교만으로 설명하기 어려운 정확도 차이를 보완하기 위해, Eye Task의 OPEN/CLOSED 판단 근거를 Grad-CAM으로 시각화했습니다. CLOSED 샘플은 세 모델 모두 정확히 분류했으나, OPEN 샘플에서는 MobileViT-XXS가 8/10으로 가장 우수했습니다. Heatmap 비교 결과 MobileNetV2는 눈 전체와 주변까지 넓게 활성화된 반면, MobileViT-XXS는 동공·눈꺼풀 중심의 좁은 영역에 집중되어 Eye 상태 판단과 직접 관련된 특징에 더 집중하는 경향을 확인했습니다.
졸음 판단의 핵심 지표인 Eye Accuracy, 가장 작은 모델 크기(Jetson에서 여러 모델을 동시 실행해야 하는 메모리 제약), Grad-CAM으로 확인한 판단 근거의 타당성을 종합해 MobileViT-XXS를 System1의 최종 모델로 선정했습니다.
System2는 YOLO11(Person/Cat/Dog 검출) + MiVOLO V2(연령 추정)의 2-Stage Pipeline으로 7세 이하 아동·반려동물 잔류를 감지하고, 잔류 지속시간에 따라 Stage 0~3 단계별로 경고합니다. 저는 이 중 YOLO11n/s/m의 Jetson 실측 전력 소비(Average Power, Energy/Frame) 측정 및 분석을 담당했습니다.
측정 초기에는 1회 단발 측정으로 인해 YOLO11s가 YOLO11m보다 전력이 높게 나오는 비정상적인 결과가 나왔는데, 모델당 30초씩 3회 반복 실행 + 실행 사이 5초 대기시간을 Shell Script로 자동화하고 평균·표준편차를 계산해 YOLO11n < YOLO11s < YOLO11m의 정상 경향을 재현성 있게 확인했습니다(다만 차이는 약 67mW로 미미). 또한 MiVOLO V2가 측정 시점에 ONNX CPU Provider로 실행되어 최종 시스템(TorchScript GPU)과 조건이 달랐던 것을 발견해 GPU 실행으로 통일 후 재측정했습니다.
팀원이 구현한 서브시스템으로, YuNet(얼굴 검출) → MiniFASNetV2(Anti-Spoofing) → SFace(얼굴 인식) 3-Stage Pipeline으로 OWNER/GUEST/UNKNOWN/SPOOF를 판정합니다. UNKNOWN/SPOOF를 단일 Frame에서 즉시 경고하지 않고, Capture Timer와 Presence Timer를 분리 운영해 순간적 오검출과 실제 장시간 비인가 접근을 구분하는 지속시간 기반 보안 정책이 특징입니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| 초기 전체 영상 기반 졸음 분류 시 학습-실행 환경 간 배경/조명 차이로 예측 불안정 | 전체 Frame을 입력하면 졸음 판단과 무관한 배경·조명 특징까지 함께 학습되어, 촬영 환경이 바뀌면 예측이 흔들림 | 졸음 여부를 하나의 Class로 직접 분류하지 않고 Eye/Yawn/Head Pose 3개 Task로 분리, 얼굴 검출 기반 ROI만 입력하도록 구조 변경. 각 Task 결과를 시간축으로 누적해 Risk Score를 계산하는 구조로 확장 |
| 단일 Task 평가와 Multi-task 평가에서 모델 순위가 정반대로 나타남 | 단일 Task에서는 ResNet18이 최고, MobileViT-XXS가 최저였으나 Multi-task 결과에서는 정반대로 MobileViT-XXS가 최고 성능 — 두 구조와 데이터 구성이 달라 직접 비교가 어려움 | 실제 사용하는 Multi-task 결과를 모델 선정의 우선 기준으로 설정하고, 정량 수치만으로 원인을 단정하지 않기 위해 Grad-CAM으로 각 모델의 판단 근거를 추가 검증 |
| 이론적으로 가장 경량인 MobileViT-XXS의 실제 Jetson Latency가 가장 길게 측정됨 | ResNet18/MobileNetV2는 Convolution 중심 구조로 TensorRT 최적화 Kernel을 효과적으로 활용하지만, MobileViT-XXS의 Transformer/Attention 연산은 동일 수준의 최적화 효과를 얻지 못함 | GFLOPs만으로 실행 속도를 판단하지 않고 실제 Jetson Latency/FPS를 최종 평가 항목에 포함, 7.13ms가 30FPS 처리 요구조건(약 33.3ms)을 충분히 만족하는지 확인 후 채택 |
HAL 없이 레지스터를 직접 제어하는 방식으로 4x4 키패드 인증, I2C LCD, 8x8 도트매트릭스, 서보 PWM을 통합한 상태 머신 기반 도어락 시스템.
STM32F411RE(Cortex-M4) 위에서 HAL 라이브러리를 쓰지 않고 레지스터를 직접 제어하는 방식으로 만든 도어락 시스템입니다. 4x4 키패드로 비밀번호를 입력받아 I2C LCD에 표시하고, 인증에 성공하면 서보모터로 잠금을 해제, 실패가 누적되면 일정 시간 입력을 막는 Lockout 상태로 전환됩니다. HAL 없이 직접 레지스터를 건드린 이유는 각 페리퍼럴이 실제로 어떻게 동작하는지 이해하고 싶었기 때문입니다.
IDLE → INPUT → CHECK → UNLOCK/LOCKOUT의 4단계 상태 머신으로 인증 로직을 설계했습니다. 비밀번호가 일치하면 UNLOCK 상태로 전환되어 서보모터가 문을 열고, 불일치 시 다시 IDLE로 돌아가되 실패 횟수를 누적합니다. 3회 연속 오답 시 10초간 LOCKOUT 상태로 전환되어 입력 자체를 막는 방식으로 무차별 대입 시도를 방지했습니다.
LCD 통신에 쓰인 I2C1 페리퍼럴을 HAL 없이 START/ACK/STOP 시퀀스부터 직접 구현했습니다. 레지스터를 직접 다루다 보니 타이밍 이슈나 ACK 누락 같은 문제를 초반에 자주 만났고, 이 과정에서 I2C 프로토콜의 신호 레벨 동작을 훨씬 깊이 이해하게 됐습니다.
TIM2/TIM3/TIM4/TIM5 네 개의 타이머를 각각 서보 PWM 제어, LCD 화면 갱신, Lockout 카운트다운, 범용 딜레이 용도로 나눠 배치했습니다. 하나의 타이머를 여러 용도로 겸용하면 인터럽트 우선순위 충돌이나 타이밍 꼬임이 생기기 쉬워서, 기능별로 완전히 분리하는 쪽을 택했습니다.
개발 중 배선 불량, 접촉 불량 같은 하드웨어 문제와 실제 로직 버그가 뒤섞여 원인 파악이 어려운 경우가 많았습니다. 이를 해결하기 위해 각 페리퍼럴 상태를 실시간으로 출력하는 진단용 펌웨어를 별도로 만들어, 문제가 하드웨어 쪽인지 소프트웨어 쪽인지 먼저 구분한 뒤 접근하는 방식으로 디버깅 시간을 줄였습니다.
MicroBlaze를 AXI Master로 사용하고, 직접 설계한 Custom AXI I2C Master IP를 포함한 GPIO/Timer Peripheral을 AXI4-Lite로 통합한 SoC. LCD/FND/LED/Button 기반 반응속도 측정(Reaction Test) 앱을 HW/SW Co-Design으로 구현하고 UVM으로 검증.
| Processor | MicroBlaze |
|---|---|
| Bus Protocol | AXI4-Lite (32-bit, Memory-Mapped) |
| FPGA Board | Digilent Basys3 |
| Custom Peripheral | AXI I2C Master, AXI GPIO, AXI Timer |
| Application FSM | IDLE → WAIT(2~5s 랜덤) → GO → RESULT |
| Verification | Vivado Simulation, Logic Analyzer, UVM, Functional Coverage(29/29 Bin, 100%) |
Button → Custom AXI GPIO → MicroBlaze Application → Reaction Test FSM → Timer(1ms Tick) → FND/LED → Custom AXI I2C Master → PCF8574 LCD 순서로 데이터가 흐릅니다.
유지형 Control Bit(intr_en)와 Pulse형 Command(cmd_start/stop/write/read)를 분리 설계했습니다.
Command bit는 1클럭 펄스 후 하드웨어가 자동 클리어되며, done은 SR을 읽으면 자동 클리어(rc_r)되어 intr_en과 AND되어 인터럽트를 발생시킵니다.
| Offset | 이름 | 설명 |
|---|---|---|
| 0x00 | CR | cmd_read/write/stop/start (Pulse, W), intr_en (R/W) |
| 0x04 | DR | TX/RX data (R/W) |
| 0x08 | ADDR | slave_addr (R/W) — LCD(0x27), Slave(0x41) |
| 0x0C | SR | ack_out(R), done(rc_r), busy(R) — Read Only |
IDLE → WAIT(랜덤 2~5s) → GO → RESULT → IDLE 상태로 구성되며, WAIT 상태에서 너무 빨리 버튼을 누르면(Too Early) 결과를 9999로 처리합니다. Timer Interrupt 기반 1ms Tick과 FND Multiplexing으로 반응시간을 논블로킹으로 측정하도록 설계했습니다.
Custom AXI GPIO IP에 대해 Write/Readback/WSTRB/Corner Pattern 등 5개 UVM 테스트를 수행해 Functional Coverage 29/29 Bin, 100%를 달성했습니다. AXI Simulation, I2C FSM 신호, Logic Analyzer 실측까지 전부 정상 확인했습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| FND와 LCD가 서로 다른 반응시간 값을 표시 | FND는 GO 상태에서 10ms마다 갱신한 마지막 값을, LCD는 Button 이벤트 시점에 새로 계산한 값을 사용 — 서로 다른 시점의 값 | Button Push Event 감지 순간 결과값을 한 번만 계산(Capture)해 FND/LCD/I2C Slave에 동일한 전역값을 사용하도록 수정 |
| Custom I2C Master의 Command가 여러 번 반복 실행될 위험 | AXI Register의 START/WRITE/READ가 level 신호로 유지되면 반복 실행 가능 | Command를 1-clock Pulse로 변환해 한 번의 Write마다 한 번만 동작하도록 구성 |
| Master/Slave가 SDA 라인을 동시에 구동할 위험 | I2C는 여러 장치가 하나의 데이터선을 공유하는 오픈 드레인 버스 | SDA를 Open-Drain(tri-state)으로 설계해 충돌 없이 공유 |
SPI와 I2C Master/Slave RTL을 각각 설계하고, UVM(sequence → driver → monitor → scoreboard/coverage) 기반 검증 환경으로 기능 검증. Verdi 시뮬레이션·Functional Coverage뿐 아니라 Logic Analyzer로 FPGA 실측 파형까지 확인해 simulation과 hardware의 정합성을 검증한 개인 프로젝트.
| 항목 | 내용 |
|---|---|
| 언어 | SystemVerilog, Verilog HDL |
| 검증 방법론 | UVM |
| Simulator | VCS / Verdi |
| FPGA Board | Digilent Basys3 (Vivado) |
| 측정 장비 | Saleae Logic Analyzer |
| SPI 주요 신호 | SCLK, MOSI, MISO, SS_N (Mode 0: CPOL=0, CPHA=0) |
| I2C 주요 신호 | SCL, SDA (Open-Drain) |
| 최종 결과 | SPI/I2C Scoreboard 전부 PASS, Functional Coverage 100% |
sequence → agent(sequencer/driver/monitor) → scoreboard/coverage 구조이며, Interface를 경계로 위쪽은 Software(UVM), 아래쪽은 Hardware(DUT)로 분리했습니다.
Master 보드(button_debounce + Master Controller + Master + 7-seg)와 Slave 보드(Slave + Slave Controller + 7-seg)로 나눈 실제 FPGA 2-Board 구성입니다.
START/STOP 조건과 ACK/NACK 응답이 I2C 프로토콜의 핵심입니다. 실제 파형으로 두 상황을 비교 분석했습니다.
단순 데이터 비교가 아니라 I2C 프로토콜 규칙 자체를 판정 로직에 반영했습니다.
트랜잭션 완료 여부(타임아웃), NACK 테스트인지 ACK 테스트인지, 그리고 실제 데이터 일치 여부를 순서대로 검사해서 각각 다른 사유로 PASS/FAIL을 기록합니다.
의도적으로 잘못된 주소를 보내 NACK을 유도하는 테스트에서는 NACK이 오는 것이 오히려 정상 동작이므로, sequence item에 expect_nack 필드를 추가하고 uvm_config_db로 scoreboard까지 전달해 정상/의도된 NACK 시나리오를 모두 정확히 판정하도록 만들었습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| I2C transaction이 계속 NACK 응답, ADDR_ACK에서 멈춤 | Master Controller의 SLAVE_ADDR와 Slave의 my_addr 파라미터 불일치 | 인스턴스화 시 두 파라미터를 일치하도록 수정 |
expect_nack 플래그가 scoreboard에 전달되지 않아 NACK 테스트가 FAIL로 오판정 |
UVM monitor는 관찰 결과로 새 transaction item을 생성하므로 sequence item 값이 monitor item으로 자동 전달되지 않음 | uvm_config_db로 expect_nack 값을 scoreboard까지 전달, 이 값 기준으로 판정 분기 |
| I2C 0xFF 데이터가 coverage에 반영되지 않아 100% 미달성 | 마지막 transaction이 monitor/coverage collector에 전달되기 전에 simulation 종료 | sequence 종료 후 delay를 추가해 마지막 transaction 전달 시간 확보 |
| SPI monitor의 tx_data 캡처 타이밍 오류로 scoreboard mismatch | Loopback 구조에서 현재 rx_data는 이전 transaction의 tx_data와 대응되는데, monitor가 같은 transaction으로 묶음 | monitor capture timing 조정 + scoreboard가 prev_tx를 expected value로 사용하도록 수정 |
RV32I 명령어 서브셋을 지원하는 Single Cycle CPU를 SystemVerilog로 설계하고, R/I/IL/S/B/U/J 각 명령어 타입별 datapath·control path를 시뮬레이션으로 검증. C 코드 기반 누적합(sum)·버블 정렬 프로그램 실행까지 검증.
| CPU 구조 | RV32I Single Cycle CPU |
|---|---|
| Register File | 32 registers, x0 write protection 적용 |
| 지원 명령어 타입 | R, I, IL(Load), S, B, U, J |
| ALU 기능 | ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND, branch compare |
| Load / Store | LB/LH/LW/LBU/LHU, SB/SH/SW |
| Branch / Jump | BEQ/BNE/BLT/BGE/BLTU/BGEU, JAL/JALR, LUI/AUIPC |
Program Counter → Instruction Memory → Control Unit(opcode/funct3/funct7 decode) → Register File(rs1/rs2 read) → Immediate Generator → ALU → Data Memory → Write-back MUX → Register File(write-back) 순서로 데이터가 흐르며, branch/jump 여부에 따라 next PC가 결정됩니다.
R-type은 rf_we=1, alusrc_sel=0으로 rs1/rs2가 ALU를 거쳐 곧바로 write-back되고, I/IL-type은 alusrc_sel=1로 immediate를 ALU 입력으로 사용합니다.
같은 immediate 기반 연산이라도 I-type은 ALU 결과를, IL-type(Load)은 Data Memory 출력을 write-back한다는 점이 구분 포인트였습니다. B-type은 Register File write 없이 branch 조건만으로 PC를 갱신하고, JAL/JALR은 PC+4를 link register(rd)에 저장합니다.
R-type부터 J-type까지 7개 타입 모두 opcode decode → control signal → datapath 연산 → write-back까지 단계별로 검증했습니다. 여기서 그치지 않고, 누적합(loop, function call, stack frame 포함)과 버블 정렬(배열 접근, swap, branch) C 프로그램을 instruction memory에 올려 실제 프로그램 흐름 레벨까지 검증했습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| x0 레지스터 값이 0으로 유지되지 않고 변경됨 | write-back 조건이 waddr에 상관없이 write를 수행 — 특히 JAL/JALR에서 rd=x0로 지정된 경우 PC+4가 x0에 write-back됨 | write-back 조건에 waddr != 5'd0을 추가해 x0에 대한 write를 항상 차단 |
| I-type과 IL-type의 write-back 결과가 섞일 위험 | 둘 다 immediate 기반 ALU 연산을 하지만 최종 저장값의 출처가 다름(ALU result vs Load data) | rf_src_sel을 I-type=ALU result, IL-type=Data Memory output으로 분리 |
| ADD/SUB, SRL/SRA처럼 funct3가 동일한 명령어 구분 | funct3만으로는 두 연산을 구분할 수 없음 | funct7[5] 비트를 함께 확인하는 ALU control mapping 적용 |
UART RX/TX, FIFO, ASCII Decoder를 하나의 DUT로 통합하고, SystemVerilog 기반 class-based testbench(Generator → Driver → Monitor → Scoreboard)로 End-to-End 기능 검증을 수행한 개인 프로젝트입니다. Directed test와 Constraint Random test를 함께 적용해 각 블록 단위 검증과 통합 경로 검증을 모두 수행했습니다.
| UART | 9600bps, 16x Oversampling, 1 Start + 8 Data + 1 Stop bit, LSB First |
|---|---|
| FIFO | 8-bit 폭, 16-depth, Register File 기반 Circular Buffer |
| ASCII Decoder | 입력 R/L/U/D/M/S → one-hot 출력 |
| Testbench 구조 | Class-based (Transaction/Generator/Driver/Monitor/Scoreboard), Interface + Virtual Interface |
| 반복 검증 | UART Random Test 200회, FIFO Random Test 100회 |
UART RX → FIFO → ASCII Decoder 전체 경로를 하나의 top 모듈로 통합했습니다. Generator가 transaction을 생성하면 Driver가 interface를 통해 DUT에 입력하고, Monitor가 출력을 관찰해 Scoreboard로 전달, Scoreboard가 내부 reference queue와 비교해 PASS/FAIL을 판정하는 구조입니다.
| 파일 | 설명 |
|---|---|
uart_top.v | UART RX/TX + FIFO + ASCII Decoder 통합 DUT |
uart_if.sv | DUT ↔ Testbench 연결 Interface |
uart_transaction.sv | UART 자극(stimulus) 데이터 정의 (Transaction class) |
uart_generator.sv | Directed/Random transaction 생성 |
uart_driver.sv | Transaction → interface 신호로 변환해 DUT에 인가 |
uart_monitor.sv | DUT 출력 관찰 및 transaction 재구성 |
uart_scoreboard.sv | Reference queue 기반 PASS/FAIL 판정 |
uart_env.sv | Generator/Driver/Monitor/Scoreboard 연결 환경 |
uart_test.sv | 시나리오별 테스트 (Reset/Directed/Random) |
| 모듈 | 시나리오 | 결과 |
|---|---|---|
| UART | Reset 상태 확인 | ✅ PASS |
| TX only (Directed) | ✅ PASS | |
| RX only (Directed) | ✅ PASS | |
| Random TX (Constraint Random 200회) | ✅ PASS | |
| FIFO | Reset 상태 확인 | ✅ PASS |
| Push only / Pop only (Directed) | ✅ PASS | |
| Push/Pop 동시 동작 (Constraint Random 100회) | ✅ PASS | |
| 통합 | UART RX → FIFO → ASCII Decoder End-to-End | ✅ PASS |
| 문제 | 원인 | 해결 |
|---|---|---|
| FIFO Random test에서 push+pop 동시 발생 시 fail 다수 | Scoreboard 모델은 push→pop 순서로 비교했지만, 실제 DUT는 pop_data가 조합 논리(현재 rptr 기준)이고 write는 posedge에서 반영되어 pop→push 순서로 동작 | Scoreboard의 처리 순서를 DUT와 동일하게 pop 먼저 → push 나중으로 수정 |
fork ~ join_any + disable fork 종료 구조에 대한 피드백 |
병렬 thread 중 하나만 끝나도 다음 코드로 진행되어, Scoreboard 비교가 끝나기 전에 강제 종료될 가능성 | transaction count 기반으로 모든 비교 완료 후 종료하는 구조로 개선 필요성 정리 |
Testbench 아키텍처 설계(Generator/Driver/Monitor/Scoreboard), Interface 정의, Directed/Constraint Random 시나리오 작성, 통합 경로 Scoreboard 판정 로직 구현까지 전 과정을 직접 담당했습니다.
FPGA(Basys-3) 기반 UART 통신 + FIFO 버퍼링 + SR04/DHT11 센서 + Stopwatch/Watch를 하나의 시스템으로 통합한 프로젝트입니다. 물리 버튼과 UART 명령을 공통 제어 경로로 통합하고, 시간 데이터·센서 데이터를 FND와 UART 양쪽에 동시에 출력하도록 설계했습니다. 4인 팀 프로젝트.
| 동작 클럭 | 100MHz (System Clock) |
|---|---|
| UART 통신 속도 | 9600 bps (16x Oversampling) |
| UART 프레임 | 1 Start bit + 8 Data bit + 1 Stop bit |
| FIFO | RX FIFO(수신 버퍼) / TX FIFO(송신 버퍼), Circular Buffer 구조 |
| 시간 해상도 | msec(0-99) · sec(0-59) · min(0-59) · hour(0-23) |
| SR04 | Trig/Echo 기반 거리 측정, 3-digit 출력 |
| DHT11 | 단일 데이터라인, 40bit 수신 + checksum 검증, 4-digit 출력 |
| 입력 | 버튼(btnR/L/U/D), Switch, UART 명령(R/L/U/D/M/S) |
| 출력 | 4-digit FND(Main/Sensor 공용), LED, UART ASCII 상태 출력 |
top_uart가 UART 명령을 해석해 내부 제어 신호를 만들고 상태를 ASCII로 되돌려보내는 역할을, top_fnd가 Stopwatch/Watch·SR04·DHT11·FND 출력 등 실제 데이터 생성을 담당합니다.
Control Unit FSM이 버튼과 UART 입력을 공통 경로로 받아 스위치 조합(sw[3]/sw[1])에 따라 Watch/Stopwatch/SR04/DHT11 모드를 선택해 각 블록에 제어 신호를 분배하고, FND 출력 선택 로직이 sw[3] 기준으로 Main FND(시간)와 Sensor FND(센서) 중 하나만 최종 출력 포트로 전달합니다.
| 파일 | 설명 |
|---|---|
top_final.v | 최상위 통합 모듈 (top_uart + top_fnd) |
top_uart.v | UART 명령 해석 + 상태 ASCII 송신 상위 블록 |
top_fnd.v | Stopwatch/Watch + Sensor + FND 출력 상위 블록 |
uart.v | UART Rx/Tx + Baud Tick Generator |
fifo.v | RX/TX FIFO (Circular Buffer) |
ascii_decoder.v | UART ASCII 명령 → 내부 제어 신호 변환 |
ascii_sender.v | 내부 상태 데이터 → ASCII 문자열 변환 및 송신 |
control_unit_fsm.v | 전체 모드 선택 및 제어 신호 생성 (중앙 제어 FSM) |
sr04_controller.v | SR04 초음파 센서 제어 (Trig/Echo, 거리 계산) |
dht11_controller.v | DHT11 온습도 센서 제어 (40bit 수신, checksum) |
sensor_fnd.v | SR04/DHT11 데이터 공용 FND 출력 (Mux + Leading zero 제거) |
button_debounce.v | 버튼 Debounce + Edge Detection |
| 항목 | 목표 | 결과 |
|---|---|---|
| DHT11 Controller | 40bit 수신 후 humidity/temperature/checksum 정상 산출 | ✅ 달성 (humidity=60, temperature=25, valid=1) |
| SR04 Controller | Trig 출력, Echo pulse width 기반 거리 계산 | ✅ 달성 (echo 1160us → distance=20) |
| top_fnd 모드 선택 | sw 조합에 따라 Watch/Stopwatch/SR04/DHT11 FND 출력 선택 | ✅ 달성 |
| Watch 설정 모드 전이 | WATCH→HOUR→MIN→SEC 및 역방향 전이, blink 표시 | ✅ 달성 |
| UART RX 명령 해석 | R/L/U/D/M/S 문자 → 대응 제어 신호 생성 | ✅ 달성 |
| UART TX 상태 송신 | select에 따라 watch/stopwatch/SR04/DHT11 데이터를 ASCII로 송신 | ✅ 달성 |
FPGA(Basys-3) + ComPortMaster 환경에서도 DHT11(temp/humidity:24/51 등)과 SR04(DISTANCE:167 등) 데이터가 FND·UART 양쪽에 정상 반영됨을 확인했습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| SR04 실보드에서 거리값이 갱신되지 않음 | 사용 중이던 초음파 센서 자체의 하드웨어 불량 | RTL/FSM은 정상 확인, 센서 모듈 교체로 해결 — simulation만으로는 판단 불가함을 확인 |
| ASCII Decoder 출력이 한 사이클 늦게 나옴 | FSM(순차 논리)으로 설계되어 즉시성이 떨어짐 | 조합 논리 디코더로 변경하여 입력 즉시 one-hot 출력되도록 수정 |
| FIFO_TX에서 empty 상태 pop 오동작, 포인터 미복구 | 데이터 폭([31:0])과 FIFO 접근 범위([20:0])가 불일치 | 전송 완료 후 포인터 reset 조건 추가, 데이터 폭과 접근 범위 일치시킴 |
| ASCII_SENDER에서 multiple driven net 발생 | 문자열 생성과 송신 제어가 한 블록에 혼재 | 문자열 생성부와 송신 제어부를 분리, 준비-송신 타이밍을 단계적으로 구분 |
| DHT11 FPGA-센서 간 출력 충돌 가능성 | 단일 데이터선(inout)에서 FPGA와 센서가 동시에 구동 | tri-state 구조로 출력 구간 분리 |
| DHT11 응답 신호 비동기 입력 metastability 우려 | system clock과 비동기 관계 | 2-stage synchronizer 추가로 FSM에는 동기화된 신호만 전달 |
| Stopwatch/Watch + Sensor 통합 시 FSM 상태 전이 복잡도 증가 | 기존 FSM이 Stopwatch/Watch 중심 구조 | SR04/DHT11 상태를 추가하고 sw 조합 기준으로 상태 전이를 재정리 |
| 담당 | 역할 |
|---|---|
| 최민영 | DHT11 Sensor 설계, Watch/Stopwatch 설계, FND Controller 설계 |
| 공동 | Testbench 및 Simulation, 시스템 통합 및 디버깅 |
FPGA(Basys-3) 상에서 UART(Universal Asynchronous Receiver/Transmitter) 송수신 구조를 구현하고, Loopback(수신 데이터를 그대로 재전송) 방식을 통해 송수신 기능의 정확성을 검증한 프로젝트입니다. Tx/Rx를 FSM 기반으로 분리 설계하고, 16배 Oversampling으로 수신 안정성을 확보했습니다.
| 동작 주파수 | 100MHz (System Clock) |
|---|---|
| Baud Rate | 9600 bps |
| Oversampling | 16x (b_tick 기준) |
| 데이터 프레임 | 1 Start bit + 8 Data bits + 1 Stop bit |
| 전송 방식 | Serial, LSB-first |
| FPGA / Tool | Basys-3 / Vivado, Vivado Simulator, ComPortMaster |
UART Receiver(Rx)는 rx 신호의 High→Low 전이로 Start bit를 감지한 뒤 b_tick 기반 카운터로 mid-point에서 샘플링하고, 8bit 수신이 완료되면 rx_done(1-cycle pulse)을 발생시킵니다. UART Transmitter(Tx)는 tx_start 신호로 동작을 시작해 Start bit(0) → 8bit 데이터(LSB-first, shift register) → Stop bit(1) 순서로 출력하며, 전송 중에는 tx_busy가 High를 유지합니다. Baud Tick Generator는 시스템 클럭을 분주하여 Tx/Rx가 공유하는 공통 타이밍 기준(9600bps × 16)을 생성하고, Loopback 구조는 rx_done → tx_start를 직접 연결해 별도 제어 로직 없이 수신 즉시 재전송되도록 합니다.
| 파일 | 설명 |
|---|---|
uart.v | UART Top module — uart_tx, uart_rx, baud_tick_gen 통합 |
uart_loopback.v | Loopback 구조 최상위 모듈 (rx_done → tx_start 연결) |
| 항목 | 목표 | 결과 |
|---|---|---|
| UART Loopback 동작 | 입력 데이터(8'h30)와 동일한 값이 출력됨 | ✅ 달성 |
| Tx FSM 상태 전이 | IDLE → START → DATA → STOP → IDLE 순서 전이 | ✅ 달성 |
| Tx 데이터 전송 순서 | LSB-first(bit0→bit7) 방식으로 출력 | ✅ 달성 |
| tx_busy 신호 | START~STOP 구간 동안 High 유지, 종료 후 Low | ✅ 달성 |
| Rx FSM 상태 전이 | IDLE → START → DATA → STOP → IDLE 순서 전이 | ✅ 달성 |
| Rx 데이터 샘플링 | b_tick 기준 각 bit 구간에서 안정적 샘플링 | ✅ 달성 |
| rx_done 신호 | 수신 완료 시 1-cycle pulse 발생 | ✅ 달성 |
FPGA(Basys-3) + PC 시리얼 통신(ComPortMaster) 환경에서도 PC에서 입력한 문자열이 동일하게 되돌아오는 것을 확인했으며, 반복 실험에서도 데이터 손실·순서 오류 없이 안정적으로 동작함을 검증했습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| DATA bit 전송 순서가 직관적으로 이해 안 됨 | LSB-first 전송 방식 | data_reg를 right shift하는 구조로 이해/구현 |
| bit 전송 타이밍이 일정하지 않음 | 타이밍 기준 미정립 | b_tick 기준 16 tick마다 1bit 전송하는 카운터 구조 추가 |
| Start bit 검출 후 샘플링 시점 불명확 | 노이즈·전이 구간 영향 | 16x oversampling 중 8번째 tick(mid-point)에서 샘플링 |
| rx_done → tx_start 직접 연결 시 Tx 동작 불안정 | rx_done이 1-cycle pulse인지 불명확 | Tx FSM이 IDLE 상태에서만 tx_start를 인식하도록 제한 |
| STOP 상태에서도 tx_busy가 계속 유지됨(오동작으로 오인) | STOP 상태가 실제 Stop bit를 출력 중인 구간이라 정상 동작 | 파형 분석으로 정상 동작임을 확인, tx_busy는 전송 완료 시점과 정확히 일치 |
UART Tx/Rx FSM 설계, Testbench 작성 및 시뮬레이션 검증, Loopback 구조 System Integration, FPGA Bitstream 생성 및 보드 동작 확인까지 전 과정을 직접 담당했습니다.
FPGA(Basys-3)에서 Stopwatch와 Digital Watch를 하나의 시스템으로 통합 구현. Control Unit(FSM)과 Datapath를 분리한 모듈화 구조, Debounce + Edge Detection 기반 버튼 입력 처리, 계층적 Tick 카운터 구조 적용.
Control Unit(FSM)이 상태 전이와 제어 신호 생성을 담당하고(Stopwatch: STOP→RUN→STOP / STOP→CLEAR / STOP→MODE→STOP, Watch: WATCH→HOUR_SET→MIN_SET→WATCH), Datapath가 그 신호를 받아 실제 시간 데이터를 연산합니다. msec→sec→min→hour로 overflow/underflow(carry/borrow)가 전파되는 계층적 Tick 카운터 구조를 적용했습니다.
2인 팀 프로젝트였고, 저는 Testbench 작성 및 시뮬레이션 검증, 모듈 통합(System Integration), FPGA Implementation을 담당했습니다.
| 문제 | 원인 | 해결 |
|---|---|---|
| 버튼 하나 입력이 여러 번 인식됨 | 기계식 버튼의 Bouncing 현상 | Debounce + Rising Edge Detection으로 1-cycle pulse 생성 |
| minute↔hour 연동이 안 됨 | carry/borrow 신호가 상위 카운터로 제대로 전달되지 않음 | overflow/underflow 시 tick 전달 경로 수정 |
| 설정 모드에서 값이 여러 단계 튐 | 버튼을 level 신호로 처리해 눌려있는 동안 계속 반영됨 | edge 기반(pulse) 입력으로 변경 |