北航计算机学院-计算机组成原理课程设计-2020秋

PreProject-Logisim-斐波那契数列问题(简单迭代法+矩阵乘法的快速幂)


从本节开始,课程组给出的教程中增添了很多视频讲解。为了避免侵权,本系列博客将不会搬运课程组的视频讲解,而对于文字讲解也会相应地加以调整,重点在于根据笔者自己的理解给出习题的解析。因此带来的讲解不到位敬请见谅。


文章目录

  • 北航计算机学院-计算机组成原理课程设计-2020秋
  • PreProject-Logisim-斐波那契数列问题(简单迭代法+矩阵乘法的快速幂)
  • 斐波那契数列问题的简单迭代法
  • 斐波那契数列-矩阵乘法快速幂法
    • 斐波那契数与矩阵乘法
    • 快速幂
    • 快速幂的迭代表达
    • Logisim电路实现

斐波那契数列问题的简单迭代法

提交要求

使用Logisim搭建一个根据输入序号x计算对应序号斐波那契数fib[x]的电路(输入序号0对应输出数0,输入序号1对应输出数1,输入序号2对应输出数1,以此类推)并提交。

  • 输入: N(3bit)
  • 输出: Nth(4bit)
  • 文件内模块名: main
  • 测试要求:每次给定一个固定输入保持不变,电路在64个周期内计算出结果并稳定输出,在结果未计算出之前输出端口输出0
  • 请使用时序逻辑完成本题目。未按照要求(或采用捷径)完成并提交通过的同学,请重新提交。

提到斐波那契数列,想必大家小学二年级就已经学过了经典的1, 1, 2, 3, 5, 8, …,在程序设计入门的时候大家也一定写过斐波那契数的实现代码,最直观地,我们通过定义可以写出递归版本的斐波那契数(以下展示C语言版本):

int fib(int n) {if (n <= 2) return 1; // fib(1) = fib(2) = 1return fib(n - 1) + fib(n - 2);
}

但是放在本题,是无法用这样的思路直接实现电路的,因为递归实质上是操作系统在不断索取硬件资源,我们无法让电路实现递归式的栈增长,因此需要考虑迭代的方法:

int fib(int n) {int prev = 1, now = 0; // fib(-1) = 1, fib(0) = 0;for (int i = 0; i < n; i++) {now = now + prev;prev = now - prev;}return now;
}

我们有没有可能用电路实现循环控制呢?答案是肯定的。我们可以使用寄存器来存储变量的值,然后通过其他运算器件在相邻时钟上升沿之间改变寄存器的输入,更新寄存器的值,最后通过计数器确定运算的次数进行输出即可。

题外话,题目所述的“必须采用时序逻辑电路”、“禁止走捷径”,走捷径的方法是什么呢?很容易,输入只有3位,我们将输入范围内的斐波那契数全部存储在RAM里,根据输入读出该值即可(大家不要采用这种方法。

下面的介绍将自顶向下地描述电路构建成果。首先电路在顶层之下划分出“计数逻辑”和“计算逻辑”两个子电路,“计算逻辑”是整个电路的核心,其功能主要是计算出不断更新的fib(n)的值;“计数逻辑”是电路正确输出的保障,其主要功能是给出电路何时应当输出答案的信号。两者配合达到的效果是:一端不断更新fib(n)数列,另一端在合适的时候输出数列的当前值,就像是列车的发动机只负责提供向前的动力,而控制系统掌管列车何时到站停下。

顶层电路的设计如下图所示。 按照题目要求,左侧三位输入是待求fib(n)中的n,右侧四位输出是fib(n)的值,在计算得到结果之前输出0,得到正确结果之后稳定输出fib(n)。为了增强电路的可读性,我在设计中尽可能多地使用了Tunnel来说明每一根信号的功能。

左下方的reset信号是在设计过程中为了方便调试添加的复位信号,在调试时将该信号设置为1则整个电路回到初始状态,子电路中所有计数器和寄存器的值全部清零。该信号单纯为了调试方便,和整体的功能没有直接关系,可以直接删除。该信号接入了两个子电路,下文的子电路介绍中不再阐述。

上面已经提到,计数逻辑子电路主要功能是给出电路何时应当输出答案的信号,当计数逻辑右侧的输出信号output_signal为0时,右侧多路选择器选择常数0,输出保持为0,而当output_signal为1时,将输出计算逻辑子电路的输出值,也就是计算得到的正确答案。上文同样提到,计算逻辑将会不断地更新fib(n)数列,而并不关心输出值如何稳定保持,因此output_signal信号会被接入计算逻辑,在应当输出的时候,通过寄存器enable端口冻结计算逻辑中寄存器的值,让计算逻辑的输出一直保持为正确输出,这样就实现了“得到结果前一直输出0,得到结果后该结果稳定输出”的效果。

计算逻辑子电路的设计如下图所示。 首先观察中部两个寄存器的设计,回顾在前面的分析中我们得到的fib(n)算法,其中循环体里用到了两个变量prev和now,分别表示数列当前值和上一个值,初始时要对两个值赋初值,分别表示fib(-1) = 1, fib(0) = 0,随后开始的循环中now = now + prev; prev = now - prev。

根据这样的思路,我们使用两个寄存器来分别存储prev和now的值,在每一次循环中,now寄存器应当被更新为now + prev,那么只需将两个寄存器输出端口求和接入now寄存器即可。而对于prev寄存器,只需要直接接入now的输出值即可,而不是计算now - prev,这是由于我们书写在C语言中的算法,其语句是顺序执行的,在now被更新后就必须使用now - prev的值作为新的prev值,而在此处prev和now是同时更新的。换句话说,正确的算法是prev’ = now’ - prev = now,因此直接接入now的输出即可。根据这样的特点我们也会发现,原本每个循环需要执行两次的循环体,在电路实现中只需要一个时钟周期就可以完成。

真正的计算很简单,但还有一个棘手的问题,就是如何优雅地为寄存器赋初值。考虑到电路的应用性,我们不应当每次手动为寄存器赋值为1,因此需要考虑设计一种机制,让第一个时钟周期给prev寄存器赋值为1,now寄存器赋值为0,而在后面的时钟周期根据上面一段叙述的逻辑改变两者的值。在此处我采用的解决办法是使用一个1位计数器,并设置计数器达到上限后"stay at value",保持最大值不变(具体设置方法如下图),这样一来该计数器只有第一个时钟周期输出0,后面一直输出1,而此时将其输出作为选择信号接入多路选择器,就实现了prev寄存器第一个周期赋初值,后面的周期赋值为now。而对于now寄存器,第一个周期now和prev之和本身就是0,无需特殊处理。最终电路始终输出now的值即可。

在顶层电路的描述中我们提到,为了保证得到正确答案后稳定输出,我们采用的方式是将计数逻辑输出信号接入计算逻辑,也就是左下方的freeze信号,当该信号为1时,代表外部提醒:已经得到正确答案了,请冻结输出。此时将freeze信号取反接入两个寄存器的enable端即可,没有得到正确答案前寄存器正常工作,一旦得到冻结信号,寄存器停止工作,稳定输出当前值。

分析过后我们考虑电路的计算特点:在第一个周期为寄存器赋初值,其后的每一个周期输出当前值,那么计算fib(n)就需要(n+1)个时钟周期,具体来说就是(n+1)个时钟上升沿,这条结论就指导了我们下一步设计计数逻辑,应当在(n+1)个时钟上升沿过后再发出输出指令。就像列车的目标是前进100公里,我们只有知道了发动机每小时让列车前进例如50公里,才能够设计控制系统,让列车在2小时之后停下。

**计数逻辑子电路如下图所示。**有了上面计算逻辑的分析和构建,下面我们很容易设计计数逻辑:让该子电路在第(n+1)个时钟上升沿之前都输出0,代表着此时还未计算出结果,在第(n+1)个上升沿之后输出1,代表此时已得到正确结果,计算逻辑冻结并输出当前值。下面的电路中只需要使用counter,当计数器的值小于等于输入n时均输出0,随后输出1即可,注意此处的counter也需要设置到达最大值时"stay at value"。

至此整个电路已经构建完毕了。本设计更注重的是模块化的思路,而不在乎一两个元件的得失,例如我们完全可以将计数逻辑中counter的值作为一路信号接入计算逻辑,可以省去计算逻辑中赋初值所用的counter,但是本设计并没有这么做,而是让两个逻辑子电路尽可能地解耦。当然,这也只是笔者个人想法,应当有更精巧的思路值得大家探索。笔者认为,不管是软件还是硬件设计,有时候工程化模块化的设计布局,工整良好的可读性,可维护和可扩展性,是比小技巧更值得我们注意的关键点。


斐波那契数列-矩阵乘法快速幂法

黄小板同学暗中观察了公司负责人很久,觉得他搭建的电路性能实在太差,他提出只需要64个周期就能计算出32位无符号整数能表示的最大数位置上的斐波那契数的(最后32bit),在完成搭建这样的电路后,公司负责人五体投地,宣布给黄小板开出了东门烤串无限量供应的实习工资,从此黄小板每日吃串,终于吃成了黄老板…

那么,这个电路是什么样子的呢?

注意:这道题是一个对你的挑战,需要一定的算法和工程能力,请谨慎思考,大胆尝试!

提交要求

使用Logisim搭建一个根据输入序号x计算对应序号斐波那契数fib[x]的电路(输入序号0对应输出数0,输入序号1对应输出数1,输入序号2对应输出数1,以此类推)并提交。

  • 输入: N(32bit无符号数)
  • 输出: Nth(32bit无符号数,表示第N个斐波那契数)
  • 文件内模块名: main
  • 测试要求:在64个周期内计算出结果并稳定输出,在结果未计算出之前输出端口输出0。
  • HINT:矩阵乘法的快速幂

观察此题,和上题一样都是计算斐波那契数,区别仅仅在于输入从3位变成了32位无符号整数,而时间要求仍然是64个时钟周期。我们试想,如果仍用上面的电路会如何?该电路计算fib(n)需要(n+1)个时钟周期,此时输入变为32位,那么最大输入则为232-1,需要232个时钟周期,这远远超过了64个。换言之,想要在64个时钟周期内计算出32位无符号整数输入,算法的复杂度应当为O(logn)级的。有没有可能实现这样的算法呢?

题目的最后一句给出HINT:矩阵乘法的快速幂。小伙伴们立马懵逼了,矩阵乘法和斐波那契数有啥关系呀,还快速幂?这一概念我们拆开来看,先是矩阵乘法,后是快速幂

斐波那契数与矩阵乘法

斐波那契数的通项我们都知道,fib(n)=fib(n−1)+fib(n−2)fib(n) = fib(n-1) + fib(n-2)fib(n)=fib(n−1)+fib(n−2) ,但现在我们多写一项,写成如下形式:
fib(n)=1∗fib(n−1)+1∗fib(n−2)fib(n−1)=1∗fib(n−1)+0∗fib(n−2)fib(n) = 1 * fib(n-1) + 1 * fib(n-2)\\ fib(n-1) = 1 * fib(n-1) + 0 * fib(n-2) fib(n)=1∗fib(n−1)+1∗fib(n−2)fib(n−1)=1∗fib(n−1)+0∗fib(n−2)

为什么要多写一次没用的第二项呢?受上面一道题的启发,如果要将原本复杂度为O(2n)的算法,我们多使用了一个变量空间,同时更新fib(n)和fib(n-1),将上一个斐波那契数的值保留下来,是一种“空间换时间”的策略,那么此时我们所做的操作和上一道题是完全一样的,想要实现同时更新fib(n)和fib(n-1)。换言之,在“矩阵乘法”部分,我们相对上一题还没有实现任何优化,而仅仅是改换了计算形式。

接着,根据我们刚刚学过的线性代数,引入矩阵乘法如下:

(1110)∗(fib(n−1)fib(n−2))=(1∗fib(n−1)+1∗fib(n−2)1∗fib(n−1)+0∗fib(n−2))=(fib(n)fib(n−1))\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} * \begin{pmatrix} fib(n-1) \\ fib(n-2) \end{pmatrix} = \begin{pmatrix} 1 * fib(n-1) + 1 * fib(n-2) \\ 1 * fib(n-1) + 0 * fib(n-2) \end{pmatrix} = \begin{pmatrix} fib(n) \\ fib(n-1) \end{pmatrix} (11​10​)∗(fib(n−1)fib(n−2)​)=(1∗fib(n−1)+1∗fib(n−2)1∗fib(n−1)+0∗fib(n−2)​)=(fib(n)fib(n−1)​)

由此我们得到了全新的,一次更新两项的递推公式:

(1110)∗(fib(n−1)fib(n−2))=(fib(n)fib(n−1))\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} * \begin{pmatrix} fib(n-1) \\ fib(n-2) \end{pmatrix} = \begin{pmatrix} fib(n) \\ fib(n-1) \end{pmatrix} (11​10​)∗(fib(n−1)fib(n−2)​)=(fib(n)fib(n−1)​)

记(fib(n)fib(n−1))=FIB(n),(1110)=A,记 \begin{pmatrix} fib(n) \\ fib(n-1) \end{pmatrix} = FIB(n), \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = A, 记(fib(n)fib(n−1)​)=FIB(n),(11​10​)=A,

则有FIB(n)=A∗FIB(n−1),其中FIB(0)=(fib(0)fib(−1))=(01)则有\space FIB(n) = A * FIB(n-1), \space 其中 \space FIB(0) = \begin{pmatrix} fib(0) \\ fib(-1) \end{pmatrix} = \begin{pmatrix} 0 \\ 1 \end{pmatrix} 则有 FIB(n)=A∗FIB(n−1), 其中 FIB(0)=(fib(0)fib(−1)​)=(01​)

我们惊喜地发现,这一版本的递推公式不仅可以一次更新两项,还成功地将加法转化为乘法,将二阶递推式转化为一阶递推式,将加和关系转化为等比数列。又根据矩阵乘法满足交换律,该递推式容易得到通项如下:
FIB(n)=An∗FIB(0)=(1110)n∗(01)FIB(n) = A^n * FIB(0) = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n * \begin{pmatrix} 0 \\ 1 \end{pmatrix} FIB(n)=An∗FIB(0)=(11​10​)n∗(01​)
根据线性代数学过的知识,2*2的矩阵不管多少次乘方,其结果都还是2*2的矩阵,
记(1110)n=(abcd),则FIB(n)=(fib(n)fib(n−1))=(1110)n∗(01)=(a∗0+b∗1c∗0+d∗1)=(bd)记 \space \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n = \begin{pmatrix} a & b \\ c & d \end{pmatrix}, \space 则 \space FIB(n)= \begin{pmatrix} fib(n) \\ fib(n-1) \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n * \begin{pmatrix} 0 \\ 1 \end{pmatrix} = \begin{pmatrix} a * 0 + b * 1 \\ c * 0 + d * 1 \end{pmatrix} = \begin{pmatrix} b \\ d \end{pmatrix} 记 (11​10​)n=(ac​bd​), 则 FIB(n)=(fib(n)fib(n−1)​)=(11​10​)n∗(01​)=(a∗0+b∗1c∗0+d∗1​)=(bd​)
也就是说,我们所关心的fib(n),只是矩阵A的n次乘方,这一2*2矩阵的右上角的数。

有了以上的分析,我们的目标很明确:计算A的n次乘方即可。但小伙伴们会发现,这一顿操作猛如虎,结果好像并没有改进算法的时间复杂度:矩阵的n次乘方还是要计算n次,毕竟Logisim并没有内置一次计算某数n次方的元件,更不用说矩阵还同时涉及乘法和加法。因此有了矩阵乘法的改写还远远不够,下面我们引入快速幂。

快速幂

先做一个引入的思考:假设每次只能做两个数之间加减乘除的运算,对于一个整数a,要求an,应当如何设计算法?

最简单直观的算法:引入一个变量s赋初值为1,使用n次循环,每次为s乘上一个a,最终s = an,如下:

int power(int a, int n) { // 计算 a^n 的值 int s = 1;for (int i = 0; i < n; i++)s *= a;return s;
}

这种算法的时间复杂度显然是O(n),有没有什么办法更快地计算a的n次幂呢?我们思考,假设我们已经计算得到了22=4,那么要求24就无需再花两次的时间计算23 = 22 * 2, 24 = 23 * 2,可以直接进行计算:24 = 22 * 22,只需要一次计算。更进一步地,我们会发现在每次只能做两个数的乘法的条件下,相乘的两数尽可能大,就可以尽可能快地接近待求结果。例如:
a17=(a8∗a8)∗a=((a4∗a4)2)∗a=((a2∗a2)2)2∗a=(((a2)2)2)2∗aa^{17} = (a^8 * a^8) * a = ((a^4 * a^4)^2)*a=((a^2*a^2)^2)^2*a=(((a^2)^2)^2)^2*a a17=(a8∗a8)∗a=((a4∗a4)2)∗a=((a2∗a2)2)2∗a=(((a2)2)2)2∗a
将高次幂向下拆解的过程中,每次尽可能使用最大的两数平方来逼近高次幂,这样原本需要计算17次的a17现在只需要计算5次。在分析中我们还可以发现,当前幂次如果是偶数,就可以直接分解为两个数的平方:a2n = (an)2,需要一次计算;而如果当前幂次为奇数,则必须分解为两个数的平方再乘底数:a2n+1 = (an)2 * a,需要两次计算(一次算两数平方,一次算平方后再与底数相乘),根据这样的规律可以得到递推关系如下:

power(a,n)={power(a,n/2)∗power(a,n/2)n为偶数,且 n > 0power(a,[n/2])∗power(a,[n/2])∗an为奇数,[x]表示不超过x的最大整数1n = 0power(a, n)= \begin{cases} power(a, n/2) * power(a, n/2)& \text{n为偶数,且 n > 0}\\ power(a, [n/2]) * power(a, [n/2]) * a& \text{n为奇数,[x]表示不超过x的最大整数}\\ 1 & \text{n = 0} \end{cases} power(a,n)=⎩⎪⎨⎪⎧​power(a,n/2)∗power(a,n/2)power(a,[n/2])∗power(a,[n/2])∗a1​n为偶数,且 n > 0n为奇数,[x]表示不超过x的最大整数n = 0​

根据递推关系就可以直接写出如下的递归算法(逻辑与(n&1)用以快速判定奇偶,内联函数用以减少power()的调用,逻辑右移(n >> 1)用以快速实现n / 2取整):

inline int sqr(int a) {return a * a;
}int power(int a, int n) {if (n == 0) return 1;return (n & 1) ? sqr(power(a, n >> 1)) * a : sqr(power(a, n >> 1));
}

分析这一递归算法,每一递归实例唯一地调用下一个递归实例,每次调用时递归变量a不变,n减小为原来的一半,直到n = 0,因此递归深度应当为log2n;而在每个递归实例之中,n为偶数时需要1次计算(一次平方即可),n为奇数时需要2次计算(一次算平方,一次算平方的结果再乘底数a),因此整体的算法最多计算2log2n次(每次遇到的n都是奇数),最少计算log2n次(每次遇到的n都是偶数),时间复杂度O(logn),成功地实现了将O(n)降为O(logn)。这便是快速幂的思想,其本质是一种分治算法。

这种快速幂算法能否移植到矩阵乘法上呢?也就是说,上文中的整数a能否替换为矩阵A呢?答案是肯定的,因为矩阵乘法满足结合律,并且同阶的方阵相乘后,其结果仍是与原来阶数相同的方阵。只需要将上面算法中的整数乘法全部替换为矩阵阵法,就得到了矩阵乘法的快速幂算法。

快速幂的迭代表达

当我们欣喜地发现已经找到O(logn)计算n次幂的算法后,现实的问题又摆在了眼前:硬件设计中我们无法使用递归。因此与上一题O(n)算法一样,我们需要将递归改写为迭代,通过寄存器的更新和外部计数控制来实现循环。如果你的数据结构功底扎实,将递归改写为迭代应该不是什么困难的事,你可以跳过本节的讲解。

本题将递归改为迭代主要的难点在于,递归的过程中有针对当前递归值的判断:当前的n若为奇数,则取 (an/2)2∗a(a^{n/2})^2*a(an/2)2∗a ,若为偶数则取 (an/2)2(a^{n/2})^2(an/2)2 ,在反向后自底向上迭代的过程中,无法确定当前的i是由奇数n还是偶数n递归而来。事实上大部分有难度的递归改迭代,其难点都在于反向后的计算逻辑和正向的有较大差别,需要分析计算的本质。

针对递归中的奇偶分类判断,我们做如下的思考:先从极端情况考虑,什么样的数n每次递归实例都是偶数?什么样的数n每次递归实例都是奇数?第一个问题容易回答,如果n是2的幂,n = 2k,那么每次n >> 1之后仍旧是偶数,这意味着n的二进制展开除了最高位之外每一位都是0(这样右移一位之后最低位是0,仍是偶数);有了第一个问题,第二个问题也有了思路:如果每次n >> 1后都是奇数,这意味着n的二进制每一位都是1(这样右移一位之后最低位还是1,仍是奇数)。推广到一般情况:任意数n在递归过程中有多少次奇数多少次偶数?这取决于n的二进制展开中有几个1和几个0,1的个数就是递归实例为奇数的个数。

有了这样的启发,我们可以重新思考快速幂的计算过程:

a13=a(1101)b=a(1000)b+(0100)b+(0001)b=a8+4+1=a8∗a4∗a1=((a2)2)2∗(a2)2∗aa^{13} = a^{(1101)_b} = a^{(1000)_b+(0100)_b+(0001)_b} = a^{8+4+1}\\ = a^8 * a^4 * a^1 = ((a^2)^2)^2 * (a^2)^2 * a a13=a(1101)b​=a(1000)b​+(0100)b​+(0001)b​=a8+4+1=a8∗a4∗a1=((a2)2)2∗(a2)2∗a
在上面的例子中,假设幂次n本身是2的幂,其二进制展开除了最高位都是0,那么最终形式里不会有多余的乘号(只会是a的log2n次平方),计算log2n次平方即结束。该例子中的13次方,二进制展开后有3个1,最终形式里有3个乘号,多算3次,这与递归方法中的特点相吻合。重要的是,以这种形式展开后,计算过程是自底向上的:我们想象一个乘子m,其初始值为a,每次更新让m变为自身的平方(1次计算),同时扫描幂次n的二进制展开位,当扫描到当前位是1时,意味着“需要多一次乘法了”,就将当前乘子的值乘入最终结果(多1次计算)。仍旧分析上面的例子,初始乘子为a,扫描到13最低位是1,此时将乘子a乘入最终结果(最终形式中最后一项"a");第二轮乘子更新为自身的平方a2,扫描到13二进制展开次低位为0,无需将乘子乘入最终结果;第三轮乘子更新为自身的平方(a2)2,扫描到13二进制展开次高位为1,将乘子乘入最终结果(最终形式中的第二项"(a2)2");第四轮乘子更新为自身的平方((a2)2)2,扫描到13二进制展开最高位为1,将乘子乘入最终结果(最终形式中的第一项"((a2)2)2"),四轮结束后算法结束,得到了最终形式的正确结果。

int power(int a, int n) {int s = 1; // 存储最终结果 while (n > 0) { // 依次扫描 n 的每个二进制位直到 n = 0 if (n & 1) s *= a; // 如果当前二进制位为 1,则将乘子当前值乘入最终结果 a *= a; // 乘子更新为自身的平方 n >>= 1; // n 向右移位,扫描下一位 }return s;
}

Logisim电路实现

有了上面大篇幅的算法理论分析,接下来就是在电路中实践了——将上面的C语言算法转化为Logisim电路。

如上图,本次工程延续了上一道题中,计算逻辑和计数逻辑解耦的基本结构,但由于本题设计矩阵乘法,存储和计算所用的元件比较多,根据模块化设计思想拆分出了较多的子电路,分别设计和调试以实现高效地开发。本节的讲解我们首先介绍作为支持功能的几个子电路。

上图展示了寄存器矩阵子电路。由于计算逻辑中矩阵乘法需要以整个矩阵的形式一次存储四个数,摆放过多的寄存器显得臃肿杂乱,因此将这部分单独拆开为子电路,其功能和结构非常明确,4个32位寄存器,其输入输出接口和传统寄存器完全一致。

上图展示了矩阵乘法子电路。在前文的算法分析中可以看出,我们需要在计算逻辑中作2*2矩阵的乘法,因此讲乘法过程单独拆开作为子电路。2*2矩阵乘法的计算也相对简单,套用公式用元件计算相应结果的输出即可。

上图展示了矩阵MUX子电路。该子电路的构建是由于计算逻辑中,我们会遇到需要选择两个矩阵中的一个进行运算的情况(例如当前n的末位为1时乘入乘子的值,为0则乘单位阵),因此为了方便一次选择一组四个数,我们将四个多路选择器组合单独设计为子电路,由一路1位信号选择两个矩阵的其中一个作为输出。

上图展示了矩阵初始值子电路和单位阵子电路。没错,我将常数单独封装为子电路应用于设计中,这可能并不符合某些设计原则,但可以在上层电路中节省空间,让电路更加简洁。矩阵初始值,也是乘方的底数矩阵;任意矩阵与单位阵作乘法后保持不变,用以实现“若当前n最低位为0则不将乘子乘入最终结果”。

上图展示了作为本项目核心的计算逻辑子电路。这一子电路中的核心在于中部靠右侧的两个寄存器矩阵:矩阵A和矩阵S。矩阵A作为乘子,第一个时钟周期存入初始值,后面每个时钟周期更新为自身的平方,因此其左侧使用一个矩阵MUX选择初值或自身平方,而自身平方通过矩阵乘法单元进行计算(初始值的存入和上一道题相同,使用了1位counter,并选择达到最大值时stay at value,不再赘述)。矩阵S作为存储最终结果的矩阵,其左侧在初始时存入单位阵(相当于int s = 1;),其后的每一个周期根据当前n的最低位进行判断,如果最低位为0则乘单位阵(不改变),如果为1则乘入当前乘子的值。有了这样的核心计算逻辑,电路可以不断计算出当前最终结果。

和上题一样的点在于,我们仍旧使用计数逻辑来提示计算是否结束,是否可以输出计算结果,这一功能使用freeze信号来表示,最右侧的输出在计算得到正确结果之前一直输出0,freeze信号为1时输出当前结果。同样的为了调试方便增设了重置信号clr。而与上一题不同的是,n的最低位也由计数逻辑来计算生成并传入计算逻辑,以保证计算逻辑知晓当前的乘子是否应当乘入最终结果。

上图展示了计数逻辑子电路。本题的计数逻辑和上一题的计数逻辑有较大差别,因为本题只需要每次将n右移一位,当n等于0时即可宣告计算结束,无需等待固定个时钟周期。因此我们看到右上方使用寄存器来存储并更新n的值,初始存入输入值input,随后每个时钟周期将其右移一位,右下方的输出等待n为0时向外部报告计算结束。此外,计数逻辑需要向计算逻辑报告每一个时钟周期当前n的最低位,以便计算逻辑判断是否将乘子乘入最终结果,因此右侧中部多出一路n_low输出。

上图展示了本项目顶层的main电路,此时的顶层电路已经变得简单,计数逻辑向计算逻辑报告当前n的最低位n_low和是否计算完成的freeze,计算逻辑经过log2n个时钟周期计算出正确结果,再加上赋初值的一个周期,一共需要1+log2n个周期即可得到结果,32位输入最多需要33个周期,达到了题目要求的计算时间。


后记:两个斐波那契数列问题是本课程Logisim部分难度巅峰,熟练掌握Logisim设计对后续的Logisim实现单周期CPU有极大的帮助,祝大家计组顺利渡劫!

北航计算机组成原理课程设计-2020秋 PreProject-Logisim-斐波那契数列问题(简单迭代法+矩阵乘法的快速幂)相关推荐

  1. 北航计算机组成原理课程设计-2020秋 【系列完结】Verilog或ISE高级特性与自动化测试

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Verilog HDL 与 ISE Verilog或ISE高级特性与自动化测试 本系列所有博客,知识讲解.习题以及答案均由北航 ...

  2. 北航计算机组成原理课程设计-2020秋 PreProject-Verilog HDL与ISE-Verilog工程的设计开发调试

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Verilog HDL 与 ISE Verilog工程的设计开发调试 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院 ...

  3. 北航计算机组成原理课程设计-2020秋 PreProject-入门测试

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-入门测试 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔者创作,如有侵权联系删 ...

  4. 北航计算机组成原理课程设计-2020秋 PreProject-数制

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-数制 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔者创作,如有侵权联系删除. ...

  5. 北航计算机组成原理课程设计-2020秋 PreProject-Verilog HDL与ISE-Verilog语法

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Verilog HDL 与 ISE Verilog语法 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理 ...

  6. 北航计算机组成原理课程设计-2020秋 PreProject-Verilog HDL与ISE-前言

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Verilog HDL 与 ISE - 前言 本节内容完全转载自北航计算机学院计算机组成原理课程组,原文链接:Verilog ...

  7. 北航计算机组成原理课程设计-2020秋 PreProject-Logisim-2^n mod 5问题

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Logisim-2^n mod 5问题 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析 ...

  8. 北航计算机组成原理课程设计-2020秋 PreProject-Verilog HDL与ISE-Verilog题目实例与分析

    北航计算机学院-计算机组成原理课程设计-2020秋 PreProject-Verilog HDL 与 ISE Verilog题目实例与分析 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算 ...

  9. 北航计算机组成原理课程设计-2021秋 PreProject-MIPS-测试程序设计

    北航计算机学院-计算机组成原理课程设计-2021秋 PreProject-MIPS 测试程序设计 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔者创作, ...

  10. 北航计算机组成原理课程设计-2021秋 PreProject-MIPS-MIPS 指令集架构

    北航计算机学院-计算机组成原理课程设计-2021秋 PreProject-MIPS MIPS 指令集架构 本系列所有博客,知识讲解.习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔 ...

最新文章

  1. 浅浅认识之VBS脚本访问接口与COMODO拦截COM接口
  2. python基础语法第10关作业-【python基础语法】第11天作业练习题
  3. python3字典相关方法
  4. Vue中vue-router路由使用分析
  5. 【算法】八大排序算法的时间复杂度 表格
  6. 版本分支不宜间隔太久
  7. vue项目json格式化显示
  8. 推荐你一款国产的倾斜摄影实景三维模型浏览器
  9. 文章总结:ASPLOS 2013 Architecture I
  10. MATLAB/ArcGIS读取nc数据并进行可视化
  11. jquery vue 替代_vue能代替jquery吗
  12. Dijkstra迪杰斯特拉算法的介绍(分为朴素dj和堆优化版dj),包含模板总结(必掌握)与具体例题应用
  13. 妥妥的去面试之数据结构与算法(一)
  14. 贾树森_(第一章)手机摄影拍摄详解_(第一课)手机摄影四个真相
  15. 通信原理——CDMA
  16. 数学建模 从小白到小白教学指南
  17. 软考证书相关分类及考证建议
  18. 一个不是很了解CS(计算机科学)的人,应从哪里开始自学?
  19. 字体管理工具 - RightFont使用教程
  20. python 安卓模拟器电脑版下载_九百万安卓模拟器pc版下载|安卓模拟器v3.2.0.1中文版...

热门文章

  1. mysql sin度数正玄值_正弦值角度对照表
  2. 怎么测量标准工时,实用分享:几种标准工时的测量方法
  3. matlab电机仿真 pdf,现代永磁同步电机控制原理及MATLAB仿真 pdf+随书仿真模型
  4. 逻辑回归之ROC曲线的绘制
  5. 网络工程制图论文计算机,计算机工程制图教学的课业评价-计算机工程论文-计算机论文.docx...
  6. 稀疏表示与字典学习大略讲解
  7. Jmeter基础教程
  8. RT-Thread与cubemx|74HC595驱动数码管详解
  9. H3CSE路由-配置OSPF高级
  10. HDU2157 How many ways??(可达矩阵+矩阵快速幂)