01signal.com

일반 헤드폰을 digital output pin 에 연결하고 음악 듣기

이 웹 페이지는 Smart Zynq board의 기능을 탐색하는 소규모 프로젝트 그룹 에 속합니다.

소개

본 튜토리얼에서는 일반 헤드폰을 Smart Zynq 보드에 연결하여 음악을 듣는 방법을 설명합니다. 이 프로젝트의 목적은 FPGA에 지속적인 데이터를 전송하기 위해 Xillybus stream을 사용하는 방법을 시연하는 것입니다. PWM modulator를 구현하는 Verilog 코드도 여기에 표시됩니다.

여기에 표시된 코드는 오디오 출력을 구현하는 방법에 대한 예가 아닙니다. 아날로그 출력을 구현하는 일반적인 방법은 Sigma-Delta라고 하는 더 복잡한 기술입니다. 이 기술은 FPGA에서 구현될 수 있지만 이론적 배경은 이해하기가 훨씬 어렵습니다.

이 구현의 또 다른 단점은 sample rate가 정확하지 않다는 것입니다( 48000 Hz대신48828 Hz ). 이 문제는 logic에서 사용되는 clock 의 주파수를 변경하여 쉽게 해결할 수 있습니다. 이 목적을 위해 clocks를 조작하는 주제는 여기에 표시되지 않습니다. 이 예는 정확성보다는 단순성에 중점을 두기 때문입니다.

이번 시연에 필요한 장비는 다음과 같습니다.

Vivado 프로젝트 준비

demo bundle의 zip 파일( boot partition kit)에서 새 Vivado 프로젝트를 만듭니다. 텍스트 편집기에서 verilog/src/xillydemo.v를 엽니다. "PART 2"라고 표시된 코드 부분을 삭제하십시오. 그 대신 다음 코드 조각을 삽입하세요.

/*
    * PART 2
    * ======
    *
    * This code demonstrates a PWM-based audio output
    */

   reg [10:0]   pwm_level, threshold_left, threshold_right;
   reg 		pwm_left, pwm_right;
   reg 		fifo_out_valid;
   wire [31:0] 	fifo_out;
   wire 	fifo_empty;

   wire 	fifo_rd_en = !fifo_out_valid && !fifo_empty;
   wire 	next_word = (pwm_level == 11'h7ff);

   assign J6 = { pwm_right, pwm_left };

   always @(posedge bus_clk)
     begin
	pwm_level <= pwm_level + 1;

	if (next_word && fifo_out_valid)
	  begin
	     // The audio samples are signed integers. Change them to
	     // unsigned by adding 1024.
	     threshold_left <= fifo_out[15:5] + 1024;
	     threshold_right <= fifo_out[31:21] + 1024;
	  end
	else if (next_word) // FIFO's output not valid, keep silent
	  begin
	     threshold_left <= 0;
	     threshold_right <= 0;
	  end

	pwm_left <= (threshold_left > pwm_level);
	pwm_right <= (threshold_right > pwm_level);

	if (fifo_rd_en)
	  fifo_out_valid <= 1;
	else if (next_word)
	  fifo_out_valid <= 0;
     end

   // 32-bit FIFO for audio samples
   fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      // Interface with Xillybus IP core
      .srst(!user_w_write_32_open),
      .din(user_w_write_32_data),
      .wr_en(user_w_write_32_wren),
      .full(user_w_write_32_full),

      // Interface with application logic
      .rd_en(fifo_rd_en),
      .dout(fifo_out),
      .empty(fifo_empty)
      );

   // Send the text "PWM" to reassure that the correct bitstream is used.
   assign user_r_read_32_eof = 0;
   assign user_r_read_32_empty = 0;
   assign user_r_read_32_data = 32'h0a_4d_57_50; // "PWM" + LF

또는 여기 에서 xillydemo.v 파일을 다운로드하세요.

demo bundle용 bitstream 파일을 생성한 것과 동일한 방법으로 업데이트된 프로젝트에서 bitstream 파일을 생성합니다. 또한 동일한 방법으로 bitstream 파일을 TF card 에 복사합니다(기존 xillydemo.bit 파일을 이 프로젝트로 생성된 파일로 덮어쓰기).

헤드폰 연결

오디오 신호를 전달하는 I/O pin 에 50Ω-200Ω resistor를 연결합니다. J6/1 (왼쪽 귀) 또는 J6/2 (오른쪽 귀). J6을 찾으려면 Smart Zynq 보드 뒷면에 "Bank 33 VCCIO Vadj"라고 쓰여 있는 곳을 찾으세요. 이 표시에 가까운 pins 행이 우리가 작업할 pin header 입니다. 따라서 J6/1은 HDMI 커넥터에 가장 가까운 pin 입니다.

resistor 의 반대쪽을 헤드폰 플러그 끝에 연결합니다. 이 목적으로 악어 클립을 사용할 수 있습니다.

헤드폰 플러그의 슬리브 부분을 Smart Zynq의 ground에 연결합니다. pin header의 ground는 J6/35 또는 J6/36에 위치합니다. 그러나 이러한 pins는 power supply pins에 가깝기 때문에 사용하지 않는 것이 좋습니다.

대신 J6/3 부터 J6/34 까지 pin을 사용하는 것도 가능합니다. FPGA는 이를 output pins로 간주하고 '0' logic level에서 유지합니다. 따라서 pins를 ground로 사용하는 것이 가능합니다.

악어 클립을 보드 커넥터 중 하나의 외부 금속 부분에 연결하여 ground 에 연결할 수도 있습니다. Ethernet 커넥터, HDMI 커넥터 또는 USB 커넥터 중 하나.

보드 시작

평소대로 Smart Zynq 의 전원을 켜십시오(또는 reboot를 수행하십시오). 다음 단계는 올바른 bitstream 파일이 FPGA (PL 부분)에 로드되었는지 확인하는 것입니다.

shell prompt에 "head /dev/xillybus_read_32" 명령을 입력하세요. 이 명령은 /dev/xillybus_read_32 에서 첫 번째 행을 읽고 결과를 인쇄합니다.

# head /dev/xillybus_read_32
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM

이 명령의 출력이 없거나 출력이 위에 표시된 것과 다른 경우 잘못된 bitstream이 사용 중인 것입니다.

오디오 파일 재생

오디오 파일을 Xillinux의 file system에 복사합니다. 즉, 오디오 파일은 Linux 시스템 내부의 명령에 사용할 수 있어야 합니다.

이 파일은 WAV 형식이어야 합니다. Uncompressed PCM, 2 channels, s16le (거의 항상 WAV 파일 형식입니다). sampling rate는 48000 Hz여야 하지만 44100 Hz 도 상당히 잘 작동합니다.

이 링크 에서 적절한 오디오 파일을 다운로드할 수 있습니다.

Linux 시스템에 파일을 복사하는 방법에는 여러 가지가 있습니다. 예를 들어, Ethernet network을 사용하여 다음 명령을 사용하여 다른 컴퓨터에서 Xillinux의 home directory 로 파일을 복사할 수 있습니다.

$ scp sample.wav root@192.168.1.10:~/

이는 Microsoft Windows의 command prompt는 물론 Linux shell에서도 작동합니다. IP address (이 예에서는192.168.1.10 )를 보드의 IP address로 변경합니다.

Xillinux 에 파일을 복사하는 다른 방법도 있습니다. 예를 들어 NFS 또는 CIFS를 사용합니다.

파일이 Xillinux의 file system에 복사되면 다음 명령을 사용하여 오디오를 재생하십시오.

# cat sample.wav > /dev/xillybus_write_32

"sample.wav"를 재생하려는 파일 이름으로 바꾸십시오. 여기에 표시된 명령은 파일이 current directory에 있는 경우 작동합니다.

이 명령은 새로운 shell prompt가 나타날 때까지 헤드폰에서 파일을 재생합니다. 한쪽 귀(또는 J6/1 와 J6/2를 모두 헤드폰 플러그의 별도 부분에 연결한 경우 양쪽 귀)로 음악을 들을 수 있어야 합니다.

CTRL-C에서는 이 명령을 중간에 중지하는 것이 가능합니다.

그게 다야. 이 페이지의 나머지 부분에서는 이것이 어떻게 작동하는지 설명합니다.

오디오 데이터가 FPGA에 도달하는 방법

"cat" 명령은 오디오 파일(sample.wav)의 내용을 "xillybus_write_32"라는 device file 에 복사합니다. Linux 시스템에서 이는 hardware driver로 데이터를 보내는 일반적인 방법입니다. 이 예에서 driver는 Xillybus의 IP core와 인터페이스합니다. 결과적으로 데이터는 FPGA의 logic내부의 FIFO 로 전송됩니다.

위에 제시된 Verilog 코드에서 관련 부분을 살펴보겠습니다.

fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      // Interface with Xillybus IP core
      .srst(!user_w_write_32_open),
      .din(user_w_write_32_data),
      .wr_en(user_w_write_32_wren),
      .full(user_w_write_32_full),

      // Interface with application logic
      .rd_en(fifo_rd_en),
      .dout(fifo_out),
      .empty(fifo_empty)
      );

이것은 표준 FIFO의 instantiation 입니다. FIFO 작동 방식에 대한 일반적인 설명은 이 페이지를 참조하십시오.

이 FIFO 에는 FIFO에 데이터 삽입과 관련된 3개의 ports가 있습니다. din, wr_en 및 full. 이 ports 세 개는 모두 Xillybus IP core에 연결됩니다. 즉, 세 개의 신호(user_w_write_32_data, user_w_write_32_wren 및 user_w_write_32_full)가 xillybus라는 module 에 연결됩니다. 이러한 배열을 통해 Xillybus IP core는 FIFO에 데이터를 쓸 수 있습니다.

Xillybus는 이 배열을 사용하여 소프트웨어가 /dev/xillybus_write_32에 쓰는 데이터로 FIFO를 채웁니다. Xillybus는 지속적으로 FIFO에 가능한 많은 데이터를 쓰려고 시도하지만 overflow를 발생시키지 않습니다(즉, FIFO의 full 신호를 따릅니다).

요약하면 다음과 같은 일이 발생합니다.

Simplified data flow diagram for data playback with Xillybus

이러한 모든 작업은 동시에 지속적으로 수행됩니다.

Xillybus에 대한 자세한 내용은 이 일련의 페이지 , 특히 이 페이지를 참조하십시오.

오디오 신호가 생성되는 방법

지금까지의 설명에서는 데이터가 FPGA내부의 application logic 에 어떻게 도달하는지 설명했습니다. 이제 데이터가 어떻게 오디오로 변환되는지 살펴보겠습니다.

먼저 Verilog 코드에서 다음 행에 주의하세요.

assign J6 = { pwm_right, pwm_left };

이에 따르면 오디오 출력 2개는 pwm_right 와 pwm_left로 구성된다. 이 두 registers 에는 다음과 같이 값이 할당됩니다.

always @(posedge bus_clk)
     begin
	pwm_level <= pwm_level + 1;

 [ ... ]
	pwm_left <= (threshold_left > pwm_level);
	pwm_right <= (threshold_right > pwm_level);
 [ ... ]
    end

pwm_level은 단순한 counter입니다. 이 register는 11비트로 구성되어 있어서 0부터 2047까지 세고 다시 0부터 시작합니다.

threshold_left가 pwm_level보다 큰 경우 pwm_left 의 값은 '1' 입니다. 즉, threshold_left는 0부터 2047까지의 모든 숫자를 반복적으로 통과하는 counter 와 비교됩니다. threshold_left 의 값이 높을수록 pwm_left는 '1'의 값을 갖는 시간이 길어집니다. PWM의 원리는 다음과 같습니다. pulse의 길이는 생성하려는 아날로그 신호의 값에 선형적으로 비례합니다.

pwm_right는 threshold_right와 동일한 방식으로 작동합니다.

threshold_left 및 threshold_right 에는 Xillybus IP core를 통해 전송된 WAV 파일의 데이터가 포함되어 있습니다. 이제 이것이 어떻게 일어나는지 자세히 살펴보겠습니다.

먼저 FIFO에서 읽는 것과 관련된 FIFO의 instantiation 부분을 살펴보겠습니다.

// Interface with application logic
      .rd_en(fifo_rd_en),
      .dout(fifo_out),
      .empty(fifo_empty)

fifo_rd_en은 다음과 같이 정의됩니다.

wire 	fifo_rd_en = !fifo_out_valid && !fifo_empty;

따라서 FIFO가 비어 있지 않고 fifo_out_valid가 낮을 때 FIFO의 read enable이 높습니다. 이제 fifo_out_valid의 정의를 살펴보겠습니다.

always @(posedge bus_clk)
     begin
 [ ... ]
	if (fifo_rd_en)
	  fifo_out_valid <= 1;
	else if (next_word)
	  fifo_out_valid <= 0;
     end

fifo_out_valid 의 의미는 FIFO 의 출력이 유효할 때 이 register가 높다는 것입니다. 보다 정확하게는 FIFO의 출력이 아직 소모되지 않은 경우 fifo_out_valid는 High입니다. 이것이 fifo_rd_en이 하이(high)된 후에 이 register가 하이(high) clock cycle 로 변경되는 이유입니다. 이 register는 next_word가 High일 때 Low로 변경됩니다. 아래에서 볼 수 있듯이 PWM을 구현하는 logic은 next_word가 High일 때 FIFO의 출력을 소비합니다.

next_word는 다음과 같이 정의됩니다.

wire 	next_word = (pwm_level == 11'h7ff);

pwm_level은 0부터 2047 사이의 모든 값을 통과하는 counter 라는 점을 기억하세요. 2047을 16진수로 코딩하면 7ff입니다. 따라서 next_word는 pwm_level이 0으로 돌아가기 직전에 하이입니다.

next_word는 얼마나 자주 높습니까? bus_clk 의 주파수는 100 MHz입니다. next_word는 2048 clock cycles의 각 라운드마다 한 번씩 하이입니다. 100 MHz ÷ 2048 ≈ 48828 Hz. 따라서 next_word는 초당 약 48828회 높습니다.

앞서 FIFO의 출력이 소모되면 next_word 의 출력이 높다고 말씀드렸는데요. 이것은 Verilog 코드의 관련 부분입니다.

always @(posedge bus_clk)
     begin
 [ ... ]

	if (next_word && fifo_out_valid)
	  begin
	     // The audio samples are signed integers. Change them to
	     // unsigned by adding 1024.
	     threshold_left <= fifo_out[15:5] + 1024;
	     threshold_right <= fifo_out[31:21] + 1024;
	  end
	else if (next_word) // FIFO's output not valid, keep silent
	  begin
	     threshold_left <= 0;
	     threshold_right <= 0;
	  end
 [ ... ]
    end

우리는 먼저 next_word가 높을 때 threshold_left 와 threshold_right모두에 새로운 값이 할당된다는 것을 관찰할 것입니다. fifo_out_valid가 로우이면 이 두 registers 의 값은 0이 됩니다. 이는 FIFO에 데이터가 전송되지 않아 비어 있는 경우에 발생합니다.

fifo_out_valid가 높으면 FIFO의 dout port 에 audio sample의 값이 포함되어 있다는 의미입니다. 이 값은 두 스테레오 채널의 아날로그 신호를 나타냅니다. 이러한 각 sample 에는 16-bit 2's complement 형식으로 제공되는 두 개의 부호 있는 숫자가 포함되어 있습니다.

왼쪽 스테레오 채널에 속하는 audio sample은 fifo_out[15:0]에 제공됩니다. 이는 -32768에서 32767 사이의 부호 있는 숫자입니다. 5개의 하위 비트가 제거되므로 fifo_out[15:5] 의 범위는 -1024에서 1023 사이입니다. 따라서 "fifo_out[15:5] + 1024"라는 표현은 0에서 2047 사이의 부호 없는 숫자입니다. 이 숫자 범위는 다음과 같습니다. pwm_level와 비교하기에 적합합니다.

따라서 fifo_out[15:0]이 -32768과 같을 때 threshold_left 에는 0 값이 할당됩니다. "threshold_left > pwm_level" 조건은 결코 충족되지 않으므로 pwm_left는 항상 낮은 상태로 유지됩니다. 반면 fifo_out[15:0]이 32767이면 threshold_left의 값은 2047입니다. 결과적으로 pwm_left는 거의 항상 하이 상태입니다. 이는 fifo_out[15:0]이 각 pulse에서 pwm_left가 얼마나 오랫동안 높은지 제어하는 방법입니다. fifo_out[31:16]은 같은 방식으로 pwm_right를 제어합니다.

전체 메커니즘을 요약하면 다음과 같습니다. next_word는 2047 clock cycles마다 한 번씩 높습니다. next_word가 높으면 FIFO 의 출력이 조정되어 threshold_left 및 threshold_right에 복사됩니다. 이는 FIFO의 출력을 소모하므로 fifo_out_valid는 로우가 됩니다. 결과적으로 FIFO가 비어 있지 않으면 FIFO에서 새로운 audio sample을 읽기 위해 fifo_rd_en이 High가 됩니다.

Xillybus IP core는 이 FIFO를 sample.wav의 콘텐츠로 채운다는 점을 기억하세요. 그래서 sample.wav 의 내용에서 threshold_left , threshold_right로 audio samples 의 데이터 흐름이 있습니다. 위에서 언급한 것처럼 next_word는 초당 48828회 정도로 높은 편입니다. 이것이 이 메커니즘의 sample rate가 다.

threshold_left는 pwm_left가 높은 시간 비율을 제어합니다. threshold_right 와 pwm_right도 마찬가지입니다. 그리고 마지막으로 pwm_right 와 pwm_left가 J6라는 이름의 output port 에 연결되어 있어서 pin header에서 보이는 신호들입니다.

next_word가 높으면 두 가지 일이 발생합니다. audio sample이 소모되고 pwm_level이 0부터 계산되기 시작합니다. 따라서 각 audio sample에 대해 하나의 pulse가 생성됩니다.

"PWM" 인쇄하기

이전에는 FPGA 에 올바른 bitstream이 포함되어 있는지 확인하기 위해 "head /dev/xillybus_read_32" 명령을 사용하도록 권장했습니다. 예상했던 결과는 "PWM"가 여러번 출력되는 것이었습니다. 이는 Verilog 코드의 다음 부분에 의해 구현됩니다.

// Send the text "PWM" to reassure that the correct bitstream is used.
   assign user_r_read_32_eof = 0;
   assign user_r_read_32_empty = 0;
   assign user_r_read_32_data = 32'h0a_4d_57_50; // "PWM" + LF

변경 전의 모습으로 xillydemo.v를 살펴보면 user_r_read_32_rden, user_r_read_32_data , user_r_read_32_empty가 FIFO에 연결되어 있는 것을 확인할 수 있습니다. Xillybus IP core는 이러한 신호를 사용하여 FIFO 에서 데이터를 읽고 이 데이터를 /dev/xillybus_read_32에 표시되는 데이터 스트림으로 사용할 수 있도록 합니다.

xillydemo.v가 변경되기 전에 이러한 신호는 Xillybus IP core가 쓰는 것과 동일한 FIFO 에 연결되었습니다. 결과는 loopback였습니다. 소프트웨어에 의해 /dev/xillybus_write_32 에 기록된 데이터는 Xillybus IP core에 의해 FIFO 에 처음 삽입되었습니다. 그런 다음 Xillybus IP core는 FIFO 에서 데이터를 읽고 이를 /dev/xillybus_read_32에 표시합니다. 이 loopback 의 목적은 Xillybus 의 작동 방식을 배우기 위한 출발점이 되는 것입니다.

xillydemo.v를 변경한 후 이러한 신호는 FIFO에서 연결이 끊어집니다. 대신 user_r_read_32_data는 항상 0x0a4d5750 와 같고 user_r_read_32_empty는 항상 0입니다. 게다가 user_r_read_32_rden은 logic에 의해 무시됩니다. 이는 결코 비어 있지 않은 가상의 FIFO를 생성합니다. 이 가상 FIFO 의 출력은 항상 동일한 값을 갖습니다. 0x0a4d5750. Xillybus IP core는 항상 이 상수 값으로 채워져 있는 FIFO가 있는 것처럼 동작합니다. 따라서 /dev/xillybus_read_32에서 읽을 때 0x0a4d5750 라는 단어가 반복적으로 도착합니다. 이 단어가 인쇄되면 4바이트로 해석됩니다. 0x50, 0x57, 0x4d 및 0x0a. 즉, P, W, M 및 line feed 문자( Linux에서 행의 끝을 표시하는 데 사용됨)입니다.

Verilog 코드와 실제 pins의 관계

위의 Verilog 코드는 PWM 신호를 J6에 연결하는데, 이것이 pin header에 어떻게 도달합니까? 그 답은 xillydemo.xdc에서 찾을 수 있습니다. 이 파일은 bitstream을 생성하는 Vivado 프로젝트("vivado-essentials" 디렉터리에 있음)의 일부입니다.

xillydemo.xdc 에는 FPGA가 전자부품으로 제대로 작동하기 위해 필요한 다양한 정보가 담겨 있습니다. 이 파일에는 다음과 같은 행이 포함되어 있습니다.

[ ... ]

## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22  [get_ports {J6[0]}];   #J6/1  = IO_B33_LN2
set_property PACKAGE_PIN T22  [get_ports {J6[1]}];   #J6/2  = IO_B33_LP2
set_property PACKAGE_PIN W22  [get_ports {J6[2]}];   #J6/3  = IO_B33_LN3
set_property PACKAGE_PIN V22  [get_ports {J6[3]}];   #J6/4  = IO_B33_LP3
set_property PACKAGE_PIN Y21  [get_ports {J6[4]}];   #J6/5  = IO_B33_LN9
set_property PACKAGE_PIN Y20  [get_ports {J6[5]}];   #J6/6  = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}];   #J6/7  = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}];   #J6/8  = IO_B33_LP7

[ ... ]

첫 번째 행에는 J6[0] 신호가 U22에 연결되어야 한다고 나와 있습니다. FPGA의 물리적 패키지에 대한 위치입니다. Smart Zynq의 schematics에 따르면 이 FPGA pin은 pin header의 첫 번째 pin 에 연결됩니다. 다른 ports 의 위치도 같은 방식으로 정의됩니다.

위에서 J6/3 부터 J6/34 까지의 범위에 있는 모든 pin은 ground로 사용될 수 있다고 언급했는데, 이러한 output pins는 '0'값을 갖기 때문입니다. xillydemo.v시작 부분에 있는 다음 행에 따르면 J6은 34비트로 구성되어 있기 때문에 이는 사실입니다.

inout [33:0] J6,  //BANK33 VADJ

J6 의 값 할당은 다음과 같습니다.

assign J6 = { pwm_right, pwm_left };

즉, J6[0]은 pwm_left 와 같고 J6[1]은 pwm_right와 같습니다. 나머지는 어떻습니까? Verilog의 구문에 따르면 다른 모든 비트에는 0 값이 할당됩니다.

DC bias

pin header는 FPGA의 logic outputs에 연결됩니다. 이들 pins 각각은 logic state가 '1'일 때 3.3V 부근의 전압을 갖습니다. logic state가 '0'일 때 전압은 0V정도입니다.

원래 audio sample 의 값이 0이라면 threshold_left 와 threshold_right 의 값은 1024가 됩니다. 즉, 평균적으로 절반의 시간 동안 pwm_right 와 pwm_left가 하이 상태가 됩니다. 따라서 평균 전압(DC)은 3.3V ÷ 2 = 1.65V입니다. 따라서 WAV 파일의 audio samples가 완벽한 DC balance를 가지고 있더라도 헤드폰은 DC 구성 요소로 1.65V 에 노출됩니다.

따라서 100Ω resistor 의 목적은 사운드 레벨을 줄이는 것뿐만 아니라 DC current를 제한하는 것입니다. 하지만 이 저항이 없더라도 FPGA자체의 한계와 헤드폰의 전기 저항으로 인해 전류는 아마도 무해할 것입니다. resistor는 단지 예방 조치일 뿐입니다.

요약

이 프로젝트에서는 헤드폰에 직접 연결할 수 있는 아날로그 오디오 신호를 생성하기 위해 디지털 output pin을 사용하는 방법을 보여주었습니다. 이 프로젝트의 강조점은 소프트웨어에서 FPGA로 데이터를 전송할 목적으로 Xillybus stream을 사용하는 방법을 제시하는 것이었습니다. PWM 의 간단한 구현도 보여졌습니다.

이 페이지는 영어에서 자동으로 번역됩니다. 불분명한 사항이 있으면 원본 페이지를 참조하십시오.
Copyright © 2021-2024. All rights reserved. (6f913017)