最近迷上了FPGA的网络通信和GTP光通信,个人感觉光通信简单一些,那就从难得网络通信开始吧,先搞个最简单的,使用MDIO配置和读取网络PHY的信息。
板子:米联客的MA703FA(A7-35T板子);
参考例程:正点原子达芬奇开发板例程;
IDE:vivado2020.2;
具体的原理啥的建议去看正点原子的文档吧,讲得很好,但原子的例程感觉不贴近实际项目,所以我改了一下,使之适合真是项目。
先来看看这块芯片RTL8211FD的数据手册。

这是官方给的应用架构,很简单,RTL8211FD与MAC通信,通过MDC和MDIO配置。

芯片BD,没啥好说的,典型的rgmii接口,内部模块电路感觉没必要深究,反正也不懂,能用就行了。

硬件复位这里要注意,官方说应至少保持10ms低电平,最好还是按官方说的做,菜就要多听话。

芯片地址这里注意了,RTL8211FD器件地址由5位构成,高两位固定为2’b00,第三位后这三个引脚的上下拉电平决定,所以看看米联客板子的原理图:

由此可得:此RTL8211FD器件地址为:5’b00001;
这块芯片是不需要配置也能使用的,参考官方给的寄存器默认值可知,默认状态下芯片开启自协商,速率1000M,这就行了,已经不需要配置了,但为了学习,我们可以读状态寄存器,从而获得连接状态和通信速率。需要读两个状态寄存器:

第一是这个,需要读他的第2和第5位,


第2位:芯片和MAC连接成功;
第5位:芯片和MAC自协商成功;

还有这个寄存器,主要读芯片的通信速率;

第4到5位是通信速率;
好了,在看代码:
MDIO控制器直接用原子的,写法和他们家的iic控制器类似,够骚,也够繁琐,但能用,菜就别讲究了吧。
//****************************************************************************************//
module mdio_dri #(
parameter PHY_ADDR = 5'b00001,//PHY地址
parameter CLK_DIV = 6'd10 //分频系数
)
(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input i_op_exec , //触发开始信号
input i_op_rh_wl , //低电平写,高电平读
input [4:0] i_op_addr , //寄存器地址
input [15:0] i_op_wr_data, //写入寄存器的数据
output reg o_op_done , //读写完成
output reg [15:0] o_op_rd_data, //读出的数据
output reg o_op_rd_ack , //读应答信号 0:应答 1:未应答
output reg o_dri_clk , //驱动时钟
output reg o_eth_mdc , //PHY管理接口的时钟信号
inout i_eth_mdio //PHY管理接口的双向数据信号
);
//parameter define
localparam st_idle = 6'b00_0001; //空闲状态
localparam st_pre = 6'b00_0010; //发送PRE(前导码)
localparam st_start = 6'b00_0100; //开始状态,发送ST(开始)+OP(操作码)
localparam st_addr = 6'b00_1000; //写地址,发送PHY地址+寄存器地址
localparam st_wr_data = 6'b01_0000; //TA+写数据
localparam st_rd_data = 6'b10_0000; //TA+读数据
//reg define
reg [5:0] cur_state ;
reg [5:0] next_state;
reg [5:0] clk_cnt ; //分频计数
reg [15:0] wr_data_t ; //缓存写寄存器的数据
reg [4:0] addr_t ; //缓存寄存器地址
reg [6:0] cnt ; //计数器
reg st_done ; //状态开始跳转信号
reg [1:0] op_code ; //操作码 2'b01(写) 2'b10(读)
reg mdio_dir ; //MDIO数据(SDA)方向控制
reg mdio_out ; //MDIO输出信号
reg [15:0] rd_data_t ; //缓存读寄存器数据
//wire define
wire mdio_in ; //MDIO数据输入
wire [5:0] clk_divide ; //PHY_CLK的分频系数
assign i_eth_mdio = mdio_dir ? mdio_out : 1'bz; //控制双向io方向
assign mdio_in = i_eth_mdio; //MDIO数据输入
//将PHY_CLK的分频系数除以2,得到dri_clk的分频系数,方便对MDC和MDIO信号操作
assign clk_divide = CLK_DIV >> 1;
//分频得到dri_clk时钟
always @(posedge clk) begin
if(!rst_n) begin
o_dri_clk <= 1'b0;
clk_cnt <= 1'b0;
end
else if(clk_cnt == clk_divide[5:1] - 1'd1) begin
clk_cnt <= 1'b0;
o_dri_clk <= ~o_dri_clk;
end
else clk_cnt <= clk_cnt + 1'b1;
end
//产生PHY_MDC时钟
always @(posedge o_dri_clk) begin
if(!rst_n) o_eth_mdc <= 1'b1;
else if(cnt[0] == 1'b0) o_eth_mdc <= 1'b1;
else o_eth_mdc <= 1'b0;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge o_dri_clk) begin
if(!rst_n) cur_state <= st_idle;
else cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle : begin
if(i_op_exec) next_state = st_pre;
else next_state = st_idle;
end
st_pre : begin //发送前导码,32'hffffffff
if(st_done) next_state = st_start;
else next_state = st_pre;
end
st_start : begin //ST+OP=2'b01+2'bxx
if(st_done) next_state = st_addr;
else next_state = st_start;
end
st_addr : begin //PHYAD+REGAD
if(st_done) begin
if(op_code == 2'b01) next_state = st_wr_data; //MDIO接口写操作
else next_state = st_rd_data; //MDIO接口读操作
end
else next_state = st_addr;
end
st_wr_data : begin
if(st_done) next_state = st_idle;
else next_state = st_wr_data;
end
st_rd_data : begin
if(st_done) next_state = st_idle;
else next_state = st_rd_data;
end
default : next_state = st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge o_dri_clk) begin
if(!rst_n) begin
cnt <= 5'd0;
op_code <= 1'b0;
addr_t <= 1'b0;
wr_data_t <= 1'b0;
rd_data_t <= 1'b0;
o_op_done <= 1'b0;
st_done <= 1'b0;
o_op_rd_data <= 1'b0;
o_op_rd_ack <= 1'b1;
mdio_dir <= 1'b0;
mdio_out <= 1'b1;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle : begin
mdio_out <= 1'b1;
mdio_dir <= 1'b0;
o_op_done <= 1'b0;
cnt <= 7'b0;
if(i_op_exec) begin
op_code <= {i_op_rh_wl,~i_op_rh_wl}; //OP_CODE: 2'b01(写) 2'b10(读)
addr_t <= i_op_addr;
wr_data_t <= i_op_wr_data;
o_op_rd_ack <= 1'b1;
end
end
st_pre : begin //发送前导码:32个1bit
mdio_dir <= 1'b1; //切换MDIO引脚方向:输出
mdio_out <= 1'b1; //MDIO引脚输出高电平
if(cnt == 7'd62) st_done <= 1'b1;
else if(cnt == 7'd63) cnt <= 7'b0;
end
st_start : begin
case(cnt)
7'd1 : mdio_out <= 1'b0; //发送开始信号 2'b01
7'd3 : mdio_out <= 1'b1;
7'd5 : mdio_out <= op_code[1]; //发送操作码
7'd6 : st_done <= 1'b1;
7'd7 : begin
mdio_out <= op_code[0];
cnt <= 7'b0;
end
default : ;
endcase
end
st_addr : begin
case(cnt)
7'd1 : mdio_out <= PHY_ADDR[4]; //发送PHY地址
7'd3 : mdio_out <= PHY_ADDR[3];
7'd5 : mdio_out <= PHY_ADDR[2];
7'd7 : mdio_out <= PHY_ADDR[1];
7'd9 : mdio_out <= PHY_ADDR[0];
7'd11: mdio_out <= addr_t[4]; //发送寄存器地址
7'd13: mdio_out <= addr_t[3];
7'd15: mdio_out <= addr_t[2];
7'd17: mdio_out <= addr_t[1];
7'd18: st_done <= 1'b1;
7'd19: begin
mdio_out <= addr_t[0];
cnt <= 7'd0;
end
default : ;
endcase
end
st_wr_data : begin
case(cnt)
7'd1 : mdio_out <= 1'b1; //发送TA,写操作(2'b10)
7'd3 : mdio_out <= 1'b0;
7'd5 : mdio_out <= wr_data_t[15];//发送写寄存器数据
7'd7 : mdio_out <= wr_data_t[14];
7'd9 : mdio_out <= wr_data_t[13];
7'd11: mdio_out <= wr_data_t[12];
7'd13: mdio_out <= wr_data_t[11];
7'd15: mdio_out <= wr_data_t[10];
7'd17: mdio_out <= wr_data_t[9];
7'd19: mdio_out <= wr_data_t[8];
7'd21: mdio_out <= wr_data_t[7];
7'd23: mdio_out <= wr_data_t[6];
7'd25: mdio_out <= wr_data_t[5];
7'd27: mdio_out <= wr_data_t[4];
7'd29: mdio_out <= wr_data_t[3];
7'd31: mdio_out <= wr_data_t[2];
7'd33: mdio_out <= wr_data_t[1];
7'd35: mdio_out <= wr_data_t[0];
7'd37: begin
mdio_dir <= 1'b0;
mdio_out <= 1'b1;
end
7'd39: st_done <= 1'b1;
7'd40: begin
cnt <= 7'b0;
o_op_done <= 1'b1; //写操作完成,拉高op_done信号
end
default : ;
endcase
end
st_rd_data : begin
case(cnt)
7'd1 : begin
mdio_dir <= 1'b0; //MDIO引脚切换至输入状态
mdio_out <= 1'b1;
end
7'd2 : ; //TA[1]位,该位为高阻状态,不操作
7'd4 : o_op_rd_ack <= mdio_in; //TA[0]位,0(应答) 1(未应答)
7'd6 : rd_data_t[15] <= mdio_in; //接收寄存器数据
7'd8 : rd_data_t[14] <= mdio_in;
7'd10: rd_data_t[13] <= mdio_in;
7'd12: rd_data_t[12] <= mdio_in;
7'd14: rd_data_t[11] <= mdio_in;
7'd16: rd_data_t[10] <= mdio_in;
7'd18: rd_data_t[9] <= mdio_in;
7'd20: rd_data_t[8] <= mdio_in;
7'd22: rd_data_t[7] <= mdio_in;
7'd24: rd_data_t[6] <= mdio_in;
7'd26: rd_data_t[5] <= mdio_in;
7'd28: rd_data_t[4] <= mdio_in;
7'd30: rd_data_t[3] <= mdio_in;
7'd32: rd_data_t[2] <= mdio_in;
7'd34: rd_data_t[1] <= mdio_in;
7'd36: rd_data_t[0] <= mdio_in;
7'd39: st_done <= 1'b1;
7'd40: begin
o_op_done <= 1'b1; //读操作完成,拉高op_done信号
o_op_rd_data <= rd_data_t;
rd_data_t <= 16'd0;
cnt <= 7'd0;
end
default : ;
endcase
end
default : ;
endcase
end
end
endmodule
然后是控制逻辑,原子的例程是通过触摸按键软复位RTL8211FD,然后再读那两个寄存器,感觉没啥用,自己重写了一个,功能是硬件复位完成后直接循环读那两个寄存器,如果自协商完成且连接成功,led亮,否则灭,再用另个led来只是当前的通信速率,直接上代码:
//****************************************************************************************//
module mdio_ctrl(
(* mark_debug ="true" *) input clk ,
(* mark_debug ="true" *) input rst_n ,
(* mark_debug ="true" *) input i_op_done , //读写完成
(* mark_debug ="true" *) input [15:0] i_op_rd_data , //读出的数据
(* mark_debug ="true" *) input i_op_rd_ack , //读应答信号 0:应答 1:未应答
(* mark_debug ="true" *) output reg o_op_exec , //触发开始信号
(* mark_debug ="true" *) output reg o_op_rh_wl , //低电平写,高电平读
(* mark_debug ="true" *) output reg [4:0] o_op_addr , //寄存器地址
(* mark_debug ="true" *) input i_eth_rst_n ,
(* mark_debug ="true" *) output reg o_link_led ,
(* mark_debug ="true" *) output reg [1:0] o_speed_led
);
(* mark_debug ="true" *) reg [2:0] flow_cnt; //流程控制计数器
always @(posedge clk) begin
if(!rst_n) begin
flow_cnt <= 3'd0;
// link_error <= 1'b0;
o_op_exec <= 1'b0;
o_op_rh_wl <= 1'b0;
o_op_addr <= 1'b0;
// op_wr_data <= 1'b0;
o_link_led <= 1'b0;
o_speed_led<= 2'b0;
end
else begin
case(flow_cnt)
3'd0:begin
if(i_eth_rst_n) flow_cnt <= 3'd1; //reste_n is ok
end
3'd1: begin
o_op_exec <= 1'b1; //开始操作MDIO
o_op_rh_wl<= 1'b1; //读操作
o_op_addr <= 5'h01; //读BMSR状态寄存器
flow_cnt<= 3'd2;
end
3'd2: begin
if(i_op_done) begin //读操作完成
if(!i_op_rd_ack) flow_cnt<= 3'd3; //phy应答,做后续判断
else flow_cnt<= 3'd1; //phy未应答,重新发起读BMSR状态寄存器
end
end
3'd3: begin
if(i_op_rd_data[5] == 1'b1 && i_op_rd_data[2] == 1'b1) begin
o_link_led<=1'b1; //自协商完成,link完成
flow_cnt<=3'd4;
end
else o_link_led<=1'b0;
end
3'd4: begin //读PHYSR特定状态寄存器
o_op_addr <= 5'h1A;
flow_cnt<= 3'd5;
end
3'd5: begin
if(i_op_done) begin //读操作完成
if(!i_op_rd_ack) flow_cnt<= 3'd6; //phy应答,做后续判断
else flow_cnt<= 3'd4; //phy未应答,重新发起读PHYSR特定状态寄存器
end
end
3'd6: begin
flow_cnt<=3'd1;
if(i_op_rd_data[5:4] == 2'b10) o_speed_led<=2'b11; //1000Mbps
else if(i_op_rd_data[5:4] == 2'b01) o_speed_led<=2'b01; //100Mbps
else if(i_op_rd_data[5:4] == 2'b00) o_speed_led<=2'b10; //100Mbps
else o_speed_led<= 2'b0;
end
default: flow_cnt <= 3'd0;
endcase
end
end
endmodule
最后是顶层,这里加了硬件复位的10ms,之前说了,菜就别嫌麻烦,跟官方走:
module mdio_rw_test(
input sys_clk ,
output eth_mdc , //PHY管理接口的时钟信号
inout eth_mdio , //PHY管理接口的双向数据信号
output eth_rst_n , //以太网复位信号
output o_link_led ,
output [1:0] o_speed_led //LED连接速率指示
);
//wire define
wire op_exec ; //触发开始信号
wire op_rh_wl ; //低电平写,高电平读
wire [4:0] op_addr ; //寄存器地址
wire [15:0] op_wr_data ; //写入寄存器的数据
wire op_done ; //读写完成
wire [15:0] op_rd_data ; //读出的数据
wire op_rd_ack ; //读应答信号 0:应答 1:未应答
wire dri_clk ; //驱动时钟
wire rsn_n;
wire clk_50m;
clk_wiz_0 u_clk_wiz_0
(
// Clock out ports
.clk_out1(clk_50m), // output clk_out1
// Status and control signals
.locked(rsn_n), // output locked
// Clock in ports
.clk_in1(sys_clk)); // input clk_in1
localparam T_10MS = 10_000_000; //10ms=10_000_000ns
localparam T_P = 20 ; //50M周期=20ns
localparam RST_CNT= T_10MS/T_P+10;
reg [18:0] eth_rsrn_cnt;
reg o_eth_rst_n;
//根据数据手册,硬件复位至少10ms的低电平
always @(posedge clk_50m) begin
if(!rsn_n) eth_rsrn_cnt<='d0;
else if(eth_rsrn_cnt==RST_CNT) eth_rsrn_cnt<=RST_CNT;
else eth_rsrn_cnt<=eth_rsrn_cnt+19'd1;
end
always @(posedge clk_50m) begin
if(!rsn_n) o_eth_rst_n<='d0;
else if(eth_rsrn_cnt==RST_CNT) o_eth_rst_n<=1'd1;
else o_eth_rst_n<=1'd0;
end
//硬件复位
assign eth_rst_n = o_eth_rst_n;
//MDIO接口驱动
mdio_dri #(
.PHY_ADDR (5'h01), //PHY地址
.CLK_DIV (6'd10) //分频系数
)
u_mdio_dri(
.clk (clk_50m ),
.rst_n (rsn_n ),
.i_op_exec (op_exec ),
.i_op_rh_wl (op_rh_wl ),
.i_op_addr (op_addr ),
.i_op_wr_data(op_wr_data),
.o_op_done (op_done ),
.o_op_rd_data(op_rd_data),
.o_op_rd_ack (op_rd_ack ),
.o_dri_clk (dri_clk ),
.o_eth_mdc (eth_mdc ),
.i_eth_mdio (eth_mdio )
);
mdio_ctrl u_mdio_ctrl(
.clk (dri_clk ),
.rst_n (rsn_n ),
.i_op_done (op_done ), //读写完成
.i_op_rd_data (op_rd_data ), //读出的数据
.i_op_rd_ack (op_rd_ack ), //读应答信号 0:应答 1:未应答
.o_op_exec (op_exec ), //触发开始信号
.o_op_rh_wl (op_rh_wl ), //低电平写,高电平读
.o_op_addr (op_addr ), //寄存器地址
.i_eth_rst_n (o_eth_rst_n),
.o_link_led (o_link_led ),
.o_speed_led (o_speed_led)
);
endmodule
结果:灯亮,成功

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po