거북이처럼 천천히

Verilog RTL 설계(8월 1일 - 1, PWM - 5) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(8월 1일 - 1, PWM - 5)

유로 청년 2024. 8. 2. 15:07

 1. Period = 100usec, Frequency = 10kHz인 Pulse wave를 128단계로 나누어 컨트롤하는 모듈 설계

 

< Source, Pulse wave를 128단계로 나누어 컨트롤하는 모듈 >

// PWM Duty ratio 128
module PWM_Duty_Ratio_cntr(
    input clk, reset_p,
    input [6:0] duty,
    output pwm);
    
    // Declare parameter.
    parameter sysclk_freq = 100_000_000;
    parameter duty_step = 128;
    parameter pwm_freq = 10_000;
    
    parameter temp = sysclk_freq / duty_step / pwm_freq;
    parameter half_temp = temp / 2;
    
    // Prescaling for making 10kHz pulse wave.
    integer cnt_sysclk;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_sysclk = 0;
        else begin
            if(cnt_sysclk >= temp-1) cnt_sysclk = 0;
            else cnt_sysclk = cnt_sysclk + 1;
        end
    end
    
    wire clk_div_temp;
    assign clk_div_temp = (cnt_sysclk < half_temp)? 0 : 1;
    
    wire clk_div_temp_nedge;
    edge_detector edge_detector_0(.clk(clk), .reset_p(reset_p), .cp(clk_div_temp), .n_edge(clk_div_temp_nedge));
    
    // prescaling 128
    reg [6:0] cnt_div_128;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_div_128 = 0;
        else if(clk_div_temp_nedge) cnt_div_128 = cnt_div_128 + 1;
    end
    
    assign pwm = (cnt_div_128 < duty)? 1 : 0;
    
endmodule

// Edge Detector
module edge_detector (
    input clk, reset_p,
    input cp,
    output p_edge, n_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            flip_flop_current <= 0;
            flip_flop_old <= 0;
        end
        else begin
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10)? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01)? 1 : 0;
    
endmodule

 

 

 

< Source, Top module >

// PWM Control top module
module PWM_top_module (
    input clk, reset_p,
    output led_pwm);
  
    reg [31:0] cnt_sysclk;
    always @(posedge clk) cnt_sysclk = cnt_sysclk + 1;
  
    PWM_Duty_Ratio_cntr PWM_Module(.clk(clk), .reset_p(reset_p), .duty(cnt_sysclk[29:24]), .pwm(led_pwm));
  
endmodule

 

  • Q) 아래 코드처럼 duty 값을 주었는데, 무엇을 의미하는가?
PWM_Duty_Ratio_cntr PWM_Module(.clk(clk), .reset_p(reset_p), .duty(cnt_sysclk[29:24]), .pwm(led_pwm));
  • A) 아래 그림과 함께 보면 쉽게 이해할 수 있다.

 

  • cnt_sysclk counter는 10ns마다 1씩 증가하는 counter이다. 
  • LSB 부근에 있는 bit 값들은 빠르게 변화하지만, MSB 부근에 있는 bit 값들은 상대적으로 천천히 변화하는 것을 확인할 수 있다.
  • 이를 통해 MSB 부근에 있는 7bit 값들을 묶어 duty값으로 준다면 천천히 변화하겠지만, 반대로 LSB 부근에 있는 7bit 값들을 묶어 준다면 duty 값이 빠르게 변화할 것이다.

  • Q) 구체적으로 얼마나 MSB 쪽으로 갈수록 duty가 천천히 변화하는가?
  • A) LSB에서 MSB로 1bit 씩 이동할 때마다 2배씩 천천히 변화할 것이며, duty 값의 bit 숫자가 7bit에서 1bit씩 늘어날수록 2배씩 천천히 변화한다.

 

 

 

 

 

 2. RGB diode에 PWM을 인가하여 다양한 색상 확인하기.

 

< Source, Pulse wave를 128단계로 나누어 컨트롤하는 모듈 >

// PWM Duty ratio 128 step cntr
module LED_PWM_cntr(
    input clk, reset_p,
    input [6:0] duty,
    output pwm);
    
    // Declare parameter
    parameter sysclk_freq = 100_000_000;
    parameter duty_step = 128;
    parameter pwm_freq = 10_000;
    
    // Prescaling for pwm frequency
    parameter temp = sysclk_freq / duty_step / pwm_freq;
    parameter half_temp = temp / 2;
    
    reg [6:0] cnt_sysclk;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_sysclk = 0;
        else cnt_sysclk = cnt_sysclk + 1;
    end
    
    wire clk_div_temp;
    assign clk_div_temp = (cnt_sysclk < half_temp) ? 0 : 1;
    
    wire clk_div_temp_nedge;
    edge_detector edge_detector_0(.clk(clk), .reset_p(reset_p), .cp(clk_div_temp), .n_edge(clk_div_temp_nedge));
    
    // Prescaling of duty ratio step 128
    reg [6:0] cnt_duty_step;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_duty_step = 0;
        else if(clk_div_temp_nedge) cnt_duty_step = cnt_duty_step + 1;
    end 
    
    assign pwm = (clk_div_temp_nedge < duty)? 1 : 0;
    
endmodule

// edge detector.
module edge_detector (
    input clk, reset_p,
    input cp,
    output p_edge, n_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            flip_flop_current <= 0;
            flip_flop_old <= 0;
        end
        else begin
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10)? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01)? 1 : 0;
        
endmodule

 

 

 

< Source, Top module >

// top module
module PWM_top_module (
    input clk, reset_p,
    output led_red,
    output led_green,
    output led_blue );
    
    reg [31:0] cnt_sysclk;
    always @(posedge clk) cnt_sysclk = cnt_sysclk + 1;
   
   LED_PWM_cntr pwm_red(.clk(clk), .reset_p(reset_p), .duty(cnt_sysclk[28:23]), .pwm(led_red));
   LED_PWM_cntr pwm_green(.clk(clk), .reset_p(reset_p), .duty(cnt_sysclk[27:22]), .pwm(led_green));
   LED_PWM_cntr pwm_blue(.clk(clk), .reset_p(reset_p), .duty(cnt_sysclk[26:21]), .pwm(led_blue));
   
endmodule

 

 

< RGB LED 구동 영상 >

 

 

 

 

 

 

 

 3. 다목적으로 사용 가능한 PWM의 Duty ratio 컨트롤 모듈

  • LED를 연속적으로 켜져 있는 상태로 만들기 위해서는 10kHz 주파수를 갖는 PWM이 필요하지만, Motor에서는 연속적으로 구동하게 만들기 위해서는 100Hz 주파수를 갖는 PWM이 필요하다.
  • 따라서 다양한 주파수를 갖는 PWM을 만드는 동시에 PWM의 duty ratio를 컨트롤 하기 위해서는 다목적으로 사용이 가능한 PWM Duty ratio 컨트롤이 가능한 모듈이 필요하다.
  • 따라서 이번에는 만들고자하는 PWM의 frequency, Duty Step 값을 Parameter 값으로 받아 원하는 주파수를 갖는 PWM과 원하는 Duty ratio의 Step으로 컨트롤 할 수 있게끔 모듈을 설계하도록 하겠다.


< Source, 다목적으로 사용 가능한 PWM의 Duty ratio 컨트롤 모듈 >

// Control module for various duty ratios of various PWMs
module Multi_PWM_Duty_Ratio #(
    // Declare parameter.
    parameter pwm_freq = 10_000, 
    parameter duty_step = 128,
    parameter sysclk_freq = 100_000_000,
    parameter temp = sysclk_freq / pwm_freq / duty_step,
    parameter temp_half = temp / 2
    ) ( input clk, reset_p,
      input [31:0] duty,
      output pwm);
    
    // Prescaling for making pwm frequency.
    integer cnt_sysclk;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_sysclk = 0;
        else begin
                if(cnt_sysclk >= temp - 1) cnt_sysclk = 0;
                else cnt_sysclk = cnt_sysclk + 1;
        end
    end
    
    wire clk_div_temp;
    assign clk_div_temp = (cnt_sysclk < temp_half)? 0 : 1;
    
    wire clk_div_temp_nedge;
    edge_detector edge_detector_0(.clk(clk), .reset_p(reset_p), .cp(clk_div_temp), .n_edge(clk_div_temp_nedge));
    
    
    // Prescaling for PWM Duty ratio step control
    integer cnt_pwm;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_pwm = 0;
        else if(clk_div_temp_nedge) begin
                if(cnt_pwm >= duty_step - 1) cnt_pwm = 0;
                else cnt_pwm = cnt_pwm + 1;
        end 
    end 
    
    assign pwm = (cnt_pwm < duty)? 1 : 0;  
endmodule


// Edge detector
module edge_detector (
    input clk, reset_p,
    input cp, 
    output p_edge, n_edge);
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
                flip_flop_current <= 0;
                flip_flop_old <= 0;
        end 
        else begin
                flip_flop_current <= cp;
                flip_flop_old <= flip_flop_current;
        end
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10)? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01)? 1 : 0;    
 endmodule
  • clcok Pulse 값과 만들고자 하는 PWM의 주파수, Duty ratio의 step값을 Instance 생성할 때, Parameter값으로 주면 다목적으로 사용할 수 있는 PWM의 duty ratio control module 생성할 수 있다.
  • parameter 생성 시, 모든 parameter는 Parameter 위치에 선언 및 위치해야 한다.
  • 범용성을 위해서 duty 변수와 각각의 count 크기를 32bit로 선언하였다.

 

 

< Source, Top Module >

// Top Module
module Multi_PWM_Top_Module (
    input clk, reset_p,
    output led_red,
    output led_green,
    output led_blue);
    
    // Making system clock counter.
    reg [31:0] cnt_sysclk;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_sysclk = 0;
        else cnt_sysclk = cnt_sysclk + 1;
    end
    
    // Declare Instance
    Multi_PWM_Duty_Ratio #(
        .duty_step(93)
    ) pwm_red (
        .clk(clk),
        .reset_p(reset_p),
        .duty(cnt_sysclk[28:23]),
        .pwm(led_red)
    );
    
    Multi_PWM_Duty_Ratio #(
        .duty_step(97)
    ) pwm_blue (
        .clk(clk),
        .reset_p(reset_p),
        .duty(cnt_sysclk[26:21]),
        .pwm(led_blue)
    );
    
    Multi_PWM_Duty_Ratio #(
        .duty_step(101)
    ) pwm_green (
        .clk(clk),
        .reset_p(reset_p),
        .duty(cnt_sysclk[27:22]),
        .pwm(led_green)
    );
    
    
    
endmodule
  • Red, Blue, Green LED는 각각 93단계, 97단계, 101단계로 밝기 단계를 나누었다.
  • Red LED는 5.368초를 주기로 밝았다 꺼졌다를 반복한다.
  • Blue LED는 1.342초를 주기로 밝았다 꺼졌다를 반복한다.
  • Green LED는 2.684초를 주기로 밝았다 꺼졌다를 반복한다.
  • 이처럼 서로 다른 주기로 밝았다 꺼졌다를 하기 때문에 다양한 색상이 LED로 출력되는 것을 확인할 수 있다.

 

 

 

< 구동 영상 >

 

 

 

 

 

4. 어떻게 Duty ratio를 컨트롤함으로서 LED의 밝기를 조절할 수 있는가? 

  • Duty ratio = 50, Duty step = 100, Period = 1sec인 PWM을 LED를 인가하는 상황을 가정하자.

 

 

  • 위와 같은 PWM을 LED에 인가하면 0.5sec동안 0V, 0.5sec동안 3.3V를 인가한다.
  • 0.5sec동안 0V를 인가하는 동안에는 다이오드의 문턱 전압을 넘지 못하기 때문에 다이오드가 켜지지 않지만, 0.5sec 동안 3.3V를 인가하는 동안에는 다이오드의 문턱 전압을 넘어 다이오드가 켜진다.
  • 따라서 실제로는 0V, 3.3V 전압을 0.5sec 동안 주는 것이지만, 다이오드 입장에서는 평균 전압, 1.65V을 인가해주는 것과 동일하게 작용하기 때문에 최대 밝기의 50%만 나오게 된다.
  • 이 처럼 PWM의 duty ratio를 조절 함으로서 다이오드에 전해지는 평균 전압을 조절할 수 있고, 이를 통해 다이오드, LED의 밝기가 조절되어 나타난다.
  • 주의) 실제로는 0V, 3.3V 전압이 작용하기 때문에 이를 잊지 말것.