The Element Of Computer System 笔记(第五周)

第五章 计算机体系结构

背景知识

存储程序概念

现代的数字计算机的功能多样化,可以由有限硬件组成的计算机却可以执行无限的任务队列。从交互式游戏到字处理到科学计算。
这些是“存储程序(stored program)”概念的功劳。

存储程序概念的基本思想很简单。计算机基于固定的硬件平台,能够执行固定的指令集。同时,这些指令能够被当成构建模块,组成
任意的程序。这些程序的逻辑被存储到了计算机的存储设备(memory)里,跟数据一样,称为所谓的“软件(software)”。

冯·诺依曼结构

存储程序概念是很多抽象的、应用型计算机模型的关键,其中最著名的是通用图灵机(1936)和冯·诺依曼(1945)。图灵机是描述虚拟的简单计算机的抽象机,主要用来分析计算机系统的逻辑基础。相比之下,冯诺依曼机是实际应用型的体系结构,它几乎是今天所有计算机平台的基础。
在这里插入图片描述
冯诺依曼体系结构的基础是一个中央处理单元(CPU,central processing Unit),它与记忆设备(memory device)即内存进行交互,负责从输入设备(input device)接收数据,向输出设备(output device)发送数据。这个体系结构的核心是存储程序的概念:计算机内存不仅存储着要进行操作的数据,还存储着指示计算机运行的指令。

内存

冯诺依曼的内存中存有两种类型的信息:数据项(data items)和程序指令(programming instructions)。对这两种信息通常采用不同的方式来进行处理。在某些计算机中,它们被分别存储到不同的内存区中。尽管它们具有不同功能,但两种信息都以二进制数形式存储在具有通用结构的随机存储器中(一个连续的固定宽度的单位阵列,也称为字,即word,或者存储单元,每个单元都有一个独立的地址)表示。因此,一个独立的字(代表一个数据项或者一条指令)通过它的地址来指定。

数据内存(Data Memory) 高级程序(high-level programs)操纵抽象的元件(artifacts),例如变量(variables)、数组(arrays)和对象(objects)。这些数据抽象被翻译成机器语言后,就变成一连串的二进制数,存储在计算机的数据内存中。一旦通过指定的地址,在数据内存中找到对应的内存单元,就可以对该内存单元进行读操作和写操作。对于读操作,则是获取这个字的值;对于写操作,则是将新值存入指定的内存单元中,取代原来的旧值。

指令内存(Instruction Memory) 当高级语言被翻译成机器语言时,它变成一系列的二进制字(word),这些字代表机器的指令。这些指令被存储在计算机的指令内存中。在计算机操作的每一步,CPU从指令中取出一个字,对其进行解码,从而执行指定的指令,然后计算下一条将要执行的指令。因此,改变指令中的内容会完全改变计算机的操作。

中央处理器

CPU是计算机体系的核心,负责执行已经被加载到指令内存中的指令。这些指令告诉CPU去执行不同的计算,对内存进行读/写操作,以及根据条件跳转去执行程序中其他指令。CPU通过使用三个主要的硬件要素来执行这些任务:算数逻辑单元(ALU,Arithmetic-Logic Unit),一组寄存器(registers)控制单元(control unit)

算数逻辑单元(ALU) ALU负责执行计算机中所有底层的算数操作和逻辑操作。比如,典型的ALU可以执行的操作包括:将两个数相加:检查一个数是否为整数;在一个数据字(word)中进行位操作;等等。

寄存器(Registers) CPU的设计是为了能够快速地执行简单计算。为了提高它的性能,将这些和运算相关的数据暂存到某个局部存储器中是十分必要的,这远比从内存中搬进搬出要好。因此,每个CPU都配有一组高速寄存器,每个寄存器都能保存一个单独的字。
控制单元(Control Unit) 计算机指令用二进制代码来表示,通常具有16、32或64位宽。在指令能够被执行之前,须对其进行解码,指令里面包含的信息向不同的硬件设备(ALU,寄存器,内存)发送信号,指使它们如何执行指令。指令的解码过程是通过某些控制单元(control unit)完成的。这些控制单元还负责决定下一步需要取出和执行哪一条指令。

CPU操作现在可以被描述成一个重复的循环:从内存中取出一条指令(字):将其解码;执行该指令,取下一条指令。执行该指令,取下一条指令;如此反复循环。指令的执行过程可能包含下面的一些子任务;让ALU计算一些值,控制内部寄存器,从存储设备中读取一个字,或向存储设备中写入一个字。在执行这些任务的过程中,CPU也会计算出下一步该读取并执行哪一条指令。

寄存器

内存访问是很慢的过程。当CPU被指示去取内存中地址j的内容时,下面的一些过程会连续发生:(a)j从CPU传到RAM;(b)RAM的直接访问逻辑(direct-access logic)选中地址为j的寄存器;(c)RAM[j]的内容传回到CPU。寄存器也能提供同样的数据访问功能,但没有来回的数据传递和寻址开销。首先,寄存器位于CPU芯片内部,所以对它们的访问几乎可以瞬间完成;其次,寄存器的数量非常少,与内存单元相比。

寄存器的多个用途

数据寄存器(Data registers) 这些寄存器为CPU提供短期记忆(memory)服务。比如
,当计算(a-b)*c时,首先计算(a-b)的值并且记住它。虽然这个结果可以暂时地被存储到存储单元中,但更好的方法是存储在CPU内部即数据寄存器中。

寻址寄存器(Addressing registers) 为了进行读写,CPU必须连续访问内存中的数据。这样我们必须确定被访问的内存字(word)所在的内存地址。在某些情况下这个地址作为当前指令的一个部分给出,而其他某些情况下它依赖于前面一条指令的执行结果。对于后者,这个地址应该被存储到某个寄存器中,使得该寄存器的内容在今后的操作中能够被当做存储单元的地址——这就需要用到寻址寄存器。

程序计数寄存器(Program counter registers) 执行程序时,CPU必须总是知道下一条指令在指令内存中的地址。这个地址保存在一个特殊的寄存器即程序计数器中(或称PC,Program Counter)。PC的内容就被当作从指令内存中取指令的地址。因此,在执行当前指令的过程中,CPU通过两种方式之一来更新PC的内容:1)如果当前指令不包括goto命令,PC增以便使指针指向程序中的下一条指令;2)如果当前指令中包含需要执行的goto n命令,则CPU将PC置为n。

输入和输出

计算机使用一组输入输出(I/O)设备来与其外部环境进行交互。让计算机能够对这些外部设备进行操作,其中最简单的实现技巧是I/O映像(memory-mapped I/O)。

I/O映像的基本思想是:创建I/O设备的二进制仿真,使其对于CPU而言,“看上去”就像普通的内存段。特别地,每个I/O设备在内存中都分配了独立的区域,作为它的“内存映像”。对于输入设备(键盘、鼠标等),内存映像能连续不断地反映设备的物理状态;对于输出设备(屏幕、扬声器等),内存映像连续地驱动设备的物理状态。当外部事件作用于输入设备时(比如,在键盘上按键或者移动鼠标),某些特定的值就被写入它们各自对应的内存映像中。同样地,想要控制某些输出设备(比如,在屏幕上画图或者播放一首歌),就将一些特定值写入它们各自对应的内存映像。从硬件的角度来看,这个方案需要所有I/O设备提供类似于记忆单元(memory unit ,或称内存单元)的那种接口。从软件的角度来看,需要对每个I/O设备定义交互协议,这样程序才能正确地访问它。若有大量可用的计算机平台和I/O设备,就不难明白各类标准规范(standards)在计算机体系设计中所起的重要作用。

I/O内存映像体系的实际应用是很有意义的:CPU以及整个平台的设计可以完全不依赖于要与计算机进行交互的I/O设备,也不依赖于I/O设备的数量和种类。无论何时将新的I/O设备与计算机连接,所要做的只是为其分配一个新的内存映像并记录它的基地址(这些配置工作是由操作系统来完成的)。这样以来,程序可以通过操控I/O内存映像中的比特数据来实现对相应物理I/O外设的操作。

Hack硬件平台描述规范

概念(RAM和ROM介绍、D和A寄存器以及PC)

Hack平台是16-位冯诺依曼机,包括一个CPU、两个独立的内存模块(指令内存和数据内存)和两个内存映像I/O设备(屏幕和键盘)。这个体系结构中的某些部分——尤其是它的机器语言-第四章以及介绍过了。

Hack计算机执行位于指令内存中的程序。指令内存是只读设备,因此必须使用一些外部方法将程序加载进去。比如,指令内存可以用ROM芯片来实现,该芯片预先烧写了所需要的程序。加载新的程序意味着要替换整个ROM芯片。为了模拟这个操作,Hack平台的硬件仿真器提供了加载文本文件的方法,该文本文件包含用Hack机器语言编写的程序(从现在起,我们分别用RAM和ROM来指代Hack的数据内存和指令内存)

Hack的CPU由第二章介绍的ALU和三个分别称为数据寄存器(D,data register)、地址寄存器(A,address register)、程序计数器(PC,program counter)的寄存器组成。D和A是通用的16-位寄存器,它们可以被算数和逻辑指令(比如A=D-1,D=D|A等)使用。D寄存器仅仅用来存储数据值;而对于A寄存器,根据指令的内容,A寄存器中的内容可以被解释为数据值、RAM地址或ROM地址。

Hack机器语言有两种16-位命令类型。其中地址指令的格式为0vvvvvvv…vv,这里v代表0或1。这个指令使得计算机将15-位常数vvv…v加载到A寄存器中。计算指令的格式则为1111acccccdddjjj,根据Hack机器语言规范,a-位域(a-bits)和c-位域(c-bits)指示ALU计算哪个函数,d-位域(d-bits)表示将ALU的输出存于何处,j-位域(j-bits)指定了可选的跳转条件。

计算机体系结构执行流程

计算机系统结构以这样的方式来进行连接:PC(程序计数器)芯片的输出端被连接到ROM芯片的地址输出端。这样一来,ROM芯片总是输出ROM[PC](大小为一个word),即“PC所指向的指令内存单元”的地址。这个值称为当前指令(current instruction)。那么,每个时钟周期内整个计算机的操作可以表示为:

执行(Execute):当前指令中不同的bit位域被同时被送入计算机中不同的芯片。如果其是地址指令(即MSB=0),则A寄存器被置为指令中内含的15-位常数。如果其是计算指令(即MSB=1),即指令中内含的a-位域、c-位域、d-位域和j-位域被当做是控制位,导致ALU和寄存器会相应的去执行该指令。

取指令(Fetch): 计算机下一步取哪一条指令,取决于当前指令的jump位和ALU的输出。这些值共同决定了是否去执行跳转。如果是要执行跳转,则PC被置为A寄存器的值;否则就将PC的值加1.在一个时钟周期,PC指向的指令在ROM的输出中出现,如此不断循环。

这个“取指令-执行”循环意味着在Hack平台里,涉及内存单元访问的基本操作通常需要两个指令:一个地址指令(address instruction),用于将给定的地址赋予A寄存器:以及一个后续的计算指令(compute instruction),用于对于该地址对应的内存单元进行操作(可以是对RAM单元的读/写操作,或是到ROM的跳转操作)。

然后开始详细介绍Hack硬件平台。该平台可以由前面构建的一些部分来实现。CPU是基于第2章构建的ALU的。寄存器程序计数器就是第3章中介绍的16-位寄存器和16-位计数器。同样,ROMRAM芯片也是第3章中构建的内存单元的版本。最后,屏幕和键盘设备将通过内存映像与硬件平台进行接口,通过有相同接口的内置芯片(比如RAM芯片)来实现。

中央处理器

Hack平台的CPU被设计用来执行16-位指令,它与两个相互独立的内存模块相连:一个是指令内存,CPU从该内存取指令;另一个是数据内存,CPU可以对其进行读/写数据值,如图5.2所示。

指令内存

Hack指令内存是可以进行直接访问的只读内存设备,也称为ROM(Read-Only Memory)。Hack的只读内存(ROM)由32K可寻址的16-位寄存器组成,如图5.3所示。

数据内存

Hack的数据内存芯片具有典型的RAM设备(跟第3章中构建的一样,请参看图3.3的接口。)为了读取地址为n的内存单元中所存储的内容,就先将n放入内存的地址输入端口,然后从内存的输出端口得到结果,这是一个与时序无关的组合操作。为了将值v写入内存单元,则先将v置于内存的数据输入端口,然后将地址n置于地址输入端口,并且将内存的load比特位置为1,这是一个时序操作,所以内存单元中的内容将在下一个时钟周期变成n。

除了能够作为计算机的通用数据存储之外,数据内存还可以通过内存映像在CPU和计算机输入/输出设备之间充当接口。

内存映像(Memory Maps) 为了使与用户之间的交互变得简单,Hack平台与两个外部设备相连:屏幕(screen)和键盘(keyboard)。两个设备都通过内存映像与计算机平台进行接口。通过对屏幕内存映像区段中的单元进行读写,分别实现得到屏幕状态和屏幕绘图操作。同样,可以通过检查键盘内存映像中的内存单元来得知当前按下了哪个键。内存映像通过存在于计算机之外的外部逻辑,与它们各自的I/O设备进行交互。交互协议如下:任何时候改变屏幕内存映像中的比特位,相对应的像素就被画到物理显示器上。任何时候在物理键盘上按下一个键,这个键值对应的编码将被存储到键盘内存映像中对应的内存单元内。
在这里插入图片描述
这里用一张参考的图片来说明芯片的设计流程

在这里插入图片描述
在这里插入图片描述
这里说一下CPU的设计要点
1.第一个Mux如何选择输入?显然,对于A指令,希望把instruction读入A寄存器作为地址,当指令为C指令时,选择的则是ALU的aluoutput
2.A指令必定访问A寄存器,C指令根据指令的第5位判断是否访问A寄存器。
3.A就餐器的输出是否写回存储器writem?A指令从不写回,C指令根据第3位判断。

这里放C-指令的jump域与ALU芯片的图片作为跳转的提示
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
我们首先介绍在硬件接口和I/O设备之间相互作用的内置芯片,然后介绍嵌入了这些芯片的完整内存模块。

屏幕 Hack计算机能够与一个黑白屏幕进行交互,该屏幕可以显示256像素行,每行宽512像素。计算机通过内存映像与物理显示设备(即屏幕)进行接口。也就是说,内存映像中的内容与屏幕上的内容是对应的,可以通过读写内存映像来操控物理显示设备(即屏幕)上的显示。内存单元中的数值对应与屏幕上的一个像素(1=黑色,0=白色)。内存映像和屏幕之间准确的映射如图5.4所示。

键盘 Hack计算机跟个人计算机一样可以与标准键盘进行交互。计算机与物理键盘之间通过名为Keyboard的芯片进行交互(如图5.5所示)。当在物理键盘上按下一个键时,它的16-位ASCII码作为Keyboard芯片的输出。当没有按下键时,芯片输出0。除了通常的ASCII码之外,Keyboard芯片还能识别并反映图5.6中所列的一些键。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面介绍了数据内存的内部单元,然后介绍整个数据内存地址空间。

全局内存(overall memory) Hack平台的地址空间(也就是它的整个数据内存)是由一个称为Memory的芯片提供的。该芯片包含RAM(用于常规的数据内存和指令内存)和屏幕、键盘内存映像。整个地址空间被分隔成4个相互独立的子空间,数据内存、指令内存、键盘和屏幕各自占用一个子空间。如图5.7所示。

计算机

Hack硬件阶层体系的顶层是一个单一的芯片(Computer芯片),作为完整计算机系统的抽象,它能够执行用Hack机器语言编写的程序。图5.8展示了这种抽象。Computer芯片包含了运行计算机所需要的所有硬件设备,包含CPU、内存、键盘、屏幕,它们都被集成到该芯片内部,以实现整个计算机功能。为了执行一个程序,该程序的代码首先必须预加载到ROM中。对屏幕和键盘的控制都是通过它们的内存映像来完成的。

实现

在这里插入图片描述
在这里插入图片描述

中央处理器

CPU的实现目标是建立逻辑门结构,其能够执行指定的Hack指令和读取下一条要执行的指令。
在这里插入图片描述
CPU包括执行Hack指令的ALU、一组寄存器和一些用于取指令和对指令解码的控制逻辑。因为几乎所有硬件元素都已经在前面的章节中构建好了,所以这里的关键就是如何连接它们了。

图5.9中没有给出的部分是CPU的控制逻辑,用来执行下面的任务:

  • 指令解码(Instruction decoding):解析出指令所代表的的意思(指令的功能)。
  • 指令执行(Instruction execution):发信号指示计算机的各个部分应该做什么工作来执行指令(指令的功能)。
  • 读取下一条指令?(Next instruction fetching):指出下一步执行哪一条指令(指令的功能以及ALU的输出)。

(本书后续所提到的“CPU实现”就是指5.9所展示的架构。)

指令解码(Instruction Decoding) CPU输入的16位指令可以代表一条A-指令或者一条C-指令。为了解析该16-位字的内容,可以将其分解为“i xx a cccccc ddd jjj”。其中i-位代表指令类型,0代表A-指令,1代表C-指令。如果是C-指令,那么a-位域和c-位域共同表示comp部分,它指明要执行什么计算,d-位域表示dest部分,j-位域表示jump部分。如果是A-指令,那么除了i-位之外的15位就被解释为常数。

指令执行(Instruction Execution) 指令的各个域(i-位域、a-位域、c-位域、d-位域、j-位域)会被同时发送到CPU中的各个组件,这样各组件就会根据指令的内容各自进行工作,协同执行指令。这里所指的指令可以是A-指令或C-指令,它们由机器语言规范描述。其中a-位域决定ALU是把A寄存器的输入当做操作数,还是把内存单元的输入当作操作数,c-位域决定ALU执行什么函数,d-位域决定ALU的计算结果将结果保存到哪里。

读取下一条指令(Next Instruction Fetching) 在执行当前指令的同时,CPU还可以确定下一条指令的地址,通过程序计数器的输出端发送该地址。这个任务的“驱动器”就是程序计数器(它是CPU内部结构的一部分),它的输出直接和CPU的pc输出端口连接。这就是第3章中构建的PC芯片(如图3.5所示)。

大部分时候程序员都希望计算机去取程序中下一条指令并予以执行。因此,设t是当前的单位时间,默认的程序计数器操作应该是PC(t)=PC(t-1)+1。当我们要执行goto n操作时,机器语言首先要将A寄存器置为n(通过一条A-指令实现),然后执行一条跳转指令(由后续的C-指令的j-位指定)。因此,我们的任务是提出一个能实现如下逻辑的硬件方案:
if jump(t) then PC(t)=A(t-1)
else PC(t)=PC(t-1)+1。

为了方便起见,该jump控制逻辑很容易通过我们给出的CPU来实现。前面提到的PC芯片接口(参见图3.5)有一个load位来控制它对输入值的接收。因此,为了执行期望的jump控制逻辑,先要将A寄存器的输出端和PC的输入端相连接。余下的问题就是决定PC何时从A寄存器接收数据,也就是什么时候去执行jump。这是一个包含两个信号的函数:(a)当前指令的j-域,它用来指定在什么条件下才执行jump;(b)ALU的输出状态位,它用来指示jump条件是否满足。如果要执行一个jump,就应该将A寄存器的输出值加载给PC。否则,PC应该加1。

另外,如果希望计算机重新去执行程序,所要做的就是将程序计数器置为0。这就是为什么在我们提出的CPU实现方案中,直接将CPU的reset输入和PC芯片的reset管脚相连接。

内存

Hack平台的Memory芯片主要由三个底层芯片构成:RAM16K、Screen、Keyboard。同时我们必须通过这3个底层芯片来实现一个统一的逻辑地址空间来满足程序员的要求,这个空间从地址0到24576(0x0000到0x6000,如图5.7所示)。Memory芯片的实现应该创建这样的连续空间。在第3章中介绍了将若干小容量RAM芯片组合为一个较大容量的RAM芯片(参看图3.6以及相应的n-寄存器内存(n-register memory)的介绍,)在这里可以采用相同的技术来实现这个连续空间。
在这里插入图片描述

计算机

一旦实现并测试了CPU和Memory芯片,整个计算机的构建就变得简单了。图5.10给出了一种可能的实现方案。

观点

在功能方面,计算机系统可以被划分为两个类型:通用计算机(general-purpose computers),简单地用来执行一个个程序;专用计算机(dedicated computers),通常嵌入在其他系统中(比如手机、游戏机、数码相机等)。对于一些特殊的应用,一个单一的程序会被烧写进专用计算机的ROM中,并且该程序是唯一能够被执行的程序(比如,在游戏机中,游戏软件存储在外部的游戏卡中,它实际上就是可更换的ROM模块,封装在漂亮的包装里)。除了这个显著的区别之外,通用计算机和专用计算机具有相同的结构:存储式程序、“获取-解码-执行”逻辑、CPU、寄存器、程序计数器等。

不同与Hack,大多数通用计算机使用单一的地址空间用于存储数据和指令。在这样的结构中,指令中描述的指令地址和可选的数据地址必须被发送到相同的目的地:共享地址空间的单一地址输入。显然这不能同时完成。标准的解决方法是将计算机的实现基于双周期控制逻辑。在读取循环(fetch cycle) 中,从内存的输入端输入指令的地址,这样可立即从内存中取得想要指令,并将该指令暂存到指令寄存器中。在接下来的执行循环(execute cycle)中,指令被解码。从中得出操作数所在内存单元的地址,并从该地址从内存的地址输入端输入,从而使指令可以操纵指定的内存单元。相比之下Hack体系结构将地址空间分成独立的两个部分,允许在同一个时钟周期内实现对指令的读取和执行。这样的简化硬件设计带来的代价是程序不能被动态地改变。

置于I/O,Hack的键盘和屏幕则相当简单。通用计算机与多个I/O设备(比如打印机、磁盘、网卡等)相连。并且通用计算机的显示器的功能显然比Hack的屏幕强大,它具有更多的像素,每个像素有若干亮度级别和颜色。跟Hack的基本交互原则一样,每个像素是通过驻留内存的二进制值来控制的:与Hack中单一的位控制一个像素的黑或白不同的是,在通用计算机中,若干个位用来控制三个基本色的亮度级别,由此来产生该像素的最终颜色。同样地,Hack屏幕的内存映像是十分简单的,它直接将像素映像到内存的位中。然而大多数现代计算机允许CPU发送高级图像指令到用于控制显示器的显卡(graphicard)上。这样,CPU再也不用位直接画一些类似于圆圈和多边形的图像而感到麻烦了,显卡使用其内嵌的芯片组来完成这个任务。

最后应该强调的是,设计计算机硬件时,大部分的努力和创造性都致力于使计算机获取更好的性能。因此,关于硬件体系结构的课程和教材都在围绕这些问题展开讨论,比如实现内存层级、更好地与I/O设备交互、流水线操作、并行化、指令预取以及本章中没有介绍的其他优化技术。

从发展的历程来看,为了加强处理器性能而付出的努力引出了两个主要的硬件设计派系。复杂指令集(CISC Complex Instruction Set Computing)方法的倡导者认为,为了取得更好的性能,必须提供丰富和详细的指令集。与此相反的是,精简指令集计算机(RISC,Reduced Instruction Set Computing)阵营为了尽可能的提升软件速度,使用了较简单的指令集。Hack计算机并不加入这场争辩,既不具有强大的指令集也没有采用特殊的硬件加载技术。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
In the early days of computer science, the interactions of hardware, software, compilers, and operating system were simple enough to allow students to see an overall picture of how computers worked. With the increasing complexity of computer technology and the resulting specialization of knowledge, such clarity is often lost. Unlike other texts that cover only one aspect of the field, The Elements of Computing Systems gives students an integrated and rigorous picture of applied computer science, as its comes to play in the construction of a simple yet powerful computer system. Indeed, the best way to understand how computers work is to build one from scratch, and this textbook leads students through twelve chapters and projects that gradually build a basic hardware platform and a modern software hierarchy from the ground up. In the process, the students gain hands-on knowledge of hardware architecture, operating systems, programming languages, compilers, data structures, algorithms, and software engineering. Using this constructive approach, the book exposes a significant body of computer science knowledge and demonstrates how theoretical and applied techniques taught in other courses fit into the overall picture. Designed to support one- or two-semester courses, the book is based on an abstraction-implementation paradigm; each chapter presents a key hardware or software abstraction, a proposed implementation that makes it concrete, and an actual project. The emerging computer system can be built by following the chapters, although this is only one option, since the projects are self-contained and can be done or skipped in any order. All the computer science knowledge necessary for completing the projects is embedded in the book, the only pre-requisite being a programming experience. The book's web site provides all tools and materials necessary to build all the hardware and software systems described in the text, including two hundred test programs for the twelve projects. The projects and systems can be modified to meet various teaching needs, and all the supplied software is open-source.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值