Appearance
💻 最近发现了一个网站里面内容是一个小游戏nandgame.com
doge 引导玩家从一个与非门开始搭建其他门电路,最终完成一个cpu的搭建,完了一下感觉挺有意思的,顺便还能复习一番计算机组成结构 🏹🦅🦅。虽然还没有通关,但是这里还是大概记录一下过程和思路。后面想看的时候就会比较顺畅。用这种引导式的游戏的方式反而让人觉得还是可以接触的,不像当初学习那会那么抵触,我觉得计算机组成原理改成玩游戏或许更好doge。不过我这么想也可能是因为之前已经学过了的原因。
打开首页关掉提示后长这样,可以点击右边的中文切换成中文语言。本来想演示一下第一关,可是发现他记录了我通过的关卡,重新打开一个tab也是,这样的话只能清除掉我的level了...淦
📅 2020-12-10,我已经到了16位数减法了,记录一下。
nandgame_hexless.png
清除完所有关卡后重新开始的界面:
nandgame_index.png
英文是各部分操作和理解的一个介绍,toolbox工具箱那一列是保存的你已经创建出来的门电路或者组件,现在里面就一个 nand 与非门,右边的蓝色画布是你用来画电路的,下方是输入Input,上方是输出Output,可以将toolbox里面的组件拖到蓝色画布内,如果拖入的组件不想要了,可以拖到toolbox下的垃圾箱内删除。可以看到组件都有输入和输出,比如这个nand与非门,同样的上边是输出,下边是输入,它有a,b两个输入一个输出,拖入画布后可以点击组件的 !查看它的输入与输出表。就像左边的关卡说明最下方的那样,你可以看到nand与非门的输入输出表张这样
输入 输入 输出 a b 0 0 1 1 0 0 0 1 0 1 1 1 输入 输出 a a 0 0 1 1 0 0 0 1 0 1 1 1 canvas.png
点击任意一个输入,比如nand的a输入们就可以看到除了最后的输出以外的所有输出都开始转圈,说明他们都可以进行连接,然后点击其中一个输出,比如全局的输入(也是某种输出),这样全局的输入就与nand的a输入相连了,同样点击全局输入的输入,也是同样的效果。如果连线连接错误了,可以再次点击错误连线的输入就可以重新连接或者放弃这条连接线。接下来就可以连接电路了。
然后看看右边的关卡说明,第一个任务是做一个inv非门,然后给出了非门的输入和输出表,同时我们有一个与非门nand以及它的输入输出表,可以根据已有门电路的输入输出表想办法通过组合和连接线路得到需要的门电路。这就是与非门游戏的关卡。好了,介绍完了现在开始正式通关。
level1.1 非门inv 🤔 非门只有一个输入和一个输出,而与非门有两个输入和一个输出,与非门是输入都为1时才输出0,那么我们可以考虑将全局输入同时连接到与非门的两个输入,这样与非门的两个输入相同,根据与非门的输入输出表同为0时输出为1,同为1时输出为0,这样全局输入与输出不正好是非门的逻辑吗?第一关就这样通关了🚀️nandgame_level1.png
level1.2 与门and 🤔其实这个非常简单,一看and与门要求的输入输出表和nand与非门是正好相反的,而我们刚刚又创建了非门,所以...全局输入连接与非门的输入,与非门的输出连接非门inv的输入,inv的输出连接全局输出就可以了。其实从字面意思上看与非门和与门,不就是差了一个 非 吗?,从英文来看nand和and直接也只差了一个字母n.
nandgame_and.png
level1.3 或门or 💭 这个确实有点难度,虽然说上次做过一次了,但是再看的时候还是难住我了。可以这样思考,与非门的逻辑是当两个输入均为1时才输出0,而或门or的逻辑是当两个输入均为0时才输出0,所以只要我门将与非门的输入都变成相反的,就可以实现当两个输入均为0时才输出0的逻辑。nandgame_or.png
[X]level1.4 异或门xor 🤔 这个相对简单,两个输入不同时输出为1,相同时输出为0,通过观察或门or和与非门nand的逻辑,他们的交集就是两个输入不同时输出为1,而交集的表示正好是and与门,所以就得到了答案。nandgame_xor.png
恭喜!你已经做完了基础逻辑门。
接下来是算数元件,因为处理器要能做加减法。
第一个任务是将两个1位数字加载一起。结果是一个2位数字
你需要理解二进制才能解这道题。
level2.1 半加器 doge 这个很简单,前面如果你不是直接看答案的话那几个基础门电路的输入输出表你已经很熟悉了,这个半加器的输入输出表,虽然有两个输出,但是卐🐉一眼就能看出高位h是输入ab相与的结果,低位i是输入ab相异或的结果
h = a and b; i = a xor b 所以,答案已经出来了。nandgame_add.png
你已经完成了一个能加两个比特的加法器。
但是要想加更多位数,我们需要将前一位的"进位"考虑进去。
本关的任务是做一个三位加法器,将a、b和c加在一起。这里c是上一位的进位。
level2.2 加法器 其实加法器也很简单,关键在于理解其中的逻辑,既然上面我们已经有了半加器,那理所当然的想到将两个半加器叠加得到新的加法器,但是具体怎么叠加呢,我们可以思考一下自己做三个数加法的思路,那就是化繁为简
a+b+c=(a+b)+c 先将前两个数相加得到一个中间数,再用中间数和c相加得到结果,关键是abc三个一位数相加的时候,前两个ab相加会得到一个高位h1和低位i1,然后我们用低位i1和第三个数c相加,这样又得到一个高位h2和一个低位h2,我们可以确定最终输出的低位肯定就是h2,但是最终的高位呢?其实仔细一想就会有一个很清楚的规则,那就是这两个高位h1和h2肯定不会同时是1,为什么呢?因为三个一位二进制数最大是1,加起来也就3而已,怎么可能再次进位得到4呢?这种情况下,我们发现,两个高位同时为0时,都没有向第二位进位,那么高位肯定为0,但是只要有一位为1,那么最终高位必须是1,这不正是或or的逻辑吗,所以高位就是h1与h2相或的结果。
h1 = (a add b)[0]; i1 = (a add b)[1];
h2 = (h1 add c)[0]; i2 = (h1 add c)[1];
h = h1 or h2; i = i2; nandgame_add2.png
现在我们做一个加法器,它能对两个2位数(以及1位进位)做加法。
我们可以通过叠加2位加法器来加更大的数字。
level2.3 多位加法器 doge 有了刚才的加法器的训练,做两位数的加法器也很简单,基本上分清楚 两位数的概念,就很容易了,显然两位数加法器只是比一位数加法器多了一位而已,那么我们直接叠加即可,具体怎么叠加呢?还记得我们算多位数加法的时候是怎么算的吗?小数点对齐,从地位到高位依次相加,主要是有一个进位。所以就很easy了。
2 components used. 38 nand gates in total.
This is the simplest possible solution!
可加2位二进制数的元件可以通过叠加实现任意多位数的加法。
既然我们要做的是一个16位处理器,我们将它叠加到能加16位数,加入你的工具箱,并叫它add 16.
nandgame_addmore.png
现在你可以开始操纵16位数了。
你的任务是做一个自增元件,能在一个16位数的基础上加一。
既然现在操纵的是一个16位数,我们在图表中 简化了表达方法:用一个端口来代替16条端口。这样的16位端口会带着一个小小的"16"标记。
level2.4 自增器 🚀️ 自增的话就是简单的加1,直接将输入随便连到16位加法器的任意一位,然后对0取反得到1连接到16位加法器的进位为c,将16位加法器的S位连接到全局输出即可,因为忽略进位位,这样就直接完成了。nandgame_hexadd1.png
现在,做一个减法元件,从一个16位数减掉另一个。
level2.5 16位减法器 这个似乎有点难度,但是看关卡说明的话其实可以发现一点,那就是给出了负数的表示,这样我们根据公式a-b=a+(-b)就可以将减法转化为加法,而加法器我们已经有了,问题就变成了负数怎么表示,看起来这个游戏似乎在引导我们用简单事物构造复杂事物的方式去思考。负数的表示根据关卡表中给出的数据,我们可以发现一些规律🌔,其实这里看到
(这就是二的补码)
的时候就想起了"按位取反加1"的口诀,然后看了一下,负数的表示确实是其对应的正数按位取反再加一的结果,当然看到toolbox里给出的组件包括了16进制数按位取反和16进制数自增的时候这个就已经不是暗示了😂
结果 16位进制数 无符号十进制 1 0000000000000001 1 0 0000000000000000 0 -1 1111111111111111 65535 -2 1111111111111110 65534 -3 1111111111111101 65533 到这里就很简单了,将输入B按位取反再自增,然后与A相加得到的16进制数S就是我们所要的结果了
nandgame_hexsub.png
恭喜,你已经完成了基本四则运算的元件。现代处理器支持更复杂的运算,比如乘法、除法和浮点数运算, 但在这个游戏里,我们尽可能保持简单,做一个最简的、能工作的处理器。
现在,做一个元件来判断一个数字是否为0。首先我们实现在一个4位数上的判断。
level2.6 等于0判断 🍵 这个其实很简单,4位数都为0则输出1,都为0则怎么样 立马就想到了or或运算
nandgame_hex_equ0.png
同样的方法很容易延申到16位数。因此我们也做出了检查16位数是否为0的元件。
level2.7 判断16进制负数 现在做一个元件,判断一个16位数是否为负数。
如果第15位是1,那么这个数就小于0。### 位的序号
我们将数字的每一位从右往左标上序号。最右边是第0位。因此,在一个16位的字中,最左边一位是第15位。
输入 输出 输入>=0 0 输入<0 1 😂 这明示的有点过分了啊,关卡说明直接这么说判断第15位,然后toolbox直接给出了将一个16进制数分割为16个独立端口的splitter组件,所以答案已经有了
nandgame_hex_isless0.png
level3.1 选择器 选择器
选择器元件选择两个输入中的一个作为输出。
s为选择比特,决定选择哪个输入: 为0时,选择d0;为1时,选择d1。
🤔 这个似乎有点难了。想了很久,最后终于想到了,要让选择器实现2选1,关键是如何实现s为0时让d0生效同时让d1失效,s为1时如何让d1生效同时让d0失效,关于失效和生效的定义该怎么理解?如何让一个输入决定最终的输入呢?最简单的模型就是改造或or门电路,让输入a为0,那么输出就可以保持与输入b一致起来。所以问题就变成了当s为0时让d1变为0,当s为1时让d0变为0。那么又如何实现当s为0时让d1变为0并且当s为1时d1的输出不变呢?这还不简单,直接让s和d1相与不就实现了吗?同样的,如何实现s为1时让d0变为0并且当s为0时d0的输出不变?让d0和s的非相与即可啊。这样的话不就整个都解决了吗?
nandgame_selector.png
虽然提示说还可以有更少的门电路,但是我看了网上的其他人的发现这个就是最少的门电路了,可能是因为之前提交了一个门电路较多的也过了,所以有影响。
这里我的思路有点啰嗦,网上说根据真值表可以直接得到😂是我太菜了。
所以我们需要记住这个案例,选择器的实现。
level3.2 开关 接下来,做一个开关,将数据比特送到2个输出中的一个。
这个就easy很多了,就像大佬说的,我们现在应该具备根据真值表就能直接得出公式的能力。显然,我们能一眼看出c1是s和d相与的关系,而c0的话我们要稍微观察一下就会发现,真值表中只有第二行c0为0,而对比这一行与其他三行的关系我们也可以发现c0是d与c1相异或的关系,这里的c0是其中一个输出c1作为输入。
c1 = s and d; c0 = c1 xor d; nandgame_switch.png
level3.3 锁存器 迄今为止,我们做的元件都没有记忆功能。
你的任务是做一个锁存器,一个能存储一个比特的元件。
锁存器能存储并输出一个比特。
当st(存储位)为1,d上的数值将被存储并输出出去。
当st为0,d的数值将被忽略,依然输出之前存储的数值。
🔒 锁存器实现的关键在于st为0时d的数值被忽略,依然输出之前存储的数值。我们需要利用到之前的选择器selector,可以看到toolbox里也给出了。根据选择器的逻辑,将st接s,d接d1就可以实现st为1时,d的数值被输出,但是st为0时如何输出之前的值呢?我们有了选择器,当st为0时其实是要输出d0的值,而d0必须为之前选择器输出的值,那么如何做呢?只需要将选择器的输出接到d0不就实现了吗?这里用到的将前一步的输出作为这一步的输入的思想,就是将输出反过来再接输入。
nandgame_Latch.png
level3.4 触发器 用锁存器可以做一个状态随时间改变的电路。
但现在有一个问题:因为电路里的状态并不同步改变, 电路中的状态就会以不可预知的方式传播,让计算的结果不可预测(这种情况被叫做“竞争条件”)。
解决的方法是使用时钟信号:它能按照节奏自动改变输出的比特位,并输送到所有连接的状态元件中。
如果元件只在时钟信号改变的时候改变,那么电路中所有的元件就会同时发生变化, 我们也就解决了同步问题。
本关中你将制作一个触发器元件,在时钟信号为0时存储一个比特, 在时钟信号变为1的时候开始输出这个存储的比特。