ReedSolomon编解码数字设计回顾
[toc]
最近完成了一个Reed-Solomon编解码模块的设计和验证. 趁此机会, 回顾总结其设计过程.
功能分析
Reed-Solomon编解码模块的功能: 对输入信息块添加冗余之后再传输, 经过信道之后, 如果遇到少量的错误,可以通过数学算法计算进行错误纠正.
输入信息块是固定长度的字节流, 每一个字节称为一个symbol, 每个symbol的值代表的是GF(256)中的一个数, symbol之间的运算就是Galois Field中的运算.
编码算法比较简单, 通过输入的symbol, 计算出4个冗余的symbol.将此4个冗余的symbol和输入symbol一起合并传输. 可以这样理解, 这些冗余symbol就是对输入所有symbol的一个数值校验. 类似做奇偶校验一样.
解码算法相对复杂, 对于简单的ReedSolomon, 可以分两步完成:
- 计算错误症状Syndrome. 通过syndrome的值可以得到一个方程组.
- 迭代解方程组. 通过遍历所有的可能数值来解方程组.
设计步骤分析
纠错算法模块是以数学计算为核心的数字模块, 他的设计验证方式和以控制时序为核心的数字模块设计方式不同. 我的设计步骤如下:
- 学习ReedSolomin算法, 查资料, 理解编解码的工作原理和算法步骤. 因为之前有一定的抽象代数基础, 所以费时不太多, 大概费时2~3天.
- 用数学编程软件Gap建模RS-ECC编解码过程, Gap自带Galois Field运算支持, 因此实现基于域运算的算法非常简洁. 代码长度150行左右即可. 编程, 调试, 加debug, 大概费时1~2天.步骤1和2有部分时间重合, 总共时间不超过3天.
- 用python语言重新实现Gap的数学模型, 需要对Galois Field的运算做了细节的实现. 利用了Github上的开源代码, 在开源代码的基础上做了扩展和修缮. 开源提供了GF(256)的基础运算, 自己补充了运算之后, 又写了多项式运算扩展类, 扩展类的代码不到80行. 在多项式扩展类的基础上, 对RS-ECC做编解码建模, 代码长度不到150行.可以随机产生激励来做算法的验证, 并作为将来RTL的行为级参考. 编码, 调试, corner测试, 费时1周.
- 实现编码算法, 用python算法模型产生激励和验证pattern. 由于编码算法等价于一个GF域的多项式除法, 硬件实现比较简单, 1-2天即可完成RTL编码加基础验证.
- 解码算法相对较复杂. 分析解码算法实现的硬件架构, 主要包括如下:
- 计算动作的分步细化, 分析所需要的硬件资源.1~2天
- 关键信号分析, 计算的过程中需要哪些关键数据, 减少信息冗余.1-2天
- 状态机设计, 根据硬件资源和关键信号分析结果, 设计紧凑的状态机, 来回迭代.1-2天
- 在状态机基础上, 修改python的行为级实现, 增加状态机硬件级别的算法建模, 将解码的过程通过状态机的计算步骤来实现. 目的是给出RTL的实现参考, 同时也是RTL实现的行为级model. 3~4天
- 解码分析的过程, 除了python建模之外, 还有框架图, timing分析图.
- 解码算法的RTL编码以及验证
- 参考python的状态机实现model, 分析和优化register资源, 尽量节省register的使用.
- 开始RTL编码, 1~2天时间.
- 验证RTL: 由于有了python的状态机级别的建模. 加上很详细的timing和register分配资源图, 可以先对着python状态机和timing/register资源图, 对RTL做几轮人工检查. 在人工检查阶段, 发现了不少错误. 耗时0.5~1天. 代码对照设计文档检查费时较少, 只需要细心认真即可. 这些bug通过仿真来检查, 需要耗费更多的时间. 所以设计思路清晰, 推敲严谨, 看似耗费了不少时间, 但是实际上是为后续的验证节省了很多时间. 并且也会增加自己设计能力的自信心.
- 写testcase对RTL验证, 仿真有错误, debug发现有一个信号定义的宽度错误, 修正之后, basic case仿真竟然OK了.
- 随机regression测试, 找到1个Corner Case,没有分析很透彻. 因为硬件实现上和python描述的算法还是有差异, 有些差异只有在corner case才能体现出来. 需要充分的regression以及directly corner case测试才能发现. 大概耗时3~4天.
- 总结RTL的解码算法实现和验证, 分成2个子模块完成, 第一个是syndrome计算, 实现和验证相对简单, 在python模型准备好的基础上, 1天时间足够完成RTL加上unit test. 第二个模块是解方程组, 需要迭代, 涉及到的变量和计算过程相对复杂不少, 时序也复杂不少. 因此时间要多一些. 大概需要3~5天. 最后整理一个top模块, 将这两个子模块整合在一起, 然后写随机验证pattern, 这属于水到渠成的事情. 不过top模块还有一个难度, 就是如何定义top层的接口时序, 需要仔细推敲.
- 解码top层的接口时序, 需要和上下游进行讨论, 他们需要的速度, 带宽, 数据的传输方式, 是否需要hold机制, 是否需要内部缓冲来加快输入数据的连贯性. 都是考量的因素.
- 解码top层设计主要是时序考量和throughput分析, 代码量很少, 但是控制时序比较精巧, debug要借助于波形. 总耗时1~2天. 对于时序控制为主的电路, 关键在于定义重要信号, 然后画波形图, 对于每一个重要信号, 我都在笔记本上写出它们的控制条件. 然后对照典型波形进行推演, 迭代.待所有的重要信号的控制规则都写下来之后, 就可以开始设计代码了.
Bug分析
在Gap/Python建模阶段, 在RTL阶段, 都会出现Bug, 作为一个有十几年经验的人, 写代码依然会有一系列bug, 回顾这个设计, 主要的bug如下:
- python建模的时候, 对于多项式class的设计, 定义了degree, 经过运算之后如果最高次系数结果为0, 会消去最高次项. 如此继续下去, 直到最高次项不为0.
- 考虑corner case, 即次数为1的时候, 是常数多项式. 若常数为0, 则不要消去这个唯一的0.
- 做多项式除法取模的时候, 除一个5次多项式, 结果最高是4次多项式. 有可能为3次多项式,甚至更低次多项式. 在ReedSolomon的redundent计算的时候, 没有考虑余数多项式低于4次的情形, 导致所有的编码数据shift. 从而仿真错误. 只有特定的pattern才能发现这个问题. 我是通过大量随机测试才发现这个问题的.
- 通过详细的设计文档, 加上RTL写完之后, 对照设计文档从头到尾检查一次, 然后对照python model从头到尾检查一次. 这两次检查之后, 仿真结果有一半正确. 剩下的错误是由于RTL中对两个变量没有定义, 结果缺省为1bit, 导致仿真错误. 如果用lint检查, 可以很快发现问题. 另外, systemverilog仿真器对变量的定义要求比较松, 如果没有定义, 出现在assign中没有报错.
- RTL还有一个bug, 比较难发现. 不是RTL实现本身, 是算法映射到物理的时候, 某个值会超过边界. 当时分析, 超过边界用一个错误的返回值代替, 就可以解除. 结果, 在regression大批量验证的时候, 某个数据组合正好碰巧可以满足这个错误返回值, 让解码出现错误结果. 报告出一个错误的方程解. 第一次debug的时候, 将超过边界的返回值换成另外一个错误值, 这个pattern就可以pass, 当时经过数学推理, 发现其他的某些pattern仍然可以满足这个新的错误值, 从而出现错误解. 最后改用比较彻底的方式, 如果出现越界, 直接让输出为error. 这样避免了错误的解出现.
- 解码整合电路的时候, 涉及到很多时序, 因此要借助waveform来debug. 发现很重要的错误是:
- 优先级控制, 连两个控制信号同时到达的时候, 优先级设计错误.
- 模块间数据传递, 时序错开.
- 思考过这个问题, 对于这种控制密集, 时序设计比较多的电路模块, 能否不用波形就能debug? 就像计算密集型电路一样, 仅仅通过print, assertion就可以debug. 目前看起来有点困难. 可能后续要加强assertion, 从而减少对波形debug的依赖.