上节内容讲解在接收端串并转换后需要捕获同步字,这在通信中是个广泛和通用的概念。例如UART需要起始位和停止位在异步串行通信中就起到了同步字的作用,以太网的 “前导字+帧起始”(Preamble +SFD)同样起到同步字的作用。其它还有在无线领域经常使用的调制方式如OFDM,就是利用自相关的特性进行同步字或同步信号的搜索。同步字可以在时域实现也可以在频域实现,根据不同的应用选择不同的方式。本章内容借鉴以太网MAC层对同步字的处理,在串行通信中插入Preamble+SFD的结构,从而初步实现二层结构的通信方式,通信结构的初步框架如图1所示:

图1
1. 各分层细节
由于本章内容采用循序渐进的方式逐步介绍通信中分层的概念,因此对于数据链路层的介绍也是由浅入深的方式逐步深入的。初步设计如下:

图2
一般在通信中把物理层划分为3个子层,每层各完成不同的任务,如以太网通信中把物理层分为PCS(Physical Coding Sublayer),PMA( Physical Media Attachment),PMD (Physical Media Dependent)3个子层,关于这方面的内容参照网络通信相关的章节。在本章的异步串行通信中也分为PCS, PMA, PMD 3个子层。 其中PMA对应是并/串转换和串/并转换,PMD对应的是LVDS接口。
由于物理层中并/串(PMA子层),串/并转换(PMA子层),LVDS (PMD子层)以及数据链路层接收侧的同步字搜索,接收端的帧形成已经讲解,下面重点是设计应用层(APP)到数据链路层的实现。
2. 发送端数据链路层设计
继续在hs_txrx的工程文件下修改trans_data.v文件,增加握手信号tx_vld,tx_done。这样在应用层(如tb.v)文件里就不需要处理数据链路层的信息了。

图3
- 修改链路层trans_data.v文件
新的trans_data.v文件如下:
module trans_data
#(
parameter SYNC1 = 10'b1010_1010_10,
parameter SYNC2 = 10'b1010_1010_11
)
(
input rst,
input clk_10M,
input clk_100M,
input tx_vld, //新添加的数据有效信号
input [ 9: 0 ] tx_data, // 10M的时钟对应的数据
input tx_done, //新添加包结束信号
output tx_out
);
wire tx_in;
reg [ 9: 0 ] tx_dra; // tx_data_ra register a
reg [ 9: 0 ] tx_drb; //tx_data_rb;
reg [ 9: 0 ] tx_dr;
reg tx_dr_en;
reg [ 9: 0 ] tx_sft; //tx shift
//reg [3:0] sft_count;
reg cmp;
assign tx_in = tx_sft[ 9 ];
//================= new code ===========================
reg tx_vld_r;
reg tx_done_r;
reg [ 9: 0 ] lvds10_data_in;
reg [ 1: 0 ] sync_data_st;
always@( posedge clk_10M )
begin
tx_done_r <= tx_done;
tx_vld_r <= tx_vld;
end
always@( posedge clk_10M or posedge rst )
if ( rst )
begin
lvds10_data_in <= SYNC1;
sync_data_st <= 3'b0;
end
else
case ( sync_data_st )
0:
begin
lvds10_data_in <= SYNC1;
if ( tx_vld )
begin
lvds10_data_in <= SYNC2;
sync_data_st <= 3'd1;
end
end
1:
begin
sync_data_st <= 3'd2;
if ( tx_vld_r )
begin
lvds10_data_in <= tx_data;
end
end
2:
begin
if ( tx_done_r )
begin
lvds10_data_in <= SYNC1; //idle 空闲
sync_data_st <= 3'd0;
end
else
begin
lvds10_data_in <= tx_data;
end
end
default:
sync_data_st <= 3'd0;
endcase
//=============== new code =================
always@( posedge clk_100M or posedge rst )
if ( rst )
begin
tx_dra <= 0;
tx_drb <= 0;
tx_dr <= 0;
cmp <= 0;
end
else
begin
//tx_dra<=tx_data;
tx_dra <= lvds10_data_in; //修改数据源
tx_drb <= tx_dra;
tx_dr_en <= 1'b0;
cmp <= tx_dra != tx_drb; //compare
if ( cmp )
begin
tx_dr <= lvds10_data_in;
tx_dr_en <= 1'b1;
end
end
//=============方法1:利用100Mhz时钟提取10M边沿脉冲,利用边沿脉冲采样10Mhz同步的数据,
//=============可以避开数据的变化区域进行采样
//reg clk_10M_r;
//reg nclk_10M_r;
//reg clk_10M_rg;// rg---posedge
//reg [9:0] tx_data_100M;
//reg tx_data_100M_vld;
//always@(posedge clk_100M or posedge rst)
//if(rst) begin
// clk_10M_r <=0;
// nclk_10M_r <=1'b1;
// clk_10M_rg <=0;
// tx_data_100M <=0;
// tx_data_100M_vld<=0;
//end
//else begin
// clk_10M_r<=clk_10M;
// nclk_10M_r<=~clk_10M_r;
// clk_10M_rg<=clk_10M_r&nclk_10M_r;
//
// tx_data_100M_vld<=1'b0;
// if(clk_10M_rg) begin
// tx_data_100M_vld<=1'b1;
// tx_data_100M<=tx_data;
//end
//
//end
//==============
//===============方法2:利用循环移位实现在数据没有更新的前提下重复数据串行化
always@( posedge clk_100M or posedge rst )
if ( rst )
tx_sft <= 0; //tx-shift
else
begin
if ( tx_dr_en )
tx_sft <= tx_dr;
else
tx_sft <= { tx_sft[ 8: 0 ], tx_sft[ 9 ] } ; //new tx_sft.d[9]=old_tx_sft.q[8]
end
//=================方法3:利用移位计数器,触发并行10位数据恢复
//always@(posedge clk_100M or posedge rst)
//if(rst) begin
// tx_sft <=0; //tx-shift
// sft_count<=0;
//end
//else begin
//if(tx_dr_en) begin
//tx_sft<=tx_dr;
//sft_count<=0;
//end
//
//else if(sft_count==9) begin
//tx_sft<=tx_dr;
//sft_count<=0;
//end
//else begin
//
//tx_sft<={tx_sft[8:0],1'b0} ; //new tx_sft.d[9]=old_tx_sft.q[8]
//
//sft_count<=sft_count+1;
//end
//
//
//end
IO_TX IO_TX_inst
(
.tx_in ( tx_in ),
.tx_out ( tx_out )
);
endmodule
- 修改仿真文件tb.v
在tb.v文件里处理好握手信号tx_vld和tx_done,同时将Preamble,SFD等信息移出,只实现用户数据与数据链路层的传递。添加包与包之间的间隔控制(inter-packet interval )
`timescale 1 ns / 1 ps
module tb
( );
parameter PERIOD = 20 ; //周期20
reg pll_rst; //实体模块PLL的rst信号
wire tx_rx; //收发在testbench的回环信号
wire clk_10M;
wire pll_locked; //实体PLL锁定信号
wire rst = !pll_locked; //Testbench内生成复位信号
reg [ 9: 0 ] tx_data; //待发送数据
wire rx_data_vld;
wire [ 9: 0 ] rx_data;
reg inclk ; //模拟板级提供的输入时钟
reg tx_vld;
reg tx_done;
reg [ 7: 0 ] frame_interval; //frame interval , interval between two consecutive frame 帧间隔
reg [ 7: 0 ] frame_len;
reg [ 2: 0 ] tx_st;
initial
begin
inclk = 1'b0;
#( PERIOD / 2 );
forever
#( PERIOD / 2 ) inclk = ~inclk;
end
//always #10 inclk=~inclk;
initial
begin
pll_rst = 1'b1;
tx_data = 0;
#50 pll_rst = 1'b0;
end
always@( posedge clk_10M or posedge rst )
if ( rst )
begin
tx_vld <= 1'b0;
tx_done <= 1'b0;
frame_len <= 0;
tx_data <= 0;
frame_interval <= 0;
tx_st <= 0;
end
else
begin
case ( tx_st )
0:
begin
tx_vld <= 1'b0;
tx_done <= 1'b0;
tx_data <= 0;
frame_interval <= frame_interval + 1; //帧间隔+1
if ( frame_interval == 15 )
tx_st <= 1;
end
1:
begin
frame_interval <= 0;
frame_len <= 0;
tx_vld <= 1'b1;
tx_done <= 1'b0;
tx_data <= 0;
tx_st <= 2;
end
2:
begin
frame_len <= frame_len + 1;
tx_vld <= 1'b1;
tx_data <= frame_len;
if ( frame_len == 15 )
begin
tx_st <= 3;
end
end
3:
begin
tx_vld <= 1'b0;
tx_done <= 1'b1;
tx_data <= 8'h00;
tx_st <= 0;
end
default:
tx_st <= 0;
endcase
end
hs_txrx hs_txrx_inst
(
.inclk ( inclk ),
.pll_rst ( pll_rst ),
.tx_vld ( tx_vld ),
.tx_done ( tx_done ),
.tx_data ( tx_data ),
.tx ( tx_rx ),
.rx ( tx_rx ),
.rx_data_vld( rx_data_vld ),
.rx_data ( rx_data ),
.clk_10M ( clk_10M ),
.pll_locked ( pll_locked )
);
endmodule
- 修改hs_txrx.v
主要目的是令新添加的控制信号能正确传递, 代码修改如下:
module hs_txrx
#(
parameter SYNC1 = 10'b1010_1010_10,
parameter SYNC2 = 10'b1010_1010_11
)
(
input inclk,
input pll_rst,
input tx_vld,
input tx_done,
input [ 9: 0 ] tx_data,
output tx,
input rx,
output rx_data_vld,
output [ 9: 0 ] rx_data,
output clk_10M,
output pll_locked
);
wire clk_100M;
wire clk_400M;
wire rst = !pll_locked;
trans_data
#(
.SYNC1( SYNC1 ),
.SYNC2( SYNC2 )
)
trans_data_inst
(
.rst ( rst ),
.clk_10M ( clk_10M ),
.clk_100M ( clk_100M ),
.tx_vld ( tx_vld ),
.tx_done ( tx_done ),
.tx_data ( tx_data ), // 10M的时钟对应的数据
.tx_out ( tx )
);
recv_data
#(
.SYNC1( SYNC1 ),
.SYNC2( SYNC2 )
)
recv_data_inst
(
.rst ( rst ),
.clk_100M ( clk_100M ),
.clk_400M ( clk_400M ),
.rx ( rx ),
.rx_data ( rx_data ),
.rx_data_vld ( rx_data_vld )
);
pll1 pll1_inst
(
.areset ( pll_rst ), //1'b0;
.inclk0 ( inclk ),
.c0 ( clk_10M ),
.c1 ( clk_100M ),
.c2 ( clk_400M ),
.locked ( pll_locked )
);
endmodule
仿真波形下:

图4 tb.v仿真波形
从图4的波形和发送与接收对比看,已经成功完成数据包的正确收发。而且tb.v可以认为是应用层,因此只关心APP数据,将数据链路层和物理层需要的处理措施,交给底层处理。

图5 数据链路层波形(trans_data.v)

图6 接收端数据帧
从上面讨论的结果看,我们已经实现了三层协议,应用层,数据链路层和物理层。其中物理层又实现了PMA和PMD层。后续章节将会讲解物理层中控制子层PCS层的构建。