上节内容讲解了位提取以及自适应同步的概念,同时也输出了与本地过采样时钟同步的rx_bit和rx_bit_vld信号,下文将介绍如何利用这两个信号进行并行化处理以及如何实现帧同步。高速串行通信接收器recv_data.v在整个工程结构中的位置如图1所示:

图1
1. 串并转换
串并转换比较简单,利用移位寄存器就可以实现。收、发双方在并/串转换与串/并转换应遵循一致的原则。
(1)收发并行位数一致
比如发送端采用8-bit的并行数据串行化,则接收端也应使用8-bit的移位寄存器实现串/并转换才能识别发送的并行数据, 如果发送端采用10-bit数据(如8b/10b编码),接收端也要采用10-bit的移位寄存器存储10-bit的临时数据。如果发送端采用66-bit,接收端也应采用66-bit等。
(2)移位方向一致
如发送端左移,则接收端也因该左移。如下面语句,
always@(posedge clk_400M or posedge rst) . . . if (rx_bit_vld) begin bit_sft <={bit_sft[8:0],rx_bit}; //10位左移,与发送端一致 sync_shift <={sync_shift[8:0],bit_sft[9]}; end
注意一定要在rx_bit_vld的控制下进行,为什么?
以10-bit为例串/并转换的程序框图如图2所示:

图2
对应的程序代码如下:
例1:
module recv_data
(
input rst,
input clk_100M ,
input clk_400M ,
input rx,
output reg[ 9: 0 ] rx_data,
output reg rx_data_vld
);
localparam SYNC1 = 10'b1010_1010_10;
localparam SYNC2 = 10'b1010_1010_11;
wire rx_bit;
wire rx_bit_vld;
reg [ 9: 0 ] bit_sft;
reg [ 9: 0 ] sync_shift = 0;
always@( posedge clk_400M or posedge rst )
if ( rst )
begin
bit_sft <= 0;
sync_shift <= 0;
end
else
begin
if ( rx_bit_vld )
begin
bit_sft <= { bit_sft[ 8: 0 ], rx_bit }; //10位左移,与发送端一致
sync_shift <= { sync_shift[ 8: 0 ], bit_sft[ 9 ] };
end
end
reg [ 3: 0 ] bit_count;
reg fr_sync1, fr_sync2;
reg fr_sync;
reg fr_end;
reg [ 7: 0 ] fr_count;
always@( posedge clk_400M or posedge rst )
if ( rst )
begin
rx_data <= 0;
rx_data_vld <= 0;
bit_count <= 0;
fr_sync1 <= 0;
fr_sync2 <= 0;
fr_sync <= 0;
fr_end <= 0;
fr_count <= 0;
end
else
begin
if ( fr_sync )
fr_sync2 <= 1'b0;
else if ( bit_sft == SYNC2 )
fr_sync2 <= 1'b1; //fr----frame
else
fr_sync2 <= 1'b0;
if ( fr_sync )
fr_sync1 <= 1'b0;
else if ( sync_shift == SYNC1 )
fr_sync1 <= 1'b1;
else
fr_sync1 <= 1'b0;
if ( fr_end )
fr_sync <= 1'b0;
else if ( fr_sync1 & fr_sync2 & rx_bit_vld )
fr_sync <= 1'b1;
if ( fr_sync )
begin
if ( rx_bit_vld )
begin
if ( bit_count == 9 )
bit_count <= 0;
else
bit_count <= bit_count + 1;
end
end
rx_data_vld <= 0;
if ( bit_count == 9 )
begin
rx_data <= bit_sft;
rx_data_vld <= 1'b1;
end
fr_end <= 1'b0;
if ( rx_data_vld & rx_bit_vld )
if ( fr_count == 15 )
begin
fr_end <= 1'b1;
fr_count <= 0;
end
else
fr_count <= fr_count + 1;
end
recv_bit recv_bit_inst
(
.rst ( rst ),
.clk_400M ( clk_400M ),
.rx ( rx ),
.rx_bit ( rx_bit ),
.rx_bit_vld ( rx_bit_vld )
);
endmodule
2. 同步字搜索
完成串/并转换后,下一步就要搜索同步字,受以太网的启发,同步字可以采用“前导字+帧起始”的格式。至于前导字(Preamble)到底采用几个字实现根据需要确定。
- 前导字
上例中前导字采用10’b10_1010_1010, 同时在Idle状态也采用10’b10_1010_1010可以有效平衡通信线路的直流电压的偏移。
(思考题:在差分信号传输中,由于长0或长1的原因引起传输线路的直流偏移,是否对共模信号也会产生影响?)
- 帧起始(SFD)
帧起始采用10’b1010_1010_11,这种格式的帧起始与以太网MAC层的帧起始非常类似。
- 同步字捕获
在级联的移位中,Preamble和SFD是首尾相连的,因此可以用来联合判断,即Preamble和SFD同时捕获才算同步字捕获。
可以用如下语句实现:
if((sync_shift==SYNC1) && (bit_sft==SYNC2) && rx_bit_vld)
fr_sync<=1'b1;
也可以用下面的语句实现:
if ( fr_sync )
fr_sync2 <= 1'b0;
else if ( bit_sft == SYNC2 )
fr_sync2 <= 1'b1; //fr----frame
else
fr_sync2 <= 1'b0;
if ( fr_sync )
fr_sync1 <= 1'b0;
else if ( sync_shift == SYNC1 )
fr_sync1 <= 1'b1;
else
fr_sync1 <= 1'b0;
if ( fr_end )
fr_sync <= 1'b0;
else if ( fr_sync1 & fr_sync2 & rx_bit_vld )
fr_sync <= 1'b1;
思考题:上面两段代码有什么不同?
3. 数据捕获
一旦同步字捕获后,就可以实现帧同步,也就是帧的起始已经得到定位,后面可以根据字的长度通过计数器的方式实现帧内数据的捕获,捕获代码如下:
if ( fr_sync )
begin
if ( rx_bit_vld )
begin
if ( bit_count == 9 )
bit_count <= 0;
else
bit_count <= bit_count + 1;
end
end
rx_data_vld <= 0;
if ( bit_count == 9 )
begin
rx_data <= bit_sft;
rx_data_vld <= 1'b1;
end
4. 帧结束
数据在线路上传输总是按照帧格式一帧一帧的传输的,有帧起始,也有帧结束。帧起始通过Preamble + SFD 可以实现,帧结束可以有如下方式实现:
(1)收发双方约定双方共同遵循的帧长度(或包长度)。但这种约定方式的缺点很明显,一旦包的长度约定后,在通信中一般不能动态修改,除非有严格通信协议可以保证。比如协议可以如下:
![]()
图3
其中:
- D-ID,S-ID分别表示目的ID和源ID。
- P-length指定该数据包的长度,在帧同步后就开始计数(fr_count),并与P-length比较,一旦比较结果相等,表示该数据帧结束。[P-length = len(D-ID + S-ID + P-length+ data0 + data1 + … datan)]
数据帧结束后撤销帧同步信号。此时接收端进入空闲(IDLE)状态,并为下一帧数据做准备。这种协议方式可以参照IP或UDP的协议进行。
(2)可以指定帧结束标志字,利用标志字结束该数据帧,这种用法一般利用4b/5b编码或8b/10b编码的控制字实现,如8b/10b中的K值实现。关于信道编码的相关知识,参照后续课程“信道编码”的文章。
下面的程序利用双方约定的每帧16个字为数据帧的长度,控制帧结束。
if ( fr_end )
fr_sync <= 1'b0;
else if ( fr_sync1 & fr_sync2 & rx_bit_vld )
fr_sync <= 1'b1;
fr_end <= 1'b0;
if ( rx_data_vld & rx_bit_vld )
if ( fr_count == 15 )
begin
fr_end <= 1'b1;
fr_count <= 0;
end
else
fr_count <= fr_count + 1;
将上述代码合并后即如例1所示。

图4
5. 仿真
仿真程序组件:
(1)发送端程序,采用带有preamble+SFD 的10bit串行化程序,见文章高速通信发送与数据串行化。
(2)仿真程序如下:
`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 [ 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_st <= 0;
tx_data <= 0;
end
else
begin
case ( tx_st )
0:
begin
tx_st <= 1;
tx_data <= 0;
end
1:
begin
tx_st <= 2;
tx_data <= 10'b10_1010_1010;
end
2:
begin
tx_st <= 3;
tx_data <= 10'b10_1010_1010;
end
3:
begin
tx_st <= 4;
tx_data <= 10'b10_1010_1011;
end
4:
begin
tx_st <= 5;
tx_data <= 0;
end
5:
begin
tx_data <= tx_data + 1;
if ( tx_data == 14 )
tx_st <= 6;
end
6:
begin
tx_st <= 1;
tx_data <= 10'b10_1010_1010;
end
default:
tx_st <= 6;
endcase
end
hs_txrx hs_txrx_inst
(
.inclk ( inclk ),
.pll_rst ( pll_rst ),
.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
(3)仿真波形:
图5可以看出通过preamble + SFD可以成功捕获串行数据,并能成功解析数据包。

图5
图6 显示了同步字捕获逻辑,实现帧同步。

图6
图7显示,帧同步后,利用计数器bit_count==9作为条件,成功捕获数据。

图7
图8显示,利用fr_count计数器与约定的帧长度比较,最终成功结束本帧的收发,进入到IDLE状态,为接收下一帧数据做准备。

图8
总结:本文着重介绍了串/并转换,帧同步,数据捕获,帧结束等内容,并对接收的算法用Verilog语言实现。当然在通信的设计中还有信道编码、协议、数据缓冲,异步时钟域的数据交换,异步过程等诸多内容,将在后续章节中逐一介绍。
工程文件下载(Quartus版本:18.1):
[content_control]
[/content_control]