Making a VGA Driver in SystemVerilog
SystemVerilog로 VGA 드라이버 만들기
DE10-Lite FPGA와 SystemVerilog로 VGA 신호 타이밍을 만들고 이미지를 출력하는 과정을 설명합니다. 50MHz 보드 클럭을 25MHz로 나누고, 수평·수직 카운터와 ROM 이미지 데이터를 사용해 화면을 구성하며 알파 합성 방식도 비교합니다.
- 주제
에디터 노트
이 프로젝트의 교육적 가치는 VGA 신호를 실제 픽셀 출력까지 이어 붙이는 과정에 있습니다. 카운터로 표시 구간과 동기 신호를 만들고 ROM 주소를 계산해 이미지를 읽는 구조가 구체적으로 설명됩니다. 알파 합성에서는 간단한 투명 여부 판정과 비트 시프트 근사, 곱셈을 비교해 구현 선택이 화면 결과에 어떻게 드러나는지도 보여줍니다. 다만 타이밍 수치에는 내부 불일치가 있습니다. 수평 후면 포치는 표에서 16픽셀인데 본문 계산은 48픽셀을 더해 총 800픽셀로 잡습니다. 구현에 필요한 기준을 확인하려는 독자에게는 이 차이가 혼란을 줄 수 있습니다. 댓글에서 소스 공개를 요청했지만 작성자는 수업 과제의 정답 공유 문제를 이유로 거절했습니다. 따라서 글은 설계 과정을 설명하지만 재현 가능한 코드까지 제공하지는 않습니다.
AI 요약
대학 디지털 논리 설계 실습에서 VGA 드라이버를 만든 과정을 다룹니다. VGA는 CRT가 전자빔으로 화면을 한 줄씩 그리던 시절의 표준으로, 수평·수직 동기 신호인 HSync와 VSync가 빔의 이동을 조정합니다. 화면에 픽셀을 표시하지 않는 구간을 포함해 정해진 시간 안에 한 프레임을 보내야 합니다.
클럭과 화면 타이밍
60Hz 화면 갱신에 필요한 픽셀 클럭은 약 25.175MHz입니다. 사용한 DE10-Lite FPGA 보드는 50MHz 클럭을 제공하므로, 매 클럭마다 신호를 반전하는 간단한 분주 회로를 만들어 25MHz를 얻습니다. 글에서는 HSync와 VSync로 디스플레이를 재동기화하므로 25.175MHz와 정확히 일치하지 않아도 된다고 설명합니다.
색 신호를 계속 출력하지 않도록 수평·수직 위치를 세는 카운터도 둡니다. 수평 방향은 표시 영역 640픽셀에 전면 포치 16픽셀, 동기 펄스 96픽셀, 후면 포치를 더해 한 줄을 구성합니다. 수직 방향은 표시 영역 480줄에 전면 포치 33줄, 동기 펄스 2줄, 후면 포치 10줄을 더해 한 프레임을 구성합니다. 수평 카운터가 줄 끝에 도달할 때만 수직 카운터를 증가시키며, 표시 영역 안에서만 색 신호를 켭니다. 글의 표에는 수평 후면 포치가 16픽셀로 적혀 있지만 본문 계산에는 48픽셀이 쓰여 있습니다.
시뮬레이션과 색 출력
먼저 지정된 횟수만큼 신호를 낮게 유지한 뒤 설정한 카운트에서 초기화하는 매개변수화 카운터 모듈을 만듭니다. 이 모듈을 이용해 프레임 위치와 표시 가능 여부를 계산합니다. 작성자는 타이밍을 맞추는 데 10시간 이상을 썼고, 설계를 다시 손보는 과정도 거쳤다고 합니다.
보드의 스위치는 10개지만 VGA 색상은 빨강, 초록, 파랑 각 4비트로 총 12비트입니다. 제한된 입력을 넓은 색상 값으로 대응시키기 위해 2비트 입력 00, 01, 10, 11을 각각 0000, 0101, 1010, 1111로 변환합니다. 핀을 VGA 포트에 연결한 뒤 실제 모니터에서 출력을 확인합니다.
ROM 이미지와 투명도
두 번째 실습에서는 이미지를 FPGA 보드의 ROM에 넣고 화면 왼쪽 위에 표시합니다. 이미지 파일을 메모리 초기화 파일로 변환하는 프로그램은 원래 Python으로 작성됐지만, 의존성 문제를 겪은 뒤 파일 형식을 이해하고 다른 학생도 쓰기 쉽게 하려고 Go로 다시 작성합니다. 수평·수직 카운터를 픽셀 좌표로 사용하고 ROM 주소를 계산해 화면에 이미지 데이터를 출력합니다.
추가 과제로 투명도도 구현합니다. 픽셀의 마지막 바이트에 저장된 알파 채널을 읽어 배경과 합성합니다. 완전히 불투명한 픽셀만 표시하면 가장자리가 계단처럼 보입니다. 비트 시프트를 이용한 근사 방식은 배경색에 반올림 차이가 생겨 최종 시연에서는 곱셈을 사용하는 알파 합성을 선택합니다. 원문에는 세 방식의 출력 예시도 포함됩니다.
Lobsters 반응
- @jtm — 멋집니다. Verilog 소스가 있나요? 글에서 제가 놓쳤을 수도 있습니다. 오래전에 단색 MDA 모니터로 비슷한 작업을 했습니다. UART로 farbfeld 이미지를 보내고 즉석에서 디더링하도록 만들었습니다. 공유해 주셔서 감사합니다. 옛 모니터를 꺼내 다시 만져보고 싶어졌습니다.
- @lukasmwerner — 소스를 공유하고 싶지만 학생들이 실습 과제를 베끼게 될 위험이 있습니다. 정확한 답을 제공하지 않는 선에서 과정을 설명한 것이 글에서 할 수 있는 최대한입니다. 정말 멋진 프로젝트네요. 하드웨어에서 디더링도 해보고 싶어졌습니다.
- @jtm — 실습 과제를 베끼게 될 위험이 있군요. 그렇다면 이해됩니다. 제가 미처 생각하지 못했습니다.
- @edwintorok — HardCaml로 작성한 코드가 있습니다. 파형을 이용해 테스트 케이스를 작성하는 방식이 좋았습니다. 시뮬레이터를 직접 만들 필요 없이 TinyTapeout Demoscene 대회에서 제공한 시뮬레이터를 썼습니다. FPGA 보드와 모니터에서 시험할 시간은 없어서 시뮬레이션으로만 확인했지만, 데모 장면 심사위원이 실행했을 때와 나중에 실제 ASIC에서 실행했을 때 모두 처음부터 작동했습니다.
- @orib — 학부 때 선택 과목으로 Verilog 강의를 들었는데 정말 재미있었습니다. 첫 프로젝트 가운데 하나가 VGA 드라이버였고, 그 뒤로 프로젝트를 이어갔습니다. 마지막 실습은 첫 실습에서 만든 VGA 드라이버로 이미지를 표시하는 Verilog JPEG 디코더였습니다. 다만 몇 주 안에 끝내야 해서 DCT 계수의 산술 부호화 대신 간단한 RLE를 사용했습니다. Verilog를 배우고 컴파일 결과가 되는 LUT 관점에서 생각해 볼 가치가 있습니다. 또 하나의 절차형 언어처럼 다루는 데 그치지 말아야 합니다.
- @wrs — VGA 드라이버는 FPGA를 처음 배울 때 만들기 좋고 성취감도 큽니다. 다만 이제 VGA 입력을 지원하는 디스플레이가 많지 않습니다. 제가 만들 때는 지역 재활용 PC 매장에서 멀티싱크 CRT를 5달러에 샀습니다. 화면을 띄운 뒤 문자 생성기, PS/2 키보드 입력, 버퍼가 있는 UART를 추가해 터미널로 확장했습니다. 커서 추적과 일부 이스케이프 코드 처리도 구현했습니다. 모듈을 계속 붙이면 됩니다. 요즘 FPGA라면 터미널에 연결되는 Z80 시스템을 만들어 CP/M을 실행할 수도 있습니다.
- @elijahpotter — 신호가 작동하는 방식을 보여주는 그래픽이 정말 훌륭합니다. FPGA를 사고 싶어졌습니다.
- @larsw — 래스터 바 효과를 에뮬레이션할 수 있나요?
- @lukasmwerner — 이미 누군가 구현한 것 같습니다.
- @Relax — 아스테릭스 이미지 해상도가 어떻게 되나요? VGA로 표시하기에는 훨씬 높아 보입니다.
- @lukasmwerner — 저도 놀랐습니다. ROM 너비에 따라 제한되므로 255x255픽셀이어야 합니다.
원문: Lobsters / 번역·요약: Trawling