本帖最后由 瓜弟 于 2023-3-28 21:46 编辑
FPGA使用USB与PC进行通信的方式通常有以下几种方案:1、通过FT232、CH340等转接芯片使用SPI、UART进行通信;2、CY7C68013等第三方MCU进行转发;3、直接使用FPGA的IO口进行低速USB通信;4、使用FPGA+USBPHY进行高速通信。本文使用外接USBPHY进行USB2.0高速通信,方案框图如下
1、概述
USBD协议栈使用ASICS.ws公司的开源USB2_Dev核,该公司开源产品GitHub地址为www-asics-ws · GitHub,框图如下图所示。从框图可知,该IP核与FPGA与其他功能模块之间使用Wishbone总线进行通信,与USBPHY芯片之间使用UTMI接口进行通信,同时移植过程中还需要实现SSRAM功能。通过该IP,可实现USB2.0的高速通信(480Mbps)。由于安路的SF1系列FPGA的RISCV硬核与外部交互的总线为AHB,所以再USBD核与AHB总线之间需要使用AHB转Wishbone总线模块。该USBD核与USBPHY之间的接口使用的是UTMI接口,该接口信号众多,且当前UTMI接口的USBPHY芯片也较少,故使用UTMI2ULPI接口,将其转换为ULPI接口以减少IO口的占用,该模块使用开源项目core_ulpi_wrapper。
2、AHB2Wishbone总线转换
如上图所示为Wishbone的主从连接关系,各引脚功能如下:
CLK_O |
输出信号,系统时钟,作为MASTER和SLAVE的时钟输入 |
RST_O |
输出复位信号,作为MASTER和SLAVE的复位输入,使得WISHBONE接口内部的状态机全部恢复到起始态 |
CLK_I |
输入信号,MASTER和SLAVE的时钟输入端,所有WISHBONE输出信号都在CLK-I的上升沿有效 |
DAT_O |
数据输出信号,最大位宽为64位 |
RST_I |
输入信号,使得WISHBONE接口内部的状态机全部恢复到起始态 |
TGD_I |
输入信号,数据标签类型 |
TGD_O |
输出信号,数据标签类型 |
ACK_I |
输入信号,确认信号,当该信号有效时,表明一个总线周期结束 |
ADR_O |
输出信号,地址输出 |
CYC_O |
周期输出信号,当该信号有效,表明进程中的总线是有效的,即它确定了总线周期的持续时间。CYC_O从数据传输的第一个比特开始有效,到数据传输结束为止 |
ERR_l |
输入信号,当该信号有效,表明总线周期非正常结束,表示有错误发生 |
LOCK_O |
输出信号,当该信号有效,表明当前总线周期锁定,不能被其他进程中断 |
RTY_I |
输入信号,当该信号有效,表明MASTER还没有准备好接收或发送数据,重新请求总线 |
SEL_O |
输出信号,用于选择数据信号线的输出 |
STB_O |
输出信号,表明一个有效数据传送周期 |
WE_O |
读使能信号,决定信号的读和写功能 |
上述信号可裁剪,本文使用了ADR_O、WE_O、STB_O、SEL_O、ACK_I、CLK_O、RST_O等信号,AHB转Wishbone代码如下
- module AHB2WB
- (
- input wire CLK,
- input wire RSTn,
-
- output reg[31:0] WB_ADDR,
- input wire[31:0] WB_DATA_IN,
- output reg[31:0] WB_DATA_OUT,
- output reg WB_WE,
- output reg[3:0] WB_SEL,
- output reg WB_STB,
- input wire WB_ACK,
- output reg WB_CYC,
-
- input wire HSEL,
- input wire[1:0] HTRANS,
- input wire[31:0] HADDR,
- input wire HWRITE,
- input wire[2:0] HSIEZ,
- input wire[2:0] HBURST,
- input wire[3:0] HPROT,
- input wire[31:0] HWDATA,
- output reg[31:0] HRDATA,
- output reg HREADY,
- output reg[1:0] HRESP
- );
-
- parameter HTRANS_IDLE = 2'b00; //Slave忽略掉此时的传输
- parameter HTRANS_BUSY = 2'b01;
- parameter HTRANS_NONSEQ = 2'b10; //表明当前是单笔的数据,或者是Burst的第一笔数据
- parameter HTRANS_SEQ = 2'b11;
-
- parameter HBURST_SINGLE = 3'b000; //单笔数据传输
- parameter HBURST_INCR = 3'b000;
- parameter HBURST_WRAP4 = 3'b000; //4个数据回绕方式批量传输
- parameter HBURST_INCR4 = 3'b000;
- parameter HBURST_WRAP8 = 3'b000; //8个数据回绕方式批量传输
- parameter HBURST_INCR8 = 3'b000;
- parameter HBURST_WRAP16 = 3'b000; //16个数据回绕方式批量传输
- parameter HBURST_INCR16 = 3'b000;
-
- parameter HREADY_REDY = 1'b1;
- parameter HREADY_BUSY = 1'b0;
-
- parameter HRESP_OKEY = 2'b00; //传输完成
- parameter HRESP_ERROR = 2'b01;
- parameter HRESP_RETRY = 2'b10; //传输未完成,请求主设备重新开始一个传输,
- //arbiter会继续使用通常的优先级
- parameter HRESP_SPLIT = 2'b11;
-
-
- parameter HSIZE_8b = 3'b000;
- parameter HSIZE_16b = 3'b001;
- parameter HSIZE_32b = 3'b010;
- parameter HSIZE_64b = 3'b011;
- parameter HSIZE_128b = 3'b100;
- parameter HSIZE_256b = 3'b101;
- parameter HSIZE_512b = 3'b110;
- parameter HSIZE_1024b = 3'b111;
-
- parameter HWRITE_WR = 1'b1;
- parameter HWRITE_RD = 1'b0;
-
- parameter FSM_IDLE = 4'h0;
- //parameter FSM_ADDR = 4'h1;
- parameter FSM_WR = 4'h2;
- parameter FSM_RD = 4'h4;
- reg[3:0] FSM_Current;
- reg[3:0] FSM_Next;
-
- always@(posedge CLK)
- begin
- if(RSTn == 0)
- begin
- FSM_Current <= FSM_IDLE;
- end
- else
- begin
- FSM_Current <= FSM_Next;
- end
- end
-
- always @(*)
- begin
- if(RSTn == 0)
- begin
- FSM_Next <= FSM_IDLE;
- end
- else
- begin
- case(FSM_Current)
- FSM_IDLE:
- begin
- if(
- (HSEL == 1)
- &&(HTRANS == HTRANS_NONSEQ)
- )
- begin
- if(HWRITE == HWRITE_WR)
- begin
- FSM_Next <= FSM_WR;
- end
- else
- begin
- FSM_Next <= FSM_RD;
- end
- end
- else
- begin
- FSM_Next <= FSM_IDLE;
- end
- end
- FSM_WR:
- begin
- if(WB_ACK == 1)
- begin
- FSM_Next <= FSM_IDLE;
- end
- else
- begin
- FSM_Next <= FSM_WR;
- end
- end
- FSM_RD:
- begin
- if(WB_ACK == 1)
- begin
- FSM_Next <= FSM_IDLE;
- end
- else
- begin
- FSM_Next <= FSM_WR;
- end
- end
- default:
- begin
- FSM_Next <= FSM_IDLE;
- end
- endcase
- end
- end
-
- always@(posedge CLK)
- begin
- if(RSTn == 0)
- begin
- HRDATA <= 32'hZ;
- HREADY <= HREADY_REDY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= 32'hZ;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b0;
- WB_STB <= 1'b0;
- WB_CYC <= 1'b0;
- WB_SEL <= 4'h0;
- end
- else
- begin
- case(FSM_Current)
- FSM_IDLE:
- begin
- if(
- (HSEL == 1)
- &&(HTRANS == HTRANS_NONSEQ)
- )
- begin
- HRDATA <= HRDATA;
- HREADY <= HREADY_BUSY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= HADDR;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b0;
- WB_STB <= 1'b0;
- WB_CYC <= 1'b0;
- WB_SEL <= 4'h0;
- end
- else
- begin
- HRDATA <= HRDATA;
- HREADY <= HREADY_REDY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= HADDR;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b0;
- WB_STB <= 1'b0;
- WB_CYC <= 1'b0;
- WB_SEL <= 4'h0;
- end
- end
- FSM_WR:
- begin
- HRDATA <= HRDATA;
- HREADY <= HREADY_BUSY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= WB_ADDR;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b1;
- WB_STB <= 1'b1;
- WB_CYC <= 1'b1;
- case(HSIEZ)
- HSIZE_8b :
- begin
- WB_SEL <= 4'h1;
- end
- HSIZE_16b:
- begin
- WB_SEL <= 4'h3;
- end
- HSIZE_32b:
- begin
- WB_SEL <= 4'hF;
- end
- default :
- begin
- WB_SEL <= 4'h0;
- end
- endcase
- end
- FSM_RD:
- begin
- HRDATA <= WB_DATA_IN;
- HREADY <= HREADY_BUSY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= WB_ADDR;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b0;
- WB_STB <= 1'b1;
- WB_CYC <= 1'b1;
- case(HSIEZ)
- HSIZE_8b :
- begin
- WB_SEL <= 4'h1;
- end
- HSIZE_16b:
- begin
- WB_SEL <= 4'h3;
- end
- HSIZE_32b:
- begin
- WB_SEL <= 4'hF;
- end
- default :
- begin
- WB_SEL <= 4'h0;
- end
- endcase
- end
- default:
- begin
- HRDATA <= 32'hZ;
- HREADY <= HREADY_REDY;
- HRESP <= HRESP_OKEY;
- WB_ADDR <= 32'hZ;
- WB_DATA_OUT <= HWDATA;
- WB_WE <= 1'b0;
- WB_STB <= 1'b0;
- WB_CYC <= 1'b0;
- WB_SEL <= 4'h0;
- end
- endcase
- end
- end
-
- endmodule
Riscv端的软件代码如下
- #include <stdio.h>
- #include "nuclei_sdk_hal.h"
- #include "anl_printf.h"
-
-
-
- int main(void)
- {
- unsigned long test;
- while(1)
- {
- *((unsigned long *)0x40000000) = 0x5A5A5A;
- test = *((unsigned long *)0x40000004);
- }
- }
-
-
-
使用片上逻辑分析仪对Wishbone的关键信号进行分析,截图如下,可见其符合Wishbone时序。