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

블록 RAM에 저장된 사인파를 사용하여 호흡 LED 효과를 만드는 방법

나는 지난 몇 년 동안 내가 구입한 많은 가제트가 LED 깜박임에서 주도 호흡으로 바뀌었다는 것을 알게 되었습니다. 대부분의 전자 장치에는 장치 내부에서 일어나는 일을 알려주는 상태 LED가 포함되어 있습니다.

내 전동 칫솔은 충전이 필요할 때 LED가 깜박이고, 내 휴대폰은 다양한 이유로 LED를 사용하여 내 주의를 환기시킵니다. 그러나 LED는 예전처럼 깜박이지 않습니다. 지속적으로 변화하는 강도의 아날로그 펄스 효과와 비슷합니다.

아래 GIF 애니메이션은 이 효과를 사용하여 배터리를 충전 중임을 나타내는 Logitech 마우스를 보여줍니다. 저는 이 효과를 호흡 LED라고 부릅니다. 빛의 강도 패턴이 사람의 호흡 주기와 속도와 가속도가 비슷하기 때문입니다. 조명 주기가 사인파 패턴을 따르기 때문에 매우 자연스럽게 보입니다.

이 기사는 펄스 폭 변조(PWM)에 대한 지난주 블로그 게시물의 연속입니다. 오늘은 이전 튜토리얼에서 생성한 PWM 모듈, 톱니 카운터, 리셋 모듈을 사용할 것입니다. PWM에 대한 기사를 읽으려면 아래 링크를 클릭하십시오!

VHDL에서 PWM 컨트롤러를 만드는 방법

블록 RAM에 사인파 값 저장

FPGA에서 DSP(디지털 신호 처리) 프리미티브를 사용하여 사인파를 생성할 수 있지만 더 간단한 방법은 샘플 포인트를 블록 RAM에 저장하는 것입니다. 런타임 중에 값을 계산하는 대신 합성 중에 일련의 사인 값을 계산하고 이를 저장할 ROM(읽기 전용 메모리)을 만듭니다.

3×3 비트 ROM에 전체 사인파를 저장하는 방법을 보여주는 아래의 최소한의 예를 고려하십시오. 주소 입력과 데이터 출력은 모두 3비트 너비입니다. 즉, 0에서 7 사이의 정수 값을 나타낼 수 있습니다. ROM에 8개의 사인 값을 저장할 수 있으며 데이터의 분해능도 0에서 7입니다. .

삼각 사인 함수는 모든 각도 입력 x에 대해 [-1, 1] 범위의 숫자를 생성합니다. . 또한 x ≥ 2π일 때 주기가 반복됩니다. 따라서 0에서 2π까지의 사인 값만 저장하는 것으로 충분하지만 2π는 포함하지 않습니다. 2π에 대한 사인 값은 0에 대한 사인과 동일합니다. 위의 그림은 이 개념을 보여줍니다. 0에서 \frac{7\pi}{4}까지 사인 값을 저장하고 있습니다. 이는 전체 2π 원 이전의 마지막 균등 간격 단계입니다.

디지털 논리는 각도 또는 사인 값과 같은 실제 값을 무한한 정밀도로 나타낼 수 없습니다. 모든 컴퓨터 시스템의 경우입니다. 배정밀도 부동 소수점 숫자를 사용하는 경우에도 이는 근사치일 뿐입니다. 이것이 이진수가 작동하는 방식이며 사인 ROM도 다르지 않습니다.

사용 가능한 데이터 비트를 최대한 활용하기 위해 ROM 내용을 계산할 때 사인 값에 오프셋과 스케일을 추가합니다. 가능한 가장 낮은 사인 값 -1은 데이터 값 0에 매핑되고 가능한 가장 높은 사인 값 1은 아래 공식과 같이 2^{\mathit{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의 메모리 기본 요소와 일치시키는 것을 고려하십시오. 그러면 최고의 리소스 활용도를 얻을 수 있습니다. 실제 FPGA 프로젝트에 사인 ROM을 사용하는 방법을 잘 모르는 경우 항상 예제 Lattice 프로젝트를 참조할 수 있습니다.

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 기본 요소는 재설정할 수 없기 때문에 재설정이 없습니다. 주소 입력은 스케일된 각도(x ) 값 및 데이터 출력은 스케일된 사인 값이 나타날 위치입니다.

유형 선언

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);

그러나 우리는 아직 저장 신호를 선언하지 않을 것입니다. 먼저, RAM을 ROM으로 바꾸는 데 사용할 수 있는 사인 값을 생성하는 함수를 정의해야 합니다. 함수를 사용하여 ROM 저장 신호에 초기 값을 할당할 수 있도록 신호 선언 위에 선언해야 합니다.

addr_bits를 사용하고 있습니다. addr_range 정의를 위한 기본으로 일반 . 이것이 내가 addr_bits에 대해 최대값을 30으로 지정해야 했던 이유입니다. . 더 큰 값의 경우 2**addr_bits - 1 계산이 넘칠 것입니다. VHDL 정수의 길이는 32비트이지만 64비트 정수를 사용하는 VHDL-2019로 변경될 예정입니다. 그러나 현재로서는 도구가 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이므로 완벽합니다. 이제 함수를 사용할 차례입니다. 우리는 init_rom이라고 부릅니다. 위의 코드와 같이 초기 값을 생성합니다.

ROM 프로세스

사인 ROM 파일의 유일한 논리는 아래 나열된 다소 간단한 프로세스입니다. 단일 읽기 포트가 있는 블록 RAM을 설명합니다.

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

상단 모듈

이 디자인은 이전 블로그 게시물에서 소개한 PWM 프로젝트의 연속입니다. 리셋 모듈, PWM 생성기 모듈 및 자유 실행 클록 주기 카운터(톱니식 카운터) 모듈이 있습니다. 이 모듈이 어떻게 작동하는지 보려면 지난 주 기사를 참조하십시오.

아래 다이어그램은 상위 모듈의 하위 모듈 간의 연결을 보여줍니다.

아래 코드는 상위 VHDL 파일의 선언적 영역을 보여줍니다. 지난 주 PWM 설계에서 duty_cycle 개체는 cnt의 MSB에 대한 별칭이었습니다. 카운터. 그러나 사인 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을 인스턴스화하는 방법을 볼 수 있습니다. PWM 모듈의 내부 카운터 길이를 따르도록 RAM의 너비와 깊이를 설정했습니다. 데이터 ROM의 출력은 duty_cycle을 제어합니다. PWM 모듈에 입력합니다. 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의 표시를 변경했습니다. 사인파를 관찰할 수 있도록 신호를 아날로그 형식으로 변환합니다.

led_5입니다. 외부 LED를 제어하는 ​​PWM 신호를 전달하는 최상위 출력. 듀티 사이클이 상승하거나 하강할 때 출력이 빠르게 변하는 것을 볼 수 있습니다. 그러나 듀티 사이클이 사인파의 상단에 있을 때 led_5 꾸준한 '1'이다. 웨이브가 슬로프 하단에 있을 때 출력은 잠시 '0'을 유지합니다.

집에 있는 컴퓨터에서 사용해 보시겠습니까? VHDL 파일과 ModelSim 및 iCEcube2 프로젝트를 수신하려면 아래 양식에 이메일 주소를 입력하세요!

FPGA에서 LED 호흡 구현

Lattice iCEcube2 소프트웨어를 사용하여 iCEstick FPGA 보드에 디자인을 구현했습니다. iCEstick을 소유하고 있다면 위의 양식을 사용하여 프로젝트를 다운로드하고 보드에서 사용해 보십시오!

아래 목록은 iCEcube2와 함께 제공되는 Synplify Pro 소프트웨어에서 보고한 리소스 사용량을 보여줍니다. 디자인이 하나의 블록 RAM 프리미티브를 사용함을 보여줍니다. 이것이 우리의 사인 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을 찾을 수 있습니다. led_breathing_Implmnt\sbt\outputs\bitmap의 파일 Lattice_iCEcube2_proj 내부의 폴더 프로젝트 디렉토리.

iCEstick 사용 설명서에 표시된 대로 Lattice Diamond Programmer Standalone 소프트웨어를 사용하여 FPGA를 프로그래밍할 수 있습니다. 그것이 내가 한 것이고 아래의 Gif 애니메이션이 그 결과를 보여줍니다. LED의 광도는 사인파 패턴으로 진동합니다. 굉장히 자연스러워 보이고, LED에 상상력을 더하면 '숨쉬는 것'처럼 보입니다.

최종 생각

미리 계산된 사인 값을 저장하기 위해 블록 RAM을 사용하는 것은 매우 간단합니다. 그러나 이 방법을 X 및 Y 분해능이 제한된 사인파에만 적합하게 만드는 몇 가지 제한 사항이 있습니다.

마음에 오는 첫 번째 이유는 앞에서 논의한 정수 값에 대한 32비트 제한입니다. 사인 값을 더 영리하게 계산하면 이 문제를 극복할 수 있다고 확신하지만 그게 다가 아닙니다.

ROM 주소를 확장하는 모든 비트에 대해 RAM 사용량이 두 배가 됩니다. X축에서 높은 정밀도가 필요한 경우 더 큰 FPGA에서도 RAM이 충분하지 않을 수 있습니다.

Y축 사인 값에 사용되는 비트 수가 기본 RAM 깊이를 초과하는 경우 합성 도구는 추가 RAM 또는 LUT를 사용하여 ROM을 구현합니다. Y 정밀도를 높이면 리소스 예산이 더 많이 소모됩니다.

이론적으로 사인파의 1사분면만 저장하면 됩니다. 따라서 FSM(유한 상태 기계)을 사용하여 ROM 판독값을 제어하는 ​​경우 RAM 사용량의 1/4을 줄일 수 있습니다. X 및 Y 축의 4가지 순열 모두에 대해 사인 사분면을 반전해야 합니다. 그런 다음 블록 RAM에 저장된 단일 사분면에서 전체 사인파를 만들 수 있습니다.

불행히도 4개의 세그먼트를 모두 원활하게 결합하기는 어렵습니다. 사인파의 상단과 하단에 있는 조인트에 있는 두 개의 동일한 샘플은 사인파에 평평한 점을 만들어 데이터를 왜곡합니다. 노이즈를 도입하면 사인파의 정밀도를 향상시키기 위해 사분면만 저장하는 목적이 무효화됩니다.


VHDL

  1. AWS를 사용하여 CloudFormation 템플릿을 만드는 방법
  2. 마찰 없는 UX를 만드는 방법
  3. VHDL에서 문자열 목록을 만드는 방법
  4. VHDL 코드 잠금 모듈을 위한 Tcl 기반 테스트벤치를 만드는 방법
  5. TEXTIO를 사용하여 파일에서 RAM을 초기화하는 방법
  6. 자가 점검 테스트벤치를 만드는 방법
  7. VHDL에서 연결 목록을 만드는 방법
  8. Java에서 객체 배열을 만드는 방법
  9. 의료 전문가가 디지털 제조를 사용하여 차세대 해부학 모델을 만드는 방법
  10. 정보 모델을 사용하여 OPC UA 클라이언트에서 기능 블록을 호출하는 방법