mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
215 lines
12 KiB
Markdown
215 lines
12 KiB
Markdown
|
[#]: subject: "Learn to code with my retro computer program"
|
|||
|
[#]: via: "https://opensource.com/article/23/1/learn-machine-language-retro-computer"
|
|||
|
[#]: author: "Jim Hall https://opensource.com/users/jim-hall"
|
|||
|
[#]: collector: "lkxed"
|
|||
|
[#]: translator: "wxy"
|
|||
|
[#]: reviewer: "wxy"
|
|||
|
[#]: publisher: "wxy"
|
|||
|
[#]: url: "https://linux.cn/article-15461-1.html"
|
|||
|
|
|||
|
用复古电脑程序 Toy CPU 学习低级编程
|
|||
|
======
|
|||
|
|
|||
|
![][0]
|
|||
|
|
|||
|
> 我写了一个名为 “Toy CPU” 的教育性复古计算机程序,以便我的学生能够学习机器语言。
|
|||
|
|
|||
|
我兼职教授大学课程,包括一个对所有专业开放的一般计算机主题的课程。这是一门入门课程,向学生讲授技术是如何运作的,以消除围绕计算的神秘感。
|
|||
|
|
|||
|
虽然不是计算机科学课程,但这门课的一个部分涉及计算机编程。我通常用非常抽象的术语谈论编程,所以不会让听众听不懂。但是今年,我想让我的学生以 “老派” 的方式做一些需要 “动手” 的编程。同时,我又想保持简单,以便让每个人都能跟上。
|
|||
|
|
|||
|
我喜欢将我的课程结构化,以显示你是如何从 “那里” 到 “这里” 的。理想情况下,我会让我的学生学习如何编写一个简单的程序。然后,我将从这里开始,展示现代编程是如何让开发人员创建更复杂的程序的。我决定尝试一种非常规的方法 —— 教学生学习终极的低级别编程语言:机器语言。
|
|||
|
|
|||
|
### 机器语言编程
|
|||
|
|
|||
|
早期的个人电脑如 Apple II(1977 年)、TRS-80(1977 年)和 IBM PC(1981 年)让用户用键盘输入程序,并在屏幕上显示结果。但计算机并不总是带有屏幕和键盘。
|
|||
|
|
|||
|
Altair 8800 和 IMSAI 8080(均为 1975 年制造)要求用户使用面板上的 “开关和灯” 输入程序。你可以用机器语言输入指令,使用一组开关,机器会点亮 LED 灯以代表每个二进制指令的 1 和 0。
|
|||
|
|
|||
|
![Altair 8800 计算机的图片][1]
|
|||
|
|
|||
|
对这些早期机器进行编程,需要了解被称为 “<ruby>操作码<rt>opcode</rt></ruby>” (操作代码的简称)的机器语言指令,以执行基本操作,如将两个数字相加或将一个值存储到计算机的存储器中。我想向我的学生展示程序员是如何通过开关和灯,手工输入一系列指令和内存地址的。
|
|||
|
|
|||
|
然而,在这门课上,使用实际的 Altair 8800 就有点太复杂了。我需要一些简单的、任何初级水平的学生都能掌握的东西。理想情况下,我希望能找到一个简单的 “业余” 复古计算机,其工作原理与 Altair 8800 相似,但我无法找到一个价格低于 100 美元的合适的 “类似 Altair” 的设备。我找到了几个 “Altair” 软件模拟器,但它们忠实地再现了 Altair 8800 的操作码,这对我的需求来说太过沉重。
|
|||
|
|
|||
|
我决定编写我自己的 “教育” 复古计算机。我称它为 “Toy CPU”。你可以在我的 [GitHub 代码库][2] 上找到它,包括几个可以运行的版本。第一版是一个实验性的原型,运行在 [FreeDOS][3] 上。第二版是一个更新的原型,在 Linux 上用 [ncurses][4] 运行。版本 3 是一个 FreeDOS 程序,在图形模式下运行。
|
|||
|
|
|||
|
### Toy CPU 的编程
|
|||
|
|
|||
|
Toy CPU 是一个非常简单的复古计算机。它只有 256 字节的内存和一个最小化的指令集,其目的是在复制 “开关和灯” 编程模式的同时保持简单化。它的界面模仿 Altair 8800,有一系列 8 个 LED 灯,分别代表计数器(程序的 “行号”)、指令、累积器(用于临时数据的内部存储器)和状态。
|
|||
|
|
|||
|
当你启动 Toy CPU 时,它通过清除内存来模拟 “启动”。当它启动时,它也会在屏幕右下方的状态灯中显示 “INI”(初始化)。“PWR”(电源)灯亮表示 Toy CPU 已被打开。
|
|||
|
|
|||
|
![Toy CPU 的启动屏幕][5]
|
|||
|
|
|||
|
当 Toy CPU 准备好让你进入一个程序时,它通过状态灯指示 “INP”(“输入”模式),并让你从程序的计数器 0 开始。Toy CPU 的程序总是从计数器 0 开始。
|
|||
|
|
|||
|
在 “输入” 模式下,用上下方向键显示不同的程序计数器,按回车键编辑当前计数器上的指令。当你进入 “编辑” 模式时,Toy CPU 的状态灯上会显示 “EDT”(“编辑” 模式)。
|
|||
|
|
|||
|
![Toy CPU 编辑屏幕][6]
|
|||
|
|
|||
|
Toy CPU 有一张速查表,被 “贴” 在显示屏的前面。它列出了 Toy CPU 可以处理的不同操作码。
|
|||
|
|
|||
|
- `00000000`(`STOP`):停止程序执行。
|
|||
|
- `00000001`(`RIGHT`):将累加器中的位向右移动一个位置。值 `00000010` 变成 `00000001`,`00000001` 变成 `00000000`。
|
|||
|
- `00000010`(`LEFT`):将累加器中的位向左移动一个位置。值 `01000000` 变成 `10000000`,`10000000` 变成 `00000000`。
|
|||
|
- `00001111`(`NOT`):对累加器进行二进制非操作。例如,值 `10001000` 变成 `01110111`。
|
|||
|
- `00010001`(`AND`):对累加器用存储在某一地址的值进行二进制与操作。该地址被存储在下一个计数器中。
|
|||
|
- `00010010`(`OR`):对累积器用存储在某一地址的值进行二进制或运算。
|
|||
|
- `00010011`(`XOR`):对累加器用存储在某一地址的值进行二进制异或运算。
|
|||
|
- `00010100`(`LOAD`):将一个地址的值加载(复制)到累加器中。
|
|||
|
- `00010101`(`STORE`): 存储(复制)累加器中的值到一个地址。
|
|||
|
- `00010110`(`ADD`):将存储在某一地址的数值加入到累加器中。
|
|||
|
- `00010111`(`SUB`):从累积器中减去储存在某一地址的数值。
|
|||
|
- `00011000`(`GOTO`):转到(跳到)一个计数器地址。
|
|||
|
- `00011001`(`IFZERO`):如果累加器为零,转到(跳到)一个计数器地址。
|
|||
|
- `10000000`(`NOP`):空操作,可以安全地忽略。
|
|||
|
|
|||
|
当处于 “编辑” 模式时,使用左右方向键选择操作码中的一个位,然后按空格键在关闭(0)和开启(1)之间翻转数值。当你完成编辑后,按回车键回到 “输入” 模式。
|
|||
|
|
|||
|
![Toy CPU 输入模式屏幕][7]
|
|||
|
|
|||
|
### 一个示例程序
|
|||
|
|
|||
|
我想通过输入一个简短的程序来探索 Toy CPU,将两个数值相加,并将结果存储在 Toy CPU 的内存中。实际上,这执行的是算术运算 `A+B=C`。要创建这个程序,你只需要几个操作码:
|
|||
|
|
|||
|
- `00010100`(`LOAD`)
|
|||
|
- `00010110`(`ADD`)
|
|||
|
- `00010101`(`STORE`)
|
|||
|
- `00000000`(`STOP`)
|
|||
|
|
|||
|
`LOAD`、`ADD` 和 `STORE` 指令需要一个内存地址,这个地址总是在下一个计数器的位置。例如,程序的前两条指令是:
|
|||
|
|
|||
|
```
|
|||
|
计数器 0:00010100
|
|||
|
计数器 1:某个内存地址,第一个值 A 存放在那里
|
|||
|
```
|
|||
|
|
|||
|
计数器 0 中的指令是 `LOAD` 操作,计数器 1 中的值是你存储某个值的内存地址。这两条指令一起将内存中的数值复制到 Toy CPU 的累加器中,在那里你可以对该数值进行操作。
|
|||
|
|
|||
|
将一个数字 `A` 装入累加器后,你需要将数值 `B` 加到它上面。你可以用这两条指令来做:
|
|||
|
|
|||
|
```
|
|||
|
计数器 2:00010110
|
|||
|
计数器 3:存储第二个值 B 的内存地址
|
|||
|
```
|
|||
|
|
|||
|
假设你把值 `1`(`A`)装入累加器,然后把值 `3`(`B`)加到它上面。现在累加器的值是 `4`。现在你需要用这两条指令把数值 `4` 复制到另一个内存地址(`C`):
|
|||
|
|
|||
|
```
|
|||
|
计数器 4:00010101
|
|||
|
计数器 5:一个内存地址(C),我们可以在那里保存新的值
|
|||
|
```
|
|||
|
|
|||
|
把这两个值加在一起后,现在可以用这条指令结束程序:
|
|||
|
|
|||
|
```
|
|||
|
计数器 6: 00000000
|
|||
|
```
|
|||
|
|
|||
|
计数器 6 之后的任何指令都可以供程序作为存储内存使用。这意味着你可以用计数器 7 的内存来储存值 `A`,计数器 8 的内存来储存值 `B` ,计数器 9 的内存来储存值 `C`。你需要将这些分别输入到 Toy CPU 中:
|
|||
|
|
|||
|
```
|
|||
|
计数器 7:00000001(1)
|
|||
|
计数器 8:00000011(3)
|
|||
|
计数器 9:00000000(0,以后会被覆盖)
|
|||
|
```
|
|||
|
|
|||
|
在弄清了所有指令和 `A`、`B` 和 `C` 的内存位置后,现在可以将完整的程序输入到 Toy CPU 中。这个程序将数值 1 和 3 相加,得到 4:
|
|||
|
|
|||
|
```
|
|||
|
计数器 0:00010100
|
|||
|
计数器 1:00000111(7)
|
|||
|
计数器 2:00010110
|
|||
|
计数器 3:00001000(8)
|
|||
|
计数器 4:00010101
|
|||
|
计数器 5:00001001(9)
|
|||
|
计数器 6:00000000
|
|||
|
计数器 7:00000001(1)
|
|||
|
计数器 8:00000011(3)
|
|||
|
计数器 9:00000000(0,以后会被覆盖)
|
|||
|
```
|
|||
|
|
|||
|
要运行程序,在 “输入” 模式下按下 `R` 键。Toy CPU 将在状态灯中显示 “RUN”(“运行” 模式),并从计数器 0 开始执行你的程序。
|
|||
|
|
|||
|
Toy CPU 有一个明显的延迟,所以你可以看到它执行程序中的每一步。随着程序的进行,你应该看到计数器从 `00000000`(0)移动到 `00000110`(6)。在计数器 1 之后,程序从内存位置 7 加载数值 `1`,累积器更新为 `00000001`(1)。在计数器 3 之后,程序将加数值 `3`,并更新累加器显示 `00000100`(4)。累加器将保持这种状态,直到程序在计数器 5 之后将数值存入内存位置 9,然后在计数器 6 结束。
|
|||
|
|
|||
|
![在运行模式下的 Toy CPU][8]
|
|||
|
|
|||
|
### 探索机器语言编程
|
|||
|
|
|||
|
你可以使用 Toy CPU 来创建其他程序,并进一步探索机器语言编程。通过用机器语言编写这些程序来测试你的创造力。
|
|||
|
|
|||
|
### 一个在累积器上闪灯的程序
|
|||
|
|
|||
|
你能点亮累加器上的右四位,然后是左四位,然后是所有的位吗?你可以用两种方法之一来写这个程序。
|
|||
|
|
|||
|
一种直接的方法是,从不同的内存地址加载三个数值,像这样:
|
|||
|
|
|||
|
```
|
|||
|
计数器 0:LOAD
|
|||
|
计数器 1:“右边”
|
|||
|
计数器 2:LOAD
|
|||
|
计数器 3:“左边”
|
|||
|
计数器 4:LOAD
|
|||
|
计数器 5:“所有”
|
|||
|
计数器 6:STOP
|
|||
|
计数器 7:00001111(“右边”)
|
|||
|
计数器 8:11110000(“左边”)
|
|||
|
计数器 9:11111111(“全部”)
|
|||
|
```
|
|||
|
|
|||
|
写这个程序的另一种方法是尝试使用 `NOT` 和 `OR` 二进制操作。这样可以得到一个更小的程序:
|
|||
|
|
|||
|
```
|
|||
|
计数器 0:LOAD
|
|||
|
计数器 1:“右边”
|
|||
|
计数器 2:NOT
|
|||
|
计数器 3:OR
|
|||
|
计数器 4:“右边”
|
|||
|
计数器 5:STOP
|
|||
|
计数器 6:00001111(“右边”)
|
|||
|
```
|
|||
|
|
|||
|
### 从一个数字开始倒数
|
|||
|
|
|||
|
你可以把 Toy CPU 作为一个倒数计时器。这个程序行使 `IFZERO` 测试,只有当累加器为零时,程序才会跳转到一个新的计数器:
|
|||
|
|
|||
|
```
|
|||
|
计数器 0:LOAD
|
|||
|
计数器 1:“初始值”
|
|||
|
计数器 2:IFZERO(这也是倒计时的“开始”)
|
|||
|
计数器 3:“结束”
|
|||
|
计数器 4:SUB
|
|||
|
计数器 5:“1”
|
|||
|
计数器 6:GOTO
|
|||
|
计数器 7:“开始”
|
|||
|
计数器 8:STOP
|
|||
|
计数器 9:00000111(“初始值”)
|
|||
|
计数器 10:00000001(“1”)
|
|||
|
```
|
|||
|
|
|||
|
Toy CPU 是学习机器语言的一个好方法。我在入门课程中使用了 Toy CPU,学生们说他们发现写第一个程序很困难,但写下一个程序就容易多了。学生们还表示,用这种方式编写程序其实很有趣,他们学到了很多关于计算机实际工作的知识。Toy CPU 既具有教育性,也很有趣味性!
|
|||
|
|
|||
|
--------------------------------------------------------------------------------
|
|||
|
|
|||
|
via: https://opensource.com/article/23/1/learn-machine-language-retro-computer
|
|||
|
|
|||
|
作者:[Jim Hall][a]
|
|||
|
选题:[lkxed][b]
|
|||
|
译者:[wxy](https://github.com/wxy)
|
|||
|
校对:[wxy](https://github.com/wxy)
|
|||
|
|
|||
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
|||
|
|
|||
|
[a]: https://opensource.com/users/jim-hall
|
|||
|
[b]: https://github.com/lkxed
|
|||
|
[1]: https://opensource.com/sites/default/files/2022-12/MITS_Altair_8800_Computer_%281975%29.png
|
|||
|
[2]: https://github.com/freedosproject/toycpu
|
|||
|
[3]: https://opensource.com/downloads/guide-using-freedos
|
|||
|
[4]: https://opensource.com/article/21/8/ncurses-linux
|
|||
|
[5]: https://opensource.com/sites/default/files/2022-12/toycpu.png
|
|||
|
[6]: https://opensource.com/sites/default/files/2022-12/edit0-load.png
|
|||
|
[7]: https://opensource.com/sites/default/files/2022-12/input0-load.png
|
|||
|
[8]: https://opensource.com/sites/default/files/2022-12/run-3.png
|
|||
|
[0]: https://opensource.com/sites/default/files/lead-images/retro_old_unix_computer.png
|