前言:

作为我的FPGA入门项目,我需要在FPGA上实现一个可以通过I2C接口来访问的GPIO扩展以及PWM生成的功能.原因是我在ESP32-S3接了一块i8080接口的LCD屏,这个屏基本占用了ESP32-S3的所有IO,剩余的IO不够实现其他功能了,所以需要一个GPIO扩展IC,另外控制屏幕的背光亮度又需要一个生成指定占空比的PWM的功能,以及后续可能还需要有读写SPI Flash以及播放音频的功能,这些如果都使用现有的IC的话,器件就比较多,而且因为ESP32-S3的IO不够用,所以只能指定使用某种总线,比如I2C或者SPI,但是SPI需要为每个从器件分配一个CS引脚,这又加重了ESP32-S3的IO负担,所以只能选择I2C,但是这样就要求上面提到的所有功能都需要有I2C接口的器件,这增加了设计难度.

为了完成这个项目,我需要首先在FPGA上实现一个I2C从机接口.在这期间,我用到下面这几种局部设计(啊....果然初学的时候闭门造车是不对的,要多看别人的设计,多用通用的设计):

一.边沿检测:

边沿检测是检测一个信号的上升沿,下降沿,并输出对应的脉冲.

verilog实现方法如下:

module edge_detect( 
input clk,
input rst_n,
input signal,
output pos_edge,
output neg_edge,
output both_edge
);

reg [1:0]sig_fifo;
always @ (posedge clk or negedge rst_n)begin
    if (!rst_n) begin 
        sig_fifo <= 2'b0;
    end 
    else begin 
        sig_fifo <= {sig_fifo[0], signal};
    end

end
 
assign pos_edge = (sig_fifo == 2'b01);
assign neg_edge = (sig_fifo == 2'b10);  
assign both_edge = pos_edge|neg_edge;
 
endmodule

二.计数器

计数器是对输入的脉冲进行计数,并以并行输出的形式输出计数的值.

verilog实现方法如下:

module count
(
input clk,
input rst_n,
output reg[ 3:0] cnt
);

 

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 4'b0;

    end
    else begin
        cnt <= cnt + 1'b1;

    end
end

endmodule

三.串行转并行(移位寄存器)

串行转并行是将输入的串行输入转为指定位数的并行数据输出(注意有高位优先和低位优先的区别).

verilog实现方法如下:

module serial_parallel_msb_first(
input clk,
input rst_n,
input int,            //一位输入
output reg [7:0] out  //8位并行输出
);

always @(posedge clk or negedge rst_n)begin
    if (rst_n == 1'b0)begin
        out <= 8'b0;

    end
    else begin
        out <= {int,out[7:1]};	//高位先赋值

    end
end

endmodule

四.时钟分频(偶数分频)

时钟分频用在对高速时钟进行分频,以生成低速的时钟信号.

verilog实现方法如下:

module clk_div(clk_out,clk_in,rst_n,clk_div);
input clk_in;
input rst_n;
input [15:0]clk_div;
output clk_out;
reg[15:0]cnt;
reg clk_out;


always@(posedge clk_in or negedge rst_n)
begin
    if(!rst_n)begin
        cnt<=0;
        clk_out<=0;
    end
    else begin
        if(cnt==((clk_div/16'd2)-16'd1))begin
            clk_out<=~clk_out;
            cnt<=0;
        end
        else begin
            cnt<=cnt+1'b1;
        end
    end
end




endmodule

五.独热码转二进制码

这个应用是因为做状态机的时候,当状态数量比较多时,最好用独热码来表示不同状态,但是在使用逻辑分析仪调试的时候,要通过io口输出状态机状态的话,如果用独热码就占用了太多引脚,所以需要一个独热码转二进制码的功能,方便调试输出,因为只是一个debug工具,所以我直接用查表的方式去实现了.

verilog实现方法如下:

module one_hot_to_binary
(
input[31:0] one_hot,
output reg[ 4:0] binary
);

always @(*)begin
    case(one_hot)
        32'b0000_0000_0000_0000_0000_0000_0000_0001:
        begin
            binary=5'd1;
        end
        32'b0000_0000_0000_0000_0000_0000_0000_0010:
        begin
            binary=5'd2;
        end
        32'b0000_0000_0000_0000_0000_0000_0000_0100:
        begin
            binary=5'd3;
        end
        32'b0000_0000_0000_0000_0000_0000_0000_1000:
        begin
            binary=5'd4;
        end
        32'b0000_0000_0000_0000_0000_0000_0001_0000:
        begin
            binary=5'd5;
        end
        32'b0000_0000_0000_0000_0000_0000_0010_0000:
        begin
            binary=5'd6;
        end
        32'b0000_0000_0000_0000_0000_0000_0100_0000:
        begin
            binary=5'd7;
        end
        32'b0000_0000_0000_0000_0000_0000_1000_0000:
        begin
            binary=5'd8;
        end
        32'b0000_0000_0000_0000_0000_0001_0000_0000:
        begin
            binary=5'd9;
        end
        32'b0000_0000_0000_0000_0000_0010_0000_0000:
        begin
            binary=5'd10;
        end
        32'b0000_0000_0000_0000_0000_0100_0000_0000:
        begin
            binary=5'd11;
        end
        32'b0000_0000_0000_0000_0000_1000_0000_0000:
        begin
            binary=5'd12;
        end
        32'b0000_0000_0000_0000_0001_0000_0000_0000:
        begin
            binary=5'd13;
        end
        32'b0000_0000_0000_0000_0010_0000_0000_0000:
        begin
            binary=5'd14;
        end
        32'b0000_0000_0000_0000_0100_0000_0000_0000:
        begin
            binary=5'd15;
        end
        32'b0000_0000_0000_0000_1000_0000_0000_0000:
        begin
            binary=5'd16;
        end
        32'b0000_0000_0000_0001_0000_0000_0000_0000:
        begin
            binary=5'd17;
        end
        32'b0000_0000_0000_0010_0000_0000_0000_0000:
        begin
            binary=5'd18;
        end
        32'b0000_0000_0000_0100_0000_0000_0000_0000:
        begin
            binary=5'd19;
        end
        32'b0000_0000_0001_0000_0000_0000_0000_0000:
        begin
            binary=5'd20;
        end
        32'b0000_0000_0010_0000_0000_0000_0000_0000:
        begin
            binary=5'd21;
        end
        32'b0000_0000_0100_0000_0000_0000_0000_0000:
        begin
            binary=5'd22;
        end
        32'b0000_0000_1000_0000_0000_0000_0000_0000:
        begin
            binary=5'd23;
        end
        32'b0000_0001_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd24;
        end
        32'b0000_0010_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd25;
        end
        32'b0000_0100_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd26;
        end
        32'b0000_1000_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd27;
        end
        32'b0001_0000_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd28;
        end
        32'b0010_0000_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd29;
        end
        32'b0100_0000_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd30;
        end
        32'b1000_0000_0000_0000_0000_0000_0000_0000:
        begin
            binary=5'd31;
        end
        default:
        begin
            binary=5'd0;
        end
    endcase
end

endmodule


一个电子工程师的自我修养