谷动谷力

标题: 基于FPGA的以太网控制器设计(附代码) [打印本页]

作者: sunsili    时间: 2023-12-23 14:43
标题: 基于FPGA的以太网控制器设计(附代码)
基于FPGA的以太网控制器设计(附代码)


大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。


今天给大侠带来基于FPGA的以太网控制器(MAC)设计,由于篇幅较长,分三篇。今天带来第三篇,下篇,程序的仿真与测试和总结。话不多说,上货。
这里也给出前两篇的超链接:
基于FPGA的以太网控制器(MAC)设计(上)
基于FPGA的以太网控制器(MAC)设计(中)





导读


当前,互联网已经极大地改变了我们的生产和生活。与之相适应的,在嵌入式系统的研究开发方面,也越来越重视网络功能。嵌入式系统已经不再局限于一个个孤立的控制、处理单元,而是走向网络集成化,从而实现了多个系统的集中控制、信息共享。
以太网(Ethernet)技术在嵌入式系统上的开发应用,已经成为当前嵌入式研究领域的技术热点之一。一方面,与传统的 RS-485、CAN 等相比较,以太网更加高速、通用,而且还可以直接与 Internet 相连接,提供更大范围的远程访问;此外,经过适当剪裁和优化的 TCP/IP 协议栈,也完全可以适应工业用途的需求。另一方面,相对于新兴的 USB 2.0、IEEE 1394 等总线,以太网技术在传输距离、布线成本以及控制软件的通用性上都有明显的优势。
基于以太网的嵌入式系统,在以下方面都有良好的应用前景:
• 工业:工业控制、网络仪表、远程的分布式数据采集……
• 家庭自动化:智能家庭、信息家电、家庭网关……
• 商业:远程销售平台、智能自动售货机、公共电话卡发行系统……
• 环保:水源和空气污染监测,防洪体系及水土质量监测、堤坝安全……
• 其他:交通管理、车辆导航、自动抄表……

因此在使用 FPGA 设计各种嵌入式应用系统时,需要考虑为系统提供以太网接口。本章将 通过 FPGA 实现一个以太网控制器(MAC)的实例,详细介绍实现过程。

第三篇内容摘要:本篇会介绍程序的仿真与测试和总结,包括顶层程序、外部 PHY 芯片模拟程序、仿真结果等相关内容。



四、程序的仿真与测试


上面已经介绍了程序的主要部分。为了检验程序是否实现预先设定的功能,需要编写仿真程序。
以太网控制器的仿真程序(Testbench)需要同时模拟数据通信的两端:主机(上层协议)和外部 PHY 芯片。因此,设计仿真程序的结构如图 12 所示。

图 12 以太网控制器程序 Testbench 的结构
从图 12 上可以看到仿真程序应该包括:顶层程序、模拟 PHY 程序、模拟主机程序和以太网控制程序。

4.1 顶层程序
顶层程序负责连接仿真程序的各个部分:模拟 PHY 程序、模拟主机程序和以太网控制程序。同时顶层程序需要控制仿真的进行,主要代码如下:
  1. `include "eth_phy_defines.v"
  2. `include "wb_model_defines.v"
  3. `include "tb_eth_defines.v"
  4. `include "eth_defines.v"
  5. `include "timescale.v"
  6. module tb_ethernet();

  7. //寄存器与连线
  8.   reg wb_clk;
  9.   ……

  10. //连接以太网控制器
  11. eth_top eth_top(
  12.                 .wb_clk_i(wb_clk), .wb_rst_i(wb_rst),
  13.                 .wb_adr_i(eth_sl_wb_adr_i[11:2]), .wb_sel_i(eth_sl_wb_sel_i), .wb_we_i(eth_sl_wb_we_i),
  14.                 .wb_cyc_i(eth_sl_wb_cyc_i), .wb_stb_i(eth_sl_wb_stb_i), .wb_ack_o(eth_sl_wb_ack_o),
  15.                 .wb_err_o(eth_sl_wb_err_o), .wb_dat_i(eth_sl_wb_dat_i), .wb_dat_o(eth_sl_wb_dat_o),
  16.                 .m_wb_adr_o(eth_ma_wb_adr_o), .m_wb_sel_o(eth_ma_wb_sel_o), .m_wb_we_o(eth_ma_wb_we_o), .m_wb_dat_i(eth_ma_wb_dat_i), .m_wb_dat_o(eth_ma_wb_dat_o), .m_wb_cyc_o(eth_ma_wb_cyc_o),
  17.                 .m_wb_stb_o(eth_ma_wb_stb_o), .m_wb_ack_i(eth_ma_wb_ack_i), .m_wb_err_i(eth_ma_wb_err_i),
  18.                 //发送数据
  19.                 .mtx_clk_pad_i(mtx_clk), .mtxd_pad_o(MTxD), .mtxen_pad_o(MTxEn), .mtxerr_pad_o(MTxErr),
  20.                 //接收数据部分
  21.                 .mrx_clk_pad_i(mrx_clk), .mrxd_pad_i(MRxD), .mrxdv_pad_i(MRxDV), .mrxerr_pad_i(MRxErr),
  22.                 .mcoll_pad_i(MColl), .mcrs_pad_i(MCrs),
  23.                 //媒体无关接口模块
  24.                 .mdc_pad_o(Mdc_O), .md_pad_i(Mdi_I), .md_pad_o(Mdo_O), .md_padoe_o(Mdo_OE),
  25.                 .int_o(wb_int)
  26.                 )

  27. //连接模拟 PHY 部分
  28.   assign Mdio_IO = Mdo_OE ? Mdo_O : 1'bz ;
  29.   assign Mdi_I = Mdio_IO;

  30.   integer phy_log_file_desc;

  31.   eth_phy eth_phy(
  32.                   .m_rst_n_i(!wb_rst),
  33.                   // MAC 发送数据
  34.                   .mtx_clk_o(mtx_clk), .mtxd_i(MTxD), .mtxen_i(MTxEn), .mtxerr_i(MTxErr),
  35.                   // MAC 接收数据
  36.                   .mrx_clk_o(mrx_clk), .mrxd_o(MRxD), .mrxdv_o(MRxDV), .mrxerr_o(MRxErr),
  37.                   .mcoll_o(MColl), .mcrs_o(MCrs),
  38.                   //媒体无关接口模块
  39.                   .mdc_i(Mdc_O), .md_io(Mdio_IO),
  40.                   .phy_log(phy_log_file_desc)
  41.                  );
  42.                  
  43. // 连接主机模块
  44.   integer host_log_file_desc;
  45.   WB_MASTER_BEHAVIORAL wb_master(
  46.                                   .CLK_I(wb_clk),
  47.                                   .RST_I(wb_rst),
  48.                                   .TAG_I({`WB_TAG_WIDTH{1'b0}}),
  49.                                   .TAG_O(),
  50.                                   .ACK_I(eth_sl_wb_ack_o),
  51.                                   .ADR_O(eth_sl_wb_adr), // only eth_sl_wb_adr_i[11:2] used
  52.                                   .CYC_O(eth_sl_wb_cyc_i),
  53.                                   .DAT_I(eth_sl_wb_dat_o),
  54.                                   .DAT_O(eth_sl_wb_dat_i),
  55.                                   .ERR_I(eth_sl_wb_err_o),
  56.                                   .RTY_I(1'b0), // inactive (1'b0)
  57.                                   .SEL_O(eth_sl_wb_sel_i),
  58.                                   .STB_O(eth_sl_wb_stb_i),
  59.                                   .WE_O (eth_sl_wb_we_i),
  60.                                   .CAB_O() // NOT USED for now!
  61.                                   )

  62.   assign eth_sl_wb_adr_i = {20'h0, eth_sl_wb_adr[11:2], 2'h0};
  63.   ……

  64. //初始化
  65.   initial
  66.   begin
  67.   //复位信号
  68.   wb_rst = 1'b1;
  69.   #423 wb_rst = 1'b0;
  70. //清除存储器内容
  71.   clear_memories;
  72.   clear_buffer_descriptors;
  73.   #423 StartTB = 1'b1;
  74.   end

  75. //产生时钟信号
  76.   initial
  77.   begin
  78.   wb_clk=0;
  79.   forever #15 wb_clk = ~wb_clk; // 2*10 ns -> 33.3 MHz
  80.   end

  81.   integer tests_successfull;
  82.   integer tests_failed;
  83.   reg [799:0] test_name; // used for tb_log_file
  84.   reg [3:0] wbm_init_waits; // initial wait cycles between CYC_O and STB_O of WB Master
  85.   reg [3:0] wbm_subseq_waits; // subsequent wait cycles between STB_Os of WB Master
  86.   reg [2:0] wbs_waits; // wait cycles befor WB Slave responds
  87.   reg [7:0] wbs_retries; // if RTY response, then this is the number of retries before ACK
  88.   reg wbm_working; // tasks wbm_write and wbm_read set signal when working and resetit when stop working

  89. //开始测试内容
  90.   initial
  91.   begin
  92.   wait(StartTB); // 开始测试
  93.   //初始化全局变量
  94.   tests_successfull = 0;
  95.   tests_failed = 0;
  96.   wbm_working = 0;
  97.   wbm_init_waits = 4'h1;
  98.   wbm_subseq_waits = 4'h3;
  99.   wbs_waits = 4'h1;
  100.   wbs_retries = 8'h2;
  101.   wb_slave.cycle_response(`ACK_RESPONSE, wbs_waits, wbs_retries);

  102. //测试的各个任务
  103.   test_note("PHY generates ideal Carrier sense and Collision signals for following tests");
  104.   eth_phy.carrier_sense_real_delay(0);
  105.   test_mac_full_duplex_transmit(0, 21); //测试全双工方式下传输数据
  106.   test_mac_full_duplex_receive(0, 13); //测试全双工方式下接收数据
  107.   test_mac_full_duplex_flow_control(0, 4); // 测试整个数据流程
  108.   test_note("PHY generates 'real delayed' Carrier sense and Collision signals for following
  109.   tests");
  110.   eth_phy.carrier_sense_real_delay(1);
  111. // 结束测试
  112.   test_summary;
  113.   $stop;
  114.   end
复制代码


测试内容通过多个测试任务来执行。限于篇幅,测试任务的内容不一一列出。


4.2 外部 PHY 芯片模拟程序
模拟程序模拟了简化的 LXT971A 芯片(Inter 公司的外部 PHY 芯片)。PHY 芯片通过 MIIM(媒体无关接口管理模块)来连接以太网控制器,因此:
• 当以太网控制器向 PHY 芯片模拟程序发送数据时,PHY 芯片模拟程序控制数据按照协议的进行传输;
• 当 PHY 芯片向以太网控制器发送数据时,外部 PHY 芯片模拟程序首先按照协议要求产生需要传输的数据,然后发送到以太网控制器。

外部 PHY 芯片模拟程序的主要代码如下:
  1. `include "timescale.v"
  2. `include "eth_phy_defines.v"
  3. `include "tb_eth_defines.v"
  4. module eth_phy (m_rst_n_i, mtx_clk_o, mtxd_i, mtxen_i, mtxerr_i, mrx_clk_o, mrxd_o, mrxdv_o,
  5. mrxerr_o,mcoll_o, mcrs_o,mdc_i, md_io, phy_log);

  6. //输入输出信号
  7.   input m_rst_n_i;
  8.   ……

  9. //寄存器和连线
  10.   reg control_bit15; // self clearing bit
  11.   ……

  12. // PHY 芯片模拟程序的 MIIM 部分
  13.   ……

  14. //初始化
  15.   initial
  16.   begin
  17.     md_io_enable = 1'b0;
  18.     respond_to_all_phy_addr = 1'b0;
  19.     no_preamble = 1'b0;
  20.   end

  21. // 使输出处于三态
  22.   assign #1 md_io = (m_rst_n_i && md_io_enable) ? md_io_output : 1'  bz ;

  23. //寄存器输入
  24.   always@(posedge mdc_i or negedge m_rst_n_i)
  25.   begin
  26.     if (!m_rst_n_i)
  27.       md_io_reg <= #1 0;
  28.     else
  29.       md_io_reg <= #1 md_io;
  30.   end

  31. // 获得 PHY 地址、寄存器地址和数据输入,把需要输出的数据移位输出
  32. // putting Data out and shifting
  33.   always@(posedge mdc_i or negedge m_rst_n_i)
  34.   begin
  35.     if (!m_rst_n_i)
  36.       begin
  37.         phy_address <= 0;
  38.         reg_address <= 0;
  39.         reg_data_in <= 0;
  40.         reg_data_out <= 0;
  41.         md_io_output <= 0;
  42.       end
  43.     else
  44.       begin
  45.         if (md_get_phy_address)
  46.           begin
  47.             phy_address[4:1] <= phy_address[3:0]; // correct address is `ETH_PHY_ADDR
  48.             phy_address[0] <= md_io;
  49.           end
  50.         if (md_get_reg_address)
  51.           begin
  52.             reg_address[4:1] <= reg_address[3:0];
  53.             reg_address[0] <= md_io;
  54.           end
  55.         if (md_get_reg_data_in)
  56.           begin
  57.             reg_data_in[15:1] <= reg_data_in[14:0];
  58.             reg_data_in[0] <= md_io;
  59.           end
  60.         if (md_put_reg_data_out)
  61.           begin
  62.             reg_data_out <= register_bus_out;
  63.           end
  64.         if (md_io_enable)
  65.           begin
  66.             md_io_output <= reg_data_out[15];
  67.             reg_data_out[15:1] <= reg_data_out[14:0];
  68.             reg_data_out[0] <= 1'b0;
  69.           end
  70.       end
  71.   end

  72.   assign #1 register_bus_in = reg_data_in; // md_put_reg_data_in - allows writing to a selected

  73.   register
  74. // 统计通过 MIIM(媒体无关接口管理模块)传输的数据
  75.   always@(posedge mdc_i or negedge m_rst_n_i)
  76.   begin
  77.     if (!m_rst_n_i)
  78.       begin
  79.         if (no_preamble)
  80.           md_transfer_cnt <= 33;
  81.         else
  82.           md_transfer_cnt <= 1;
  83.           end
  84.         else
  85.           begin
  86.             if (md_transfer_cnt_reset)
  87.               begin
  88.                 if (no_preamble)
  89.                   md_transfer_cnt <= 33;
  90.                 else
  91.                   md_transfer_cnt <= 1;
  92.                   end
  93.                 else if (md_transfer_cnt < 64)
  94.                   begin
  95.                     md_transfer_cnt <= md_transfer_cnt + 1'b1;
  96.                   end
  97.                 else
  98.                   begin
  99.                     if (no_preamble)
  100.                       md_transfer_cnt <= 33;
  101.                     else
  102.                       md_transfer_cnt <= 1;
  103.                   end
  104.             end
  105.   end

  106. // MIIM 的传输控制
  107.   always@(m_rst_n_i or md_transfer_cnt or md_io_reg or md_io_rd_wr orphy_address or respond_to_all_phy_addr or no_preamble)
  108.   begin
  109.   #1;
  110.   while ((m_rst_n_i) && (md_transfer_cnt <= 64))
  111.   begin
  112. // 复位信号
  113. // 检查报头
  114.     if (md_transfer_cnt < 33)
  115.       begin
  116.         #4 md_put_reg_data_in = 1'b0;
  117.         if (md_io_reg !== 1'b1)
  118.           begin
  119.           #1 md_transfer_cnt_reset = 1'b1;
  120.           end
  121.         else
  122.           begin
  123.           #1 md_transfer_cnt_reset = 1'b0;
  124.           end
  125.   end
  126. //检查开始位
  127.     else if (md_transfer_cnt == 33)
  128.       begin
  129.         if (no_preamble)
  130.           begin
  131.           #4 md_put_reg_data_in = 1'b0;
  132.         if (md_io_reg === 1'b0)
  133.           begin
  134.           #1 md_transfer_cnt_reset = 1'b0;
  135.           end
  136.         else
  137.           begin
  138.             #1 md_transfer_cnt_reset = 1'b1;
  139.             //if ((md_io_reg !== 1'bz) && (md_io_reg !== 1'b1))
  140.             if (md_io_reg !== 1'bz)
  141.           begin
  142. //错误
  143.             `ifdef VERBOSE
  144.             $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong first start bit (without preamble)",
  145.             $time);
  146.             `endif
  147.             #10 $stop;
  148.           end
  149.     end
  150.   end

  151.   else // with preamble
  152.           begin
  153.             #4 ;
  154.             `ifdef VERBOSE
  155.             $fdisplay(phy_log, " (%0t)(%m)MIIM - 32-bit preamble received", $time);
  156.             `endif
  157.             // check start bit only if md_transfer_cnt_reset is inactive, because if
  158.             // preamble suppression was changed start bit should not be checked
  159.             if ((md_io_reg !== 1'b0) && (md_transfer_cnt_reset == 1'b0))
  160.             begin
  161. // 错误
  162.               `ifdef VERBOSE
  163.               $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong first start bit", $time);
  164.               `endif
  165.               #10 $stop;
  166.             end
  167.           end
  168.         end
  169.       else if (md_transfer_cnt == 34)
  170.         begin
  171.         #4;
  172.         if (md_io_reg !== 1'b1)
  173.           begin
  174. // 错误
  175.             #1;
  176.             `ifdef VERBOSE
  177.             if (no_preamble)
  178.             $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong second start bit (without preamble)",
  179.             $time);
  180.           else
  181.               $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong second start bit", $time);
  182.               `endif
  183.               #10 $stop;
  184.             end
  185.             else
  186.             begin
  187.               `ifdef VERBOSE
  188.               if (no_preamble)
  189.                 #1 $fdisplay(phy_log, " (%0t)(%m)MIIM - 2 start bits received (without preamble)",$time);
  190.               else
  191.                 #1 $fdisplay(phy_log, " (%0t)(%m)MIIM - 2 start bits received", $time);
  192.               `endif
  193.             end
  194.           end
  195.             // 寄存器 op-code
  196.             else if (md_transfer_cnt == 35)
  197.             begin
  198.               #4;
  199.                 if (md_io_reg === 1'b1)
  200.                   begin
  201.                   #1 md_io_rd_wr = 1'b1;
  202.             end
  203.                 else
  204.                   begin
  205.                     #1 md_io_rd_wr = 1'b0;
  206.                   end
  207.             end
  208.             else if (md_transfer_cnt == 36)
  209.             begin
  210.             #4;
  211.             if ((md_io_reg === 1'b0) && (md_io_rd_wr == 1'b1))
  212.             begin
  213.             #1 md_io_rd_wr = 1'b1; // reading from PHY registers
  214.             `ifdef VERBOSE
  215.             $fdisplay(phy_log, " (%0t)(%m)MIIM - op-code for READING from registers", $time);
  216.             `endif
  217.             end
  218.             else if ((md_io_reg === 1'b1) && (md_io_rd_wr == 1'b0))
  219.             begin
  220.             #1 md_io_rd_wr = 1'b0; // writing to PHY registers
  221.             `ifdef VERBOSE
  222.             $fdisplay(phy_log, " (%0t)(%m)MIIM - op-code for WRITING to registers", $time);
  223.             `endif
  224.             end
  225.             else
  226.             begin
  227.             // 操作码错误
  228.             `ifdef VERBOSE
  229.             #1 $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong OP-CODE", $time);
  230.             `endif
  231.             #10 $stop;
  232.   end

  233. // 获得 PHY 地址
  234.   begin
  235.   #1 md_get_phy_address = 1'b1;
  236.   end
  237.   end
  238.   else if (md_transfer_cnt == 41)
  239.   begin
  240.   #4 md_get_phy_address = 1'b0;
  241.   // set the signal - get register address
  242.   #1 md_get_reg_address = 1'b1;
  243.   end
  244.   // 获得寄存器地址
  245.   else if (md_transfer_cnt == 46)
  246.   begin
  247.   #4 md_get_reg_address = 1'b0;
  248.   #1 md_put_reg_data_out = 1'b1;
  249.   end
  250.   ……

  251. // PHY 芯片与以太网控制器之间数据传输的控制
  252. // 寄存器
  253.   reg mcoll_o;
  254.   ……
  255. //初始化所有寄存器
  256.   initial
  257.   begin
  258.     mcrs_rx = 0;
  259.     mcrs_tx = 0;
  260.     task_mcoll = 0;
  261.     task_mcrs = 0;
  262.     task_mcrs_lost = 0;
  263.     no_collision_in_half_duplex = 0;
  264.     collision_in_full_duplex = 0;
  265.     no_carrier_sense_in_tx_half_duplex = 0;
  266.     no_carrier_sense_in_rx_half_duplex = 0;
  267.     carrier_sense_in_tx_full_duplex = 0;
  268.     no_carrier_sense_in_rx_full_duplex = 0;
  269.     real_carrier_sense = 0;
  270.   end

  271. // 数据冲突
  272.   always@(m_rst_n_i or control_bit8_0 or collision_in_full_duplex or
  273.   mcrs_rx or mcrs_tx or task_mcoll or no_collision_in_half_duplex)
  274.   begin
  275.     if (!m_rst_n_i)
  276.       mcoll_o = 0;
  277.     else
  278.       begin
  279.     if (control_bit8_0[8]) // full duplex
  280.       begin
  281.     if (collision_in_full_duplex) // collision is usually not asserted in full duplex
  282.       begin
  283.       mcoll_o = ((mcrs_rx && mcrs_tx) || task_mcoll);
  284.       `ifdef VERBOSE
  285.       if (mcrs_rx && mcrs_tx)
  286.             $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex!", $time);
  287.         if (task_mcoll)
  288.           $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex from TASK!", $time);
  289.       `endif
  290.         end
  291.       else
  292.           begin
  293.             mcoll_o = task_mcoll;
  294.             `ifdef VERBOSE
  295.             if (task_mcoll)
  296.             $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex from TASK!", $time);
  297.             `endif
  298.           end
  299.       end
  300.           else // half duplex
  301.           begin
  302.           mcoll_o = ((mcrs_rx && mcrs_tx && !no_collision_in_half_duplex) ||task_mcoll);
  303.             `ifdef VERBOSE
  304.             if (mcrs_rx && mcrs_tx)
  305.             $fdisplay(phy_log, " (%0t)(%m) Collision set in HalfDuplex!", $time);
  306.             if (task_mcoll)
  307.             $fdisplay(phy_log, " (%0t)(%m) Collision set in HalfDuplex from TASK!", $time);
  308.             `endif
  309.           end
  310.      end
  311.   end

  312. //载波监听多路访问
  313.   always@(m_rst_n_i or control_bit8_0 or carrier_sense_in_tx_full_duplex or
  314.   no_carrier_sense_in_rx_full_duplex or
  315.   no_carrier_sense_in_tx_half_duplex or
  316.   no_carrier_sense_in_rx_half_duplex or
  317.   mcrs_rx or mcrs_tx or task_mcrs or task_mcrs_lost)
  318.   begin
  319.     if (!m_rst_n_i)
  320.       mcrs_o = 0;
  321.   else
  322.     begin
  323.       if (control_bit8_0[8]) // full duplex
  324.         begin
  325.           if (carrier_sense_in_tx_full_duplex) // carrier sense is usually not asserted duringTX in full duplex
  326.             mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_full_duplex) ||
  327.             mcrs_tx || task_mcrs) && !task_mcrs_lost;
  328.           else
  329.             mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_full_duplex) ||
  330.             task_mcrs) && !task_mcrs_lost;
  331.          end
  332.       else // half duplex
  333.         begin
  334.           mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_half_duplex) ||
  335.           (mcrs_tx && !no_carrier_sense_in_tx_half_duplex) ||
  336.           task_mcrs) && !task_mcrs_lost;
  337.         end
  338.     end
  339.   end

  340. // 以太网控制器发送数据控制,PHY 芯片接收数据
  341. //寄存器
  342.   reg [7:0] tx_mem [0:4194303]; // 4194304 是 22 位地址线所能提供的所有地址,每个地址是 8位
  343.   ……

  344. //发送数据控制
  345.   always@(posedge mtx_clk_o)
  346.   begin
  347. // 保存数据并进行基本的帧数据检查
  348.     if (!m_rst_n_i)
  349.       begin
  350.         tx_cnt <= 0;
  351.         tx_preamble_ok <= 0;
  352.         tx_sfd_ok <= 0;
  353.         tx_len <= 0;
  354.         tx_len_err <= 0;
  355.       end
  356.     else
  357.       begin
  358.         if (!mtxen_i)
  359.           begin
  360.             tx_cnt <= 0;
  361.           end
  362.         else
  363.           begin
  364. //发送四位字节数据的计数器
  365.             tx_cnt <= tx_cnt + 1;
  366. //设置初始化值,检查第一个四位字节数据的报头
  367.         if (tx_cnt == 0)
  368.             begin
  369.               `ifdef VERBOSE
  370.               $fdisplay(phy_log, " (%0t)(%m) TX frame started with tx_en set!", $time);
  371.               `endif
  372.                 if (mtxd_i == 4'h5)
  373.                     tx_preamble_ok <= 1;
  374.                 else
  375.                     tx_preamble_ok <= 0;
  376.                     tx_sfd_ok <= 0;
  377.                     tx_byte_aligned_ok <= 0;
  378.                     tx_len <= 0;
  379.                     tx_len_err <= 0;
  380.               end
  381. // 检查报头
  382.                   if ((tx_cnt > 0) && (tx_cnt <= 13))
  383.                     begin
  384.                       if ((tx_preamble_ok != 1) || (mtxd_i != 4'h5))
  385.                       tx_preamble_ok <= 0;
  386.                     end
  387.   // 检查 SFD
  388.                 if (tx_cnt == 14)
  389.                     begin
  390.                       `ifdef VERBOSE
  391.                  if (tx_preamble_ok == 1)
  392.                     $fdisplay(phy_log, " (%0t)(%m) TX frame preamble OK!", $time);
  393.                   else
  394.                     $fdisplay(phy_log, "*E (%0t)(%m) TX frame preamble NOT OK!", $time);
  395.                     `endif
  396.                   if (mtxd_i == 4'h5)
  397.                       tx_sfd_ok <= 1;
  398.                   else
  399.                       tx_sfd_ok <= 0;
  400.                       end
  401.                   if (tx_cnt == 15)
  402.                     begin
  403.                       if ((tx_sfd_ok != 1) || (mtxd_i != 4'hD))
  404.                         tx_sfd_ok <= 0;
  405.                     end
  406.   // 控制存储地址数据、类型/长度、数据内容和 FCS 到发送数据缓冲区
  407.                   if (tx_cnt > 15)
  408.                     begin
  409.                     if (tx_cnt == 16)
  410.                       begin
  411.                         `ifdef VERBOSE
  412.                     if (tx_sfd_ok == 1)
  413.                         $fdisplay(phy_log, " (%0t)(%m) TX frame SFD OK!", $time);
  414.                       else
  415.                           $fdisplay(phy_log, "*E (%0t)(%m) TX frame SFD NOT OK!", $time);
  416.                         `endif
  417.                           end
  418.                     if (tx_cnt[0] == 0)
  419.                           begin
  420.                             tx_mem_data_in[3:0] <= mtxd_i; // storing LSB nibble
  421.                             tx_byte_aligned_ok <= 0; // if transfer will stop after this, then there was driblenibble
  422.                           end
  423.                       else
  424.                   begin
  425.                     tx_mem[tx_mem_addr_in[21:0]] <= {mtxd_i, tx_mem_data_in[3:0]}; // storing data into
  426.                     tx memory
  427.                     tx_len <= tx_len + 1; // enlarge byte length counter
  428.                     tx_byte_aligned_ok <= 1; // if transfer will stop after this, then transfer is byte
  429.                     alligned
  430.                     tx_mem_addr_in <= tx_mem_addr_in + 1'b1;
  431.                   end
  432.                     if (mtxerr_i)
  433.                       tx_len_err <= tx_len;
  434.           end
  435.       end
  436.   end

  437. //为发送数据产生载波信号
  438.                 if (!m_rst_n_i)
  439.                       begin
  440.                           mcrs_tx <= 0;
  441.                           mtxen_d1 <= 0;
  442.                           mtxen_d2 <= 0;
  443.                           mtxen_d3 <= 0;
  444.                           mtxen_d4 <= 0;
  445.                           mtxen_d5 <= 0;
  446.                           mtxen_d6 <= 0;
  447.                       end
  448.                 else
  449.                       begin
  450.                           mtxen_d1 <= mtxen_i;
  451.                           mtxen_d2 <= mtxen_d1;
  452.                           mtxen_d3 <= mtxen_d2;
  453.                           mtxen_d4 <= mtxen_d3;
  454.                           mtxen_d5 <= mtxen_d4;
  455.                           mtxen_d6 <= mtxen_d5;
  456.                           if (real_carrier_sense)
  457.                           mcrs_tx <= mtxen_d6;
  458.                 else
  459.                    mcrs_tx <= mtxen_i;
  460.                         end
  461.             end

  462.   `ifdef VERBOSE
  463.   reg frame_started;

  464.   initial
  465.   begin
  466.     frame_started = 0;
  467.   end

  468.   always@(posedge mtxen_i)
  469.   begin
  470.     frame_started <= 1;
  471.   end

  472.   always@(negedge mtxen_i)
  473.   begin
  474.     if (frame_started)
  475.       begin
  476.         $fdisplay(phy_log, " (%0t)(%m) TX frame ended with tx_en reset!", $time);
  477.         frame_started <= 0;
  478.       end
  479.   end

  480.   always@(posedge mrxerr_o)
  481.   begin
  482.     $fdisplay(phy_log, " (%0t)(%m) RX frame ERROR signal was set!", $time);
  483.   end
  484.   `endif
  485.   ……
  486. endmodule
复制代码



4.3 仿真结果
如图 13 所示是对全双工方式下传输数据的测试,图中加亮的 MTxD 是以太网控制器的数据输出。

图 13 全双工模式下发送数据的测试结果
如图 14 所示的是全双工模式下接收数据的测试,图中加亮的 MRxD 是以太网控制器接收数据的输入。

图 14 全双工模式下接收数据的测试结果

如图 15 所示的是全双工模式下数据发送和接收整个过程的测试结果,图中加亮的 MTxD和 MRxD 是以太网控制器发送数据和接收数据的输出和输入端口。

图 15 全双模式下数据发送和接收全过程的测试结果

如图 16 所示的是半双工模式下发送和接收数据全过程的测试结果。

图16 半双工模式下发送和接收数据全过程的测试结果


五、总结


本篇介绍了一个以太网控制器(MAC)的实例。首先介绍了以太网的基本原理,然后介绍了以太网控制器程序的主要结构和主要功能模块的实现过程。最后用一个测试程序验证程序的功能是否满足要求。本章为读者设计自己的以太网控制器提供了一个有用的方案,并且有助于加深对以太网协议的理解。







欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2