
FPGA正交编码器非法跳变检测把16种状态转移和采样边界测清楚观察正交编码器时假设连续两次有效采样读到00→11。A、B都变了但中间经过01还是10已经无法从这两个值判断。这里做一个小模块比较相邻两次有效采样发现双比特变化时输出illegal。重点放在三个容易写错的地方复位后第一次采样怎么处理暂停采样后跟谁比较以及报错后下一次比较用哪个基准。它适合放在编码器采样链路后面给调试或上层控制提供异常事件。接口不包含位置计数和方向输出。1. 先看四个状态不急着写逻辑本文把状态写成AB{A,B}。两个方向的正常状态序列分别为00 → 01 → 11 → 10 → 00 00 → 10 → 11 → 01 → 00每次只改变一位。停住时前后状态也可以完全相同。图中绿色连线可以双向走代表单比特变化红色对角线代表两位都变化。这是状态关系示意图不是示波器截图。Microchip的QEI参考手册列出了完整转移表保持状态不计数相邻状态对应一个方向的计数双比特变化属于无效状态变化。Microchip QEI参考手册Table 1-1把“是否非法”单独拿出来只有下面这张表上次采样 \ 本次采样0001101100保持合法合法非法01合法保持非法合法10合法非法保持合法11非法合法合法保持16种组合中4种保持、8种单比特变化、4种双比特变化。方向突然改变仍然可以是合法单步例如00→01→00本模块不会把它判成非法。二进制异或恰好能回答“哪几位变了”previous ^ ab 00没有变化 previous ^ ab 01只有B变化 previous ^ ab 10只有A变化 previous ^ ab 11A、B都变化判非法所以核心条件是(previous ^ ab) 2b11不能写成“只要前后不一样就报错”。2. 采样契约决定这行比较是否有意义模块名为quadrature_guard全部状态在clk上升沿更新。信号含义rst同步、高有效复位优先于采样sample_en为1时接受一个新的AB样本ab[1:0]已同步到本时钟域的AB状态illegal这一拍接受的样本是否构成双比特跳变ab已经同步是接口前提。本模块没有输入同步器也没有消抖逻辑。实际接编码器引脚时要先在顶层处理异步输入再把同步后的结果交给它。这里还有四条时序规则复位后的第一次有效采样只建立基准任何AB值都不报错sample_en0时不更新基准输入端的变化不会被记作已接受样本非法样本也会更新基准下一次仍与最近一次已接受样本比较没有新的非法事件时illegal下一拍清零连续发生非法事件时它可以连续为高最后一条影响错误统计。如果上层想统计非法样本数量应在每个时钟检查illegal不能只检测它的上升沿。连续两个非法样本可能对应一段宽度为两拍的高电平。3. 完整RTL将以下代码保存为rtl.sv。代码使用SystemVerilog文件扩展名和编译选项都要对应。宏MUT_ANY_CHANGE用来故意注入一个错误后面检查testbench能否抓到它。正常编译不定义这个宏。timescale 1ns/1ps // ab must already be synchronous; sample_en marks an accepted sample. module quadrature_guard(input logic clk, rst, sample_en, input logic [1:0] ab, output logic illegal); logic [1:0] previous; logic have_previous; always_ff (posedge clk) begin if (rst) begin previous 0; have_previous 0; illegal 0; end else begin illegal 0; if (sample_en) begin ifdef MUT_ANY_CHANGE illegal have_previous ((previous ^ ab) ! 0); else illegal have_previous ((previous ^ ab) 2b11); endif previous ab; have_previous 1; end end end endmodulehave_previous很重要。虽然复位把previous写成了0但这个0只是寄存器复位值还不是编码器已经提供过的样本。没有这个标志上电时AB恰好为11就会被误判。illegal 0是每拍默认值。同一个时序块内接受样本时后面的非阻塞赋值会覆盖这个默认安排所以该拍事件可以置1没有接受样本则自然清零。previous ab放在是否非法的判断之后但它不受非法结果限制。比较使用的是更新前的previous然后把本次样本存下来作为下次基准。4. 用一段时序说明暂停和报错后的行为下面是按接口契约展开的时序示意。表中“基准”和输出均指该上升沿更新后的值。时钟边沿sample_enab比较对象illegal更新后基准t0100首个样本只建基准000t1001不接受样本000t211100与11111t310011与00100t410100与01001t5010不接受样本001t1输入端出现了01但采样使能为0。到了t2模块比较的仍是00与11应该报错。如果每拍都更新previous这次异常就会被吞掉。t2报错后基准变成11t3又出现00所以连续第二拍报错。到t400到01是合法单步错误输出清零。更新基准只是继续跟踪后面的观测序列。已经丢失的运动路径不会因此补回来。若上层维护位置计数发生非法事件后如何标记位置可信度、是否需要重新找零应由上层控制策略决定。TI的eQEP把QEPA、QEPB同时变化列为相位错误事件。TI eQEP用户指南QFLG的PHE字段 但对这个离散采样模块报错的准确含义是“两次接受的样本相差两位”。它可能来自输入干扰也可能是两个正常边沿之间的状态没有被采到单凭这一位告警不能区分原因。5. 把16种组合全部跑一遍这一题的状态空间很小可以直接穷举不必只挑一条漂亮的正转序列。testbench对每一组old/nxt都先复位再送入基准和下一次采样。期望值用oldnxt3表达在取值0、1、2、3内只有0与3、1与2这四个有序组合互为双比特取反。它与RTL的异或表达式不同便于人工逐项核对。穷举之后继续测试正向一圈、反向一圈、停采样、连续非法、非法后的基准更新和复位优先级。5.1 完整testbench保存为同一目录中的tb.svtimescale 1ns/1ps module tb; logic clk0, rst1, sample_en0; logic [1:0] ab0; wire illegal; integer checks0; always #5 clk~clk; quadrature_guard dut(.*); task automatic step(input bit reset_i, en_i, input logic [1:0] value, input bit expected, input string label_i); (negedge clk); rstreset_i; sample_enen_i; abvalue; (posedge clk); #1; checks; if (illegal ! expected) $fatal(1,CHECK_FAIL QUAD %s got%b expected%b,label_i,illegal,expected); endtask initial begin // All 16 old/new pairs, including 4 holds and 4 two-bit jumps. for (int old0; old4; old) begin for (int nxt0; nxt4; nxt) begin step(1,0,0,0,reset); step(0,1,old,0,first sample); step(0,1,nxt,(oldnxt3),all transitions); step(0,0,0,0,pulse clears without sampling); end end step(1,0,0,0,reset); step(0,1,0,0,seed); step(0,1,1,0,forward 00-01); step(0,1,3,0,forward 01-11); step(0,1,2,0,forward 11-10); step(0,1,0,0,forward 10-00); step(0,1,2,0,reverse 00-10); step(0,1,3,0,reverse 10-11); step(0,1,1,0,reverse 11-01); step(0,1,0,0,reverse 01-00); step(0,0,1,0,disabled input ignored); step(0,1,3,1,compare with last accepted sample); step(0,1,0,1,consecutive illegal samples); step(0,1,1,0,baseline follows illegal sample); step(1,1,3,0,reset priority); step(0,1,2,0,new baseline after reset); $display(TEST_PASS QUAD checks%0d transitions16,checks); $finish; end initial begin #10000; $fatal(1,WATCHDOG QUAD); end endmodule激励在下降沿改变上升沿后1 ns检查输出。这样检查点落在非阻塞赋值更新之后避免testbench读到上一拍结果。!是四态比较输出里有X或Z也会失败。末尾的watchdog避免错误修改后仿真一直不结束。5.2 运行方法本次实测采用Windows PowerShell和Vivado Simulator 2022.1时钟周期10 ns进行了数字RTL仿真没有进行综合、静态时序分析、CDC或板级测试。创建一个新的空目录把上面的rtl.sv与tb.sv放进去。确保当前终端能找到xvlog、xelab、xsim然后运行xvlog -sv rtl.sv tb.sv xelab work.tb -s sim -debug typical xsim sim -runall三条是仿真器命令不是Tcl脚本。Windows可以在配置好Vivado环境的PowerShell或命令提示符中执行Linux应先加载本机Vivado的settings64.sh再在Bash中执行同样三条命令。这里不依赖PowerShell专用脚本也不需要其他HDL库。AMD UG900 2022.1命令行仿真原版实测输出中的关键行是TEST_PASS QUAD checks80 transitions16 $finish called at time : 806 nschecks80表示80次逐拍输出检查其中64次来自16组状态对各自的复位、首样本、下一样本和脉冲清零检查。剩下16次覆盖连续序列和接口边界。它不是80种独立状态转移。5.3 故意改错一次检查测试是否真的有效另建一个空目录复制同样两份源码编译时加上宏xvlog -sv -d MUT_ANY_CHANGE rtl.sv tb.sv xelab work.tb -s sim -debug typical xsim sim -runall宏会把判据改成“任何变化都非法”。这次实际输出为Fatal: CHECK_FAIL QUAD all transitions got1 expected0 Time: 76 ns失败发生在合法的00到01转移处说明testbench确实区分了“输入变化”和“非法变化”。它先成功编译、展开再由功能检查报错。这次xsim在$fatal后仍返回了进程退出码0。因此自动化脚本应同时检查TEST_PASS、CHECK_FAIL、Fatal和watchdog不能只根据退出码判定通过。6. 接到板子前再核对三个边界6.1 同步和消抖不是本模块的功能异步引脚不能直接作为同步逻辑的数据前提。AMD的单比特同步宏说明了同步链的采样要求和可配置级数。AMD XPM_CDC_SINGLE实际同步电路要结合器件CDC约束和输入速率设计。两级寄存器的作用是降低亚稳态传播风险不能保证任意窄脉冲都被捕获也不能让采样不足自动消失。机械旋钮还可能需要滤波。之前的按键消抖文章讨论了单路电平保持一段时间才确认的办法。用于A/B两路时要重新核算最短状态保持时间不能直接照搬长延时。滤波窗口太长会吞掉正常边沿两路不同的延迟还可能改变观察到的先后次序。6.2 采样频率要按边沿数核算假设编码器每转产生N个完整AB周期转速为R rpm那么理想情况下每秒状态变化次数为f_transition 4 × N × R / 60例如N1000、R3000得到每秒200000次状态变化理想相邻状态间隔5 μs。如果每10 μs才接受一次样本即使系统主时钟很快也已经可能跳过中间状态。这里真正相关的是sample_en的接受节奏。上面的平均间隔还没有包含占空比误差、相位误差和抖动工程上应检查最短保持时间并保留余量不能只让两个平均频率刚好相等。6.3illegal0不代表没有丢步考虑从00开始、期间跨过多次真实状态变化跨过2次00 → 01 → 11 采到00与11会报非法 跨过3次00 → 01 → 11 → 10 采到00与10看起来像反向单步 跨过4次00 → 01 → 11 → 10 → 00 采到00与00看起来没有移动本模块能抓到第一种观测但抓不到后两种。排查时应同时看原始A/B、同步后的A/B、sample_en和illegal先确认有没有漏采样再检查状态判断。把这些边界写清楚非法检测器就能提供明确的信息哪一拍接受了双比特变化。测试也围绕这个契约展开首样本、停采样和连续异常都能在同一组源码里复现。