官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > 高级实训案例 >

【至简设计案例系列】基于USB3.0的数据通信设计-一位研三学生学习FPGA的点滴感悟

发布时间:2020-04-15   作者:admin 浏览量:

        

作为一个即将毕业但由于疫情不能回校的研三学生的独白。。。。。。


真正和FPGA打交道应该是刚读研一的时候,不知道市场上什么资料好,可能和刚开始学习FPGA的同学们一样,主要心态是想快速掌握这门技术,经过一番调查选择了潘老师的《手把手教你学FPGA设计》,终于被大道至简的至简设计法所打败了。


一、为什么大道至简好用且方便


由于《手把手教你学FPGA设计》书中讲解详细且区域划分鲜明,至简设计法最大特点2个,设计模板化和思考步骤化。


设计模板化:

在设计上明德扬把一些功能模板规范成可复用的模板,我们只需要填入参数就能把设计做出来,极大地减少代码上不必要的错误,我们只需要专心做好设计; 


思考步骤化:

至简设计法能帮助我们形成严谨的逻辑思维,做到设计有根据、思考有步骤。 


过多赘述不必多说,就像潘老师所说过那样,学好FPGA最重要就是状态机和计数器,记住,这是重点,要考的!!!

其实刚开始我没认为这两项多么重要,哈哈哈,但是随着工程代码量的不断增大以及对时序等要求的不断增强,愈发觉得基础才能决定上层建筑。而反观三段式或四段式状态机代码清晰,结构分明,你所需要做的只是添砖加瓦,也就是你自己的状态,因为地基已经为你搭好了。再说计数器,应该说没有什么说的了,抬手就写,因为代码放在那了啊,只需要添加你的加一和结束条件就可以了啊!!!现在想想当初的我为什么要特立独行!!!导致在仿真阶段浪费好多时间。

二. USB3.0数据通信

        最近写的代码(也不是最近了,大约在过年回家之前)是关于USB3.0数据通信的,这里多说几句,板子前期是购买的,后来自己设计的。代码部分是卖家提供(具体名称不方便透露,因为我不是挑事的人,哈哈哈),但是真的是他的代码太繁琐以及太不易搞懂且移植性太差了,但比较好的是我自己写也不浪费什么事情,我将USB3.0的状态分为读,写,空闲三个,然后根据三段式顺利写好属于自己的代码。源代码在下面,哈哈哈,如果可以的话请不要乱发或者帮我打码,毕竟毕业论文里还在用。并且家里电脑真的是什么软件都没安装,我就用记事本复制过来的。

2.1. USB3.0控制模块参考代码

信号
类型
位宽
意义
clk
输入
1
时钟信号
pclk_in
输入
1
USB芯片复位信号
rst_n
输入
1
复位信号,低电平有效
flag_a
输入
1
USB芯片标志位,控制写状态
flag_b
输入
1
USB芯片标志位,控制写状态
flag_c
输入
1
USB芯片标志位,控制读状态
flag_d
输入
1
USB芯片标志位,控制读状态
pclk
输出
1
USB芯片复位信号
slcs
输出
1
USB芯片片选信号
sloe
输出
1
USB芯片控制信号
slrd
输出
1
USB芯片读状态选择信号
slwr
输出
1
USB芯片写状态选择信号
pktend
输出
1
USB芯片数据包信号
fifo_addr
输出
2
USB芯片数据读取方向控制信号
usb_data
双向
32
USB3.0数据
cmd_flag
输出
1
USB传输数据标志位
cmd_data
输出
32
USB传输数据

module  USB_command(
        input                   clk  ,
        input                   pclk_in  ,
        input                   rst_n  ,
        //usb
        input                   flag_a  ,
        input                   flag_b  ,
        input                   flag_c  ,
        input                   flag_d  ,
output  wirepclk                            ,
output  wireslcs                            ,
output  reg             sloe                            ,
output  regslrd                            ,
output  regslwr                            ,
output  wirepktend                          ,
output  reg   [ 1: 0]   fifo_addr                       ,
inoutwire  [31: 0]   usb_data                        ,
output  regcmd_flag                        ,
output  wire  [31: 0]   cmd_data
                
                                
);
parameter   IDLE            =   4'b0001                         ;
parameter   WRITE           =   4'b0010                         ;
parameter   READ            =   4'b0100                         ;
reg   [ 3: 0]                 state_c /*synthesis preserve*/  ;
reg   [ 3: 0]                 state_n /*synthesis preserve*/  ;
//cnt
reg   [31: 0]                 cnt                             ;
wire                            add_cnt  ;
wire                            end_cnt  ;
reg   [15: 0]                 rd_cnt                          ;
reg   [15: 0]                 rd_data_len                     ;
regwr_trig                         ;//写触发
assign  slcs        =   1'b0;
assign  pclk        =   pclk_in;
assign  pktend      =   1'b1;
assign  usb_data    =   (!slwr) ? usb_write_data : 32'dz;
wire[31:0]  usb_write_data ;
//state_c
always@(posedgeclk or negedgerst_n)begin
    if(!rst_n)begin
state_c<= IDLE;
    end
    else begin
state_c<= state_n;
    end
end
//state_n
always@(*)begin
    case(state_c)
IDLE:begin
if(flag_a&&flag_b&&wr_trig)begin
state_n = WRITE;
            end
            else if(flag_c&&flag_d)begin
state_n = READ;                                        
            end
            else begin
state_n = state_c;                                        
            end
        end
WRITE:begin
            if(flag_b == 1'b0)begin //写满
state_n = IDLE;
            end
            else begin
state_n = state_c;
            end
        end
READ:begin
            if(flag_d == 1'b0)begin //读空
state_n = IDLE;
            end
            else begin
state_n = state_c;
            end
        end
default:begin
state_n = IDLE;
        end
endcase
end
//fifo_addr
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
fifo_addr<=  2'b00;
        sloe        <=  1'b1;
    end
    else if(state_c==READ)begin
fifo_addr<=  2'b11;
        sloe        <=  1'b0;
    end
    else begin
fifo_addr<=  2'b00;
        sloe        <=  1'b1;
    end
end
//slwr 输出信号,用时序逻辑好点
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
slwr<=  1'b1;
    end
    else if(state_c==WRITE)begin
slwr<=  1'b0;
    end
    else begin
slwr<=  1'b1;
    end
end
//slrd
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
slrd<=  1'b1;
    end
    else if(state_c==READ)begin
slrd<=  1'b0;
    end
    else begin
slrd<=  1'b1;
    end
end
//rd_cnt
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
rd_cnt<=  16'd0;
    end
    else if(slrd)begin
rd_cnt<=  16'd0;
    end
    else begin
rd_cnt<=  rd_cnt + 1'b1;
    end
end
//rd_data_len
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
rd_data_len<=  16'd0;
    end
    else if(rd_cnt == 16'd3)begin
rd_data_len<=  usb_data + 16'd3;
    end
end
//cmd_flag
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
cmd_flag<=  1'b0;
    end
    else if(rd_cnt == 16'd3)begin
cmd_flag<=  1'b1;
    end
    else if(rd_cnt == rd_data_len)begin
cmd_flag<=  1'b0;
    end
end
assign  cmd_data   =   usb_data ;  //将cmd_flag与cmd_data配合使用                                   
//cnt
always @(posedgeclk or negedgerst_n)begin
    if(!rst_n)begin
cnt<= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
cnt<= 0;
        else
cnt<= cnt + 1;
    end
    else begin
cnt<= 0;
    end
end
assign  add_cnt     =       state_c==WRITE;      
assign  end_cnt     =       add_cnt&&cnt == 4096-1;   //测试用的计数数据
//wr_trig
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
wr_trig<=  1'b0;
    end
    else if(cmd_flag&&cmd_data == 32'h11111111)begin
wr_trig<=  1'b1;
    end
    //else if(slwr == 1'b0)begin //开始写的时候,将wr_trig拉低   
      //  wr_trig<=  1'b0;
    //end
end
//fifo 读请求
regfifo_rdeq;
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
fifo_rdeq<=  1'b0;                
    end
    else if(flag_a )begin                
                fifo_rdeq<=  1'b1;        
    end
    else fifo_rdeq<= 1'b1;
end
//fifo 写请求
regfifo_wdeq;
always  @(posedgeclk or negedgerst_n)begin
if(rst_n == 1'b0)begin
fifo_wdeq<=  1'b0;
    end
    else if(flag_c)begin
fifo_wdeq<=  1'b1;
    end
    else fifo_wdeq<= 1'b1;
end
//FIFO
fifo        fifo_inst (        
        .clock (clk),
        .data ( cnt ),
        .rdreq ( fifo_rdeq ),
        .wrreq ( fifo_wdeq ),
        .q ( usb_write_data )
        );
Endmodule

2.2. 明德扬的signaltap教程及仿真验证

       这里还得感谢明德扬的signaltap教程,因为USB通信的数据线为32根,因此调试阶段使用modelsim真的不太好用,所以根据明德扬所讲的signaltap教程,我很容易的实现了抓图,分析出代码问题,并且我自己的体会,signaltap真的就像现实的示波器啊!!!好处自己去钻研吧。下面是抓图,通过抓图分析数据简直不要太快!!

三. 结论

       

无论是FPGA初学者,还是已经工作的工程师,都推荐学习明德扬的至简设计法,一定会有大大的收获。最后,祝福明德扬越来越好,大道至简,受益匪浅。愿桃李不言下自成蹊,终其教育,无外乎传道授业解惑,古有孔丘弟三千,愿今师者百树人。



  •   
  •   
  •   
  •  
  • FPGA教育领域第一品牌
  • 咨询热线:020-39002701
  • 技术交流Q群:544453837