以太网控制器程序的仿真与测试

电子说

1.3w人已加入

描述

   

今天给大侠带来基于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 程序、模拟主机程序和以太网控制程序。同时顶层程序需要控制仿真的进行,主要代码如下:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
`include "eth_phy_defines.v"`include "wb_model_defines.v"`include "tb_eth_defines.v"`include "eth_defines.v"`include "timescale.v"module tb_ethernet();
//寄存器与连线  reg wb_clk;  ……
//连接以太网控制器eth_top eth_top(                .wb_clk_i(wb_clk), .wb_rst_i(wb_rst),                .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),                .wb_cyc_i(eth_sl_wb_cyc_i), .wb_stb_i(eth_sl_wb_stb_i), .wb_ack_o(eth_sl_wb_ack_o),                .wb_err_o(eth_sl_wb_err_o), .wb_dat_i(eth_sl_wb_dat_i), .wb_dat_o(eth_sl_wb_dat_o),                .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),                .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),                //发送数据                .mtx_clk_pad_i(mtx_clk), .mtxd_pad_o(MTxD), .mtxen_pad_o(MTxEn), .mtxerr_pad_o(MTxErr),                //接收数据部分                .mrx_clk_pad_i(mrx_clk), .mrxd_pad_i(MRxD), .mrxdv_pad_i(MRxDV), .mrxerr_pad_i(MRxErr),                .mcoll_pad_i(MColl), .mcrs_pad_i(MCrs),                //媒体无关接口模块                .mdc_pad_o(Mdc_O), .md_pad_i(Mdi_I), .md_pad_o(Mdo_O), .md_padoe_o(Mdo_OE),                .int_o(wb_int)                )
//连接模拟 PHY 部分  assign Mdio_IO = Mdo_OE ? Mdo_O : 1'bz ;  assign Mdi_I = Mdio_IO;
  integer phy_log_file_desc;
  eth_phy eth_phy(                  .m_rst_n_i(!wb_rst),                  // MAC 发送数据                  .mtx_clk_o(mtx_clk), .mtxd_i(MTxD), .mtxen_i(MTxEn), .mtxerr_i(MTxErr),                  // MAC 接收数据                  .mrx_clk_o(mrx_clk), .mrxd_o(MRxD), .mrxdv_o(MRxDV), .mrxerr_o(MRxErr),                  .mcoll_o(MColl), .mcrs_o(MCrs),                  //媒体无关接口模块                  .mdc_i(Mdc_O), .md_io(Mdio_IO),                  .phy_log(phy_log_file_desc)                 );                 // 连接主机模块  integer host_log_file_desc;  WB_MASTER_BEHAVIORAL wb_master(                                  .CLK_I(wb_clk),                                  .RST_I(wb_rst),                                  .TAG_I({`WB_TAG_WIDTH{1'b0}}),                                  .TAG_O(),                                  .ACK_I(eth_sl_wb_ack_o),                                  .ADR_O(eth_sl_wb_adr), // only eth_sl_wb_adr_i[11:2] used                                  .CYC_O(eth_sl_wb_cyc_i),                                  .DAT_I(eth_sl_wb_dat_o),                                  .DAT_O(eth_sl_wb_dat_i),                                  .ERR_I(eth_sl_wb_err_o),                                  .RTY_I(1'b0), // inactive (1'b0)                                  .SEL_O(eth_sl_wb_sel_i),                                  .STB_O(eth_sl_wb_stb_i),                                  .WE_O (eth_sl_wb_we_i),                                  .CAB_O() // NOT USED for now!                                  )
  assign eth_sl_wb_adr_i = {20'h0, eth_sl_wb_adr[11:2], 2'h0};  ……
//初始化  initial  begin  //复位信号  wb_rst = 1'b1;  #423 wb_rst = 1'b0;//清除存储器内容  clear_memories;  clear_buffer_descriptors;  #423 StartTB = 1'b1;  end
//产生时钟信号  initial  begin  wb_clk=0;  forever #15 wb_clk = ~wb_clk; // 2*10 ns -> 33.3 MHz  end
  integer tests_successfull;  integer tests_failed;  reg [799:0] test_name; // used for tb_log_file  reg [3:0] wbm_init_waits; // initial wait cycles between CYC_O and STB_O of WB Master  reg [3:0] wbm_subseq_waits; // subsequent wait cycles between STB_Os of WB Master  reg [2:0] wbs_waits; // wait cycles befor WB Slave responds  reg [7:0] wbs_retries; // if RTY response, then this is the number of retries before ACK  reg wbm_working; // tasks wbm_write and wbm_read set signal when working and resetit when stop working
//开始测试内容  initial  begin  wait(StartTB); // 开始测试  //初始化全局变量  tests_successfull = 0;  tests_failed = 0;  wbm_working = 0;  wbm_init_waits = 4'h1;  wbm_subseq_waits = 4'h3;  wbs_waits = 4'h1;  wbs_retries = 8'h2;  wb_slave.cycle_response(`ACK_RESPONSE, wbs_waits, wbs_retries);
//测试的各个任务  test_note("PHY generates ideal Carrier sense and Collision signals for following tests");  eth_phy.carrier_sense_real_delay(0);  test_mac_full_duplex_transmit(0, 21); //测试全双工方式下传输数据  test_mac_full_duplex_receive(0, 13); //测试全双工方式下接收数据  test_mac_full_duplex_flow_control(0, 4); // 测试整个数据流程  test_note("PHY generates 'real delayed' Carrier sense and Collision signals for following  tests");  eth_phy.carrier_sense_real_delay(1);// 结束测试  test_summary;  $stop;  end

 

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

 

 

 

4.2 外部 PHY 芯片模拟程序

模拟程序模拟了简化的 LXT971A 芯片(Inter 公司的外部 PHY 芯片)。PHY 芯片通过 MIIM(媒体无关接口管理模块)来连接以太网控制器,因此:

• 当以太网控制器向 PHY 芯片模拟程序发送数据时,PHY 芯片模拟程序控制数据按照协议的进行传输;

• 当 PHY 芯片向以太网控制器发送数据时,外部 PHY 芯片模拟程序首先按照协议要求产生需要传输的数据,然后发送到以太网控制器。

 

外部 PHY 芯片模拟程序的主要代码如下:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
`include "timescale.v"`include "eth_phy_defines.v"`include "tb_eth_defines.v"module eth_phy (m_rst_n_i, mtx_clk_o, mtxd_i, mtxen_i, mtxerr_i, mrx_clk_o, mrxd_o, mrxdv_o,mrxerr_o,mcoll_o, mcrs_o,mdc_i, md_io, phy_log);
//输入输出信号  input m_rst_n_i;  ……
//寄存器和连线  reg control_bit15; // self clearing bit  ……
// PHY 芯片模拟程序的 MIIM 部分  ……
//初始化  initial  begin    md_io_enable = 1'b0;    respond_to_all_phy_addr = 1'b0;    no_preamble = 1'b0;  end
// 使输出处于三态  assign #1 md_io = (m_rst_n_i && md_io_enable) ? md_io_output : 1'  bz ;
//寄存器输入  always@(posedge mdc_i or negedge m_rst_n_i)  begin    if (!m_rst_n_i)      md_io_reg <= #1 0;    else      md_io_reg <= #1 md_io;  end
// 获得 PHY 地址、寄存器地址和数据输入,把需要输出的数据移位输出// putting Data out and shifting  always@(posedge mdc_i or negedge m_rst_n_i)  begin    if (!m_rst_n_i)      begin        phy_address <= 0;        reg_address <= 0;        reg_data_in <= 0;        reg_data_out <= 0;        md_io_output <= 0;      end    else      begin        if (md_get_phy_address)          begin            phy_address[4:1] <= phy_address[3:0]; // correct address is `ETH_PHY_ADDR            phy_address[0] <= md_io;          end        if (md_get_reg_address)          begin            reg_address[4:1] <= reg_address[3:0];            reg_address[0] <= md_io;          end        if (md_get_reg_data_in)          begin            reg_data_in[15:1] <= reg_data_in[14:0];            reg_data_in[0] <= md_io;          end        if (md_put_reg_data_out)          begin            reg_data_out <= register_bus_out;          end        if (md_io_enable)          begin            md_io_output <= reg_data_out[15];            reg_data_out[15:1] <= reg_data_out[14:0];            reg_data_out[0] <= 1'b0;          end      end  end
  assign #1 register_bus_in = reg_data_in; // md_put_reg_data_in - allows writing to a selected
  register// 统计通过 MIIM(媒体无关接口管理模块)传输的数据  always@(posedge mdc_i or negedge m_rst_n_i)  begin    if (!m_rst_n_i)      begin        if (no_preamble)          md_transfer_cnt <= 33;        else          md_transfer_cnt <= 1;          end        else          begin            if (md_transfer_cnt_reset)              begin                if (no_preamble)                  md_transfer_cnt <= 33;                else                  md_transfer_cnt <= 1;                  end                else if (md_transfer_cnt < 64)                  begin                    md_transfer_cnt <= md_transfer_cnt + 1'b1;                  end                else                  begin                    if (no_preamble)                      md_transfer_cnt <= 33;                    else                      md_transfer_cnt <= 1;                  end            end  end
// MIIM 的传输控制  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)  begin  #1;  while ((m_rst_n_i) && (md_transfer_cnt <= 64))  begin// 复位信号// 检查报头    if (md_transfer_cnt < 33)      begin        #4 md_put_reg_data_in = 1'b0;        if (md_io_reg !== 1'b1)          begin          #1 md_transfer_cnt_reset = 1'b1;          end        else          begin          #1 md_transfer_cnt_reset = 1'b0;          end  end//检查开始位    else if (md_transfer_cnt == 33)      begin        if (no_preamble)          begin          #4 md_put_reg_data_in = 1'b0;        if (md_io_reg === 1'b0)          begin          #1 md_transfer_cnt_reset = 1'b0;          end        else          begin            #1 md_transfer_cnt_reset = 1'b1;            //if ((md_io_reg !== 1'bz) && (md_io_reg !== 1'b1))            if (md_io_reg !== 1'bz)          begin//错误            `ifdef VERBOSE            $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong first start bit (without preamble)",            $time);            `endif            #10 $stop;          end    end  end
  else // with preamble          begin            #4 ;            `ifdef VERBOSE            $fdisplay(phy_log, " (%0t)(%m)MIIM - 32-bit preamble received", $time);            `endif            // check start bit only if md_transfer_cnt_reset is inactive, because if            // preamble suppression was changed start bit should not be checked            if ((md_io_reg !== 1'b0) && (md_transfer_cnt_reset == 1'b0))            begin// 错误              `ifdef VERBOSE              $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong first start bit", $time);              `endif              #10 $stop;            end          end        end      else if (md_transfer_cnt == 34)        begin        #4;        if (md_io_reg !== 1'b1)          begin // 错误            #1;            `ifdef VERBOSE            if (no_preamble)            $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong second start bit (without preamble)",            $time);          else              $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong second start bit", $time);              `endif              #10 $stop;            end            else            begin              `ifdef VERBOSE              if (no_preamble)                #1 $fdisplay(phy_log, " (%0t)(%m)MIIM - 2 start bits received (without preamble)",$time);              else                #1 $fdisplay(phy_log, " (%0t)(%m)MIIM - 2 start bits received", $time);              `endif            end          end            // 寄存器 op-code            else if (md_transfer_cnt == 35)            begin              #4;                if (md_io_reg === 1'b1)                  begin                  #1 md_io_rd_wr = 1'b1;            end                else                  begin                    #1 md_io_rd_wr = 1'b0;                  end            end            else if (md_transfer_cnt == 36)            begin            #4;            if ((md_io_reg === 1'b0) && (md_io_rd_wr == 1'b1))            begin            #1 md_io_rd_wr = 1'b1; // reading from PHY registers            `ifdef VERBOSE            $fdisplay(phy_log, " (%0t)(%m)MIIM - op-code for READING from registers", $time);            `endif            end            else if ((md_io_reg === 1'b1) && (md_io_rd_wr == 1'b0))            begin            #1 md_io_rd_wr = 1'b0; // writing to PHY registers            `ifdef VERBOSE            $fdisplay(phy_log, " (%0t)(%m)MIIM - op-code for WRITING to registers", $time);            `endif            end            else            begin            // 操作码错误            `ifdef VERBOSE            #1 $fdisplay(phy_log, "*E (%0t)(%m)MIIM - wrong OP-CODE", $time);            `endif            #10 $stop;  end
// 获得 PHY 地址  begin  #1 md_get_phy_address = 1'b1;  end  end  else if (md_transfer_cnt == 41)  begin  #4 md_get_phy_address = 1'b0;  // set the signal - get register address  #1 md_get_reg_address = 1'b1;  end  // 获得寄存器地址  else if (md_transfer_cnt == 46)  begin  #4 md_get_reg_address = 1'b0;  #1 md_put_reg_data_out = 1'b1;  end  ……
// PHY 芯片与以太网控制器之间数据传输的控制// 寄存器  reg mcoll_o;  ……//初始化所有寄存器  initial  begin    mcrs_rx = 0;    mcrs_tx = 0;    task_mcoll = 0;    task_mcrs = 0;    task_mcrs_lost = 0;    no_collision_in_half_duplex = 0;    collision_in_full_duplex = 0;    no_carrier_sense_in_tx_half_duplex = 0;    no_carrier_sense_in_rx_half_duplex = 0;    carrier_sense_in_tx_full_duplex = 0;    no_carrier_sense_in_rx_full_duplex = 0;    real_carrier_sense = 0;  end
// 数据冲突  always@(m_rst_n_i or control_bit8_0 or collision_in_full_duplex or  mcrs_rx or mcrs_tx or task_mcoll or no_collision_in_half_duplex)  begin    if (!m_rst_n_i)      mcoll_o = 0;    else      begin    if (control_bit8_0[8]) // full duplex      begin    if (collision_in_full_duplex) // collision is usually not asserted in full duplex      begin      mcoll_o = ((mcrs_rx && mcrs_tx) || task_mcoll);      `ifdef VERBOSE      if (mcrs_rx && mcrs_tx)            $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex!", $time);        if (task_mcoll)          $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex from TASK!", $time);      `endif        end      else          begin            mcoll_o = task_mcoll;            `ifdef VERBOSE            if (task_mcoll)            $fdisplay(phy_log, " (%0t)(%m) Collision set in FullDuplex from TASK!", $time);            `endif          end      end          else // half duplex          begin          mcoll_o = ((mcrs_rx && mcrs_tx && !no_collision_in_half_duplex) ||task_mcoll);            `ifdef VERBOSE            if (mcrs_rx && mcrs_tx)            $fdisplay(phy_log, " (%0t)(%m) Collision set in HalfDuplex!", $time);            if (task_mcoll)            $fdisplay(phy_log, " (%0t)(%m) Collision set in HalfDuplex from TASK!", $time);            `endif          end     end  end
//载波监听多路访问  always@(m_rst_n_i or control_bit8_0 or carrier_sense_in_tx_full_duplex or  no_carrier_sense_in_rx_full_duplex or  no_carrier_sense_in_tx_half_duplex or  no_carrier_sense_in_rx_half_duplex or  mcrs_rx or mcrs_tx or task_mcrs or task_mcrs_lost)  begin    if (!m_rst_n_i)      mcrs_o = 0;  else    begin      if (control_bit8_0[8]) // full duplex        begin          if (carrier_sense_in_tx_full_duplex) // carrier sense is usually not asserted duringTX in full duplex            mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_full_duplex) ||            mcrs_tx || task_mcrs) && !task_mcrs_lost;          else            mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_full_duplex) ||            task_mcrs) && !task_mcrs_lost;         end      else // half duplex        begin          mcrs_o = ((mcrs_rx && !no_carrier_sense_in_rx_half_duplex) ||          (mcrs_tx && !no_carrier_sense_in_tx_half_duplex) ||          task_mcrs) && !task_mcrs_lost;        end    end  end
// 以太网控制器发送数据控制,PHY 芯片接收数据//寄存器  reg [7:0] tx_mem [0:4194303]; // 4194304 是 22 位地址线所能提供的所有地址,每个地址是 8位  ……
//发送数据控制  always@(posedge mtx_clk_o)  begin// 保存数据并进行基本的帧数据检查    if (!m_rst_n_i)      begin        tx_cnt <= 0;        tx_preamble_ok <= 0;        tx_sfd_ok <= 0;        tx_len <= 0;        tx_len_err <= 0;      end    else      begin        if (!mtxen_i)          begin            tx_cnt <= 0;          end        else          begin//发送四位字节数据的计数器            tx_cnt <= tx_cnt + 1;//设置初始化值,检查第一个四位字节数据的报头        if (tx_cnt == 0)            begin              `ifdef VERBOSE              $fdisplay(phy_log, " (%0t)(%m) TX frame started with tx_en set!", $time);              `endif                if (mtxd_i == 4'h5)                    tx_preamble_ok <= 1;                else                    tx_preamble_ok <= 0;                    tx_sfd_ok <= 0;                    tx_byte_aligned_ok <= 0;                    tx_len <= 0;                    tx_len_err <= 0;              end// 检查报头                  if ((tx_cnt > 0) && (tx_cnt <= 13))                    begin                      if ((tx_preamble_ok != 1) || (mtxd_i != 4'h5))                      tx_preamble_ok <= 0;                    end  // 检查 SFD                if (tx_cnt == 14)                    begin                      `ifdef VERBOSE                 if (tx_preamble_ok == 1)                    $fdisplay(phy_log, " (%0t)(%m) TX frame preamble OK!", $time);                  else                    $fdisplay(phy_log, "*E (%0t)(%m) TX frame preamble NOT OK!", $time);                    `endif                  if (mtxd_i == 4'h5)                      tx_sfd_ok <= 1;                  else                      tx_sfd_ok <= 0;                      end                  if (tx_cnt == 15)                    begin                      if ((tx_sfd_ok != 1) || (mtxd_i != 4'hD))                        tx_sfd_ok <= 0;                    end  // 控制存储地址数据、类型/长度、数据内容和 FCS 到发送数据缓冲区                  if (tx_cnt > 15)                    begin                    if (tx_cnt == 16)                      begin                        `ifdef VERBOSE                    if (tx_sfd_ok == 1)                        $fdisplay(phy_log, " (%0t)(%m) TX frame SFD OK!", $time);                      else                          $fdisplay(phy_log, "*E (%0t)(%m) TX frame SFD NOT OK!", $time);                        `endif                          end                    if (tx_cnt[0] == 0)                          begin                            tx_mem_data_in[3:0] <= mtxd_i; // storing LSB nibble                            tx_byte_aligned_ok <= 0; // if transfer will stop after this, then there was driblenibble                          end                      else                  begin                    tx_mem[tx_mem_addr_in[21:0]] <= {mtxd_i, tx_mem_data_in[3:0]}; // storing data into                    tx memory                    tx_len <= tx_len + 1; // enlarge byte length counter                    tx_byte_aligned_ok <= 1; // if transfer will stop after this, then transfer is byte                    alligned                    tx_mem_addr_in <= tx_mem_addr_in + 1'b1;                  end                    if (mtxerr_i)                      tx_len_err <= tx_len;          end      end  end
//为发送数据产生载波信号                if (!m_rst_n_i)                      begin                          mcrs_tx <= 0;                          mtxen_d1 <= 0;                          mtxen_d2 <= 0;                          mtxen_d3 <= 0;                          mtxen_d4 <= 0;                          mtxen_d5 <= 0;                          mtxen_d6 <= 0;                      end                else                      begin                          mtxen_d1 <= mtxen_i;                          mtxen_d2 <= mtxen_d1;                          mtxen_d3 <= mtxen_d2;                          mtxen_d4 <= mtxen_d3;                          mtxen_d5 <= mtxen_d4;                          mtxen_d6 <= mtxen_d5;                          if (real_carrier_sense)                          mcrs_tx <= mtxen_d6;                else                   mcrs_tx <= mtxen_i;                        end            end
  `ifdef VERBOSE  reg frame_started;
  initial  begin    frame_started = 0;  end
  always@(posedge mtxen_i)  begin    frame_started <= 1;  end
  always@(negedge mtxen_i)  begin    if (frame_started)      begin        $fdisplay(phy_log, " (%0t)(%m) TX frame ended with tx_en reset!", $time);        frame_started <= 0;      end  end
  always@(posedge mrxerr_o)  begin    $fdisplay(phy_log, " (%0t)(%m) RX frame ERROR signal was set!", $time);  end  `endif  ……endmodule

    

 

 

4.3 仿真结果

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

 

以太网

图 13 全双工模式下发送数据的测试结果

 

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

 

以太网

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

 

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

 

以太网

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

 

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

 

以太网

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

 

 

五、总结

 

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

 

 

  审核编辑:汤梓红


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分