上节内容讲解在接收端串并转换后需要捕获同步字,这在通信中是个广泛和通用的概念。例如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文件如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
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 )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
`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
主要目的是令新添加的控制信号能正确传递, 代码修改如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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层的构建。