Chapter 5 Code

To comply the Chapter 5 Verilog codes within the book titled Fundamental Digital Circuits and FPGA

Section 5.1: LED chaser

State Diagram

Verilog Code

Code 5.1: Complete Verilog code for 8-bit LED chaser with 50ms time interval
/*---------------------------------------------------------------------------------------------------    
*- File name:        LEDchaser.v
*- Top Module name:  LEDchaser
*- Submodules:       None
*- Description:      Implementation of an LED chaser with 8 states
*-
*- Example of Usage:
    This code is designed to cycle through LEDs in a chaser pattern. It can be implemented on 
    any FPGA development board with an 8-bit LED array. To observe the LED chaser effect:
        - Connect the LEDs output to the 8 on-board LEDs.
        - Provide the system clock to the clk input.

- Reliability:       
                     This code is intended for educational and demonstration purposes and has not been
                     validated for use in production systems. Users are advised to thoroughly test the 
                     module in their specific application context.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module LEDchaser (
    input clk,
    output reg [7:0] LEDs
);
/**************** Defining the 8 states with binary numbers *******************/
parameter   S0 = 3'b000,   
            S1 = 3'b001,   
            S2 = 3'b010,   
            S3 = 3'b011,   
            S4 = 3'b100,   
            S5 = 3'b101,   
            S6 = 3'b110,   
            S7 = 3'b111; 
/****************  Describing the LED actions in each state  *******************/
reg [2:0] state;                   
always @ (posedge clk) begin     
    case (state)  
        S0: LEDs = 8'b11111110;  
        S1: LEDs = 8'b11111101;  
        S2: LEDs = 8'b11111011;  
        S3: LEDs = 8'b11110111;  
        S4: LEDs = 8'b11101111;  
        S5: LEDs = 8'b11011111;  
        S6: LEDs = 8'b10111111;  
        S7: LEDs = 8'b01111111;  
        default: LEDs = 8'b11111111; // Default case to turn off all LEDs if state is unknown
    endcase
end
/****************  Enable jumping among different states  **********************/
reg [23:0] cnt; 
parameter CNT_NUM = 600000;       
always @ (posedge clk) begin
    if (cnt == CNT_NUM-1)  
        cnt <= 20'b0; 
    else
        cnt <= cnt + 1'b1;           
end
always @ (posedge clk) begin
    if (cnt == CNT_NUM-1)  
        state <= state + 1'b1; 
    if (state > S7)         // Reset state to S0 after reaching S7
        state <= S0;
end
endmodule

Implementation

Section 5.2: Traffic Light

This is a simple version of traffic light system that can be fully implemented by the STEPFPGA board alone. A more sophisticated and delicate traffic light system can be found in this Project for Smart Traffic Light.

Timing Diagram

State Machine

Digital Structure

Verilog Code

Code 5.2: Complete code for traffic light3
/*--------------------------------------------------------------------------------------------------- 	
*- File name: 			simple_traffic.v
*- Top Module name: 	simple_traffic
  - Submodules:		divider_integer
*- Description:			Using a state machine to control two RGB LEDs to simulate a traffic light
					
*- Example of Usage:
       - You may assign the output directly to the 2 RGB LEDs on the STEPFPGA board

- Reliability:       
                     This code is intended for educational and demonstration purposes and has not been
                     validated for use in production systems. Users are advised to thoroughly test the 
                     module in their specific application context.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module simple_traffic (
	input clk, rst_n,
	output reg [5:0] RGB_out		// connects the TWO RGB lights
);

// The four states
reg             [1:0] state;
parameter       S1 = 2'b00,       
                S2 = 2'b01,   
                S3 = 2'b10,   
                S4 = 2'b11; 

// RGB light, inverted logic; 111 means all OFF; 011 means R is ON, G and B are OFF
parameter       led_s1 = 6'b101011,   // '101' for Green, '011' for RED
                led_s2 = 6'b001011,   // '001' for Yellow, '011' for RED (yellow is not obvious)
                led_s3 = 6'b011101,   // '011' for Red, '101' for Green
                led_s4 = 6'b011001;   // '011' for Red, '001' for Yellow

// RGB light behaviors in each state
always @ (*)   begin
    case  (state)  
        S1:  RGB_out = led_s1; 
        S2:  RGB_out = led_s2; 
        S3:  RGB_out = led_s3; 
        S4:  RGB_out = led_s4; 
        default:  RGB_out = led_s1; 
    endcase
end

/**************************************   Integer Clock Divider  **********************************
	>>	Instantiate the clock divider module, and divide the 12MHz clock frequency by
	12000000 time thus generated a 1Hz clock signal
  *************************************************************************************************/	
wire clk1hz;
divider_integer # (.WIDTH (24),.N (12_000_000)) u1  (    
    .clk        (clk),      
    .clkout     (clk1hz)     
);

/***********************************   Traffic Control State Machine  ****************************/
// Implementing the state machine; use the 1Hz clock signal
reg	[4:0] time_cnt;		// Reserve 5 bit register space for timer counter
always @ (posedge clk1hz or negedge rst_n) begin
    if(!rst_n) begin
        state <= S1; 
        time_cnt <= 0;
    end
    else begin
        case  (state)  
            S1: if  (time_cnt < 4'd15) begin	// 15s
                    state <= S1; 
                    time_cnt <= time_cnt + 1; 
                end
                else begin
                    state <= S2; 
                    time_cnt <= 0; 
                end
            S2: if  (time_cnt < 4'd3) begin		// 3s
                    state <= S2; 
                    time_cnt <= time_cnt + 1; 
                end
                else begin
                    state <= S3; 
                    time_cnt <= 0; 
                end
            S3: if  (time_cnt < 4'd7) begin		// 7s
                    state <= S3; 
                    time_cnt <= time_cnt + 1; 
                end
                else begin
                    state <= S4; 
                    time_cnt <= 0; 
                end 
            S4: if  (time_cnt < 4'd3) begin		// 3s
                    state <= S4; 
                    time_cnt <= time_cnt + 1; 
                end
                else begin
                    state <= S1; 
                    time_cnt <= 0; 
                end
            default: begin
                    state <= S1; 
                    time_cnt <= 0;
            end
        endcase 
    end
end 
endmodule



/**************************************   Integer Clock Divider  **********************************
	>>	Frequency divider code; seen in Section 3.7 of the book
  *************************************************************************************************/	
module divider_integer # (           
    parameter   WIDTH = 24,          
    parameter   N     = 12000000     
)  
(
    input clk,
    output reg clkout 
);
reg [WIDTH-1:0] cnt; 
always @ (posedge clk) begin
    if(cnt>=(N-1))
        cnt <= 1'b0;
    else
        cnt <= cnt + 1'b1;
    clkout <= (cnt<N/2)?1'b1:1'b0;
end
endmodule

Implementation

Section 5.3: Switch Debouncing

Digital Structure

Verilog Code

Code 5.2: Implementation of debounce module
/*--------------------------------------------------------------------------------------------------- 	
- File name:         debounce.v
- Top Module name:   debounce
- Submodules:        dff, divider_integer
- Description:       This module debounces a mechanical switch input to produce a stable digital output.
                     It uses a clock divider to slow down the main clock and then a double flip-flop
                     setup to filter out any bouncing effects caused by the mechanical switch.
- 
- Example of Usage:  
                     To use this debounce module, connect the 'key' input to a mechanical switch and the
                     'key_deb' output to a digital circuit input that requires a debounced signal. The
                     module uses a slow clock generated by the 'divider_integer' submodule to sample the
                     switch state at a lower rate to mitigate bouncing effects.

- Reliability:       
                     This code is intended for educational and demonstration purposes and has not been
                     validated for use in production systems. Users are advised to thoroughly test the 
                     module in their specific application context.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module debounce (
    input clk, key,
    output key_deb
);

wire slow_clk;
wire Q1,Q2,Q2_bar;

divider_integer #(.WIDTH(17),.N(240000)) U1 ( 
    .clk(clk),                              
    .clkout(slow_clk)           
);
dff U2 (                        
    .clk(slow_clk),
    .D(key),
    .Q(Q1) 
);
dff U3 (                        
    .clk(slow_clk),
    .D(Q1),
    .Q(Q2) 
);

assign Q2_bar = ~Q2;
assign key_deb = Q1 & Q2_bar;   
endmodule

/********** Submodule 1 **********/
module dff(input clk, D, output reg Q);
    always @ (posedge clk) begin
        Q <= D;
    end
endmodule


/********** Submodule 2 **********/
module divider_integer # (           
    parameter   N     = 12000000,	// the divisor
    parameter   WIDTH = 24 			// the minimum bit-width to hold this divisor
)  
(
    input clk,
    output reg clkout 
);
reg [WIDTH-1:0] cnt; 
always @ (posedge clk) begin
    if(cnt>=(N-1))
        cnt <= 1'b0;
    else
        cnt <= cnt + 1'b1;
    clkout <= (cnt<N/2)?1'b1:1'b0;
end
endmodule

Implementation

Section 5.4: Ultrasonic Distance Sensor

Timing Diagram

State Machine

Digtial Structure

Verilog Code

Code 5.4: Complete code for ultrasonic distance measurement module
/*--------------------------------------------------------------------------------------------------- 
*- File name:            SR04_display.v
*- Top Module name:      SR04_display
*- Submodules:           hc_sr04, bin_to_bcd, segment7
*- Description:          Main display module that interfaces with the HC-SR04 sensor module, 
                         converts the binary distance to BCD, and drives two 7-segment displays.
*-
*- Example of Usage:
    - The `SR04_display` module can be instantiated within a top-level design file.
      Connect the `clk`, `rst_n`, and `echo` inputs to the respective signals.
      Connect the `trig`, `segment_led_1`, and `segment_led_2` to the HC-SR04 sensor
      and 7-segment display inputs respectively.
*-
*- Reliability:       
                     This code is provided 'as is', primarily for educational purposes and is not guaranteed 
                     for any industrial or commercial applications. Users must validate the functionality 
                     before use in any critical systems.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module SR04_display(
    input clk, rst_n ,              
    input echo,                     // reads the ECHO signal from HC_SR04
    output trig,                    // send trigger pulse to HC_SR04 module
    output [8:0] segment_led_1,     // 7-Segment display 1
    output [8:0] segment_led_2      // 7-Segment display 2
);

hc_sr04 ultrsonic (clk, rst_n, echo, trig, distance);
wire    [15:0] distance;            // 16-bit binary output from SR-04 sensor

bin_to_bcd bin_to_bcd_U(rst_n, distance, bcd_distance);
wire    [19:0] bcd_distance;        // 19-bit BCD output from BCD module    

wire    [3:0] bcd_digit1;       
wire    [3:0] bcd_digit2;
assign  bcd_digit1 = bcd_distance[7:4]; // convert smaller 4 bits BCD to binary 
assign  bcd_digit2 = bcd_distance[3:0]; // convert higher 4 bits BCD to binary

segment7 seg_x1(                        // display the first digit on Segment1
        .seg_data(bcd_digit1),
        .segment_led (segment_led_1)
);
segment7 seg_x10(                       // display the second digital on Segment2
        .seg_data(bcd_digit2),
        .segment_led (segment_led_2)
);
endmodule

/*******************************************************************************************************
* Module Name: hc_sr04
* Description:
*   This module interfaces with the HC_SR04 ultrasonic sensor to measure distance. The primary 
*   operations are:
*     1. Generating a 10us pulse for triggering the HC_SR04 sensor.
*     2. Detecting the rising and falling edges of the Echo signal from the sensor.
*     3. Generating a 17kHz pulse, where each pulse corresponds to 1cm of the real distance 
*        measured by the HC_SR04 sensor.
*     4. Counting the number of 17kHz pulses between the rising and falling edges of the Echo signal 
*        to determine the distance measurement in centimeters.

- Reliability:       
                     This code is provided 'as is', primarily for educational purposes and is not guaranteed 
                     for any industrial or commercial applications. Users must validate the functionality 
                     before use in any critical systems.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
********************************************************************************************************/
module hc_sr04 (
    input clk, rst_n,               // STPFFPGA has on board frequency of 12MHz  
    input echo,                     // Module input, connects to HC_SR04 -> echo
    output trig,                    // Module output, connects to HC_SR04 -> trig     
    output reg [15:0] distance      
);  
/****************************************   Generate a 10us pulse  ************************************
	>>	This piece of code generates a 10us pulse to enable trigger of the HC_SR04 sensor 			
  ******************************************************************************************************/	
	reg [25:0] cnt_10us;		// Counter for generating 10us pulse
	always @(posedge clk or negedge rst_n)begin
		if(!rst_n) begin
			cnt_10us <= 0;
		end
		else if(cnt_10us == 11_999_999)
			cnt_10us <= 0;
		else
			cnt_10us <= cnt_10us + 1'b1;
	end
	assign trig = (cnt_10us < 120) ? 1:0;

/*********************************   Edge detection of Echo signal  *****************************
	>>	This piece of code detects the rising edge and falling edge of Echo signal
	>>	when 'pose_echo' is 1, rising edge; when 'nege_echo' is 1, falling edge		
  *************************************************************************************************/	
	reg echo_2;
	reg echo_1;
	wire pose_echo;
	wire nege_echo;
	always @(posedge clk17k or negedge rst_n)begin
		if(!rst_n)begin
			echo_1 <= 0;
			echo_2 <= 0;
		end
		else begin
			echo_1 <= echo;
			echo_2 <= echo_1;
		end
	end
	assign pose_echo = echo_1 && (~echo_2);
	assign nege_echo = (~echo_1) && echo_2;

/***********************************   Generate a 17kHz pulse  **********************************
	>>	This piece of code generates a 17kHz pulse signal
	>>	By calculation (see Chapter 5 of the book), each pulse corresponds to 1cm
	of the real distance measured by the HC_SR04 ultrasonic sensor.  			
  *************************************************************************************************/
	reg clk17k;
	reg [15:0] cnt17k;		   // Counter for a 17KHz signal (explained in the book)
	always @(posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			cnt17k <= 0;
		end
		else if(cnt17k == 706)			// divide 12MHz by 706 times will get 17kHz
			cnt17k <= 0;
		else
			cnt17k <= cnt17k + 1;
	end
	always @(posedge clk or negedge rst_n)begin
		if(!rst_n)
			clk17k <= 0;
		else if(cnt17k < 706>>1)
			clk17k <= 0;
		else
			clk17k <= 1;
	end
	
/*************************  Convert counter result to the actual distance  ************************
	>>	Since we have generated a 17kHz pulse, as soon as we detected the rising edge
	of the Echo, then the system starts counting how many pulses for clk17k, until a
	falling edge is received to stop counting. 
	>> 	Each pulse of 'clk17k' represents 1cm in the physical measurement		
*****************************************************************************************************/
	parameter S0 = 2'b00; 	// state S0 is when rising edge of echo is detected begin of distance measurement
	parameter S1 = 2'b01; 	// state S1 is when echo stays HIGH, and we will calculate distance in this state 
	parameter S2 = 2'b10; 	// state S2 is when falling edge of echo is detected, end of distance measurement
	reg [1:0] state;
	reg [15:0] cnt_dist;						// Counter for final measured distance with smallest unit of 'cm'
	always@(posedge clk17k or negedge rst_n)begin
		if(!rst_n)begin
			cnt_dist<= 0; 
			distance <= 0;
			state <= S0;
		end
		else
			begin
               case(state)
				S0:begin						// detected the rising edge of the echo signal
					cnt_dist <= 0;				// start to count for distance
					if (pose_echo) 				
						state <= S1;	
					else	
						state <= S0;	
				end	
				S1:begin						// the echo signal level stays HIGH	
					cnt_dist <= cnt_dist + 1;	// counts for distance, each 'cnt_dist' increments 1cm
					if (nege_echo) 				
						state <= S2;			
					else	
						state <= S1;	
                end	
				S2:begin	
					distance <= cnt_dist; 		// detected the falling edge of the echo signal
					cnt_dist <= 0;				// save the 'cnt_dist' result for the actual distance
					state <= S0;	
				end	
				default:begin					// default
					cnt_dist <= 0;	            
					state <= S0;	
				end	
        endcase	
    end	
end	
endmodule


/*--------------------------------------------------------------------------------------------------- 	
*- File name: 			bin_to_bcd.v
*- Top Module name: 	bin_to_bcd
*- Submodules:			N/A
*- Description:			Conversion of 16-bit binary data to 20-bit Binary Coded Decimal (BCD) representation 
                        using the double dabble algorithm.
                        
*- Example of Usage:
	You can implement this code on all variants of STEPFPGA family boards. 
	If you intend to implement this module on a board and observe its operation:
		- Connect a 16-bit binary input to bin_code.
		- Observe the 20-bit BCD output on bcd_code.
		- Utilize the rst_n signal to reset the conversion process when needed.

*- Additional comments: 	 
   This BCD converter is designed to handle up to a 16-bit binary number and convert it into a 
   20-bit BCD representation. It is especially useful when interfacing with displays that 
   require decimal number representation, such as 7-segment displays.

- Reliability:       
                     This code is provided 'as is', primarily for educational purposes and is not guaranteed 
                     for any industrial or commercial applications. Users must validate the functionality 
                     before use in any critical systems.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module bin_to_bcd (
    input               rst_n,      // Reset signal (active low)
    input       [15:0]  bin_code,
    output  reg [19:0]  bcd_code    
);

// Internal shift register for the algorithm
reg [35:0] shift_reg;

always @(posedge bin_code or negedge rst_n) begin
    if (!rst_n) begin
        shift_reg <= 36'b0;  // Reset the shift register on active low reset signal
    end else begin
        shift_reg <= {16'b0, bin_code};  // Initialize shift register with binary code at the rightmost position
        
        // Using repeat statement for the conversion process
        repeat (16) begin
            if (shift_reg[3:0] > 4) shift_reg[3:0] <= shift_reg[3:0] + 3;
            if (shift_reg[7:4] > 4) shift_reg[7:4] <= shift_reg[7:4] + 3;
            if (shift_reg[11:8] > 4) shift_reg[11:8] <= shift_reg[11:8] + 3;
            if (shift_reg[15:12] > 4) shift_reg[15:12] <= shift_reg[15:12] + 3;
            if (shift_reg[19:16] > 4) shift_reg[19:16] <= shift_reg[19:16] + 3;
            
            shift_reg <= shift_reg << 1;
        end
        
        bcd_code <= shift_reg[19:0];  // Assign the 20-bit BCD result
    end
end

endmodule


/*--------------------------------------------------------------------------------------------------- 	
*- File name: 			segment7.v
*- Top Module name: 	segment7
  - Submodules:		N/A
*- Description:			Implementation of a 7-segment display driver (common cathod)
*- 
*- Example of Usage:
	You can implement this code on all variants of STEPFPGA family boards. 
	If you want to implement this code on board and observe the logic behaviors: 
		- assign seg_data[3]...seg_data[0] to the 4 on-board swiches
		- assign segment_led[8] to SEG, segment_led[7] to DP
		- assign segment_led[6]...segment_led[0] to 'g, f, ... a' accordingly

* - Additional comments: 	 
   If you want to display decimal numbers only, the 4-bit input data must be in BCD converted
   form; will explain in Chapter 5 when implement the Elevator project. 

- Reliability:       
                     This code is provided 'as is', primarily for educational purposes and is not guaranteed 
                     for any industrial or commercial applications. Users must validate the functionality 
                     before use in any critical systems.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module segment7 (
    input  wire [3:0] seg_data,       // The 4 bit input data in binary form	
    output reg  [8:0] segment_led	  //  9 output for the 7-segment LEDs from MSB to LSB: SEG, DP, g, f, e, d, c, b, a
) ; 
always @ (seg_data) begin
    case  (seg_data) 
      4'b0000: segment_led = 9'h3f;   //  0
      4'b0001: segment_led = 9'h06;   //  1
      4'b0010: segment_led = 9'h5b;   //  2
      4'b0011: segment_led = 9'h4f;   //  3
      4'b0100: segment_led = 9'h66;   //  4
      4'b0101: segment_led = 9'h6d;   //  5
      4'b0110: segment_led = 9'h7d;   //  6
      4'b0111: segment_led = 9'h07;   //  7
      4'b1000: segment_led = 9'h7f;   //  8
      4'b1001: segment_led = 9'h6f;   //  9
      4'b1010: segment_led = 9'h77;   //  A
      4'b1011: segment_led = 9'h7C;   //  b
      4'b1100: segment_led = 9'h39;   //  C
      4'b1101: segment_led = 9'h5e;   //  d
      4'b1110: segment_led = 9'h79;   //  E
      4'b1111: segment_led = 9'h71;   //  F
    endcase
 end
endmodule

Implementation

Section 5.5: Matrix Keypad

State Machine

Verilog Code

Code 5.5: Complete code to interface with 4-by-3 matrix keypad with number displaed
/*--------------------------------------------------------------------------------------------------- 	
- File name:         keypad_3by4.v
- Top Module name:   keypad_3by4
- Description:       Implements a 3x4 matrix keypad scanner with debouncing and 7-segment display decoding.
                     The module scans the keypad, debounces the keypresses, identifies the pressed key,
                     and translates it to a corresponding 7-segment display code.
  
- Example of Usage:  
                     This module can be instantiated in a system that requires keypad interfacing with
                     visual feedback via a 7-segment display. Connect the rows and columns to the
                     respective keypad terminals and the segment_led1 output to a 7-segment display driver.

- Reliability:       
                     This code is intended for educational and prototype purposes. For commercial
                     applications, thorough testing and validation are required.

- Copyright:         Copyright (c) 2023 by EIM Technology
- License:           MIT License
--------------------------------------------------------------------------------------------------- */

module keypad_3by4 (
	input					clk,		
	input					rst_n,		
	input			[2:0]	col,		// the 3 output signals for 3 Columns 
	output	reg		[3:0]	row,		// the 4 input signals for 4 Rows 
	output	reg		[3:0]	keyPressed,
	
	 output	reg		[8:0]	segment_led1
);

	localparam			NUM_FOR_200HZ = 60000;	// Used to generate a 200Hz frequency for column scanning
	localparam			ROW0_SCAN = 2'b00;      // the state when scanning first row
	localparam			ROW1_SCAN = 2'b01;      // the state when scanning second row
	localparam			ROW2_SCAN = 2'b10;      // the state when scanning third row
	localparam			ROW3_SCAN = 2'b11;		// the state when scanning forth row
 
	reg		[11:0]	key,key_r;
	reg		[11:0]	key_out;		// debounce all keys

	reg		[15:0]	cnt;
	reg				clk_200hz;
	
	// generate a 200Hz clock
	always@(posedge clk or negedge rst_n) begin
		if(!rst_n) begin		
			cnt <= 16'd0;
			clk_200hz <= 1'b0;
		end else begin
			if(cnt >= ((NUM_FOR_200HZ>>1) - 1)) begin	//  >>1 means divide by 2
				cnt <= 16'd0;
				clk_200hz <= ~clk_200hz;	
			end else begin
				cnt <= cnt + 1'b1;
				clk_200hz <= clk_200hz;
			end
		end
	end
 
	reg		[1:0]		c_state;
	always@(posedge clk_200hz or negedge rst_n) begin
		if(!rst_n) begin
			c_state <= ROW0_SCAN;
			row <= 4'b1110;
		end else begin
			case(c_state)
				ROW0_SCAN: begin c_state <= ROW1_SCAN; row <= 4'b1101; end	
				ROW1_SCAN: begin c_state <= ROW2_SCAN; row <= 4'b1011; end
				ROW2_SCAN: begin c_state <= ROW3_SCAN; row <= 4'b0111; end
				ROW3_SCAN: begin c_state <= ROW0_SCAN; row <= 4'b1110; end
				default:begin c_state <= ROW0_SCAN; row <= 4'b1110; end
			endcase
		end
	end
 
always@(negedge clk_200hz or negedge rst_n) begin
	if(!rst_n) begin
		key_out <= 12'hfff;
	end else begin
		case(c_state)
			ROW0_SCAN: begin 				// check for colum 0, 1, 2
						key[2:0] <= col;    
						key_r[2:0] <= key[2:0];    
						key_out[2:0] <= key_r[2:0]|key[2:0];   // double comfirm the pressed key
						
					end 
			ROW1_SCAN:begin 				// check for colum 3, 4, 5
						key[5:3] <= col;    
						key_r[5:3] <= key[5:3];    
						key_out[5:3] <= key_r[5:3]|key[5:3];   // double comfirm the pressed key
						
					end 
			ROW2_SCAN:begin 
						key[8:6] <= col;    // check for colum 6, 7, 8
						key_r[8:6] <= key[8:6];   
						key_out[8:6] <= key_r[8:6]|key[8:6];   // double comfirm the pressed key
						
					end 
			ROW3_SCAN:begin 
						key[11:9] <= col;    // check for colum 9, 10, 11
						key_r[11:9] <= key[11:9];    
						key_out[11:9] <= key_r[11:9]|key[11:9]; // double comfirm the pressed key
						
					end 
			default:key_out <= 12'hfff;
		endcase
	end
end
	
	
reg	[3:0]	key_code;	

reg		[11:0]		key_out_r;
wire	[11:0]		 key_pulse;
always @ ( posedge clk  or  negedge rst_n )begin
	if (!rst_n) key_out_r <= 12'hfff;
	else  key_out_r <= key_out;  
end 

assign key_pulse= key_out_r & (~key_out);   

always@(*)begin
	case(key_pulse)
		12'b0000_0000_0001: key_code=4'd1 ;	// key 1
		12'b0000_0000_0010: key_code=4'd2 ;	// key 2
		12'b0000_0000_0100: key_code=4'd3 ;	// key 3
		12'b0000_0000_1000: key_code=4'd4 ;	// key 4
		12'b0000_0001_0000: key_code=4'd5 ; 	// key 5
		12'b0000_0010_0000: key_code=4'd6 ; 	// key 6
		12'b0000_0100_0000: key_code=4'd7 ; 	// key 7
		12'b0000_1000_0000: key_code=4'd8 ; 	// key 8
		12'b0001_0000_0000: key_code=4'd9 ;	// key 9
		12'b0010_0000_0000: key_code=4'd10;	// key *
		12'b0100_0000_0000: key_code=4'd0 ;	// key 0
		12'b1000_0000_0000: key_code=4'd12;	// key #
		default: key_code=4'd15;           
	endcase                                
end                                        
                                           
always@(posedge clk or  negedge rst_n)begin
	if(!rst_n) 	keyPressed <= 4'd15;
	else			keyPressed<=key_code;
end 

	
always@(posedge clk)begin
	case(keyPressed)
	4'd0:  begin segment_led1 <=9'h3f;		    	end 
	4'd1:  begin segment_led1 <=9'h06;		    	end 
	4'd2:  begin segment_led1 <=9'h5b;		    	end
	4'd3:  begin segment_led1 <=9'h4f;		    	end
	4'd4:  begin segment_led1 <=9'h66;		    	end
	4'd5:  begin segment_led1 <=9'h6d;		    	end
	4'd6:  begin segment_led1 <=9'h7d;		    	end 
	4'd7:  begin segment_led1 <=9'h07;		    	end
	4'd8:  begin segment_led1 <=9'h7f;		    	end
	4'd9:  begin segment_led1 <=9'h6f;		    	end 
	4'd10: begin segment_led1 <=9'h77;		    	end
	4'd12: begin segment_led1 <=9'h39;		    	end	
	default:begin segment_led1<=segment_led1;	end
	endcase 
end 
endmodule

Implementation

Section 5.6: Rotary Encoder

Incremental Rotary Encoder

Digtial Structure

Verilog Code

Code 5.6: Complete code to interface with incremental rotary encoder
//---------------------------------------------------------------------------------------------------
// File name:         RotEncoder.v
// Top Module name:   RotEncoder
// Submodules:        None
// Description:       This module detects and processes rotary encoder signals to generate clean
//                    pulses indicating clockwise (CW) and counterclockwise (CCW) movements.
//                    It includes debouncing for the encoder's A and B outputs and detects the
//                    edges to determine the direction of rotation. 
//		      This code has also included the "Soft debouncing" algorithm so no external
//		      filtering capacitors are needed
//
// Example of Usage:  
//                    Connect 'key_A' and 'key_B' to the respective outputs of a rotary encoder,
//                    'CC_pulse' will go high for one clock cycle on a detected clockwise rotation,
//                    and 'CCW_pulse' will go high for one clock cycle on a counterclockwise rotation.
//                    Use these signals to increment or decrement a counter or to navigate through
//                    a menu interface.
//
// Reliability:       
//                    This code is intended for educational and demonstration purposes and has not been
//                    validated for use in production systems. Users are advised to thoroughly test the
//                    module in their specific application context.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------


module RotEncoder (
	input			clk, rst_n,	
	input			key_A, key_B,			
	output	reg		CC_pulse, CCW_pulse
);
	
localparam				NUM_250US = 3_000;

/************* Eliminate the glitches unstable signals during movement ********/
reg	[12:0]	cnt;											//
//count for clk_500us                                                                                                         //
always@(posedge clk or negedge rst_n) begin                 
	if(!rst_n) cnt <= 0;                                    
	else if(cnt >= NUM_250US-1) cnt <= 1'b0;                
	else cnt <= cnt + 1'b1;                                 
end                                                         
reg	clk_500us;	                                            
always@(posedge clk or negedge rst_n) begin                 
	if(!rst_n) clk_500us <= 0;                              
	else if(cnt == NUM_250US-1) clk_500us <= ~clk_500us;    
	else clk_500us <= clk_500us;                            
end                                                         
reg	key_A_r,key_A_r1,key_A_r2;                              
always@(posedge clk_500us) begin                            
	key_A_r		<=	key_A;                                  
	key_A_r1	<=	key_A_r;                                
	key_A_r2	<=	key_A_r1;                               
end                                                         
reg	A_state;                                                
always@(key_A_r1 or key_A_r2) begin                         
	case({key_A_r1,key_A_r2})                               
		2'b11:	A_state <= 1'b1;                            
		2'b00:	A_state <= 1'b0;                            
		default: A_state <= A_state;                        
	endcase                                                 
end                                                         
reg	key_B_r,key_B_r1,key_B_r2;                              
always@(posedge clk_500us) begin                            
	key_B_r		<=	key_B;                                  
	key_B_r1	<=	key_B_r;                                
	key_B_r2	<=	key_B_r1;                               
end                                                         
reg	B_state;                                                
always@(key_B_r1 or key_B_r2) begin                         
	case({key_B_r1,key_B_r2})                               
		2'b11:	B_state <= 1'b1;                            
		2'b00:	B_state <= 1'b0;                            
		default: B_state <= B_state;                        
	endcase                                                 
end                                                         
/*************************************************************************************/

// Detects the edge transition of Signal A on Rotary Encoder
reg A_state_r,A_state_r1;
always@(posedge clk) begin
	A_state_r <= A_state; 
	A_state_r1 <= A_state_r;
end
wire	A_pos	= (!A_state_r1) && A_state_r;
wire	A_neg	= A_state_r1 && (!A_state_r);

// If A is posedge and B is HIGH, or if A is falling edge and B is LOW, then clockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CC_pulse <= 1'b0;
	else if((A_pos&&B_state)||(A_neg&&(!B_state))) CC_pulse <= 1'b1;
	else CC_pulse <= 1'b0;
end 

// If A is posedge and B is LOW, or if A is falling edge and B is HIGH, then counterclockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CCW_pulse <= 1'b0;
	else if((A_pos&&(!B_state))||(A_neg&&B_state)) CCW_pulse <= 1'b1;
	else CCW_pulse <= 1'b0;
end 
endmodule

Implementation

Section 5.7: DC Motor & PWM

PWM and DC Motor

Digital Structure

Verilog Code

//---------------------------------------------------------------------------------------------------
// File name:         motorSpeedCtrl.v
// Top Module name:   motorSpeedCtrl
// Submodules:        RotEncoder, pwm_ctrl
// Description:       This module interfaces with a rotary encoder to control motor speed using PWM.
//                    It utilizes the 'RotEncoder' submodule to interpret rotary encoder inputs for
//                    rotational direction, and adjusts the PWM signal accordingly to increase or
//                    decrease the motor speed using the 'pwm_ctrl' submodule.
//
// Example of Usage:  
//                    Connect the rotary encoder's outputs to 'key_A' and 'key_B'. The 'pwm_out'
//                    should be connected to the motor controller's PWM input. Rotating the encoder
//                    clockwise will increase the motor speed, while rotating it counterclockwise
//                    will decrease the speed.
//
// Reliability:       
//                    The module is designed for educational purposes and prototyping. For production
//                    use, it should be rigorously tested, especially in safety-critical applications.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------

module motorSpeedCtrl (
    input clk,
    input rst_n,
    input key_A, key_B,
    output pwm_out
);

    // Intermediate signals
    wire CC_pulse, CCW_pulse;

    RotEncoder encoder (
        .clk(clk),
        .rst_n(rst_n),
        .key_A(key_A),
        .key_B(key_B),
        .CC_pulse(CC_pulse),
        .CCW_pulse(CCW_pulse)
    );
    
    pwm_ctrl pwmGenerator (
        .clk(clk),
        .speedUP(CC_pulse),
        .speedDOWN(CCW_pulse),
        .pwm_out(pwm_out)
    );
endmodule

//---------------------------------------------------------------------------------------------------
// File name:         pwm_ctrl.v
// Top Module name:   pwm_ctrl
// Parameters:        PWM_FREQUENCY, DUTY_STEP
// Description:       This module generates a Pulse Width Modulation (PWM) signal to control devices 
//                    like motors or LEDs. The PWM frequency and duty cycle step increment are 
//                    configurable via parameters. The duty cycle can be increased or decreased 
//                    dynamically using the 'speedUP' and 'speedDOWN' inputs.
//
// Example of Usage:  
//                    Instantiate this module and connect the 'speedUP' input to a source that 
//                    indicates the need to increase the duty cycle, and 'speedDOWN' to decrease it.
//                    The generated PWM signal at 'pwm_out' can then be used to control the speed 
//                    of a motor or the brightness of an LED, for example.
//
// Reliability:       
//                    This module is provided as-is for demonstration purposes and should be tested 
//                    thoroughly within its intended application and environment before any production 
//                    use, especially in systems where precise timing and response are critical.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------

module pwm_ctrl #( 
	parameter 	PWM_FREQUENCY = 1000,
	parameter 	DUTY_STEP = 5
)
(
    input 		clk,       
    input 		speedUP,   
    input 		speedDOWN, 
    output reg 	pwm_out    
);

    localparam duty_INIT = 20;  			
    localparam TOTAL_COUNT = 12000000 / PWM_FREQUENCY;  
    reg [31:0] counter = 0;               

    reg [7:0] duty_cycle = duty_INIT;   
    localparam MAX_DUTY = 100;          
    localparam MIN_DUTY = 0;            

    // Calculate counts for the high duration
    wire [31:0] HIGH_COUNT = (TOTAL_COUNT * duty_cycle) / 100;

    always @(posedge clk) begin
        if(speedUP) begin
            // Increase duty cycle if it doesn't exceed max limit
            if(duty_cycle + DUTY_STEP <= MAX_DUTY) begin
                duty_cycle <= duty_cycle + DUTY_STEP;
            end else begin
                duty_cycle <= MAX_DUTY;
            end
        end
        if(speedDOWN) begin
            // Decrease duty cycle if it doesn't go below min limit
            if(duty_cycle - DUTY_STEP >= MIN_DUTY) begin
                duty_cycle <= duty_cycle - DUTY_STEP;
            end else begin
                duty_cycle <= MIN_DUTY;
            end
        end

        if(counter < HIGH_COUNT) begin
            pwm_out <= 1'b1;
        end else begin
            pwm_out <= 1'b0;
        end

        if(counter == TOTAL_COUNT-1) begin
            counter <= 0;
        end else begin
            counter <= counter + 1;
        end
    end

endmodule


/*--------------------------------------------------------------------------------------------------- 	
*- File name: 		RotEncoder.v
*- Top Module name: 	RotEncoder
  - Submodules:		
*- Description:		Interface with an incremental rotary encoder
					
*- Example of Usage:
       - This code interfaces with an incremental rotary encoder. Connect Key_A and Key_B to
	the two phase outputs of the encoder; you can also add additional input to interface with
	the OK Press function of the encoder which is essentially a pushbutton. 
	CC_pulse and CCW_pulse indicates for rotating orientations. 
	
*- Copyright of this code: MIT License
--------------------------------------------------------------------------------------------------- */

module RotEncoder (
	input			clk, rst_n,	
	input			key_A, key_B,			
	output	reg		CC_pulse, CCW_pulse
);
	
localparam				NUM_250US = 3_000;

/************* Eliminate the glitches unstable signals during movement ********/
reg	[12:0]	cnt;											//
//count for clk_500us                                                                                                         //
always@(posedge clk or negedge rst_n) begin                 
	if(!rst_n) cnt <= 0;                                    
	else if(cnt >= NUM_250US-1) cnt <= 1'b0;                
	else cnt <= cnt + 1'b1;                                 
end                                                         
reg	clk_500us;	                                            
always@(posedge clk or negedge rst_n) begin                 
	if(!rst_n) clk_500us <= 0;                              
	else if(cnt == NUM_250US-1) clk_500us <= ~clk_500us;    
	else clk_500us <= clk_500us;                            
end                                                         
reg	key_A_r,key_A_r1,key_A_r2;                              
always@(posedge clk_500us) begin                            
	key_A_r		<=	key_A;                                  
	key_A_r1	<=	key_A_r;                                
	key_A_r2	<=	key_A_r1;                               
end                                                         
reg	A_state;                                                
always@(key_A_r1 or key_A_r2) begin                         
	case({key_A_r1,key_A_r2})                               
		2'b11:	A_state <= 1'b1;                            
		2'b00:	A_state <= 1'b0;                            
		default: A_state <= A_state;                        
	endcase                                                 
end                                                         
reg	key_B_r,key_B_r1,key_B_r2;                              
always@(posedge clk_500us) begin                            
	key_B_r		<=	key_B;                                  
	key_B_r1	<=	key_B_r;                                
	key_B_r2	<=	key_B_r1;                               
end                                                         
reg	B_state;                                            
always@(key_B_r1 or key_B_r2) begin                         
	case({key_B_r1,key_B_r2})                               
		2'b11:	B_state <= 1'b1;                            
		2'b00:	B_state <= 1'b0;                            
		default: B_state <= B_state;                        
	endcase                                                 
end                                                         
/*************************************************************************************/

// Detects the edge transition of Signal A on Rotary Encoder
reg A_state_r,A_state_r1;
always@(posedge clk) begin
	A_state_r <= A_state; 
	A_state_r1 <= A_state_r;
end
wire	A_pos	= (!A_state_r1) && A_state_r;
wire	A_neg	= A_state_r1 && (!A_state_r);

// If A is posedge and B is HIGH, or if A is falling edge and B is LOW, then clockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CC_pulse <= 1'b0;
	else if((A_pos&&B_state)||(A_neg&&(!B_state))) CC_pulse <= 1'b1;
	else CC_pulse <= 1'b0;
end 

// If A is posedge and B is LOW, or if A is falling edge and B is HIGH, then counterclockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CCW_pulse <= 1'b0;
	else if((A_pos&&(!B_state))||(A_neg&&B_state)) CCW_pulse <= 1'b1;
	else CCW_pulse <= 1'b0;
end 
endmodule

Implementation

Section 5.8: Servo Motor

Servo motor basics

Digital Structure

Verilog Code

Code 5.12: Using the incremental rotary encoder to control servo motor positioning
//---------------------------------------------------------------------------------------------------
// File name:         servoEncoder.v
// Top Module name:   servoEncoder
// Submodules:        RotEncoder, servoCtrl
// Description:       This module interfaces with a rotary encoder to control the angle of a servo motor.
//                    It uses the signals from the incremental rotary encoder to determine the direction of the 
//                    rotation and accordingly adjust the servo angle. The angle value is then used to 
//                    generate the corresponding PWM signal to drive the servo to the desired position.
//
// Example of Usage:
//    Instantiate the servoEncoder module, connect the encoder inputs to 'key_A' and 'key_B', and use
//    the 'servo_pwm' signal to drive a servo motor. The motor angle is adjusted via the rotary encoder.
// 
// Reliability:
//    The provided code is intended for educational and prototype use. It has not been tested for all
//    edge cases and operational scenarios. Users should perform rigorous validation for any
//    mission-critical applications.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------

module servoEncoder (
    input   clk, rst_n,
    input   key_A, key_B,
    output  servo_pwm
);

    wire     CC_pulse, CCW_pulse;
    reg     [7:0] rotate_angle = 8'd90;  

    RotEncoder U1 (
        .clk(clk),
        .rst_n(rst_n),
        .key_A(key_A),
        .key_B(key_B),
        .CC_pulse(CC_pulse),
        .CCW_pulse(CCW_pulse)
    );

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rotate_angle <= 8'd90;  // Reset to mid-point on reset
        end else begin
            if (CC_pulse) begin
                if (rotate_angle < 179) 
					rotate_angle <= rotate_angle + 1;
            end
            if (CCW_pulse) begin
                if (rotate_angle > 0) 
					rotate_angle <= rotate_angle - 1;
            end
        end
    end

    servoCtrl U2 (
        .clk(clk),
        .rst_n(rst_n),
        .rotate_angle(rotate_angle),
        .servo_pwm(servo_pwm)
    );
endmodule

module RotEncoder (
	input			clk, rst_n,	
	input			key_A, key_B,			
	output	reg		CC_pulse, CCW_pulse
);
	
localparam				NUM_250US = 3_000;

/************* Eliminate the glitches unstable signals during movement ********/
reg	[12:0]	cnt;										
//count for clk_500us                                        
always@(posedge clk or negedge rst_n) begin             
	if(!rst_n) cnt <= 0;                                
	else if(cnt >= NUM_250US-1) cnt <= 1'b0;             
	else cnt <= cnt + 1'b1;                             
end                                                    
reg	clk_500us;	                                       
always@(posedge clk or negedge rst_n) begin             
	if(!rst_n) clk_500us <= 0;                          
	else if(cnt == NUM_250US-1) clk_500us <= ~clk_500us;  
	else clk_500us <= clk_500us;                         
end                                                    
reg	key_A_r,key_A_r1,key_A_r2;                           
always@(posedge clk_500us) begin                        
	key_A_r		<=	key_A;                              
	key_A_r1	<=	key_A_r;                                
	key_A_r2	<=	key_A_r1;                               
end                                                    
reg	A_state;                                            
always@(key_A_r1 or key_A_r2) begin                      
	case({key_A_r1,key_A_r2})                            
		2'b11:	A_state <= 1'b1;                        
		2'b00:	A_state <= 1'b0;                        
		default: A_state <= A_state;                    
	endcase                                            
end                                                    
reg	key_B_r,key_B_r1,key_B_r2;                           
always@(posedge clk_500us) begin                        
	key_B_r		<=	key_B;                              
	key_B_r1	<=	key_B_r;                                
	key_B_r2	<=	key_B_r1;                               
end                                                    
reg	B_state;                                            
always@(key_B_r1 or key_B_r2) begin                      
	case({key_B_r1,key_B_r2})                            
		2'b11:	B_state <= 1'b1;                        
		2'b00:	B_state <= 1'b0;                        
		default: B_state <= B_state;                    
	endcase                                            
end                                                    
/*************************************************************************************/

// Detects the edge transition of Signal A on Rotary Encoder
reg A_state_r,A_state_r1;
always@(posedge clk) begin
	A_state_r <= A_state; 
	A_state_r1 <= A_state_r;
end
wire	A_pos	= (!A_state_r1) && A_state_r;
wire	A_neg	= A_state_r1 && (!A_state_r);

// If A is posedge and B is HIGH, or if A is falling edge and B is LOW, then clockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CC_pulse <= 1'b0;
	else if((A_pos&&B_state)||(A_neg&&(!B_state))) CC_pulse <= 1'b1;
	else CC_pulse <= 1'b0;
end 

// If A is posedge and B is LOW, or if A is falling edge and B is HIGH, then counterclockwise
always@(posedge clk or negedge rst_n) begin
	if(!rst_n) CCW_pulse <= 1'b0;
	else if((A_pos&&(!B_state))||(A_neg&&B_state)) CCW_pulse <= 1'b1;
	else CCW_pulse <= 1'b0;
end 
endmodule

//---------------------------------------------------------------------------------------------------
// File name:         servoCtrl.v
// Module name:       servoCtrl
// Description:       This module generates a Pulse Width Modulation (PWM) signal to control a servo 
//                    motor based on the desired rotation angle provided as an input. The PWM signal
//                    corresponds to the conventional 20ms servo control cycle, with the duty cycle 
//                    adjusted to represent the angle between 0 and 180 degrees.
//
// Reliability: 
//    This code is intended for educational and demonstration purposes and should be tested for 
//    specific applications before production use.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------

module servoCtrl(
    input clk, rst_n,
    input [7:0] rotate_angle,
    output reg servo_pwm
);

    localparam CNT_20MS = 240_000;
    localparam pulse_min = 6000;
    localparam pulse_max = 30000;
    localparam tick_Degree = (pulse_max - pulse_min) / 180;

    reg [17:0] cnt_20ms;
    reg [17:0] pulse_width;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_20ms <= 0;
        end else if(cnt_20ms >= CNT_20MS - 1) begin
            cnt_20ms <= 0;
        end else begin
            cnt_20ms <= cnt_20ms + 1;
        end
    end

    always @(posedge clk) begin
        if(rotate_angle > 180) begin
            pulse_width <= pulse_max;
        end else begin
            pulse_width <= pulse_min + (rotate_angle * tick_Degree);
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            servo_pwm <= 1'b0;
        end else begin
            servo_pwm <= (cnt_20ms < pulse_width) ? 1'b1 : 1'b0;
        end
    end
endmodule
Code 5.13: Periodic scanning between 0 to 179 degrees
//---------------------------------------------------------------------------------------------------
// File name:         servoScan.v
// Module name:       servoScan
// Description:       This module controls a servo to sweep back and forth across its full range. It 
//                    gradually increments or decrements the servo angle to create a scanning motion. 
//                    A delay counter is used to provide time for the servo to reach each position 
//                    before moving to the next.
//
// Usage Example:
//    - Connect the servoScan module to a higher level controller that provides a clock and reset 
//      signal, and connect the servo_pwm output to a servo motor.
//
// Reliability: 
//    - This code is for educational and demonstration purposes and should be validated for each 
//      specific application.
//
// Copyright:         Copyright (c) 2023 by EIM Technology
// License:           MIT License
//---------------------------------------------------------------------------------------------------

module servoScan(
    input clk,
    input rst_n,
    output servo_pwm
);

    reg [7:0] angle; // Servo angle
    reg dir; // Direction of scan: 1 for increasing, 0 for decreasing
    reg [15:0] delay_counter; // Delay counter to provide some time for the servo to reach a position

    // Instantiate the servo control module
    servoControl servoCtrl (
        .clk(clk),
        .rst_n(rst_n),
        .rotate_angle(angle),
        .servo_pwm(servo_pwm)
    );

    // Sweep logic
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            angle <= 8'd0; // Start at 0 degrees
            dir <= 1'b1; // Start in increasing direction
            delay_counter <= 16'd0; // Reset delay counter
        end else begin
            if (delay_counter == 16'd60000) begin // Delay to allow servo to reach position
                delay_counter <= 16'd0; // Reset delay counter
                if (dir) begin
                    if (angle < 8'd179) angle <= angle + 1;
                    else dir <= 1'b0;
                end else begin
                    if (angle > 8'd0) angle <= angle - 1;
                    else dir <= 1'b1;
                end
            end else begin
                delay_counter <= delay_counter + 1;
            end
        end
    end
endmodule

module servoControl(
    input clk,
    input rst_n,
    input [7:0] rotate_angle,
    output reg servo_pwm
);

    // Local parameters for timing calculations
    localparam CNT_20MS = 240_000;
    localparam PULSE_MIN = 6_000;
    localparam PULSE_MAX = 30_000;
    localparam DEG_TICK = (PULSE_MAX - PULSE_MIN) / 180;

    // Counters
    reg [17:0] cnt_20ms;
    reg [17:0] pulse_width;

    // 20ms period counter
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_20ms <= 0;
        end else if(cnt_20ms >= CNT_20MS - 1) begin
            cnt_20ms <= 0;
        end else begin
            cnt_20ms <= cnt_20ms + 1;
        end
    end

    // Calculate pulse width based on desired angle
    always @(posedge clk) begin
        if(rotate_angle > 180) begin
            pulse_width <= PULSE_MAX;
        end else begin
            pulse_width <= PULSE_MIN + (rotate_angle * DEG_TICK);
        end
    end

    // Generate PWM signal
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            servo_pwm <= 1'b0;
        end else begin
            servo_pwm <= (cnt_20ms < pulse_width) ? 1'b1 : 1'b0;
        end
    end
endmodule

Implementation

Last updated