全部设计参考达芬奇pro教程五十三和五十四章
GTP IP核
第一步:找到GTP的IP核
第二步:配置IP核第一页
我用的是A7的板子,选GTP,GTP被用于A7系列(GTZ被用于少数V7系列,V7系列大多用GTH,K7常用GTX),它所带载的最大带宽为6.6Gb/s
Include Shared Logic in example design:
包含收发器的QPLL、时钟和复位逻辑等在示例设计中,称为从核
Include Shared Logic in example design:
包含收发器的QPLL、时钟和复位逻辑在内核中,称为主核
从核和主核的区别:可以在示例设计中修改共享逻辑,在实际的设计中,可以使用主核也可以使用从核,但若设计中使用了一个主核后,则其内部便使用了该Quad上的QPLL资源,在使用该Quad上的其他GTP接口时,不能再使用主核,也无需再给从核添加共享逻辑(教程写的有点绕哈哈,我的理解:主核若被使用,主核内部就使用了主核Quad上的QPLL资源,主核的GTP接口使用顺位就靠后)
第三步:配置IP核第二页
我选择Quad0上的一个GTP的tx和另一个GTP上的rx来实现数据传输,在一个Quad下成为数据传输的闭环
初始默认只有一个GTP channel被选择,实现两个channel的闭环传输,需要再自行选择一个GTP的channel,图中画圈处即再选一个GTP channel的操作步骤
基于我所选择的闭环用到了Quad中的两个GTP channel,所以Protocol处选择 aurora 8b10b multi lane 4byte (Aurora 8b10b single lane 4byte可用于一个GTP channel,它的rx接收数据,tx发送数据,数据收发和接收相互独立)
可以选择不同的线速率和编码格式,此处均选择一个常见的较低速率5Gbps用于功能验证,参考时钟为125M(收发器输入时钟,频率必须与开发板上为GTP提供时钟的晶振一致)
GTP的两个参考输入时钟都能连到PLL0和PLL1, 所以在ip核中可以任意选择用PLL0还是PLL1,本次实验收发速率一致,所以RX和TX可以共用一个锁相环,否则必须用两个锁相环,本次开发板的外部时钟是接在GTPREFCLK1,故在ip核中将参考时钟设为GTPREFCLK1,本次开发板用了两个SFP接口,这两个接口分别连接到GTP_X0Y4和GTP_X0Y5
第四步:配置IP核第三页
选的4byte,所以外部数据位宽设置为32bit,编码方式选8b/10b
DRP/System Clock Frequency是动态配置或系统工作时钟,通过DRP可以让设计者根据所选线速率和定义的协议实时调整收发器参数,该时钟可以通过锁相环来配置,本次实验将该时钟设为50M
enable TX Buffer和enable RX Buffer是因为数据不管从TSUSRCLK到XCLK还是反过来,都经过了时钟域的转换,所以TX和RX都要一个缓存区缓存下
第五步:配置IP核第四页
8b/10b编码表中有12个控制字符,以K开头,用于一些控制功能,K码中的comma码用于接收端时钟校准和数据对齐,K28.5(对应用户数据为16‘hbc)最为常见,数据在链路中以串行方式进行传输,所以接收端必须对其进行串并转换
第五页和第六页保持默认设置
GTP IP核的Example design更改
注释掉这部分,因为例程中DRP的输入时钟为差分时钟,A7开发板只带载了一个50M时钟的单端晶振,所以要将DRP的输入时钟改为单端时钟输入,我直接将drp_clk输入,注释掉IBUFDS和BUFG
PL部分
moni数据
moni数据模块用的是之前ddr的模拟数据,将640*480的数据传输到GTP的rx端,然后通过GTP的tx端发送出去
只是改动了时钟,改成了clk_200,200MHz
module moni_data(
input clk_200, //例程里clkout的一个,不用白不用,用在此处
output vs,
output hs,
output de_en ,
output [15:0] data_in
);
reg [11:0] x_cnt=12'd1;//每行传输
reg [11:0] y_cnt=12'd1;//行数
reg [1:0]data_en =1'b0;
reg [15:0] de_cnt =12'd0;
always @(posedge clk_200)
begin
if(x_cnt == 12'd800)
x_cnt <= 12'd1;
else
x_cnt <= x_cnt + 12'd1;
end
always @(posedge clk_200)
begin
if(x_cnt==12'd800)//每行在800时,行数加1
if(y_cnt==12'd525)
y_cnt <= 12'd1;
else
y_cnt <= y_cnt + 12'd1;
else;
end
wire hs_en = ((x_cnt>(12'd160)) && (x_cnt<=(12'd800)))?1:0; //注意一句话不能同时<=和>=
wire vs_en = ((y_cnt>(12'd45)) && (y_cnt<=(12'd525)))?1:0;
//一幅图读完 就重新计数x_cnt y_cnt
always @ (posedge clk_200)
begin
if(hs_en)
if(vs_en)
// if ((x_cnt>( 12'd160)) && (x_cnt<=(12'd800)))
// if((y_cnt>( 12'd45)) && (y_cnt<=(12'd525)))
begin
data_en<= 1'b1;
end
else
begin
data_en <= 1'b0;
end
else
data_en <= 1'b0;
end
always @ (posedge clk_200)
begin
if(de_en==1'b1)
if(de_cnt==12'd639)
de_cnt<=12'd0;
else
de_cnt<=de_cnt+12'd1;
else
de_cnt<=12'd0;
end
assign vs = vs_en;
assign hs = hs_en;
assign de_en=data_en;
assign data_in=(de_en==1)?de_cnt:16'd0;
//自己加的ila测试用
ila_5 ila_5
(
.clk (clk_200) ,
.probe0 (de_en) ,
.probe1 (vs) ,
.probe2 (hs) ,
.probe3 (x_cnt) ,
.probe4 (y_cnt) ,
.probe5 (de_cnt),
.probe6 (data_in)
);
endmodule
clk模块
根据达芬奇pro教程的sfp_hdmi生成的模块
输入50MHz
输出如下:
光口模块
光口模块将接收到的16bit数据转换为32bit数据,然后按照8b10b的通信编码格式将数据发送出去,然后将光口接收到的数据按照8b10b的通信解码格式将数据解析出来,最后将32bit的数据转化为16bit
GTP顶层模块(gt_exdes)
一方面负责与用户(FPGA)进行数据交互,一方面还产生控制光口传输的各种时序,并实现对光口的通信操作,用户可以将IP看作是FPGA与光口交流信息的桥梁,这个桥梁Xilinx已经写好,封装成了一个IP供我们使用,我们只需要根据实际需求对IP进行配置即可
光口编码模块(sfp_encoder)
K码(32’hbc),前24位由用户自定义(随意定义),最后的bc因为采用的K28.5,K28.5的格式就是’hbc(低8位是bc)
fifo阈值设为500,该设置的数小于一行有效数即可,例如640*480,640为一行像素数,设的阈值小于640即可,这样是为了最后一行有数可以读
module sfp_encoder#(
//k28.5是32'bc,最低八位是bc,其余随便设,当K是0001时代表数据有K
//码,k码为0000时代表发送的是原始数据
parameter VS_POSE_DATA1 = 32'h55a101bc,
parameter VS_POSE_DATA2 = 32'h55a102bc,
parameter DATA_START1 = 32'h55a105bc,
parameter DATA_START2 = 32'h55a106bc,
parameter DATA_END1 = 32'h55a107bc,
parameter DATA_END2 = 32'h55a108bc,
parameter UNUSE_DATA = 32'h55a109bc
)(
input clk_in,
input rst_n,
input vs_in, //输入场信号
input data_valid_in, //输入数据有效信号
input [15:0] data_in, //输入数据
input tx_clk, //光口tx端时钟
input tx_rst_n, //光口tx端复位
output [3:0] gt_txcharisk, //光口tx端K码发送信号
output [31:0] gt_txdata //光口tx端发送数据
);
//parameter define
parameter tx_unuse_data = 8'b0000_0001;
parameter tx_vs_pose1 = 8'b0000_0010;
parameter tx_vs_pose2 = 8'b0000_0100;
parameter tx_data_start1 = 8'b0000_1000;
parameter tx_data_start2 = 8'b0001_0000;
parameter tx_send_data = 8'b0010_0000;
parameter tx_data_end1 = 8'b0100_0000;
parameter tx_data_end2 = 8'b1000_0000;
reg vs_in_d0;
reg vs_in_d1;
reg data_valid_in_d0;
reg [15:0] data_in_d0;
reg vs_pose;
reg [3:0] gt_txcharisk;
reg [31:0] gt_txdata;
reg [7:0] state;
reg [7:0] cnt_data;
reg fifo_rd_en;
reg [7:0] cnt_fifo_rst;
reg fifo_rst;
reg data_start;
reg data_start_d0;
reg [9:0] data_cnt;
//wire define
wire [31:0] fifo_dout;
wire fifo_empty;
wire fifo_almost_empty;
wire [9:0] rd_data_count;
wire [9:0] fifo_threshold_value;
//比如视频帧率为640*480(有效),那么fifo一行最好设置为小于640的数
//这样便于读最后一行
assign fifo_threshold_value = 10'd500; //这个信号是给amlost_full1用的
//对输入的数据进行寄存
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
data_valid_in_d0 <= 1'b0;
data_in_d0 <= 16'd0;
end
else begin
data_valid_in_d0 <= data_valid_in;
data_in_d0 <= data_in;
end
end
//对输入的信号进行跨时钟域处理
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
vs_in_d0 <= 1'b0;
vs_in_d1 <= 1'b0;
end
else begin
vs_in_d0 <= vs_in;
vs_in_d1 <= vs_in_d0;
end
end
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
data_start_d0 <= 1'b0;
end
else begin
data_start_d0 <= data_start;
end
end
//产生场信号上升沿
//输入的场信号的信息传送给vs_pose
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)
vs_pose <= 1'b0;
else if(vs_in_d0 && ~vs_in_d1)
vs_pose <= 1'b1;
else
vs_pose <= 1'b0;
end
//数据发送开始信号
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)
data_start <= 1'b0;
else if(fifo_empty || vs_pose)//fifo空(无数据可读)或者vs高
data_start <= 1'b0;
else if(rd_data_count >= fifo_threshold_value)//读完一行数据后
//数据开始发送
data_start <= 1'b1;
else
data_start <= data_start;
end
//产生tx端K码发送信号和发送数据
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
gt_txcharisk <= 4'd0;
gt_txdata <= 32'd0;
state <= tx_unuse_data;
fifo_rd_en <= 1'b0;
cnt_data <= 8'd0;
data_cnt<=10'd0;
end
else begin
if(vs_pose)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252; //只是随便设的一个标志,标志vs上升沿到来
state <= tx_vs_pose1;
cnt_data <= 0;
end
else if(data_start && ~data_start_d0)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_data_start1;
cnt_data <= 0;
data_cnt<=10'd0;
end
else begin
case(state)
tx_unuse_data: begin
cnt_data <= cnt_data + 1;
fifo_rd_en <= 1'b0;
if(cnt_data == 255)begin
gt_txcharisk <= 4'd0001;
gt_txdata <= UNUSE_DATA;
state <= tx_unuse_data;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_unuse_data;
end
end
tx_vs_pose1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= VS_POSE_DATA1;
state <= tx_vs_pose2;
fifo_rd_en <= 1'b0;
end
tx_vs_pose2: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= VS_POSE_DATA2;
state <= tx_unuse_data;
end
tx_data_start1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_START1;
state <= tx_data_start2;
fifo_rd_en <= 1'b1;
end
tx_data_start2: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_START2;
state <= tx_send_data;
fifo_rd_en <= 1'b1;
end
//这个状态发送视频或者图像数据(来自fifo的数据)
tx_send_data: begin
if(!fifo_almost_empty)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= fifo_dout;
state <= tx_send_data;
fifo_rd_en <= 1'b1;
end
else if(fifo_almost_empty && !fifo_empty)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= fifo_dout;
state <= tx_send_data;
fifo_rd_en <= 1'b0;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= fifo_dout;
state <= tx_data_end1;
fifo_rd_en <= 1'b0;
end
end
tx_data_end1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_END1;
state <= tx_data_end2;
fifo_rd_en <= 1'b0;
end
tx_data_end2: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_END2;
state <= tx_unuse_data;
fifo_rd_en <= 1'b0;
end
default : begin
cnt_data <= cnt_data + 1;
if(cnt_data == 255)begin//255就是UNUSE_DATA持续255个周期
gt_txcharisk <= 4'd0001;
gt_txdata <= UNUSE_DATA;
state <= tx_unuse_data;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_unuse_data;
end
end
endcase
end
end
end
//产生fifo复位信号
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
cnt_fifo_rst <= 8'b0;
fifo_rst <= 1'b1; //fifo_rst为高意味着fifo复位有效
end
else if(vs_pose)begin
cnt_fifo_rst <= 8'd0;
fifo_rst <= 1;
end
else if(cnt_fifo_rst >= 100) begin //100个tx_clk时间单位,为保证fifo完全复位成功
cnt_fifo_rst <= cnt_fifo_rst;
fifo_rst <= 0;
end
else begin
cnt_fifo_rst <= cnt_fifo_rst +1;
fifo_rst <= fifo_rst;
end
end
sfp_tx_2048x16 u_sfp_2048x16(
.rst (fifo_rst),
.wr_clk (clk_in),
.rd_clk (tx_clk),
.din (data_in_d0),
.wr_en (data_valid_in_d0),
.rd_en (fifo_rd_en),
.dout (fifo_dout),
.full (),
.rd_data_count (rd_data_count),
.empty (fifo_empty),
.almost_empty (fifo_almost_empty)
);
ila_0 ila_0(
.clk(clk_in),
.probe0(vs_in),
.probe1(data_valid_in),
.probe2(data_in)
);
ila_1 ila_1(
.clk(tx_clk),
.probe0(gt_txcharisk),
.probe1(gt_txdata)
);
endmodule
更改之后的encoder模块
module sfp_encoder#(
//k28.5是32'bc,最低八位是bc,其余随便设,当K是0001时代表数据有K
//码,k码为0000时代表发送的是原始数据
parameter VS_POSE_DATA1 = 32'h55a101bc,
parameter VS_POSE_DATA2 = 32'h55a102bc,
parameter DATA_START1 = 32'h55a105bc,
parameter DATA_START2 = 32'h55a106bc,
parameter DATA_END1 = 32'h55a107bc,
parameter DATA_END2 = 32'h55a108bc,
parameter UNUSE_DATA = 32'h55a109bc
)(
input clk_in,
input rst_n,
input vs_in, //输入场信号
input data_valid_in, //输入数据有效信号
input [15:0] data_in, //输入数据
input tx_clk, //光口tx端时钟
input tx_rst_n, //光口tx端复位
output [3:0] gt_txcharisk, //光口tx端K码发送信号
output [31:0] gt_txdata //光口tx端发送数据
);
//parameter define
parameter tx_unuse_data = 8'b0000_0001;
parameter tx_vs_pose1 = 8'b0000_0010;
parameter tx_vs_pose2 = 8'b0000_0100;
parameter tx_data_start1 = 8'b0000_1000;
parameter tx_data_start2 = 8'b0001_0000;
parameter tx_send_data = 8'b0010_0000;
parameter tx_data_end1 = 8'b0100_0000;
parameter tx_data_end2 = 8'b1000_0000;
reg vs_in_d0;
reg vs_in_d1;
reg data_valid_in_d0;
reg [15:0] data_in_d0;
reg vs_pose;
reg [3:0] gt_txcharisk;
reg [31:0] gt_txdata;
reg [7:0] state;
reg [7:0] cnt_data;
reg fifo_rd_en;
reg [7:0] cnt_fifo_rst;
reg fifo_rst;
reg data_start;
reg data_start_d0;
///
reg [9:0] fifo_rd_cnt;
//wire define
wire [31:0] fifo_dout;
wire fifo_empty;
wire fifo_almost_empty;
wire [9:0] rd_data_count; //fifo读计数
wire [9:0] fifo_threshold_value;
//比如视频帧率为640*480(有效),那么fifo一行最好设置为小于640的数
//这样便于读最后一行
//640*16=320*32
//异步fifo,一行写进去640个16bit数,读出是320个32bit数,此处读fifo阈值要略低于320,保证
//最后一行数据能达到阈值,触发读条件
assign fifo_threshold_value = 10'd300;
//对输入的数据进行寄存
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
data_valid_in_d0 <= 1'b0;
data_in_d0 <= 16'd0;
end
else begin
data_valid_in_d0 <= data_valid_in;
data_in_d0 <= data_in;
end
end
//对输入的信号进行跨时钟域处理
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
vs_in_d0 <= 1'b0;
vs_in_d1 <= 1'b0;
end
else begin
vs_in_d0 <= vs_in;
vs_in_d1 <= vs_in_d0;
end
end
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
data_start_d0 <= 1'b0;
end
else begin
data_start_d0 <= data_start;
end
end
//产生场信号上升沿
//输入的场信号的信息传送给vs_pose
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)
vs_pose <= 1'b0;
else if(vs_in_d0 && ~vs_in_d1)
vs_pose <= 1'b1;
else
vs_pose <= 1'b0;
end
//数据发送开始信号
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)
data_start <= 1'b0;
//将条件fifo_empty换成了fifo_rd_cnt == 319,更改的原因是我改了状态机发送数据状态的条
//件,换之前数据一帧只有一行的gt_txdata,
//换之后一帧正常出480行数据
else if((fifo_rd_cnt == 319) || vs_pose)//fifo空(无数据可读)或者vs上升沿
// else if(fifo_empty || vs_pose)
data_start <= 1'b0;
else if(rd_data_count >= fifo_threshold_value)//读完一行数据后
//数据开始发送
data_start <= 1'b1;
else
data_start <= data_start;
end
//产生tx端K码发送信号和发送数据
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
gt_txcharisk <= 4'd0;
gt_txdata <= 32'd0;
state <= tx_unuse_data;
fifo_rd_en <= 1'b0;
cnt_data <= 8'd0;
fifo_rd_cnt<=10'd0;
end
else begin
if(vs_pose)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252; //只是随便设的一个标志,标志vs上升沿到来
state <= tx_vs_pose1;
cnt_data <= 0;
end
else if(data_start && ~data_start_d0)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_data_start1;
cnt_data <= 0;
fifo_rd_cnt<=10'd0;
end
else begin
case(state)
tx_unuse_data: begin
cnt_data <= cnt_data + 1;
fifo_rd_en <= 1'b0;
if(cnt_data == 255)begin //255只是151a252持续的一个时
//间,无具体意义
gt_txcharisk <= 4'd0001;
gt_txdata <= UNUSE_DATA;
state <= tx_unuse_data;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_unuse_data;
end
end
tx_vs_pose1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= VS_POSE_DATA1;
state <= tx_vs_pose2;
fifo_rd_en <= 1'b0;
end
tx_vs_pose2: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= VS_POSE_DATA2;
state <= tx_unuse_data;
end
// tx_data_start1: begin
// gt_txcharisk <= 4'd0001;
// gt_txdata <= DATA_START1;
// state <= tx_data_start2;
// fifo_rd_en <= 1'b1;
// end
// tx_data_start2: begin
// gt_txcharisk <= 4'd0001;
// gt_txdata <= DATA_START2;
// state <= tx_send_data;
// fifo_rd_en <= 1'b1;
// end
//删去了start2,原因是由仿真图可知,start1时,fifo读使能就拉高,下一个读使能
//时钟(tx_clk)就要开始读数据,如果start1后面再有个strat2,那么开始读的数据
//就会错乱,以至于一帧里面每一行后面的数据都会错乱(这个问题是师兄帮忙找的)
tx_data_start1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_START1;
state <= tx_send_data;
fifo_rd_en <= 1'b1;
end
tx_send_data: begin
//读到320个数,就结束一行读数
if(fifo_rd_cnt == 10'd319)begin
gt_txcharisk <= 4'd0000;
gt_txdata <= fifo_dout;
state <= tx_data_end1;
fifo_rd_en <= 1'b0;
fifo_rd_cnt <= 0;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= fifo_dout;
state <= tx_send_data;
fifo_rd_cnt <= fifo_rd_cnt + 1'b1;
fifo_rd_en <= 1'b1;
end
end
tx_data_end1: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_END1;
state <= tx_data_end2;
fifo_rd_en <= 1'b0;
end
tx_data_end2: begin
gt_txcharisk <= 4'd0001;
gt_txdata <= DATA_END2;
state <= tx_unuse_data;
fifo_rd_en <= 1'b0;
end
default:begin
cnt_data <= cnt_data + 1;
if(cnt_data == 255)begin
gt_txcharisk <= 4'd0001;
gt_txdata <= UNUSE_DATA;
state <= tx_unuse_data;
end
else begin
gt_txcharisk <= 4'd0000;
gt_txdata <= 32'ha151a252;
state <= tx_unuse_data;
end
end
endcase
end
end
end
//产生fifo复位信号
always@(posedge tx_clk or negedge tx_rst_n)begin
if(!tx_rst_n)begin
cnt_fifo_rst <= 8'b0;
fifo_rst <= 1'b1;
end
else if(vs_pose)begin
cnt_fifo_rst <= 8'd0;
fifo_rst <= 1; //vs下降沿时fifo清空开始准备读数据
end
else if(cnt_fifo_rst >= 15) begin //之前fifo复位是100个时钟,太久了,导致数据从一开始
//就有一部分没有读出来,异步fifo复位大概十几个时钟就
//够了,我试了20,30,40,50,112,15,15是比较合
//的
cnt_fifo_rst <= cnt_fifo_rst;
fifo_rst <= 0;
end
else begin
cnt_fifo_rst <= cnt_fifo_rst +1;
fifo_rst <= fifo_rst;
end
end
sfp_tx_2048x16 u_sfp_2048x16(
.rst (fifo_rst),
.wr_clk (clk_in),
.rd_clk (tx_clk),
.din (data_in_d0),
.wr_en (data_valid_in_d0),
.rd_en (fifo_rd_en),
.dout (fifo_dout),
.full (),
.rd_data_count (rd_data_count),
.empty (fifo_empty),
.almost_empty (fifo_almost_empty)
);
ila_0 ila_0(
.clk(clk_in),
.probe0(vs_in),
.probe1(data_valid_in),
.probe2(data_in)
);
// ila_1 ila_1(
// .clk(tx_clk),
// .probe0(gt_txcharisk),
// .probe1(gt_txdata)
// );
endmodule
encoder用到fifo是异步fifo
standard fifo读数据会延迟两拍
first word fall through会把数据先挂在传输口上,不会有延迟
彤告诉我说一般选第二种好些
光口字对齐模块(sfp_data_align)
将接收回来不对齐的数据转化为对齐的数据,然后再输出到光口解码模块
module sfp_data_align(
input clk_in, //rx端时钟
input rst_n,
input [31:0] rx_data_in, //rx端未校正发送数据
input [3:0] rx_charisk_in,//rx端K码未校正接收信号
output [31:0] rx_data_out,//rx端校正后发送数据
output [3:0] rx_charisk_out //rx端K码校正后接收信号
);
(* keep="true" *)reg [31:0] rx_data_out;
(* keep="true" *)reg [3:0] rx_charisk_out;
(* keep="true" *)reg [31:0] rx_data_in_d0;
(* keep="true" *)reg [3:0] rx_charisk_in_d0;
(* keep="true" *)reg [31:0] rx_data_in_d1;
(* keep="true" *)reg [3:0] rx_charisk_in_d1;
(* keep="true" *)reg [3:0] byte_ctrl;
//main code//
//对输入数据进行打拍
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rx_data_in_d0 <= 1'b0;
rx_charisk_in_d0 <= 1'b0;
rx_data_in_d1 <= 1'b0;
rx_charisk_in_d1 <= 1'b0;
end
else begin
rx_data_in_d0 <= rx_data_in;
rx_data_in_d1 <= rx_data_in_d0;
rx_charisk_in_d0 <= rx_charisk_in;
rx_charisk_in_d1 <= rx_charisk_in_d0;
end
end
//对rx端K码未校正接收信号进行寄存
//把rx_charisk_in寄存给byte_ctrl
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
byte_ctrl <= 4'd0;
end
else begin
if(rx_charisk_in_d0 > 0)
byte_ctrl <= rx_charisk_in_d0;
else
byte_ctrl <= byte_ctrl;
end
end
//对rx端的信号进行对齐
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
rx_data_out <= 32'd0;
rx_charisk_out <= 4'd0;
end
else begin
//实际过程中,发送的32位数据可能会出现16位数据的移位,若0001变为
//0100,说明存在数据移位,需要重新组合
if(byte_ctrl == 4'b0100)begin
rx_data_out <= {rx_data_in_d0[15:0],rx_data_in_d1[31:16]};
rx_charisk_out <= {rx_charisk_in_d0[1:0],rx_charisk_in_d1[3:2]};
end
else begin
rx_data_out <= rx_data_in_d0;
rx_charisk_out <= rx_charisk_in_d0;
end
end
end
ila_4 ila_4(
.clk(clk_in),
.probe0(rx_charisk_in),
.probe1(rx_data_out),
.probe2(rx_charisk_out)
);
endmodule
光口解码模块(sfp_decode)
将对齐后的数据按照8b10b的解码格式转化为32bit的像素数据,再通过fifo将32bit的数据转化为16bit
module sfp_decode#(
parameter VS_POSE_DATA1 = 32'h55a101bc,
parameter VS_POSE_DATA2 = 32'h55a102bc,
parameter DATA_START1 = 32'h55a105bc,
parameter DATA_START2 = 32'h55a106bc,
parameter DATA_END1 = 32'h55a107bc,
parameter DATA_END2 = 32'h55a108bc,
parameter UNUSE_DATA = 32'h55a109bc
)(
input clk_in, //200M
input rst_n,
output vs_out, //场输出信号
output data_valid_out, //数据输出有效信号
output [15:0] data_out, //数据输出信号
input rx_clk,
input rx_rst_n,
input [3:0] rx_charisk_align, //rx端K码校正后接收信号
input [31:0] rx_data_align //rx端校正后发送数据
);
reg sfp_line_end_t;
reg sfp_line_end_t1;
//reg fifo_rd_en;
reg data_valid_out;
reg [7:0] data_end_en_d;
reg sfp_line_end;
reg [7:0] cnt_vs;
reg vs_out;
reg data_start_en;
reg data_end_en;
reg fifo_wren;
reg [31:0] fifo_datain;
reg [31:0] fifo_rd_en;
reg [3:0] data_valid_out;
reg [31:0] rx_data_align_d0;
reg [3:0] rx_charisk_align_d0;
reg [31:0] rx_data_align_d1;
reg [3:0] rx_charisk_align_d1;
reg [3:0] data_start_en_d;
wire [15:0] data_out;
wire fifo_almost_empty;
main code/
//对输入的数据进行打拍
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
rx_charisk_align_d0 <= 1'b0;
rx_data_align_d0 <= 16'd0;
rx_charisk_align_d1 <= 1'b0;
rx_data_align_d1 <= 16'd0;
end
else begin
rx_charisk_align_d0 <= rx_charisk_align;
rx_charisk_align_d1 <= rx_charisk_align_d0;
rx_data_align_d0 <= rx_data_align;
rx_data_align_d1 <= rx_data_align_d0;
end
end
//对数据接收开始和结束信号进行移位
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
data_end_en_d <= 8'd0;
data_start_en_d <= 4'd0;
end
else begin
//这里是在打拍子,第一个信号打了7拍,第二个信号打了3拍,这俩信号实际有效位数就是一位
//仔细观察,这样的信号被这样处理都是在打拍
data_end_en_d <= {data_end_en_d[6:0],data_end_en};
data_start_en_d <= {data_start_en_d[2:0],data_start_en};
end
end
//产生一行数据接收结束信号
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)
sfp_line_end <= 1'd0;
else if(data_end_en_d > 0)
sfp_line_end <= 1'd1;
else
sfp_line_end <= 1'd0;
end
//产生场输出信号
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
vs_out <= 1'b0;
cnt_vs <= 8'h0;
end
//当rx端K码校正后接收信号为1&&连续两次检测到场上升沿的特殊字符
else if((rx_charisk_align == 4'd1)&&(rx_data_align == VS_POSE_DATA2)
&&((rx_data_align_d0 == VS_POSE_DATA1)))begin
vs_out <= 1'b1; //拉高vs信号
cnt_vs <= 8'h0;
end
else if(cnt_vs >= 100)begin //场信号拉高持续一段时间
vs_out <= 1'b0;
cnt_vs <= cnt_vs +1;
end
else begin
vs_out <= vs_out;
cnt_vs <= cnt_vs + 1;
end
end
//产生数据接收开始信号
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
data_start_en <= 1'b0;
end
//当rx段K码校正后为1&&连续两次检测到数据发送开始的特殊字符
else if((rx_charisk_align == 4'd1)&&(rx_data_align == DATA_START2)
&&((rx_data_align_d0 == DATA_START1)))begin
data_start_en <= 1'b1; //将数据接收信号拉高
end
else begin
data_start_en <= 1'b0; //下一个时钟周期(包括其他情况)信号被拉低
end
end
//产生数据接收结束信号
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
data_end_en <= 1'b0;
end
else if((rx_charisk_align == 4'd1)&&(rx_data_align == DATA_END2)
&&((rx_data_align_d0 == DATA_END1)))begin
data_end_en <= 1'b1;
end
else begin
data_end_en <= 1'b0;
end
end
//产生fifo写使能
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
fifo_wren <= 1'b0;
end
else if(data_start_en_d[1])begin //这是打了一拍
fifo_wren <= 1'b1;
end
//数据接收结束信号拉高后,写使能拉低
else if(data_end_en)begin
fifo_wren <= 1'b0;
end
else begin
fifo_wren <= fifo_wren;
end
end
//产生fifo写数据
always@(posedge rx_clk or negedge rx_rst_n)begin
if(!rx_rst_n)begin
fifo_datain <= 32'b0;
end
else begin
fifo_datain <= rx_data_align_d1; //rx端校正后发送的数据
end
end
//读数据/
//对一行数据接收结束信号进行时钟域切换
//sfp_line_end,该信号是在125MHz时钟下产生的,所以要打拍
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
sfp_line_end_t <= 1'b0;
sfp_line_end_t1 <= 1'b0;
end
else begin
sfp_line_end_t <= sfp_line_end;
sfp_line_end_t1 <= sfp_line_end_t;
end
end
//产生fifo读使能
//光口的rx端将一行数据接收完后立即拉高读使能,当fifo将要
//读空时立即拉低读使能
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)begin
fifo_rd_en <= 1'b0;
end
else begin
if(sfp_line_end_t1)begin
fifo_rd_en <= 1'b1;
end
else if(fifo_almost_empty)begin
fifo_rd_en <= 1'b0;
end
else begin
fifo_rd_en <= fifo_rd_en;
end
end
end
//产生数据输出有效信号
always@(posedge clk_in or negedge rst_n)begin
if(!rst_n)
data_valid_out <= 1'b0;
else
data_valid_out <= fifo_rd_en;
end
sfp_rx_32x1024 u_sfp_rx_32x1024(
.rst(vs_out), //一个新的vs来的时候,hs的有效数据并不是同步的,有个后沿还是什么东西
//所以刚好在一个新的vs开始的时候对fifo进行复位,以便它读新一行的数据
.wr_clk(rx_clk),
.rd_clk(clk_in),
.din(fifo_datain),
.dout(data_out),
.wr_en(fifo_wren),
.rd_en(fifo_rd_en),
.full(),
.empty(),
.almost_empty(fifo_almost_empty)
);
ila_2 ila_2(
.clk(clk_in),
.probe0(vs_out),
.probe1(data_valid_out),
.probe2(data_out)
);
ila_3 ial_3(
.clk(rx_clk),
.probe0(rx_charisk_align),
.probe1(rx_data_align)
);
endmodule