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; //表示master正在处理数据,slave需要忽略掉此时的传输
parameter HTRANS_NONSEQ = 2'b10; //表明当前是单笔的数据,或者是Burst的第一笔数据
parameter HTRANS_SEQ = 2'b11; //是Burst传输的剩余数据
parameter HBURST_SINGLE = 3'b000; //单笔数据传输
parameter HBURST_INCR = 3'b000; //不定长递增方式批量传输
parameter HBURST_WRAP4 = 3'b000; //4个数据回绕方式批量传输
parameter HBURST_INCR4 = 3'b000; //4个数据递增方式批量传输
parameter HBURST_WRAP8 = 3'b000; //8个数据回绕方式批量传输
parameter HBURST_INCR8 = 3'b000; //8个数据递增方式批量传输
parameter HBURST_WRAP16 = 3'b000; //16个数据回绕方式批量传输
parameter HBURST_INCR16 = 3'b000; //16个数据递增方式批量传输
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; //传输未完成,请求主设备分离一次传输,
//arbiter会调整优先级方案以便其他请求总线的主设备可以访问总线
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时序。
本帖最后由 瓜弟 于 2023-3-28 21:46 编辑硬件工程
软件工程
厉害厉害,话说FPGA可以自制成一个USBPHY吗,感觉应该也可以。
引用: wangerxian 发表于 2023-3-29 09:11 厉害厉害,话说FPGA可以自制成一个USBPHY吗,感觉应该也可以。
USB3好像可以,走LVDS,USB2应该不行