산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Industrial programming >> VHDL

블록 RAM의 사인파로 LED 호흡 효과 구현하기

최근 몇 년간 구입한 많은 기기들이 LED 점멸에서 LED 호흡 효과로 전환했음을 발견했습니다. 대부분 전자 기기엔 내부 상태를 나타내는 상태 LED가 있습니다.

전동 칫솔은 충전 시 LED 깜빡이고, 휴대폰은 다양한 이유로 LED로 주의 환기. 하지만 예전처럼 LED가 깜빡이지 않습니다. 연속적으로 강도가 변하는 아날로그 펄싱 효과에 가깝습니다.

아래 GIF 애니메이션은 로지텍 마우스가 배터리 충전 중임을 이 효과로 나타내는 모습. 빛 강도 패턴이 사람 호흡 주기의 속도·가속도와 비슷해 호흡 LED라 부릅니다. 조명 주기가 사인파 패턴을 따르므로 매우 자연스러워 보입니다.

블록 RAM의 사인파로 LED 호흡 효과 구현하기

이 글은 지난주 펄스 폭 변조(PWM) 블로그 포스트의 후속입니다. 오늘은 이전 튜토리얼에서 만든 PWM 모듈, 톱니 카운터, 리셋 모듈을 사용합니다. PWM 관련 글은 아래 링크 참조.

VHDL로 PWM 컨트롤러 만들기

블록 RAM에 사인파 값 저장

FPGA에서 DSP 프리미티브로 사인파 생성 가능하지만, 더 간단한 방법은 샘플 포인트를 블록 RAM에 저장하는 것. 런타임에 값 계산 대신 합성 시점에 사인 값 시리즈 계산해 읽기 전용 메모리(ROM)에 저장.

아래 최소 예제는 3×3비트 ROM에 전체 사인파 저장하는 법 보여줍니다. 주소 입력과 데이터 출력이 모두 3비트 폭으로 0~7 정수 값 표현. ROM에 8개 사인 값 저장, 데이터 해상도도 0~7.

블록 RAM의 사인파로 LED 호흡 효과 구현하기

삼각함수 사인은 임의 각도 입력 x에 대해 [-1, 1] 범위 숫자 생성. x ≥ 2π일 때 주기 반복. 따라서 0부터 2π 미만까지 균등 간격 사인 값만 저장하면 충분. 2π의 사인 값은 0의 사인과 동일. 위 그림은 0부터 7π/4(2π 직전 마지막 균등 단계)까지 사인 값 저장 보여줌.

디지털 로직은 각도나 사인 값 같은 실수를 무한 정밀도로 표현 불가. 모든 컴퓨터 시스템 마찬가지. 배정밀도 부동소수점 써도 근사치일 뿐. 이진수 작동 원리이고 우리 사인 ROM도 예외 아님.

사용 가능한 데이터 비트 최대한 활용 위해 ROM 내용 계산 시 사인 값에 오프셋과 스케일 적용. 최저 사인 값 -1을 데이터 값 0에, 최고 사인 값 1을 2^(data_bits-1)에 매핑. 공식:

\mathit{data} = \mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)

ROM 주소를 각도 x로 변환 공식:

x = \frac{\mathit{addr} * 2\pi}{2^\mathit{addr\_bits}}

물론 이 방법으로 범용 각도-사인 변환기 만들 순 없음. 그게 목적이면 주소 입력과 데이터 출력을 스케일링하는 추가 로직이나 DSP 프리미티브 필요. 하지만 많은 애플리케이션에서 부호 없는 정수로 표현된 사인파면 충분. 다음 섹션에서 보듯 우리 LED 펄싱 프로젝트에 정확히 필요한 것.

사인 ROM 모듈

본문에서 제시하는 사인 ROM 모듈은 대부분 FPGA 아키텍처에서 블록 RAM 추론. 타겟 FPGA의 메모리 프리미티브에 맞춰 폭과 깊이 제네릭 매칭 권장. 그게 최적 리소스 활용. Lattice 예제 프로젝트 참조해 실제 FPGA 프로젝트에서 사인 ROM 사용하는 법 확인 가능.

아래 양식에 이메일 남겨 VHDL 파일과 ModelSim / iCEcube2 프로젝트 다운로드!

엔티티

아래 코드는 사인 ROM 모듈 엔티티. 추론될 블록 RAM의 폭과 깊이를 지정하는 두 제네릭 입력 포함. 상수에 범위 지정자로 의도치 않은 정수 오버플로 방지. 나중에 자세히 설명.

 entity sine_rom is generic ( addr_bits : integer range 1 to 30; data_bits : integer range 1 to 31 ); port ( clk : in std_logic; addr : in unsigned(addr_bits - 1 downto 0); data : out unsigned(data_bits - 1 downto 0) ); end sine_rom; 

포트 선언에 클럭 입력 있지만 리셋 없음(RAM 프리미티브는 리셋 불가). addr 입력이 스케일된 각도(x) 값 할당 위치, data 출력이 스케일된 사인 값 나타날 위치.

타입 선언

VHDL 파일 선언부 최상단에 ROM 저장 객체용 타입과 서브타입 선언. addr_range 서브타입은 RAM 슬롯 수와 같은 정수 범위, rom_type은 모든 사인 값 저장할 2D 배열 기술.

 subtype addr_range is integer range 0 to 2**addr_bits - 1; type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0); 

하지만 저장 시그널 선언 전, ROM 내용 생성할 함수 먼저 정의해야 함. 시그널 선언 위에 선언해 함수로 ROM 저장 시그널 초기값 할당 가능하게.

addr_bits 제네릭을 addr_range 정의 기반으로 쓰는 이유. addr_bits 최대값 30으로 지정한 이유: 더 큰 값이면 2**addr_bits - 1 계산이 오버플로. VHDL 정수는 32비트(비록 VHDL-2019에서 64비트 정수로 변경 예정). 하지만 도구가 VHDL-2019 지원 전까지 VHDL에서 정수 쓸 땐 이 한계 감수해야 함.

사인 값 생성 함수

아래 코드는 사인 값 생성하는 init_rom 함수. rom_type 객체 반환하므로 타입 먼저 선언, 그다음 함수, 마지막으로 ROM 상수 선언 순서 지켜야 함. 이 순서로 서로 의존.

 function init_rom return rom_type is variable rom_v : rom_type; variable angle : real; variable sin_scaled : real; begin for i in addr_range loop angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits); sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0; rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits); end loop; return rom_v; end init_rom; 

함수는 몇 편의 변수 사용: rom_v는 사인 값 채울 로컬 배열 복사본. 서브프로그램 내부에서 for-루프로 주소 범위 순회, 각 ROM 슬롯마다 앞서 설명한 공식으로 사인 값 계산. 마지막에 지금까지 모든 사인 샘플 담은 rom_v 변수 반환.

for-루프 마지막 줄 정수 변환이 data_bits 제네릭을 31비트로 제한한 이유. 더 큰 비트 길이면 오버플로.

 constant rom : rom_type := init_rom; 

init_rom 함수 정의 아래 rom 객체를 상수로 선언. ROM은 쓰지 않는 RAM이므로 완벽. 이제 함수 호출로 초기값 생성(위 코드 참조).

ROM 프로세스

사인 ROM 파일의 유일한 로직은 아래 나열된 꽤 간단한 프로세스. 단일 읽기 포트 가진 블록 RAM 기술.

 ROM_PROC : process(clk) begin if rising_edge(clk) then data <= rom(to_integer(addr)); end if; end process; 

최상위 모듈

이 설계는 앞선 블로그 포스트의 PWM 프로젝트 연속. 리셋 모듈, PWM 생성기 모듈, 프리러닝 클럭 사이클 카운터(톱니 카운터) 모듈 포함. 지난주 기사 참조해 이 모듈들 작동 원리 확인.

아래 다이어그램은 최상위 모듈 내 서브모듈 간 연결 보여줌.

블록 RAM의 사인파로 LED 호흡 효과 구현하기

아래 코드는 최상위 VHDL 파일 선언부. 지난주 PWM 설계에서 duty_cycle 객체는 cnt 카운터 MSB들의 별칭(alias)이었음. 하지만 지금은 사인 ROM 출력이 듀티 사이클 제어하므로 실제 시그널로 교체. 또한 addr이라는 새 별칭 생성해 카운터 MSB들로 구성. 이걸 ROM 주소 입력으로 사용.

 signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; signal duty_cycle : unsigned(pwm_bits - 1 downto 0); -- Use MSBs of counter for sine ROM address input alias addr is cnt(cnt'high downto cnt'length - pwm_bits); 

아래 리스팅에서 최상위 모듈 내 새 사인 ROM 인스턴스화 방법 확인. RAM 폭과 깊이를 PWM 모듈 내부 카운터 길이 따라 설정. ROM의 data 출력이 PWM 모듈의 duty_cycle 입력 제어. duty_cycle 시그널 값이 RAM 슬롯 순차 읽기로 사인파 패턴 묘사.

 SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => pwm_bits, addr_bits => pwm_bits ) port map ( clk => clk, addr => addr, data => duty_cycle ); 

사인파 ROM 시뮬레이션

아래 이미지는 ModelSim에서 최상위 모듈 시뮬레이션 파형. 부호 없는 duty_cycle 신호 표현을 아날로그 포맷으로 변경해 사인파 관찰.

블록 RAM의 사인파로 LED 호흡 효과 구현하기

led_5 최상위 출력이 외부 LED 제어하는 PWM 신호 담당. 듀티 사이클 상승/하강 시 출력이 급격히 변하지만, 사인파 최상단에서 led_5는 꾸준히 '1', 파도 바닥에서 잠시 '0' 유지 확인 가능.

집에서 직접 해보시려면 아래 양식에 이메일 주소 입력해 VHDL 파일과 ModelSim, iCEcube2 프로젝트 수신!

FPGA에서 LED 호흡 구현

Lattice iCEcube2 소프트웨어로 iCEstick FPGA 보드에 설계 구현. 위 양식으로 프로젝트 다운로드해 본인 보드로 시도해 보세요(iCEstick 소유 시).

아래 리스팅은 iCEcube2에 동봉된 Synplify Pro 소프트웨어가 보고한 리소스 사용량. 설계가 블록 RAM 프리미티브 1개 사용. 그게 우리 사인 ROM.

 Resource Usage Report for led_breathing Mapping to part: ice40hx1ktq144 Cell usage: GND 4 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use SB_RAM256x16 1 use VCC 4 uses SB_LUT4 65 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) RAM/ROM usage summary Block Rams : 1 of 16 (6%) Total load per clock: led_breathing|clk: 1 @S |Mapping Summary: Total LUTs: 65 (5%) 

iCEcube2에서 라우팅 후 .bin 파일은 Lattice_iCEcube2_proj 프로젝트 디렉터리 내 led_breathing_Implmnt\sbt\outputs\bitmap 폴더에서 찾기.

Lattice Diamond Programmer Standalone 소프트웨어로 FPGA 프로그래밍(iCEstick 사용자 매뉴얼 참조). 그렇게 했고, 아래 GIF 애니메이션이 결과. LED 빛 강도가 사인파 패턴으로 진동. 매우 자연스러워 보이고, 상상력 더하면 LED가 '호흡'하는 듯.

블록 RAM의 사인파로 LED 호흡 효과 구현하기

마무리

사전 계산된 사인 값 블록 RAM에 저장하는 건 꽤 직관적. 하지만 이 방법을 제한된 X, Y 해상도 사인파에만 적합하게 만드는 몇 가지 한계 존재.

먼저 앞서 논의한 정수 값 32비트 한계. 더 영리하게 사인 값 계산하면 극복 가능하겠지만 그게 다가 아님.

ROM 주소 비트 하나 늘릴 때마다 RAM 사용량 2배. X축 고정밀도 필요하면 큰 FPGA에서도 RAM 부족할 수 있음.

Y축 사인 값 비트 수가 네이티브 RAM 깊이를 초과하면 합성 도구가 추가 RAM이나 LUT로 ROM 구현. Y 정밀도 높일수록 리소스 예산 더 소모.

이론적으로 사인파 1사분면만 저장하면 됨. 따라서 유한 상태 머신(FSM)으로 ROM 읽기 제어하면 RAM 사용량 1/4로 줄일 수 있음. X, Y축 네 가지 순열 모두에 대해 사인 1사분면 반전시켜 전체 사인파 구성. 그럼 블록 RAM에 저장된 단일 1사분면에서 전체 사인파 구성 가능.

불행히 네 세그먼트가 매끄럽게 이어지게 하긴 어려움. 사인파 상단과 하단의 조인트에서 두 동일한 샘플이 평평한 부분 생성해 데이터 왜곡. 노이즈 도입은 사인파 정밀도 향상 위해 1사분면만 저장하는 목적 무력화.

VHDL

  1. 첫 AWS CloudFormation 템플릿 구축 – 실용 가이드
  2. 마찰 없는 사용자 경험 만들기: UX 디자인 청사진
  3. VHDL 문자열 리스트 구현: 모범 사례와 예제
  4. Tcl 기반 VHDL 코드락 모듈 테스트벤치 만들기
  5. VHDL TEXTIO 라이브러리로 텍스트 파일에서 FPGA 블록 RAM 초기화하기
  6. VHDL 자가 검증 테스트벤치 구축 실전 가이드
  7. VHDL에서 보호 타입과 액세스 포인터로 동적 링크드 리스트 구현하기
  8. Java 객체 배열 생성하기: 단계별 완벽 가이드
  9. 디지털 제조가 현대 의학의 해부학적 모델링을 혁신하다
  10. OPC UA 클라이언트로 PLC 함수 블록 호출하기: 실용 가이드