FT600Q开发笔记
- 为什么选择FT600Q
- 电路图
- FT600Q引脚
- 时序
- FPGA接收数据
- 时序
- 说明
- FPGA发送时序
- 时序
- 说明
- FT600中止(abort)接收
- FT600中止(abort)发送
- Verilog代码
- 运行结果
- FPGA接收
- FPGA发送
- FT600中止接收
- FT600中止发送
- 接收速率
- 发送速率
为什么选择FT600Q
为将SDR中FPGA处理后的IQ数据发送到PC,可以通过网络或者USB的方式。FPGA处理后的数据为24位,抽取后,10MHz的采样率,两个通道数据传输率将达到(24/8) x 2 x 10MHz=60MB/s。至少千兆网和USB 3.0才能满足速度要求。
选择FT600Q作为USB 3.0的接口芯片的一个因素是FT600Q为QFN封装,便于焊接;另一个因素是手头的FPGA开发板只有40pin的扩展插槽,无法支持32位的USB接口。
FT600Q支持多种模式,各种模式有不同的通道数量。这里全部都使用245模式。
FT600Q引脚
CLK:输出时钟到bus master[FPGA]
TXE_N:输出,发送缓冲区空(4KB),FPGA可以将数据发送到FT600
RXF_N:输出,接收缓冲区满(4KB),FPGA可以接收数据
OE_N:输入,输出使能,FPGA告知FT600可以输出数据
WR_N:输入,写使能,FPGA告知向FT600写入数据
RD_N:输入,读使能,FPGA告知从FT600读取数据
BE[3:0]:(FT600是BE[1:0]),字节有效信号,在FPGA的读周期中,FT600告知哪些字节是有效的;在FPGA的写周期中,FPGA告知FT600哪些字节是有效的。通常在写入的最后一个周期中,如果数据的字节长度没有对齐,那么有些字节就是无效的。
时序
这个代码主要实现与FTDI提供的FT600DataStreamerDemoApp进行通信。
FPGA接收数据
时序
说明
PC端调用FT_WritePipe函数,向FT600发送数据
FT600拉低RXF_N引脚,告知FPGA缓冲区有数据
FPGA拉低OE_N,告知FT600可以使用数据线
FPGA拉低RD_N,告知FT600开始发送数据
FT600缓冲区空,FT600拉高RXF_N
FPGA拉高OE_N和RD_N,停止接收
FPGA发送时序
时序
说明
PC端调用FT_ReadPipi函数,告知FT600准备接收数据
FT600拉低TXE_N,告知FPGA缓冲区空,可以开始发送
FPGA拉低WR_N,告知FT600开始发送数据
FT600在缓冲区满后,拉高TXE_N,告知FPGA停止发送
FPGA拉高WR_N,停止发送
FT600中止(abort)接收
当PC端要中止接收时要通过OOB(Out-Of-Band)信号告知FPGA中止。FT600的stream demo中,接收中止OOB信号是通过GPIO[0]发送的。
- 调用FT_ReadPipe
- FT_ReadPipe超时
- 应用初始化abort recovery
- 应用通过FT_WriteGPIO发出read side OOB信号给FPGA
- FPGA读到read side OOB signal
- 如果TXE_N被拉低,说明FT600还能接收数据,此时FGPA应进行非对齐写,这样可以使FT600中的会话关闭。
- 应用等待10毫秒(依赖于FGPA的设计)
- 应用调用FT_AbortPipe并返回
- 应用停止read side OOB信号
- pipe为空
- 应用可以继续传输数据
FT600中止(abort)发送
中止发送也是通过OOB发送给FPGA。FT600的stream demo中,接收中止OOB信号是通过GPIO[1]发送的。
- 应用调用FT_WritePipe
- FT_WritePipe超时
- 应用初始化abort recovery
- 应用通过FT_WriteGPIO发出write side OOB信号给FPGA
- 应用等待10毫秒(或更长时间)
- FPGA检测到write side OOB信号
- FPGA从FT600中读取数据,直到RXF_N被拉高
- 应用定时器到
- 应用调用FT_AbortPipe
- 应用停止write side OOB信号
- 管道变空,完成abort recovery
Verilog代码
//FT600 stream
//BG6RDF
`timescale 1ns / 1ns
module FT600_stream
(
//PL时钟
input pl_clk, //PL端时钟
// 复位
input rst_n, //外部复位
output reg ft600_reset_n, //ft600复位
// FIFO interface
input ft600_clk,
inout [15:0] ft600_data,
inout [1:0] ft600_be,
input ft600_rxf_n,
input ft600_txe_n,
output ft600_oe_n,
output ft600_wr_n,
output ft600_rd_n,
output reg ft600_wakeup_n,
input [1:0] ft600_gpio,
output ft600_siwu_n,
output led0,
output led1
);
reg ft600_wakeup_reg;
reg led0_reg;
reg led1_reg;
assign led0=led0_reg;
assign led1=led1_reg;
//从host来,退出读操作
wire r_oob;
//从host来,退出写操作
wire w_oob;
assign ft600_siwu_n = 1'b1; // pull-up
wire [15:0] rd_data;
wire [15:0] wr_data;
wire [1:0] be_rd;
reg [1:0] be_wr;
reg [2:0] ft600_state; //状态
localparam FT600_IDLE=0,
FT600_READ=1,
FT600_READ_ABORT=2,
FT600_WRITE=3,
FT600_WRITE_ABORT=4;
assign rd_data = ft600_data;
assign ft600_data = (ft600_state==FT600_WRITE || ft600_state==FT600_WRITE_ABORT) ? wr_data : 16'bz;
assign be_rd = ft600_be;
assign ft600_be = (ft600_state==FT600_WRITE || ft600_state==FT600_WRITE_ABORT) ? be_wr : 2'bz;// write data dir
ila_ft600 ila_ft600_inst
(
.clk(ft600_clk) ,
.probe0(ft600_clk),
.probe1(ft600_data),
.probe2(ft600_be),
.probe3(ft600_rxf_n),
.probe4(ft600_txe_n),
.probe5(ft600_oe_n),
.probe6(ft600_wr_n),
.probe7(ft600_siwu_n),
.probe8(ft600_rd_n),
.probe9(ft600_wakeup_n),
.probe10(ft600_gpio),
.probe11(rd_data),
.probe12(wr_data),
.probe13(be_rd),
.probe14(be_wr),
.probe15(led0),
.probe16(led1),
.probe17(ft600_state),
.probe18(r_oob),
.probe19(w_oob),
.probe20(ft600_reset_n)
);
reg [15:0] wr_cnt;
assign wr_data = 16'hffff - wr_cnt;
//写计数器
always @(posedge ft600_clk)
begin
if(!ft600_reset_n)begin
wr_cnt <= 16'd0;
end
else
if(!ft600_wr_n)
begin
wr_cnt <= wr_cnt + 1'b1;
end
end
//写信号
assign ft600_wr_n=!((ft600_state==FT600_WRITE || ft600_state==FT600_WRITE_ABORT) && !ft600_txe_n);
//读信号,读状态且有数据可读
assign ft600_rd_n=!((ft600_state==FT600_READ || ft600_state==FT600_READ_ABORT) && (!ft600_rxf_n));
//在空闲态或读状态,且有数据可读时,允许FT600控制数据线
assign ft600_oe_n=!((ft600_state==FT600_IDLE || ft600_state==FT600_READ || ft600_state==FT600_READ_ABORT) && !ft600_rxf_n);
//定时器
localparam DELAY_5MS_CNT=(5*100000-1);
reg[31:0] delay_timer;
//状态机
always @(posedge ft600_clk)begin
if(!ft600_reset_n)
begin
ft600_state <= FT600_IDLE;
end
else
begin
if (r_oob)
begin
be_wr<=2'b01;
delay_timer <= DELAY_5MS_CNT;
ft600_state<=FT600_WRITE_ABORT;
end
else if (w_oob)
begin
delay_timer <= DELAY_5MS_CNT;
ft600_state<=FT600_READ_ABORT;
end
else
begin
case(ft600_state)
FT600_IDLE:
begin
be_wr <= 2'b11;
if((!ft600_rxf_n))
ft600_state <= FT600_READ; //有数据可读
else if(!ft600_txe_n)
ft600_state <= FT600_WRITE; //可写入数据
else
ft600_state <= FT600_IDLE;
end
FT600_READ:
if (ft600_rxf_n)
ft600_state <= FT600_IDLE;
FT600_READ_ABORT:
if (delay_timer==32'b0)
ft600_state <= FT600_IDLE;
else
begin
delay_timer=delay_timer-1'b1;
end
FT600_WRITE:
if(ft600_txe_n)
ft600_state <= FT600_IDLE;
FT600_WRITE_ABORT:
if (delay_timer==32'b0)
ft600_state <= FT600_IDLE;
else
begin
delay_timer=delay_timer-1'b1;
end
endcase
end
end
end
//延时复位
localparam RST_CYCLE=100;
reg [19:0] rst_cnt;
always @(posedge pl_clk)
begin
if (!rst_n)
begin
ft600_reset_n<=0;
rst_cnt<=0;
end
else
begin
if (rst_cnt<RST_CYCLE)
rst_cnt<=rst_cnt+1;
else
begin
ft600_reset_n<=1;
end
end
end
//定时唤醒FT600,确保DEBUG_HUB有时钟
localparam WAKEUP_CYCLE=8'd100;
reg [7:0] wakeup_cnt;
always @(posedge pl_clk)
begin
if (!rst_n)
begin
ft600_wakeup_n<=1'b1;
wakeup_cnt<=0;
end
else
begin
if (wakeup_cnt==WAKEUP_CYCLE)
begin
wakeup_cnt<=0;
ft600_wakeup_n<=!ft600_wakeup_n;
end
else
wakeup_cnt<=wakeup_cnt+1;
end
end
//LED
reg [1:0] ft600_gpio_p1;
reg [1:0] ft600_gpio_p2;
assign r_oob=(ft600_gpio_p2[0]==1'b0 && ft600_gpio_p1[0]==1'b1) ? 1'b1 : 1'b0;
assign w_oob=(ft600_gpio_p2[1]==1'b0 && ft600_gpio_p1[1]==1'b1) ? 1'b1 : 1'b0;
//OOB
always @(posedge ft600_clk)
begin
if (!ft600_reset_n)
begin
led0_reg<=1;
led1_reg<=1;
ft600_gpio_p1<=2'b11;
ft600_gpio_p2<=2'b11;
end
else
begin
ft600_gpio_p2<=ft600_gpio_p1;
ft600_gpio_p1<=ft600_gpio;
if (ft600_gpio_p2[0]==1'b0 && ft600_gpio_p1[0]==1'b1)
led0_reg<=1'b0;
if (ft600_gpio_p2[1]==1'b0 && ft600_gpio_p1[1]==1'b1)
led1_reg<=1'b0;
end
end
endmodule
运行结果
FPGA接收
FPGA发送
FT600中止接收
FT600中止发送
接收速率
发送速率