tkcb8b 发表于 2019-3-26 13:13:44

MDIO接口实现


一、前言  

  本文设计思想采用明德扬至简设计法。以太网这一高效实用的数据传输方式应用于各个领域,如网络交换设备,高速网络相机等。虽然各FPGA厂商都提供MAC IP核,但大多收费,有时无法破解。不同厂家之间无法移植,而且为了通用性考虑牺牲了效率,因此自己动手写一个以太网MAC是个不错的选择。

  本博文讨论通过MDIO接口管理PHY芯片来验证其正确工作,为在此基础上设计MAC逻辑开个头。PHY芯片采用RTL8211EGVB,选用GMII接口与MAC连接。下面我们来开始第一步,在此之前明确设计目的:检测PHY芯片是否完成自动协商 链路速率是否达到1000M。所以要从datasheet中了解到芯片引脚 寄存器地址 接口时序。

二、设计分析

  管理帧格式如下:



  读写操作时序:





  MDC为MAC驱动时钟信号,MDIO是串行数据总线,需要连接上拉电阻保证idle状态下高电平。其中前导码包含32个比特“1”,PHY地址根据芯片引脚连接而定,此处为01.turn around域是为了防止读操作时数据冲突,在读操作过程中MAC和PHY均在第1比特处进入高阻态,PHY在第2比特处驱动MDIO接口为低电平以占据总线控制权。注意两点:第一如果时钟信号在读写操作后停止,时钟必须保证至少7个时钟周期持续翻转且MDIO高电平从而保证之前的操作完成。故在设计中可以等待一段时间后再拉低时钟使能信号。第二两个操作之间至少一个idle比特。

  正确驱动接口时序需要关注AC characterisics.

         

  很明显MAC驱动总线时,在MDC下降沿更新数据。而PHY驱动总线时,MDC上升沿后更新数据。根据datasheet中的timing参数设定MDC时钟周期是800ns,MAC接收PHY数据时下降沿采样。

  接下来关注要访问的内部寄存器地址,首先读取PHY寄存器数据以检测其工作状态,若发现异常再考虑写入数据。这里读取基本模式状态寄存器0X01的bit5,若为1说明自动协商完成。第二个寄存器是PHY特定状态寄存器0X11中的和13,分别是当前速率和全/半双工通信模式。若检测到自动协商完成,且工作在1000M全双工模式下,说明工作正确。

三、硬件架构与状态机设计

  所有准备工作完成,现在开始设计。按照“自顶向下”设计原则,规划好整体结构和模块间接口,再设计内部状态机一步步实现逻辑功能。



  Mdio_ctrl模块负责完成PHY芯片的配置与检测逻辑,Mdio接口模块完成读写操作时序。此处仅通过读操作简单检测PHY状态,暂不进行配置,故两模块工作状态跳转如图所示:



  剩下的工作就是把两个状态机实现出来,非常简单。有需要的朋友可以参考一下,关于芯片的具体参数详见:Realtek RTL8211E(G)-VB(VL)-CG Datasheet 1.8.上代码!

四、代码编写

MDIO控制模块:


复制代码
1 `timescale 1ns / 1ps
2
3 module mdio_ctrl(
4   input         clk,//100M
5   input         rst_n,
6
7   input         en,
8   output reg          chk_result =0,
9   output reg          chk_vld =0,
10
11   input         rdy,   
12   output reg          rd_en =0,
13   output reg phy_addr =0,
14   output reg reg_addr =0,
15   input rd_data,
16   input         rd_vld
17 );
18
19 parameter MS_CYC = 100_000;
20
21
22 localparamIDLE = 0 ;
23 localparamWAIT = 1 ;
24 localparamRD_PHY = 2 ;
25 localparamCHECK = 3 ;
26
27 localparam WAIT_MS = 10;   
28      
29 localparam BMSR = 5'h01,
30            PHYSR = 5'h11;
31
32 reg state_c = 0,state_n = 0;
33 wire idle2wait,wait2rd_phy,rd_phy2check,check2idle,check2wait;
34 wire link_up;
35 reg rd_memory ;
36 reg [ (17-1):0]ms_cnt =0    ;
37 wire      add_ms_cnt ;
38 wire      end_ms_cnt ;
39 reg [ (4-1):0]wait_cnt=0    ;
40 wire      add_wait_cnt ;
41 wire      end_wait_cnt ;
42 reg [ (2-1):0]rd_cnt=0    ;
43 wire      add_rd_cnt ;
44 wire      end_rd_cnt ;
45 reg [ (2-1):0]rdata_cnt=0    ;
46 wire      add_rdata_cnt ;
47 wire      end_rdata_cnt ;   
48 wire registers;   
49 reg rd_finish = 0;
50
51 initial begin
52   rd_memory = 0;
53   rd_memory = 0;
54 end
55
56 always @(posedge clk or negedge rst_n) begin
57   if (rst_n==0) begin
58         state_c <= IDLE ;
59   end
60   else begin
61         state_c <= state_n;
62    end
63 end
64
65 always @(*) begin
66   case(state_c)
67         IDLE :begin
68             if(idle2wait)
69               state_n = WAIT ;
70             else
71               state_n = state_c ;
72         end
73         WAIT :begin
74             if(wait2rd_phy)
75               state_n = RD_PHY ;
76             else
77               state_n = state_c ;
78         end
79         RD_PHY :begin
80             if(rd_phy2check)
81               state_n = CHECK ;
82             else
83               state_n = state_c ;
84         end
85         CHECK :begin
86             if(check2idle)
87               state_n = IDLE ;
88             else if(check2wait)
89               state_n = WAIT ;
90             else
91               state_n = state_c ;
92         end
93         default : state_n = IDLE ;
94   endcase
95 end
96
97 assign idle2wait    = state_c==IDLE && (en);
98 assign wait2rd_phy= state_c==WAIT && (end_wait_cnt);
99 assign rd_phy2check = state_c==RD_PHY && (end_rdata_cnt);
100 assign check2idle   = state_c==CHECK && (link_up);
101 assign check2wait   = state_c==CHECK && (!link_up);
102
103
104 assign link_up = rd_memory == 1'b1 && rd_memory == 3'b10_1;//auto_nego && gigabit && full_duplex
105
106
107 //计数器
108 always @(posedge clk or negedge rst_n) begin
109   if (rst_n==0) begin
110         ms_cnt <= 0;
111   end
112   else if(add_ms_cnt) begin
113         if(end_ms_cnt)
114             ms_cnt <= 0;
115         else
116             ms_cnt <= ms_cnt+1 ;
117    end
118 end
119 assign add_ms_cnt = (state_c == WAIT);
120 assign end_ms_cnt = add_ms_cnt&& ms_cnt == (MS_CYC)-1 ;//100MHZ时钟100_000
121
122 always @(posedge clk or negedge rst_n) begin
123   if (rst_n==0) begin
124         wait_cnt <= 0;
125   end
126   else if(add_wait_cnt) begin
127         if(end_wait_cnt)
128             wait_cnt <= 0;
129         else
130             wait_cnt <= wait_cnt+1 ;
131    end
132 end
133 assign add_wait_cnt = (end_ms_cnt);
134 assign end_wait_cnt = add_wait_cnt&& wait_cnt == (WAIT_MS)-1 ;
135
136 always @(posedge clk or negedge rst_n) begin
137   if (rst_n==0) begin
138         rd_cnt <= 0;
139   end
140   else if(add_rd_cnt) begin
141         if(end_rd_cnt)
142             rd_cnt <= 0;
143         else
144             rd_cnt <= rd_cnt+1 ;
145    end
146 end
147 assign add_rd_cnt = (state_c == RD_PHY && rdy && !rd_finish);
148 assign end_rd_cnt = add_rd_cnt&& rd_cnt == (2)-1 ;
149
150 always@(posedge clk or negedge rst_n)begin
151   if(rst_n==1'b0)begin
152         rd_finish <= 0;
153   end
154   else if(end_rd_cnt)begin
155         rd_finish <= 1'b1;
156   end
157   else if(state_c == CHECK)
158         rd_finish <= 0;
159 end
160
161
162 always @(posedge clk or negedge rst_n) begin
163   if (rst_n==0) begin
164         rdata_cnt <= 0;
165   end
166   else if(add_rdata_cnt) begin
167         if(end_rdata_cnt)
168             rdata_cnt <= 0;
169         else
170             rdata_cnt <= rdata_cnt+1 ;
171    end
172 end
173 assign add_rdata_cnt = (rd_vld);
174 assign end_rdata_cnt = add_rdata_cnt&& rdata_cnt == (2)-1 ;
175
176 //接口信号逻辑
177 always@(posedge clk or negedge rst_n)begin
178   if(rst_n==1'b0)begin
179         rd_en <= 0;
180         phy_addr <= 0;
181         reg_addr <= 0;
182   end
183   else if(add_rd_cnt)begin
184         rd_en <= 1'b1;
185         phy_addr <= 5'b00001;
186         reg_addr <= registers;
187   end
188   else begin
189         rd_en <= 0;
190         phy_addr <= 0;
191         reg_addr <= 0;
192   end
193 end
194
195 assign registers = {BMSR,PHYSR};//5'h01,5'h11
196
197 always@(posedge clk or negedge rst_n)begin
198   if(rst_n==1'b0)begin
199         rd_memory <= 0;
200         rd_memory <= 0;
201   end
202   else if(add_rdata_cnt)begin
203         rd_memory <= rd_data;
204   end
205 end
206
207 //用户侧输出检测结果
208 always@(posedge clk or negedge rst_n)begin
209   if(rst_n==1'b0)begin
210         chk_vld <= 0;
211   end
212   else if(state_c == CHECK)begin
213         chk_vld <= 1'b1;
214   end
215   else
216         chk_vld <= 0;
217 end
218
219 always@(posedge clk or negedge rst_n)begin
220   if(rst_n==1'b0)begin
221         chk_result <= 0;
222   end
223   else if(check2idle)begin
224         chk_result <= 1'b1;
225   end
226   else if(check2wait)
227         chk_result <= 0;
228 end
229
230 endmodule
复制代码
MDIO时序接口模块:


复制代码
1 `timescale 1ns / 1ps
2
3 module mdio_interface#(parameter MDC_CYC = 800)//ns
4 (
5   input                   clk,//100M时钟
6   input                   rst_n,
7
8   input                   rd_en,
9   input          phy_addr,
10   input          reg_addr,
11   output reg    rd_data =0,
12   output reg            rd_vld =0,
13   output reg            rdy =0,
14
15   output reg            mdo =1,
16   output reg            mdo_en =0,
17   input                   mdi,
18   output reg            mdc =1
19   );
20
21   localparam N = MDC_CYC/10;
22
23
24 localparamIDLE = 0 ;
25 localparamWRI_COM = 1 ;
26 localparamRD_DATA = 2 ;
27
28 localparam PRE      = 32'hffff_ffff,
29            START    = 2'b01,
30            OP       = 2'b10,
31            TA       = 2'b11;
32
33 reg state_c =0,state_n =0;
34 wire idle2wri_com,wri_com2rd_data,rd_data2idle;
35 reg [ (7-1):0]div_cnt=0    ;
36 wire      add_div_cnt ;
37 wire      end_div_cnt ;
38 reg [ (6-1):0]bit_cnt=0    ;
39 wire      add_bit_cnt ;
40 wire      end_bit_cnt ;
41 reg M =0;
42 wire command;
43 reg   rd_flag=0    ;
44 reg phy_addr_tmp = 0;
45 reg reg_addr_tmp = 0;
46
47
48 //寄存地址
49 always@(posedge clk or negedge rst_n)begin
50   if(rst_n==1'b0)begin
51         phy_addr_tmp <= 0;
52         reg_addr_tmp <= 0;
53   end
54   else if(rd_en)begin
55         phy_addr_tmp <= phy_addr;
56         reg_addr_tmp <= reg_addr;
57   end
58 end
59
60
61 always@(*)begin
62   if(state_c == IDLE && !rd_en && !rd_flag)
63         rdy <= 1;
64   else
65         rdy <= 0;
66 end
67
68 always @(posedge clk or negedge rst_n) begin
69   if (rst_n==0) begin
70         state_c <= IDLE ;
71   end
72   else begin
73         state_c <= state_n;
74    end
75 end
76
77 always @(*) begin
78   case(state_c)
79         IDLE :begin
80             if(idle2wri_com)
81               state_n = WRI_COM ;
82             else
83               state_n = state_c ;
84         end
85         WRI_COM :begin
86             if(wri_com2rd_data)
87               state_n = RD_DATA ;
88             else
89               state_n = state_c ;
90         end
91         RD_DATA :begin
92             if(rd_data2idle)
93               state_n = IDLE ;
94             else
95               state_n = state_c ;
96         end
97         default : state_n = IDLE ;
98   endcase
99 end
100
101
102 assign idle2wri_com   = state_c==IDLE   && end_div_cnt && (rd_flag || rd_en);
103 assign wri_com2rd_data= state_c==WRI_COM&& end_bit_cnt;
104 assign rd_data2idle   = state_c==RD_DATA&& end_bit_cnt;
105
106
107 always @(posedge clk or negedge rst_n )begin
108   if(rst_n==0) begin
109         rd_flag <= (0);
110   end
111   else if(state_c == IDLE && rd_en)begin
112         rd_flag <= (1'b1);
113   end
114   else if(state_c == WRI_COM)
115         rd_flag <= 0;
116 end
117
118
119 //分频计数器
120 always @(posedge clk or negedge rst_n) begin
121   if (rst_n==0) begin
122         div_cnt <= 0;
123   end
124   else if(add_div_cnt) begin
125         if(end_div_cnt)
126             div_cnt <= 0;
127         else
128             div_cnt <= div_cnt+1 ;
129    end
130 end
131 assign add_div_cnt = (1);
132 assign end_div_cnt = add_div_cnt&& div_cnt == (N)-1 ;
133
134 //比特计数器
135 always @(posedge clk or negedge rst_n) begin
136   if (rst_n==0) begin
137         bit_cnt <= 0;
138   end
139   else if(add_bit_cnt) begin
140         if(end_bit_cnt)
141             bit_cnt <= 0;
142         else
143             bit_cnt <= bit_cnt+1 ;
144    end
145 end
146 assign add_bit_cnt = (end_div_cnt && state_c != IDLE);
147 assign end_bit_cnt = add_bit_cnt&& bit_cnt == (M)-1 ;
148
149 always@(*)begin
150   case(state_c)
151         WRI_COM:M = 48;
152         RD_DATA:M = 16;
153         default:M = 10;
154   endcase
155 end
156
157 //mdc时钟
158 always @(posedge clk or negedge rst_n )begin
159   if(rst_n==0) begin
160         mdc <= (1'b1);
161   end
162   else if(add_div_cnt && div_cnt == (N>>1) - 1)begin
163         mdc <= (1'b1);
164   end
165   else if(end_div_cnt)
166         mdc <= 0;
167 end
168
169
170 //mdio输出
171 always @(posedge clk or negedge rst_n )begin
172   if(rst_n==0) begin
173         mdo <= (1'b1);
174   end
175   else if(add_bit_cnt && state_c == WRI_COM)begin
176         mdo <= command;
177   end
178   else if(state_c != WRI_COM)
179         mdo <= 1'b1;
180 end
181
182 assign command = {PRE,START,OP,phy_addr_tmp,reg_addr_tmp,TA};
183
184 always @(posedge clk or negedge rst_n )begin
185   if(rst_n==0) begin
186         mdo_en <= (0);
187   end
188   else if(state_c == WRI_COM && add_bit_cnt)
189         case(bit_cnt)
190             0: mdo_en <= 1'b1;
191             46:mdo_en <= 0;
192             default:;
193         endcase
194 end
195
196 //mdio输入
197 always @(posedge clk or negedge rst_n )begin
198   if(rst_n==0) begin
199         rd_data <= (0);
200   end
201   else if(add_bit_cnt && state_c == RD_DATA)begin
202         rd_data <= (mdi);
203   end
204 end
205
206 always @(posedge clk or negedge rst_n )begin
207   if(rst_n==0) begin
208         rd_vld <= (0);
209   end
210   else if(rd_data2idle)begin
211         rd_vld <= (1'b1);
212   end
213   else
214         rd_vld <= 0;
215 end
216
217
218 endmodule
复制代码
顶层封装:

phy_manage
五、功能仿真

之后编写testbench进行行为仿真:


复制代码
1 `timescale 1 ns/1 ps
2
3 `define BIT_CNT uut.mdio_interface.bit_cnt
4
5 module phy_manage_tb();
6
7 //时钟和复位
8 reg clk;
9 reg rst_n;
10
11 //uut的输入信号
12 reg mdio_en;
13
14 //uut的输出信号
15 wire link_up;
16 wire chk_done;
17 wire mdc;
18 wire mdio;
19 wire back_data1,back_data2;
20
21         //时钟周期,单位为ns,可在此修改时钟周期。
22         parameter CYCLE    = 10;
23
24         //复位时间,此时表示复位3个时钟周期的时间。
25         parameter RST_TIME = 2 ;
26
27         defparam uut.mdio_ctrl.MS_CYC = 100;
28
29         //待测试的模块例化
30      phy_manage uut(
31      .clk       (clk) ,
32      .rst_n   (rst_n) ,
33
34      .mdio_en   (mdio_en) ,
35      .link_up   (link_up) ,
36      .chk_done(chk_done) ,
37
38      .mdc       (mdc) ,
39      .mdio      (mdio)
40   );
41
42
43      //生成本地时钟50M
44      initial begin
45          clk = 1;
46          forever
47          #(CYCLE/2)
48          clk=~clk;
49      end
50
51      //产生复位信号
52      initial begin
53          rst_n = 1;
54          #1;
55          rst_n = 0;
56          #(CYCLE*RST_TIME);
57          rst_n = 1;
58      end
59
60      //输入信号din0赋值方式
61      initial begin
62          #1;
63          //赋初值
64          mdio_en = 0;
65          #(10*CYCLE);
66          mdio_en = 1;
67          #(1*CYCLE);
68          mdio_en = 0;
69          //开始赋值
70         #100_000;
71         $stop;
72      end
73   
74      //模拟PHY响应
75
76   //data
77   assign back_data1 = {16'b0000_0000_0010_0000};
78   assign back_data2 = {16'b1010_0000_0000_0000};
79
80   integer i = 0,j = 0;
81   initial begin
82         forever begin
83             wait(uut.mdio_interface.state_c == 1 && `BIT_CNT == 47 );
84             @(posedge mdc);
85             force mdio = 0;
86             @(posedge mdc);
87             j = j+1;
88             if(j == 1)
89               force mdio = back_data1;
90             else
91               force mdio = back_data2;
92
93             wait(uut.mdio_interface.state_c == 0);
94             @(posedge mdc);
95             release mdio;
96         end
97   end
98
99   initial begin
100         forever begin
101             @(posedge mdc);
102             if(uut.mdio_interface.state_c == 2)begin
103               #10;
104               i = i+1;
105             end
106             else
107               i = 0;
108         end
109   end
110
111
112endmodule
复制代码
  testbench中利用force强迫更新mdio双向端口方式模拟PHY芯片响应。仿真波形上半部分为MDIO控制模块信号,下半部分则是MDIO时序接口模块信号。可见当读取寄存器数值满足PHY工作需求时,link_up信号拉高,证明此时MAC可以传输数据给PHY。

六、板级调试

  完整的设计,板级调试是必不可少的。真正地将接口调通,PHY芯片正确响应才能说明达到设计目的。顶层封装测试工程,内部例化:差分时钟缓冲原语、PLL、PHY管理顶层封装以及VIO ILA调试IP。我们来看下原理图顶层:



测试工程顶层:


复制代码
1 `timescale 1ns / 1ps
2
3
4 module mdio_test(
5   input sys_clk_p,
6   input sys_clk_n,
7   input rst_n,
8
9   output mdc,
10   inout mdio,
11
12   output phy_reset//PHY芯片复位信号 低有效
13   );
14
15
16 wire sys_clk_ibufg;
17 wire clk;
18 wire en;
19 wire chk_done;
20 wire link_up;
21
22 assign phy_reset = 1'b1;//始终不复位
23
24 IBUFGDS #
25 (
26 .DIFF_TERM ("FALSE"),
27 .IBUF_LOW_PWR ("FALSE")
28 )
29 u_ibufg_sys_clk
30 (
31 .I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接
32 .IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接
33 .O (sys_clk_ibufg) //时钟缓冲输出
34 );
35
36clk_wiz_0 u_clk
37    (
38   // Clock out ports
39   .clk_out1(clk),   // output clk_out1 100Mhz
40    // Clock in ports
41   .clk_in1(sys_clk_ibufg));      // input clk_in1
42
43vio_0 u_vio (
44   .clk(clk),                // input wire clk
45   .probe_out0(en)// output wire probe_out0
46 );
47
48 phy_manage phy_manage(
49   .clk      (clk),
50   .rst_n    (rst_n),
51
52   .mdio_en(en),
53   .link_up(link_up),
54   .chk_done (chk_done),
55
56   .mdc      (mdc),
57   .mdio   (mdio)
58   );
59
60
61 endmodule
复制代码
时钟引脚约束文件:


复制代码
1 create_clock -period 5.000
2 set_property PACKAGE_PIN R4
3 set_property IOSTANDARD DIFF_SSTL15
4
5 set_property PACKAGE_PIN T6
6 set_property IOSTANDARD LVCMOS15
7
8 set_property PACKAGE_PIN W10
9 set_property IOSTANDARD LVCMOS33
10
11 set_property PACKAGE_PIN V10
12 set_property IOSTANDARD LVCMOS33
13
14 set_property PACKAGE_PIN L15
15 set_property IOSTANDARD LVCMOS33
复制代码
 
  有一点相信调试过以太网的人大多都跳过一个坑:没有驱动PHY的复位输入信号。本人也在此处栽过跟头,这里直接连续赋值拉高PHY芯片复位信号。关于板级调试还有个小技巧,根据高亚军老师的书籍得知,将set up debug生成的ILA探针相关约束命令单独放入一个约束文件便于调试IP的管理和修改,debug约束文件就不贴出来了。

  查看debug波形,MDIO时序接口模块在释放MDIO串行总线时,由于存在上拉电阻为高电平,下一个MDC时钟上升沿时刻,PHY拉低MDIO信号响应并得到总线控制权,开始输出数据。



  得到读取的两个寄存器数据,根据数值分析满足:PHY自动协商完成,且工作在全双工1000Mbps速率下。



  最终RJ45接口绿色指示灯常亮,表明自动协商完成,网络连接正确。到此简易的PHY芯片检测管理模块设计完成。
页: [1]
查看完整版本: MDIO接口实现