我在系统设计上犯过的14个错

本文回顾了作者近八年来在系统设计中犯过的重大错误,包括服务框架设计缺陷、技术选型失误、通信协议设计不当等问题,并从中总结出架构师应具备的素质。
797de3b81d57d972fc219f2ce141ea22ce4d4a6b 

在上篇《架构师画像》的文章中提到了自己在系统设计上犯过的一些错,觉得还挺有意义的,这篇文章就来回顾下自己近八年来所做的一些系统设计,看看犯的一些比较大的血淋淋的错误(很多都是推倒重来),这八年来主要做了三个基础技术产品,三个横跨三年的大的技术项目(其中有两个还在进行中),发现大的错误基本集中在前面几年,从这个点看起来能比较自豪的说在最近的几年在系统设计的掌控上确实比以前成熟了很多。

第1个错
在设计服务框架时,我期望服务框架对使用者完全不侵入,于是做了一个在外部放一个.xml文件来描述spring里的哪些bean发布为服务的设计,这个版本发布后,第一个小白鼠的用户勉强在用,但觉得用的很别扭,不过还是忍着用下去了,到了发布的时候,发现出现了两个问题,一是这个xml文件研发也不知道放哪好,所以到了发布的时候都不知道去哪拿这个xml文件。
这个设计的关键错误就在于在设计时没考虑过这个设计方式对研发阶段、运维阶段的影响,后来纠正这个错误的方法是去掉了这个xml文件,改为写了一个Spring FactoryBean,用户在spring的bean配置文件中配置下就可以。
因此对于一个架构师来说,设计时在全面性上要充分考虑。

第2个错
服务框架在核心应用上线时,出现了前端web应用负载高,处理线程数不够用的现象,当时处理这个故障的方式是回滚了服务框架的上线,这个故障排查了比较长的时间后,查到的原因是服务框架用的JBoss Remoting在通信时默认时间是60s,导致一些处理速度慢的请求占据了前端web应用的处理线程池。
上面这里故障的原因简单来说是分布式调用中超时时间太长的问题,但更深层次来思考,问题是犯在了设计服务框架时的技术选型,在选择JBoss-Remoting时没有充分的掌握它的运行细节,这个设计的错误导致的是后来决定放弃JBoss-Remoting,改为基于Mina重写了服务框架的通信部分,这里造成了服务框架的可用版本发布推迟了两个多月。
因此对于一个架构师来说,在技术选型上对技术细节是要有很强的掌控力的。

第3个错
在服务框架大概演进到第4个版本时,通信协议上需要做一些改造,突然发现一个问题是以前的通信协议上是没有版本号的,于是悲催的只能在代码上做一个很龌蹉的处理来判断是新版本还是老版本。
这个设计的错误非常明显,这个其实只要在最早设计通信协议时参考下现有的很多的通信协议就可以避免了,因此这个错误纠正也非常简单,就是参考一些经典的协议重新设计了下。
因此对于一个架构师来说,知识面的广是非常重要的,或者是在设计时对未来有一定的考虑也是非常重要的。

说到协议,就顺带说下,当时在设计通信协议和选择序列化/反序列化上没充分考虑到将来多语言的问题,导致了后来在多语言场景非常的被动,这也是由于设计时前瞻性的缺失,所谓的前瞻性不是说一定要一开始就把未来可能会出现的问题就解掉,而是应该留下不需要整个改造就可以解掉的方法,这点对于架构师来说也是非常重要的。

第4个错
在服务框架切换为Mina的版本上线后,发布服务的应用重启时出现一个问题,就是发现重启后集群中的机器负载严重不均,排查发现是由于这个版本采用是服务的调用方会通过硬件负载均衡去建立到服务发布方的连接,而且是单个的长连接,由于是通过硬件负载均衡建连,意味着服务调用方其实看到的都是同一个地址,这也就导致了当服务发布方重启时,服务调用方重连就会集中的连到存活的机器上,连接还是长连,因此就导致了负载的不均衡现象。
这个设计的错误主要在于没有考虑生产环境中走硬件负载均衡后,这种单个长连接方式带来的问题,这个错误呢还真不太好纠正,当时临时用的一个方法是服务调用方的连接每发送了1w个请求后,就把连接自动断开重建,最终的解决方法是去掉了负载均衡设备这个中间点。
因此对于一个架构师来说,设计时的全面性要非常的好,我现在一般更多采用的方式是推演上线后的状况,一般来说在脑海里过一遍会比较容易考虑到这些问题。

第5个错
服务框架在做了一年多以后,某个版本中出现了一个严重bug,然后我们就希望能通知到用了这个版本的应用紧急升级,在这个时候悲催的发现一个问题是我们压根就不知道生产环境中哪些应用和机器部署了这个版本,当时只好用一个临时的扫全网机器的方法来解决。
这个问题后来纠正的方法是在服务发布和调用者在连接我们的一个点时,顺带把用的服务框架的版本号带上,于是就可以很简单的知道全网的服务框架目前在运行的版本号了。
因此对于一个架构师来说,设计时的全面性是非常重要的,推演能起到很大的帮助作用。

第6个错
服务框架这种基础类型的产品,在发布时会碰到个很大的问题,就是需要通知到使用者去发布,导致了整个发布周期会相当的长,当时做了一个决定,投入资源去实现完全动态化的发布,就是不需要重启,等到做的时候才发现这完全就是个超级大坑,最终这件事在投入两个人做了接近半年后,才终于决定放弃,而且最终来看其实升级的问题也没那么大。
这个问题最大的错误在于对细节把握不力,而且决策太慢。
因此对于一个架构师来说,技术细节的掌控非常重要,同时决策力也是非常重要的。

第7个错
服务发布方经常会碰到一个问题,就是一个服务里的某些方法是比较耗资源的,另外的一些可能是不太耗资源,但对业务非常重要的方法,有些场景下会出现由于耗资源的方法被请求的多了些导致不太耗资源的方法受影响,这种场景下如果要去拆成多个服务,会导致开发阶段还是挺痛苦的,因此服务框架这边决定提供一个按方法做七层路由的功能,服务的发布方可以在一个地方编写一个规则文件,这个规则文件允许按照方法将生产环境的机器划分为不同组,这样当服务调用方调用时就可以做到不同方法调用到不同的机器。
这个功能对有些场景来说用的很爽,但随着时间的演进和人员的更换,能维护那个文件的人越来越少了,也成为了问题。
这个功能到现在为止我自己其实觉得也是一直处于争议中,我也不知道到底是好还是不好…
因此对于一个架构师来说,设计时的全面性是非常重要的。

第8个错
服务框架在用的越来越广后,碰到了一个比较突出的问题,服务框架依赖的jar版本和应用依赖的jar版本冲突,服务框架作为一个通用技术产品,基本上没办法为了一个应用改变服务框架自己依赖的jar版本,这个问题到底怎么去解,当时思考了比较久。
可能是由于我以前OSGi这块背景的原因,在设计上我做了一个决定,引入OSGi,将服务框架的一堆jar处于一个独立的classloader,和应用本身的分开,这样就可以避免掉jar冲突的问题,在我做了引入OSGi这个决定后,团队的1个资深的同学就去做了,结果是折腾了近两个月整个匹配OSGi的maven开发环境都没完全搭好,后来我自己决定进去搞这件事,即使是我对OSGi比较熟,也折腾了差不多1个多月才把整个开发的环境,工程的结构,以及之前的代码基本迁移为OSGi结构,这件事当时折腾好上线后,效果看起来是不错的,达到了预期。
但这件事后来随着加入服务框架的新的研发人员越来越多,发现多数的新人都在学习OSGi模式的开发这件事上投入了不少的时间,就是比较难适应,所以后来有其他业务问是不是要引入OSGi的时候,我基本都会建议不要引入,主要的原因是OSGi模式对大家熟悉的开发模式、排查问题的冲击,除非是明确需要classloader隔离、动态化这两个点。
让我重新做一个决策的话,我会去掉对OSGi的引入,自己做一个简单的classloader隔离策略来解决jar版本冲突的问题,保持大家都很熟悉的开发模式。
因此对于一个架构师来说,设计时的全面性是非常重要的。

第9个错
服务框架在用的非常广了后,团队经常会被一个问题困扰和折腾,就是业务经常会碰到调用服务出错或超时的现象,这种情况通常会让服务框架这边的研发来帮助排查,这个现象之所以查起来会比较复杂,是因为服务调用通常是多层的关系,并不是简单的A–>B的问题,很多时候都会出现A–>B–>C–>D或者更多层的调用,超时或者出错都有可能是在其中某个环节,因此排查起来非常麻烦。
在这个问题越来越麻烦后,这个时候才想起在09年左右团队里有同学看过G家的一篇叫dapper的论文,并且做了一个类似的东西,只是当时上线后我们一直想不明白这东西拿来做什么,到了排查问题这个暴露的越来越严重后,终于逐渐想起这东西貌似可以对排查问题会产生很大的帮助。
到了这个阶段才开始做这件事后,碰到的主要不是技术问题,而是怎么把新版本升级上去的问题,这个折腾了挺长时间,然后上线后又发现了一个新的问题是,即使服务框架具备了Trace能力,但服务里又会调外部的例如数据库、缓存等,那些地方如果有问题也会看不到,排查起来还是麻烦,于是这件事要真正展现效果就必须让Trace完全贯穿所有系统,为了做成这件事,N个团队付出了好几年的代价。
因此对于一个架构师来说,设计时的全面性、前瞻性非常重要,例如Trace这个的重要性,如果在最初就考虑到,那么在一开始就可以留好口子埋好伏笔,后面再要做完整就不会太复杂。

第10个错
服务的发布方有些时候会碰到一个现象是,服务还没完全ready,就被调用了;还有第二个现象是服务发布方出现问题时,要保留现场排查问题,但服务又一直在被调用,这种情况下就没有办法很好的完全保留现场来慢慢排查问题了。
这两个现象会出现的原因是服务框架的设计是通过启动后和某个中心建立连接,心跳成功后其他调用方就可以调用到,心跳失败后就不会被调到,这样看起来很自动化,但事实上会导致的另外一个问题是外部控制上下线这件事的能力就很弱。
这个设计的错误主要还是在设计时考虑的不够全面。
因此对于一个架构师来说,设计时的全面性非常重要。

第11个错
在某年我和几个小伙伴决定改变当时用xen的模式,换成用一种轻量级的“虚拟机”方式来做,从而提升单机跑的应用数量的密度,在做这件事时,我们决定自己做一个轻量级的类虚拟机的方案,当时决定的做法是在一个机器上直接跑进程,然后碰到一堆的问题,例如从运维体系上来讲,希望ssh到“机器”、独立的ip、看到自己的系统指标等等,为了解决这些问题,用了N多的黑科技,搞得很悲催,更悲催的是当时觉得这个问题不多,于是用一些机器跑了这个模式,结果最后发现这里面要黑科技解决的问题实在太多了,后来突然有个小伙伴提出我们试用lxc吧,才发现我们之前用黑科技解的很多问题都没了,哎,然后就是决定切换到这个模式,结果就是线上的那堆机器重来。
这个设计的主要错误在于知识面不够广,导致做了个不正确的决定,而且推倒重来。
因此对于一个架构师来说,知识面的广非常重要,在技术选型这点上非常明显。

第12个错
还是上面这个技术产品,这个东西有一个需求是磁盘空间的限额,并且要支持磁盘空间一定程度的超卖,当时的做法是用image的方式来占磁盘空间限额,这个方式跑了一段时间觉得没什么问题,于是就更大程度的铺开了,但铺开跑了一段时间后,出现了一个问题,就是经常出现物理机磁盘空间不足的报警,而且删掉了lxc容器里的文件也还是不行,因为image方式只要占用了就会一直占着这个大小,只会扩大不会缩小。
当时对这个问题极度的头疼,只能是删掉文件后,重建image,但这个会有个要求是物理机上有足够的空间,即使有足够的空间,这个操作也是很折腾人的,因为得先停掉容器,cp文件到新创建的容器,这个如果东西多的话,还是要耗掉一定时间的。
后来觉得这个模式实在是没法玩,于是寻找新的解决方法,来满足磁盘空间限额,允许超卖的这两需求,最后我们也是折腾了比较长一段时间后终于找到了更靠谱的解决方案。
这个设计的主要错误还是在选择技术方案时没考虑清楚,对细节掌握不够,考虑的面不够全,导致了后面为了换掉image这个方案,用了极大的代价,我印象中是一堆的人熬了多次通宵来解决。
因此对于一个架构师来说,知识面的广、对技术细节的掌控和设计的全面性都非常重要。

第13个错
仍然是上面的这个技术产品,在运行的过程中,突然碰到了一个虚拟机中线程数创建太多,导致其他的虚拟机也创建不了线程的现象(不是因为物理资源不够的问题),排查发现是由于尽管lxc支持各个容器里跑相同名字的账号,但相同名字的账号的uid是相同的,而max processes是限制在UID上的,所以当一个虚拟机创建的线程数超过时,就同样影响到了其他相同账号的容器。
这个问题我觉得一定程度也可以算是设计问题,设计的时候确实由于对细节掌握的不够,考虑的不全导致忽略了这个点。
因此对于一个架构师来说,对技术细节的掌控和设计的全面性都非常重要。

第14个错
在三年前做一个非常大的项目时,项目即将到上线时间时,突然发现一个问题是,有一个关键的点遗漏掉了,只好赶紧临时讨论方案决定怎么做,这个的改动动作是非常大的,于是项目的上线时间只能推迟,我记得那个时候紧急周末加班等搞这件事,最后带着比较高的风险上了。
这个问题主要原因是在做整体设计时遗漏掉了这个关键点的考虑,当时倒不是完全忽略了这个点,而是在技术细节上判断错误,导致以为不太要做改动。
因此对于一个架构师来说,对技术细节的掌控是非常重要的,这里要注意的是,其实不代表架构师自己要完全什么都很懂,但架构师应该清楚在某个点上靠谱的人是谁。

转载自:http://hellojava.info/?p=458

作者:阿里毕玄

6.1 哪些对象需要随机化 随机时需考虑设计输入的各个方面,器件配置,环境配置,原始输入数据,封装后的输出数据,协议异常,延时,事务状态,误和违例等情况。 6.2 SV中的随机化 //带有随机变量的简单类 class Packet; rand bit [31:0] src, dst, data[8]; randc bit [7:0] kind; //src的约束 constraint c {src > 10; src < 15;} endclass Packet p; initial begin p = new(); assert (p.randmize()); else $fatal(0, "Packet::randomize failed"); transmit(p); end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 使用rand/randc修饰符修饰的变量为随机变量,表示每次随机化这个类时(调用randmize()函数),这些变量都会被随机赋一个值(满足约束条件)。randc表示周期性随机,即当所有可能值没有全部被取到前不会出现数据重复的问题。约束是一组用来确定变量的值的范围的关系表达式。约束表达式式放在括号中,而没有放在begin-end块之间,这是由于这段代码是声明性质的,而不是程序性质的。随机化函数randomize()在遇到约束方面的问题时返回0。 不能在类的构造函数里随机化对象,因为在随机化前可能需要打开或者关闭约束,改变权重,添加新的约束。**构造函数是用来初始化对象的变量,不能调用randmize()函数。**类中的所有变量都应该设置为随机的(random)和公有的(public),这样测试平台才能最大程度地控制DUT。 randomize()函数为类里所有的rand和randc类型的随机变量赋一个随机值,并保证不违背所有有效的约束。当你的代码中有矛盾的约束时,随机化过程会失败,所以一定要检测随机化的结果(可以使用立即断言)。如果不检查,变量可能会赋未知的值,导致仿真的失败。 **约束表达式的求解是由SV的约束求解器完成的。**求解器能够选择满足约束的值,这个值由SV的PRNG(伪随机数生成器,即随机种子确定后生成随机数可通过公式计算出来,产生的每个随机数概率相同)从一个初始值(random seed)产生。如果SV仿真器每次使用相同的初始值,相同的测试平台,那么仿真的结果也是相同的。各种仿真器的求解器都是不同的,因此使用不同的仿真器时受约束的随机测试得到的结果也有可能不同,甚至同一个仿真器的不同版本的仿真记过也不相同。 6.2.2 哪些数据可以被随机化 可以随机化整型,随机只能产生二值数据类型(0/1),尽管随机变量类型为四值类型。可以使用整数和位矢量,但是不能使用随机化字符串,或在约束中指向句柄。 6.3 约束 每个表达式里至少有一个变量必须是rand或randc类型的随机变量。 //除非age随机化恰巧在允许的范围内,否则会报 class Child; bit [31:0] age; constraint c_teenager { age > 12; age < 20;} endclass AI生成项目 java 运行 1 2 3 4 5 6 在一个表达式中最多只能出现一个关系操作符(<,<=,==,>=,>)。 class order; rand bit [7:0] lo, med, hi; constraint bad {lo < med < hi; } //误约束 constraint good {lo < med; //正确约束 med < hi;} endclass AI生成项目 java 运行 1 2 3 4 5 6 此时求解器会按照从左至右的顺序分隔成两个关系吧表达式:((lo < med) < hi)。lo < med的结果是0或1,然后判断hi大于0或1。 因为约束块里只能包含表达式,所以不能在约束块里进行赋值(=)。应该使用关系运算符为随机变量赋一个固定的值,如len == 42; 6.3.1 权重分布 关键字dist,值或者权重可以是常数或者变量。值可以是一个值或值的范围,例如[lo:hi]。权重不用百分比表示,权重的和也不必是100。权重符号有:=(表示值范围内的每一个值的权重都是相同的),:/(表示权重要均分到范围内每一个值)。要产生带权重的分布,正确的做法是使用dist操作符。 rand int src, dst; constraint c_dist { //0,1,2,3的权重依次为40/220,60/220,60/220,60/220 src dist {0:=40, [1:3]:=60}; //0,1,2,3的权重依次为40/100,20/100,20/100,20/100 dst dist {0:/40, [1:3]:/60}; } AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 6.3.2 集合set成员和inside运算符 使用inside运算符产生一个值的集合。如果不存在其他约束,SV在值的集合里取随机值,各个值的选取机会是相等的。在集合里可以使用变量。 rand int c; int lo, hi; constraint c_range { c inside {[lo:hi]}; //lo <= c 且 c <= hi } AI生成项目 java 运行 1 2 3 4 5 可选的范围由lo和hi决定,可以采用这种方法使约束参数化,实现不修改约束,测试平台就可以改变激励发生器的行为。如果lo>hi,就会产生一个空集合,最终导致约束误。可以使用$来代表取值范围里的最小值和最大值。 rand bit [6:0] b; //0 <= b <= 127 rand bit [5:0] e; //0 <= e <= 63 constraint c_range { b inside {[$:4], [20:$]}; //0 <= b <= 4 || 20 <= b <= 127 e inside {[$:4], [20:$]}; //0 <= e <= 4 || 20 <= b <= 63 } //如果想选择一个集合之外的值,只需要用取反操作符!对约束取反 constraint c_range { ! (c inside {[lo:hi]}); // c < lo || c > hi } AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 6.3.3 在集合中使用数组 rand int f; int fib[5] = '{1,2,3,5,8}; constraint c_fibonacci { f inside fib; } AI生成项目 java 运行 1 2 3 4 5 集合中的每一个值取出的概率都是相同的,即使值在数组中出现多次。 class Weighted; rand int val; int array[] = '{1,2,3,3,5,8,8,8,8,8}; endclass AI生成项目 shell 1 2 3 4 6.3.4 条件约束 SV支持两种关系操作符:->和if-else。 6.3.5 约束的双向性(并行性) SV中的约束是双向的,约束块不像自上而下执行的程序性代码,他们是声明性的代码,是并行的,所有的约束表达式同时有效。 6.3.6 替换数学运算符来提高效率 约束求解器可以有效的处理简单的数学运算,如加,减,位移和移位。约束求解器对于32位数值的乘法,除法和取模运算的运算量是非常大的。SV中任何没有显式声明位宽的常数都是作为32位数值对待的。可以利用移位运算代替除法和取模运算,以提高约束求解器的效率。 6.4 解的概率 SV并不保证随机约束求解器能给出准确的解,但可以干预解的概率分布。 6.4.1 没有约束的类 class Unconstrained; rand bit x; rand bit [1:0] y; endclass AI生成项目 java 运行 1 2 3 4 经过千次随机化,x,y的每个值的取值概率是相同的。 6.4.2 关系操作 class Imp1; rand bit x; rand bit [1:0] y; constraint c_xy { (x==0) -> y==0; } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 x=0,y=0的概率为1/2,x=1,y=0,x=1,y=1,x=1,y=2,x=1,y=3的概率各为1/8。此求解器将x=0时,y为0,1,2,3的四种情况都归到了x=0,y=0的情况中。 6.4.3 关系操作和双向约束 class Imp2; rand bit x; rand bit [1:0] y; constraint c_xy { y > 0; (x==0) -> y==0; } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 因约束是双向的,所以x的值为1,y值为1,2,3,每种组合的概率各为1/3。 6.4.4 Solve…Before 使用solve...before可以改变值出现的概率,除非对某些值出现的概率不满意,否则不要使用solve...before块,过度使用slove...before会降低计算的速度,也会使你的约束让人难以理解。(不推荐使用) 6.5 控制多个约束块 一个类可以包含多个约束块,可以把不同的约束块用于不同的测试,例如一种约束用来限制数据的长度,用于产生小的事务,另一种约束用来产生大的事务。在运行期间,可以使用内建的constraint_mode()函数打开或者关闭约束,可以用handle.constraint_mode()控制一个约束块,用handle.constraint_mode()控制对象的所有约束。 class Packet; rand int length; constraint c_short {length inside {[1:32]}; } constraint c_long {length inside {[1000:1023]}; } endclass Packet P; initial begin p = new(); //关闭c_short,产生长事务 p.c_short.constraint_mode(0); assert (p.randomize()); transmit(p); //打开c_short,产生短事务 p.constraint_mode(0); p.c_short.constraint_mode(1); assert (p.randomize()); transmit(p); end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 6.6 有效性约束 设置多个约束以保证随机激励的正确性是一种很好的随机化技术,它也称为“有效性约束”。 class Transcation rand enum {BYTE, WORD, LWRD, QWRD} length; rand enum {READ, WRITE, RMW, INTR} opc; constraint valid_RMW_LWRD { (opc == RMW) -> length == LWRD; } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 6.7 内嵌约束 约束越来越多时,他们会互相作用,最终产生难以预测的结果。SV允许使用randomize() with来增加额外的约束,这和在类里增加约束是等效的。 class Transaction; rand bit [31:0] addr, data; constraint c1 { addr inside{[0:100], [1000:2000]}; } endclass Transaction t; initial begin t = new(); assert(t.randomize() with {addr >= 50; addr <= 1500; data < 10;}); driveBus(t); arrser(t.randomize() with {addr == 2000; data > 10;}); driveBus(t); end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 此代码和在现有的约束上增加额外的约束是等效的。如果约束之间存在冲突,可以使用constraint_mode()函数禁止冲突的约束。**在with{}语句中,SV使用了类作用域,使用了addr而不是t.addr。**在使用randmize() with语句时常误是使用()包括内嵌的约束,而没有使用{}。约束块内应该使用{},所以内嵌约束也应该使用{},{}用于声明性的代码。 6.8 pre_randomize和post_randomize函数 有时需要在调用randmize()函数之前或之后立即执行一些操作,如在随机化之前设置类里的非随机变量(如上下限,权重),或者随机化之后需要计算随机数据的误差校正位。SV可以使用两个特殊的void类型的pre_randomize和post_randomize函数来完成这些功能。 6.9 随机数函数 下面是一些常用的随机数函数: $random()–平均分布,返回32位有符号随机数 $urandom()–平均分布,返回32位无符号随机数 $urandom_range(low,upper)–发挥[low, upper]范围(包括上下限)内的无符号数 $dist_exponential()–指数衰落 $dist_uniform()–平均分布 a = $urandom_range(3,10); //值的范围是3~10 a = $urandom_range(10,3); //值的范围是3~10 b = $urandom_range(5); //值的范围是0~5 AI生成项目 java 运行 1 2 3 6.10 约束的技巧和技术 6.10.1 使用变量的约束 //可以通过改变max_size的值来改变约束上限 class bounds; rand int size; int max_size = 100; constraint c_size { size inside {[1:max_size]}; } endclass //带有权重变量的dist约束 typedef enum {READ8, READ16, READ32} read_e; class ReadCommands; rand read_e read_cmd; int read8_wt = 1, read16_wt = 1, read32_wt = 1; constraint c_read { read_cmd dist {READ8 := read8_wt, READ16 := read16_wt, READ32 := read32_wt }; } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 6.10.2 使用非随机值 如果用一套约束在随机化的过程中已经产生了几乎所有想要的激励向量,但是还缺少几种激励向量,可以采用先调用randomize函数,然后再把随机变量的值设置为固定的期望值的方法来解决。 class Packet; rand bit [7:0] length, payload[]; constraint c_valid { length > 0; payload.size == length; } endclass Packet p; initial begin p = new(); assert(p.randomize()); p.length.rand_mode(0); p.length = 42; assert (p.randomize()); end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 6.10.3 用约束检查值的有效性 在随机化一个对象并改变它的变量的值后,可以通过检查值是否遵守约束来检查对象是否仍然有效,在调用handle.randomize(null)函数时,SV会把所有的变量当作非随机变量,仅仅检查这些变量是否满足约束条件。 6.10.4 随机化个别变量 可以在调用randomize函数只传递变量的一个子集,这样就只会随机化类里的几个变量。只有参数列表里的变量才会被随机化,其他变量会被当作状态变量而不会被随机化。 class Rising; byte low; rand byte med, hi; constraint up { low < med; med < hi; } endclass initial begin Rising r; r = new(); r.randomize(); //随机化med,hi,但不改变low r.randomize(med); //随机化med r.randomize(low); //随机化low end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 6.10.5 打开或关闭约束 可以使用constraint_mode打开或关闭约束 class Instruction; typedef enum {NOP, HALT, CLR, NOT} opcode_e; rand opcode_e opcode; ... constraint c_no_oprands { opcode == NOP || opcode == HALT; } constraint c_one_operand { opcode == CLR || opcode == NOT; } endclass Instruction instr; initial begin instr = new(); instr.constraint_mode(0); //关闭所有约束 instr.c_no_operands.constraint_mode(1); //打开c_no_operands约束 assert (instr.randomize()); instr.constraint_mode(0); //关闭所有约束 instr.c_one_operand.constraint_mode(1); //打开c_one_operands约束 assert (instr.randomize()); end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 6.10.6 使用内嵌约束 如前问所述,SV可以使用randmize() with{}内嵌约束来改变约束。 6.10.7 外部约束 函数的函数体可以在函数的外部定义,同样,约束的约束体也可以在类的外部定义,可以在一个文件里定义一个类,这个类只有一个空的约束,然后在每个不同的测试里定义这个约束的不同版本以产生不同的激励。 class Packet; rand bit [7:0] length; rand bit [7:0] payload[]; constraint c_valid { length > 0; payload.size() == length; } endclass //类外部定义约束 test.sv文件 program automatinc test; include "packet.sv" constraint Packet::c_external {length == 1;} ... endprogram AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 外部约束和内嵌约束相比具有很多优点,外部约束可以放在另一个文件里,从而在不同的测试里可以复用外部约束。外部约束对类的所有实例都起作用,而内嵌约束仅仅影响一次randomize()调用。需要注意,外部约束只能增加约束,而不能改变已有的约束。和内嵌约束一样,因为外部约束可能分布在多个文件里,所以可能导致潜在的问题。 6.10.8 扩展类 通过拓展类(继承),可以在测试平台中使用一个已有的类,然后切换到增加了约束,子程序和变量的拓展类。 6.11 随机化的常见误 除非必要,不要在随机约束里使用有符号类型。 class SignedVars; rand byte pkt1_len, pk2_len; constraint total_len { pkt1_len+pk2_len == 64; } endclass //为了避免得到负的包长这样无意义的值,应该使用无符号随机变量 class Vars32; rand bit [7:0] pkt_1_len, pk2_len; //无符号类型 constraint total_len { pkt1_len + pk2_len == 9'd64; } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 6.11.1 提高求解器性能的技巧 避免使用复杂的运算,例如除法,乘法和取模。 如果需要除以或乘以2的幂次方,使用右移或者左移操作。 如果需要进行这些操作,使用宽度小于32位的变量可以得到更高的运算性能。 6.12 迭代和数组约束 数组的大小可以用size()函数进行约束,此函数可以约束动态数组和队列的元素个数。 //使用inside约束可以设置数组大小的上限和下限 class dyn_size; rand logic [31:0] d[]; constraint d_size {d.size() inside {[1:10]}; } endclass AI生成项目 java 运行 1 2 3 4 5 6.12.1 元素的和 //可以用sum()函数约束随机数组只有四个位有效 parameter MAX_TRANSFER_LEN = 10; class StrobePat; rand bit strobe[MAX_TRANSFER_LEN]; constraint c_set_four { strobe.sum() == 4'h4} endclass AI生成项目 java 运行 1 2 3 4 5 6 7 6.12.2 对每个元素进行约束 SV中可以用foreach对数组的每一个元素进行约束,和直接写出对固定大小的数组的每一个元素的约束相比,使用foreach要更简洁。 class goo_sum5; rand unit len[]; constraint c_len { foreach(len[i]) len[i] inside {[1:255]}; len.sum < 1024; len.size() inside {[1:8]}; } endclass //使用foreach产生递增的数组元素的值 class Ascend; rand unit d[10]; constraint c { foreach (d[i]) if(i>0) d[i]>d[i-1]; //i=0时i-1=-1??? } endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 6.12.3 产生具有唯一元素值的数组 class UniqueSlow; rand bit [7:0] ua[64]; constraint c { foreach (ua[i]) foreach (ua[j]) if(i!=j) ua[i] != ua[j]; } endclass //更好的办法是使用randc辅助类产生唯一的元素值 class randc8; randc bit [7:0] val; endclass class LittleUniqueArray; bit [7:0] ua [64]; function void pre_randomize; randc8 rc8; rc8 = new(); foreach(ua[i]) begin assert(rc8.randomize()); ua[i] = rc8.val; end endfunction endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 6.12.4 随机化句柄数组 如果需要产生多个随机对象,那么你可能需要建立随机句柄数组。和整数数组不同,你需要在随机化前分配所有的元素,因为随机求解器不会创建对象。 parameter MAX_SIZE = 10; class RandStuff; rand int value; endclass class RandArray; array = new [MAX_SIZE]; constraint c {array.sise() inside {[1:MAX_SIZE]}; } function new(); array = new [MAX_SIZE]; foreach (array[i]) array[i] = new(); endfunction RandArray ra; initial begin ra = new(); assert (ra.randomize()); foreach (ra.array[i]) $display(ra.array[i].value); end endclass AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 6.13 产生原子激励和场景 产生事务序列的一个方法是SV的randsequence结构。 initial begin for (int i = 0; i < 15; i++) begin randsequence(stream) stream : cfg_read := 1 | io_read := 2 | mem_read := 5; cfg_read : {cfg_read_task;} | {cfg_read_task} cfg_read; mem_read : {meme_read_task;} | {mem_read_task;} mem_read; io_read : {io_read_task;} | {io_read_task;} io_read; endsequence end end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 随机sequence序列会从三种操作中选取一种。 6.14 随机控制 6.14.1 使用randcase和$urandom_range的随机控制 initial begin int len; randcase 1: len = $urandom_range(0, 2); 8: len = $urandom_range(3, 5); 1: len = $urandom_range(6, 7); endcase end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 使用randcase的代码会比随机约束的代码更难修改和重载。修改随机代码结果的位移方法是修改代码或使用权重变量。 6.14.2 可以使用randcase建立决策树 initial begin //一层决策 randcase one_write_wt: do_one_write(); one_read_wt: do_one_read(); seq_write_wt: do_seq_write(); seq_read_wt: do_seq_read(); endcase //二层决策 task do_one_write; randcase mem_write_wt: do_mem_write(); io_write_wt: do_io_write(); cfg_write_wt: do_cfg_write(); endcase endtask end AI生成项目 java 运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 6.15 随机数发生器 6.15.1 伪随机数发生器 V使用一种简单的PRNG(伪随机数发生器),通过$random函数访问,这个发生器有一个内部状态,可以通过$random的种子来设置。下面是一个简单的PRNG,它并不是SV使用的PRNG,这个PRNG有一个32位的内部状态,要计算下一个随机值,先计算出状态的64位平方值,取中间的32位数值,然后加上原来的32位数值。 reg [31:0] state = 32'h12345678; function logic [31:0] my_random; logic [63:0] s64; s64 = state * state; state = (s64>>16) + state; endfunction AI生成项目 java 运行 1 2 3 4 5 6 6.15.2 随机稳定性,多个随机发生器 V在整个仿真过程中使用一个PRNG,但如果SV仍然使用这种方案,测试平台通常会有几个激励发生器同时运行,为被测设计产生数据,如果两个码流共享一个PRNG,他们获得的都是随机数的一个子集。由于是从一个PRNG获取随机数据,所以改变其中一个类的随机数值个数会影响到另一个类获取的数值。在SV中,每个对象和线程都有一个独立的PRNG,改变一个对象不会影响其他对象获得的随机数。 6.15.3 随机稳定性和层次化种子 SV的每个对象都有自己的PRNG和独立的种子。当启动一个新的对象或线程时,**子PRNG的种子由父PRNG产生,**所以在仿真开始时的一个种子可以产生多个随机激励流,他们之间又是相互独立的。 6.16 随机器件配置 测试DUT的一个重要工作就是测试DUT内部设置和环绕DUT的系统的配置。如上所述,测试应该随机化环境,这样才能保证尽可能测试足够多的模式。
最新发布
07-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值