OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1152|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第五十章 以太网ARP测试实验(下)

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4666
金钱
4666
注册时间
2019-5-8
在线时间
1224 小时
发表于 2024-1-4 18:00:42 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2024-1-2 17:29 编辑

第五十章 以太网ARP测试实验

1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0

3)购买链接:https://detail.tmall.com/item_o.htm?id=692712955836

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

1.4 程序设计
根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要完成RGMII接口数据和GMII接口数据的转换,以方便数据的采集和解析,在数据采集过程中所用到的延时原语参考时钟由锁相环输出的时钟提供;其次整个以太网帧格式与ARP协议的实现由ARP顶层模块完成;ARP控制模块负责检测输入的触摸按键是否被按下,控制ARP顶层模块发起请求与产生应答等操作。由此画出系统的功能框图如下图所示:                             
image088.png
图 50.4.1 以太网ARP测试系统框图
GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块实现了以太网ARP数据包的接收、发送以及CRC校验的功能;ARP控制模块根据输入的按键触摸信号和接收到的ARP请求信号,控制ARP顶层模块发送ARP请求或者ARP应答。
FPGA顶层模块例化了以下四个模块,网口复位模块(reset_dly)、GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)和ARP控制模块(arp_ctrl),实现了各模块之间的数据交互。
其中ARP顶层模块和GMII TO RGMII模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。
顶层模块的代码如下:
  1. 1  module eth_arp_test(
  2. 2       input             sys_clk   , //系统时钟
  3. 3       input             sys_rst_n , //系统复位信号,低电平有效
  4. 4       input             touch_key , //触摸按键,用于触发开发板发出ARP请求
  5. 5       //以太网RGMII接口   
  6. 6       input             eth_rxc   , //RGMII接收数据时钟
  7. 7       input             eth_rx_ctl, //RGMII输入数据有效信号
  8. 8       input       [3:0  eth_rxd   ,//RGMII输入数据
  9. 9       output            eth_txc   , //RGMII发送数据时钟   
  10. 10      output            eth_tx_ctl, //RGMII输出数据有效信号
  11. 11      output      [3:0  eth_txd   ,//RGMII输出数据         
  12. 12      output            eth_rst_n   //以太网芯片复位信号,低电平有效   
  13. 13      );
  14. 14
  15. 15 //parameter define
  16. 16 //开发板MAC地址00-11-22-33-44-55
  17. 17 parameter  BOARD_MAC = 48'h00_11_22_33_44_55;
  18. 18 //开发板IP地址192.168.1.10     
  19. 19 parameter  BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
  20. 20 //目的MAC地址ff_ff_ff_ff_ff_ff
  21. 21 parameter  DES_MAC  = 48'hff_ff_ff_ff_ff_ff;
  22. 22 //目的IP地址192.168.1.102
  23. 23 parameter  DES_IP   = {8'd192,8'd168,8'd1,8'd102};
  24. 24
  25. 25 //wire define
  26. 26 wire  [7:0   gmii_txd   ; //GMII发送数据
  27. 27 wire          gmii_tx_en  ; //GMII发送数据使能信号
  28. 28 wire          gmii_tx_clk ; //GMII发送时钟
  29. 29 wire  [7:0   gmii_rxd   ; //GMII接收数据
  30. 30 wire          gmii_rx_dv  ; //GMII接收数据有效信号
  31. 31 wire          gmii_rx_clk ; //GMII接收时钟
  32. 32
  33. 33 wire          arp_rx_done ; //ARP接收完成信号
  34. 34 wire          arp_rx_type ; //ARP接收类型 0:请求  1:应答
  35. 35 wire  [47:0  src_mac     ; //接收到目的MAC地址
  36. 36 wire  [31:0  src_ip      ; //接收到目的IP地址   
  37. 37 wire          arp_tx_en   ;//ARP发送使能信号
  38. 38 wire          arp_tx_type ; //ARP发送类型 0:请求  1:应答
  39. 39 wire          tx_done     ; //ARP发送完成信号
  40. 40 wire  [47:0  des_mac     ; //发送的目标IP地址
  41. 41 wire  [31:0  des_ip      ; //以太网发送完成信号
  42. 42 //*****************************************************
  43. 43 //**                    main code
  44. 44 //*****************************************************
  45. 45 assign des_mac = src_mac;
  46. 46 assign des_ip  = src_ip ;
  47. 47
  48. 48 //输出网口复位信号
  49. 49 reset_dly delay_u0(
  50. 50      .clk           (sys_clk  ),
  51. 51      .rst_n         (sys_rst_n),
  52. 52      .rst_n_dly     (eth_rst_n)
  53. 53 );
  54. 54
  55. 55 gmii_to_rgmii u_gmii_to_rgmii(
  56. 56      .rgmii_txd              (eth_txd   ),
  57. 57      .rgmii_tx_ctl           (eth_tx_ctl),
  58. 58      .rgmii_txc              (eth_txc   ),
  59. 59      .rgmii_rxd              (eth_rxd   ),
  60. 60      .rgmii_rx_ctl           (eth_rx_ctl),
  61. 61      .rgmii_rxc              (eth_rxc   ),
  62. 62      
  63. 63      .gmii_rx_clk            (gmii_rx_clk),
  64. 64      .gmii_txd               (gmii_txd   ),
  65. 65      .gmii_tx_en             (gmii_tx_en ),
  66. 66      .gmii_tx_clk            (gmii_tx_clk),
  67. 67      .gmii_rxd               (gmii_rxd   ),
  68. 68      .gmii_rx_dv             (gmii_rx_dv )
  69. 69      );
  70. 70
  71. 71 //ARP通信
  72. 72 arp                                             
  73. 73     #(
  74. 74      .BOARD_MAC     (BOARD_MAC  ),      //参数例化
  75. 75      .BOARD_IP      (BOARD_IP   ),
  76. 76      .DES_MAC       (DES_MAC    ),
  77. 77      .DES_IP        (DES_IP     )
  78. 78      )
  79. 79     u_arp(
  80. 80      .rst_n         (sys_rst_n  ),
  81. 81                     
  82. 82      .gmii_rx_clk   (gmii_rx_clk),
  83. 83      .gmii_rx_dv    (gmii_rx_dv ),
  84. 84      .gmii_rxd      (gmii_rxd   ),
  85. 85      .gmii_tx_clk   (gmii_tx_clk),
  86. 86      .gmii_tx_en    (gmii_tx_en ),
  87. 87      .gmii_txd      (gmii_txd   ),
  88. 88                     
  89. 89      .arp_rx_done   (arp_rx_done),
  90. 90      .arp_rx_type   (arp_rx_type),
  91. 91      .src_mac       (src_mac    ),
  92. 92      .src_ip        (src_ip     ),
  93. 93      .arp_tx_en     (arp_tx_en  ),
  94. 94      .arp_tx_type   (arp_tx_type),
  95. 95      .des_mac       (des_mac    ),
  96. 96      .des_ip        (des_ip     ),
  97. 97      .tx_done       (tx_done    )
  98. 98      );
  99. 99
  100. 100 //ARP控制
  101. 101 arp_ctrl u_arp_ctrl(
  102. 102     .clk           (gmii_rx_clk),
  103. 103     .rst_n         (sys_rst_n  ),
  104. 104                    
  105. 105     .touch_key     (touch_key  ),
  106. 106     .arp_rx_done   (arp_rx_done),
  107. 107     .arp_rx_type   (arp_rx_type),
  108. 108     .arp_tx_en     (arp_tx_en  ),
  109. 109     .arp_tx_type   (arp_tx_type)
  110. 110     );
  111. 111
  112. 112 endmodule
复制代码
顶层模块主要完成对其余模块的例化。在程序的第16行至第23行代码定义了开发板的MAC地址、IP地址、默认的目的MAC地址和目的IP地址。开发板的MAC地址为00:11:22:33:44:55;可开发板的IP地址为192.168.1.10;默认目的MAC地址为ff:ff:ff:ff:ff:ff,这是一个广播MAC地址,在收到上位机的请求或者应答之后,ARP模块会替换成实际的目的MAC地址。目的IP地址这里设置为192.168.1.102,因此大家在做本次实验时,需要把电脑的以太网的IP地址改成192.168.1.102,或者将代码中定义的DES_IP改成电脑的IP地址。
程序的第45行和46行代码将收到的对端设备MAC地址和目的IP地址,作为开发板发送时的目的MAC地址和IP地址。
gmii_to_rgmii模块代码如下:
  1. 1 module gmii_to_rgmii(
  2. 2      //以太网GMII接口
  3. 3      output            gmii_rx_clk , //GMII接收时钟
  4. 4      output            gmii_rx_dv  , //GMII接收数据有效信号
  5. 5      output      [7:0  gmii_rxd    , //GMII接收数据
  6. 6      output            gmii_tx_clk , //GMII发送时钟
  7. 7      input             gmii_tx_en  , //GMII发送数据使能信号
  8. 8      input       [7:0  gmii_txd    , //GMII发送数据            
  9. 9      //以太网RGMII接口   
  10. 10     input             rgmii_rxc   , //RGMII接收时钟
  11. 11     input             rgmii_rx_ctl, //RGMII接收数据控制信号
  12. 12     input       [3:0 rgmii_rxd   , //RGMII接收数据
  13. 13     output            rgmii_txc   , //RGMII发送时钟   
  14. 14     output            rgmii_tx_ctl, //RGMII发送数据控制信号
  15. 15     output      [3:0 rgmii_txd     //RGMII发送数据         
  16. 16     );
  17. 17 //wire
  18. 18 wire  pll_lock  ;
  19. 19 wire   gmii_tx_er;
  20. 20 //*****************************************************
  21. 21 //**                   main code
  22. 22 //*****************************************************
  23. 23 assign gmii_tx_clk = gmii_rx_clk;
  24. 24 //RGMII接收
  25. 25 rgmii_rx u_rgmii_rx(
  26. 26     .rgmii_rxc        (rgmii_rxc      ),
  27. 27     .rgmii_rx_ctl     (rgmii_rx_ctl   ),
  28. 28     .rgmii_rxd        (rgmii_rxd      ),
  29. 29                       
  30. 30     .gmii_rx_clk      (gmii_rx_clk    ),
  31. 31     .gmii_rx_dv       (gmii_rx_dv     ),
  32. 32     .gmii_rxd         (gmii_rxd       ),
  33. 33     .gmii_tx_clk_deg  (gmii_tx_clk_deg),
  34. 34     .pll_lock         (pll_lock       )
  35. 35     );
  36. 36
  37. 37 //RGMII发送
  38. 38 rgmii_tx u_rgmii_tx(
  39. 39     .reset            (1'b0           ),
  40. 40
  41. 41     .gmii_tx_er       (1'b0           ),
  42. 42     .gmii_tx_clk      (gmii_tx_clk    ),
  43. 43     .gmii_tx_en       (gmii_tx_en     ),
  44. 44     .gmii_txd         (gmii_txd       ),
  45. 45     .gmii_tx_clk_deg  (gmii_tx_clk_deg),
  46. 46     
  47. 47     .rgmii_txc        (rgmii_txc      ),
  48. 48     .rgmii_tx_ctl     (rgmii_tx_ctl   ),
  49. 49     .rgmii_txd        (rgmii_txd      )
  50. 50     );
  51. 51
  52. 52 endmodule
复制代码
由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第23行将GMII接收时钟赋值给GMII发送时钟,因此GMII的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII模块例化了rgmii_rx模块和rgmii_tx模块。
rgmii_rx模块代码如下所示:
  1. 1  module rgmii_rx(
  2. 2       //以太网RGMII接口
  3. 3       input             rgmii_rxc      , //RGMII接收时钟
  4. 4       input             rgmii_rx_ctl   , //RGMII接收数据控制信号
  5. 5       input       [3:0 rgmii_rxd      , //RGMII接收数据   
  6. 6  
  7. 7       //以太网GMII接口
  8. 8       output            gmii_rx_clk    , //GMII接收时钟
  9. 9       output            gmii_rx_dv     , //GMII接收数据有效信号
  10. 10      output      [7:0  gmii_rxd       , //GMII接收数据
  11. 11      output            gmii_tx_clk_deg,
  12. 12      output            pll_lock
  13. 13      );
  14. 14 //define reg
  15. 15 reg  [ 7:0     gmii_rxd;
  16. 16 reg             gmii_rx_dv;
  17. 17 //define wire
  18. 18 wire            gmii_rx_dv_s;
  19. 19 wire  [ 7:0    gmii_rxd_s;
  20. 20 //*****************************************************
  21. 21 //**                    main code
  22. 22 //*****************************************************
  23. 23 pll_sft U_pll_phase_shift(   
  24. 24      .clkout0   (gmii_rx_clk    ),   //125MHz
  25. 25      .clkout1   (gmii_tx_clk_deg),
  26. 26      .clkin1    (rgmii_rxc      ),
  27. 27      .clkfb     (gmii_rx_clk    ),
  28. 28      .pll_rst   (1'b0           ),
  29. 29      .pll_lock  (pll_lock       )
  30. 30      );
  31. 31      
  32. 32
  33. 33 always @(posedge gmii_rx_clk)
  34. 34 begin
  35. 35      gmii_rxd  = gmii_rxd_s;
  36. 36      gmii_rx_dv = gmii_rx_dv_s;
  37. 37 end
  38. 38
  39. 39 wire [5:0 nc1;
  40. 40 GTP_ISERDES #(
  41. 41      .ISERDES_MODE    ("IDDR"),  
  42. 42      //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
  43. 43      .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
  44. 44      .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
  45. 45 ) igddr1(         
  46. 46      .DI              (rgmii_rxd[0]),
  47. 47      .ICLK            (1'd0        ),
  48. 48      .DESCLK          (gmii_rx_clk ),
  49. 49      .RCLK            (gmii_rx_clk ),
  50. 50      .WADDR           (3'd0        ),
  51. 51      .RADDR           (3'd0        ),
  52. 52      .RST             (1'b0        ),
  53. 53      .DO              ({gmii_rxd_s[4],gmii_rxd_s[0],nc1})
  54. 54 );
  55. 55
  56. 56 wire [5:0 nc2;
  57. 57 GTP_ISERDES #(
  58. 58      .ISERDES_MODE    ("IDDR"),  
  59. 59      //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
  60. 60      .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
  61. 61      .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
  62. 62 ) igddr2(
  63. 63      .DI              (rgmii_rxd[1]),
  64. 64      .ICLK            (1'd0        ),
  65. 65      .DESCLK          (gmii_rx_clk ),
  66. 66      .RCLK            (gmii_rx_clk ),
  67. 67      .WADDR           (3'd0        ),
  68. 68      .RADDR           (3'd0        ),
  69. 69      .RST             (1'b0        ),
  70. 70      .DO              ({gmii_rxd_s[5],gmii_rxd_s[1],nc2})
  71. 71 );
  72. 72
  73. 73 wire [5:0 nc3;
  74. 74 GTP_ISERDES #(
  75. 75      .ISERDES_MODE    ("IDDR"),  
  76. 76      //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
  77. 77      .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
  78. 78      .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
  79. 79 ) igddr3(
  80. 80      .DI              (rgmii_rxd[2]),
  81. 81      .ICLK            (1'd0        ),
  82. 82      .DESCLK          (gmii_rx_clk ),
  83. 83      .RCLK            (gmii_rx_clk ),
  84. 84      .WADDR           (3'd0        ),
  85. 85      .RADDR           (3'd0        ),
  86. 86      .RST             (1'b0        ),
  87. 87      .DO              ({gmii_rxd_s[6],gmii_rxd_s[2],nc3})
  88. 88 );
  89. 89
  90. 90 wire [5:0 nc4;
  91. 91 GTP_ISERDES #(
  92. 92      .ISERDES_MODE    ("IDDR"),  
  93. 93      //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
  94. 94      .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
  95. 95      .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
  96. 96 ) igddr4(
  97. 97      .DI              (rgmii_rxd[3]),
  98. 98      .ICLK            (1'd0        ),
  99. 99      .DESCLK          (gmii_rx_clk ),
  100. 100     .RCLK            (gmii_rx_clk ),
  101. 101     .WADDR           (3'd0        ),
  102. 102     .RADDR           (3'd0        ),
  103. 103     .RST             (1'b0        ),
  104. 104     .DO              ({gmii_rxd_s[7],gmii_rxd_s[3],nc4})
  105. 105 );
  106. 106
  107. 107 wire [5:0 nc5;
  108. 108 GTP_ISERDES #(
  109. 109     .ISERDES_MODE    ("IDDR"),
  110. 110     //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
  111. 111     .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
  112. 112     .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
  113. 113 ) igddr5(
  114. 114     .DI              (rgmii_rx_ctl),
  115. 115     .ICLK            (1'd0        ),
  116. 116     .DESCLK          (gmii_rx_clk ),
  117. 117     .RCLK            (gmii_rx_clk ),
  118. 118     .WADDR           (3'd0        ),
  119. 119     .RADDR           (3'd0        ),
  120. 120     .RST             (1'b0        ),
  121. 121     .DO              ({rgmii_rx_ctl_s,gmii_rx_dv_s,nc5})
  122. 122 );
  123. 123
  124. 124 endmodule
复制代码
该模块通过调用GTP_ISERDES原语,实现了RGMII接口输入的DDR数据到SDR数据的转换,输入的rgmii_rx_ctl控制信号的转换方法同样类似。rgmii_rx模块信号转换示意图如下图所示:
image090.png
图 50.4.2 rgmii_rx模块信号转换示意图
时钟专用引脚输入的rgmii_rxc时钟经过PLL后,得到rgmii_rx_clk。
rgmii_rx_ctl控制信号和4位rgmii_rxd数据先经过GTP_ISERDES(IDDR模式)将双沿1位数据转换成单沿两位数据。
另外,在程序的第40行至122行代码是对GTP_ISERDES的五次例化。
rgmii_tx模块代码如下所示:
  1. 1  module rgmii_tx(
  2. 2       input             reset,
  3. 3       //GMII发送端口
  4. 4       input             gmii_tx_er     ,
  5. 5       input             gmii_tx_clk    , //GMII发送时钟   
  6. 6       input             gmii_tx_en     , //GMII输出数据有效信号
  7. 7       input       [7:0  gmii_txd       , //GMII输出数据   
  8. 8       input             gmii_tx_clk_deg, //GMII发送时钟相位偏移45度
  9. 9       //RGMII发送端口
  10. 10      output            rgmii_txc      , //RGMII发送数据时钟   
  11. 11      output            rgmii_tx_ctl   , //RGMII输出数据有效信号
  12. 12      output      [3:0 rgmii_txd        //RGMII输出数据
  13. 13      );
  14. 14 // registers
  15. 15 reg             tx_reset_d1    ;
  16. 16 reg             tx_reset_sync  ;
  17. 17 reg             rx_reset_d1    ;
  18. 18
  19. 19 reg   [7:0   gmii_txd_r     ;
  20. 20 reg   [7:0   gmii_txd_r_d1  ;
  21. 21
  22. 22 reg             gmii_tx_en_r   ;
  23. 23 reg             gmii_tx_en_r_d1;
  24. 24
  25. 25 reg             gmii_tx_er_r   ;
  26. 26
  27. 27 reg             rgmii_tx_ctl_r ;
  28. 28 reg   [3:0   gmii_txd_low   ;
  29. 29
  30. 30 // wire
  31. 31 wire            padt1  ;
  32. 32 wire            padt2   ;
  33. 33 wire            padt3   ;
  34. 34 wire            padt4   ;
  35. 35 wire            padt5   ;
  36. 36 wire            padt6   ;
  37. 37 wire            stx_txc ;
  38. 38 wire            stx_ctr ;
  39. 39 wire  [3:0     stxd_rgm;
  40. 40 //*****************************************************
  41. 41 //**                    main code
  42. 42 //*****************************************************
  43. 43 always @(posedge gmii_tx_clk) begin
  44. 44      tx_reset_d1   <=reset;
  45. 45      tx_reset_sync <= tx_reset_d1;
  46. 46 end
  47. 47
  48. 48 always @(posedge gmii_tx_clk) begin
  49. 49      if (tx_reset_sync == 1'b1) begin
  50. 50          gmii_txd_r   <=8'h0;
  51. 51          gmii_tx_en_r <= 1'b0;
  52. 52          gmii_tx_er_r <= 1'b0;
  53. 53      end
  54. 54      else
  55. 55      begin
  56. 56          gmii_txd_r      <= gmii_txd;
  57. 57          gmii_tx_en_r    <= gmii_tx_en;
  58. 58          gmii_tx_er_r    <= gmii_tx_er;
  59. 59          gmii_txd_r_d1   <=gmii_txd_r;
  60. 60          gmii_tx_en_r_d1 <= gmii_tx_en_r;
  61. 61      end
  62. 62 end
  63. 63
  64. 64 always @(posedge gmii_tx_clk)
  65. 65 begin
  66. 66      rgmii_tx_ctl_r = gmii_tx_en_r ^ gmii_tx_er_r;
  67. 67      gmii_txd_low   =gmii_txd_r[7:4];
  68. 68 end
  69. 69
  70. 70 //输出双沿采样寄存器 (rgmii_txd)
  71. 71 GTP_OSERDES #(
  72. 72      .OSERDES_MODE    ("ODDR" ),  
  73. 73      //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  74. 74      .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  75. 75      .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  76. 76      .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  77. 77      .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  78. 78 ) gtp_ogddr6(     
  79. 79      .DO              (stx_txc        ),
  80. 80      .TQ              (padt6          ),
  81. 81      .DI              ({7'd0,1'b1}    ),
  82. 82      .TI              (4'd0           ),
  83. 83      .RCLK            (gmii_tx_clk_deg),
  84. 84      .SERCLK          (gmii_tx_clk_deg),
  85. 85      .OCLK            (1'd0           ),
  86. 86      .RST             (tx_reset_sync  )
  87. 87 );
  88. 88 GTP_OUTBUFT  gtp_outbuft6
  89. 89 (
  90. 90      .I    (stx_txc  ),
  91. 91      .T    (padt6    ),
  92. 92      .O    (rgmii_txc)
  93. 93 );
  94. 94
  95. 95
  96. 96 GTP_OSERDES #(
  97. 97      .OSERDES_MODE    ("ODDR" ),  
  98. 98      //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  99. 99      .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  100. 100     .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  101. 101     .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  102. 102     .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  103. 103 ) gtp_ogddr2(     
  104. 104     .DO              (stxd_rgm[3  ),
  105. 105     .TQ              (padt2        ),
  106. 106     .DI              ({6'd0,gmii_txd_low[3],gmii_txd_r_d1[3]}),
  107. 107     .TI              (4'd0         ),
  108. 108     .RCLK            (gmii_tx_clk  ),
  109. 109     .SERCLK          (gmii_tx_clk  ),
  110. 110     .OCLK            (1'd0         ),
  111. 111     .RST             (tx_reset_sync)
  112. 112 );
  113. 113 GTP_OUTBUFT  gtp_outbuft2
  114. 114 (
  115. 115     .I    (stxd_rgm[3]),
  116. 116     .T    (padt2      ),
  117. 117     .O    (rgmii_txd[3])
  118. 118 );
  119. 119
  120. 120
  121. 121 GTP_OSERDES #(
  122. 122     .OSERDES_MODE    ("ODDR" ),  
  123. 123     //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  124. 124     .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  125. 125     .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  126. 126     .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  127. 127     .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  128. 128 ) gtp_ogddr3(     
  129. 129     .DO              (stxd_rgm[2  ),
  130. 130     .TQ              (padt3        ),
  131. 131     .DI              ({6'd0,gmii_txd_low[2],gmii_txd_r_d1[2]}),
  132. 132     .TI              (4'd0         ),
  133. 133     .RCLK            (gmii_tx_clk  ),
  134. 134     .SERCLK          (gmii_tx_clk  ),
  135. 135     .OCLK            (1'd0         ),
  136. 136     .RST             (tx_reset_sync)
  137. 137 );
  138. 138 GTP_OUTBUFT  gtp_outbuft3
  139. 139 (   
  140. 140     .I    (stxd_rgm[2]),
  141. 141     .T    (padt3      ),
  142. 142     .O    (rgmii_txd[2])
  143. 143 );
  144. 144
  145. 145
  146. 146 GTP_OSERDES #(
  147. 147     .OSERDES_MODE    ("ODDR" ),  
  148. 148     //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  149. 149     .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  150. 150     .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  151. 151     .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  152. 152     .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  153. 153 ) gtp_ogddr4(     
  154. 154     .DO              (stxd_rgm[1  ),
  155. 155     .TQ              (padt4        ),
  156. 156     .DI              ({6'd0,gmii_txd_low[1],gmii_txd_r_d1[1]}),
  157. 157     .TI              (4'd0         ),
  158. 158     .RCLK            (gmii_tx_clk  ),
  159. 159     .SERCLK          (gmii_tx_clk  ),
  160. 160     .OCLK            (1'd0         ),
  161. 161     .RST             (tx_reset_sync)
  162. 162 );
  163. 163 GTP_OUTBUFT  gtp_outbuft4
  164. 164 (
  165. 165     .I    (stxd_rgm[1]),
  166. 166     .T    (padt4      ),
  167. 167     .O    (rgmii_txd[1])
  168. 168 );
  169. 169
  170. 170
  171. 171 GTP_OSERDES #(
  172. 172     .OSERDES_MODE    ("ODDR" ),  
  173. 173     //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  174. 174     .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  175. 175     .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  176. 176     .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  177. 177     .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  178. 178 ) gtp_ogddr5(     
  179. 179     .DO              (stxd_rgm[0  ),
  180. 180     .TQ              (padt5        ),
  181. 181     .DI              ({6'd0,gmii_txd_low[0],gmii_txd_r_d1[0]}),
  182. 182     .TI              (4'd0         ),
  183. 183     .RCLK            (gmii_tx_clk  ),
  184. 184     .SERCLK          (gmii_tx_clk  ),
  185. 185     .OCLK            (1'd0         ),
  186. 186     .RST             (tx_reset_sync)
  187. 187 );
  188. 188 GTP_OUTBUFT  gtp_outbuft5
  189. 189 (
  190. 190     .I    (stxd_rgm[0]),
  191. 191     .T    (padt5      ),
  192. 192     .O    (rgmii_txd[0])
  193. 193 );
  194. 194
  195. 195
  196. 196 //输出双沿采样寄存器 (rgmii_tx_ctl)
  197. 197 GTP_OSERDES #(
  198. 198     .OSERDES_MODE    ("ODDR" ),  
  199. 199     //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
  200. 200     .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
  201. 201     .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  202. 202     .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
  203. 203     .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
  204. 204 ) gtp_ogddr1(     
  205. 205     .DO              (stx_ctr      ),
  206. 206     .TQ              (padt1        ),
  207. 207     .DI              ({6'd0,rgmii_tx_ctl_r,gmii_tx_en_r_d1}),
  208. 208     .TI              (4'd0         ),
  209. 209     .RCLK            (gmii_tx_clk  ),
  210. 210     .SERCLK          (gmii_tx_clk  ),
  211. 211     .OCLK            (1'd0         ),
  212. 212     .RST             (tx_reset_sync)
  213. 213 );
  214. 214 GTP_OUTBUFT  gtp_outbuft1
  215. 215 (
  216. 216     .I    (stx_ctr     ),
  217. 217     .T    (padt1       ),
  218. 218     .O    (rgmii_tx_ctl)
  219. 219 );
  220. 220
  221. 221 endmodule
复制代码
该模块通过调用ODDR原语将输入的单沿8位数据(gmii_txd)转换成双沿采样的4位数据(rgmii_txd),gmii_tx_en和rgmii_tx_ctl信号的处理方法同样类似。rgmii_tx模块信号转换示意图如下图所示:
image092.png
图 50.4.3 rgmii_tx模块信号转换示意图
gmii_tx_en数据使能信号、gmii_tx_er输出数据有效信号和8位gmii_txd数据经过GTP_OSERDES的ODDR模式将单沿2位数据转换成双沿1位数据。GTP_OSERDES通常跟GTP_OUTBUF一起使用。
ARP顶层模块实现了整个以太网帧格式与ARP协议的功能,ARP顶层模块例化了ARP接收模块(arp_rx)、ARP发送模块(arp_tx)和CRC校验模块(crc32_d8)。
ARP接收模块(arp_rx):ARP接收模块负责解析以太网的数据,判断目的MAC地址和目的IP地址是否为开发板的地址,然后按照ARP协议将数据解析出来。当解析到正确的ARP数据包后,拉高arp_rx_done信号,持续一个时钟周期。arp_rx_type用于表示ARP数据包的类型,0表示收到ARP请求包,1表示收到ARP应答包。src_mac和src_ip分别是解析出的对端设备MAC地址和IP地址。
ARP发送模块(arp_tx):ARP发送模块根据以太网帧格式和ARP协议发送ARP请求或者ARP应答数据。arp_tx_en和arp_tx_type分别表示ARP发送模块的使能信号和发送ARP类型。dec_mac和dec_ip分别设置对端设备MAC地址和IP地址。
CRC校验模块(crc32_d8):CRC校验模块是对ARP发送模块的数据(不包括前导码和帧起始界定符)做校验,把校验结果值拼在以太网帧格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的)。CRC32校验在FPGA实现的原理是LFSR(Linear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。需要说明的是,本次实验只对发送模块做校验,没有对接收模块做校验。这是由于我们可以直接通过解析出的数据来大致判断接收是否正确,而发送模块必须发送正确的校验数据,否则发送的数据直接被电脑的网卡丢弃,导致ARP请求或者应答失败。
在简介部分我们向大家介绍过,ARP的数据包格式包括前导码+SFD、以太网帧头、ARP数据(包括填充部分数据)和CRC校验。在接收以太网数据的过程中,这些不同部分的数据可以刚好对应状态机的不同状态位,因此我们可以通过状态机来解析以太网的数据。
ARP接收模块通过状态机来解析数据,其状态跳转图如下图所示:
image095.png
图 50.4.4 ARP接收模块状态跳转图
接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、MAC地址错误以及IP地址等错误时跳转到st_rx_end状态,而不是跳转到st_idle状态。因为中间状态在解析到数据错误时,单包数据的接收还没有结束,如果此时跳转到st_idle状态会误把有效数据当成前导码来解析,所以状态跳转到st_rx_end。而gmii_rx_dv信号为0时,单包数据才算接收结束,所以st_rx_end跳转到st_idle的条件是eth_rxdv=0,准备接收下一包数据。因为代码较长,只粘贴了第三段状态机的接收ARP数据状态和接收结束状态源代码,代码如下:
  1. 186            st_arp_data : begin
  2. 187                 if(gmii_rx_dv) begin
  3. 188                     cnt <= cnt + 5'd1;
  4. 189                     if(cnt == 5'd6)
  5. 190                         op_data[15:8] <= gmii_rxd;           //操作码      
  6. 191                     else if(cnt == 5'd7)
  7. 192                         op_data[7:0] <= gmii_rxd;
  8. 193                     else if(cnt >= 5'd8 && cnt < 5'd14)      //源MAC地址
  9. 194                         src_mac_t <= {src_mac_t[39:0],gmii_rxd};
  10. 195                     else if(cnt >= 5'd14 && cnt < 5'd18)     //源IP地址
  11. 196                         src_ip_t<= {src_ip_t[23:0],gmii_rxd};
  12. 197                     else if(cnt >= 5'd24 && cnt < 5'd28)     //目标IP地址
  13. 198                         des_ip_t <= {des_ip_t[23:0],gmii_rxd};
  14. 199                     else if(cnt == 5'd28) begin
  15. 200                         cnt <= 5'd0;
  16. 201                         if(des_ip_t == BOARD_IP) begin       //判断目的IP地址和操作码
  17. 202                             if((op_data == 16'd1) || (op_data == 16'd2)) begin
  18. 203                                 skip_en <= 1'b1;
  19. 204                                 rx_done_t <= 1'b1;
  20. 205                                 src_mac <= src_mac_t;
  21. 206                                 src_ip <= src_ip_t;
  22. 207                                 src_mac_t <= 48'd0;
  23. 208                                 src_ip_t <= 32'd0;
  24. 209                                 des_mac_t <= 48'd0;
  25. 210                                 des_ip_t <= 32'd0;
  26. 211                                 if(op_data == 16'd1)         
  27. 212                                    arp_rx_type <= 1'b0;     //ARP请求
  28. 213                                 else
  29. 214                                    arp_rx_type <= 1'b1;     //ARP应答
  30. 215                             end
  31. 216                             else
  32. 217                                 error_en <= 1'b1;
  33. 218                         end
  34. 219                         else
  35. 220                             error_en <= 1'b1;
  36. 221                     end
  37. 222                 end                                
  38. 223             end
  39. 224             st_rx_end : begin     
  40. 225                 cnt <= 5'd0;
  41. 226                 //单包数据接收完成   
  42. 227                 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
  43. 228                     skip_en <= 1'b1;
  44. 229             end
复制代码
st_arp_data状态根据ARP协议解析数据,在程序的第201行至第202行代码判断目的IP地址和OP操作码是否正确,如果错误,则丢弃该包数据。在程序的第211行至第214行代码根据操作码为arp_rx_type(接收到的ARP数据包类型)赋值,当接收到ARP请求包时,arp_rx_type等于0;当接收到ARP应答包时,arp_rx_type等于1。
ARP接收过程中仿真的波形图如图 50.4.5所示,gmii_rx_dv信号拉高表示此时输入的数据有效,根据gmii_rxd的值来解析数据。从图中可以看出,发送端的MAC地址和ip地址,以及当前接收到的以太网数据包类型为ARP(0x0806)。在接收完ARP数据包之后,拉高arp_rx_done信号表示接收完成,图中arp_rx_type信号为低电平,表示当前接收到的是ARP请求数据包。
arp的仿真代码如下图所示:
  1. 1  module  tb_arp;
  2. 2  
  3. 3  //parameter  define
  4. 4  parameter  T = 8;                      //时钟周期为8ns
  5. 5  parameter  OP_CYCLE = 100;              //操作周期
  6. 6  
  7. 7  //开发板MAC地址00-11-22-33-44-55
  8. 8  parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
  9. 9  //开发板IP地址192.168.1.10     
  10. 10 parameter  BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
  11. 11 //目的MAC地址ff_ff_ff_ff_ff_ff
  12. 12 parameter  DES_MAC  = 48'hff_ff_ff_ff_ff_ff;
  13. 13 //目的IP地址192.168.1.10
  14. 14 parameter  DES_IP   = {8'd192,8'd168,8'd1,8'd10};
  15. 15
  16. 16 //reg define
  17. 17 reg           gmii_clk;    //时钟信号
  18. 18 reg           sys_rst_n;   //复位信号
  19. 19
  20. 20 reg           arp_tx_en  ; //ARP发送使能信号
  21. 21 reg           arp_tx_type; //ARP发送类型 0:请求  1:应答
  22. 22 reg   [3:0  flow_cnt   ;
  23. 23 reg   [13:0 delay_cnt  ;
  24. 24
  25. 25 wire          gmii_rx_clk; //GMII接收时钟
  26. 26 wire          gmii_rx_dv ; //GMII接收数据有效信号
  27. 27 wire  [7:0   gmii_rxd  ; //GMII接收数据
  28. 28 wire          gmii_tx_clk; //GMII发送时钟
  29. 29 wire          gmii_tx_en ; //GMII发送数据使能信号
  30. 30 wire  [7:0   gmii_txd  ; //GMII发送数据
  31. 31               
  32. 32 wire          arp_rx_done; //ARP接收完成信号
  33. 33 wire          arp_rx_type; //ARP接收类型 0:请求  1:应答
  34. 34 wire  [47:0  src_mac    ; //接收到目的MAC地址
  35. 35 wire  [31:0  src_ip     ; //接收到目的IP地址   
  36. 36 wire  [47:0  des_mac    ; //发送的目标MAC地址
  37. 37 wire  [31:0  des_ip     ; //发送的目标IP地址
  38. 38 wire          tx_done    ; //以太网发送完成信号
  39. 39
  40. 40 //*****************************************************
  41. 41 //**                    main code
  42. 42 //*****************************************************
  43. 43
  44. 44 assign gmii_rx_clk = gmii_clk   ;
  45. 45 assign gmii_tx_clk = gmii_clk   ;
  46. 46 assign gmii_rx_dv  = gmii_tx_en ;
  47. 47 assign gmii_rxd    = gmii_txd   ;
  48. 48
  49. 49 assign des_mac = src_mac;
  50. 50 assign des_ip  = src_ip;
  51. 51
  52. 52 //给输入信号初始值
  53. 53 initial begin
  54. 54      gmii_clk           = 1'b0;
  55. 55      sys_rst_n          = 1'b0;     //复位
  56. 56      #(T+1)  sys_rst_n = 1'b1;     //在第(T+1)ns的时候复位信号信号拉高
  57. 57 end
  58. 58
  59. 59 //125Mhz的时钟,周期则为1/125Mhz=8ns,所以每4ns,电平取反一次
  60. 60 always #(T/2) gmii_clk = ~gmii_clk;
  61. 61
  62. 62 always @(posedge gmii_clk or negedge sys_rst_n) begin
  63. 63      if(!sys_rst_n) begin
  64. 64          arp_tx_en <= 1'b0;
  65. 65          arp_tx_type <= 1'b0;
  66. 66          delay_cnt <= 1'b0;
  67. 67          flow_cnt <= 1'b0;
  68. 68      end
  69. 69      else begin
  70. 70          case(flow_cnt)
  71. 71              'd0 : flow_cnt <= flow_cnt + 1'b1;
  72. 72              'd1 : begin
  73. 73                  arp_tx_en <= 1'b1;
  74. 74                  arp_tx_type <= 1'b0;  //发送ARP请求
  75. 75                  flow_cnt <= flow_cnt + 1'b1;
  76. 76              end
  77. 77              'd2 : begin
  78. 78                  arp_tx_en <= 1'b0;
  79. 79                  flow_cnt <= flow_cnt + 1'b1;
  80. 80              end   
  81. 81              'd3 : begin
  82. 82                  if(tx_done)
  83. 83                      flow_cnt <= flow_cnt + 1'b1;
  84. 84              end
  85. 85              'd4 : begin
  86. 86                  delay_cnt <= delay_cnt + 1'b1;
  87. 87                  if(delay_cnt == OP_CYCLE - 1'b1)
  88. 88                      flow_cnt <= flow_cnt + 1'b1;
  89. 89              end
  90. 90              'd5 : begin
  91. 91                  arp_tx_en <= 1'b1;
  92. 92                  arp_tx_type <= 1'b1;  //发送ARP应答   
  93. 93                  flow_cnt <= flow_cnt + 1'b1;               
  94. 94              end
  95. 95              'd6 : begin
  96. 96                  arp_tx_en <= 1'b0;
  97. 97                  flow_cnt <= flow_cnt + 1'b1;
  98. 98              end
  99. 99              'd7 : begin
  100. 100                 if(tx_done)
  101. 101                     flow_cnt <= flow_cnt + 1'b1;
  102. 102             end
  103. 103             default:;
  104. 104         endcase   
  105. 105     end
  106. 106 end
  107. 107
  108. 108 //ARP通信
  109. 109 arp                                             
  110. 110   #(
  111. 111     .BOARD_MAC     (BOARD_MAC),      //参数例化
  112. 112     .BOARD_IP      (BOARD_IP ),
  113. 113     .DES_MAC       (DES_MAC  ),
  114. 114     .DES_IP        (DES_IP   )
  115. 115     )
  116. 116   u_arp(
  117. 117     .rst_n         (sys_rst_n  ),
  118. 118                     
  119. 119     .gmii_rx_clk   (gmii_rx_clk),
  120. 120     .gmii_rx_dv    (gmii_rx_dv ),
  121. 121     .gmii_rxd      (gmii_rxd   ),
  122. 122     .gmii_tx_clk   (gmii_tx_clk),
  123. 123     .gmii_tx_en    (gmii_tx_en ),
  124. 124     .gmii_txd      (gmii_txd   ),
  125. 125                     
  126. 126     .arp_rx_done   (arp_rx_done),
  127. 127     .arp_rx_type   (arp_rx_type),
  128. 128     .src_mac       (src_mac    ),
  129. 129     .src_ip        (src_ip     ),
  130. 130     .arp_tx_en     (arp_tx_en  ),
  131. 131     .arp_tx_type   (arp_tx_type),
  132. 132     .des_mac       (des_mac    ),
  133. 133     .des_ip        (des_ip     ),
  134. 134     .tx_done       (tx_done    )
  135. 135     );
  136. 136
  137. 137 endmodule
复制代码
第14行代码将目的ip地址改写成与开发板的ip地址一样,因为仿真的原理是arp的环回,既将arp的发送数据直接发送给arp的接收数据,如果两个ip地址不一样error_en信号会拉高报错。  
image096.png
图 50.4.5 ARP接收采集波形图
ARP发送模块则是根据以太网帧格式是ARP协议发送数据,也就是接收模块的逆过程,同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
image099.png
图 50.4.6 ARP发送模块状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储前导码+帧头、以太网的帧头、ARP数据,在复位时初始化数组的值,部分源代码如下。
  1. 66  reg  [7:0]  preamble[7:0] ; //前导码+SFD
  2. 67  reg  [7:0]  eth_head[13:0]; //以太网首部
  3. 68  reg  [7:0]  arp_data[27:0]; //ARP数据
复制代码
省略部分代码……
  1. 155         //初始化数组   
  2. 156         //前导码 7个8'h55 + 1个8'hd5
  3. 157         preamble[0] <= 8'h55;               
  4. 158         preamble[1] <= 8'h55;
  5. 159         preamble[2] <= 8'h55;
  6. 160         preamble[3] <= 8'h55;
  7. 161         preamble[4] <= 8'h55;
  8. 162         preamble[5] <= 8'h55;
  9. 163         preamble[6] <= 8'h55;
  10. 164         preamble[7] <= 8'hd5;
  11. 165         //以太网帧头
  12. 166         eth_head[0] <= DES_MAC[47:40];      //目的MAC地址
  13. 167         eth_head[1] <= DES_MAC[39:32];
  14. 168         eth_head[2] <= DES_MAC[31:24];
  15. 169         eth_head[3] <= DES_MAC[23:16];
  16. 170         eth_head[4] <= DES_MAC[15:8];
  17. 171         eth_head[5] <= DES_MAC[7:0];        
  18. 172         eth_head[6] <= BOARD_MAC[47:40];    //源MAC地址
  19. 173         eth_head[7] <= BOARD_MAC[39:32];   
  20. 174         eth_head[8] <= BOARD_MAC[31:24];   
  21. 175         eth_head[9] <= BOARD_MAC[23:16];   
  22. 176         eth_head[10] <= BOARD_MAC[15:8];   
  23. 177         eth_head[11] <= BOARD_MAC[7:0];     
  24. 178         eth_head[12] <= ETH_TYPE[15:8];     //以太网帧类型
  25. 179         eth_head[13] <= ETH_TYPE[7:0];      
  26. 180         //ARP数据                           
  27. 181         arp_data[0] <= HD_TYPE[15:8];       //硬件类型
  28. 182         arp_data[1] <= HD_TYPE[7:0];
  29. 183         arp_data[2] <= PROTOCOL_TYPE[15:8]; //上层协议类型
  30. 184         arp_data[3] <= PROTOCOL_TYPE[7:0];
  31. 185         arp_data[4] <= 8'h06;               //硬件地址长度,6
  32. 186         arp_data[5] <= 8'h04;               //协议地址长度,4
  33. 187         arp_data[6] <= 8'h00;               //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
  34. 188         arp_data[7] <= 8'h01;
  35. 189         arp_data[8] <= BOARD_MAC[47:40];    //发送端(源)MAC地址
  36. 190         arp_data[9] <= BOARD_MAC[39:32];
  37. 191         arp_data[10] <= BOARD_MAC[31:24];
  38. 192         arp_data[11] <= BOARD_MAC[23:16];
  39. 193         arp_data[12] <= BOARD_MAC[15:8];
  40. 194         arp_data[13] <= BOARD_MAC[7:0];
  41. 195         arp_data[14] <= BOARD_IP[31:24];    //发送端(源)IP地址
  42. 196         arp_data[15] <= BOARD_IP[23:16];
  43. 197         arp_data[16] <= BOARD_IP[15:8];
  44. 198         arp_data[17] <= BOARD_IP[7:0];
  45. 199         arp_data[18] <= DES_MAC[47:40];     //接收端(目的)MAC地址
  46. 200         arp_data[19] <= DES_MAC[39:32];
  47. 201         arp_data[20] <= DES_MAC[31:24];
  48. 202         arp_data[21] <= DES_MAC[23:16];
  49. 203         arp_data[22] <= DES_MAC[15:8];
  50. 204         arp_data[23] <= DES_MAC[7:0];  
  51. 205         arp_data[24] <= DES_IP[31:24];      //接收端(目的)IP地址
  52. 206         arp_data[25] <= DES_IP[23:16];
  53. 207         arp_data[26] <= DES_IP[15:8];
  54. 208         arp_data[27] <= DES_IP[7:0];
  55. 以上代码在复位时对数组进行初始化。
  56. 216             st_idle : begin
  57. 217                 if(pos_tx_en) begin
  58. 218                     skip_en <= 1'b1;  
  59. 219                     //如果目标MAC地址和IP地址已经更新,则发送正确的地址
  60. 220                     if((des_mac != 48'b0) || (des_ip != 32'd0)) begin
  61. 221                         eth_head[0] <= des_mac[47:40];
  62. 222                         eth_head[1] <= des_mac[39:32];
  63. 223                         eth_head[2] <= des_mac[31:24];
  64. 224                         eth_head[3] <= des_mac[23:16];
  65. 225                         eth_head[4] <= des_mac[15:8];
  66. 226                         eth_head[5] <= des_mac[7:0];  
  67. 227                         arp_data[18] <= des_mac[47:40];
  68. 228                         arp_data[19] <= des_mac[39:32];
  69. 229                         arp_data[20] <= des_mac[31:24];
  70. 230                         arp_data[21] <= des_mac[23:16];
  71. 231                         arp_data[22] <= des_mac[15:8];
  72. 232                         arp_data[23] <= des_mac[7:0];  
  73. 233                         arp_data[24] <= des_ip[31:24];
  74. 234                         arp_data[25] <= des_ip[23:16];
  75. 235                         arp_data[26] <= des_ip[15:8];
  76. 236                         arp_data[27] <= des_ip[7:0];
  77. 237                     end
  78. 238                     if(arp_tx_type == 1'b0)
  79. 239                         arp_data[7] <= 8'h01;            //ARP请求
  80. 240                     else
  81. 241                         arp_data[7] <= 8'h02;            //ARP应答
  82. 242                 end   
  83. 243             end
复制代码
在程序的第220行至241行代码,根据输入的发送类型、目的MAC地址和IP地址,重新更新数组里的值。            
  1. 265             st_arp_data : begin                          //发送ARP数据                           
  2. 266                 crc_en <= 1'b1;                                                            
  3. 267                 gmii_tx_en <= 1'b1;                                                         
  4. 268                 //至少发送46个字节                                                                 
  5. 269                 if (cnt == MIN_DATA_NUM - 1'b1) begin                                       
  6. 270                     skip_en <= 1'b1;                                                        
  7. 271                     cnt <= 1'b0;                                                            
  8. 272                     data_cnt <= 1'b0;                                                      
  9. 273                 end                                                                        
  10. 274                 else                                                                        
  11. 275                     cnt <= cnt + 1'b1;                                                      
  12. 276                 if(data_cnt <=6'd27) begin                                                  
  13. 277                     data_cnt <= data_cnt + 1'b1;                                             
  14. 278                     gmii_txd <= arp_data[data_cnt];                                          
  15. 279                 end                                                                        
  16. 280                 else                                                                        
  17. 281                     gmii_txd <= 8'd0;                   //Padding,填充0                       
  18. 282             end
复制代码
程序第265行至第282行代码为发送ARP数据的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,ARP数据只有28个字节,因此在发送完ARP数据之后补充发送18个字节,填充的数据为0。
  1. 283             st_crc      : begin                          //发送CRC校验值                          
  2. 284                 gmii_tx_en <= 1'b1;                                                         
  3. 285                 cnt <= cnt + 1'b1;                                                         
  4. 286                 if(cnt == 6'd0)                                                              
  5. 287                     gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],      
  6. 288                                  ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};     
  7. 289                 else if(cnt == 6'd1)                                                        
  8. 290                     gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],               
  9. 291                                  ~crc_data[19], ~crc_data[20], ~crc_data[21],               
  10. 292                                  ~crc_data[22],~crc_data[23]};                              
  11. 293                 else if(cnt == 6'd2) begin                                                  
  12. 294                     gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],                  
  13. 295                                  ~crc_data[11],~crc_data[12], ~crc_data[13],                 
  14. 296                                  ~crc_data[14],~crc_data[15]};                              
  15. 297                 end                                                                        
  16. 298                 else if(cnt == 6'd3) begin                                                  
  17. 299                     gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],      
  18. 300                                  ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};     
  19. 301                     tx_done_t <= 1'b1;                                                      
  20. 302                     skip_en <= 1'b1;                                                        
  21. 303                     cnt <= 1'b0;                                                            
  22. 304                 end                                                                                                                                            
  23. 305             end
复制代码
程序的第283行至305行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d8模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去,crc计算部分在后面阐述。
ARP发送过程中采集的波形图如图 50.4.7所示,arp_tx_en信号作为开始发送ARP数据包的触发信号,arp_tx_type为高电平,表示发送ARP应答数据包。ARP应答数据包中的目的MAC地址和目的IP地址从ARP接收数据包中获取,gmii_tx_en拉高,表示gmii_txd数据有效,在发送完ARP数据包后,输出一个脉冲信号(tx_done),表示发送完成。
image100.png
图 50.4.7 ARP发送的波形图
CRC校验模块主要完成对ARP发送模块数据的校验,由于代码较长,这里不再贴出代码。CRC32校验在FPGA实现的原理是线性反馈移位寄存器,其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。CRC32的原理与公式推导较复杂,在此可不必深究。CRC32的生成多项式为:G(x)= x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 +x^7 + x^5 + x^4 + x^2 + x^1 + 1,只需稍作修改就可以直接使用。
ARP控制模块的代码如下:
  1. 1  module arp_ctrl(
  2. 2      input                clk        , //输入时钟   
  3. 3      input                rst_n      , //复位信号,低电平有效
  4. 4      
  5. 5      input                touch_key  , //触摸按键,用于触发开发板发出ARP请求
  6. 6      input                arp_rx_done, //ARP接收完成信号
  7. 7      input                arp_rx_type, //ARP接收类型 0:请求  1:应答
  8. 8      output  reg          arp_tx_en  , //ARP发送使能信号
  9. 9      output  reg          arp_tx_type  //ARP发送类型0:请求  1:应答
  10. 10     );
  11. 11
  12. 12 //reg define
  13. 13 reg         touch_key_d0;
  14. 14 reg         touch_key_d1;
  15. 15
  16. 16 //wire define
  17. 17 wire        pos_touch_key;  //touch_key信号上升沿
  18. 18
  19. 19 //*****************************************************
  20. 20 //**                    main code
  21. 21 //*****************************************************
  22. 22
  23. 23 assign pos_touch_key = ~touch_key_d1 & touch_key_d0;
  24. 24
  25. 25 //对arp_tx_en信号延时打拍两次,用于采touch_key的上升沿
  26. 26 always @(posedge clk or negedge rst_n) begin
  27. 27     if(!rst_n) begin
  28. 28         touch_key_d0 <= 1'b0;
  29. 29         touch_key_d1 <= 1'b0;
  30. 30     end
  31. 31     else begin
  32. 32         touch_key_d0 <= touch_key;
  33. 33         touch_key_d1 <= touch_key_d0;
  34. 34     end
  35. 35 end
  36. 36
  37. 37 //为arp_tx_en和arp_tx_type赋值
  38. 38 always @(posedge clk or negedge rst_n) begin
  39. 39     if(!rst_n) begin
  40. 40         arp_tx_en <= 1'b0;
  41. 41         arp_tx_type <= 1'b0;
  42. 42     end
  43. 43     else begin
  44. 44         if(pos_touch_key == 1'b1) begin  //检测到输入触摸按键上升沿
  45. 45             arp_tx_en <= 1'b1;           
  46. 46             arp_tx_type <= 1'b0;
  47. 47         end
  48. 48         //接收到ARP请求,开始控制ARP发送模块应答
  49. 49         else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
  50. 50             arp_tx_en <= 1'b1;
  51. 51             arp_tx_type <= 1'b1;
  52. 52         end
  53. 53         else
  54. 54             arp_tx_en <= 1'b0;
  55. 55     end
  56. 56 end
  57. 57
  58. 58 endmodule
复制代码
ARP控制模块的代码较简单,首先检测输入触摸按键的上升沿,当检测到上升沿之后,触发ARP顶层模块发起ARP请求信号;同时检测输入的arp_rx_done和arp_rx_type信号,当接收上位机的ARP请求信号后,触发ARP顶层模块发送ARP应答信号,将开发板的MAC地址发送给上位机。

1.5 下载验证
编译工程并生成比特流.sbit文件后,此时将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,网口的位置如下图所示。
image102.png
图 50.5.1网口位置
点击PDS工具栏的下载按钮,在弹出的Fabric Configuration界面中双击“Boundary Scan”,我们将生成好的sbit流文件下载到开发板中去。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
image104.png
图 50.5.2 点击网络图标
点击图 50.5.2中的“未识别的网络(无Internet)”,弹出如下图所示界面。
image106.png
图 50.5.3 网络设置界面
点击“更改适配器”选项,弹出如下图所示界面。
image108.png
图 50.5.4 “网络适配器界面”
如果看到上图“以太网”显示未识别的网络之后,说明硬件连接和程序都是没有问题的,接下来设置以太网的IP地址,改成代码中设置的目的IP地址,顶层模块参数定义如下:
  1. //目的IP地址 192.168.1.102
  2. parameter  DES_IP   = {8'd192,8'd168,8'd1,8'd102};
复制代码
因此接下来将电脑以太网的IP地址设置成192.168.1.102。鼠标右击图 50.5.4中的以太网,如下图所示:
image110.png
图 50.5.5 鼠标右击“以太网”
点击“属性”,弹出如下图所示界面。
image112.png
图 50.5.6 以太网“属性”界面
鼠标双击“Internet协议版本4(TCP/IPv4)”,弹出如下图所示界面。
image114.png
图 50.5.7 设置以太网IP地址
在“Internet协议版本4(TCP/IPv4)”属性界面中,选择使用下面的IP地址,IP地址设置成192.168.1.102,并点击确定完成设置。
接下来以管理员身份打开电脑的命令的DOS命令窗口(注意必须以管理员身份打开),打开方式如下:
image116.png
图 50.5.8 打开电脑DOS命令窗口
打开DOS命令窗口后,在命令行中输入“arp -a”,如下图所示:
image118.png
图 50.5.9 输入命令“arp -a”
输入完成后,按下键盘的回车键,此时会弹出电脑中所有网络接口的ARP缓存表,我们只需要关注以太网接口的ARP缓存表(IP地址为192.168.1.102),如下图所示:
image120.png
图 50.5.10 以太网接口ARP缓存表
可以发现,此时ARP缓存表中还没有开发板的MAC地址和IP地址,此时我们按下开发板的触摸按键(TPAD)。按下后,开发板会向电脑发起ARP请求,并且电脑会返回自己的MAC地址到开发板。
需要说明的是,在开发板发起ARP请求时,会将开发板的MAC地址和IP地址都发给电脑,此时电脑就已经获取到了开发板的MAC地址和IP地址,并更新至ARP的缓存表中,我们重新在DOS命令中输入“arp -a”,如图 50.5.11和图 50.5.12所示。
image122.png
图 50.5.11 输入“arp -a”
image124.png
图 50.5.12 开发板的MAC地址更新至缓存表中
由上图可知,此时以太网接口的ARP缓存表中已经添加了开发板的IP地址(192.168.1.10)和MAC地址(00-11-22-33-44-55),说明开发板发送ARP请求成功。如果大家操作失败,请检查开发板的网口是否通过网线连接电脑的网口,并且此时开发板已经下载程序以及通过按下触摸按键(TPAD)来触发ARP请求。另外,如果电脑网口不支持千兆网通信,那么也会导致ARP操作失败。
接下来我们再来通过电脑发起ARP请求,验证开发板有没有正确返回ARP应答。我们先从以太网ARP缓存表中删除开发板的MAC地址,删除方法是在DOS命令中输入“arp -d”,并按下按键的回车键,如下图所示:
image126.png
图 50.5.13 输入“arp -d”
接下来重新在DOS命令中输入“arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示;
image128.png
图 50.5.14 删除开发板MAC地址
此时我们之前获取的开发板MAC地址已经删除成功,接下来在DOS命令中输入“ping 192.168.1.10”,来让电脑发起ARP请求,如下图所示。
image130.png
图 50.5.15 电脑发起“ARP”请求
需要说明的是,ping是一个十分强大的TCP/IP工具,它可以用来检测网络的连通情况和分析网络速度。ping命令是一个固定格式的ICMP(Internet控制报文协议)请求数据包,之后会发起ARP请求命令,所以我们这里是通过ping命令来间接发起ARP请求。由于开发板并没有实现ICMP协议,因此在ping时会请求超时,但是在ping的过程中发起的ARP请求,开发板会响应并返回ARP应答数据。
接下来再次在DOS命令中输入“arp -a”,查询是否成功获取到开发板MAC地址,如下图所示:
image132.png
图 50.5.16 开发板MAC地址获取成功
由上图可知,电脑正确获取到开发板的MAC地址,并更新至ARP缓存表中。到这里,开发板实现的ARP协议就已经全部验证成功了。
接下来介绍一个以太网通信时经常使用的抓包软件,该软件位于开发板所随附的资料“6_软件资料/1_软件/Wireshark”目录下,也可以直接在网上搜索下载,我们现在打开Wireshark,界面如下图所示:
image134.png
图 50.5.17 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
image136.png
图 50.5.18 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的触摸按键(TPAD),就可以在wireshark中抓取到数据包了,抓取到的数据包如下图所示:
image138.png
图 50.5.19 wireshark抓取到的数据包
上图中第38行数据包是开发板发送给电脑的ARP请求包,第39行数据包是电脑发送给开发板的ARP应答包,此时双击第38行即可看到开发板发送的详细数据,如下图所示:
image140.png
图 50.5.20 wireshark抓取到的详细数据
上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,后面的18个0就是我们在发送时填充的18个字节数据。需要说明的是,当打开第39行电脑返回的ARP请求包时,看不到填充的0,这是由于后面填充的数据是网卡自动填充的,因此wireshark中会看不到。

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 17:10

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表