FPGA基础入门【10】开发板Ethernet PHY局域网配置

  • List item

上一篇教程介绍了NEXYS4 开发板中DDR2的使用方式,这一篇介绍不可或缺的网络接口RJ45在FPGA开发板中的使用

FPGA基础入门【10】开发板Ethernet PHY局域网配置

  • 板载局域网芯片
    • 网络层级模型
    • 芯片简介
      • 引脚定义
      • 数据通路
      • 芯片复位
      • 控制寄存器
      • 收发时序
  • 逻辑设计
    • 状态机设计
    • 顶层代码设计
  • 模拟仿真
    • Testbench
    • 仿真脚本
    • 仿真结果
  • 编译烧写
  • 总结

板载局域网芯片

NEXYS 4上的局域网接口RJ45使用常见的LAN8720A物理层芯片,支持10兆网和100兆网,使用RMII(Reduced Media Independent Interface)。它的文档在此:LAN8720A

NEXYS 4文档中介绍说,使用EDK(Embedded Development Kit)的工程可以用axi_ethernetlit或者axi_ethernet IP访问物理层芯片。使用EDK的意思是利用FPGA内自带的ARM核,放入一个小型的Linux核心,比如Microblaze或Zynq,然后用软件编程的形式收发数据。

网络层级模型

网络传输TCP/IP五层模型如下,最顶层的用户产生数据,经过层层包装到最底层的物理层传输出去,到接收端在经过层层拆包验收后传到接收端的用户,各层负责把数据包往下传(实线),但实际上是在和另一端的相对应层互相握手沟通(虚线)。这个过程包含很多的细节,除了物理层是由LAN8720A芯片和RJ45接口完成的以外,其他都是由FPGA内部逻辑或者ARM核中的软件逻辑完成的。
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第1张图片
这个系列的教程还没有到将各种接口综合到Linux核心的阶段,EDK先向后放,我们优先用PL(programmable logic)部分测试其基础功能,以此学习加强理解

芯片简介

LAN8720A的外部连接应该如下,我们需要做的是写一个简单的10/100 Ethernet MAC,其他部分在开发板上已经存在
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第2张图片
芯片内部结构如下,下面慢慢介绍
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第3张图片

引脚定义

在开发板上和FPGA的连接如下
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第4张图片
相关引脚简介:

  • TXD1, TXD0,MAC层输出到物理层的信号
  • TXEN,表示TXD[1:0]上有有效信号
  • RXD1, RXD0,物理层传给MAC层的信号
  • RXERR,接收错误信号,该信号升高表示物理层收到了错误信号
  • PHYAD0,物理层地址配置引脚,不拉低时则默认拉高
  • MODE2, MODE1, MODE0,物理层模式配置引脚
  • CRS_DV,有效信息接收信号,该信号升高表示物理层收到了有效信号
  • MDIO,物理层SMI(Serial Management Interface)数据出入,不用时被拉高
  • MDC,物理层SMI(Serial Management Interface)时钟信号
  • RESET#,低电平激发的硬件复位引脚,不用时被拉高
  • INT#,中断输出
  • REFCLKO,50MHz参考时钟输出
  • CLKIN,50MHz时钟输入
  • LED1,有效链接指示灯,RJ45接口上的绿灯,表示建立了有效连接,当CRS为高时会闪烁
  • LED2,连接速度指示灯,RJ45接口上的黄灯,亮起表示在使用百兆网,灭表示在使用十兆网

在右侧的信号是在RESET#信号拉低,也就是复位状态是有效的,当复位结束,RESET#拉高,相应信号就被读入芯片内部。这种模式被称为configuration strap,不知道该怎么翻译

在configuration strap时MODE引脚的配置就可以调配寄存器中相应位数的默认值,这样常见的配置模式就不需要写寄存器来配置,还是很方便的
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第5张图片

数据通路

数据信号在芯片内部流通过程我们并不关心,不过还是简单介绍一下。

发送端如下

  1. 50MHz的时钟,每个时钟带2bit信号,共100Mbit/s,在RMII转成25MHz * 4bit
  2. 4b/5b编码,让0和1平均,并不会长时间持续出现,4bit数据被编码成5bit,125Mbit/s
  3. 经过扰码(数据包互相交织,将可能全部出错的数据包分摊到多个数据包中)和PISO (并行进入串行输出 Parallel in Serial out)
  4. NRZI编码和MLT-3编码,NRZI简单说是1变0不变,MLT-3把正负1的信号变成正负1加上个0的三阶信号
  5. 发送端驱动Tx Driver将数据转化成模拟信号输出
    FPGA基础入门【10】开发板Ethernet PHY局域网配置_第6张图片
    接收端相反,只是需要多一个时钟恢复电路,利用传输的信号本身,提取连续变化的部分经过反馈电路形成和信号同步的时钟
    FPGA基础入门【10】开发板Ethernet PHY局域网配置_第7张图片

芯片复位

LAN8720A的芯片复位分为硬件复位和软件复位,硬件复位是把RESETn引脚拉低至少100us;软件复位是给控制寄存器0的最高位[15]写入1,并等待0.5s,具体操作之后就介绍。

这篇教程会在逻辑开始时先后把硬件复位和软件复位都进行一遍。

控制寄存器

SMI串行管理接口读写数据的时序图如下,MDC的频率不固定,只要周期在400ns以上就好,也就是频率在2.5MHz以下,保险起见我们选择1MHz。并且我们选择在时钟下降沿输出或者读入数据,避免上升沿的冲突
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第8张图片
PHY Address物理层地址默认是0,在复位状态时把引脚PHYAD1拉高可以修改物理层地址到1,如果你需要更高的地址就需要配置寄存器了。当你有多个物理层芯片连在一起时就需要配置不同的物理层地址来分别访问

Register Address寄存器地址和不同寄存器有关,寄存器表如下
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第9张图片
在这之中最重要的寄存器是前5个
基础控制寄存器,地址0,可读写:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第10张图片

  • [15],软件复位,默认为0,设为1即软件复位开始
  • [14],回路模式,默认为0,设为1即回路模式
  • [13],速度选择,0为十兆网,1为百兆网
  • [12],自动速度识别使能,0为关闭自动速度识别,1为开启自动速度识别
  • [11],电源关闭,默认为0,设为1关闭电源
  • [10],隔离模式,默认为0,设为1隔离物理层和RMII接口
  • [9],重启自动速度识别,默认为0,设为1重启自动速度识别
  • [8],双工模式,0为半双工,1为全双工
  • [7:0]是保留位

基础状态寄存器,地址1,仅可读
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第11张图片
这个寄存器只要看后面几位就好,看自动速度识别是否完成,连接是否完成

物理层ID1寄存器,地址2,可读写
phy 1
作为寄存器读取测试使用,看其读出默认数据是否正确

物理层ID2寄存器,地址3,可读写
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第12张图片
同样可以作为寄存器读取测试使用

自动识别广播寄存器,地址4,可读写
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第13张图片
这个寄存器在复位模式配置MODE时需要注意,具体可以参考上面的模式选择表

收发时序

在所有寄存器都配置完成后,我们就可以用下面这个时序图来收发数据了。可以看出LAN8720A的收发数据是在时钟下降沿发生的
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第14张图片

逻辑设计

基础思路是,先硬件复位,配置configuration strap,再进行软件复位,读取几个寄存器以确保一切正常后等待CRS_DV引脚来读取数据。这里不打算涉及负责的网络协议,随便找个网口连接后,看到有数据即可,不涉及数据包内部内容。

状态机设计

根据上面的设计,做出如下状态机,当中加入了为ChipScope准备的辅助开关
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第15张图片

顶层代码设计

module ethernet(
    input       clk,
    input       rst,
    input       switch_continue,
    output reg  led,
    
    // LAN8720A PHY chip port
    inout       MDIO,
    output wire MDC,
    output reg  RESETn,
    inout       RXD1_MODE1,
    inout       RXD0_MODE0,
    inout       RXERR_PHYAD0,
    output reg  TXD0,
    output reg  TXD1,
    output reg  TXEN,
    inout       CRS_DV_MODE2,
    inout       INT_REFCLKO,
    output reg  CLKIN
);

引脚定义。里面加入了一个叫switch_continue的引脚,它被连接到第二个拨动开关上,用来把状态机停在读取寄存器之前,不然我们很难在ChipScope上抓取相应数据

这里我们第一次用到了inout这种端口定义,顾名思义是出入复用的引脚,它的用法如下(不包含在最终代码中,只是示例)

双向引脚一般有一个输出使能引脚,输出时连接到相应的寄存器输出上,输入则转为高阻态1’bz,而输入信号则赋值给另一个wire,传递出去或者到另一个寄存器

module bidirec (oe, clk, inp, outp, bidir);

// Port Declaration

input   oe;
input   clk;
input   [7:0] inp;
output  [7:0] outp;
inout   [7:0] bidir;

reg     [7:0] a;
reg     [7:0] b;

assign bidir = oe ? a : 8'bZ ;
assign outp  = b;

// Always Construct

always @ (posedge clk)
begin
    b <= bidir;
    a <= inp;
end

endmodule

输出50MHz到LAN8720A的CLKIN引脚

// Clock to LAN8720A is 50MHz, need to be lowered down
always @(posedge clk or posedge rst) begin
    if(rst) begin
        CLKIN <= 1'b0;
    end
    else begin
        CLKIN <= ~CLKIN;
    end
end

双向引脚配置

// Control of the bi-directional data
reg  [2:0] MODE;
reg        PHYAD0;
reg        INTSEL;
reg        strap_oe;
wire [1:0] RXD;
(* dont_touch = "true" *)reg  [1:0] rxd_d;
wire       RXERR;
(* dont_touch = "true" *)reg        rxerr_d;
wire       CRS_DV;
(* dont_touch = "true" *)reg        crs_dv_d;
wire       INT;

assign RXD1_MODE1   = (strap_oe) ? MODE[1] : 1'bz;
assign RXD0_MODE0   = (strap_oe) ? MODE[0] : 1'bz;
assign RXERR_PHYAD0 = (strap_oe) ? PHYAD0  : 1'bz;
assign CRS_DV_MODE2 = (strap_oe) ? MODE[2] : 1'bz;
assign INT_REFCLKO  = (strap_oe) ? INTSEL  : 1'bz;

assign RXD = {RXD1_MODE1, RXD0_MODE0};
assign RXERR = RXERR_PHYAD0;
assign CRS_DV = CRS_DV_MODE2;
assign INT = INT_REFCLKO;

状态机定义

// State machine
parameter IDLE         = 4'd0;
parameter RESET        = 4'd1;
parameter RDPHYID1     = 4'd2;
parameter RDPHYID2     = 4'd3;
parameter RESET_SOFT   = 4'd4;
parameter UNRESET_SOFT = 4'd5;
parameter SETMODE      = 4'd6;
parameter UNRESET      = 4'd7;
parameter RD_BC0       = 4'd8;
parameter RD_BS1       = 4'd9;
parameter RX_TX        = 4'd10;

(* dont_touch = "true" *)reg [3:0]  state;
(* dont_touch = "true" *)reg [3:0]  next_state;
(* dont_touch = "true" *)reg [15:0] data_from_SMI;

SMI读写模块,在后面加入子模块内容

// SMI management
reg         wrh_rdl;
reg  [4:0]  reg_addr;
reg  [15:0] wr_data;
wire [15:0] rd_data;
reg         SMI_start;
wire        SMI_complete;
wire        MDI;
wire        MDO;
wire        MD_OE;

assign MDIO = (MD_OE) ? MDO : 1'bz;
assign MDI = MDIO;

SMI_manage SMI_manage(
    .clk(clk),
    .rst(rst),

    .mdc(MDC),
    .mdo(MDO),
    .mdi(MDI),
    .md_oe(MD_OE),
    
    .wrh_rdl(wrh_rdl),
    .reg_addr(reg_addr),
    .wr_data(wr_data),
    .rd_data(rd_data),
    .start(SMI_start),
    .complete(SMI_complete)
);

状态机具体实现

always @(posedge clk) begin
    led <= INT;
end

// State machine
always @(posedge clk or posedge rst) begin
    if(rst) begin
        state <= IDLE;
    end
    else begin
        state <= next_state;
    end
end

reg [25:0] wait_count;
(* dont_touch = "true" *)reg        read_phase;
(* dont_touch = "true" *)reg [3:0]  read_data;

always @(posedge clk) begin
    case(state)
        IDLE: begin
            next_state <= RESET;
            RESETn <= 1'b0;
            reg_addr <= 5'd0;
            wrh_rdl <= 1'b0;
            SMI_start <= 1'b0;
            data_from_SMI <= 16'h0000;
            wait_count <= 26'd0;
            PHYAD0 <= 1'b0; // Set PHY address to 0
            INTSEL <= 1'b1; // REF_CLK In Mode
            MODE <= 3'b111;
            strap_oe <= 1'b1;
            read_phase <= 1'b0;
            read_data <= 4'd0;
        end
        RESET: begin
            next_state <= SETMODE;
            RESETn <= 1'b0;
        end
        // Need to wait for 200us, which is 20000 clock cycles in 100MHz
        SETMODE: begin
            MODE <= 3'b111;
            PHYAD0 <= 1'b0;
            INTSEL <= 1'b1;
            if(wait_count < 26'd20000) begin
                wait_count <= wait_count + 26'd1;
            end
            else begin
                next_state <= UNRESET;
            end
        end
        UNRESET: begin
            strap_oe <= 1'b0;
            RESETn <= 1'b1;
            wait_count <= 26'd0;
            if(switch_continue) begin
                next_state <= RESET_SOFT;
            end
        end
        RESET_SOFT: begin
            if(SMI_complete) begin
                next_state <= UNRESET_SOFT;
                SMI_start <= 1'b0;
            end
            else begin
                SMI_start <= 1'b1;
                wrh_rdl <= 1'b1;
                reg_addr <= 5'd0;
                wr_data <= 16'h8000;
            end
        end
        // Need to be kept in software reset for about 0.5s
        UNRESET_SOFT: begin
            if(wait_count < 26'd50000000) begin
                wait_count <= wait_count + 26'd1;
            end
            else begin
                next_state <= RDPHYID1;
            end
        end
        RDPHYID1: begin
            if(SMI_complete) begin
                data_from_SMI <= rd_data;
                next_state <= RDPHYID2;
                SMI_start <= 1'b0;
            end
            else begin
                SMI_start <= 1'b1;
                wrh_rdl <= 1'b0;
                reg_addr <= 5'd2;
            end
        end
        RDPHYID2: begin
            if(SMI_complete) begin
                data_from_SMI <= rd_data;
                next_state <= RD_BC0;
                SMI_start <= 1'b0;
            end
            else begin
                SMI_start <= 1'b1;
                wrh_rdl <= 1'b0;
                reg_addr <= 5'd3;
            end
        end
        RD_BC0: begin
            if(SMI_complete) begin
                data_from_SMI <= rd_data;
                next_state <= RD_BS1;
                SMI_start <= 1'b0;
            end
            else begin
                SMI_start <= 1'b1;
                wrh_rdl <= 1'b0;
                reg_addr <= 5'd0;
            end
        end
        RD_BS1: begin
            if(SMI_complete) begin
                data_from_SMI <= rd_data;
                next_state <= RX_TX;
                SMI_start <= 1'b0;
            end
            else begin
                SMI_start <= 1'b1;
                wrh_rdl <= 1'b0;
                reg_addr <= 5'd1;
            end
        end
        RX_TX: begin
            SMI_start <= 1'b0;
            rxd_d <= RXD;
            rxerr_d <= RXERR;
            crs_dv_d <= CRS_DV;
            if(crs_dv_d) begin
                read_phase <= ~read_phase;  // invert every time a new signal detected
                if(read_phase) begin
                    read_data[1:0] <= rxd_d;
                end
                else begin
                    read_data[3:2] <= rxd_d;
                end
            end
        end
        default: begin
            next_state <= IDLE;
        end
    endcase
end

endmodule

状态机SMI接口控制子模块

module SMI_manage(
    input             clk,
    input             rst,

    output reg        mdc,
    output reg        mdo,
    input             mdi,
    output reg        md_oe,
    
    input             wrh_rdl,
    input      [4:0]  reg_addr,
    input      [15:0] wr_data,
    input             start,
    output reg [15:0] rd_data,
    output reg        complete
);

接口定义

  • clk, rst是时钟和复位
  • mdc时钟,mdo输出,mdi输入,md_oe输出使能
  • wrh_rdl,高电平是写指令,低电平时读指令
  • reg_addr,寄存器地址
  • wr_data,写指令用的16位数
  • start,其上升沿作为一个指令的开始
  • rd_data,一个读指令读出来的16位数
  • complete,完成信号,表示可以开启下一个指令
// MDIO input and output control
reg  mdi_d;

always @(posedge clk) begin
    mdi_d <= mdi;
end

// MDC generator, count to 50 and invert, 100MHz => 1MHz
reg  [5:0] mdc_count;
reg        mdc_en;
reg  [1:0] mdc_d;
wire       mdc_negedge;
wire       mdc_posedge;

always @(posedge clk or posedge rst) begin
    if(rst) begin
        mdc_count <= 6'd0;
        mdc <= 1'b0;
    end
    else if(mdc_en) begin
        if(mdc_count < 6'd50) begin
            mdc_count <= mdc_count + 6'd1;
        end
        else begin
            mdc_count <= 6'd0;
            mdc <= ~mdc;
        end
    end
end

由使能信号控制的mdc时钟输出,每延迟50个时钟反一次,把100MHz降频到1MHz

// negative edge detection, MDIO read and write only happen at negative edge
always @(posedge clk) begin
    mdc_d <= {mdc_d[0], mdc};
end
assign mdc_negedge = (mdc_d == 2'b10) ? 1'b1 : 1'b0;
assign mdc_posedge = (mdc_d == 2'b01) ? 1'b1 : 1'b0;

// Detect the rising edge of input signal start
reg [1:0]  start_d;
wire       start_posedge;

always @(posedge clk) begin
    start_d <= {start_d[0],start};
end
assign start_posedge = (start_d == 2'b01) ? 1'b1 : 1'b0;

侦测mdc的下降沿和start信号的上升沿,避免信号持续时间长短导致的逻辑错误

// State machine with three parts
reg [1:0]  state;
reg [5:0]  md_count;
reg [45:0] data1;
reg [15:0] data2;
reg        complete_d;

always @(posedge clk or posedge rst) begin
    if(rst) begin
        state <= 2'b00;
        complete <= 1'b0;
        mdc_en <= 1'b0;
        md_count <= 6'd0;
        mdo <= 1'b1;
        md_oe <= 1'b0;
        data1 <= 46'd0;
        data2 <= 16'd0;
        rd_data <= 16'd0;
    end
    else begin
        case(state)
            // Wait for start signal
            2'b00: begin
                md_count <= 6'd0;
                md_oe <= 1'b0;
                mdo <= 1'b1;
                complete <= complete_d;
                complete_d <= 1'b0;
                if(start_posedge) begin
                    state <= 2'b01;
                    mdc_en <= 1'b1;
                    data1 <= {32'hFFFFFFFF, 2'b01, (wrh_rdl)?2'b01:2'b10, 5'h00, reg_addr};
                    data2 <= wr_data;
                end
            end
            // Preamble, Start of Frame, OP Code, PHY addr, reg addr
            // length of 46
            2'b01: begin
                md_oe <= 1'b1;
                if(mdc_negedge) begin
                    {mdo,data1} <= {data1,1'b0};
                end
                
                if(mdc_negedge & (md_count < 6'd45)) begin
                    md_count <= md_count + 6'd1;
                end
                else if(mdc_negedge) begin
                    md_count <= 6'd0;
                    state <= 2'b10;
                end
            end
            // Turn around
            // length of 2
            2'b10: begin
                if(mdc_negedge && (md_count == 6'd0)) begin
                    md_count <= 6'd1;
                end
                else if(mdc_negedge) begin
                    md_count <= 6'd0;
                    md_oe <= wrh_rdl;
                    state <= 2'b11;
                end
            end
            // Data to or from PHY
            // length of 16
            2'b11: begin
                if(mdc_negedge) begin
                    {mdo,data2} <= {data2,1'b0};
                    rd_data <= {rd_data[14:0], mdi_d};
                end
                
                if(mdc_negedge & (md_count < 6'd15)) begin
                    md_count <= md_count + 6'd1;
                end
                else if(mdc_negedge) begin
                    md_count <= 6'd0;
                    state <= 2'b00;
                    complete <= 1'b1;
                    complete_d <= 1'b1;
                end
            end
        endcase
    end
end

endmodule

SMI控制子模块内部也有一个小的状态机,用来控制SMI指令不同部分:前序、读写模式、物理层地址、寄存器地址、读写翻转、读/写数据。

大致可以分为46位的输入、翻转、读/写数据和等待四部分,分别对应这个状态机的各个部分

模拟仿真

Testbench

仿真用的testbench和前面的教程比较相似,没有包括LAN8720A的仿真模块(其实是没找到),主要以后期的ChipScope为主。代码如下

`timescale 1ns/1ns

module tb_ethernet;

reg clock;
reg reset;
wire led;

initial begin
    clock = 1'b0;
    reset = 1'b0;
    // Reset for 1us
    #100 
    reset = 1'b1;
    #1000
    reset = 1'b0;
end

// Generate 100MHz clock signal
always #5 clock <= ~clock;


ethernet ethernet_top(
    .clk          (clock),
    .rst          (reset),
    .switch_continue (1'b1),
    .led          (led),
    
    // LAN8720A PHY chip port
    .MDIO         (),
    .MDC          (),
    .RESETn       (),
    .RXD1_MODE1   (),
    .RXD0_MODE0   (),
    .RXERR_PHYAD0 (),
    .TXD0         (),
    .TXD1         (),
    .TXEN         (),
    .CRS_DV_MODE2 (),
    .INT_REFCLKO  (),
    .CLKIN        ()
);

endmodule

仿真脚本

写一个简单的仿真脚本sim.do,由于没有调用Xilinx的IP,不需要包含库文件和glbl.v:

vlib work
vlog ../src/ethernet.v ../src/SMI_manage.v ./tb_ethernet.v
vsim work.tb_ethernet -voptargs=+acc +notimingchecks
log -depth 7 /tb_ethernet/*
do wave.do
run 1ms

仿真结果

FPGA基础入门【10】开发板Ethernet PHY局域网配置_第16张图片
调用仿真脚本do sim.do后,得到如上方结果,放大可以看到SMI控制的具体细节,不过由于读取引脚悬空,没有连接任何信号,读出来的是蓝色代表的高阻态

编译烧写

新建一个叫ethernet的project,初始配置可以参考之前的教程。添加代码文件ethernet.v和SMI_manage.v。

下一步加入约束constraint文件ethernet.xdc,同样这是用标准模板取自己需要部分修改出来的(NEXYS 4 DDR Master XDC):

## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports clk]
create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports clk]


##Switches

set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports rst]
set_property -dict {PACKAGE_PIN L16 IOSTANDARD LVCMOS33} [get_ports switch_continue]


## LEDs

set_property -dict {PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [get_ports led]


##SMSC Ethernet PHY

set_property -dict {PACKAGE_PIN C9 IOSTANDARD LVCMOS33} [get_ports MDC]
set_property -dict {PACKAGE_PIN A9 IOSTANDARD LVCMOS33} [get_ports MDIO]
set_property -dict {PACKAGE_PIN B3 IOSTANDARD LVCMOS33} [get_ports RESETn]
set_property -dict {PACKAGE_PIN D9 IOSTANDARD LVCMOS33} [get_ports CRS_DV_MODE2]
set_property -dict {PACKAGE_PIN C10 IOSTANDARD LVCMOS33} [get_ports RXERR_PHYAD0]
set_property -dict {PACKAGE_PIN C11 IOSTANDARD LVCMOS33} [get_ports RXD0_MODE0]
set_property -dict {PACKAGE_PIN D10 IOSTANDARD LVCMOS33} [get_ports RXD1_MODE1]
set_property -dict {PACKAGE_PIN B9 IOSTANDARD LVCMOS33} [get_ports TXEN]
set_property -dict {PACKAGE_PIN A10 IOSTANDARD LVCMOS33} [get_ports TXD0]
set_property -dict {PACKAGE_PIN A8 IOSTANDARD LVCMOS33} [get_ports TXD1]
set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS33} [get_ports CLKIN]
set_property -dict {PACKAGE_PIN B8 IOSTANDARD LVCMOS33} [get_ports INT_REFCLKO]

到这里可以点击 Run Synthesis做综合,几秒钟完成后用Set Up Debug配置ChipScope:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第17张图片
设置观察长度为8192,因为持续时间会比较长。下面就可以Run Implementation和Generate Bitstream生成配置文件了。

和前面的教程一样,USB线连接NEXYS4板子,开启Hardware Manager,然后auto连接上板子,Program Device烧写进程序,注意Debug probes file有对应的ltx文件,完成后用网线连接任意一台主机或者猫到开发板上的RJ45接口,只要把RESET拨回到0,就可以看到它旁边的两个LED灯亮起:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第18张图片

下面看ChipScope抓取的结果,观察前面的代码,我们有加入一个辅助ChipScope的开关,用的是第二个开关,而复位是第一个开关。如此设计来观察不同的寄存器读取:

  1. 烧写编译好的程序
  2. Switch_continue拉低,阻止状态机前进,复位拉高进入复位状态
  3. 复位拉低,进行硬件复位和软件复位
  4. 在ChipScope中,配置trigger为state等于某个值,并点击开始抓取,可以看到ChipScope处在等待trigger的状态
  5. 拉高Switch_continue,让状态机继续前进,可以从ChipScope中看到希望的状态机的读取结果

我们读取了四个寄存器,它们在ChipScope里的结果分别如下

PHYID1读出的结果是0x0007:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第19张图片
PHYID2读出的结果是0xc0f1:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第20张图片
Basic Control基础控制寄存器的结果是0x3000
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第21张图片
Basic Status基础状态寄存器的结果是0x7809
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第22张图片
查阅文档可以看出读出的结果和预期是相符的。

在这之后我们就可以看看是否从网线上读出了什么数据,修改ChipScope的trigger为CRS_DV上升沿R,抓取到的数据如下:
FPGA基础入门【10】开发板Ethernet PHY局域网配置_第23张图片
可以清楚的看到LAN8720A接收到了一串数据包,放大可以看清具体数据,如果仔细分析甚至可以看到IP地址、MAC地址之类的信息,这个就不公开了,可以自己尝试后自己分析

总结

这篇教程介绍的是NEXYS 4开发板上局域网物理层芯片LAN8720A的用法,这块芯片因为配置简单而非常常用,市面上可以买到的嵌入式局域网模块很多都是基于这款芯片,因此这篇教程不只是针对FPGA的开发者,还可以让其他嵌入式系统的学习者借鉴,比如想用Arduino控制类似局域网模块的开发者。

NEXYS 4上已经介绍了一部分接口,剩下的有UART串口通信、USB接口、麦克风、Pmod通用接口、温度传感器、模数转换ADC、音频接口、视频接口VGA。当全部介绍过之后,一个熟练的FPGA开发者就可以综合利用板上的几乎全部资源,再加上FPGA强大的并行计算能力,能做出很多ARM架构嵌入式系统无法完成的效果。

下一篇介绍UART串口通信,利用平时烧写芯片的USB线和开发板通信

你可能感兴趣的:(FPGA)