基于XC7Z100+OV5640(DSP接口)YOLO人脸识别前向推理过程(部分4)

发布时间 2023-06-12 20:40:47作者: 李白的白

AXI-DMA使用介绍

内容概述
  • 如何在Zynq平台上使用AXI-DMA进行PS和PL之间的高带宽数据传输。
  • 主要包括以下几个部分:
    • AXI-DMA的简介和模式选择
    • AXI-DMA的寄存器配置和编程顺序
    • Vivado工程的创建和IP核的添加
    • Vitis工程的创建和示例代码的运行
    • ILA信号的抓取和分析
AXI-DMA的简介和模式选择
  • AXI-DMA是一种高性能(高带宽)的直接内存访问(Direct Memory Access,缩写为DMA)IP核,可以在AXI4 Memory Mapped和AXI4 Stream接口之间进行数据传输。

    • AXI4 Memory Mapped接口连接到PS端的HP接口,用于访问DDR3内存
    • AXI4 Stream接口连接到PL端的自定义模块,用于发送或接收数据
    • AXI4 Lite接口连接到PS端的GP接口,用于配置AXI-DMA内部的寄存器
  • AXI-DMA有两个主要的接口:AXI4 Memory Mapped和AXI4 Stream。

    • AXI4 Memory Mapped接口用于连接到PS端的HP(High Performance)接口,实现对内存或寄存器等存储器读写访问。
    • AXI4 Stream接口用于连接到PL端的自定义逻辑或其他IP,实现流式数据的传输(用于传输视频、音频等连续数据)。
    • AXI-DMA还有一个AXI4 Lite接口,用于连接到PS端的GP(General Purpose)接口,实现对DMA内部寄存器的配置和控制。
  • 补充说明AXI-DMA的IP核和寄存器

    • AXI-DMA的IP核包含了以下几个部分:

      • AXI4 Memory Mapped Master Interface:连接到PS端的HP接口,用于访问PS端的DDR内存或其他存储器
      • AXI4 Memory Mapped Slave Interface:连接到PS端的GP接口,用于接收PS端对AXI-DMA寄存器的配置指令
      • AXI4 Stream Master Interface:连接到PL端的自定义逻辑,用于发送数据到PL端的FIFO或其他模块
      • AXI4 Stream Slave Interface:连接到PL端的自定义逻辑,用于接收数据从PL端的FIFO或其他模块
      • Internal Logic:包含了控制逻辑、状态机、缓冲区等组件,用于实现AXI-DMA的功能和模式
    • AXI-DMA的寄存器分为两组,分别对应MM2S通道和S2MM通道,每组寄存器包含以下几个寄存器:

      1. 每个寄存器空间都有30个寄存器,每个寄存器都是32位宽
      2. 寄存器空间的地址范围是0x00到0x7C,每个寄存器占用4个字节,地址按照4字节对齐
      3. 寄存器空间中包含了控制寄存器、状态寄存器、地址寄存器、长度寄存器等,具体的功能和位定义可以参考AXI-DMA产品指南文档
      • DMACR:DMA Control Register,用于控制DMA通道的运行或停止、中断使能、循环模式等功能
      • DMASR:DMA Status Register,用于显示DMA通道的当前状态、错误信息、中断标志等信息
      • SA or DA:Source Address or Destination Address Register,用于设置DMA通道的源地址或目的地址
      • LENGTH:Transfer Length Register,用于设置DMA通道的传输长度(以字节为单位)
      • SA_MSB or DA_MSB:Source Address MSB or Destination Address MSB Register,用于设置DMA通道的源地址或目的地址的高32位(如果地址空间超过32位)
  • AXI-DMA支持三种模式:Simple DMA Mode,Scatter Gather Mode和Direct Register Mode。在本项目中使用的是Simple DMA模式,即直接寄存器模式,它是最简单的一种模式,只需要配置少量的寄存器就可以实现数据传输。

    • Simple DMA Mode:最简单的模式,只需要配置四个寄存器(控制寄存器、源地址或目的地址、长度和状态寄存器)就可以实现单次或循环的数据传输
      • PS端通过Vitis IDE编写代码,调用AXI-DMA提供的API函数,对AXI-DMA进行初始化、配置和传输
      • PL端通过Vivado设计工具添加AXI-DMA IP,并连接到FIFO IP,实现数据的缓冲和回环
      • FIFO IP是一种存储器IP,可以作为AXI Stream接口的数据源或目的地,支持多种操作模式和参数设置
      • ILA IP是一种逻辑分析仪IP,可以抓取PL端的信号波形,用于调试和分析
    • Scatter Gather Mode:一种高级模式,可以实现多个缓冲区的数据传输,需要配置描述符表和BD寄存器,可以进行多次或连续的数据传输,适用于大数据量或高吞吐量的场景
    • Direct Register Mode:一种特殊(混合)模式,结合了简单模式和高级模式的特点,可以根据需要动态切换传输模式可以实现无缓冲的数据传输,需要配置CR寄存器和SR寄存器
  • Simple DMA模式下,AXI-DMA有两个通道:MM2S(Memory Mapped to Stream)和S2MM(Stream to Memory Mapped)。MM2S通道用于从PS端发送数据到PL端,S2MM通道用于从PL端接收数据到PS端。

    • 每个通道都有四个信号:TDATA、TVALID、TLAST和TREADY
      • TDATA:传输的数据,位宽可配置,本项目中为64位
      • TVALID:有效信号,表示TDATA是否有效,由发送方驱动
      • TLAST:结束信号,表示当前TDATA是否为最后一个数据,由发送方驱动
      • TREADY:就绪信号,表示是否可以接收TDATA,由接收方驱动
    • 信号之间的关系遵循AXI Stream协议
      • 数据传输发生在TVALID和TREADY都为高电平时
      • TLAST为高电平时表示传输结束,之后TVALID应该为低电平
      • TVALID和TLAST应该在时钟上升沿变化,TREADY应该在时钟下降沿变化
  • Simple DMA模式下,AXI-DMA只需要配置四个寄存器:控制寄存器(DMACR)、状态寄存器(DMASR)、源地址寄存器(SA)或目的地址寄存器(DA)、长度寄存器(LEN)。

AXI Stream接口的信号含义
  • 对于Memory to Stream(M2S)通道,PS端输出以下信号:
    • TDATA:传输的数据
    • TVALID:有效数据标志位,置1表示有有效数据
    • TLAST:最后一个数据标志位,置1表示当前数据是最后一个
    • TKEEP:有效字节标志位,用于指示TDATA中哪些字节是有效的
  • 对于Memory to Stream(M2S)通道,PL端输入以下信号:
    • TREADY:准备就绪标志位,置1表示可以接收数据
  • 对于Stream to Memory(S2M)通道,PL端输出以下信号:
    • TDATA:传输的数据
    • TVALID:有效数据标志位,置1表示有有效数据
    • TLAST:最后一个数据标志位,置1表示当前数据是最后一个
    • TKEEP:有效字节标志位,用于指示TDATA中哪些字节是有效的
  • 对于Stream to Memory(S2M)通道,PS端输入以下信号:
    • TREADY:准备就绪标志位,置1表示可以接收数据
AXI-DMA的配置和使用方法
  • AXI-DMA的配置和使用方法主要涉及到一些寄存器的操作,包括控制寄存器、状态寄存器、地址寄存器、长度寄存器等。

  • 对于Memory to Stream(M2S)通道,即PS发送数据给PL,需要配置以下寄存器:

    • DMACR.RS:Run/Stop控制位,置1表示启动DMA通道传输
    • DMACR.IE:中断使能位,设置DMACR.IOC_IrqEn、DMACR.Err_IrqEn、DMACR.Dly_IrqEn等位为1,表示启动中断功能(本项目不使用)
    • MM2S_SA:源地址寄存器,设置PS端的内存地址
    • MM2S_LENGTH:长度寄存器,设置传输的字节数量(以字节为单位)
    • 等待DMA完成数据传输,并检查状态寄存器(DMASR)或中断信号
  • 对于Stream to Memory(S2M)通道,即PL发送数据给PS,需要配置以下寄存器:

    • DMACR.RS:Run/Stop控制位,置1表示启动DMA传输

    • DMACR.IE:中断使能位,设置DMACR.IOC_IrqEn、DMACR.Err_IrqEn、DMACR.Dly_IrqEn等位为1,表示启动中断功能(本项目不使用)

    • S2MM_DA:目的地址寄存器,设置PS端的内存地址

    • S2MM_LENGTH:长度寄存器,设置传输的字节数量(以字节为单位)

    • 等待DMA完成数据传输,并检查状态寄存器(DMASR)或中断信号

Vivado工程的创建和IP核的添加

在Vivado中创建一个使用AXI-DMA进行数据回环的工程(使用了一个FIFO作为PL端的数据源和数据接收器,实现了一个回环测试(Loopback Test))

  • 在Vivado中创建一个新工程,选择Zynq平台的芯片型号(本教程使用XC7Z100FG900-2)。
  • 在原理图中添加Zynq Processing System IP核,并配置DDR3内存、HP接口、GP接口等参数。
  • 在原理图中添加AXI DMA IP核,并配置其为Simple DMA模式,设置数据位宽为64位,最大突发长度为256字节等参数。
  • 在原理图中添加FIFO Generator IP核,并配置其为AXI4 Stream Data FIFO模式,设置FIFO深度为512字节等参数。
  • 将AXI DMA IP核的M_AXI_MM2S接口连接到Zynq PS IP核的M_AXI_HP0接口,将AXI DMA IP核的M_AXI_S2MM接口连接到Zynq PS IP核的M_AXI_HP1接口。
  • 将AXI DMA IP核的S_AXIS_S2MM接口连接到FIFO Generator IP核的S_AXIS接口,将AXI DMA IP核的M_AXIS_MM2S接口连接到FIFO Generator IP核的M_AXIS接口。
  • 将FIFO Generator IP核和AXI DMA IP核的时钟和复位信号连接到Zynq PS IP核相应的信号上。
  • 将FIFO Generator IP核和AXI DMA IP核相关的信号添加到ILA调试核上,以便后续观察信号波形。
Vitis工程的创建和示例代码的运行

在Vitis中创建一个使用AXI-DMA进行数据回环的应用程序(使用了ILA(Integrated Logic Analyzer)来抓取AXI-DMA的Stream接口的信号,用于分析数据传输的时序)

  • 在Vitis中创建一个新工程,并导入Vivado生成的硬件平台文件(.xsa)。
  • 在Vitis中添加一个应用程序,并选择Empty Application模板。
  • 在Vitis中导入AXI DMA IP核提供的示例代码(Simple Poll Example),并修改其中一些参数,如内存基地址、传输长度等以适应本教程的工程设置。
  • 在Vitis中编译并运行示例代码,并在终端窗口查看输出信息。
  • 在Vitis中启动ILA调试会话,并设置触发条件为TVALID信号上升沿。
  • 在Vitis中单步执行示例代码,并在ILA窗口查看信号波形。
ILA信号的抓取和分析
  • 在ILA窗口中,可以看到以下几类信号:

    • TDATA:表示传输的数据内容,64位宽度,从PS端发送到PL端的数据是0C0D0E0F...2A2B2C2D,从PL端发送到PS端的数据是相同的。
    • TVALID:发送方的有效信号,表示数据是否有效,由发送方输出,高电平有效。
    • TREADY:接收方的就绪信号,表示数据是否被接收方准备好接收,由接收方输出,高电平有效。
    • TLAST:发送方的结束信号,表示数据是否是一次传输的最后一个字节,由发送方输出,高电平有效。
    • TKEEP:发送方的保持信号,表示数据中哪些字节是有效的,由发送方输出,每个字节对应一个比特位。本教程中所有字节都有效,所以TKEEP始终为全1。
  • 根据AXI4 Stream协议:

    • 数据传输只有在TVALID和TREADY都为高电平(同时为1)时才有效,表示数据传输成功。
    • 当TLAST为1时,表示数据传输结束。
    • TLAST信号用于标识一次传输结束。
    • TKEEP信号用于处理不对齐或不完整的数据字节。
  • 在本项目中,示例代码从PS端向PL端发送了4个64位数据(0x0C0D0E0F10111213, 0x1415161718191A1B, 0x1C1D1E1F20212223, 0x2425262728292A2B),然后从PL端读回了相同的数据。在ILA窗口中可以看到这些数据在TDATA信号上出现,并且每次传输都伴随着TVALID、TREADY、TLAST、TKEEP信号变化。

  • 本例中PS 端通过 MM2S 通道向 PL 端的 FIFO 写入数据,然后通过 S2MM 通道从 FIFO 读出数据,实现一个回环测试。FIFO 的作用是缓冲和同步数据流。

    • AXI Stream 接口有四个主要信号:TVALID、TREADY、TLAST 和 TDATA。TVALID 表示发送方有有效数据;TREADY 表示接收方准备好接收数据;TLAST 表示当前数据是一帧数据的最后一个字;TDATA 表示数据本身。当 TVALID 和 TREADY 都为高时,表示一次有效的数据传输。
    • AXI-DMA 的 MM2S 通道是从 PS 端向 PL 端发送数据的,因此 PS 端是发送方,PL 端是接收方。PS 端需要提供 TVALID、TLAST 和 TDATA 信号,PL 端需要提供 TREADY 信号。在本文的示例中,PL 端使用了一个 FIFO 来缓存数据,并提供了一个恒为高的 TREADY 信号,表示 FIFO 始终准备好接收数据。
    • AXI-DMA 的 S2MM 通道是从 PL 端向 PS 端接收数据的,因此 PL 端是发送方,PS 端是接收方。PL 端需要提供 TVALID、TLAST 和 TDATA 信号,PS 端需要提供 TREADY 信号。在本文的示例中,PL 端使用了一个 FIFO 来提供数据,并根据 FIFO 的状态来控制 TVALID 信号,表示 FIFO 中是否有数据可发送。PS 端则根据 DMA 的配置和状态来控制 TREADY 信号,表示 PS 端是否准备好接收数据。
    • 在本文的示例中,使用了 64 位的数据宽度和 4 字节的字对齐方式,因此每次传输的数据是一个字,即 4 字节。每次传输的数据量是由 DMA 的寄存器中的长度字段来指定的,单位是字节。在本文的示例中,每次传输的数据量是 16 字节,即 4 个字。

ZYNQ PL 端中断使用介绍

  • 中断的概念
    • 程序执行过程中遇到急需处理的事件时,暂时终止 CPU 上线行程序的运行,转而执行相应的事件处理程序

    • 处理完成后再返回源程序被中断处或调度其他程序执行的过程

    • 中断有中断请求、中断响应、中断服务程序三个要素

    • 中断可以提高 CPU 的利用率和系统的实时性

    • 示意图
      • 主程序在 PS 端用 C 语言执行
      • 中断请求来了,主程序在当前位置打一个断点,暂停执行
      • 响应中断请求,进入中断服务程序,执行一段代码进行中断处理
      • 返回到主程序之前的代码位置,继续执行
主程序->中断请求: 中断发生
Note right of 中断请求: 中断源
中断请求->主程序: 中断响应
Note right of 主程序: 暂停执行,保存现场
主程序->中断服务程序: 跳转执行
Note right of 中断服务程序: 处理中断事件
中断服务程序->主程序: 返回继续执行
Note right of 主程序: 恢复现场
  • ZYNQ PL 端中断
    • ZYNQ PL 端中断是指在 PL 端产生的中断信号,通过 ZYNQ PS 端的中断控制器(GIC)传递给 PS 端的 ARM 处理器
    • ZYNQ PL 端中断可以用于实现 PS 端和 PL 端之间的通信和协调,例如在 YOLO 项目中,PS 端给 PL 端发送命令,PL 端完成任务后给 PS 端发送反馈
    • ZYNQ 的 PS 端有一个通用中断控制器(GIC),用于管理来自 PS 和 PL 的各种中断源
    • ZYNQ 的 PL 端可以通过两种方式给 PS 端发送中断请求:IRQ_F2P 和 FIQ_F2P
      • IRQ_F2P 和 FIQ_F2P 都有 16 个通道(中断源(IRQ_F2P[15:0])),每个通道对应一个中断 ID 号中断 ID 号(61-68或84-91),可以在 ZYNQ PS 配置界面勾选需要使用的中断源
      • ZYNQ PL 端中断有两种类型:上升沿触发(rising edge triggered)和高电平触发(high level triggered),分别表示在中断信号从低电平变为高电平时或在中断信号保持高电平时产生中断
    • 在 PS 端,需要配置 GIC 的寄存器,设置中断的优先级、类型、使能等参数,并编写中断服务程序和异常处理函数
  • 示例工程的创建和运行
    • 在 Vivado 中创建 block design,并添加 ZYNQ PS 和自定义 IP 模块(例如产生中断信号的模块)
    • 在 ZYNQ PS 配置界面,勾选 PL 到 PS 的中断,并连接到 GIC 的 IRQ_S2P 接口
    • 在 block design 中,连接自定义 IP 模块的时钟、复位和中断信号,并添加 VIO 和 ILA 模块用于控制和观测
    • 在 Vivado 中生成 bitstream 文件,并导出硬件信息
    • 在 Vitis 中创建工程,并导入硬件信息和相关库文件
    • 在 Vitis 中编写 C 语言代码,实现以下功能:
      • 初始化 GIC 和自定义 IP 模块
      • 设置中断系统和异常处理函数
      • 连接中断服务程序(ISR)到对应的中断 ID 号
      • 设置中断触发类型(上升沿或高电平)和优先级
      • 使能 GIC 和自定义 IP 模块的中断
      • 在主循环中等待中断请求,并在 ISR 中处理中断事件
    • 在 Vitis 中编译代码,并下载到板子上运行
    • 在 Vivado 中启动硬件管理器,并观察 ILA 波形
  • 相关名词
    • 中断:一种计算机系统的功能,可以让 CPU 暂停当前正在执行的任务,去处理一些紧急或重要的事件,然后再返回原来的任务。例如:
      • 当用户按下键盘或鼠标时,会产生一个中断请求,通知 CPU 去处理用户的输入。
      • 当外部设备(如打印机、硬盘等)完成某个操作时,会产生一个中断请求,通知 CPU 去获取结果或发送下一个指令。
      • 当 CPU 遇到某些异常情况(如除零、缺页等)时,会产生一个中断请求,通知 CPU 去执行相应的异常处理程序。
    • 中断请求 (Interrupt Request):一种信号,用来通知 CPU 有一个需要处理的事件发生。中断请求可以由硬件设备或软件程序发起,也可以由 CPU 自身产生。不同的中断请求有不同的优先级和触发方式。
    • 中断服务程序 (Interrupt Service Routine):一种特殊的程序,用来处理特定的中断请求。当 CPU 收到一个中断请求时,会根据其类型和优先级,选择相应的中断服务程序去执行。中断服务程序通常要尽快完成任务,并清除中断标志。
    • 中断控制器 (Interrupt Controller):用于管理多个中断源和优先级的一个硬件或软件模块,可以屏蔽或使能某些中断,也可以向 CPU 发送中断向量或编号
    • 触发类型 (Trigger Type):用于指定中断请求的信号特征,常见的有上升沿触发 (Rising Edge Trigger) 和高电平触发 (High Level Trigger),分别表示信号从低到高的跳变和持续为高的状态

main_ctrl模块代码编写

main control 模块的功能
  • 对 Axi4-lite 的计算器信息进行解析

    • 计算器信息包括:数据类型、卷积类型、卷积核尺寸、输入输出通道数、输入输出特征图尺寸等 (计算器信息包括卷积类型、数据类型、卷积核大小、步长、填充等参数,分别存储在四个寄存器中)
    • 解析的方法是按照表格中给出的比特位分配,将寄存器中的比特提取出来,赋值给相应的信号
  • 根据解析的信息对向 PS 端产生反馈加速器内部各模块进行协调控制

    加速器内部各模块包括了算术逻辑单元(ALU)、数据存储单元(DSU)、数据传输单元(DTU)等,它们分别负责执行计算、存储数据、传输数据等功能。main_ctrl模块需要根据解析的计算器信息,向各个模块发送相应的控制信号,使它们按照预期的顺序和方式工作。例如,如果计算器信息指定了要进行加法运算,那么 main_ctrl模块需要向 ALU 发送加法指令,并向 DSU 发送读取操作数的地址,并向 DTU 发送写入结果的地址。

    • 加速器内部各模块包括:padding、卷积计算、转置、上采样等
    • 协调控制包括:控制各模块的工作时序、传递相关参数、接收各模块的完成信号等
    • 这个功能在本网页中没有完全实现,而是在后面的课程中逐步完成
  • 向 PS 端产生反馈加速器是否完成该阶段工作的中断请求

    中断请求是一种通知机制,用于告诉 PS 端 PL 端的工作状态。当加速器完成了一个阶段的工作,例如完成了一次计算或一次数据传输,那么 main_ctrl模块需要向 PS 端发送一个中断请求信号,让 PS 端知道 PL 端已经准备好接收下一条命令或处理下一组数据。

    • 中断请求是一个高电平信号,持续200个时钟周期
    • 这个功能是在加速器内部完成一次卷积或其他操作后,向 PS 端发送一个中断信号,通知 PS 端当前工作已经完成
    • 这个功能在本文中通过一个状态机实现,根据不同的操作类型和完成状态,控制中断信号的拉高和拉低
    • 中断请求在加速器完成当前卷积或上采样操作后产生
main control 模块的代码编写
  • 只编写第一个和第三个功能的代码,第二个功能的代码在后续课程中逐步完善

  • 先创建一个 ACU_top 模块作为加速器的顶层模块,然后在其中实例化 main control 模块

  • ACU_top 模块的端口有:

    • 与 DMA 相关的 stream 接口
      • stream 接口是一种 AXI4 流协议,用于传输数据和控制信息
      • stream 接口包括:数据线、有效位、就绪位、最后位等信号
      • stream 接口有两种方向:MM2S(主机到从机)和 S2MM(从机到主机)
    • 与 light 相关的四个寄存器接口
      • 寄存器接口是一种 AXI4 Lite 协议,用于配置和读取寄存器数据
      • 寄存器接口包括:地址线、数据线、读写使能位、响应位等信号
      • 寄存器接口有两种方向:读(从机到主机)和写(主机到从机)
    • 向 PS 端产生中断请求的 task_finish 信号
  • main control 模块的端口有:

    • 四个寄存器接口,用于接收计算器信息
    • 其他与内部模块交互的信号,如 write_start, write_finish, read_start, read_finish, conv_start, conv_finish, transpose_start, transpose_finish, upsample_start, upsample_finish 等(本节视频中暂不编写)
  • main control 模块的内部逻辑有:

    • 对四个寄存器中的计算器信息进行解析,提取出相应的参数并赋值给内部信号¹
      • 解析方法是按照表格¹中给出的比特分配,将寄存器数据按位切割并赋值给对应的信号
      • 内部信号包括:data_type, conv_type, reset_type, lets_type, control_select, kernel_size, input_channel_num, output_channel_num, input_feature_size, output_feature_size 等
    • 根据内部信号对加速器内部各模块进行协调控制(本节视频中暂不编写)
    • 根据各模块的完成信号产生中断请求信号 task_finish
      • 使用一个状态机来控制 task_finish 的产生和消除
      • 状态机有六个状态:idle, write, read, conv, transpose, upsample, finish
      • idle 状态表示空闲,根据 start 信号跳转到相应的操作状态
      • write/read/conv/transpose/upsample 状态表示正在执行相应的操作,根据 finish 信号跳转到 finish 状态
      • finish 状态表示操作完成,拉高 task_finish 信号,并启动一个计数器,计数到200后跳转回 idle 状态并拉低 task_finish 信号

assign  write_start             =       slave_lite_reg0[0];
assign  read_start              =       slave_lite_reg0[1];
assign  conv_start              =       slave_lite_reg0[2];
assign  upsample_start          =       slave_lite_reg0[3];
assign  data_type               =       slave_lite_reg0[5:4];
assign  conv_type               =       slave_lite_reg0[6];
assign  is_padding              =       slave_lite_reg0[7];
assign  is_pool                 =       slave_lite_reg0[8];
assign  site_type               =       slave_lite_reg0[10:9];
assign  batch_type              =       slave_lite_reg0[13:11];
assign  feature_col_select      =       slave_lite_reg0[16:14];
assign  feature_row             =       slave_lite_reg0[23:17];
                             
assign  wbuffer_rd_addr         =       slave_lite_reg1[7:0];
assign  bbuffer_rd_addr         =       slave_lite_reg1[15:8];
assign  mult                    =       slave_lite_reg1[31:16];
  
assign  zero_point_in           =       slave_lite_reg2[7:0];
assign  zero_point_out          =       slave_lite_reg2[15:8];
assign  zero_point_act          =       slave_lite_reg2[23:16];  
assign  shift                   =       slave_lite_reg2[31:24];  


//////////////////////////////////////////////////////
assign  task_finish             =       state[5]; 


always  @(posedge sclk or negedge s_rst_n) begin
        if(s_rst_n == 1'b0)
                state   <=      S_IDLE;
        else case(state)
                S_IDLE: 
                        if(write_start == 1'b1)
                                state   <=      S_WRITE;
                        else if(read_start == 1'b1)
                                state   <=      S_READ;
                        else if(conv_start == 1'b1)
                                state   <=      S_CONV;
                        else if(upsample_start == 1'b1)
                                state   <=      S_UPSAMPLE;
                        else
                                state   <=      S_IDLE;
                S_WRITE:
                        if(write_finish == 1'b1)
                                state   <=      S_FINISH;
                        else
                                state   <=      S_WRITE;
                S_READ:
                        if(read_finish == 1'b1)
                                state   <=      S_FINISH;
                        else
                                state   <=      S_READ;
                S_CONV:
                        if(conv_finish == 1'b1)
                                state   <=      S_FINISH;
                        else
                                state   <=      S_CONV;
                S_UPSAMPLE:
                        if(upsample_finish == 1'b1)
                                state   <=      S_FINISH;
                        else
                                state   <=      S_UPSAMPLE;
                S_FINISH:
                        if(finish_cnt >= FINISH_END)
                                state   <=      S_IDLE;
                        else
                                state   <=      S_FINISH;
                default:
                        state   <=      S_IDLE;
        endcase
end

always  @(posedge sclk or negedge s_rst_n) begin
        if(s_rst_n == 1'b0)
                finish_cnt      <=      'd0;
        else if(state == S_FINISH)
                finish_cnt      <=      finish_cnt + 1'b1;
        else
                finish_cnt      <=      'd0;
end