逻辑简化,说到底就是去除冗余逻辑,冗余逻辑主要分为两种,一种是由于设计人员自身逻辑不够简洁带来的额外开销,另一种则是由于设计场景的特殊性导致一些通用性逻辑变得冗余。1
此类逻辑冗余,通俗来讲就是脱裤子放屁。为了达到某些目的,明明可以一步到位,却绕了很大一个圈子浪费了很多资源才达成。数字电路是一个人的逻辑思维的具象化,日常生活中我们的思考有时候也很容易出现这样的问题,把一些简单的问题思考的很复杂。这样的逻辑冗余,有的时候一眼可以看出来,有的时候如果已经陷入了思维定式则很难被发现,需要有经验的工程师帮忙review方案和代码。经常在办公室听到有经验的老工程师检视新员工的代码说道:“这东西本质上不就是xxxxx吗?为啥要搞这么一大堆没用的东西,直接xxxxx不就可以了吗?“。自己尝试分析了一下,容易造成冗余逻辑的原因有两点。一是设计方案的时候遇到某些曾经做过的场景,直接套用原有的方案,或者自己熟悉的方案。比如说到跨时钟域处理,二话不说上来开始写异步FIFO,也不管具体的场景和约束,当然最后也能实现目的,但是逻辑和资源的开销有很多无谓的浪费。二是能力水平有限,看不到某种设计思路的本质,浮于表面的绕圈圈,这种情况只能靠不断学习吸取优秀设计的经验,逐渐成长。
芯片的设计是应用于具体的业务场景的。只要有具体的场景,就有激励和约束。所有的可能激励和约束一同构成了设计的电路能力的上限和下限,做多了没用,做少了不行。当你的目的是追求一些可拓展性,有的时候可能需要让设计更加通用,符合一些目前没发生但未来的应用可能发生的场景。如果你的目的是追求当前场景应用下的极致PPA,思考如何将电路能力做的不多不少“刚刚好“,是非常有必要的一种优化方向。而在可拓展性的基础上又尽力将电路做的”刚刚好“,则是所有优秀设计的共性。那么如何最大限度地利用不同场景激励和约束来简化设计呢?
1. 清楚明确实现中的约束和激励
根据不同的设计场景,不同的功能模块都有不同的约束和激励。有的是算法本身导致的,有的是协议规定的,有的是上下游能力导致的。时刻对此保持敏感,在方案设计上充分体现。举个例子,下游对上游BIU模块的读请求有约束,不能发出burst_len=3的操作,这虽然在一定程度上约束了上游发出请求的通用性,不能做一些通用性电路,可能会需要一些特殊的处理。但在另一方面也减少了上游下发场景的个数,在特殊处理中可以一定程度上利用此特点简化电路。另外一个例子,我们在很多算法中会进行加减计算,而加减数的范围很可能是有范围约束甚至是组合约束的,这种情况下使用枚举法之类的处理而非通用性加减计算,可以很大程度帮助简化逻辑。
2. 方案设计时遵从满足“大部分场景“为主
作为功能模块来说,其运行的业务分为大部分场景和偶发场景,在设计时对其区分处理,避免将偶发场景和大部分场景统一处理,可以在某些时候简化整个方案的实现难度。一个最常见的例子是memory ECC校验,检测出ECC错误之后对于电路状态的恢复暂停等操作。我们知道发生ECC错误是极小概率的事件,而对于电路状态的恢复暂停等操作除了ECC错误,还有一些比较常见的别的场景。这个时候如果将ECC这个场景和别的常见场景放到一起处理,很可能会因为ECC检测的长时延(memory读数据时延加上ECC逻辑)而导致很多原本不会有时序风险的路径也面临时序风险,为了解决这些时序风险,则需要对原有的电路做特殊处理,带来了不必要的麻烦。而如果直接将ECC通路单独拿出做特殊处理,以一些性能为代价,因为其不常发生,对于总体性能的影响也可以忽略,则可以避免处理这个特殊场景对电路复杂度带来的负面影响。
另外一个例子,某个数据池收集数据,数据包99%的时候是按照ID顺序逐步递增发送过来的,只有1%的时刻会有小规模乱序的情况。那么在制定收集策略的时候,可以将场景设定为完全乱序,这样可以满足100%场景的情况,也可以将场景设定为顺序,对乱序的部分则进行检测单独处理,在很多时候,后者所需的逻辑会更加简单。因为直观上来看,完全乱序需要处理更多的组合可能性,而这些可能性很可能不会发生或者极少发生,顺序则意味着规则简单,利用顺序本身的约束简化逻辑,避免用99%的开销去完成1%的特殊场景。
今天就到这里,欢迎大家继续关注我们后续文章。