上一篇我们实现了一系列组合逻辑芯片和CPU的运算大脑:ALU,但是组合逻辑芯片是一个实时系统,只要输入值改变了输出值也马上跟着改变,上一秒辛苦计算出来的值,下一秒就消失不见了,因此我们需要一个具备"记忆功能"的原件,用于存储数据。这就是我们本篇要介绍的:时序逻辑芯片。

反馈电路、时钟系统、D触发器

组合逻辑芯片之所以无法记住上一次计算的值,是因为电流信息的流向是单向的,它总是向前流动,导致我们无法两次踏入同一条河流。要解决这个问题也很简单,我们引一条导线让数据回流,形成一个环路即可(即形成反馈)。下图就是用OR门加上一路环路构成的一位存储器,但他的缺点是只能记忆一次1的输入,后续无论输入如何改变,输出总是为1。如果想要实现功能更强大的记忆芯片就需要更复杂的电路设计,但仍然是基于门的输出再重新作为输入的原理。

提到"记忆"就不能不涉及到"时间",计算机中的时间是基于震荡时钟系统的,震荡时钟提供连续的交变信号(高低电平)从而形成离散的时间周期。时钟周期是计算机系统中各元器件能整齐划一,协调同步的工作的基础。当一个时钟周期到来时,计算机的各个元器件必须抓紧在本次周期内完成自己的任务,然后停下来安静等待下一个周期的来临(这也是为什么时钟频率通常被当做衡量计算机性能的重要指标,因为频率越高,单位时间内计算机完成的工作步骤就越多)。例如只有当时钟上升沿到来时,各时序芯片才会瞬间改变状态,其余时间保持状态锁定。如果把CPU、内存、输入输出设备比作乐团中各个乐器的乐手,那么时钟系统就是计算机乐团的乐队指挥。

在搭建hack的时候,我们一共用到了两个基本芯片(即不用自己实现的芯片),一个是上一篇用于搭建组合逻辑芯片系统的Nand,另外一个就是本片用于搭建时序逻辑芯片的DFF(data Flip-Flop:D触发器)。DFF的结构由R-S锁存器派生而来。(DFF的实现相对复杂,如果对锁存器和DFF感兴趣,可以参考《编码的奥秘》Charles Petzold)。

DFF包含1bit的输入和1bit的输出,另外DFF有一个时钟输入(图中的小三角代表时钟输入),DFF根据数据输入位和时钟输入位的输入实现了out(t)=in(t-1),即DFF简单的将前一个时间周期的输入值作为当前时间周期的输出值。这是DFF具备记忆功能的表现,也是我们后续实现16bit寄存器,16K、64K、256K等RAM的基础。

1位寄存器和16位寄存器

有了DFF的记忆功能,我们接下来先实现最简单1bit寄存器和16bit寄存器。1bit寄存器的接口包括:in输入管脚,负责传输输入数据;load管脚,load管脚是控制位,只有当load为1的时候,才会存储in的输入数据;out管脚输出当前存储的值。接口如下所示:

1bit芯片很好实现,主要是实现load位的控制功能即可,根据芯片描述可以把芯片设计为:load为1时选择输入in的数据,load为0时选择输入上一次寄存器保存的数据。这种选择功能正好是上一篇我们实现的Multiplexor芯片(后面在实现RAM的时候,Multiplexor在地址选择中的作用体现得更加明显)。1bit寄存器内部实现如下:

然后我们用硬件描述语言(HDL)实现:

/*** 1-bit register:* If load[t] == 1 then out[t+1] = in[t]*                 else out does not change (out[t+1] = out[t])*/CHIP Bit {IN in, load;OUT out;PARTS:Mux(a=outBack, b=in, sel=load, out=dffIn);DFF(in=dffIn, out=out, out=outBack);
}

有1bit寄存器,16bit寄存器可以看做是16个1bit寄存器的组合:

HDL实现:16bit寄存器

/*** 16-bit register:* If load[t] == 1 then out[t+1] = in[t]* else out does not change*/CHIP Register {IN in[16], load;OUT out[16];PARTS:Mux(a=outBack0, b=in[0], sel=load, out=dffIn0);DFF(in=dffIn0, out=out[0], out=outBack0);Mux(a=outBack1, b=in[1], sel=load, out=dffIn1);DFF(in=dffIn1, out=out[1], out=outBack1);Mux(a=outBack2, b=in[2], sel=load, out=dffIn2);DFF(in=dffIn2, out=out[2], out=outBack2);Mux(a=outBack3, b=in[3], sel=load, out=dffIn3);DFF(in=dffIn3, out=out[3], out=outBack3);Mux(a=outBack4, b=in[4], sel=load, out=dffIn4);DFF(in=dffIn4, out=out[4], out=outBack4);Mux(a=outBack5, b=in[5], sel=load, out=dffIn5);DFF(in=dffIn5, out=out[5], out=outBack5);// ...6到15
}

RAM

接着我们来实现计算机中另一个非常重要的组成部分:RAM。刚才看到,16bit寄存器可以由16个1bit的寄存器组合而成,同理一个mKB的RAM,可以由n个16位寄存器组合而成,16位寄存器就是RAM中的基本存储单元,寄存器的宽度(此处是16位)就是RAM"字"(WORD)的宽度。我们都知道,需要通过地址来访问RAM,RAM中每个寄存器都绑定到一个唯一地址。下图是RAM的接口设计,RAM有三种输入:数据输入,地址输入和加载位。地址指定了当前周期RAM中哪一个寄存器被访问,当进行读操作时(load=0),RAM立即输出被选中的寄存器的值,当进行写操作时(load=1),被选中的寄存器被赋予新的值,从下个周期开始RAM将开始发出新的值。输入和输出的数据宽度就是RAM"字"的宽度。

接下来我们要实现RAM8,RAM64,RAM512,RAM4K,RAM16K几种内存容量的RAM。RAMn中的n即代表RAM中有n个寄存器。例如RAM16K中有16K个16位寄存器,那么他的内存容量就是16K*(16/2)byte=32Kbyte。想要实现一个RAMn芯片,我们需要在RAMn芯片中集成n个寄存器,外加一些列控制逻辑(处理load)和地址选择逻辑,那么涉及到选择逻辑自然少不了Multiplexor和DMultiplexor。那么下面我们以RAM8为例讲一下我自己的实现方式:

  • RAM8中有8个寄存器
  • 8个寄存器有8个地址,所以我们的地址位需要2^n=8, n=3位
  • DMux用于解析写入时的地址,用3位sel分离出需要写入的那一路,再和load做与操作后作为寄存器的load位输入,最终通过load位来决定哪一个寄存器被写入In。
  • Mux用于解析读取时的地址,用3位sel在8个寄存器中选择想要读取的那一路数据

HDL实现:RAM8

/*** Memory of 8 registers, each 16 bit-wide. Out holds the value* stored at the memory location specified by address. If load==1, then * the in value is loaded into the memory location specified by address * (the loaded value will be emitted to out from the next time step onward).*/CHIP RAM8 {IN in[16], load, address[3];OUT out[16];PARTS:DMux8Way(in=true, sel=address, a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h);And(a=a, b=load, out=load0);And(a=b, b=load, out=load1);And(a=c, b=load, out=load2);And(a=d, b=load, out=load3);And(a=e, b=load, out=load4);And(a=f, b=load, out=load5);And(a=g, b=load, out=load6);And(a=h, b=load, out=load7);Register(in=in, load=load0, out=R0);Register(in=in, load=load1, out=R1);Register(in=in, load=load2, out=R2);Register(in=in, load=load3, out=R3);Register(in=in, load=load4, out=R4);Register(in=in, load=load5, out=R5);Register(in=in, load=load6, out=R6);Register(in=in, load=load7, out=R7);Mux8Way16(a=R0, b=R1, c=R2, d=R3, e=R4, f=R5, g=R6, h=R7, sel=address, out=out);
}

有了RAM8,我们就可以利用8个RAM8的组合实现RAM64,以此类推实现后面容量更大的RAM。

  • 实现RAM64的技巧是对地址位的拆分,RAM64需要6个地址位,把6个地址为拆分为3+3
  • 和实现RAM8的时候类似,需要引入一组Mux和DMux,这组选择芯片使用6位地址中的高三位,通过高3位把地址初步划分为8个地址段,每个地址段的容量即RAM8中寄存器的数量
  • 每个RAM8使用6位地址中的低三位,然后从中定位到目标地址寄存器

HDL实现:RAM64

/*** Memory of 64 registers, each 16 bit-wide. Out holds the value* stored at the memory location specified by address. If load==1, then * the in value is loaded into the memory location specified by address * (the loaded value will be emitted to out from the next time step onward).*/CHIP RAM64 {IN in[16], load, address[6];OUT out[16];PARTS:DMux8Way(in=true, sel[0]=address[3], sel[1]=address[4], sel[2]=address[5], a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h);And(a=a, b=load, out=load0);And(a=b, b=load, out=load1);And(a=c, b=load, out=load2);And(a=d, b=load, out=load3);And(a=e, b=load, out=load4);And(a=f, b=load, out=load5);And(a=g, b=load, out=load6);And(a=h, b=load, out=load7);RAM8(in=in, load=load0, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R0);RAM8(in=in, load=load1, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R1);RAM8(in=in, load=load2, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R2);RAM8(in=in, load=load3, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R3);RAM8(in=in, load=load4, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R4);RAM8(in=in, load=load5, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R5);RAM8(in=in, load=load6, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R6);RAM8(in=in, load=load7, address[0]=address[0], address[1]=address[1], address[2]=address[2], out=R7);Mux8Way16(a=R0, b=R1, c=R2, d=R3, e=R4, f=R5, g=R6, h=R7, sel[0]=address[3], sel[1]=address[4], sel[2]=address[5], out=out);
}

HDL实现:RAM4K

/*** Memory of 4K registers, each 16 bit-wide. Out holds the value* stored at the memory location specified by address. If load==1, then * the in value is loaded into the memory location specified by address * (the loaded value will be emitted to out from the next time step onward).*/CHIP RAM4K {IN in[16], load, address[12];OUT out[16];PARTS:DMux8Way(in=true, sel[0..2]=address[9..11], a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h);And(a=a, b=load, out=load0);And(a=b, b=load, out=load1);And(a=c, b=load, out=load2);And(a=d, b=load, out=load3);And(a=e, b=load, out=load4);And(a=f, b=load, out=load5);And(a=g, b=load, out=load6);And(a=h, b=load, out=load7);RAM512(in=in, load=load0, address[0..8]=address[0..8], out=R0);RAM512(in=in, load=load1, address[0..8]=address[0..8], out=R1);RAM512(in=in, load=load2, address[0..8]=address[0..8], out=R2);RAM512(in=in, load=load3, address[0..8]=address[0..8], out=R3);RAM512(in=in, load=load4, address[0..8]=address[0..8], out=R4);RAM512(in=in, load=load5, address[0..8]=address[0..8], out=R5);RAM512(in=in, load=load6, address[0..8]=address[0..8], out=R6);RAM512(in=in, load=load7, address[0..8]=address[0..8], out=R7);Mux8Way16(a=R0, b=R1, c=R2, d=R3, e=R4, f=R5, g=R6, h=R7, sel[0..2]=address[9..11], out=out);
}

至此,我们了解了RAM中内存访问的方式。在RAM中的存储单元可以通过解析地址信号来进行随机的访问,而不会受限于存储单元的位置,访问任何位置的存储单元的速度都是相等的,例如访问内存中一个数组中任意下标的数据的速度都是相等的。这也是Random Access Memory概念的由来。

计数器

计数器也是CPU中一个重要的原件,主要用来存储将要执行指令的内存地址。在大多数情况下,计数器每个周期简单的进行+1操作,这时计算机能够获取下一条地址,这和CPU顺序执行指令的模式相符,同时CPU也支持直接跳转到编号为n的指令去执行,因此计数器要支持将其值设置为n的能力。下面我们来看一下计数器的接口设计:计数器芯片有2个附加控制位reset和inc,当inc=1时,计数器在每个时钟周期自加1,其输出值为out(t)=out(t-1)+1;如果想要将计数器置为0,就将reset置为1。如果想要将计数器设置为某个值,则将load置为1,并将数值从in输入。

下图是我对计数器的实现逻辑

  • reset,inc,load用一个Or8way输出,只要其中有一个为1,那么register的load为就为1,register的值会被更新
  • 3个Mux串联用于选择自增1、in的输入值、重置0这三种操作

HDL实现:计数器

/*** A 16-bit counter with load and reset control bits.* if      (reset[t] == 1) out[t+1] = 0* else if (load[t] == 1)  out[t+1] = in[t]* else if (inc[t] == 1)   out[t+1] = out[t] + 1  (integer addition)* else                    out[t+1] = out[t]*/CHIP PC {IN in[16],load,inc,reset;OUT out[16];PARTS:Or8Way(in[0]=inc, in[1]=reset, in[2]=load, in[3..7]=false, out=ReLoad);Mux16(a=outBack, b=outPlus, sel=inc, out=loadIN);Mux16(a=loadIN, b=in, sel=load, out=resetIN);Mux16(a=resetIN, b=false, sel=reset, out=ReIn);Register(in=ReIn, load=ReLoad, out=out, out=outBack);Inc16(in=outBack, out=outPlus);
}

我们以下图为例,看一下计数器是如何在时钟系统下变化数值的。

总结

至此,我们实现了hack CPU所需的所有芯片。组合逻辑芯片主要负责算数运算、逻辑运算、选择逻辑,并且通过适当的组合可以用来实现指令解析和某些功能控制;时序芯片主要负责存储数据。下一篇我们会用这些芯片搭建一个简单但实用的CPU。

从0到1构建计算机(4/12)--时序逻辑芯片:时序门、寄存器、RAM、计数器相关推荐

  1. 从0到1构建计算机(3/12)--组合逻辑芯片:逻辑门、加法器、ALU

    上篇说到:通过使用Nand门,我们可以实现任何逻辑门,进而实现可以一个CPU.后面我们就会搭建一个麻雀虽小但五脏俱全的计算机平台:hack.本篇我们开始第一步,实现搭建hack所需的一组芯片:组合逻辑 ...

  2. 从0到1构建大数据生态系列1:数据蛮荒中的拓荒之举

    缘起 我们都知道,当前大数据的需求基本属于遍地开花.无论是帝都.魔都,还是广州.深圳,亦或是全国其他各地,都在搞大数据:不管是不到百人的微小公司,还是几百上千人的中型公司,亦或是上万的大型公司,都在需 ...

  3. www.how2j.com_HOW-TO:快速开始使用Spring 4.0,以构建简单的REST-Like API(演练)

    www.how2j.com HOW-TO:快速开始使用Spring 4.0,以构建简单的REST-Like API(演练) 关于使用Spring MVC创建Web API的另一篇教程. 不太复杂. 只 ...

  4. 从0到1构建支撑企业自动化运维体系

    关注我们获得更多内容 精彩预告:第八届数据技术嘉年华大会将于2018年11月16日~17日在北京市朝阳区东三环中路61号富力万丽酒店盛大开启.本次大会邀请互联网领先企业的数据库专家,国产数据库的领军人 ...

  5. 计算机系统制造时间成本,如何构建计算机成本核算系统.doc

    如何构建计算机成本核算系统.doc 如何构建计算机成本核算系统 [摘要]为您整理了计算机论文-如何构建计算机成本核算系统,希望和您一起探讨! 目前为止,国内企业,特别是机械制造业ERP实施成功的范例不 ...

  6. 0基础如何复习计算机408

    0基础如何复习计算机408 目录 408考试科目占比:... 1 整体分析:... 1 单科分析... 2 数据结构(45分)... 2 计组(45分)... 2 操作系统(35分)... 2 计网( ...

  7. 从0到1构建数据科学竞赛知识体系,有夕,鱼佬,茂霖等竞赛大咖将特邀分享...

    从0到1构建数据科学竞赛知识体系 这是怎样的数据竞赛知识体系 为了帮助更多竞赛选手入门进阶比赛,通过数据竞赛提升理论实践能力和团队协作能力.DataFountain 和 Datawhale 联合邀请了 ...

  8. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景 ...

  9. 从0到1 构建实时音视频引擎

    最近几年,实时音视频领域越来越热,今年的疫情更是"火上浇油"了一把.网易智企旗下产品网易云信在实时音视频领域深耕多年,积累了不少实践经验.在本文里,笔者将以烹饪为比喻,深入浅出地将 ...

  10. 技术系列课|从0到1 构建实时音视频引擎

    随着5G和AI时代的到来,在线实时互动在越来越多的场景中被使用,支撑这一功能实现的关键性技术--RTC也受到了空前的关注.本节技术系列课,来自网易云信的讲师将以做菜的过程为比喻,深入浅出地讲述如何从0 ...

最新文章

  1. 容器源码分析之TreeSet(五)
  2. 前向传播算法和反向传播算法
  3. 【嵌入式工程师面试高频问题】你知道IIC吗(附程序说明)
  4. java多线程初识4
  5. 习题3.8 符号配对 (20分)
  6. 真正的正数、负数、整数等正则表达式,网上好多都是错的
  7. 《Spark Cookbook 中文版》一导读
  8. JAVA聊天室简易版1.0(多线程)
  9. element table多选和单选
  10. c++ 图片验证码识别_图片验证码识别方法
  11. php 判断来访IP地址是国内还是国外的
  12. 灵格斯怎么屏幕取词_屏幕取词插件 -- Lingoes Translator 灵格斯词霸
  13. Solidworks默认模板无效问题?修改字体问题
  14. 海康8700服务器系统,海康8700安防综合管理平台安装使用指南.pdf
  15. 微信小程序 谈谈在大学初次写项目的体验
  16. 外汇EA量化交易,怎么提高交易水平
  17. AI赋能的判定机制的倾向性
  18. 百度地图API js详细介绍
  19. 2016秋招面经(蘑菇街+BT+网易杭研+CVTE+唯品会)
  20. 在电脑屏幕上截图的5种方式

热门文章

  1. HDU 2246 神题?一千多行
  2. spotlight ios_如何禁用iOS 10的Spotlight搜索历史记录
  3. 面向对象化(封装,继承,多态)
  4. 如何在PC上显示Hololens的视野
  5. python怎么更新sp2_GitHub - Sp2-Hybrid/Python-100-Days: Python - 100天从新手到大师
  6. java 图片加多处水印_Java在Word中添加多行图片水印
  7. Android 12.0 导航栏Icon图标大小修改
  8. Windows命令行解决8080端口被占用
  9. 沈劭劼居然还是大疆的....大疆真的可怕。大疆如果做一款室内无人机不分分钟秒杀其他。
  10. 淘宝定价的方式有什么,如何根据活动来定价