ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA实现曼彻斯特编码:Verilog源码、仿真与实验报告指南

FPGA实现曼彻斯特编码:Verilog源码、仿真与实验报告指南 简介FPGA实现曼彻斯特编码的完整工程资源内含可综合的源代码与配套实验报告适合电子工程、通信工程及计算机相关专业学生用作课程设计或毕业设计参考。压缩包为rar格式整体仅181KB其中源代码覆盖状态机控制逻辑、输入数据处理、时钟同步电路与输出接口等关键模块实验报告则从曼彻斯特编码原理入手详细介绍了自同步编码机制、数据位中间电平翻转规则并完整记录了FPGA实现流程、硬件描述语言编程要点、功能仿真方法、下载测试步骤以及常见问题与调试思路。已有842人学习使用读者将源码与报告对照学习可深入理解曼彻斯特编码在FPGA上的落地方式掌握锁相环与延迟锁定环时钟对齐技术的实际应用并能独立完成从逻辑设计、代码编写到编译仿真、板级验证的完整开发过程有效提升数字通信系统设计与可编程逻辑开发实战能力。资源内容完整目录结构清晰适合自学与教学演示。1. 为什么 FPGA 做曼彻斯特编码老是出毛刺曼彻斯特编码在很多 FPGA 项目里并不算高频但一旦需要做 10M 以太网、RFID 回传或低速长线传输它几乎是绕不开的物理层格式。它的设计目标很直接把时钟信息塞进数据流里面每发送一个 bit码流中间就有一个由高往低或由低往高的跳变接收端不需要单独分发时钟也能从边沿恢复采样节奏。正因为“中间必有跳变”这个特性FPGA 实现时最容易犯的错误就是图省事把编码输出写成组合逻辑数据一变电平立刻跟随最后的波形上全是毛刺。实际项目中我更愿意用计数器把每个 bit 划分成固定拍数再用寄存器输出电平中间跳变位置由参数控制。这样无论是 fpga 入门还是做课设报告源代码都能稳定复现实验报告也有数据可写。2. 曼彻斯特编码的波形定义与 FPGA 实现方案选择如果只记住一个公式那便是曼彻斯特编码中“跳变方向代表数据值”。在写给实验报告时必须先明确你使用的极性定义否则后面仿真和实物验证都会对不上。常见定义是逻辑 1 在 bit 中心完成高电平到低电平的跳变逻辑 0 在 bit 中心完成低电平到高电平的跳变。若用序列1 0 1 1 0展开波形依次为“高-低”、“低-高”、“高-低”、“高-低”、“低-高”五个 bit 的中心点全部出现一次反向而 bit 边界处是否跳变依赖前后数据值数据相同则边界跳变数据不同则边界保持。这个细节很关键因为接收端判断比特不是看边界而是看中心跳变时刻的相对电平。2.1 从序列到波形中间跳变的反向关系实现时我习惯先在纸上画一个时间轴。以 bit 周期 T 为例前半部分电平等于当前发送的数据值后半部分电平等于数据的反相值。也就是说发 1 时前半个 T 输出高到 T/2 处拉低并保持到 T 结束发 0 时前半个 T 输出低到 T/2 处拉高。所以只要在发送数据的高位寄存器里取data作为初始电平再在T/2处做一次tx_out ~tx_out就能得到合法波形。这段规则看起来简单但容易被“1 是下降沿0 是上升沿”这种简化描述带偏。下降沿本身是两处电平变化不是电平状态如果不规定“前半段电平是什么、后半段是什么”仿真时很容易做成发 1 时先输出低再输出高接收端当然全错。因此我把源代码里的输出初始化写成“从当前 bit 的数据值开始”而不是“统一从高开始”。2.2 状态机与计数器打拍的选择FPGA 里生成曼彻斯特波形有两条常见路线用状态机加计数器或直接用assign tx_out clk ^ data;。后者几乎是最短代码在系统时钟频率等于 bit 速率两倍且相位对齐时确实成立但真实场景很难满足。系统时钟往往远高于 bit 速率直接异或会生成一个与系统时钟同频的输出数据稍微抖动输出就出现窄脉冲而且全局时钟扇出到普通 IO 的路径不同示波器上看到的跳变沿并不干净。实验报告中写这个方案送审时也容易被用“时序未约束”打回。我更推荐的方案是“计数器打拍加状态机框架”。计数器的职责是控制 bit 周期长度状态机的职责是控制帧的开始和结束。两者分开代码可读性好参数调整也不互相影响。下面的表格列出了我在课设和项目里常用的几个参数它们会直接出现在源代码参数表中。参数含义推荐取值影响CLK_PER_BIT每个 bit 对应的系统时钟周期数4、8、10决定输出码率必须大于 2HALF_BIT位中心跳变位置CLK_PER_BIT/2奇数时前后半周期不对称DATA_WIDTH并行发送数据宽度8/16串行化移位次数INVERT_OUT输出极性反向0 或 1对接外部器件极性时使用CLK_PER_BIT取偶数很重要。如果系统时钟是 100MHz想送出 10Mbps 码流就取 10后半段时间 5 拍。假如只能取奇数 5那么前半可能是 2 拍、后半 3 拍位中心跳变不再位于整个 bit 周期的正中间接收端做中心对齐采样时余量变小。最终选多少取决于开发板时钟和波特率之间的整数分频关系。2.3 跨时钟域数据怎么进编码器曼彻斯特编码器在物理层工作但data_in通常来自逻辑层或 FIFO两者使用不同的时钟域。如果不做同步start信号亚稳态可能让编码器多锁一拍导致帧头错位。常见做法是先把单 bit 的fifo_valid打成两拍再参与编码器的使能判断reg vld_1, vld_2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin vld_1 1b0; vld_2 1b0; end else begin vld_1 fifo_valid; vld_2 vld_1; end end代码里的vld_1与vld_2是两级同步寄存器作用是消除第一个触发器上的亚稳态窗口。多 bit 数据本身不要这样同步而是应该让fifo_valid与fifo_rd_en构成握手在vld_2拉高时从 FIFO 读出data_in再送给编码器。这样既能保证数据稳定也不会让编码器因为空读而发送垃圾数据。3. 用 Verilog 写曼彻斯特编码器源代码要点与参数设计这一章的代码可以直接放到 Vivado 或 Quartus 里综合。设计目标是一次发送DATA_WIDTHbit 数据按最高位在前的方式输出曼彻斯特波形发送完成后自动回到空闲态busy信号低电平表示可以接收下一次发送。3.1 端口定义和基本参数module manchester_enc #( parameter DATA_WIDTH 8, parameter CLK_PER_BIT 8, parameter INVERT_OUT 0 )( input wire clk, input wire rst_n, input wire start, input wire [DATA_WIDTH-1:0] data_in, output reg m_out, output reg busy ); localparam HALF_BIT CLK_PER_BIT / 2; localparam CNT_W $clog2(CLK_PER_BIT); reg [DATA_WIDTH-1:0] sr; reg [CNT_W-1:0] cnt; reg [CNT_W-1:0] bit_cnt; reg active;start的含义是“发送启动脉冲”建议只持续一个时钟周期。INVERT_OUT不改变帧结构只把输出电平整体翻转用于应对示波器看到极性相反的场景。CNT_W使用$clog2自动计算兼容 Vivado 和 ModelSim如果使用的综合器不支持可以直接写成localparam CNT_W 4;。3.2 主状态机与中间跳变逻辑always (posedge clk or negedge rst_n) begin if (!rst_n) begin sr 0; cnt 0; bit_cnt 0; active 0; busy 0; m_out 0; end else if (!active) begin if (start) begin sr data_in; cnt 0; bit_cnt DATA_WIDTH - 1; active 1; busy 1; m_out INVERT_OUT ? ~data_in[DATA_WIDTH-1] : data_in[DATA_WIDTH-1]; end else begin m_out 0; end end else begin if (cnt HALF_BIT - 1) begin cnt cnt 1; m_out ~m_out; end else if (cnt CLK_PER_BIT - 1) begin cnt 0; if (bit_cnt 0) begin active 0; busy 0; m_out 0; end else begin bit_cnt bit_cnt - 1; sr {sr[DATA_WIDTH-2:0], 1b0}; m_out INVERT_OUT ? ~sr[DATA_WIDTH-2] : sr[DATA_WIDTH-2]; end end else begin cnt cnt 1; end end end endmodule这段代码的核心在cnt HALF_BIT - 1这个判断。当计数器走到半个 bit 周期时把输出电平取反完成 1 的高到低或 0 的低到高跳变。判断条件使用HALF_BIT - 1是因为输出在start后的第一个时钟沿已经稳定为起始电平计数器开始累加后的第HALF_BIT个沿才是精确中心点。若写成cnt HALF_BIT中心点会整体后移一拍肉眼难以察觉但接收端做长时间统计时却会出现系统性偏移。在cnt CLK_PER_BIT - 1的分支里代码先判断当前是否是最后一个 bit。如果不是则把移位寄存器左移一位让下一位的起始电平直接出现在m_out上如果是最后一个 bit则拉低busy编码器回到空闲。这里要注意m_out没有使用组合逻辑赋值所有跳变都发生在系统时钟沿上所以综合后输出管脚不会产生毛刺。3.3 参数变化与代码可移植性把CLK_PER_BIT从 8 改成 4输出码率直接翻倍改成 10则适合 100MHz 时钟发 10Mbps 数据。DATA_WIDTH改成 16 也可以只要接收端知道发送顺序仍然是最高位在前。如果需要最低位在前把sr[DATA_WIDTH-2:0]和data_in[DATA_WIDTH-1]都改成按最低位取值但要注意初始化m_out时也必须同步修改否则第一 bit 电平不对。常见做法是在顶层用一个 parameterLSB_FIRST控制取位方向实验室里不需要这么复杂。3.4 启动脉冲丢失与背靠背发送问题上述代码在!active状态下检测start因此start必须在busy拉低之后再次出现才有效。有些外部逻辑会直接把发送使能长期置 1此时编码器会在上一帧结束后的下一个周期立即发送下一帧中间没有空闲位。如果接收端需要两个帧之间保持至少一个 bit 的低电平应该在顶层添加tx_gap计数器或在发送结束后强制空闲一段时间再判断start。最简单的做法是给active加一条延迟把active 0改成idle_cnt计数。4. 用仿真验证曼彻斯特编码器并把实验报告写得能复现代码写完后不要急着上板。先跑一遍仿真确认 8 个 bit 波形完全符合“1 中间高到低、0 中间低到高”的约定再写实验报告报告里的截图才有说服力。4.1 最小 testbench 怎么写timescale 1ns/1ps module tb_manchester; reg clk; reg rst_n; reg start; reg [7:0] data_in; wire m_out; wire busy; manchester_enc #( .DATA_WIDTH(8), .CLK_PER_BIT(8), .INVERT_OUT(0) ) dut ( .clk (clk), .rst_n (rst_n), .start (start), .data_in (data_in), .m_out (m_out), .busy (busy) ); initial begin clk 0; forever #5 clk ~clk; end initial begin rst_n 0; #20; rst_n 1; #10; (posedge clk); start 1; data_in 8b10110100; (posedge clk); start 0; #2000; $finish; end endmoduleforever #5 clk ~clk;生成周期 10ns 的 100MHz 时钟。CLK_PER_BIT8时每个 bit 占 80ns8 个 bit 共 640ns所以#2000足够看到完整帧和尾巴。data_in选10110100的好处是包含连续相同 bit两个 1和连续不同 bit10 交界能从波形上一眼看出边界跳变与中心跳变的差别。4.2 仿真波形上要检查的三个位置第一个位置是帧头busy拉高后m_out的第一段电平必须等于data_in[7]也就是 1因此输出先保持高。第二个位置是每个 bit 中心用光标分别停在第一个 bit 的 40ns、第二个 bit 的 120ns 等位置应该看到m_out发生翻转。第三个位置是最后一 bit 结束后busy拉低m_out回到 0且不能出现半拍高电平残留。如果仿真波形里中心跳变方向反了优先检查是不是INVERT_OUT参数设成了 1如果中心跳变位置偏移了整整一拍检查HALF_BIT-1是否被误写成了HALF_BIT。这两个是实验报告里最容易写错的问题。4.3 实验报告需要哪些截图和数据实验报告不是越厚越好重点是让别人照着做能复现。第一部分放一张完整仿真波形图图中标注出位边界和位中心第二部分放一张局部放大图至少覆盖1 0 1三个 bit说明高电平宽度是HALF_BIT个时钟周期第三部分从 Vivado 的 Report Utilization 里截 LUT 和 FF 数量写明用了几个寄存器第四部分放约束文件里的输出引脚配置证明不是仿真专用代码。时序报告如果不报错就截WNS到Output Delay即可。4.4 仿真常见坑与规避仿真时最常见的问题有两个。一是 testbench 中start是在always (posedge clk)之后才赋值它会在下一个时钟上升沿才被编码器采到导致波形整体往后移一个时钟周期但编码器内部逻辑没有错写报告时不要把这种问题描述成毛刺。二是$finish放在等待 busy 拉低之前导致波形截断。若要自动等待可以使用wait (busy 1b1); wait (busy 1b0); #100; $finish;这段代码先等待busy变高再等待其变低保证整个帧都已经发送完成。仿真通过后直接把这四行替换掉#2000实验报告里就能呈现完整的“开始-发送-结束”三段式波形。5. 从 FPGA 到实验板引脚约束、示波器测量和常见坑仿真只能证明逻辑功能正确不能证明物理层信号能直接连到外部。曼彻斯特编码输出是单端电平信号接示波器、接接收芯片或接另一个 FPGA 时引脚约束和接线方式都会影响最终结果。5.1 输出引脚约束与驱动强度以 Xilinx FPGA 为例约束文件里至少需要为m_out指定引脚和电平标准set_property PACKAGE_PIN P14 [get_ports m_out] set_property IOSTANDARD LVCMOS33 [get_ports m_out] set_property SLEW FAST [get_ports m_out]LVCMOS33是 3.3V 电平需要确认开发板输出 BANK 的供电电压SLEW FAST适合较长走线但会提高边沿速率。长线超过 20cm建议在外部串联一个 33Ω 电阻降低反射。若输出直接接 LED要加限流电阻否则 FPGA 脚可能承受不住持续高电平的电流。SLEW FAST不是不改也行示波器上看振铃时先把 slew 改成 SLOW往往比换探头更有效。5.2 用示波器量曼彻斯特波形的正确姿势量单端曼彻斯特波形时探头切换到 10 倍衰减接地弹簧尽量短直接接到m_out附近的 GND。10x 探头本身的输入电容约 10pF加上夹子地线后可能在地端形成振荡导致中间跳变附近出现过冲。若看到在每个 bit 中间跳变处都有 ±0.5V 的振铃先不要怀疑代码把地线夹换成接地弹簧同时把触发模式设成上升沿观察 3 到 5 个 bit 周期即可。实验报告里放示波器截图时要同时显示busy和m_out或者把两个通道一个接 FPGA 输出的编码信号、另一个接发送使能这样可以证明发送时序。单独截m_out无法说明帧长度和起始位置审阅者很难判断波形是否准确。5.3 板上实测的三个高发问题第一个高发问题是首 bit 错位现象是没有数据时m_out为高发送时第一个 bit 已变成低电平。原因是复位后的空闲电平约定不一致。发送前接收端通常期待空闲电平为低而曼彻斯特编码的 1 起始电平是高所以发送帧头必须从 1 开始并在发送结束后强制m_out0。代码里已经在!active分支通过else m_out 0处理了确认综合时没有被优化掉。第二个高发问题是时钟相位开发板晶振直接做系统时钟位周期中心跳变沿与外部接收器件的数据采样沿重叠导致接收误码。解决方法是让编码器使用带 MMCM/PLL 的时钟并添加输出延迟约束。set_output_delay可以约束m_out到外部引脚的延时建议先跑一次时序报告把约束值设在时钟周期的一半以内。第三个高发问题是开发板上的 IO 供电电压不匹配接 5V 逻辑芯片时3.3V 高电平在噪声下可能低于 5V 器件的 VIH需要在芯片侧加上拉电阻或加转换芯片。FPGA 侧不要试图在 5V 电平下工作容易损坏 IO。6. 曼彻斯特编码器背后接口设计要补的三个技巧编码器本身很简单真正考验工程能力的是把它放进一个低速串行总线里。第一个技巧是帧头加前导码。帧空闲时m_out0没有任何跳变接收端如果在上电后一直等边沿可能等不到启动发送方在数据前补一个10101010让接收端在 8 个 bit 周期里看到连续 16 次跳变用这 16 次跳变完成时钟同步。实现时只需要把前导码和数据拼在一个寄存器里frame {8hAA, data_in}再按同样的移位方式送进编码器。第二个技巧是波特率可以在运行时调整。不要在顶层把CLK_PER_BIT定义成固定值而是留一个reg [7:0] baud_div代码里把所有CLK_PER_BIT换成baud_div计数器判断改成cnt baud_div - 1。修改baud_div时尽量在当前 bit 结束后的空闲态改换到新波特率后的第一个 bit 会从新值开始计数不会出现半个旧 bit。这里注意HALF_BIT也要跟着算否则中心跳变就失去意义。变通做法是把HALF_BIT变成baud_div 1同时保证写入的值始终是偶数。第三个技巧是回环自测。把编码器的m_out直接接到同板接收引脚接收端用系统时钟做 3 倍过采样检测到中间跳变后对齐数据窗口再在每个 bit 的前 1/4 处采样一次。我在实际调试时会加一个计数器发送 10000 字节随机数接收端逐 bit 比对统计出errors。只有当errors 0时才把编码器接入真实的下游器件。这一个回环操作能过滤掉绝大多数“延迟、等待、未复位”等仿真里看不到的问题。以上便是把曼彻斯特编码源代码和实验报告写完整的一条路线。编码的关键不是背下波形而是把“中间跳变”落实到计数器的HALF_BIT - 1这一拍上。调试时如果发现接收端数据总是反向先把INVERT_OUT翻转如果发现整体码流全部错位再检查start是否与发送时钟沿对齐。最后把仿真波形、板上示波器图和利用率三个结果按顺序保存好实验报告直接引用即可不需要额外修饰。本文还有配套的精品资源点击获取
返回列表