jjzjj

千兆以太网(二)——MDIO接口协议

FPGA小白758 2023-12-18 原文

1.MDIO协议简介

  MAC和PHY芯片有一个配置接口,即MDIO接口。可以配置PHY芯片的工作模式以及获取PHY芯片的状态信息。PHY芯片内部有一系列寄存器。用户通过配置寄存器来配置PHY芯片的工作模式。
  FPGA通过MDIO接口对PHY芯片的内部寄存器进行配置。通常情况下芯片在默认情况下也可以工作,即配置芯片不是必须的。也可通过外接特殊引脚的方式来配置PHY芯片的工作模式。

2. MDIO协议时序

  MDIO接口也被称为SMI接口(Serial Management Interface,串行管理接口),包括ETH_MDC(数据管理时钟,最大不超过12.5MHZ)和ETH_MDIO(数据管理输入输出,双向数据线)两条信号线。
  MDIO接口的读写通信协议如下图:

名称1作用
Preamble32位引导码,由MAC端发送32位逻辑1,用于同步PHY芯片
ST(Start of Frame)两位帧开始信号,用01表示
OP(Operation Code)两位操作码,读:10 , 写:01
PHYAD五位PHY地址,用于表示和那个PHY芯片通讯
REGAD(Register Address)五位寄存器地址,可以表示32位寄存器
TA(Turnaround)两位转向。在读命令中MDIO由MAC驱动改为PHY驱动。写命令中MAC固定输入01
data读取PHYAD寄存器中对应的数据或者写入数据。高位在前低位在后
IDLE空闲状态均为高阻态

转向就是MAC由发送数据变成接收数据


3.MDIO程序

`timescale 1ns / 1ps

module mdio_dri(
	input	wire 			clk 		,
	input	wire 			rst_n 		,
	input	wire			op_exec 	, 	//触发开始信号
	input	wire 			op_rh_wl	,	//低电平写,高电平读
	input	wire 	[4:0] 	op_phy_addr , 	//芯片地址
	input	wire 	[4:0] 	op_reg_addr , 	//寄存器地址
	input 	wire 	[15:0] 	op_wr_data 	, 	//写数据
	output	reg 	 		op_done 	, 	//操作完成
	output	reg 	[15:0] 	op_rd_data 	,	//读出的数据
	output 	reg  			op_rd_ack 	, 	//读应答

	output	reg   			eth_mdc 	,
	inout 	wire  			eth_mdio 	
);



localparam 		SYS_CLK 		= 	'd50_000_000 					;
localparam 		DRI_CLK 		= 	'd12_500_000 		 			;
localparam 		DIV_CNT_MAX 	=	(SYS_CLK/DRI_CLK  >> 1)  -  1 	;

localparam 		IDLE 			= 	6'b000_001; 	//初始状态
localparam 		PRE 			= 	6'b000_010; 	//前导码  32位1
localparam 		START 			= 	6'b000_100; 	//发送帧开始加操作码
localparam 		ADDR 			= 	6'b001_000; 	//发送PHY地址加寄存器地址
localparam 		WR 				= 	6'b010_000; 	//发送TA加写入数据
localparam 		RD 				= 	6'b100_000; 	//发送TA加接收数据

localparam		Pre 			= 	32'b1111_1111_1111_1111 ;	//前导码
localparam		ST 				= 	2'b01 					;	//帧开始

wire 			mdio_in 	 ; 	//mdio数据输入
reg 			st_done 	 ; 	//操作完成

reg 	[5:0] 	state 		 ; 	//状态机
reg 			op_rh_wl_r 	 ;
reg 	[5:0]	op_phy_addr_r;
reg 	[5:0]	op_reg_addr_r;
reg 	[15:0]	op_wr_data_r ;
reg 			mdio_out 	 ; 	//mdio数据输出
reg 	[9:0] 	clk_cnt 	 ; 	//时钟计数器
reg 			mdio_dir 	 ; 	//mdio数据方向指示  0输入 1输出
reg 	[1:0] 	op_code 	 ;

/********************ila模块*****************************/
wire 	[255:0] 	probe0;
assign probe0 = {	eth_mdc,
					mdio_out,
					mdio_in,
					mdio_dir,
					state,
					clk_cnt,
					op_code,
					op_rd_data,
					op_rh_wl,
					op_phy_addr,
					op_wr_data,
					st_done,
					op_reg_addr,
					op_rd_ack
				};
ila_0 ila_0_inst (
	.clk(clk), // input wire clk
	.probe0(probe0) // input wire [255:0] probe0
);
/********************************************************/
//双向IO
assign eth_mdio = mdio_dir ? mdio_out : 1'bz;
assign mdio_in  = eth_mdio;


//eth_mdc 	12.5MHZ    1   3时钟变化
always @(posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		eth_mdc 	<=  	'd1;
	end
	else if (state != IDLE && clk_cnt[0] == 1'b0) begin
		eth_mdc 	<= 	~eth_mdc;
	end
end

//寄存器  当传输开始时将数据锁存起来
always @(posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		op_code 		<= 'd0;
		op_phy_addr_r 	<= 'd0;
		op_reg_addr_r 	<= 'd0;
		op_wr_data_r	<= 'd0;
	end
	else if (op_exec == 1'b1) begin
		op_code 		<= {op_rh_wl,~op_rh_wl};//OP_CODE: 2'b01(写)  2'b10(读) 
		op_phy_addr_r 	<= op_phy_addr 	;
		op_reg_addr_r 	<= op_reg_addr;
		op_wr_data_r	<= op_wr_data ;
	end
end

//状态转移
always @(posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		state <= IDLE ;
	end
	else  begin
		case(state)
			IDLE 	: 	begin
				if (op_exec == 1'b1) begin
					state <= PRE ;
				end
			end
			PRE 	: 	begin
				if (st_done == 1'b1) begin
					state <= START ;
				end
			end
			START 	: 	begin
				if (st_done == 1'b1) begin
					state <= ADDR ;
				end
			end
			ADDR 	: 	begin
				if (st_done == 1'b1 && op_code[1] == 1'b0) begin
					state <= WR ;
				end
				else if (st_done == 1'b1 && op_code[1] == 1'b1) begin
					state <= RD ;
				end
			end
			WR 		: 	begin
				if (st_done == 1'b1) begin
					state <= IDLE ;
				end
			end
			RD 		: 	begin
				if (st_done == 1'b1) begin
					state <= IDLE ;
				end
			end
		endcase
	end
end


//状态输出
always @(posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0) begin
		clk_cnt 	<= 'd0;
		mdio_out 	<= 1'b0;
		mdio_dir 	<= 1'b0;
		op_done 	<= 1'b0;
		st_done 	<= 1'b0;
		op_rd_data 	<= 16'b0;
	end
	else  begin
	clk_cnt  <= clk_cnt + 1'b1;
	op_done  <= 1'b0;
		case(state)
			IDLE 	: 	begin
				clk_cnt 	<= 'd0;
				mdio_dir 	<= 'd0;
				mdio_out 	<= 'd1;
				op_rd_ack 	<= 1'b1;
				op_done 	<= 1'b0;
			end
			PRE 	: 	begin 	//前导码  发送32位1
				mdio_dir <= 1'b1;
				mdio_out <= 1'b1;
				if (clk_cnt == 4 * 32 - 2) begin
					st_done <= 1'b1;
				end
				else if (clk_cnt == 4 * 32 - 1) begin
					st_done <= 1'b0;
					clk_cnt <= 'd0;
				end
			end
			START 	: 	begin 	//帧开始加操作码
				mdio_dir <= 1'b1;
				case(clk_cnt)
					0 	: 	mdio_out 	<= 	1'b0 		;
					4 	: 	mdio_out 	<=  1'b1 		; 	//两位帧开始
					8 	: 	mdio_out 	<= 	op_code[1] 	;
					12 	: 	mdio_out 	<= 	op_code[0] 	;
					14  : 	st_done 	<= 	1'b1 		;
					15 	: 	begin
						st_done 	<= 	1'b0 	;
						clk_cnt 	<= 	'd0 	;
					end
				endcase
			end
			ADDR 	: 	begin
				mdio_dir <= 1'b1;
				case(clk_cnt)
					0 	: 	mdio_out 	<= 	op_phy_addr_r[4] 	;
					4 	: 	mdio_out 	<= 	op_phy_addr_r[3] 	;
					8 	: 	mdio_out 	<= 	op_phy_addr_r[2] 	;
					12 	: 	mdio_out 	<= 	op_phy_addr_r[1] 	;
					16 	: 	mdio_out 	<= 	op_phy_addr_r[0] 	; 	//PHY地址
					20 	: 	mdio_out 	<= 	op_reg_addr_r[4] 	;
					24 	: 	mdio_out 	<= 	op_reg_addr_r[3] 	;
					28 	: 	mdio_out 	<= 	op_reg_addr_r[2] 	;
					32 	: 	mdio_out 	<= 	op_reg_addr_r[1] 	;
					36 	: 	mdio_out 	<= 	op_reg_addr_r[0] 	; 	//寄存器地址
					38 	: 	st_done 	<= 	1'b1 				;
					39  : 	begin
						st_done 	<= 	1'b0;
						clk_cnt 	<= 	 'b0;
					end
				endcase
			end
			WR 		: 	begin
				mdio_dir <= 1'b1;
				case(clk_cnt)
				 	0 	: 	mdio_out 	<= 	1'b1 			 ;
				 	4 	: 	mdio_out 	<= 	1'b0 			 ; 	//写操作 不转向 10
				 	8 	: 	mdio_out 	<= 	op_wr_data_r[15] ;
				 	12 	: 	mdio_out 	<= 	op_wr_data_r[14] ;
				 	16 	: 	mdio_out 	<= 	op_wr_data_r[13] ;
				 	20 	: 	mdio_out 	<= 	op_wr_data_r[12] ;
				 	24 	: 	mdio_out 	<= 	op_wr_data_r[11] ;
				 	28 	: 	mdio_out 	<= 	op_wr_data_r[10] ;
				 	32 	: 	mdio_out 	<= 	op_wr_data_r[9]  ;
				 	36 	: 	mdio_out 	<= 	op_wr_data_r[8]  ;
				 	40 	: 	mdio_out 	<= 	op_wr_data_r[7]  ;
				 	44 	: 	mdio_out 	<= 	op_wr_data_r[6]  ;
				 	48 	: 	mdio_out 	<= 	op_wr_data_r[5]  ;
				 	52 	: 	mdio_out 	<= 	op_wr_data_r[4]  ;
				 	56 	: 	mdio_out 	<= 	op_wr_data_r[3]  ;
				 	60 	: 	mdio_out 	<= 	op_wr_data_r[2]  ;
				 	64 	: 	mdio_out 	<= 	op_wr_data_r[1]  ;
				 	68 	: 	mdio_out 	<= 	op_wr_data_r[0]  ;
				 	70 	: 	st_done 	<= 	1'b1 			;
				 	71 	: 	begin
				 		st_done 	<= 1'b0 ;
				 		clk_cnt 	<= 'd0  ;
				 		mdio_dir 	<= 1'b0 ;
				 		op_done 	<= 1'b1 ;
				 		mdio_out 	<= 1'b1 ;
				 	end
				 endcase
			end
			RD 		: 	begin
				mdio_dir <= 1'b0;
				case(clk_cnt)
					2 	: 	 								; 	//等待转向
					6 	: 	op_rd_ack 		<= 	mdio_in  	; 	//转向完成  应答信号拉低代表应答成功  
					10 	: 	op_rd_data[15] 	<= 	mdio_in 	; 	
					14 	: 	op_rd_data[14] 	<= 	mdio_in 	;
					18 	: 	op_rd_data[13] 	<= 	mdio_in 	;
					22 	: 	op_rd_data[12] 	<= 	mdio_in 	;
					26 	: 	op_rd_data[11] 	<= 	mdio_in 	;
					30 	: 	op_rd_data[10] 	<= 	mdio_in 	;
					34 	: 	op_rd_data[9] 	<= 	mdio_in 	;
					38 	: 	op_rd_data[8] 	<= 	mdio_in 	;
					42 	: 	op_rd_data[7] 	<= 	mdio_in 	;
					46 	: 	op_rd_data[6] 	<= 	mdio_in 	;
					50 	: 	op_rd_data[5] 	<= 	mdio_in 	;
					54 	: 	op_rd_data[4] 	<= 	mdio_in 	;
					58 	: 	op_rd_data[3] 	<= 	mdio_in 	;
					62 	: 	op_rd_data[2] 	<= 	mdio_in 	;
					66 	: 	op_rd_data[1] 	<= 	mdio_in 	;
					70 	: 	op_rd_data[0] 	<= 	mdio_in 	;
					72 	: 	st_done 		<= 	1'b1 		;
					73 	: 	begin
						st_done 	<= 	1'b0 				;
						clk_cnt 	<= 	'd0   				;
						mdio_dir 	<= 	'd0 				; 	//高阻
						mdio_out 	<=  'd1 				;
						op_done 	<= 	1'b1 				;
					end
				endcase 
			end
		endcase
	end
end
endmodule



有关千兆以太网(二)——MDIO接口协议的更多相关文章

  1. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  2. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  3. ruby - HTTP POST 上的 SSL 错误(未知协议(protocol)) - 2

    尝试通过SSL连接到ImgurAPI时出现错误。这是代码和错误:API_URI=URI.parse('https://api.imgur.com')API_PUBLIC_KEY='Client-ID--'ENDPOINTS={:image=>'/3/image',:gallery=>'/3/gallery'}#Public:Uploadanimage##args-Theimagepathfortheimagetoupload#defupload(image_path)http=Net::HTTP.new(API_URI.host)http.use_ssl=truehttp.verify

  4. 玩以太坊链上项目的必备技能(初识智能合约语言-Solidity之旅一) - 2

    前面一篇关于智能合约翻译文讲到了,是一种计算机程序,既然是程序,那就可以使用程序语言去编写智能合约了。而若想玩区块链上的项目,大部分区块链项目都是开源的,能看得懂智能合约代码,或找出其中的漏洞,那么,学习Solidity这门高级的智能合约语言是有必要的,当然,这都得在公链``````以太坊上,毕竟国内的联盟链有些是不兼容Solidity。Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下的账户行为的程序。Solidity是运行在以太坊(Ethereum)虚拟机(EVM)上,其语法受到了c++、python、javascript影响。Solidity是静态类型

  5. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  6. ruby-on-rails - 如何在 RubyOnRails 中使用 'acts as nested set' 创建一个可排序的接口(interface) - 2

    我一直在为使用acts_as_list的模型实现一些不错的交互界面,这些界面可以对我的mRails应用程序中的列表进行排序。我有一个排序函数,在每次拖放之后使用sortable_elementscript.aculo.us函数调用并设置每条记录的位置。这是在拖放完成后处理排序的Controller操作示例:defsortparams[:documents].each_with_indexdo|id,index|Document.update_all(['position=?',index+1],['id=?',id])endend现在我正在尝试对嵌套集模型(acts_as_nested

  7. 网络实验之RIPV2协议(一) - 2

    一、RIPV2协议简介  RIP(RoutingInformationProtocol)路由协议是一种相对古老,在小型以及同介质网络中得到了广泛应用的一种路由协议。RIP采用距离向量算法,是一种距离向量协议。RIP-1是有类别路由协议(ClassfulRoutingProtocol),它只支持以广播方式发布协议报文。RIP-1的协议报文无法携带掩码信息,它只能识别A、B、C类这样的自然网段的路由,因此RIP-1不支持非连续子网(DiscontiguousSubnet)。RIP-2是一种无类别路由协议(ClasslessRoutingProtocol),支持路由标记,在路由策略中可根据路由标记对

  8. 你真正了解什么是接口测试么?接口实战一“篇”入魂 - 2

    最近在工作中,看到一些新手测试同学,对接口测试存在很多疑问,甚至包括一些从事软件测试3,5年的同学,在聊到接口时,也是一知半解;今天借着这个机会,对接口测试做个实战教学,顺便总结一下经验,分享给大家。计划拆分成4个模块跟大家做一个分享,(接口测试、接口基础知识、接口自动化、接口进阶)感兴趣的小伙伴记得关注,希望对你的日常工作和求职面试,带来一些帮助。注:文章较长有5000多字,希望小伙伴们认真看完,当然有些内容对小白同学不是太友好,如果你需要详细了解其中的一些概念或者名词,请在文章之后留言,后续我将针对大家的疑问,整理输出一些大家感兴趣的文章。随着开发模式的迭代更新,前后端分离已不是新的概念,

  9. ruby-on-rails - 在 Ruby on Rails 中为由外部 API 支持的模型使用 ActiveRecord 接口(interface) - 2

    我正在尝试在我的Rails应用程序中使用模型来从外部API检索信息。我想做的是以类似于ActiveRecord模型提供的方式(特别是关联,以及相同风格的可链接查询方法)访问我的数据模型(可能包含来自多个API调用的信息)。我最初的直觉是重新创建我想要的ActiveRecord部分并合并此API。不想“重新发明轮子”并确切地看到添加更多功能需要多少工作让我退后一步并重新评估如何处理这个问题。我找到了在没有表的情况下使用ActiveRecord的方法(请参阅:Railscast#193TablelessModel和博客文章here)并研究了ActiveRecord。因为ActiveMode

  10. [译]在C#中使用IComparable和IComparer接口 - 2

    原文:UsetheIComparableandIComparerinterfacesinVisualCSharp本文介绍了在VisualC#中如何使用IComparer和IComparable接口。概要本文同时讨论了IComparable和IComparer接口,原因有两点。这两个接口经常一起使用。虽然接口类似且名称相似,但它们却有不同的用途。如果你有一个支持IComparer的类型数组(例如字符串或整数),你可以对它进行排序而不需要提供任何对IComparer的显式引用(译注:意思是把一个IComparer的实现类作为参数传递给排序方法)。在这种情况下,数组元素会被转换为IComparer的

随机推荐