【FPGA】基于IIC协议实现FPGA对EEPROM(24LC04B)的读写控制【附源码】

实验目的 

通过FPGA实现对EEPROM(型号为24LC04B)的单字节写、单字节读、连续写入和连续读出

理论原理

一、IIC

1. 背景

        IIC(Inter-Integrated Circuit) 是IIC Bus的简称,中文名为内置集成电路总线,是一种串行通信总线,使用多主从架构,I2C通信方式为同步通信,通信方向为半双工,数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。

2. 物理结构

         IIC有两条线,分别是数据线SDA和时钟线SCL,所有数据都是通过SDA线传递的,SCL线并不传输数据,它只是提供时钟周期。IIC通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线的任何器件输出的高电平都将使总线的信号变低,即各器件的SDA及SCL都是“线与”的关系,每个接到IIC总线器件都有唯一的地址。

线与:将多个门电路的输出端接在一起,以实现逻辑“与”的效果,要求每个门电路必须是集电极开路门(OC门)或源极开路门(OD门),这是因为只有当任意一个开关对地导通时,这根线就一定是低电平,从而实现了线与的功能。

 3. 数据传输

3.1 空闲状态

I2C 总线总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

3.2 数据有效性

       IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。在SCL高电平期间的SDA变换被视为起始/停止信号。

 3.3 起始/停止信号

SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。   

起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。

3.4 字节传送与应答

在数据传送时,每个字节必须保证是8位字节长度,首先传送最高位(MSB),每个传送的字节后面必须跟随一位应答位(即一帧数据共有九位)。

数据应答(ACK):主机发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SCL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。 

二、EEPROM(24LC04B)

本实验选用Microchip Technology Inc生产的型号为24LC04B的EEPROM,以下是对数据手册的相关摘取。

1. 概述

24LC04B是一种4kbit(2*256*8)容量的电可擦除PROM,该设备有两个256 x 8bit的存储块,具有2线串行接口(兼容IIC),支持最多写入16字节数据的页写功能, 兼容100kHz和400kHz的时钟速率,有16字节的页面写入缓冲区。

2. 交流特性

 24LC04B的时钟频率最大支持400kHz,高/低电平最小持续时间为600/1300ns,SDA输出有效延时最大为900ns,一次传输完毕后会有至少1300ns的总线释放时间,一次写循环时间为5ms。

以上特性在使用仿真模型时需要注意,博主在第一次仿真时未注意到以上特性debug用了很久/(ㄒoㄒ)/~~

3. 器件寻址

控制字节是在启动条件之后从主设备接收的第一个字节,对24LC04B来说,前4bit为1010,剩下两位don’t care(本实验仿真模型使用的为00),然后的B0位由主设备用来选择要访问存储器的两个256字块中的哪一个(本实验仿真模型使用的为0)。最后一位定义了要执行的操作,当设置为“1”时,将选择读取操作;当设置为“0”时,将选择写入操作。

当主机发送起始信号后,24LC04B监视SDA总线,检查正在传输的设备类型标识符,并且在接收到“1010000”代码后,根据R/W位的状态选择读取或写入操作。

4. 写操作

4.1 字节写

字节写步骤如下:

先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送将要写入的数据,接收从机应答,最后发送停止信号。

4.2 页写

页写步骤如下:

先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送将要写入的数据n,接收从机应答,发送将要写入的数据n+1,接收从机应答,重复上述操作,直到发完最后一个数据主机发送停止位表明页写结束。

需要注意的是,页写操作最多只能写入16个有效数据字节,这些数据字节临时存储在片上页面缓冲区中,一旦主设备发送了停止条件,就会写入存储器。收到每个字后,低阶地址指针位在内部递增“1”。若传输超过16个字节,则地址计数器将翻转,并且先前接收的数据将被覆盖。

5. 读操作

5.1 当前地址读

当前地址读步骤如下:

先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为1,接收从机应答ACK,接收读到的1字节数据,主机发送应答NACK,最后发送停止信号。

5.2 随机读

随机地址读步骤如下:

先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送起始信号,发送control byte(本实验为1010_000),r/w标志位为1,接收读到的1字节数据,主机发送应答信号NACK,最后发送停止信号。

5.3 顺序读

顺序读步骤如下:

先发送起始信号,接着发送control byte(本实验为1010_000),r/w标志位为0,然后接收从机应答的ACK信号,发送字地址(block里的寄存器地址),接收从机应答,发送起始信号,发送control byte(本实验为1010_000),r/w标志位为1,接收读到数据n,主机发送应答信号ACK,接收读到的数据n+1,主机发送应答信号ACK,重复上述操作,读到想读取的最后一字节后,主机发送应答信号NACK,最后发送停止信号。

硬件原理

本实验采用FPGA芯片型号为EP4CE6F17C8,与EEPROM连接图如下:

模块设计

主模块

其中uart_rx_byte模块为串口接收模块,接收上位机发送的一个字节数据保存到d_out;uart_tx_byte为串口发送模块,接收i2c_ctrl模块传递过来的数据d_in发送给上位机。key模块为按键消抖模块,本实验用到了三个按键key2、key3、key4。i2c_ctrl为整个设计控制模块,其作用是给i2c_driver模块发送起始命令start、cmd(指示i2c_driver模块将要进行的不同写/读操作)、将要写入的数据wr_data;还有接收i2c_driver传回来的从EEPROM读到的数据及完成信号。i2c_driver 模块是底层驱动EEPROM模块,作用是将上层控制模块与EEPROM器件的数据以IIC协议进行交互。

/* ================================================ *\

Filename : i2c_eeprom.v

Author : Strauss

@Time : 2023-11-26 13:23:52

Revision & Function: 1.0

\* ================================================ */

module i2c_eeprom(clk, rst_n, key2, key3, key4, scl, sda, rxd, txd);

input clk, rst_n, key2, key3, key4, rxd;

output scl, txd;

inout sda;

//Parameter Declarations

//Internal wire/reg declarations

wire done;

wire [7:0]rd_data;

wire data_vld;

wire [2:0]cmd;

wire start;

wire [7:0]wr_data;

wire op_done;

wire [7:0]d_out;

wire rx_done;

wire [7:0]d_in;

wire wr_done, rd_done, tx_done;

wire [2:0]key_in = {key4, key3, key2};

wire [2:0]key_out;

wire tx_loading;

//Module instantiations , self-build module

i2c_ctrl i2c_ctrl_inst(.clk(clk), .rst_n(rst_n), .key2(key_out[0]), .key3(key_out[1]), .key4(key_out[2]),

.d_out(d_out), .done(done), .rx_done(rx_done), .wr_done(wr_done), .rd_done(rd_done), .d_in(d_in), .rd_data(rd_data),

.data_vld(data_vld), .cmd(cmd), .start(start), .wr_data(wr_data), .tx_loading(tx_loading));

i2c_driver i2c_driver_inst(.clk(clk), .rst_n(rst_n), .cmd(cmd), .start(start), .wr_data(wr_data), .rd_data(rd_data),

.data_vld(data_vld), .done(done), .scl(scl), .sda(sda));

uart_rx_byte uart_rx_byte_inst(.clk(clk), .rst_n(rst_n), .rxd(rxd), .dout(d_out), .rx_done(rx_done));

uart_tx_byte uart_tx_byte_inst(.clk(clk), .rst_n(rst_n), .d_in(d_in), .start(rd_done), .txd(txd), .tx_done(tx_done), .tx_loading(tx_loading));

key key_inst(.clk(clk), .rst_n(rst_n), .key_in(key_in), .key_out(key_out));

//Logic Description

endmodule

驱动模块

驱动模块为FPGA与EEPROM进行数据交互的底层模块,其作用是将上层需要写入的数据或从EEPROM读出的数据以IIC协议进行交互,输入输出信号含义如下(忽略clk、rst_n):

信号名称信号含义
                cmd                

控制i2c_driver进行一次读/写操作的类型

start控制i2c_driver开始工作的信号
wr_data将要写入EEPROM的数据
donei2c_driver完成一次操作的结束信号
                 rd_data                i2c_driver完成一次读操作读到的1字节数据

data_vld

i2c_driver完成一次读操作的标志信号
sclIIC的时钟线
sdaIIC的数据线

由EEPROM手册可知,若想实现单字节写、页写、随机读、连续读,可视为对EEPROM进行以下n次(n取决于操作的类型以及需要写/读字节的数量)操作,用cmd来表示这五种情况:

例如要进行单字节写操作,则上层控制模块应发cmd为0-1-2,(d7-d0由上层模块控制);要进行随机读操作,则上层控制模块应发cmd为0-1-0-3;剩余两种操作类似。

可画出i2c_driver模块内的状态图:

根据状态图写出代码:

/* ================================================ *\

Filename : i2c_driver.v

Author : Strauss

@Time : 2023-11-28 09:00:27

Revision & Function: 1.0

iic传输速率:200Kbit/s

\* ================================================ */

module i2c_driver(clk, rst_n, cmd, start, wr_data, rd_data, data_vld, done, scl, sda);

input clk, rst_n;

input [2:0]cmd;//写:cmd==00||cmd==01cmd==10对应三种形式的写操作 读:cmd==11读一个字节 cmd==100顺序读

input start;

input [7:0]wr_data;

output [7:0]rd_data;

output data_vld, done;//data_vld表示读出的数据rd_data有效;done表示一次驱动模块与外设的数据交互完成

output  scl;

//三态门的相关定义

reg sda_out_en_dly;

inout sda;

wire sda_in;

reg sda_out;

reg sda_out_en;

assign sda = sda_out_en_dly?sda_out:'bz;

assign sda_in = sda;

//Parameter Declarations

parameter SCL_PER = 250;//scl周期为5us

parameter SCL_PER_HALF = 250/2;

parameter SCL_PER_LOW_HALF = 250/2/2;

parameter SCL_PER_HIGH_HALF = 250/2+250/4;

//Internal wire/reg declarations

reg [7:0]r_data_tmp;

//Module instantiations , self-build module

//Logic Description

localparam //独热码定义状态参数

    IDLE    = "IDLE",

    START   = "START",

    WR  = "WR",

    RD  = "RD",

    ACK = "ACK",

    NACK    = "NACK",

    STOP    = "STOP";

    reg     [39:00]     state_c, state_n; //状态变量

    //状态转移条件声明

    wire    idle2start;

    wire    idle2wr;

    wire    idle2rd;

    wire    start2wr;

    wire    wr2ack;

    wire    rd2nack;

    wire    ack2stop;

    wire    ack2idle;

    wire    nack2stop;

    wire    stop2idle;

    wire    rd2ack;

//scl计数器

reg [7:0] cnt_scl  ; //Counter

wire add_cnt_scl  ; //Counter Enable

wire end_cnt_scl  ; //Counter Reset

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)

    cnt_scl  <= 'd0;

 else if(add_cnt_scl )begin

    if(end_cnt_scl )begin

    cnt_scl  <= 'd0;

    end

 else begin

    cnt_scl  <= cnt_scl  + 1'b1;

    end

 end

 else begin

    cnt_scl  <= cnt_scl ;

    end

end

assign add_cnt_scl  = state_c != IDLE;

assign end_cnt_scl  = add_cnt_scl  && cnt_scl  >= SCL_PER-1;

//WR/RD的bit计数

reg [3:0] cnt_bit  ; //Counter

wire add_cnt_bit  ; //Counter Enable

wire end_cnt_bit  ; //Counter Reset

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)

    cnt_bit  <= 'd0;

 else if(add_cnt_bit )begin

    if(end_cnt_bit )begin

    cnt_bit  <= 'd0;

    end

 else begin

    cnt_bit  <= cnt_bit  + 1'b1;

    end

 end

 else begin

    cnt_bit  <= cnt_bit ;

    end

end

assign add_cnt_bit  = (state_c == WR || state_c == RD)&&end_cnt_scl;

assign end_cnt_bit  = add_cnt_bit  && cnt_bit >= 7;

//scl控制

// always@(posedge clk or negedge rst_n)

    // begin

        // if(!rst_n)  

            // scl <= 1;  

        // else if(add_cnt_scl)

            // begin

                // if(cnt_scl < SCL_PER_HALF-1)

                    // scl <= 0;

                // else

                    // scl <= 1;

            // end

        // else

            // scl <= 1;

    // end

assign scl = (add_cnt_scl && cnt_scl <= SCL_PER_HALF-1)?0:1;

//读出数据

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        r_data_tmp <= 0;

    end

else if(state_c == RD&&cnt_scl == SCL_PER_HIGH_HALF-1)begin

        r_data_tmp[7-cnt_bit] <= sda_in;

    end

end //always end

assign rd_data = data_vld ? r_data_tmp : rd_data;

assign data_vld = (stop2idle && cmd == 3) || (ack2idle && cmd==4);

assign done = stop2idle || ack2idle;

//第一段, 状态空间切换

always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

        state_c <= IDLE;

    end

    else begin

        state_c <= state_n;

    end

end

//第二段, 状态转移条件描述

always @(*) begin

    case (state_c)

        IDLE:   begin

            if (idle2start) begin

                state_n = START;

            end

            else if (idle2wr) begin

                state_n = WR;

            end

            else if (idle2rd) begin

                state_n = RD;

            end

            else begin

                state_n = IDLE;

            end

        end

        START:  begin

            if (start2wr) begin

                state_n = WR;

            end

            else begin

                state_n = START;

            end

        end

        WR: begin

            if (wr2ack) begin

                state_n = ACK;

            end

            else begin

                state_n = WR;

            end

        end

        RD: begin

            if (rd2nack) begin

                state_n = NACK;

            end

            else if(rd2ack)

                state_n = ACK;

            else begin

                state_n = RD;

            end

        end

        ACK:    begin

            if (ack2stop) begin

                state_n = STOP;

            end

            else if (ack2idle) begin

                state_n = IDLE;

            end

            else begin

                state_n = ACK;

            end

        end

        NACK:   begin

            if (nack2stop) begin

                state_n = STOP;

            end

            else begin

                state_n = NACK;

            end

        end

        STOP:   begin

            if (stop2idle) begin

                state_n = IDLE;

            end

            else begin

                state_n = STOP;

            end

        end

        default:state_n = IDLE;

    endcase

end

//状态转移条件描述

assign idle2start = state_c == IDLE && (start&&!cmd);

assign idle2wr   = state_c == IDLE && (start&&cmd[0]^cmd[1]);

assign idle2rd   = state_c == IDLE && ((start&&cmd==3)||cmd==4);

assign start2wr  = state_c == START && (end_cnt_scl);

assign wr2ack    = state_c == WR && (end_cnt_bit);

assign rd2ack    = state_c == RD && (cmd==4&&end_cnt_bit);

assign rd2nack   = state_c == RD && (cmd==3&&end_cnt_bit);

assign ack2stop  = state_c == ACK && (cmd==2&&end_cnt_scl);

assign ack2idle  = state_c == ACK && (cmd!=2&&end_cnt_scl);

assign nack2stop = state_c == NACK && (end_cnt_scl);

assign stop2idle = state_c == STOP && (end_cnt_scl);

//sda_out控制

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        sda_out <= 1;

    end

else begin

        case(state_c)

            IDLE:

                // if(cmd != 4)

                    sda_out <= 1;

            START:

                if(cnt_scl >= SCL_PER_HIGH_HALF-1)

                    sda_out <= 0;

            WR:

                if(cnt_scl >= SCL_PER_LOW_HALF-1)

                    sda_out <= wr_data[7-cnt_bit];

                // else

                    // sda_out <= 1;

            RD:

                sda_out <= 0;

            ACK:

                begin

                    if(cnt_scl >= SCL_PER_LOW_HALF-1)

                       sda_out <= 0;

                end

            NACK:

                if(cnt_scl >= SCL_PER_LOW_HALF-1)

                    sda_out <= 1;

            STOP:

                if(cnt_scl == SCL_PER_LOW_HALF-1)

                    sda_out <= 0;

                else if(cnt_scl >= SCL_PER_HIGH_HALF-1)

                    sda_out <= 1;

            default:sda_out <= 1;

        endcase

end //always end

end

//sda_en控制

//assign sda_out_en = (state_c == START || state_c == WR || state_c == NACK || state_c == STOP|| (state_c==ACK&&cmd==4))?1:0;

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        sda_out_en <= 0;

    end

else if(state_c == START || state_c == WR || state_c == NACK || state_c == STOP || (state_c==ACK&&cmd==4))begin

        sda_out_en <= 1;

    end

else begin

        sda_out_en <= 0;

    end

end //always end

always@(posedge clk)

    sda_out_en_dly <= sda_out_en;

endmodule

控制模块

控制模块作用为将FPGA外界传入的写/读请求信号转化成具体的传输给下层的命令和数据,本实验写入的地址和页写数据由i2c_ctrl模块内部产生,单字节写的数据由上层串口模块发送过来。

本实验控制模块用到了两个FIFO,由于作者时间有限,这两个FIFO只用于单字节的读/写操作的缓存,所以有一点鸡肋,但是是老师要求的,所以就加上了,两个FIFO对本实验的影响不大。

输入输出信号含义如下(忽略clk、rst_n):

信号名称信号含义
dout串口传入的一字节接收数据
rx_done串口数据传输完成的标志信号
wr_done一次写操作完成
d_in串口传出的一字节发送数据
rd_done一次读操作完成
key2/key3/key4按键控制,用于读/写请求
start

控制i2c_driver模块的开始

wr_data将要写入EEPROM的数据
donei2c_driver完成一次操作的结束信号
                 rd_data                i2c_driver完成一次读操作读到的1字节数据

data_vld

i2c_driver完成一次读操作的标志信号
                cmd                

控制i2c_driver进行一次读/写操作的类型

由于本实验的目标是对EEPROM进行四种操作:单字节写/读、页写和顺序读,每一种操作i2c_ctrl模块需往下发送n段的指令和数据(单字节写时n为3,单字节读时n为4,页写和顺序读取决于需进行写/读的字节数),设计状态机时可对每种操作设置两个状态:请求状态和等待操作完成状态,其中请求状态的作用是发送每一段操作的起始信号、命令种类和需要写入的数据(读的话可以不关心),等待状态下等待i2c_driver模块的一段读/写操作完成,如果是整个命令的最后一段操作则返回到IDLE态,若不是返回到i2c_ctrl状态继续发送起始信号、命令种类和写入数据。状态机设计图如下:

其中页写操作博主设置的是写12个数据bb ba b9...b0;顺序读操作设置的是读10个数据。页写操作中cnt_page==14的原因是写入数据前有两段写control byte 和写word address的操作;顺序读操作中cnt_seq==13的原因是读有效数据前还需写入control byte、word address、control byte。

由上可写出i2c_ctrl模块的代码:

/* ================================================ *\

Filename : i2c_ctrl.v

Author : Strauss

@Time : 2023-11-28 20:22:17

Revision & Function: 1.0

\* ================================================ */

module i2c_ctrl(clk, rst_n, key2, key3, key4, d_out, done, rx_done, wr_done, rd_done, d_in, rd_data, data_vld, cmd, start, wr_data, tx_loading);

input clk, rst_n, key2, key3, key4, data_vld;

input [7:0]d_out;

input done;

input rx_done;

input [7:0]rd_data;

input tx_loading;

output [7:0]d_in;

output reg [2:0]cmd;

output reg start;

output reg [7:0]wr_data;

output wr_done, rd_done;

//Parameter Declarations

//Internal wire/reg declarations

reg [2:0]cnt_alt;//计数写/读操作的当前段数

wire [7:0]q_fifo;

reg [3:0]cnt_page;

wire seq_rd_en;

reg [3:0]cnt_seq;

// wire wr_done, rd_done;

wire rd_fifo_empty;

//Module instantiations , self-build module

wr_fifo wr_fifo_inst (

    .clock ( clk ),

    .data ( d_out ),

    .rdreq ( key2 ),

    .wrreq ( rx_done ),

    .empty (  ),

    .full (  ),

    .q ( q_fifo ),

    .usedw (  )

    );

rd_fifo rd_fifo_inst (

    .clock ( clk ),

    .data ( rd_data ),

    .rdreq ( key3 ),

    .wrreq ( data_vld ),

    .empty ( rd_fifo_empty ),

    .full (  ),

    .q ( d_in ),

    .usedw (  )

    );

//Logic Description

localparam //独热码定义状态参数

    IDLE    = "IDLE", //状态

    WR_REG  = "WR_REG", //状态

    WR_WAIT = "WR_WAIT", //状态

    RD_REG  = "RD_REG", //状态

    RD_WAIT = "RD_WAIT", //状态

    PAGE_WR_REG = "PAGE_WR_REG",

    PAGE_WR_WAIT = "PAGE_WR_WAIT",

    SEQ_RD_REG = "SEQ_RD_REG",

    SEQ_RD_WAIT = "SEQ_RD_WAIT";

    reg     [95:00]     state_c, state_n; //状态变量声明

    //状态转移条件声明

    wire    idle2wr_reg;

    wire    idle2rd_reg;

    wire    wr_reg2wr_wait;

    wire    wr_wait2wr_reg;

    wire    wr_wait2idle;

    wire    rd_reg2rd_wait;

    wire    rd_wait2rd_reg;

    wire    rd_wait2idle;

    wire    idle2page_wr_reg;

    wire    page_wr_reg2page_wr_wait;

    wire    page_wr_wait2page_wr_reg;

    wire    page_wr_wait2idle;

    wire    idle2seq_rd_reg;

    wire    seq_rd_reg2seq_rd_wait;

    wire    seq_rd_wait2seq_rd_reg;

    wire    seq_rd_wait2idle;

    //第一段, 状态空间切换

    always @(posedge clk or negedge rst_n) begin

        if (!rst_n) begin

            state_c <= IDLE;

        end

        else begin

            state_c <= state_n;

        end

    end

    //第二段, 状态转移条件描述

    always @(*) begin

        case (state_c)

            IDLE:   begin

                if (idle2wr_reg) begin

                    state_n = WR_REG;

                end

                else if (idle2rd_reg) begin

                    state_n = RD_REG;

                end

                else if(idle2page_wr_reg)

                    state_n = PAGE_WR_REG;

                else if(idle2seq_rd_reg)

                    state_n = SEQ_RD_REG;

                else begin

                    state_n = IDLE;

                end

            end

            WR_REG: begin

                if (wr_reg2wr_wait) begin

                    state_n = WR_WAIT;

                end

                else begin

                    state_n = WR_REG;

                end

            end

            WR_WAIT:    begin

                if (wr_wait2wr_reg) begin

                    state_n = WR_REG;

                end

                else if (wr_wait2idle) begin

                    state_n = IDLE;

                end

                else begin

                    state_n = WR_WAIT;

                end

            end

            RD_REG: begin

                if (rd_reg2rd_wait) begin

                    state_n = RD_WAIT;

                end

                else begin

                    state_n = RD_REG;

                end

            end

            RD_WAIT:    begin

                if (rd_wait2rd_reg) begin

                    state_n = RD_REG;

                end

                else if (rd_wait2idle) begin

                    state_n = IDLE;

                end

                else begin

                    state_n = RD_WAIT;

                end

            end

            PAGE_WR_REG:

                begin

                    if(page_wr_reg2page_wr_wait)

                        state_n = PAGE_WR_WAIT;

                    else

                        state_n = PAGE_WR_REG;

                end

            PAGE_WR_WAIT:

                begin

                    if(page_wr_wait2page_wr_reg)

                        state_n = PAGE_WR_REG;

                    else if(page_wr_wait2idle)

                        state_n = IDLE;

                    else

                        state_n = PAGE_WR_WAIT;

                end

            SEQ_RD_REG:

                begin

                    if(seq_rd_reg2seq_rd_wait)

                        state_n = SEQ_RD_WAIT;

                    else

                        state_n = SEQ_RD_REG;

                end

            SEQ_RD_WAIT:

                begin

                    if(seq_rd_wait2seq_rd_reg)

                        state_n = SEQ_RD_REG;

                    else if(seq_rd_wait2idle)

                        state_n = IDLE;

                end

            default:state_n = IDLE;

        endcase

    end

    //状态转移条件描述

    assign idle2wr_reg   = state_c == IDLE && (key2); //

    assign idle2rd_reg   = state_c == IDLE && (0); //

    assign wr_reg2wr_wait    = state_c == WR_REG && (1); //

    assign wr_wait2wr_reg    = state_c == WR_WAIT && (done && cnt_alt < 3); //

    assign wr_wait2idle  = state_c == WR_WAIT && (done && cnt_alt == 3); //

    assign rd_reg2rd_wait    = state_c == RD_REG && (1); //

    assign rd_wait2rd_reg    = state_c == RD_WAIT && (done && cnt_alt < 4); //

    assign rd_wait2idle  = state_c == RD_WAIT && (done && cnt_alt == 4); //

    assign idle2page_wr_reg = state_c == IDLE && (key4);

    assign page_wr_reg2page_wr_wait = state_c == PAGE_WR_REG && (1);

    assign page_wr_wait2page_wr_reg = state_c == PAGE_WR_WAIT && (done && cnt_page < 14);

    assign page_wr_wait2idle = state_c == PAGE_WR_WAIT && (done && cnt_page == 14);

    assign idle2seq_rd_reg = state_c == IDLE && key3;

    assign seq_rd_reg2seq_rd_wait = state_c == SEQ_RD_REG && 1;

    assign seq_rd_wait2seq_rd_reg = state_c == SEQ_RD_WAIT && (done && cnt_seq < 13);

    assign seq_rd_wait2idle = state_c == SEQ_RD_WAIT && (done && cnt_seq == 13);

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        cnt_alt <= 0;

    end

else if(state_c == IDLE)

    cnt_alt <= 0;

else if(wr_reg2wr_wait || rd_reg2rd_wait)begin

        cnt_alt <= cnt_alt + 1'd1;

    end

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        cnt_page <= 0;

    end

else if(state_c == IDLE)begin

        cnt_page <= 0;

    end

else if(page_wr_reg2page_wr_wait)begin

        cnt_page <= cnt_page + 1'd1;

    end

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        cnt_seq <= 0;

    end

else if(state_c == IDLE)begin

        cnt_seq <= 0;

    end

else if(seq_rd_reg2seq_rd_wait)begin

        cnt_seq <= cnt_seq + 1'd1;

    end

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        cmd <= 0;

        wr_data <= 0;

        start  <= 0;

    end

else

    begin

        case(state_c)

            IDLE:

                begin

                    cmd <= 0;

                    wr_data <= 0;

                    start  <= 0;

                end

            WR_REG:

                begin

                    case(cnt_alt)

                        0:

                            begin

                                cmd <= 0;

                                wr_data <= 8'ha0;

                                start <= 1;

                            end

                        1:

                            begin

                                cmd <= 1;

                                wr_data <= 8'hf0;

                                start <= 1;

                            end

                        2:

                            begin

                                cmd <= 2;

                                wr_data <= q_fifo;

                                start <= 1;

                            end

                        default:;

                    endcase

                end

            WR_WAIT:

                begin

                    start <= 0;

                end

            RD_REG:

                begin

                    case(cnt_alt)

                        0:

                            begin

                                cmd <= 0;

                                wr_data <= 8'ha0;

                                start <= 1;

                            end

                        1:

                            begin

                                cmd <= 1;

                                wr_data <= 8'h02;

                                start <= 1;

                            end

                        2:

                            begin

                                cmd <= 0;

                                wr_data <= 8'ha1;

                                start <= 1;

                            end

                        3:

                            begin

                                cmd <= 3;

                                start <= 1;

                            end

                        default:;

                    endcase

                end

            RD_WAIT:

                begin

                    start <= 0;

                end

            PAGE_WR_REG:

                begin

                    case(cnt_page)

                        0:

                        begin

                            cmd <= 0;

                            wr_data <= 8'ha0;

                            start <= 1;

                        end

                        1:

                        begin

                            cmd <= 1;

                            wr_data <= 8'h02;

                            start <= 1;

                        end    

                        2:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hbb;

                            start <= 1;

                        end    

                        3:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hba;

                            start <= 1;

                        end    

                        4:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb9;

                            start <= 1;

                        end    

                        5:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb8;

                            start <= 1;

                        end    

                        6:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb7;

                            start <= 1;

                        end    

                        7:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb6;

                            start <= 1;

                        end    

                        8:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb5;

                            start <= 1;

                        end    

                        9:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb4;

                            start <= 1;

                        end    

                        10:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb3;

                            start <= 1;

                        end    

                        11:

                        begin

                            cmd <= 1;

                            wr_data <= 8'hb2;

                            start <= 1;

                        end    

                        12:

                            begin

                                cmd <= 1;

                                wr_data <= 8'hb1;

                                start <= 1;

                            end    

                        13:

                            begin

                                cmd <= 2;

                                wr_data <= 8'hb0;

                                start <= 1;

                            end                                        

                        default:;

                    endcase

                end

            PAGE_WR_WAIT:

                start = 0;

            SEQ_RD_REG:

                begin

                    case(cnt_seq)

                        0:

                            begin

                                cmd <= 0;

                                wr_data <= 8'ha0;

                                start <= 1;

                            end

                        1:

                            begin

                                cmd <= 1;

                                wr_data <= 8'h02;

                                start <= 1;

                            end

                        2:

                            begin

                                cmd <= 0;

                                wr_data <= 8'ha1;

                                start <= 1;

                            end

                        3:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        4:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        5:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        6:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        7:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        8:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        9:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        10:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        11:

                            begin

                                cmd <= 4;

                                start <= 1;

                            end

                        12:

                            begin

                                cmd <= 3;

                                start <= 1;

                            end

                        default:;

                    endcase

                end

            SEQ_RD_WAIT:

                start <= 0;

            default:;

        endcase

    end

end //always end

assign wr_done = wr_wait2idle || page_wr_wait2idle;

assign rd_done = rd_wait2idle || seq_rd_wait2idle || (seq_rd_wait2seq_rd_reg&&cnt_seq>3);

endmodule

需要注意的是,由于博主使用的开发板上只有四个按键,其中key1用于复位,key2用于写FIFO的读出使能,实现单字节写、单字节读、页写、顺序读四种操作请求只能对key3、key4进行时分复用,被注意以下转移条件需要不同请求下改变(进行相应操作时把&&后面的值换成key,不用的时候写0):

assign idle2wr_reg  = state_c == IDLE && (key2); //单字节写

assign idle2rd_reg  = state_c == IDLE && (0); //单字节读

assign idle2page_wr_reg = state_c == IDLE && (key4);//页写

assign idle2seq_rd_reg = state_c == IDLE && key3;//顺序读

串口模块 

串口接收和发送模块比较简单,这里不再分析,就是选用115200的波特率,实现PC端和FPGA一个字节数据的交互:

/* ================================================ *\

Filename : uart_rx_byte.v

Author : Strauss

@Time : 2023-11-09 13:57:09

Revision & Function: 1.0

\* ================================================ */

module uart_rx_byte (clk, rst_n, rxd, dout, rx_done);

 input clk, rst_n, rxd;

 output reg [7:0]dout;

 output reg rx_done;

//Parameter Declarations

parameter SYS_CLK = 50_000_000;

parameter SECOND = 49_999_999;

parameter BPS_CLK = 115200;

//Internal wire/reg declarations

reg [19:0]cnt_bps;

reg [3:0]cnt_bit;

reg [9:0]tmp_data;

reg rx_en;

reg rxd_syn1;

reg rxd_syn2;

reg [1:0]rxd_r;

wire [12:0]MAX_BPS;

//Logic Description

assign MAX_BPS = SYS_CLK/BPS_CLK;

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        rxd_r <= 2'b11;

    end

else begin

        rxd_r <= {rxd_r[0], rxd};

    end

end //always end

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)begin

        rx_en <= 0;

    end

 else if(!rxd_r[1])begin

        rx_en <= 1'd1;

    end

 else if(rx_done)

    rx_en <= 0;

 else begin

        rx_en <= rx_en;

    end

end //always end

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)begin

        cnt_bps <= 0;

    end

 else if(rx_en)

    begin

        if(cnt_bps >= MAX_BPS-1)

            cnt_bps <= 0;

        else

            cnt_bps <= cnt_bps + 1'd1;

    end

 else

    cnt_bps <= 0;

end //always end

 always@(posedge clk or negedge rst_n)begin

  if(!rst_n)begin

        cnt_bit <= 0;

    end

  else if(cnt_bps >= MAX_BPS-1)

    begin

        if(cnt_bit == 9)

            cnt_bit <= 0;

        else

            cnt_bit <= cnt_bit + 1'd1;

    end

  else begin

        cnt_bit <= cnt_bit;

    end

 end //always end

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)begin

        tmp_data <= 0;

    end

 else if(rx_en && cnt_bps == MAX_BPS/2)

            tmp_data[cnt_bit] <= rxd_r[1];

 else

    tmp_data <= tmp_data;

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        dout <= 0;

        rx_done <= 0;

end

else if((cnt_bit == 9) && (cnt_bps >= MAX_BPS - 1) && (!tmp_data[0]) && (tmp_data[9]))begin

        dout <= tmp_data[8:1];

        rx_done <= 1;

end

else begin

        dout = dout;

        rx_done = 0;

    end

end //always end

endmodule

module uart_tx_byte(clk, rst_n, d_in, start, txd, tx_done, tx_loading);

 input clk, rst_n;

 input [7:0]d_in;

 input start;

 output reg txd, tx_done, tx_loading;

//Parameter Declarations

parameter SYS_CLK = 50_000_000;

parameter BPS_CLK = 115200;

//Internal wire/reg declarations

reg [19:0]cnt_bps;

reg [3:0]cnt_bit;

wire [12:0]MAX_BPS;

//Module instantiations , self-build module

//Logic Description

assign MAX_BPS = SYS_CLK/BPS_CLK;

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)begin

        cnt_bps <= 0;

    end

 else if(tx_loading)

    begin

        if(cnt_bps >= MAX_BPS)

            cnt_bps <= 0;

        else

            cnt_bps <= cnt_bps + 1'd1;

    end

 else

    cnt_bps <= 0;

end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        tx_loading <= 0;

    end

else if(start)

        tx_loading <= 1'd1;    

else if(cnt_bit == 9&& cnt_bps >= MAX_BPS)begin

        tx_loading <= 0;

    end

else

    tx_loading <= tx_loading;

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        cnt_bit <= 0;

    end

else if(cnt_bps >= MAX_BPS)begin

        if(cnt_bit == 9)

            cnt_bit <= 0;

        else

            cnt_bit <= cnt_bit + 1'd1;

    end

else begin

        cnt_bit <= cnt_bit;

    end

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        txd <= 1'd1;

    end

else if(tx_loading)

    begin

        case(cnt_bit)

        0:txd <= 0;

        1:txd <= d_in[0];

        2:txd <= d_in[1];

        3:txd <= d_in[2];

        4:txd <= d_in[3];

        5:txd <= d_in[4];

        6:txd <= d_in[5];

        7:txd <= d_in[6];

        8:txd <= d_in[7];

        9:txd <= 1;

        default: txd <= 1;

        endcase

    end

else

    txd <= 1'b1;

end //always end

always@(posedge clk or negedge rst_n)begin

if(!rst_n)

        tx_done <= 0;

else if(cnt_bit == 9 && cnt_bps >= MAX_BPS)

        tx_done <= 1'd1;

else

        tx_done <=0;

end //always end

endmodule

按键消抖模块

/* ================================================ *\

Filename : key.v

Author : Strauss

@Time : 2023-12-04 21:09:36

Revision & Function: 1.0

\* ================================================ */

module key(clk, rst_n, key_in, key_out);

input clk, rst_n;

input [3-1:0]key_in;

output reg [3-1:0]key_out;

//Parameter Declarations

parameter width = 3;

parameter SECOND = 50_000_000;

//Internal wire/reg declarations

reg [width-1:0]key_syn1;

reg [width-1:0]key_syn2;

wire [width-1:0]key_pos;

wire [width-1:0]key_neg;

localparam //独热码定义状态参数

IDLE = "IDLE",

PFIL = "PFIL",

LOW = "LOW",

RFIL = "RFIL";

 reg [31:0] current_state, next_state; //状态变量声明

 //跳转条件定义

 wire ILDE2PFIL;

 wire PFIL2IDLE;

 wire PFIL2LOW;

 wire LOW2RFIL;

 wire RFIL2IDLE;

//Module instantiations , self-build module

//Logic Description

always@(posedge clk or negedge rst_n)begin

if(!rst_n)begin

        key_syn1 <= {width{1'd1}};

        key_syn2 <= {width{1'd1}};

    end

else begin

        key_syn1 <= key_in;

        key_syn2 <= key_syn1;

    end

end

assign key_pos = key_syn1 & ~key_syn2;

assign key_neg = ~key_syn1 & key_syn2;

reg [19:0] cnt_fil ; //Counter

wire add_cnt_fil ; //Counter Enable

wire end_cnt_fil ; //Counter Reset

always@(posedge clk or negedge rst_n)begin

 if(!rst_n)

    cnt_fil <= 'd0;

 else if(add_cnt_fil)begin

    if(end_cnt_fil)begin

    cnt_fil <= 'd0;

    end

 else begin

    cnt_fil <= cnt_fil + 1'b1;

    end

 end

 else begin

    cnt_fil <= cnt_fil;

    end

end

assign add_cnt_fil = current_state==PFIL||current_state==RFIL;

assign end_cnt_fil = add_cnt_fil && cnt_fil >= SECOND/50-1;

//三段式状态机

//状态转移

always @(posedge clk or negedge rst_n)begin

 if(!rst_n)begin

    current_state <= IDLE;

 end

 else begin

    current_state <= next_state;

 end

end //always end

//组合逻辑定义状态转移

always@(*)begin

 case(current_state)

    IDLE :

           if(ILDE2PFIL)

                next_state = PFIL;

            else

                next_state = current_state;

    PFIL :

           if(PFIL2IDLE)

                next_state = IDLE;

           else if(PFIL2LOW)

                next_state = LOW;

            else

                next_state = current_state;

    LOW :

           if(LOW2RFIL)

                next_state = RFIL;

            else

                next_state = current_state;

    RFIL :

           if(RFIL2IDLE)

                next_state = IDLE;

            else

                next_state = current_state;

    default: begin

        next_state = IDLE;

    end

 endcase

end //always end

//状态转移条件

assign ILDE2PFIL = current_state == IDLE && (key_neg);//

assign PFIL2IDLE = current_state == PFIL && (key_pos);//

assign PFIL2LOW  = current_state == PFIL && (end_cnt_fil);//

assign LOW2RFIL  = current_state == LOW && (key_pos);//

assign RFIL2IDLE = current_state == RFIL && (end_cnt_fil);//

//第三段,状态机输出

always@(posedge clk or negedge rst_n)

    begin

        if(!rst_n)  

            key_out <= {width{1'd0}};

        else if(PFIL2LOW)

            key_out <= ~key_syn2;

        else

            key_out <= 0;

    end

endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值