본문 바로가기

VERILOG

Verilog - DSP FIR Lowpass Filter(2MHz + 30MHz sin PHASE)

주로 주파수 응답의 특정 부분을 필터링하는 데 사용되는 디지털 필터입니다. 
코드는 주어진 노이지 신호에 대해 필터링을 수행하고 최종적으로 출력된 신호를 생성합니다. 
필터의 동작은 샘플링된 입력 신호의 각 샘플에 대해 가중치가 적용된 이전 값들의 합을 계산하는 것입니다.

 

디지털 필터 구분(IIR, FIR)

 

FIR 필터(finite impulse response filter)

피드백구조가 아닌 오픈루프방식입니다.

FIR필터는 디지털 필터의 한 종류로 입력신호의 일정한(유한한, finite) 값들만을 가지고 필터링을 수행합니다.

따라서 필터의 특성함수인 임펄스 응답을 구해보면 유한한 길이를 가집니다.

필터의 식의 형태에서 보면 회귀(feedback)성분을 갖지 않습니다.

 

그러므로 동일한 특성을 구현할 때 차수가 IIR필터에 비하여 높아져서 구현비용(부품가격, 실행시간 등)이 많이 들지만 위상변이(즉, 입력과 출력간의 파형의 형태유지)가 중요한 경우에는 반드시 FIR필터를 사용해야 합니다.

설계법으로는 윈도우에 의한 방법, 주파수 표본화 방법, 컴퓨터에 의한 최적 설계법 등이 있습니다.

 

IIR 필터(infinite impulse response filter)

피드백구조를 가지며 구조가 간편합니다.

IIR필터는 디지털 필터의 한 종류로 입력신호의 값과 출력신호의 값이 재귀적으로(recursive, feedback) 적용되어 필터링이 수행됩니다.

 

따라서 구현식의 형태로 반복식이 되며 특성함수인 임펄스 응답은 무한한 길이를 갖습니다.

동일한 특성을 갖는 FIR필터에 비해 차수가 적어져서 경제성이 있으나 위상특성의 측면에서는 비선형성을 가지므로(즉, 각 주파수 성분마다 위상의 차이가 비선형적으로 달라서) 입력 파형과 출력 파형이 유사한 파형을 갖지 않습니다.

 

설계법으로는 bilinear transform에 의한 방법, 임펄스 응답 불변법이 있는데 이러한 방법은 공통적으로 차단 주파수 근방에서 진폭이나 주파수 축의 왜곡이 발생할 가능성이 있으므로 원하는 필터링 대역보다 표본화 주파수를 크게 잡는 것이 좋습니다.

 

디지털에서 필터를 다룰때는

시간영역(우리가 일반적으로 느끼는 시간축에 따른 신호크기성분)에서

주파수영역(주파수축에 따른 신호크기)으로 바꿔서 필요한 주파수부분만 추출하여 통과처리하는 방식으로 선택을 합니다.

위의 사진에서 왼쪽이 시간영역이고 오른쪽이 주파수 영역입니다.

 

저희는 이 FIR필터를 Verilog 에 적용하여 시뮬레이션을 해보겠습니다.

 

우선 Design Sources 파일을 만들어줍니다. 

파일 이름을 fir.sv 로 하였습니다.

해당 코드입니다.

 

코드를 다 쓴 후 저장을 하고 오류가 없다면 

Design Sources 에 Add Sources 눌러줍니다.

방금 코드를 완성한 fir.sv 를 추가해줍니다.

 

이렇게 하지 않으면 signed << 이 데이터 형식을 쓸수가 없습니다.

이 형식을 쓰는 이유는 signed는 양수 및 음수 값을 나타낼 수 있습니다.

ex -> reg signed [15:0] coeff [0:8] = {16'h04F6 

 

 

시뮬레이션을 돌리기 위한 테스트벤치 파일을 만들어줍니다.

 

같은 방법으로 fir_tb.v 를 만들어줍니다.

 9-tap FIR lowpass filter의 테스트벤치
 
 Cordic을 사용하여 2MHz 및 30MHz에서 샘플링된 두 사인 파형으로 구성된 노이지 신호를 합성합니다.
 노이지 신호는 100Mhz에서 다시 샘플링되어 약 10MHz의 컷오프 주파수를 가진 FIR lowpass 필터에 공급됩니다.

 

 

이전과 마찬가지로 Simulation Sources에 추가하여 줍니다.

 

오류가 없는지 확인한 후 IP Catalog를 눌러줍니다.

 

주로 삼각함수(사인, 코사인 등) 계산이나 회전 변환을 수행하는 데 사용되는 IP인 CORDIC IP를 생성하여 줍니다.

 

CORDIC IP 는 CORDIC 알고리즘을 구현한 하드웨어 블록이며 FPGA 또는 ASIC 디자인에서 사용할 수 있습니다.

CORDIC IP를 사용하여 2MHz와 30MHz에서 샘플링된 두 사인 파형을 합성하는 데 활용되었습니다.

 

VIVADO 상의 왼쪽 PROJECT MANAGER 란에 있는 IP Catalog 를 클릭하여 CORDIC을 검색합니다.

 

 

CORDIC 검색 후 나온 CORDIC을 더블클릭하여 Customize IP에서 Functional Selection 클릭하여 Sin and Cos으로 바꿔줍니다.

OK누른 후

 

Generate 눌러줍니다.

 

이제 모든 준비는 다 끝났고 왼쪽 Run Simulation 을 클릭해서 시뮬레이션을 동작합니다.

 

초기 상태 화면입니다.

혹시 sin_2MHz, sin_30MHz, noisy_signal 값이 안나오시는 분은 테스트벤치에서 빠진 코드가 있는 상태라 확인해주시면 됩니다.

 

시뮬레이션 상에 sin_2MHz[15:0] sin_30MHz[15:0] noisy,signal[15:0] filtered_signal[15:0] 을 클릭하여 우측 마우스를 눌러서 Radix - Real Settings 를 눌러줍니다

 

포인트를 잡아주기 위하여 Fixed point를 누르고

Signed - Binary point 14를 하고 Apply 적용 후 OK를 눌러줍니다.

 

같은 방법으로 우측 마우스를 눌러서 Waveform style - analog 를 눌러줍니다

 

상단의 재생버튼을 눌러줍니다. 

sin_2MHz 파형과

 

sin_30MHz 파형

 

noisy_signal 잡음

 

filtered_signal 필터 된 신호

 

를 확인할 수 있습니다.

 

module fir(
    input clk,                                  // 100MHz 샘플링 클럭
    input signed [15:0] noisy_signal,           // 필터링할 노이지 신호, 1.1.14
    output signed [15:0] filtered_signal        // 필터링된 출력 신호, 1.1.14
    );

    integer i, j;
// 9-tap FIR 필터에 대한 계수, 1.1.14
// 100MHz 샘플링 속도에서 약 10MHz의 컷오프 주파수
reg signed [15:0] coeff [0:8] = {16'h04F6,
                                 16'h0AE4,
                                 16'h1089,
                                 16'h1496,
                                 16'h160F,
                                 16'h1496,
                                 16'h1089,
                                 16'h0AE4,
                                 16'h04F6};
                                 
reg signed [15:0] delayed_signal [0:8];
reg signed [31:0] prod [0:8];                   // 1.3.28                  
reg signed [32:0] sum_0 [0:4];                  // 1.4.28
reg signed [33:0] sum_1 [0:2];                  // 1.5.28
reg signed [34:0] sum_2 [0:1];                  // 1.6.28
reg signed [35:0] sum_3;                        // 1.7.28

// 노이지 신호를 9개의 딜레이된 레지스터에 넣어 준비
always@(posedge clk)
begin
    delayed_signal[0] <= noisy_signal;
    for(i=1; i<=8; i=i+1) begin
        delayed_signal[i] <= delayed_signal[i-1];
    end
end

// 파이프라인화된 곱셈과 누적
always@(posedge clk)
begin
    for(j=0; j<=8; j=j+1) begin
        prod[j] <= delayed_signal[j] * coeff[j];
    end
end
    
always@(posedge clk)
begin
    sum_0[0] <= prod[0] + prod[1];
    sum_0[1] <= prod[2] + prod[3];
    sum_0[2] <= prod[4] + prod[5];
    sum_0[3] <= prod[6] + prod[7];
    sum_0[4] <= prod[8]; 
end

always@(posedge clk)
begin
    sum_1[0] <= sum_0[0] + sum_0[1];
    sum_1[1] <= sum_0[2] + sum_0[3];
    sum_1[2] <= sum_0[4];
end
    
always@(posedge clk)
begin
    sum_2[0] <= sum_1[0] + sum_1[1];
    sum_2[1] <= sum_1[2];    
end

always@(posedge clk)
begin
    sum_3 <= sum_2[0] + sum_2[1];
end

// 필터링된 출력 신호, 1.1.14
assign filtered_signal = $signed(sum_3[35:14]);

endmodule

 

 

 

 

`timescale 1ns/10ps

module fir_tb();

localparam CORDIC_CLK_PERIOD = 2;               // 500MHz CORDIC 샘플링 클럭을 생성하기 위한 값
localparam FIR_CLK_PERIOD = 10;                 // 100MHz FIR lowpass 필터 샘플링 클럭을 생성하기 위한 값
localparam signed [15:0] PI_POS = 16'h6488;     // 고정 소수점 1.2.13 형식의 +pi
localparam signed [15:0] PI_NEG = 16'h9B78;     // 고정 소수점 1.2.13 형식의 -pi
localparam PHASE_INC_2MHZ = 200;                // 2MHz 사인 파형 합성을 위한 위상 증가량
localparam PHASE_INC_30MHZ = 3000;              // 30MHz 사인 파형 합성을 위한 위상 증가량

reg cordic_clk = 1'b0;
reg fir_clk = 1'b0;
reg phase_tvalid = 1'b0;
reg signed [15:0] phase_2MHz = 0;
reg signed [15:0] phase_30MHz = 0;
wire sincos_2MHz_tvalid;
wire signed [15:0] sin_2MHz, cos_2MHz;
wire sincos_30MHz_tvalid;
wire signed [15:0] sin_30MHz, cos_30MHz;

reg signed [15:0] noisy_signal = 0;
wire signed [15:0] filtered_signal;

// 2MHz 사인 파형을 생성
cordic_0 cordic_inst_0(
    .aclk                   (cordic_clk),
    .s_axis_phase_tvalid    (phase_tvalid),
    .s_axis_phase_tdata     (phase_2MHz),
    .m_axis_dout_tvalid     (sincos_2MHz_tvalid),
    .m_axis_dout_tdata      ({sin_2MHz, cos_2MHz})
);

// 30MHz 사인 파형을 생성
cordic_0 cordic_inst_1(
    .aclk                   (cordic_clk),
    .s_axis_phase_tvalid    (phase_tvalid),
    .s_axis_phase_tdata     (phase_30MHz),
    .m_axis_dout_tvalid     (sincos_30MHz_tvalid),
    .m_axis_dout_tdata      ({sin_30MHz, cos_30MHz})
);

// 위상을 변경하여 2MHz와 30MHz를 합성
always@(posedge cordic_clk)
begin
    phase_tvalid <= 1'b1;
    
    // 2MHz 사인 파형을 합성하기 위한 위상 변경
    if(phase_2MHz + PHASE_INC_2MHZ < PI_POS) begin
        phase_2MHz <= phase_2MHz + PHASE_INC_2MHZ;
    end
    else begin
        phase_2MHz <= PI_NEG + (phase_2MHz + PHASE_INC_2MHZ - PI_POS);
    end
    
    // 30MHz 사인 파형을 합성하기 위한 위상 변경
    if(phase_30MHz + PHASE_INC_30MHZ <= PI_POS) begin
        phase_30MHz <= phase_30MHz + PHASE_INC_30MHZ;
    end
    else begin
        phase_30MHz <= PI_NEG + (phase_30MHz + PHASE_INC_30MHZ - PI_POS);
    end
end

// 500MHz Cordic 클럭 생성
always begin
    cordic_clk = #(CORDIC_CLK_PERIOD/2) ~cordic_clk;
end

// 100MHz FIR 클럭 생성
always begin
    fir_clk = #(FIR_CLK_PERIOD/2) ~fir_clk;
end

// 노이지 신호 = 2MHz 사인 + 30MHz 사인
// 노이지 신호는 100MHz FIR 샘플링 속도에서 다시 샘플링됨
always @(posedge fir_clk)
begin
    noisy_signal <= (sin_2MHz + sin_30MHz) / 2;
end

// FIR lowpass 필터에 노이지 신호 공급
fir fir_inst(
    .clk            (fir_clk),
    .noisy_signal   (noisy_signal),
    .filtered_signal(filtered_signal)
);

endmodule