欢迎来到半导体与芯片的世界 - 数字电路
0 绪论最近需要开个新坑，讲讲计算机体系架构方面的东西。这方面应用最为广泛的是一个叫cache的东西。这方面内容图比较多，不想自己画了，所以图都来自《Computer Architecture : A Quantitative Approach》。这是一本体系架构方面的神书，推荐大家看一下。本文主要内容如下，基本涉及了cache的概念，工作原理，以及保持一致性的入门内容。1 为什么需要Cache1.1 为什么需要Cache我们首先从一张图来开始讲为什么需要cache.上图是CPU性能和Memory存储器访问性能的发展。我们可以看到，随着工艺和设计的演进，CPU计算性能其实发生了翻天覆地的变化，但是DRAM存储性能的发展没有那么快。所以造成了一个问题，存储限制了计算的发展。容量与速度不可兼得。如何解决这个问题呢？可以从计算访问数据的规律入手。我们随便贴段代码可以看到，由于大量循环的存在，我们访问的数据其实在内存中的位置是相近的。换句专业点的话说，我们访问的数据有局部性。我们只需要将这些数据放入一个小而快的存储中，这样就可以快速访问相关数据了。总结起来，Cache是为了给CPU提供高速存储访问，利用数据局部性而设计的小存储单元。1.2 实际系统中的Cache我们展示一下实际系统中的cache。如上图所示。整个系统的存储架构包括了CPU的寄存器，L1/L2/L3 CACHE，DRAM和硬盘。数据访问时先找寄存器，寄存器里没有找L1 Cache, L1 Cache里没有找L2 Cache依次类推，最后找到硬盘中。同时，我们可以看到，速度与存储容量的折衷关系。容量越小，访问速度越快！其中，一个概念需要搞清楚。CPU和Cache是word传输的，而Cache到主存是以块传输的。一块大约64Byte.现有SOC中的Cache一般组成如下。1.3 Cache的分类Cache按照不同标准分类可以分为若干类。按照数据类型划分：I-Cache与D-Cache。其中I-Cache负责放置指令，D-Cache负责方式数据。两者最大的不同是D-Cache里的数据可以写回，I-Cache是只读的。按照大小划分：分为small cache和large cache。没路组（后文组相连介绍）<4KB叫small cache, 多用于L1 Cache, 大于4KB叫large cache。多用于L2及其他cache.按照位置划分：Inner Cache和Outer Cache。一般独属于CPU微架构的叫Inner cache, 例如上图的L1 L2 CACHE。不属于CPU微架构的叫outer cache.按照数据关系划分：Inclusive/exclusive Cache: 下级Cache包含上级的数据叫inclusive cache。不包含叫exclusive cache。举个例子，L3 Cache里有L2 Cache的数据，则L2 Cache叫exclusive cache。2 Cache的工作原理要讲清楚Cache的工作原理，需要回答4个问题：数据如何放置数据如何查询数据如何被替换如果发生了写操作，cache如何处理2.1 数据如何放置这个问题也好解决。我们举个简单的栗子来说明问题。假设我们主存中有32个块，而我们的cache一共有8个cache行( 一个cache行放一行数据）。假设我们要把主存中的块12放到cache里。那么应该放到cache里什么位置呢？三种方法：全相连（Fully associative）。可以放在cache的任何位置。直接映射（Direct mapped）。只允许放在cache的某一行。比如12 mod 8组相连（set associative）。可以放在cache的某几行。例如2路组相连，一共有4组，所以可以放在0,1位置中的一个。可以看到，全相连和直接映射是Cache组相连的两种极端情况。不同的放置方式主要影响有两点：组相连组数越大，比较电路就越大，但Cache利用率更高，cache miss发生的概率小。组相连数目变小，cache经常发生替换，但是比较电路比较小。这也好理解，内存中的块在cache中可放置的位置多，自然找起来就麻烦。2.2 如何在Cache中找数据其实找数据就是一个比对过程。如下图所示。我们地址都以Byte为单位的。但主存于cache之间的数据交换单位都是块（block，现代cache一般一个block大约64Byte)。所以地址对最后几位是block offset。由于我们采用了组相连，则还有几个比特代表的是存储到了哪个组。组内放着若干数据。我们需要比较Tag, 如果组内有Tag出现，则说明我们访问的数据在缓存中，可以开心的使用了。比如举个2路组相连的例子。如下图所示。T表示Tag。直接比较Tag，就能得知是不是命中了。如果命中了，则根据index(组号）将对应的块取出来即可。如上图所示。用index选出位于组相连的哪个组。然后并行的比较Tag, 判断最后是不是在cache中。上图是2路组相连，也就是说两组并行比较。那如果不在缓存中呢？这就涉及到另一个问题。不在缓存中如何替换cache？2.3 如何替换Cache中的数据cache中的数据如何被替换的？这个就比较简单直接。随机替换。如果发生cache miss里随机替换掉一块。Least recently used. LRU。最近使用的块最后替换。First in, first out (FIFO), 先进先出。实际上第一个不怎么使用，LRU和FIFO根据实际情况选择即可。cache在什么时候数据会被替换内？也有几种策略。不在本cache替换。如果cache miss了，直接转发访问地址到主存，取到的数据不会写到cache.在读MISS时替换。如果读的时候cache里没有该数据，则从主存读取该数据后写入cache。在写MISS时替换。如果写的时候cache里没有该数据，则将本数据调入cache再写。2.4 如果发生了写操作怎么办Cache毕竟是个临时缓存。如果发生了写操作，会造成cache和主存中的数据不一致。如何保证写数据操作正确呢？也有三种策略。通写：直接把数据写回cache的同时写回主存。极其影响写速度。回写：先把数据写回cache, 然后当cache的数据被替换时再写回主存。通写队列：通写与回写的结合。先写回一个队列，然后慢慢往主存储写。如果多次写同一个数据，直接写这个队列。避免频繁写主存。3 Cache一致性Cache一致性是Cache中遇到的比较坑的一个问题。什么原因需要cache处理一致性呢？主要是多核系统中，假如core 0读了主存储的数据，写了数据。core 1也读了主从的数据。这个时候core 1并不知道数据已经被改动了，也就是说，core 1 cache中的数据过时了，会产生错误。Cache一致性的保证就是让多核访问不出错。Cache一致性主要有两种策略。策略一：基于监听的一致性策略这种策略是所有cache均监听各cache的写操作，如果一个cache中的数据被写了，有两种处理办法。写更新协议：某个cache发生写了，就索性把所有cache都给更新了。写失效协议：某个cache发生写了，就把其他cache中的该数据块置为无效。策略1由于监听起来成本比较大，所以只应用于极简单的系统中。策略二：基于目录的一致性策略这种策略是在主存处维护一张表。记录各数据块都被写到了哪些cache, 从而更新相应的状态。一般来讲这种策略采用的比较多。又分为下面几个常用的策略。SI: 对于一个数据块来讲，有share和invalid两种状态。如果是share状态，直接通知其他cache, 将对应的块置为无效。MSI：对于一个数据块来讲，有share和invalid，modified三种状态。其中modified状态表表示该数据只属于这个cache, 被修改过了。当这个数据被逐出cache时更新主存。这么做的好处是避免了大量的主从写入。同时，如果是invalid时写该数据，就要保证其他所有cache里该数据的标志位不为M，负责要先写回主存储。MESI：对于一个数据来讲，有4个状态。modified, invalid, shared, exclusive。其中exclusive状态用于标识该数据与其他cache不依赖。要写的时候直接将该cache状态改成M即可。我们着重讲讲MESI。图中黑线：CPU的访问。红线：总线的访问，其他cache的访问。当前状态时I状态时，如果发生处理器读操作prrd如果其他cache里有这份数据，如果其他cache里是M态，先 把M态写回主存再读。否则直接读。最终状态变为S。其他cache里没这个数据，直接变到E状态。当前状态为S态如果发生了处理器读操作，仍然在S态。如果发生了处理器写操作，则跳转到M状态。如果其他cache发生了写操作，跳到I态。当前状态E态发生了处理器读操作还是E。发生了处理器写操作变成M。如果其他cache发生了读操作，变到S状态。当前状态M态发生了读操作依旧是M态。发生了写操作依旧是M态。如果其他cache发生了读操作，则将数据写回主存储，变换到S态。4 总结cache在计算机体系架构中有非常重要的地位。本文讲了cache中最主要的内容。具体细节可以再根据某个点深入研究。https://zhuanlan.zhihu.com/p/386919471
本文介绍了 GitHub 上 star 量最高的 5 个机器学习项目，涉及人脸识别、文本处理、机器学习框架等。选自towardsdatascience，作者：Rishi Sidhu，机器之心编译参与：Nurhachu Null、Geek AI。机器学习领域正在飞速发展。GitHub 是一张举世瞩目的白板，高质量的代码通常被发布在这张充满智慧的无限大白板上。显然，我们不可能追踪机器学习世界中的所有东西，但是 GitHub 上每个项目都具备自己的 star 量。即，如果你标星了一个仓库，这意味着你对这个项目表达了赞赏，同时也跟踪了你觉得有意思的仓库。星数排名可作为了解最受关注项目的重要指标。本文就介绍了机器学习领域星数排名最高的 5 个项目。Face Recognition：26073★GitHub 地址：https://github.com/ageitgey/face_recognition?source=post_page---------------------------这是世界上最简洁的人脸识别工具。它提供对 Python 和命令行的应用程序接口（API），其用途是识别以及操作图像中的人脸。它使用 Dlib 最先进的人脸识别算法构建，该深度学习模型在 LFW（Labeled Faces in the Wild）数据集上达到了 99.38% 的准确率。它还提供了 face_recognition 命令行工具，它可以让你在包含图像的文件夹中使用命令行来进行人脸识别！这个库还可以处理实时人脸识别。动图封面fastText：18931 ★GitHub 地址：https://github.com/facebookresearch/fastText?source=post_page---------------------------fastText 是由 Facebook 团队开发的免费开源库，用于高效词表征学习。它是轻量级的，允许用户学习文本表征和句子分类器。它可以在标准通用硬件上运行，模型甚至可以被压缩到适应移动设备的大小。文本分类是很多应用的核心问题，例如垃圾邮件检测、情感分析或智能回复。文本分类的目标是给文档（例如电子邮件、博客、短信、产品评论等）分配多个类别。词类示例（图源：Alterra.ai）对自然语言处理（NLP）爱好者而言，这是一款非常有用的工具。图源：https://fasttext.cc/?source=post_pag (https://fasttext.cc/?source=post_pag%EF%BC%89)Awesome TensorFlow：14501★GitHub 地址：https://github.com/jtoy/awesome-tensorflow这是一个帮你理解和使用 TensorFlow 的资源集合。该 repo 涵盖一系列资源列表，如很棒的 TensorFlow 实验、库和项目。TensorFlow 是 Google 开发的端到端开源机器学习平台。它有全面的生态系统，包括工具、库和社区资源，允许研究者创建最先进的机器学习算法。使用 TensorFlow，开发者可以很容易地构建并部署由机器学习驱动的应用。图源：https://www.tensorflow.org/tutorials/keras/basic_classification?source=post_pageApache predictionio 11866 ★GitHub 地址：https://github.com/apache/predictionioApache PredictionIO 是供开发者、数据科学家和终端用户使用的开源机器学习框架。用户可使用该框架构建真实的机器学习应用，并进行部署和测试。它甚至支持事件收集、评估，以及查询预测结果。它基于可扩展的开源服务，如 Hadoop、HBase 等。就机器学习而言，该工具减轻了开发人员的思维负担。图源：http://predictionio.apache.org/appintegration/?source=post_pageStyle2Paints：9860 ★GitHub 地址：https://github.com/lllyasviel/style2paints该 repo 与前面 4 个有点不一样，因为缺乏资金，它已经被关闭了！它确实是一个有趣的设想，使用 AI 给图像上色。创建者称 Style2paints V4 是当前最好的 AI 线稿上色工具。他们称这个项目与之前的端到端图像转换方法不同，因为它是第一个用真实的人类作业流程为线稿上色的系统。很多艺术家熟悉这个流程。素描-->彩色填充/扁平化-->渐变/细节添加-->阴影处理Style2Paints就是根据这个流程设计的。只用两次点击，该流程就可以使下图中最左的图变成中间的图。图源：https://style2paints.github.io/?source=post_page仅仅点击 4 次，你就能够得到下面这张图：图源：https://github.com/lllyasviel/style2paints?source=post_page原文链接：https://towardsdatascience.com/
想了解CPU的工作原理莫过于从头开始用最基础的元素打造一个简单CPU。接下来我会从最简单的晶体管开始一步步讲解CPU是如何构造出来的，明白了这个过程理解 CPU 的工作原理不在话下，在此之后我会从最基础的二进制机器指令一步步讲解高级语言的基本原理，通读本文后你将彻底明白CPU与高级语言的工作原理。以下内容出自我的两篇文章《你管这破玩意叫CPU？》《你管这破玩意叫编程语言？》。次回家开灯时你有没有想过，用你按的简单开关实际上能打造出复杂的 CPU 来，只不过需要的数量会比较多，也就几十亿个吧。伟大的发明过去200年人类最重要的发明是什么？蒸汽机？电灯？火箭？这些可能都不是，最重要的也许是这个小东西：这个小东西就叫晶体管，你可能会问，晶体管有什么用呢？实际上晶体管的功能简单到不能再简单，给一端通上电，那么电流可以从另外两端通过，否则不能通过，其本质就是一个开关。就是这个小东西的发明让三个人获得了诺贝尔物理学奖，可见其举足轻重的地位。无论程序员编写的程序多么复杂，软件承载的功能最终都是通过这个小东西简单的开闭完成的，除了神奇二字，我想不出其它词来。AND、OR、NOT现在有了晶体管，也就是开关，在此基础之上就可以搭积木了，你随手搭建出来这样三种组合：两个开关只有同时打开电流才会通过，灯才会亮两个开关中只要有一个打开电流就能通过，灯就会亮当开关关闭时电流通过灯会亮，打开开关灯反而电流不能通过灯会灭天赋异禀的你搭建的上述组合分别就是：与门，AND Gate、或门，OR gate、非门，NOT gate，用符号表示就是这样：道生一、一生二、二生三、三生万物最神奇的是，你随手搭建的三种电路竟然有一种很amazing的特性，那就是：任何一个逻辑函数最终都可以通过AND、OR以及NOT表达出来，这就是所谓的逻辑完备性，就是这么神奇。也就是说给定足够的AND、OR以及NOT门，就可以实现任何一个逻辑函数，除此之外我们不需要任何其它类型的逻辑门电路，这时我们认为{AND、OR、NOT}就是逻辑完备的。这一结论的得出吹响了计算机革命的号角，这个结论告诉我们计算机最终可以通过简单的{AND、OR、NOT}门构造出来，就好比基因。计算能力是怎么来的现在能生成万物的基础元素与或非门出现了，接下来我们着手设计CPU 最重要的能力：计算，以加法为例。由于CPU只认知 0 和 1，也就是二进制，那么二进制的加法有哪些组合呢：0 + 0，结果为0，进位为00 + 1，结果为1，进位为01 + 0，结果为1，进位为01 + 1，结果为0，进位为1，二进制嘛！注意进位一列，只有当两路输入的值都是 1 时，进位才是 1 ，看一下你设计的三种组合电路，这就是与门啊，有没有！再看下结果一列，当两路输入的值不同时结果为1，输入结果相同时结果为0，这就是异或啊，有没有！我们说过与或非门是逻辑完备可以生万物，异或逻辑当然不在话下，用一个与门和一个异或门就可以实现二进制加法：上述电路就是一个简单的加法器，就问你神奇不神奇，加法可以用与或非门实现，其它的也一样能实现，逻辑完备嘛。除了加法，我们也可以根据需要将不同的算数运算设计出来，负责计算的电路有一个统称，这就是所谓的arithmetic/logic unit，ALU，CPU 中专门负责运算的模块，本质上和上面的简单电路没什么区别，就是更加复杂而已。现在，通过与或非门的组合我们获得了计算能力，计算能力就是这么来的。但，只有计算能力是不够的，电路需要能记得住信息。神奇的记忆能力到目前为止，你设计的组合电路比如加法器天生是没有办法存储信息的，它们只是简单的根据输入得出输出，但输入输出总的有个地方能够保存起来，这就是需要电路能保存信息。电路怎么能保存信息呢？你不知道该怎么设计，这个问题解决不了你寝食难安，吃饭时在思考、走路时在思考，蹲坑时在思考，直到有一天你在梦中遇一位英国物理学家，他给了你这样一个简单但极其神奇的电路：这是两个NAND门的组合，不要紧张，NAND也是有你设计的与或非门组合而成的，所谓NAND门就是与非门，先与然后取非，比如给定输入1和0，那么与运算后为0，非运算后为1，这就是与非门，这些不重要。比较独特的是该电路的组合方式，一个NAND门的输出是两一个NAND门的输入，该电路的组合方式会自带一种很有趣的特性，只要给S和R段输入1，那么这个电路只会有两种状态:要么a端为1，此时B=0、A=1、b=0；要么a端为0，此时B=1、A=0、b=1;不会再有其他可能了，我们把a端的值作为电路的输出。此后，你把S端置为0的话(R保持为1)，那么电路的输出也就是a端永远为1，这时就可以说我们把1存到电路中了；而如果你把R段置为0的话(S保持为1)，那么此时电路的输出也就是a端永远为0，此时我们可以说把0存到电路中了。就问你神奇不神奇，电路竟然具备存储信息的能力了。现在为保存信息你需要同时设置S端和R端，但你的输入是有一个(存储一个bit位嘛)，为此你对电路进行了简单的改造：这样，当D为0时，整个电路保存的就是0，否则就是1。寄存器与内存的诞生现在你的电路能存储一个比特位了，想存储多个比特位还不简单，复制粘贴就可以了：我们管这个组合电路就叫寄存器，你没有看错，我们常说的寄存器就是这个东西。你不满足，还要继续搭建更加复杂的电路以存储更多信息，同时提供寻址功能，就这样内存也诞生了。寄存器及内存都离不开上一节那个简单电路，只要通电，这个电路中就保存信息，但是断电后很显然保存的信息就丢掉了，现在你应该明白为什么内存在断电后就不能保存数据了吧。硬件的基本功让我们来思考一个问题，CPU怎么能知道自己要去对两个数进行加法计算，以及哪两个数进行加法计算呢？很显然，你得告诉CPU，该怎么告诉呢？还记得上一节中给厨师的菜谱吗？没错，CPU也需要一张菜谱告诉自己该接下来该干啥，在这里菜谱就是机器指令，指令通过我们上述实现的组合电路来执行。接下来我们面临另一个问题，那就是这样的指令应该会很多吧，废话，还是以加法指令为例，你可以让CPU计算1+1，也可以计算1+2等等，实际上单单加法指令就可以有无数种组合，显然CPU不可能去实现所有的指令。实际上CPU只需要提供加法操作，你提供操作数就可以了，CPU 说：“我可以打人”，你告诉CPU该打谁、CPU 说：“我可以唱歌”，你告诉CPU唱什么，CPU 说我可以做饭，你告诉CPU该做什么饭，CPU 说：“我可以炒股”，你告诉CPU快滚一边去吧韭菜。因此我们可以看到CPU只提供机制或者说功能(打人、唱歌、炒菜，加法、减法、跳转)，我们提供策略(打谁、歌名、菜名，操作数，跳转地址)。CPU 表达机制就通过指令集来实现的。指令集指令集告诉我们 CPU 可以执行什么指令，每种指令需要提供什么样的操作数。不同类型的CPU会有不同的指令集。指令集中的指令其实都非常简单，画风大体上是这样的：从内存中读一个数，地址是abc对两个数加和检查一个数是不是大于6把这数存储到内存，地址是abc等等看上去很像碎碎念有没有，这就是机器指令，我们用高级语言编写的程序，比如对一个数组进行排序，最终都会等价转换为上面的碎碎念指令，然后 CPU 一条一条的去执行，很神奇有没有。接下来我们看一条可能的机器指令：这条指令占据16比特，其中前四个比特告诉CPU这是加法指令，这意味着该CPU的指令集中可以包含2^4也就是16个机器指令，这四个比特位告诉CPU该做什么，剩下的bit告诉CPU该怎么做，也就是把寄存器R6和寄存器R2中的值相加然后写到寄存器R6中。可以看到，机器指令是非常繁琐的，现代程序员都使用高级语言来编写程序，关于高级程序语言以及机器指令的话题请参见《你管这破玩意叫编程语言？》。大功告成现在我们有了可以完成各种计算的ALU、可以存储信息的寄存器以及控制它们协同工作的时钟信号，这些统称 Central Processing Unit，简称就是 CPU。接下来我们看一下CPU 与高级语言。程序员按照 CPU 的旨意直接用0和1编写指令，你没有看错，这破玩意就是代码了，就是这么原生态，然后放到打孔纸带上输入给CPU，CPU 开始工作，这时的程序可真的是看得见摸得着，就是有点浪费纸。这时程序员必须站在 CPU 的角度来写代码，画风是这样的：1101101010011010100100110010100111001000110111101011101101010010终于有一天程序员受够了说鸟语，好歹也是灵长类，叽叽喳喳说鸟语太没面子，你被委以重任：让程序员说人话。你没有苦其心志劳其筋骨，而是仔细研究了一下 CPU，发现 CPU 执行的指令集来来回回就那么几个指令，比如加法指令、跳转指令等等，因此你把机器指令和对应的具体操作做了一个简单的映射，把机器指令映射到人类能看懂的单词，这样上面的01串就变成了：sub $8, %rsp mov $.LC0, %edi call puts mov $0, %eax这样，程序员不必生硬的记住1011.....，而是记住人类可以认识的ADD SUB MUL DIV等这样的单词即可。汇编语言就这样诞生了，编程语言中首次出现了人类可以认识的东西细节 VS 抽象尽管汇编语言已经有人类可以认识的单词，但汇编语言和机器语言一样都属于低级语言。所谓低级语言是说你需要关心所有细节。关心什么细节呢？我们说过，CPU 是非常原始的东西，只知道把数据从一个地方搬到另一个地方，简单的操作一下再从一个地方搬到另一地方。因此，如果你想用低级语言来编程的话，你需要使用多个“把数据从一个地方搬到另一个地方，简单的操作一下再从一个地方搬到另一地方”这样的简单指令来实现诸如排序这样复杂的问题。有的同学可能对此感触不深，这就好比，本来你想表达“去给我端杯水过来”：如果你用汇编这种低级语言就得这样实现：我想你已经 Get 到了。弥补差异CPU 实在太简单了，简单到不能了理解任何稍微抽象一点诸如“给我端杯水”这样的东西，但人类天生习惯抽象化的表达，人类和机器的差距有办法来弥补吗？换句话说就是有没有一种办法可以自动把人类抽象的表达转为 CPU 可以理解的具体实现，这显然可以极大增强程序员的生产力，现在，这个问题需要你来解决。电光火石之间灵光乍现，你发现了满满的套路，或者说模式。大部分情况下 CPU 执行的指令平铺直叙的，就像这样：这些都是告诉 CPU 完成某个特定动作，你给这些平铺直叙的指令起了个名字，姑且就叫陈述句吧，statement。除此之外，你还发现了这样的套路，那就是需要根据某种特定状态决定走哪段指令，这个套路在人看来就是“如果。。。就。。。否则。。就。。。”：if * blablabla else * blablabla在某些情况下还需要不断重复一些指令，这个套路看起来就是原地打转：while * blablabla最后就是这里有很多看起来差不多的指令，就像这里：这些指令是重复的，只是个别细节有所差异，把这些差异提取出来，剩下的指令打包到一起，用一个代号来指定这些指令就好了，这要有个名字，就叫函数吧：func abc: blablabla现在你发现了所有套路：// 条件转移 if * blablablaelse * blablabla// 循环 while * blablabla// 函数 func abc: blablabla这些相比汇编语言已经有了质的飞跃，因为这已经和人类的语言非常接近了。接下来你发现自己面临两个问题：这里的blablabla该是什么呢？该怎样把上面的人类可以认识的字符串转换为 CPU 可以认识的机器指令递归：代码的本质不就是嵌套嘛，一层套一层嘛，递归天生就是来表达这玩意的 (提示：这里的表达并不完备，真实的编程语言不会这么简单，这里仅仅用作示例)：if : if bool statement else statements for: while bool statements statement: if | for | statement让计算机理解递归现在还差一个问题，怎样才能把这语言最终转化为 CPU 可以认识的机器指令呢？人类可以按照语法写出代码，这些代码其实就是一串字符，怎么让计算机也能认识用递归语法表达的一串字符呢？这是一项事关人类命运的事情，你不禁感到责任重大，但这最后一步又看似困难重重，你不禁仰天长叹，计算机可太难了。此时你的初中老师过来拍了拍你的肩膀，递给了你一本初中植物学课本，你恼羞成怒，给我这破玩意干什么，我现在想的问题这么高深，岂是一本破初中教科书能解决的了的，抓过来一把扔在了地上。此时又一阵妖风挂过，书被翻到了介绍树的一章，你望着这一页不禁发起呆来：我们可以把根据递归语法写出来的的代码用树来表示啊！优秀的翻译官计算机处理编程语言时可以按照递归定义把代码用树的形式组织起来，由于这棵树是按照语法生成的，姑且就叫语法树吧。现在代码被表示成了树的形式，你仔细观察后发现，其实叶子节点的表达是非常简单的，可以很简单的翻译成对应的机器指令，只要叶子节点翻译成了机器指令，你就可以把此结果应用到叶子节点的父节点，父节点又可以把翻译结果引用到父节点的父节点，一层层向上传递，最终整颗树都可以翻译成具体的机器指令。完成这个工作的程序也要有个名字，根据“弄不懂原则”(该原则的解释见下文 :)总结本文我们从最基本的晶体管一路讲解到CPU的工作原理，再从最低级的二进制机器指令到高级语言，相信如果你能读到这里定能有所收获。最后，有同学问有没有书单，我也仔细回想自己认真读过的计算机数据，在这里也给出自认为很经典的几本，书单这东西贵精不贵多，我在这里精心挑选了10本 ，不要贪心，如果你真能把这里推荐的 10 本书读通，可以说你已经能超越 90% 的程序员了。
CPU 系统级验证 —— 指令集验证 ——OpenHW core-v 验证环境及文件分析
本文记录的相关源工程和文件为：core-v RISCV 核功能验证工程：https://github.com/openhwgroup/core-v-verifcore-v 验证策略：https://core-v-docs-verif-strat.readthedocs.io/en/latest/#core-v 系列核 cva6 工程：https://github.com/openhwgroup/cva6core-v 系列核 cv32e40p 工程：https://github.com/openhwgroup/cv32e40pLowRISCV Ibex 核工程：https://github.com/lowRISC/ibexcore-v RISCV 核相关说明文档：https://github.com/openhwgroup/core-v-docsRISCV ISA 指令流（instruction stream）生成器：https://github.com/openhwgroup/force-riscvcore-v 系列指令流（instruction stream）生成器：https://github.com/openhwgroup/core-v-isgsail 语言介绍：https://www.cl.cam.ac.uk/~pes20/sail/REMS 的 sail RISCV 开源项目：https://github.com/rems-project/sail-riscvopenHW 全称为 open-source hardware，是一个支持开源软件和硬件的非盈利组织。该组织开源的内容包括开源 CPU 核、相关 IP、工具和软件等。指令集验证指令集验证的需求能够使用操作数产生合法的基本整数运算指令在指令执行完成后能够检查通用寄存器的状态指令执行完成后能检查副效应，例如溢出等。指令集验证内容指令集的验证需要验证工程师思考该指令需要测试的特征是什么，为了确保指令的正确执行需要检查的内容是什么，为了确保该特征被测试需要进行哪些激励和配置等。验证的充不充分取决于验证人员对 CPU 微架构的理解和风险应对能力。（1）RISCV 基本指令定向测试设计出来的 CPU 首先需要兼容 RISCV 基本指令，即需要满足 RISCV 基本整数指令及相关标准指令集。主要测试的内容有：溢出检测及相关标志位下溢检测及相关标志位无指令执行的副效应，比如异常的通用处理寄存器变化，异常的条件代码使用 x0-x31 作为 rs1/rs2使用 x0-x31 作为 rdx0 寄存器的值始终为 0set/clear 所有立即数的比特位set/clear 源寄存器 rs1/rs2 的比特位根据 7/8 两条 set/clear 目的寄存器 rd 的比特位（2）自定义扩展指令定向测试对于自定义扩展指令集的验证，该验证需要修改相关的工具链并通过相关测试验证工具链的修改成功，再进行（1）中的测试。（3）随机约束测试对进行大量的随机指令测试指令集验证激励的种类指令集测试激励分为自检（self-checking）和预存（pre-existing）两种。自检测试激励运行时按照源文件中指令顺序依次，并同时将指令执行结果和参考值作检查，若出错则程序跳转到 fail，打断正常执行顺序，立即执行完成。类似的测试集有 riscv-tests、riscv-compilance-tests 等预存测试激励运行时不会打断执行顺序，执行的结果会和指令集模拟器（ISS）的结果作对比，最终统计对比的结果。类似的测试集有 riscv-dv 等指令集验证 pass/fail 的标志测试的 pass/fail 与上述激励的种类有关self checking：自检性质的测试激励在每条指令执行时就会同时将结果和参考值作对比，若失败则跳转到 failsignature check：特征检查，相对自检来说更加复杂深入。测试的结果将被用来计算该指令的某个特征，这将会和预先确定好的较标准特征作对比。采用该检查的为 riscv-complience-tests。check against ISS：与指令集模拟器对比检查。该情况下，testcase 并不知道测试的参考值，仅仅给 DUT 提供激励，testbench 会对比 DUT 和 ISS 的输出结果确定测试的 pass/fail。该检查较被经常使用，因为它是的 testcase 更加简单。采用该检查的如 riscv-dvcheck against RM：与参考模型对比检查，该检查方式与第三点类似，但相对 ISS 更加通用。通常用作 ISS 检测的补充。assertion check：通过断言判断是否执行出错。相关 CPU 核的验证工程cv32e40pCV32E40P (原先 PULP 开发的 RI5CY) 是一个 32bit 的 4 级流水线核，支持整数运算指令、乘法除法指令、单浮点运算指令、压缩指令以及 DSP 扩展指令（包括硬件循环、SIMD 扩展、位操作和增量指令）该核的验证环境有两套，一套是专门的 core testbench；一套是 UVM 环境搭建的 testbench，该环境下 testcase 的产生是可以基于 UVM 环境的（这种方式类似于 Ibex 核验证环境）。两套环境均由 systemverilog 编写。均包含了指令集验证、中断验证、CSR 寄存器验证、异常验证、debug 验证等。1 core testbench该工程是 PULP 遗留下的产物，OpenHW 做了一定修改并持续维护。验证环境中已集成了指令集模拟器。该工程的测试集激励包含指令兼容性测试（偏定向测试，如 riscv-tests 和 riscv-compilance-tests）和随机约束（google riscv-dv），和其他的一些 fireware 测试用例。测试环境结构如下：该 testbench 简单，运行较快，通过 verilator 运行，但是有额外的开销且不能获取覆盖率。2 UVM 验证环境（uvmt_cv32）cv32 核的 UVM 验证环境已搭建完成，在 DSIM（Metrics）运行也很稳定。验证环境中已集成了指令集模拟器。uvm 验证环境的 testcase 均是由 uvm 相关类生成，同时该环境还集成了 riscv-dv，但具体的激励生成还需要再分析。具体验证环境结构UVM 内部组件关系debug、interrupt、control/status 均通过 UVM_agent 组件产生，且通过 uvm_sequence 组件输入激励。UVM 运行流程采用 UVM 搭环境的优势a) 便于验证环境的结构建模b) UVM 环境类支持完全的 UVM 运行流程和 log 服务c) 使用 UVM sequence-item 类可以产生随即约束激励d) 使用内建于验证环境中的参考模型预测执行的结果。（imperas 有现成的开源 ISS）e) scoreboard 可以比较参考模型和 RTL 的结果。f) 功能覆盖率和代码覆盖率确保验证的完全性。且 UVM 测试环境可复用性较高，也可以用在 CVA6 核上。CVA6CVA６(ariane) 是一个 6 级流水线、单发射、顺序执行的 RV32GC 或 RV64GC 核，支持 M/U/S 三种模式，支持 linux 操作系统。该核现存的验证环境尚未成熟，但同样可以构建 UVM 的验证环境。该工程的指令集测试激励包括 riscv 官方测试套件（即 riscv-tests）。IbexIbex 不是 openHW 的 core-v 系列，而是 lowRISC 持续维护的较为成熟的一款核，验证环境也较为成熟，其结构和运行非常类似于 CV32E 和 CVA6 的激励随机约束。该验证环境激励的产生同样是基于 GOOGLE 的随机指令生成器验证环境特征运行有效性testcase 在验证环境中会持续运行完成，除非激励运行出错方便调试能够增加功能覆盖率能够对比检查 RTL 运行的结果指令集描述语言 ——sailsail 是 REMS 构建的一种描述指令集的语言，可以认为是一种机器可读的形式化 ISA 模型。REMS 是英国的一家学术组织，该组织已构建出 RV32IMAC 和 RV64IMAC 指令集的 sail model，链接见上。riscv 基金会有意用其来描述 RISCV 指令集。初步来看，sail 可以用来形式化 testbench 断言的参考模型，这些 assertion 可以根据 sail spec 来验证基于 RISCV ISA 的某微架构。目前该领域 OneSpin GapFree 已经在 sail model 和 RTL 代码间做了可比性的 check，该公司仍在探索如何充分使用 sail model该语言将持续关注。
无意中发现一个开源的工具，使用systemRDL文件作为输入，可以生成寄存器相关的RTL module，UVM中使用的regmodel，C和verilog的头文件(包含寄存器某个字段的偏移以及mask bits)，以及html的文档。资源如下：https://github.com/zhajio1988/Open_RegModel生成的html文档示例如下：https://systemrdl.github.io/RALBot-html————————————————版权声明：本文为CSDN博主「XtremeDV」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。原文链接：https://blog.csdn.net/zhajio/article/details/102620859开源uvm reg model (UVM 寄存器模型)生成工具open-register-design-toolOrdt is a tool for automation of IC register definition and documentation. It currently supports 2 input formats:SystemRDL - a stardard register description format released by Accellera.orgJSpec - a register description format used within Juniper NetworksThe tool can generate several outputs from SystemRDL or JSpec, including:SystemVerilog/Verilog RTL code description of registersUVM model of the registersC++ models of the registersXML and text file register descriptionsSystemRDL and JSpec (conversion)Easiest way to get started with ordt is to download a runnable jar from the release area.Ordt documentation can be found here.注：nvdla中使用SystemRDL格式描述寄存器模型，并使用jar xxx ordt.jar ......生成uvm reg model————————————————版权声明：本文为CSDN博主「XtremeDV」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。原文链接：https://blog.csdn.net/zhajio/article/details/82870523
IPYXACT：Python 中的 IEEE-1685 (IP-XACT) 处理工具
IPYXACT：Python 中的 IEEE-1685 (IP-XACT) 处理工具项目介绍IPYXACT 是一个基于 Python 的库，旨在简化对 IEEE-1685 标准（通常称为 IP-XACT）的支持。这一标准定义了用于描述集成电路知识产权（IP核）元数据的 XML 方式。IPYXACT 提供了解析、操作及生成 IP-XACT 描述文件的能力，对于那些需要处理或创建 SoC 设计中IP组件描述的软件开发人员和硬件工程师来说非常有用。项目快速启动要快速开始使用 IPYXACT，首先确保你的环境中已经安装了 Python 3.6 或更高版本。然后，通过以下命令将项目添加到你的环境：pip install ipyxactbash接下来，尝试简单的示例来体验其基本功能： from ipyxact.ipyxact import Component # 创建一个新的组件实例 comp = Component() compvendor = comp.component_vendor compvendor.name = "Example Vendor" compvendor.description = "A demonstration component" # 保存这个组件到XML文件 with open("example.xml", "wb") as f: comp.write_xml(f) code here...这段代码创建了一个最简化的 IP-XACT 元数据结构并保存到了一个 XML 文件中。应用案例和最佳实践在设计复杂的SoC时，IPYXACT可以极大地帮助管理IP核的元数据。例如，当你需要自动化生成IP核的配置界面时，可以通过解析已有IP-XACT描述文件，自动生成对应的配置UI，或者在进行IP复用过程中，验证IP的合规性和一致性。示例：自动构建IP配置界面假设已有一个IP-XACT描述文件，你可以读取该文件来获取IP的信息，然后利用这些信息动态地创建GUI元素。 component = Component.from_file("your_ip.xact") for memory_map in component.memory_maps: for register in memory_map.register_list: # 假设这里会根据register的信息生成GUI控件 print(f"Register: {register.name} at {register.address_offset}") code here...典型生态项目虽然直接相关的“典型生态项目”特定于 IPYXACT 的提及不多，但在嵌入式系统和SoC设计领域，结合使用 IPYXACT 的项目通常与硬件描述语言（如Verilog、SystemVerilog）、EDA工具（如Vivado、Quartus）以及芯片自动化流片流程紧密相关。开发者可能会集成IPYXACT到他们的工作流程中，以自动化生成或验证与IP核相关的文档、配置界面或是用于构建系统的脚本，从而提高整个设计周期的效率和准确性。对于进一步的集成案例和技术堆栈的探索，建议参考 FPGA 开发、ASIC设计社区以及电子设计自动化领域的其他开源和商业解决方案。开发者可以在设计流程的不同阶段利用IPYXACT的标准化信息，促进IP核的重用和管理————————————————版权声明：本文为CSDN博主「申梦珏Efrain」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。原文链接：https://blog.csdn.net/gitblog_00259/article/details/141456709
https://www.cnblogs.com/kingstacker/p/7490002.html前言当你器件的引脚贼少的时候，需要主机和从机通信，spi就派上了用场，它可以一对多，但只是片选到的从机能和主机通信，其他的挂机。spi：serial peripheral interface 串行外围接口大致了解：spi是个同步协议，数据在master和slaver间交换通过时钟sck，由于它是同步协议，时钟速率就可以各种变换。sck：主机提供，从机不能操控，从器件由主机产生的时钟控制。数据只有在sck来了的上升沿或者下降沿才传输。高级一点的spi芯片有配置寄存器，高级一点的工作有四种模式，采样相位和sck空闲电平可配置。当然在这里我们主要实现简单的spi协议：sck是系统时钟的四分频，wr请求信号有效时，主机开始工作，数据位8bit，sck空闲时低电平，工作时第一个沿数据传输。只有一个从机，cs低电平片选。看下结构：接口定义：编码实现：（版权所有，请勿用于商业用途，仅供学习使用）//************************************************ // Filename : spi_ms_test1.v // Author : Kingstacker // Company : School // Email : kingstacker_work@163.com // Device : Altera cyclone4 ep4ce6f17c8 // Description : spi master module;data 8bit;sck is 4 div of the clk; //************************************************ module spi_ms #(parameter WIDTH = 8)( //input; input wire clk, input wire rst_n, input wire wr, //send request; input wire [WIDTH-1:0] master_din, //the data you want send; input wire miso, //the data form slave; //output; output reg cs, //slave select; output reg sck, //data exchange clock; output reg mosi, //master out; output reg [WIDTH-1:0] master_dout //the data you received; ); localparam CLK_HZ = 50_000_000; //clk frequency; localparam SCK_HZ = 12_500_000; //sck frequency; localparam DIV_NUMBER = CLK_HZ / SCK_HZ; localparam CNT_MAX = (DIV_NUMBER >>1) - 1'b1; localparam DATA_CNT_MAX = 5'd31; localparam MOSI_CNT_MAX = 3'd7; localparam IDEL = 2'b00; localparam SEND = 2'b01; localparam FINISH = 2'b10; reg cnt; //sck cnt; reg sck_en; //enable sck; reg data_cnt_en; reg sck_reg1; reg sck_reg2; wire sck_p; //posedge sck; wire sck_n; //negedge sck; wire send_over; reg [1:0] cstate; reg [4:0] data_cnt; //cnt the send data; reg [2:0] mosi_cnt; reg [WIDTH-1:0] master_din_reg; reg [WIDTH-1:0] master_dout_reg; //produce sck; always @(posedge clk or negedge rst_n) begin if (~rst_n) begin cnt <= 0; sck <= 1'b0; end //if else begin if (sck_en == 1'b1) begin if (cnt == CNT_MAX) begin cnt <= 0; sck <= ~sck; end else begin cnt <= cnt + 1'b1; sck <= sck; end end else begin cnt <= 0; sck <= 1'b0; end end //else end //always //produce sck_p and sck_n; always @(posedge clk or negedge rst_n) begin if (~rst_n) begin sck_reg1 <= 1'b0; sck_reg2 <= 1'b0; end //if else begin sck_reg1 <= sck; sck_reg2 <= sck_reg1; end //else end //always assign sck_p = (sck_reg1 & (~sck_reg2)); //sck posedge; assign sck_n = ((~sck_reg1) & sck_reg2); //sck negedge; //fsm;hot code; always @(posedge clk or negedge rst_n) begin if (~rst_n) begin cstate <= IDEL; end else begin case (cstate) IDEL: cstate <= (wr)? SEND : IDEL; SEND: cstate <= (send_over) ? FINISH : SEND; FINISH: cstate <= IDEL; default: cstate <= IDEL; endcase //case end end always @(posedge clk or negedge rst_n) begin if (~rst_n) begin cs <= 1'b1; data_cnt_en <= 1'b0; sck_en <= 1'b0; master_din_reg <= 0; master_dout <= 0; end else begin case (cstate) IDEL: begin data_cnt_en <= 1'b0; master_din_reg <= (wr) ? master_din : master_din_reg; //load the data you want send to slaver; end SEND: begin data_cnt_en <= 1'b1; cs <= 1'b0; sck_en <= 1'b1; master_dout <= (send_over) ? master_dout_reg : master_dout; //master receiverd data; end FINISH: begin //send and load ok; sck_en <= 1'b0; cs <= 1'b1; data_cnt_en <= 1'b0; end default: begin cs <= 1'b1; sck_en <= 1'b0; data_cnt_en <= 1'b0; end endcase //case end end always @(posedge clk or negedge rst_n) begin if (~rst_n) begin data_cnt <= 0; end else begin data_cnt <= (data_cnt_en) ? (data_cnt + 1'b1) : 5'd0; //4 div * 8bit = 32 cnt; end end assign send_over = (data_cnt == DATA_CNT_MAX) ? 1'b1 : 1'b0; //rising edge miso; always @(posedge clk or negedge rst_n) begin if (~rst_n) begin master_dout_reg <= 0; end else begin master_dout_reg <= (sck_p) ? {master_dout_reg[6:0],miso} : master_dout_reg; end end //mosi; always @(posedge clk or negedge rst_n) begin if (~rst_n) begin mosi_cnt <= 0; end else begin if (sck_n) begin if (mosi_cnt == MOSI_CNT_MAX) begin mosi_cnt <= 0; end else begin mosi_cnt <= mosi_cnt + 1'b1; end end else begin mosi_cnt <= mosi_cnt; end end end always @(posedge clk or negedge rst_n) begin if (~rst_n) begin mosi <= 1'b0; end else begin mosi <= (sck_n) ? master_din_reg[MOSI_CNT_MAX-mosi_cnt] : mosi; end end endmodule仿真：综合资源使用：Fmax：
systemverilog interface普通的模块使用法：注意我们这里只实现了部分功能。。。。不是完全的读写模块。。。。module mem_core( input logic wen, input logic ren, output logic mrdy=1, input logic [7:0] addr, input logic [7:0] mem_din, //写进mem output logic [7:0] mem_dout, //从mem读出 output logic status, input logic clk);logic[7:0] mem [7:0]; //初始化一个meminitial $readmemh("d:/init.txt",mem); //d:/init.txt 文件中是 @01 10 。//或者 assign mem [2'h01]=8'b00000111; 注意这里一定要用 initial 或者assign等语句，不能直接=task reply_read(input logic [7:0] data, integer delay); #delay; @(negedge clk) mrdy=1'b0; mem_dout=data; //从图中可看出这两句话几乎同时执行。 @(negedge clk) mrdy=1'b1;endtaskalways@(negedge ren) reply_read(mem[addr],10);endmodulemodule cpu_core( output logic wen=1, output logic ren=1, input logic mrdy, output logic [7:0] addr=0, input logic [7:0] cpu_din, output logic [7:0] cpu_dout, output logic status=0, input logic clk);task read_memory(input logic [7:0] raddr, output logic [7:0] data); @(posedge clk); ren=1'b0; addr=raddr; @(negedge mrdy); @(posedge clk); data=cpu_din; ren=1'b1; endtaskinitial beginlogic[7:0] read_data; read_memory(2'h01, read_data); $display("Read Result", $time,read_data);endendmodulemodule top; logic mrdy,wen,ren; logic[7:0] addr,d1,d2; wor status; logic clk=0;mem_core mem(., .mem_din(d1), .mem_dout(d2)); //采用对同名的信号做默认连接 cpu_core cpu(.*, .cpu_din(d2), .cpu_dout(d1));initial for(int i=0;i<=255;i++) #1 clk=!clk;endmoduleSystemverilog interface另外，ＳｙｓｔｅｍＶｅｒｉｌｏｇ引入一个重要的数据类型：ｉｎｔｅｒｆａｃｅ。其主要作用有两个：一是简化模块之间的连接；二是实现类和模块之间的通信；随着复杂度的提高，模块间互联变得复杂，SV引入接口，代表一捆连线的结构，具有智能同步和连接功能的代码；接口（ｉｎｔｅｒｆａｃｅ）为硬件模块的端口提供了一个标准化的封装方式。用ｉｎｔｅｒｆａｃｅ来封装接口的信号和功能。ｉｎｔｅｒｆａｃｅ的定义是独立于模块的，通过关键字ｉｎｔｅｒｆａｃｅ和ｅｎｄｉｎｔｅｒｆａｃｅ包起来。此外，ｉｎｔｅｒｆａｃｅ里面还可以带时钟、断言、方法等定义。 一个interface 也可以有input,output或是inout端口。当interface例化时，只有当变量或是线网声明在一个interface的端口列表中才能通过名字或是位置来互连.一种新加的和interface有关系的构造体是Modport 。它提供了module的interface端口和在特定的module中控制task和function使用的方向性信息。这些端口的方向可以在module中可以看到。接口使用无信号的连接方式。Modport将接口中信号分组并指定方向。就像下图中的黑色矩形块里面一样，黑盒，我们从外面看并不关心Modport的定义，只需要考虑clk。Systemverilog interfaceinterface membus(input logic clk, output wor status); logic mrdy; logic wen; logic ren; logic [7:0] addr; logic [7:0] c2m_data; logic [7:0] m2c_data;task reply_read(input logic [7:0] data, integer delay); #delay; @(negedge clk) mrdy=1'b0; m2c_data=data; @(negedge clk) mrdy=1'b1;endtask//Task和function可以定义在interface中，从而允许构造更抽象级的模型task read_memory(input logic [7:0] raddr, output logic [7:0] data); @(posedge clk); ren=1'b0; addr=raddr; @(negedge mrdy); @(posedge clk); data=m2c_data; ren=1'b1; endtaskmodport master(output wen, ren, addr, c2m_data, input mrdy, m2c_data, status, read_memory);modport slave(input wen, ren, addr, c2m_data, output mrdy, m2c_data, status, reply_read);//控制task和function使用的方向性信息，以便在下面的module中使用endinterfacemodule mem_core(membus.slave mb); //modport只需在模块首部指明（或者在()中），在模块例化时不需要指明使用接口时在模块和程序块之外声明接口变量；//接口信号必须采用非阻塞值赋值来驱动。 logic[7:0] mem [7:0]; assign mem [2'h01]=8'b00000111; assign mb.status=0; always@(negedge mb.ren) mb.reply_read(mem[mb.addr],100); //module可使用interface端口endmodulemodule cpu_core(membus.master mb); assign mb.status=0; initial beginlogic[7:0] read_data; mb.read_memory(2'h01, read_data); $display("Read Result", $time,read_data);endendmodulemodule top; wor status; logic clk=0; membus mb(clk,status); mem_core mem(.mb(mb.slave)); cpu_core cpu(.mb(mb.master));initial for(int i=0;i<=255;i++) #1 clk=!clk;endmoduleSystem verilog把测试平台的代码放在一个程序块中，包含代码和变量，我总结了几步使用interface的方法1、 首先定义一个interface interface arb_if(input bit clk); logic [1:0] grant, request; logic reset; clocking cb @(posedge clk); //在其中定义一个时钟块。供下面的测试program使用。测试program中所有使用到的信号都应该定义在其中 output request; //注意这里的方向是测试program中所需要的方向，一般跟DUT 中的相反 input grant; endclockingmodport TEST (clocking cb, // 使用modport,将信号分组 output reset); modport DUT (input request, reset, clk, output grant); modport MONITOR (input request, grant, reset, clk);endinterface2、定义一个基于interface参数的设计模块modulemodule arb (arb_if.DUT arbif); //该interface参数要实例化 reg last_winner; reg winner; reg [1:0] next_grant; reg [1:0] state, nxState; always @(posedge arbif.clk or posedge arbif.reset) begin 。。。 endendmodule 3、定义一个基于interface参数的测试程序program program automatic test (arb_if.TEST arbif); //该interface参数也要实例化task reset_test();begin$display("Task reset_test: asserting and checking reset"); arbif.reset <= 0; #100 arbif.reset <= 1; //测试program中所有使用到的信号都应该调用在interface中的时钟块里定义的信号 arbif.cb.request <= 0; repeat (2) @arbif.cb; arbif.reset <= 0; @arbif.cb; //测试program中是这样等待时钟边沿的。 a0: assert (arbif.cb.grant == 2'b00); 。。。 end endtask task request_grant_test();begin 。。。 end endtask //注意program中不允许使用always块。 initial begin repeat (10) @arbif.cb; reset_test(); request_grant_test(); repeat (10) @arbif.cb; $finish; endendprogram4、‘最后使用一个顶层模块将它们组合起来module top; bit clk; always #5 clk = !clk; arb_if arbif(clk); //实例化一个interface arb a1 (arbif); //实例化一个module，参数调用上面实例化的interface test t1(arbif); //实例化一个测试program，参数调用上面实例化的interfaceendmodule当然也可以隐式端口连接,值使用.*即可。module top; bit clk; always #5 clk = !clk; arb_if arbif(.*); arb a1 (.*); test t1(.*); endmodule虚接口：虚接口是物理接口的句柄interface 和 module是一样的, 都是静态的变量, 也就是在程序开始时, 内存中就有了其实例.但是在class里使用virtual interface时之前有两部必须提前完成：l 定义是将接口作为一个类进行定义。l 实例化：在RTL级的顶层中对接口进行实例化。先定义一个接口。interface Rx_if (input logic clk);logic [7:0] data; logic soc, en, clav, rclk; clocking cb @(posedge clk); output data, soc, clav; input en; endclocking : cb modport DUT (output en, rclk, input data, soc, clav); modport TB (clocking cb);endinterface : Rx_if例如网络交换机中DUT 的每一个通道都有一个接口。，一个Driver类可能会连接到很多接口。我们可以在Driver类中使用一个虚接口作为参数。 class Driver;virtual Rx_if.TB Rx; //想一想，如果不是虚接口，而是一个普通接口，就像一个普通模块一样，是一个静态变量。比如我们在顶层模块例化了这个接口 Rx， 那么下面所有的 实例化的 drv[i]都是对这同一个接口 Rx进行操作，这显然不是我们想要的。如果定义了virtual，则每个实例独立。......endclass然后在测试program中 创建一组虚接口program automatic test(Rx_if.TB Rx[4], Tx_if.TB Tx[4], output logic rst); ........Driver drv[4]; //实例化了4个 Driver 对象，每个 Driver对象带有1个实例化的虚接口 .........initial beginvirtual Rx_if.TB vRx_t=Rx; //创建一组虚接口，由于这里定义了virtual，所以实例化的时候可以有Rx[]. for (int i=0; i<4; i++) begin drv[i] = new(...., vRx[i]); end rst <= 1; repeat (10) @Rx[0].cb; rst <= 0; for (int i=0; i<4; i++) begin drv[i].run(5, driver_done); //发送....... end..........endprogram : test最后在顶层：module top; logic clk, rst;Rx_if Rx[4] (clk); ，，，， atm_router a1 (Rx[0], Rx[1], Rx[2], Rx[3], Tx[0], Tx[1], Tx[2], Tx[3], clk, rst);test t1 (Rx, Tx, rst);initial beginclk = 0; forever #20 clk = !clk; end endmodule : top定义一个interface，且实例化多个后，如果没有定义virtual，则在任何一个实例中修改了某个信号值，在其他实例中都会受到影响。如果定义了virtual，则每个实例独立。如果该interface只有一个实例，可用可不用virtual，有多个实例，需要virtual。再举个例子：8位计数器`timescale 1ns/1nsinterface X_if (input logic clk);logic [7:0] din, dout; logic reset_l, load; clocking cb @(posedge clk); output din, load; input dout; endclocking always @cb //接口里面也可以带子程序，断言，initial，always块等代码。 $strobe("@ : %m: dout= , din= , load= , reset= ", $time, dout, din, load, reset_l); modport DUT (input clk, din, reset_l, load, output dout); modport TB (clocking cb, output reset_l);endinterface// Simple 8-bit counter with load and active-low reset`timescale 1ns/1nsmodule DUT(X_if.DUT xi); logic [7:0] count; assign xi.dout = count; //们想要输出的结果就是计数器always @(posedge xi.clk or negedge xi.reset_l)begin if (!xi.reset_l) count = 0; else if (xi.load) count = xi.din; else count++; end endmodule////////////////////////////////`timescale 1ns/1nsprogram automatic test();parameter NUM_XI = 2; // Number of interface instancestypedef virtual X_if.TB vXi_t; vXi_t vxi[NUM_XI]; //虚接口数组 class Driver; //在测试程序中定义类 vXi_t xi; int id; function new(vXi_t xi, int id); this.xi = xi; this.id = id; endfunction task reset; fork begin $display("@ : %m: Start reset [ ]", $time, id); // Reset the device xi.reset_l <= 1; xi.cb.load <= 0; xi.cb.din <= 0; @(xi.cb) xi.reset_l <= 0; @(xi.cb) xi.reset_l <= 1; $display("@ : %m: End reset [ ]", $time, id); end join_none endtask task load; fork begin $display("@ : %m: Start load [ ]", $time, id); xi.cb.load <= 1; xi.cb.din <= id + 10; xi.cb.load <= 0; repeat (5) @(xi.cb); $display("@ : %m: End load [ ]", $time, id); end join_none endtask endclass Driver driver[]; initial begin // Connect the local virtual interfaces to the top $display("Test.v: There are NUM_XI = interfaces", NUM_XI); if (NUM_XI <= 0) $finish; driver = new[NUM_XI]; //创建driver, 每个DUT 要对应一个driver vxi = top.xi; //XMR跨模块连接。这种是推荐做法，就不用带参数了program automatic test(X_if xi[NUM_XI]); 了。//注意这里其实是把top模块中生成的xi[]数组的句柄传过来的for (int i=0; i《NUM_XI; i++) begin driver[i] = new(vxi[i], i); driver[i].reset; end foreach (driver[i]) driver[i].load; repeat (10) @(vxi[0].cb); $display("@ : Test completed", $time); $finish; end endprogram////////////////////////////////////////////////////////`timescale 1ns/1nsparameter NUM_XI = 2; // Number of interface instancesmodule top; // Clock generator bit clk; initial forever #20 clk = !clk;X_if xi [NUM_XI] (clk); // Instantiate N Xi interfaces// Generate N DUT instances generate for (genvar i=0; i《NUM_XI; i++）begin : dut DUT d (xi[i]); endendgenerate// Instantiate the testbench, overriding the parameter with number of instances test tb();endmodule : top
什么是eMMC和UFS？ 日常选机大家往往关注64GB、128GB等存储空间，却很少像选笔记本关注SSD一样关注手机闪存规格。首先手机储存容量、固态硬盘（SSD）、U盘和SD卡等使用的都是一种叫做NAND的储存介质，NAND闪存是一种非易失性存储技术，即断电后仍能保存数据。它的发展目标就是降低每比特存储成本、提高存储容量。 无论是eMMC还是UFS都是在NAND存储芯片的基础上，再加上了控制芯片，接入标准接口，进行标准封装，形成一个高度集成的储存模块。 手机闪存的设计概念，就是为了简化手机内存储器的使用，将NAND Flash芯片和控制芯片设计成1颗MCP芯片，手机客户只需要采购芯片，不需要处理其它繁琐的NAND Flash兼容性和管理问题。 eMMC和UFS都是面向移动端闪存的标准，其中eMMC全称为“embedded Multi Media Card”，即嵌入式的多媒体存储卡，eMMC从eMMC4.3一路发展到4.4、4.5直到现在的5.0、5.1，传输速度也从50MB/s一路狂飙到200MB/s直到现在eMMC5.1的600MB/s，不过实际使用中读取速率都会低于低于理论值，比如搭载eMMC5.1的手机一般读取速度在250MB/s。 不过对于eMMC来说600MB/s已经是极限，通过并行接口提升接口速率越发困难，接口串行提供了更高速率的可能，这就是后来的UFS标准。UFS使用高速串行接口替代了并行接口，改用了全双工方式，收发数据可以同时进行。 相比eMMC，JEDEC发布了全新的USF 2.0标准在接口速率上提升不少，全新的USF 2.0有两个版本，其中UFS 2.0 HS-G2的理论带宽为5.8Gbps，大约740MB/s，更快速的UFS 2.1 HS-G3的理论带宽更是达到了11.6Gbps，约1.5GB/s。 emmc和ufs的区别_ufs和emmc使用差别 上面这张图很好的反映出了eMMC和UFS两种规格之间的速率差异，而关于手机闪存带宽的提升并没有停止，根据路线图，明年我们有望在手机上UFS3.0的身影，其带宽峰值将超过2.4GB/s。 截至目前，大多数手机会采用eMMC存储接口，但是我们看到eMMC本身存在速率瓶颈，比如不能同时读写，也就是我们在连接电脑导图片的时候，并没有办法同时往手机拷贝数据，另外在传一些大体积的数据时，往往带宽的限制会很慢。 emmc和ufs的区别_ufs和emmc使用差别 而UFS2.X接口无论在性能和速率上都更胜一筹，并真正在改善移动设备的操作体验。如果用一个词来UFS接口带来改善，那就是更快的响应能力。 最新的Android系统已经根据UFS2.0的多线程读取能力进行了优化，一个最直观的变化是我们的手机在读取文件上花费的时间会更少，另外打开应用程序的时间也变短，一旦软件开发人员优化各自的App，充分利用UFS，这种移动体验就会持续得到改善。总而言之UFS2.0的存在让我们的手机看起来反应更快。 实测体验ufs和emmc哪个好？ 前面我们提到UFS比eMMC可以提升文件读写速度，应用到实际使用中主要表现为手机解压大型游戏和加载大型游戏的时间会变短。除了这些，使用会更快的UFS接口的手机要比eMMC在实际表现的差异还是比较明显的，下面这段视频可以很清晰的表现出两种规格的差异。 多任务执行响应速度更快 拥有UFS2.0接口手机，LVDS（低压差分信号）有专门串行接口，读写操作同时进行； CQ（命令）队列动态调配任务，无需等待上一进程结束。相反eMMC接口的手机读写必须分开执行，指令也是打包的，在速度上就已经是略逊一筹，在执行多任务时eMMC自然要慢一步。 低延迟，UFS响应速度快3倍 在读取大型游戏以及大体积文件时，UFS2.0所需时间更短，载入一款游戏所需要的时间为eMMC5.0的1/3，相应在体验游戏时UFS2.0的手机延迟更低，画面更流畅。 连续快拍的照片写入更快 UFS和eMMC体验上的区别还在连续拍照上，连续拍照时UFS让照片写入更快，从下面的对比可以看出，不同接口标准的手机在连续拍照时，因为写入速度的限制eMMC拍摄时从按下快门到存储一张照片花费的时间更长，而UFS缩短了存储时间从而可以让我们可以在同一时间内抓拍到更多照片。 相册图片缩略图载入时间更短 这一点相信大家深有体会，很多人的手机装满了几百张甚至上千张照片，当你打开相册的图片缩略图很明显的看到加载的过程，这就是手机在读取闪存中的照片时更不上刷新的速度造成的。优秀的手机屏幕时画面会随着滑动流畅载入，而差一点的手机就会明显体会到加载时的卡顿。 UFS缩略图载入速度更快 速度快了 功耗也更低 UFS接口在提升了速率之后，也就意味着在相同的任务面前它所花费的时间更短，更高的效率意味着更低的功耗。在同时工作的时候UFS的功耗要比eMMC低出10%，日常工作中大约能省35%的功耗，这对于目前的智能手机续航无疑是更好的选择。 总结：移动设备闪存使用UFS接口标准无论性能、功耗各方面都要优于eMMC，协同处理器让我们的手机在应对更多场景时不会卡顿，购买移动设备时选择UFS闪存的产品在未来5G数据狂潮的年代，显然更有价值。
前言veirlog模块例化的时候，辣么多的信号端子，手动例化又慢又容易出错，葵花妈妈开课啦，孩子手残老犯错怎么办？当然是脚本一劳永逸，妈妈再也不担心手残党。流程（1）在vscode中安装如下插件。（2）在电脑中安装python3以上的环境。下载地址：https://www.python.org/downloads/release/python-373/安装记得一定要勾选添加路径，记得管理员安装。重启你的电脑。在cmd窗口输入python即可验证是否安装成功！（3）安装chardet。为确保插件可用，这个需要安装。参考链接：https://blog.csdn.net/sinat_28631741/article/details/80483064方式一 源码按照：第一步：下载压缩文件，例如： 'chardet-3.0.4.tar.gz'； 第二步：解压文件到python安装位置下的‘site-packages’目录下，例如：‘D:\python2.7\Lib\site-packages’； 第三步：打开终端命令窗口，进入解压的‘chardet’目录下，执行命令：python setup.py install （4）修改插件的原始py文件，觉得开发者的py有瑕疵，让帅气的同事重新整了个，把以下代码替换进原始py文件即可。在打开v文件的vscode下按ctrl+p，输入instance可出现下述界面。替换此py文件的代码即可。 #! /usr/bin/env python ''' vTbgenerator.py -- generate verilog module Testbench generated bench file like this: fifo_sc #( .DATA_WIDTH ( 8 ), .ADDR_WIDTH ( 8 ) ) u_fifo_sc ( .CLK ( CLK ), .RST_N ( RST_N ), .RD_EN ( RD_EN ), .WR_EN ( WR_EN ), .DIN ( DIN [DATA_WIDTH-1 :0] ), .DOUT ( DOUT [DATA_WIDTH-1 :0] ), .EMPTY ( EMPTY ), .FULL ( FULL ) ); Usage: python vTbgenerator.py ModuleFileName.v ''' import random import re import sys from queue import Queue import chardet def delComment(Text): """ removed comment """ single_line_comment = re.compile(r"//(.*)$", re.MULTILINE) multi_line_comment = re.compile(r"/\*(.*?)\*/", re.DOTALL) Text = multi_line_comment.sub('\n', Text) Text = single_line_comment.sub('\n', Text) return Text def delBlock(Text): """ removed task and function block """ Text = re.sub(r'\Wtask\W[\W\w]*?\Wendtask\W', '\n', Text) Text = re.sub(r'\Wfunction\W[\W\w]*?\Wendfunction\W', '\n', Text) return Text def findName(inText): """ find module name and port list""" p = re.search(r'([a-zA-Z_][a-zA-Z_0-9]*)\s*', inText) mo_Name = p.group(0).strip() return mo_Name def paraDeclare(inText, portArr): """ find parameter declare """ pat = r'\s' + portArr + r'\s[\w\W]*?[;,)]' ParaList = re.findall(pat, inText) return ParaList def portDeclare(inText, portArr): """find port declare, Syntax: input [ net_type ] [ signed ] [ range ] list_of_port_identifiers return list as : (port, [range]) """ port_definition = re.compile( r'\b' + portArr + r''' (\s+(wire|reg)\s+)* (\s*signed\s+)* (\s*\[.*?:.*?\]\s*)* (?P<port_list>.*?) (?= \binput\b | \boutput\b | \binout\b | ; | \) ) ''', re.VERBOSE | re.MULTILINE | re.DOTALL ) pList = port_definition.findall(inText) t = [] for ls in pList: if len(ls) >= 2: t = t + portDic(ls[-2:]) return t def portDic(port): """delet as : input a =c &d; return list as : (port, [range]) """ pRe = re.compile(r'(.*?)\s*=.*', re.DOTALL) pRange = port[0] pList = port[1].split(',') pList = [i.strip() for i in pList if i.strip() != ''] pList = [(pRe.sub(r'\1', p), pRange.strip()) for p in pList] return pList def formatPort(AllPortList, isPortRange=1): PortList = AllPortList str = '' if PortList != []: l1 = max([len(i[0]) for i in PortList])+2 l3 = max(18, l1) strList = [] str = ',\n'.join([' ' * 4 + '.' + i[0].ljust(l3) + '(' + (i[0]) + ')' for i in AllPortList]) strList = strList + [str] str = ',\n\n'.join(strList) return str def formatDeclare(PortList, portArr, initial=""): str = '' if PortList != []: str = '\n'.join([portArr.ljust(4) + ' '+(i[1]+min(len(i[1]), 1)*' ' + i[0]) + ';' for i in PortList]) return str def formatPara(ParaList): paraDec = '' paraDef = '' if ParaList != []: s = '\n'.join(ParaList) pat = r'([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*([\w\W]*?)\s*[;,)]' p = re.findall(pat, s) l1 = max([len(i[0]) for i in p]) l2 = max([len(i[1]) for i in p]) paraDec = '\n'.join(['parameter %s = %s;' % (i[0].ljust(l1 + 1), i[1].ljust(l2)) for i in p]) paraDef = '#(\n' + ',\n'.join([' .' + i[0].ljust(l1 + 1) + '( ' + i[1].ljust(l2)+' )' for i in p]) + ')\n' return paraDec, paraDef def portT(inText, ioPadAttr): x = {} count_list = [] order_list = [] for i in ioPadAttr: p = port_index_list(inText, i) for j in p: count_list.append(j) x[j] = i count_list = quick_sort(count_list, 0, len(count_list)-1) for c in count_list: order_list.append(x.get(c)) return order_list def quick_sort(myList, start, end): if start < end: i, j = start, end base = myList[i] while i < j: while (i < j) and (myList[j] >= base): j = j - 1 myList[i] = myList[j] while (i < j) and (myList[i] <= base): i = i + 1 myList[j] = myList[i] myList[i] = base quick_sort(myList, start, i - 1) quick_sort(myList, j + 1, end) return myList def formatPort_order(padAttr, orderList): for p in padAttr: q = Queue() for i in padAttr.get(p): q.put(i) padAttr[p] = q AllPortList = [] for o in orderList: AllPortList.append(padAttr.get(o).get()) return AllPortList def port_index_list(intext, text): l = [] t = intext index = t.find(text) while index > -1: t = t.replace(text, random_str(len(text)), 1) l.append(index) index = t.find(text) return l def random_str(size): s = '' for i in range(size): s += str(random.randint(0, 9)) return s def getPortMap(AllPortList, ioPadAttr): if len(AllPortList) != len(ioPadAttr): return p_map = {} for i in range(len(AllPortList)): p_map[ioPadAttr[i]] = AllPortList[i] return p_map def writeTestBench(input_file): """ write testbench to file """ with open(input_file, 'rb') as f: f_info = chardet.detect(f.read()) f_encoding = f_info['encoding'] with open(input_file, encoding=f_encoding) as inFile: inText = inFile.read() # removed comment,task,function inText = delComment(inText) inText = delBlock(inText) # moduel ... endmodule # moPos_begin = re.search(r'(\b|^)module\b', inText).end() moPos_end = re.search(r'\bendmodule\b', inText).start() inText = inText[moPos_begin:moPos_end] name = findName(inText) paraList = paraDeclare(inText, 'parameter') paraDec, paraDef = formatPara(paraList) ioPadAttr = ['input', 'output', 'inout'] orlder = portT(inText, ioPadAttr) input = portDeclare(inText, ioPadAttr[0]) output = portDeclare(inText, ioPadAttr[1]) inout = portDeclare(inText, ioPadAttr[2]) portList = formatPort(formatPort_order( getPortMap([input, output, inout], ioPadAttr), orlder)) input = formatDeclare(input, 'reg') output = formatDeclare(output, 'wire') inout = formatDeclare(inout, 'wire') # write Instance # module_parameter_port_list if(paraDec != ''): print("// %s Parameters\n%s\n" % (name, paraDec)) # list_of_port_declarations #print("// %s Inputs\n%s\n" % (name, input)) #print("// %s Outputs\n%s\n" % (name, output)) #if(inout != ''): # print("// %s Bidirs\n%s\n" % (name, inout)) print("\n") # UUT print("%s %s inst_%s (\n%s\n);" % (name, paraDef, name, portList)) if __name__ == '__main__': writeTestBench(sys.argv[1]) （5）享受一下吧。比如我们有如下代码：crtl+p，输入instance，按回车即可。 复制粘贴大发好。以上。转载于:https://www.cnblogs.com/kingstacker/p/9944259.html