简介:围绕M序列在FPGA中实现同步时钟提取的工程资料包,面向通信、电子类相关专业学生及数字逻辑开发者,覆盖从LFSR序列生成、位同步电路到全数字锁相环(DPLL)时钟校准的学习与实验需求。包内共68个文件,以VHDL源码、ISE工程与综合/仿真中间文件、PDF和DOC技术文档、仿真波形及运行日志为主,另有可执行与配置文件,压缩包约2.66MB;工程结构较完整,便于直接打开仿真、查看设计层次并对照文档理解反馈抽头、鉴相与滤波等关键环节。已有510人学习或下载。读者可据此梳理63位M序列发生器的VHDL实现,结合DPLL位同步、Cyclone同步设计及通信系统位同步电路资料,获得从代码到波形验证、从原理到工程调试与排错的参考,适合课程设计、毕业设计及通信同步模块的入门与进阶实践。
1. 从“别急着滤波”说起
第一次在 FPGA 上做 m 序列的同步时钟提取,很多人的第一反应是找一个窄带滤波器,把码片速率那根谱线滤出来。这条路在模拟域成立,在 FPGA 里却基本行不通:可综合的只有触发器、LUT 和乘法器,没有品质因数足够高的谐振腔,也没有负延迟的延时线。真正能跑通的做法反过来——不去“滤”时钟,而是把数据流里的每一次跳变当成一次相位投票,用一个数字锁相环把成百上千次投票平均成一个稳定的采样选通。m 序列恰好是跳变密度接近 50%、游程很短、自相关只有一个尖峰的序列,天生适合干这件事。搞懂它,也就搞懂了 FPGA 程序里所有“无随路时钟”场景的定时恢复。
2. m 序列为什么自带时钟:游程分布、转移密度与自相关尖峰
2.1 从 LFSR 到 m 序列:周期、游程与接近 50% 的跳变率
n 级线性反馈移位寄存器在反馈多项式为本原多项式时,会遍历除全 0 以外的全部 2^n − 1 个状态,输出序列的周期 N = 2^n − 1,这就是 m 序列。以 n = 7、多项式 x^7 + x^6 + 1 为例,周期 127,工业上常说的 PRBS-7 就是它。这个多项式必须本原,否则状态空间会退化成几个互不相通的短环,周期不再是 2^n − 1,后面所有基于“整周期”的推导全部失效。
真正决定能不能提取时钟的,是游程分布。m 序列在一个周期内共有 2^(n−1) 个游程,长度 k(1 ≤ k ≤ n−2)的游程中,0 和 1 各有 2^(n−2−k) 个;另有一个长度 n−1 的全 0 游程和一个长度 n 的全 1 游程。每个游程的起点恰好对应一次电平跳变,所以一个周期内的跳变次数就是 2^(n−1),转移密度 = 2^(n−1)/(2^n − 1),n 稍大就无限接近 0.5。n = 7 时是 64/127 ≈ 0.504,平均游程长度不到 2 个码片。
这个数字很关键。跳变越密,单位时间里能拿到的相位观测就越多,环路收敛越快,抖动被平均得越狠。作为对照,全 0 或全 1 的长连码在同样的码片速率下跳变率接近 0,任何基于边沿的定时恢复都会失锁;而 0xAA 这种 1010 交替图样跳变率 100%,频谱上却是一根根离散谐波、没有处理增益,也没法做相关捕获。m 序列是在“有足够跳变”和“有处理增益”之间取的一个平衡点。
代价也要提前认清:一个周期里必然存在长度 n 的全 1 游程和长度 n−1 的全 0 游程。这段时间内没有任何边沿,鉴相器输出为空,环路只能靠 NCO 自由振荡撑过去。n 越大,这段“盲区”越长,对本地振荡器短稳的要求越高。这是后面环路带宽不能无限压低的原因之一。
先用软件把参数验证一遍,比直接写 Verilog 再上板调试省事得多:
# 用软件模型先确认多项式本原、游程分布、自相关形状,再动手写 RTL def mseq(taps, n, seed=1, length=None): N = (1 << n) - 1 length = length or N reg, out = seed & N, [] for _ in range(length): out.append(reg & 1) fb = 0 for t in taps: # 抽头位置从 1 开始编号 fb ^= (reg >> (t - 1)) & 1 reg = (reg >> 1) | (fb << (n - 1)) # Fibonacci 结构,右移 return out n, N = 7, 127 s = mseq([7, 6], n) b = [1 - 2 * x for x in s] # 双极性映射 0->+1, 1->-1 # 周期自相关:理论上只有 tau=0 处是 N,其余全是 -1 R = [sum(b[i] * b[(i + tau) % N] for i in range(N)) for tau in range(N)] print(R[0], sorted(set(R[1:]))) # 期望 127 和 [-1] # 转移密度:一个周期内的跳变次数 trans = sum(1 for i in range(N) if s[i] != s[(i + 1) % N]) print(trans, trans / N) # 期望 64 与 0.504taps=[7,6]对应 x^7 + x^6 + 1,seed必须非零,reg的位宽决定状态空间大小。这段脚本跑出来的两个数如果和理论值对不上,说明多项式选错了或者抽头接反了,此时改 RTL 是白费功夫。
2.2 自相关函数:为什么只在零延迟处出现尖峰
m 序列的周期自相关是二值的:τ ≡ 0 (mod N) 时为 N,其余全部为 −1。这个结论有一个不太依赖教科书的推导,理解了它,后面所有相关捕获的设计才有落脚点。
关键在于移位相加性:m 序列与任意非零循环移位做异或,结果仍然是同一个 m 序列的某个循环移位。任何一个 m 序列在一个周期内恰有 2^(n−1) 个 1,所以异或结果里 1 的个数恒为 2^(n−1),也就是说两个序列在任意非零偏移下的汉明距离恒为 2^(n−1)。于是相同位的个数 = N − 2^(n−1) = 2^(n−1) − 1,双极性相关值 = 相同数 − 不同数 = (2^(n−1) − 1) − 2^(n−1) = −1。
对 n = 7 就是:完全对齐时相关值 127,任意错开一位都是 −1,换算成“匹配比特数”则是 127 对 63。这个 63 的旁瓣电平是固定的、与偏移无关的常数,不像白噪声那样随机起伏,所以判决门限可以直接取在两者中间,工程上取 95 就留了 32 个比特的余量。这就是 m 序列能被用来做帧同步和相位捕获的根本原因,也是它比普通伪随机序列更适合“同步时钟提取”的地方。
处理增益同样可以从这里读出来:相干积累一个完整周期,噪声被压低了约 10·log10(N) dB。n = 7 是 21 dB,n = 10 是 30 dB。这是用资源换信噪比,后面讲相关器结构时会看到它的价格。
2.3 三条时钟提取路线的取舍
摆在面前的路其实有三条,区别在于把“相位信息”从哪里取出来。
第一条是准模拟的窄带滤波:先微分再整流,把跳变变成窄脉冲,然后用一个高 Q 值的数字谐振器或者长滑动平均提取码片频率。这条路资源极省,但它只能给出频率,给不出相位,而且对游程长度敏感,遇到 n 连 1 就会漏掉一拍。它适合作为辅助的频率粗估,不适合做主环。
第二条是过采样加边沿鉴相的 DPLL。它不关心序列长什么样,只看有没有跳变,资源开销与序列长度无关,跟踪是连续的。缺点是每个边沿只提供一次量化的相位观测,观测噪声全靠环路带宽去压,低信噪比下抖动明显。序列越短、M 越小,抖动越大。
第三条是相关捕获加早迟门。它把整个周期的能量都用上了,21~30 dB 的增益是前两条路给不了的,代价是资源随 N 线性增长,n 超过 12 之后单靠滑动相关基本塞不进中低端器件。
我的一般做法是两条腿走路:先用第二条路把码片时钟粗锁住,让相关器有一个大致正确的码片使能,再用第三条路做精同步和帧相位判决。这两条路的输出最终都汇到同一个 NCO 上,谁在跟踪状态就听谁的。
3. 数字锁相环:过采样、边沿鉴相与 NCO 怎么把时钟锁住
3.1 过采样与边沿鉴相:相位误差到底测的是什么
FPGA 里没有“延迟半个码片”的模拟延时线,能做的只有用远高于码片速率的系统时钟去采样,然后数一数跳变落在第几个采样点上。取 M 倍过采样(M = 8、16、32),系统时钟 f_s = M · f_chip,码片内被切成 M 个等分,相位分辨率就是 T_chip / M。
鉴相器的做法是:把相邻两个采样点做异或,结果为 1 就说明这一小段里有跳变。理想情况下,一个码片周期内最多出现一次跳变,跳变发生的位置理论上应当正好落在码片边界上。本地 NCO 给出一个“当前相位”,跳变出现时读一下这个相位值 p,就得到了误差:p 偏离 0 多少,本地的码片边界就偏了多少。
这里有两个绕不开的细节。第一个是量化噪声:单次观测的误差被量化到 1/M 的整数倍,服从近似均匀分布,均方根抖动约 T_chip / (M·√12)。M = 16 时是 T_chip 的 1.8%,靠环路平均还能再压一到两个数量级。M 每翻一倍,抖动降 6 dB,但采样时钟频率也翻倍,功耗和时序压力随之上升,这是纯粹的取舍。
第二个是毛刺。真实信道上的噪声会让同一个码片内出现两个甚至多个“跳变”,如果照单全收,环路会被来回拉扯。常见的实现思路是加一条规则:一个码片窗口内只接受第一个跳变,后续的边沿在窗口结束前一律屏蔽;更严格一点,用三个相邻采样做多数表决,只有“前后两个采样一致、中间那个不一致”才算真边沿。这条规则看起来
本文还有配套的精品资源,点击获取