FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)

news2024/11/15 10:24:14

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)

  • 工程目的
  • I2C 单字节写操作
  • I2C 随机读操作
  • EEPROM 字节读写整体框图
  • 模块功能简介
  • I2C 驱动模块
    • 模块框图
    • 跨时钟域处理
    • 状态转移图
  • 波形分析
    • 单字节写操作局部波形图(一)
    • 单字节写操作局部波形图(二)
      • 第一部分:输入信号说明
    • i2c_clk 信号波形图
      • 第二部分:时钟信号计数器 cnt_clk 和输出信号 i2c_clk 的设计与实现
      • 第三部分:状态机相关信号波形的设计与实现
  • 下面开始随机读操作部分的讲解。
  • 数据收发模块

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(3)

工程目的

使用按键控制数据写入或读出 EEPROM。使用写控制按键向 EEPROM 中写入数据 1-10 共 10 字节数据,使用读控制按键读出之前写入到 EEPROM 的数据,并将读出的数据在数码管上显示出来。

I2C 单字节写操作

在这里插入图片描述
单字节写操作时序图(单字节存储地址)
在这里插入图片描述
单字节写操作时序图(2 字节存储地址)

参照时序图,列出单字节写操作流程如下:
(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
(3) 先向从机写入高 8 位地址,且高位在前低位在后;
(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2字节地址,跳转到步骤(6);
(5) 按高位在前低位在后的顺序写入单字节存储地址;
(6) 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入;
(7) 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据写入完成。

I2C 随机读操作

在这里插入图片描述
随机读操作时序图(单字节存储地址)

在这里插入图片描述

随机读操作时序图(2 字节存储地址)

参照时序图,列出页写时序操作流程如下:
(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
(3) 先向从机写入高 8 位地址,且高位在前低位在后;
(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2字节地址,跳转到步骤(6);
(5) 按高位在前低位在后的顺序写入单字节存储地址;
(6) 地址写入完成,主机接收到从机回传的应答信号后,主机再次向从机发送一个起始信号;

EEPROM 字节读写整体框图

在这里插入图片描述

模块功能简介

在这里插入图片描述

I2C 驱动模块

模块框图

I2C 驱动模块的主要功能是按照 I2C 协议对 EERPROM 存储芯片执行数据读写操作。

在这里插入图片描述
I2C 驱动模块框图和输入输出端口简介
在这里插入图片描述
在这里插入图片描述

wr_en、rd_en 为写使能信号,由数据收发模块生成并传入,高电平有效;
i2c_start 信号为单字节数据读/写开始信号, i2c_start 信号同时传入的还有数据存储地址 byte_addr 和待写入字节数据wr_data;
写使能 wr_en 和 i2c_start 信号同时有效,模块执行单字节数据写操作,按照数据存储地址 byte_addr,向 EEPROM 对应地址写入数据 wr_data;
读使能信号 rd_en 和i2c_start 信号同时有效,模块执行单字节数据读操作,按照数据存储地址 byte_addr 读取EEPROM 对应地址中的数据;

I2C 设备存储地址有单字节和 2 字节两种,为了应对这一情况,我们向模块输入 addr_num 信号,当信号为低电平时,表示 I2C 设备存储地址为单字节,在进行数据读写操作时只写入数据存储地址 byte_addr 的低 8 位;当信号为高电平时,表示 I2C 设备存储地址为 2 字节,在进行数据读写操作时要写入数据存储地址 byte_addr 的全部 16 位。

为了后面的通用性,我们这是使用了单字节操作,如果想实现数据的连续读写,可以持续拉高读写使能信号,同时要保证开始信号的使能,这样就能够实现数据连续读取或写入。

跨时钟域处理

输出信号中,i2c_clk 是本模块的工作时钟,由系统时钟 sys_clk 分频而来,它的时钟频率为串行时钟 i2c_scl 频率的 4 倍,时钟信号 i2c_clk 要传入数据收发模块(i2c_rw_data)作为模块的工作时钟;输出给数据收发模块(i2c_rw_data)的单字节数据读/写结束信号i2c_end,高电平有效,表示一次单字节数据读/写操作完成;rd_data 信号表示自 EEPROM读出的单字节单字节数据,输出至数据收发模块(i2c_rw_data);i2c_scl、i2c_sda 分别是串行时钟信号和串行数据信号,由模块产生传入 EEPROM 存储芯片。

状态转移图

参照 I2C 设备单字节写操作和随机读操作的操作流程
在这里插入图片描述
系统上电后,状态机处于 IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start 后,状态机跳转到 START_1(起始状态);FPGA 向 EEPROM 存储芯片发送起始信号;随后状态机跳转到 SEND_D_ADDR(发送器件地址状态),在此状态下向 EEPROM 存储芯片写入控制指令,控制指令高 7 位为器件地址,最低位为读写控制字,写入“0”,表示执行写操作;控制指令写入完毕后,状态机跳转ACK_1(应答状态)。

在 ACK_1(应答状态)状态下,要根据存储地址字节数进行不同状态的跳转。当 FPGA接收到 EEPROM 回 传 的 应 答 信 号 且 存 储 地 址 字 节 为 2 字 节 , 状 态 机 跳 转 到SEND_B_ADDR_H(发送高字节地址状态),将存储地址的高 8 位写入 EEPROM,写入完成后,状态机跳转到 ACK_2(应答状态);FPGA 接收到应答信号后,状态机跳转到SEND_B_ADDR_L(发送低字节地址状态);当 FPGA 接收到 EEPROM 回传的应答信号且存储地址字节为单字节,状态机状态机直接跳转到SEND_B_ADDR_L(发送低字节地址状态);在此状态低 8 位存储地址或单字节存储地址写入完成后,状态机跳转到 ACK_3(应答状态)。

在 ACK_3(应答状态)状态下,要根据读/写使能信号做不同的状态跳转。当 FPGA 接收到应答信号且写使能信号有效,状态机跳转到 WR_DATA(写数据状态);在写数据状态,向 EEPROM 写入单字节数据后,状态机跳转到 ACK_4(应答状态);待 FPGA 接收到有效应答信号后,状态机跳转到 STOP(停止状态);当 FPGA 接收到应答信号且读使能信号有效,状态机跳转到 START_2(起始状态);再次向EEPROM 写入起始信号,状态跳转到SEND_RD_ADDR(发送读控制状态);再次向 EEPROM 写入控制字节,高 7 位器件地址不变,读写控制位写入“1”,表示进行读操作,控制字节写入完毕后,状态机跳转到ACK_5(应答状态);待 FPGA 接收到有效应答信号后,状态机跳转到 RD_DATA(读数据状态);在 RD_DATA(读数据状态)状态,EEPROM 向 FPGA 发送存储地址对应存储单元下的单字节数据,待数据读取完成户,状态机跳转到 N_ACK(无应答状态),在此状态下向EEPROM 写入一个时钟的高电平,表示数据读取完成,随后状态机跳转到 STOP(停止状态)。在 STOP(停止状态)状态,FPGA 向 EEPROM 发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回 IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

波形分析

单字节写操作局部波形图(一)

单字节写操作局部波形图(一)

单字节写操作局部波形图(二)

在这里插入图片描述

第一部分:输入信号说明

本模块的输入信号有 8 路,其中 7 路信号与单字节写操作有关。系统时钟信号 sys_clk和复位信号 sys_rst_n 不必多说,这是模块正常工作必不可少的;写使能信号 wr_en、 单字节数据读/写开始信号 i2c_start,只有在两信号同时有效时,模块才会执行单字节数据写操作,若 wr_en 有效时,i2c_start 信号 n 次有效输入,可以实现 n 个字节的连续写操作;addr_num 信号为存储地址字节数标志信号,赋值为 0 时,表示 I2C 设备存储地址为单字节,赋值为 1 时,表示 2C 设备存储地址为 2 字节,本实验使用的 EEPROM 存储芯片的存储地址位 2 字节,此信号恒为高电平;信号 byte_addr 为存储地址;wr_data 表示要写入该地址的单字节数据。

i2c_clk 信号波形图

在这里插入图片描述

第二部分:时钟信号计数器 cnt_clk 和输出信号 i2c_clk 的设计与实现

串行时钟 scl 的时钟频率为 250KHz,我们要生成的新时钟 i2c_clk 的频率要是 scl 的 4倍,之所以这样是为了后面更好的生成 scl 和 sda,所以 i2c_clk 的时钟频率为 1MHz。经计算,cnt_clk 要在 0-24 内循环计数,每个系统时钟周期自加 1;cnt_clk 每计完一个周期,i2c_clk 进行一次取反,最后得到 i2c_clk 为频率 1MHz 的时钟,本模块中其他信号的生成都以此信号为同步时钟。

第三部分:状态机相关信号波形的设计与实现

前文理论部分提到,输出至 EEPROM 的串行时钟 scl 与串行数据 sda 只有在进行数据读写操作时有效,其他时刻始终保持高电平。由前文状态机相关讲解可知,除 IDLE(初始状态)状态之外的其他状态均属于数据读写操作的有效部分,所以声明一个使能信号cnt_i2c_clk_en,在除 IDLE(初始状态)状态之外的其他状态保持有效高电平,作为 I2C 数据读写操作使能信号。

我们使用 50MHz 系统时钟生成了 1MHz 时钟 i2c_clk,但输出至 EEPROM 的串行时钟scl 的时钟频率为 250KHz,我们声明时钟信号计数器 cnt_i2c_clk,作为分频计数器,对时钟 i2c_clk 时钟信号进行计数,初值为 0,计数范围为 0-3,计数时钟为 i2c_clk 时钟,每个时钟周期自加 1,实现时钟 i2c_clk 信号的 4 分频,生成串行时钟 scl。同时计数器cnt_i2c_clk 也可作为生成串行数据 sda 的约束条件,以及状态机跳转条件。

计数器 cnt_i2c_clk 循环计数一个周期,对应串行时钟 scl 的 1 个时钟周期以及串行数据 sda 的 1 位数据保持时间,进行数据读写操作时,传输的指令、地址以及数据,位宽为固定的 8 位数据,我们声明一个比特计数器 cnt_bit,对计数器 cnt_i2c_clk 的计数周期进行计数,可以辅助串行数据 sda 的生成,同时作为状态机状态跳转的约束条件。

输出的串行数据 sda 作为一个双向端口,主机通过它向从机发送控制指令、地址以及数据,接收从机回传的应答信号和读取数据。回传给主机的应答信号是实现状态机跳转的条件之一。声明信号 sda_in 作为串行数据 sda 缓存,声明 ack 信号作为应答信号,ack 信号只在状态机处于各应答状态时由 sda_in 信号赋值,此时为从机回传的应答信号,其他状态时钟保持高电平。

状态机状态跳转的各约束条件均已介绍完毕,声明状态变量 state,结合各约束信号,单字节写操作状态机跳转流程如下:
系统上电后,状态机处于 IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start 后,状态机跳转到 START_1(起始状态),同时使能信号cnt_i2c_clk_en 拉高、计数器 cnt_i2c_clk、cnt_bit 开始计数,开始数据读写操作;
在 START_1(起始状态)状态保持一个串行时钟周期,期间 FPGA 向 EEPROM 存储芯片发送起始信号,一个时钟周期过后,计数器 cnt_ i2c_clk 完成一个周期计数,计数器 cnt_i2c_clk 计数到最大值 3,状态机跳转到 SEND_D_ADDR(发送器件地址状态);
计数器 cnt_i2c_clk、cnt_bit 同时归 0,重新计数,计数器 cnt_i2c_clk 每计完一个周期,cnt_bit 自加 1,当计数器 cnt_i2c_clk 完成 8 个计数周期后,cnt_bit 计数到 7,实现 8 个比特计数,器件 FPGA 按照时序向 EEPROM 存储芯片写入控制指令,控制指令高 7 位为器件地址,最低位为读写控制字,写入“0”,表示执行写操作。当计数器 cnt_ i2c_clk 计数到最大值 3、cnt_bit 计数到 7,两计数器同时归 0,状态机跳转到转到 ACK_1(应答状态);

在 ACK_1(应答状态)状态下,计数器 cnt_i2c_clk、cnt_bit 重新计数,当计数器 cnt_i2c_clk 计 数 到 最 大 值 3 , 且 应 答 信 号 ack 为 有 效 的 低电平,状态机跳转到SEND_B_ADDR_H(发送高字节地址状态),两计数器清 0;此状态下,FPGA 将存储地址的高 8 位按时序写入 EEPROM,当计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_2(应答状态), 两计数器清 0;

ACK_2 状态下,当计数器 cnt_ i2c_clk 计数到 3,且应答信号 ack 为有效的低电平,状态机跳转到 SEND_B_ADDR_L(发送低字节地址状态) ,两计数器清 0;在此状态下,低 8 位存储地址按时序写入 EEPROM,计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_3(应答状态);
在 ACK_3(应答状态)状态下,当 cnt_ i2c_clk 计数 3、应答信号 ack 有效,且写使能信号 wr_en 有效,状态机跳转到 WR_DATA(写数据状态);
在写数据状态,按时序向 EEPROM 写入单字节数据,计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_4(应答状态);
在 ACK_4(应答状态)状态下,当 cnt_ i2c_clk 计数 3、应答信号 ack 有效,状态机跳转到 STOP(停止状态)状态;
在 STOP(停止状态)状态,FPGA 向 EEPROM 发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回 IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

状态机相关信号波形如下
在这里插入图片描述
状态机相关信号波形图
在这里插入图片描述
状态机相关信号波形图

第四部分:输出串行时钟 i2c_scl、串行数据信号 i2c_sda 及相关信号的波形设计与实现
串口数据 sda 端口作为一个双向端口,在单字节读取操作中,主机只在除应答状态之外的其他状态拥有它的控制权,在应答状态下主机只能接收由从机通过 sda 传入的应答信号。声明使能信号 sda_en,只在除应答状态之外的其他状态赋值为有效的高电平,sda_en有效时,主机拥有对 sda 的控制权。

声明 i2c_sda_reg 作为输出 i2c_sda 信号的数据缓存,在 sda_en 有效时,将 i2c_sda_reg的值赋值给输出串口数据 i2c_sda,sda_en 无效时,输出串口数据 i2c_sda 为高阻态,主机放弃其控制权,接收其传入的应答信号。

i2c_sda_reg 在使能信号 sda_en 无效时始终保持高电平,在使能 sda_en 有效时,在状态机对应状态下,以计数器 cnt_ i2c_clk、cnt_bit 为约束条件,对应写入起始信号、控制指令、存储地址、写入数据、停止信号。

对于输出的串行时钟 i2c_clk,由 I2C 通讯协议可知,I2C 设备只在串行时钟为高电平时进行数据采集,在串行时钟低电平时实现串行数据更新。我们使用计数器 cnt_ i2c_clk、cnt_bit 以及状态变量 state 为约束条件,结合 I2C 通讯协议,生成满足时序要求的输出串行时钟i2c_clk。

输出串行时钟 i2c_scl、串行数据信号 i2c_sda 及相关信号的波形图如下。
在这里插入图片描述
i2c_scl、i2c_sda 及相关信号波形图
在这里插入图片描述
i2c_scl、i2c_sda 及相关信号波形图

单字节写操作部分涉及的各信号波形的设计与实现讲解完毕,下面开始随机读操作部分的讲解。单字节写操作和随机读操作所涉及的各信号大体相同,在随机读操作,我们只讲解差别较大之处,两操作相同或相似之处不再说明,读者可回顾单字节写操作部分的介绍。

下面开始随机读操作部分的讲解。

在这里插入图片描述
状态机相关信号波形图
在这里插入图片描述
状态机相关信号波形图
在这里插入图片描述
状态机相关信号波形图
在这里插入图片描述
i2c_scl、i2c_sda、rd_data 及相关信号波形图
在这里插入图片描述
i2c_scl、i2c_sda、rd_data 及相关信号波形图
在这里插入图片描述
i2c_scl、i2c_sda、rd_data 及相关信号波形图

驱动模块参考代码(i2c_ctrl.v)

parameter CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2’d3 ;
解释: 50000000/250000=200, 200 / (222)200 / (222)

`timescale  1ns/1ns

module  i2c_ctrl
#(
    parameter   DEVICE_ADDR     =   7'b1010_000     ,   //i2c设备地址
    parameter   SYS_CLK_FREQ    =   26'd50_000_000  ,   //输入系统时钟频率
    parameter   SCL_FREQ        =   18'd250_000         //i2c设备scl时钟频率
)
(
    input   wire            sys_clk     ,   //输入系统时钟,50MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire            wr_en       ,   //输入写使能信号
    input   wire            rd_en       ,   //输入读使能信号
    input   wire            i2c_start   ,   //输入i2c触发信号
    input   wire            addr_num    ,   //输入i2c字节地址字节数
    input   wire    [15:0]  byte_addr   ,   //输入i2c字节地址
    input   wire    [7:0]   wr_data     ,   //输入i2c设备数据

    output  reg             i2c_clk     ,   //i2c驱动时钟
    output  reg             i2c_end     ,   //i2c一次读/写操作完成
    output  reg     [7:0]   rd_data     ,   //输出i2c设备读取数据
    output  reg             i2c_scl     ,   //输出至i2c设备的串行时钟信号scl
    inout   wire            i2c_sda         //输出至i2c设备的串行数据信号sda
);

//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
// parameter define
parameter   CNT_CLK_MAX     =   (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3   ;   //cnt_clk计数器计数最大值

parameter   CNT_START_MAX   =   8'd100; //cnt_start计数器计数最大值

parameter   IDLE            =   4'd00,  //初始状态
            START_1         =   4'd01,  //开始状态1
            SEND_D_ADDR     =   4'd02,  //设备地址写入状态 + 控制写
            ACK_1           =   4'd03,  //应答状态1
            SEND_B_ADDR_H   =   4'd04,  //字节地址高八位写入状态
            ACK_2           =   4'd05,  //应答状态2
            SEND_B_ADDR_L   =   4'd06,  //字节地址低八位写入状态
            ACK_3           =   4'd07,  //应答状态3
            WR_DATA         =   4'd08,  //写数据状态
            ACK_4           =   4'd09,  //应答状态4
            START_2         =   4'd10,  //开始状态2
            SEND_RD_ADDR    =   4'd11,  //设备地址写入状态 + 控制读
            ACK_5           =   4'd12,  //应答状态5
            RD_DATA         =   4'd13,  //读数据状态
            N_ACK           =   4'd14,  //非应答状态
            STOP            =   4'd15;  //结束状态

// wire  define
wire            sda_in          ;   //sda输入数据寄存
wire            sda_en          ;   //sda数据写入使能信号

// reg   define
reg     [7:0]   cnt_clk         ;   //系统时钟计数器,控制生成clk_i2c时钟信号
reg     [3:0]   state           ;   //状态机状态
reg             cnt_i2c_clk_en  ;   //cnt_i2c_clk计数器使能信号
reg     [1:0]   cnt_i2c_clk     ;   //clk_i2c时钟计数器,控制生成cnt_bit信号
reg     [2:0]   cnt_bit         ;   //sda比特计数器
reg             ack             ;   //应答信号
reg             i2c_sda_reg     ;   //sda数据缓存
reg     [7:0]   rd_data_reg     ;   //自i2c设备读出数据

//************************************************************************//
//******************************* Main Code ******************************//
//************************************************************************//
// cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  8'd0;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        cnt_clk <=  8'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

// i2c_clk:i2c驱动时钟
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_clk <=  1'b1;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        i2c_clk <=  ~i2c_clk;

// cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk_en  <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        cnt_i2c_clk_en  <=  1'b0;
    else    if(i2c_start == 1'b1)
        cnt_i2c_clk_en  <=  1'b1;

// cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk <=  2'd0;
    else    if(cnt_i2c_clk_en == 1'b1)
        cnt_i2c_clk <=  cnt_i2c_clk + 1'b1;

// cnt_bit:sda比特计数器
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if((state == IDLE) || (state == START_1) || (state == START_2)
                || (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
                || (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
        cnt_bit <=  3'd0;
    else    if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        cnt_bit <=  3'd0;
    else    if((cnt_i2c_clk == 2'd3) && (state != IDLE))
        cnt_bit <=  cnt_bit + 1'b1;

// state:状态机状态跳转
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else    case(state)
        IDLE:
            if(i2c_start == 1'b1)
                state   <=  START_1;
            else
                state   <=  state;
        START_1:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_D_ADDR;
            else
                state   <=  state;
        SEND_D_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_1;
            else
                state   <=  state;
        ACK_1:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(addr_num == 1'b1)
                        state   <=  SEND_B_ADDR_H;
                    else
                        state   <=  SEND_B_ADDR_L;
                end
             else
                state   <=  state;
        SEND_B_ADDR_H:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_2;
            else
                state   <=  state;
        ACK_2:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  SEND_B_ADDR_L;
            else
                state   <=  state;
        SEND_B_ADDR_L:
            if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
                state   <=  ACK_3;
            else
                state   <=  state;
        ACK_3:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(wr_en == 1'b1)
                        state   <=  WR_DATA;
                    else    if(rd_en == 1'b1)
                        state   <=  START_2;
                    else
                        state   <=  state;
                end
             else
                state   <=  state;
        WR_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_4;
            else
                state   <=  state;
        ACK_4:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  STOP;
            else
                state   <=  state;
        START_2:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_RD_ADDR;
            else
                state   <=  state;
        SEND_RD_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_5;
            else
                state   <=  state;
        ACK_5:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  RD_DATA;
            else
                state   <=  state;
        RD_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  N_ACK;
            else
                state   <=  state;
        N_ACK:
            if(cnt_i2c_clk == 3)
                state   <=  STOP;
            else
                state   <=  state;
        STOP:
            if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
                state   <=  IDLE;
            else
                state   <=  state;
        default:    state   <=  IDLE;
    endcase

// ack:应答信号
always@(*)
    case    (state)
        IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
        WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
            ack <=  1'b1;
        ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
            if(cnt_i2c_clk == 2'd0)
                ack <=  sda_in;
            else
                ack <=  ack;
        default:    ack <=  1'b1;
    endcase

// i2c_scl:输出至i2c设备的串行时钟信号scl
always@(*)
    case    (state)
        IDLE:
            i2c_scl <=  1'b1;
        START_1:
            if(cnt_i2c_clk == 2'd3)
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
        ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
            if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
                i2c_scl <=  1'b1;
            else
                i2c_scl <=  1'b0;
        STOP:
            if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        default:    i2c_scl <=  1'b1;
    endcase

// i2c_sda_reg:sda数据缓存
always@(*)
    case    (state)
        IDLE:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  8'd0;
            end
        START_1:
            if(cnt_i2c_clk <= 2'd0)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_D_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b0;
        ACK_1:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_H:
            i2c_sda_reg <=  byte_addr[15 - cnt_bit];
        ACK_2:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_L:
            i2c_sda_reg <=  byte_addr[7 - cnt_bit];
        ACK_3:
            i2c_sda_reg <=  1'b1;
        WR_DATA:
            i2c_sda_reg <=  wr_data[7 - cnt_bit];
        ACK_4:
            i2c_sda_reg <=  1'b1;
        START_2:
            if(cnt_i2c_clk <= 2'd1)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_RD_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b1;
        ACK_5:
            i2c_sda_reg <=  1'b1;
        RD_DATA:
            if(cnt_i2c_clk  == 2'd2)
                rd_data_reg[7 - cnt_bit]    <=  sda_in;
            else
                rd_data_reg <=  rd_data_reg;
        N_ACK:
            i2c_sda_reg <=  1'b1;
        STOP:
            if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
                i2c_sda_reg <=  1'b0;
            else
                i2c_sda_reg <=  1'b1;
        default:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  rd_data_reg;
            end
    endcase

// rd_data:自i2c设备读出数据
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_data <=  8'd0;
    else    if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        rd_data <=  rd_data_reg;

// i2c_end:一次读/写结束信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_end <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        i2c_end <=  1'b1;
    else
        i2c_end <=  1'b0;

// sda_in:sda输入数据寄存
assign  sda_in = i2c_sda;
// sda_en:sda数据写入使能信号
assign  sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
                    || (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
                    ? 1'b0 : 1'b1;
// i2c_sda:输出至i2c设备的串行数据信号sda
assign  i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;

endmodule

数据收发模块

数据收发模块的主要功能是:为 I2C 驱动模块提供读/写数据存储地址、待写入数据以及作为 EEPROM 读出数据缓存,待数据读取完成后将读出数据发送给数码管显示模块进行 数据显示。数据收发模块框图及模块输入输出端口简介
在这里插入图片描述
I2C 数据收发模块输入输出信号简介
在这里插入图片描述
输入信号中,有 2 路时钟信号和 1 路复位信号,sys_clk 为系统时钟信号,在数据收发模块中用于采集读/写触发信号 read 和 write,2 路触发信号均由外部按键输出,经消抖处理后传入本模块,消抖模块使用的时钟信号为与 sys_clk 相同的系统时钟,所以读/写触发信号的采集要使用系统时钟;i2c_clk 为模块工作时钟,由 I2C 驱动模块生成并传入,是存储地址、读/写数据以及使能信号的同步时钟,因为 I2C 模块的工作时钟为 i2c_clk 时钟信号,两模块工作时钟相同,不会出现时钟不同引起时序问题;复位信号 sys_rst_n,低电平有效,不必多说;i2c_end 为单字节数据读/写接数信号,由 I2C 驱动模块产生并传入,告知数据生成模块单字节数据读/写操作完成。若连续读/写多字节数据,此信号可作为存储地址、写数据的更新标志;rd_data 为 I2C 驱动模块传入的数据信号,表示由 EEPROM 读出的字节数据。

输出信号中, rd_en、wr_en 分别为读写使能信号,生成后传入 I2C 驱动模块,作为I2C 驱动模块读/写操作的判断标志;i2c_start 是单字节数据读/写开始信号,作为 I2C 驱动模块单字节读/写操作开始的标志信号;byte_addr 为读写数据存储地址;wr_data 为待写入EEPROM 的字节数据;fifo_rd_data 为自 EEPROM 读出的字节数据,要发送到数码换显示模块在数码管显示出来。

跨时钟域处理
在这里插入图片描述
写有效信号 wr_valid 拉高后,工作时钟 i2c_clk 上升沿采集到 wr_valid 高电平,拉高写使能信号 wr_en,告知 I2C 驱动模块接下来要进行数据写操作。在此次实验我们要连续写入 10 字节数据,所以写使能信号 wr_en 要保持 10 次数据写操作的有效时间,在这一时间段我们要输出 10 次有效的 i2c_start 信号,在接收到第 10 次 i2c_end 信号后,表示 10 字节数据均已写入完成,将写使能信号 rw_en 拉低,完成 10 字节数据的连续写入。

要实现这一操作我们需要声明 2 个变量,声明字节计数器 wr_i2c_data_num 对已写入字节进行计数;由数据手册可知,两次相邻的读/写操作之间需要一定的时间间隔,以保证上一次读/写操作完成,所以声明计数器 cnt_start,对相邻读/写操作时间间隔进行计数。

采集到写有效信号 wr_valid 为高电平,拉高写使能信号 wr_en,计数器 cnt_wait、wr_i2c_data_num 均由 0 开始计数,每一个工作时钟周期 cnt_wait 自加 1,计数到最大值1499,i2c_start 保持一个工作时钟的高电平,同时 cnt_wait 归 0,重新开始计数;I2C 驱动模块接收到有效的 i2c_start 信号后,向 EEPROM 写入单字节数据,传回 i2c_end 信号,表示一次单字节写操作完毕,计数器 wr_i2c_data_num 加 1;计数器 cnt_start 完成 10 次循环计数,i2c_start 拉高 10 次,在接收到第 10 次有效的 i2c_end 信号后,表示连续 10 字节数据写入完毕,将写使能信号 wr_en 拉低,写操作完毕。相关信号波形如下。

在这里插入图片描述
第二部分:输出存储地址 byte_addr、写数据 wr_data 信号波形的设计与实现既然是对 EEPROM 中兴写数据操作,存储地址和写数据必不可少,在本从实验中,向EEPROM 中 10 个连续存储存储单元写入 10 字节数据。对输出存储地址 byte_addr,赋值初始存储地址,当 i2c_end 信号有效时,地址加 1,待 10 字节数据均写入完毕,再次赋值初始从从地址;对于写数据 wr_data 处理方式相同,先赋值写数据初值,当 i2c_end 信号有效时,写数据加 1 ,待 10 字节数据均写入完毕,在此赋值写数据初值。两输出信号波形如下。

在这里插入图片描述
数据收发模块写操作部分介绍完毕,接下来介绍一下读操作部分各信号波形。

与写操作部分相同,外部按键传入的读触发信号经消抖处理后传入本模块,该信号只保持一个有效时钟,且同步时钟为系统时钟 sys_clk,模块工作时钟 i2c_clk 很难采集到该触发信号。我们需要延长该读使能触发信号的有效时间,使模块工作时钟 i2c_clk 可以采集到该触发信号。处理方式和写操作方式相同,声明计数器 cnt_rd 和读有效信号 rd_valid 两信号,延长读触发信号 read 有效时间,使 i2c_clk 时钟能采集到该读触发信号。具体方法参照写操作部分相关介绍,计数器 cnt_rd 和读有效信号 rd_valid 波形图如下。
在这里插入图片描述
对于读使能信号的处理方式也与写操作方式相同,工作时钟 i2c_clk 上升沿采集到有效rd_valid 信号,拉高读使能信号 rd_en,告知 I2C 驱动模块接下来要进行数据读操作。
声明字节计数器 rd_i2c_data_num 对已读出字节进行计数;使用之前声明的计数器cnt_start,对相邻读/写操作时间间隔进行计数。
采集到读有效信号 rd_valid 为高电平,拉高读使能信号 rd_en,计数器 cnt_wait、rd_i2c_data_num 均由 0 开始计数,每一个工作时钟周期 cnt_wait 自加 1,计数到最大值1499,i2c_start 保持一个工作时钟的高电平,同时 cnt_wait 归 0,重新开始计数;I2C 驱动模块接收到有效的 i2c_start 信号后,自 EEPROM 读出单字节数据,传回 i2c_end 信号,表示一次单字节写操作完毕,计数器 rd_i2c_data_num 加 1;计数器 cnt_start 完成 10 次循环计数,i2c_start 拉高 10 次,在接收到第 10 次有效的 i2c_end 信号后,表示连续 10 字节数据写入完毕,将读使能信号 rd_en 拉低,读操作完毕。相关信号波形如下。
在这里插入图片描述
既然是数据读操作,自然有读出数据传入本模块,一次读操作连续读出 10 字节数据,先将读取的 10 字节数据暂存到内部例化的 FIFO 中,以传回的 i2c_end 结束信号为写使能,在 i2c_clk 时钟同步下将读出数据写入 FIFO 中。同时我们将 FIFO 的数据计数器引出,方便后续数据发送阶段的操作。相关信号波形图如下。
在这里插入图片描述
对于存储地址信号 byte_addr 的讲解,读者参阅写操作部分相关介绍,此处不再赘述,接下来开始数据发送部分各信号波形的讲解。

等到读取的 10 字节均写入 FIFO 中,FIFO 数据计数器 data_num 显示为 10,表示 FIFO中存有 10 字节读出数据。此时拉高 FIFO 读有效信号 fifo_rd_valid,只有信号 fifo_rd_valid为有效高电平,对 FIFO 的读操作才有效;fifo_rd_valid 有效时,计数器 cnt_wait 开始循环
计数,声明此计数器的目的是计数字节数据读出时间间隔,间隔越长,每字节数据在数码管显示时间越长,方面现象观察;当计数器 cnt_wait 计数到最大值时,归 0 重新计数,FIFO 读使能信号信号 fifo_rd_en 拉高一个时钟周期,自 FIFO 读出一个字节数据,由fifo_rd_data 将数据传出给数码管显示模块,读出字节计数器 rd_data_num 加 1;等到 10 字节数据均读取并传出后,fifo_rd_valid 信号拉低,数据发送操作完成。相关信号波形如下。
在这里插入图片描述

数据收发模块参考代码(i2c_rd_data.v)

module i2c_rw_data
(
input wire sys_clk , //输入系统时钟,频率 50MHz
input wire i2c_clk , //输入 i2c 驱动时钟,频率 1MHz
input wire sys_rst_n , //输入复位信号,低有效
input wire write , //输入写触发信号
input wire read , //输入读触发信号
input wire i2c_end , //一次 i2c 读/写结束信号
input wire [7:0] rd_data , //输入自 i2c 设备读出的数据

output reg wr_en , //输出写使能信号
output reg rd_en , //输出读使能信号
output reg i2c_start , //输出 i2c 读/写触发信号
output reg [15:0] byte_addr , //输出 i2c 设备读/写地址
output reg [7:0] wr_data , //输出写入 i2c 设备的数据
output wire [7:0] fifo_rd_data //输出自 fifo 中读出的数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
// parameter define
parameter DATA_NUM = 8'd10 ,//读/写操作读出或写入的数据个数
CNT_START_MAX = 11'd1500 ,//cnt_start 计数器计数最大值
CNT_WR_RD_MAX = 8'd200 ,//cnt_wr/cnt_rd 计数器计数最大值
CNT_WAIT_MAX = 28'd500_000 ;//cnt_wait 计数器计数最大值
// wire define
wire [7:0] data_num ; //fifo 中数据个数

// reg define
reg [7:0] cnt_wr ; //写触发有效信号保持时间计数器
reg write_valid ; //写触发有效信号
reg [7:0] cnt_rd ; //读触发有效信号保持时间计数器
reg read_valid ; //读触发有效信号
reg [10:0] cnt_start ; //单字节数据读/写时间间隔计数
reg [7:0] wr_i2c_data_num ; //写入 i2c 设备的数据个数
reg [7:0] rd_i2c_data_num ; //读出 i2c 设备的数据个数
reg fifo_rd_valid ; //fifo 读有效信号
reg [27:0] cnt_wait ; //fifo 读使能信号间时间间隔计数
reg fifo_rd_en ; //fifo 读使能信号
reg [7:0] rd_data_num ; //读出 fifo 数据个数

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_wr:写触发有效信号保持时间计数器,计数写触发有效信号保持时钟周期数
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_wr <= 8'd0;
	else if(write_valid == 1'b0)
		cnt_wr <= 8'd0;
	else if(write_valid == 1'b1)
		cnt_wr <= cnt_wr + 1'b1;

//write_valid:写触发有效信号
//由于写触发信号保持时间为一个系统时钟周期(20ns),
//不能被 i2c 驱动时钟 i2c_scl 正确采集,延长写触发信号生成写触发有效信号
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		write_valid <= 1'b0;
	else if(cnt_wr == (CNT_WR_RD_MAX - 1'b1))
		write_valid <= 1'b0;
	else if(write == 1'b1)
		write_valid <= 1'b1;

//cnt_rd:读触发有效信号保持时间计数器,计数读触发有效信号保持时钟周期数
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_rd <= 8'd0;
	else if(read_valid == 1'b0)
		cnt_rd <= 8'd0;
	else if(read_valid == 1'b1)
		cnt_rd <= cnt_rd + 1'b1;

//read_valid:读触发有效信号
//由于读触发信号保持时间为一个系统时钟周期(20ns),
//不能被 i2c 驱动时钟 i2c_scl 正确采集,延长读触发信号生成读触发有效信号
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		read_valid <= 1'b0;
	else if(cnt_rd == (CNT_WR_RD_MAX - 1'b1))
		read_valid <= 1'b0;
	else if(read == 1'b1)
		read_valid <= 1'b1;

//cnt_start:单字节数据读/写操作时间间隔计数
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_start <= 11'd0;
	else if((wr_en == 1'b0) && (rd_en == 1'b0))
		cnt_start <= 11'd0;
	else if(cnt_start == (CNT_START_MAX - 1'b1))
		cnt_start <= 11'd0;
	else if((wr_en == 1'b1) || (rd_en == 1'b1))
		cnt_start <= cnt_start + 1'b1;

//i2c_start:i2c 读/写触发信号
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		i2c_start <= 1'b0;
	else if((cnt_start == (CNT_START_MAX - 1'b1)))
		i2c_start <= 1'b1;
	else
		i2c_start <= 1'b0;

//wr_en:输出写使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		wr_en <= 1'b0;
	else if((wr_i2c_data_num == DATA_NUM - 1) 
		&& (i2c_end == 1'b1) && (wr_en == 1'b1))
		wr_en <= 1'b0;
	else if(write_valid == 1'b1)
		wr_en <= 1'b1;

//wr_i2c_data_num:写入 i2c 设备的数据个数
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		wr_i2c_data_num <= 8'd0;
	else if(wr_en == 1'b0)
		wr_i2c_data_num <= 8'd0;
	else if((wr_en == 1'b1) && (i2c_end == 1'b1))
		wr_i2c_data_num <= wr_i2c_data_num + 1'b1;

//rd_en:输出读使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		rd_en <= 1'b0;
	else if((rd_i2c_data_num == DATA_NUM - 1) 
		&& (i2c_end == 1'b1) && (rd_en == 1'b1))
		rd_en <= 1'b0;
	else if(read_valid == 1'b1)
		rd_en <= 1'b1;

//rd_i2c_data_num:写入 i2c 设备的数据个数
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		rd_i2c_data_num <= 8'd0;
	else if(rd_en == 1'b0)
		rd_i2c_data_num <= 8'd0;
	else if((rd_en == 1'b1) && (i2c_end == 1'b1))
		rd_i2c_data_num <= rd_i2c_data_num + 1'b1;

//byte_addr:输出读/写地址
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		byte_addr <= 16'h00_5A;
	else if((wr_en == 1'b0) && (rd_en == 1'b0))
		byte_addr <= 16'h00_5A;
	else if(((wr_en == 1'b1) || (rd_en == 1'b1)) && (i2c_end == 1'b1))
		byte_addr <= byte_addr + 1'b1;

//wr_data:输出待写入 i2c 设备数据
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		wr_data <= 8'hA5;
	else if(wr_en == 1'b0)
		wr_data <= 8'hA5;
	else if((wr_en == 1'b1) && (i2c_end == 1'b1))
		wr_data <= wr_data + 1'b1;

//fifo_rd_valid:fifo 读有效信号
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		fifo_rd_valid <= 1'b0;
	else if((rd_data_num == DATA_NUM)
		&& (cnt_wait == (CNT_WAIT_MAX - 1'b1)))
		fifo_rd_valid <= 1'b0;
	else if(data_num == DATA_NUM)
		fifo_rd_valid <= 1'b1;

//cnt_wait:fifo 读使能信号间时间间隔计数,计数两 fifo 读使能间的时间间隔
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_wait <= 28'd0;
	else if(fifo_rd_valid == 1'b0)
		cnt_wait <= 28'd0;
	else if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
		cnt_wait <= 28'd0;
	else if(fifo_rd_valid == 1'b1)
		cnt_wait <= cnt_wait + 1'b1;

//fifo_rd_en:fifo 读使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		fifo_rd_en <= 1'b0;
	else if((cnt_wait == (CNT_WAIT_MAX - 1'b1))
		&& (rd_data_num < DATA_NUM))
		fifo_rd_en <= 1'b1;
	else
		fifo_rd_en <= 1'b0;

//rd_data_num:自 fifo 中读出数据个数计数
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		rd_data_num <= 8'd0;
	else if(fifo_rd_valid == 1'b0)
		rd_data_num <= 8'd0;
	else if(fifo_rd_en == 1'b1)
		rd_data_num <= rd_data_num + 1'b1;

//****************************************************************//
//************************* Instantiation ************************//
//****************************************************************//
//------------- fifo_read_inst -------------
fifo_data fifo_read_inst
(
.clock (i2c_clk ), //输入时钟信号,频率 1MHz,1bit
.data (rd_data ), //输入写入数据,1bit
.rdreq (fifo_rd_en ), //输入数据读请求,1bit
.wrreq (i2c_end && rd_en ), //输入数据写请求,1bit

.q (fifo_rd_data ), //输出读出数据,1bit
.usedw (data_num ) //输出 fifo 内数据个数,1bit
);

endmodule

参考代码编写完毕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1230757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IP地址的分包与组包:网络通信的关键技术解析

在计算机网络中&#xff0c;IP地址的分包与组包是网络通信过程中关键的技术环节&#xff0c;分别涉及将数据拆分为适当大小的包以及在接收端重新组装这些包的过程。这两个过程对于确保高效、可靠的数据传输至关重要。以下将深入探讨IP地址的分包与组包的概念、原理以及在网络通…

进程管理(三)

生产者—消费者问题及初步解决方案 放了数据后,有通知消费者的义务。 空缓冲区、满缓冲区作为资源,设计为信号量 关于生产者—消费者初步解决方案的反思 秘诀:资源信号量wait在前,互斥信号量wait在后 关于生产者—消费者初步解决方案的改进

Linux常见命令手册

目录 文件命令 文件和目录命令 文件的权限命令 文件搜索命令 进程命令 查看进程命令 关闭进程命令 用户和群组命令 网络命令 firewall-cmd 网络应用命令 高级网络命令 网络测试命令 网络安全命令 网络配置命令 软件管理命令 系统信息命令 vi编辑器 关机命令…

2023年中国位置服务(LBS)产业链及市场规模分析[图]

卫星导航系统的高技术、高成本、高效益属性使其成为国家经济实力与科技实力的标志之一。卫星导航系统由空间段、地面段和用户段三个部分组成&#xff0c;已广泛用于交通运输、农林牧渔、航空航海等领域&#xff0c;服务载体包括手机、汽车、无人机、导弹等&#xff0c;对人们生…

Dubbo快速实践

文章目录 架构相关概念集群和分布式架构演进 Dubbo概述Dubbo快速入门前置准备配置服务接口配置Provider配置Consumer Dubbo基本使用总结 本文参考https://www.bilibili.com/video/BV1VE411q7dX 架构相关概念 集群和分布式 集群&#xff1a;很多“人”一起 &#xff0c;干一样…

数据结构与算法-生成树与最小生成树

生成树与最小生成树 &#x1f388;1.生成树与最小生成树&#x1f52d;1.1生成树与最小生成树的概念&#x1f52d;1.2最小生成树的生成准则&#x1f52d;1.3两种最小生成树算法&#x1f3c6;1.3.1Kruskal算法&#x1f3c6;1.3.2Prim算法 &#x1f388;2.有向无环图及其应用&…

unity 打包exe设置分辨率

unity在2019以上版本不支持在打开的时候弹出分辨率设置的窗口&#xff0c;但是因为有些需求需要可以设置分辨率进行操作&#xff0c;我在查了好多办法后找到了解决方案&#xff0c;废话不多说开始干货。 1.先去百度云上下载这个文件 链接&#xff1a;https://pan.baidu.com/s/1…

MATLAB常用绘图函数的使用

文章目录 绘制一图一线绘制一图多线用法一&#xff1a;plot用法二&#xff1a;hold on 绘制一图多图其他形式的坐标图分段函数绘制方法一&#xff1a;分段写函数的定义域值域方法二&#xff1a;判断定义域方法三&#xff1a;if else 判断 横纵坐标范围设置标题、轴标签、图例、…

如何去云服务器申请环境跑深度学习模型

我的研究方向是显著性目标检测&#xff0c;虽然对比目标检测来说&#xff0c;数据集和模型的尺寸已经降低很多了&#xff0c;然后我们实验室也有一台公用服务器&#xff0c;但是那台服务器好多人使用&#xff0c;每个人能分配到的容量就很小&#xff0c;而且有时候会宕机&#…

测试和验证有什么区别,怎么划分测试集和验证集

测试集和验证集是在机器学习中用于评估模型性能的两个不同的数据集。它们有不同的目的和使用方式。 验证集&#xff08;Validation Set&#xff09;&#xff1a; 目的&#xff1a; 用于调整模型的超参数&#xff08;例如&#xff0c;学习率、正则化参数等&#xff09;和进行模型…

软件工程第十一周

面向对象 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;不仅仅是一种程序设计方法&#xff0c;它更是一种深刻的软件工程开发思想。这种思想的核心在于通过抽象和封装来模拟现实世界中的对象和概念&#xff0c;以便更好地管理和解决复杂的软件工程问…

005 OpenCV直方图

目录 一、环境 二、直方图原理概述 三、代码 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、直方图原理概述 OpenCV是一个广泛使用的开源计算机视觉库&#xff0c;它提供了许多用于图像处理和分析的函数和算法。其中&#xff…

设置 wsl 桥接模式

一、环境要求 Win10/Win11 专业版&#xff0c;并已安装 Hyper-V 二、具体步骤 打开 Hyper-V 管理器 创建虚拟交换机 WSL Bridge 修改wsl配置文件 .wslconfig .wslconfig 文件所在路径如下&#xff1a; C:\Users\<UserName>\.wslconfig若 .wslconfig 文件不存在&am…

Sam Altman 或回归 OpenAI;格力 1.3 万研发人员没有海归派 外国人丨 RTE 开发者日报 Vol.86

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

《Python数据科学项目实战》:开启数据科学之旅的实战指南!

《Python数据科学项目实战》是一本内容丰富且组织结构严谨的书籍&#xff0c;它旨在帮助读者通过实际案例研究掌握使用Python进行数据科学工作的必要知识。本书的案例研究涵盖了在线广告分析、使用新闻数据跟踪疾病暴发等多个现实世界的场景&#xff0c;使读者能够将所学知识应…

NOSQL----redis的安装和基础命令

redis是什么 1.redis-------非关系型数据库 redis是非关系数据库的一种&#xff0c;也称为缓存型数据库。 非关系型数据库和关系型数据库 1.关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;记录方式是行和列&#xff08;列&#xff1a;声明对象&#xff0c;行&am…

【unity实战】unity3D中的PRG库存系统和换装系统(附项目源码)

文章目录 先来看看最终效果前言素材简单绘制库存UI前往mixamo获取人物模型动画获取一些自己喜欢的装备物品模型库存系统换装系统装备偏移问题添加消耗品最终效果源码完结 先来看看最终效果 前言 之前2d的换装和库存系统我们都做过不少了&#xff0c;这次就来学习一个3d版本的&…

有哪些相见恨晚的stm32学习的方法?

有哪些相见恨晚的stm32学习的方法&#xff1f; 单片机用处这么广&#xff0c;尤其是STM32生态这么火&#xff01;如何快速上手学习呢&#xff1f; 你要考虑的是&#xff0c;要用STM32实现什么&#xff1f;为什么使用STM32而不是用8051&#xff1f;是因为51的频率太低&#xff…

五分钟搭建开源ERP:Odoo,并实现公网远程访问

文章目录 前言1. 下载安装Odoo&#xff1a;2. 实现公网访问Odoo本地系统&#xff1a;3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着企…

建筑可视化中的 3D 纹理

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 1、什么是 3D 纹理&#xff1f; 纹理是将二维图像添加到三维模型的技术艺术。虽然对物体进行纹…