我兼职教授大学课程,包括一门面向所有专业的通用计算机主题课程。这是一门入门课程,教授学生技术如何运作以揭开围绕计算的神秘面纱。虽然不是计算机科学课程,但本课程的一部分涉及计算机编程。我通常用非常抽象的术语谈论编程,这样我就不会让听众不知所措。但今年,我希望我的学生以“老派”的方式进行一些“动手”编程。同时,我想保持简单,以便每个人都能跟上。我喜欢组织我的课程来展示你如何从“那里”到“这里”。理想情况下,我会让我的学生学习如何编写一个简单的程序。然后,我将从这里开始,展示现代编程如何允许开发人员创建更复杂的程序。我决定尝试一种非常规的方法——教给学生终极的低级编程语言:机器语言。机器语言编程AppleII(1977)、TRS-80(1977)和IBMPC(1981)等早期的个人计算机允许用户使用键盘输入程序并在屏幕上显示结果。但是计算机并不总是带有屏幕和键盘。Altair8800和IMSAI8080(均建于1975年)要求用户使用面板上的“开关和指示灯”进入程序。你可以用机器语言输入指令,使用一组开关,机器会点亮LED来代表每条二进制指令的1和0。Altair8800计算机图片对这些早期机器进行编程需要了解称为操作码(操作码的缩写)的机器语言指令,以执行基本操作,例如将两个数字相加或将值存储到计算机内存中。我想向我的学生展示程序员如何通过开关和灯手动输入一系列指令和内存地址。但是,使用实际的Altair8800对于本课程来说有点太复杂了。我需要任何初学者都能掌握的简单知识。理想情况下,我想找到一台与Altair8800工作方式类似的简单“爱好者”复古计算机,但我没能找到价格低于100美元的像样的“类似Altair”的设备。我找到了几个“Altair”软件模拟器,但它们忠实地再现了Altair8800的操作码,这对我的需求来说太重了。我决定编写我自己的“教育”复古计算机。我称之为“玩具CPU”。您可以在我的GitHub存储库中找到它,包括几个工作版本。第一个版本是一个实验原型,运行在FreeDOS上。版本2是在Linux上与ncurses一起运行的更新原型。版本3是一个以图形模式运行的FreeDOS程序。对玩具CPU编程玩具CPU是一台非常简单的复古计算机。它只有256字节的内存和最小的指令集,旨在在复制“开关和灯”编程模型的同时保持简单。它的界面以Altair8800为蓝本,带有一系列8个LED,代表计数器(程序的“行号”)、指令、累加器(用于临时数据的内部存储器)和状态。当您启动ToyCPU时,它会通过清除内存来模拟“启动”。当它启动时,它还会在屏幕右下方的状态灯中显示“INI”(初始化)。“PWR”(电源)灯亮表示玩具CPU已开启。玩具CPU的启动屏幕当玩具CPU准备好让您输入程序时,它会通过状态灯指示“INP”(“输入”模式)并让您从程序的计数器0开始。ToyCPU的程序总是从计数器0开始。在“输入”模式下,使用上下方向键显示不同的程序计数器,按回车键可以编辑当前计数器上的指令。当您进入“编辑”模式时,“EDT”(“编辑”模式)将显示在玩具CPU的状态灯上。玩具CPU编辑屏幕玩具CPU有一个备忘单,“贴”在显示器的前面。它列出了ToyCPU可以处理的不同操作码。00000000(STOP):停止程序执行。00000001(RIGHT):将累加器中的位向右移动一位。值00000010变为00000001,00000001变为00000000。00000010(LEFT):将累加器中的位向左移动一位。值01000000变为10000000,10000000变为00000000。00001111(NOT):对累加器执行二进制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)之间切换值。完成编辑后,按Enter返回“输入”模式。玩具CPU输入模式屏幕示例程序我想通过输入一个将两个值相加并将结果存储在玩具CPU内存中的短程序来探索玩具CPU。实际上,这执行了算术运算A+B=C。要创建这个程序,你只需要几个操作码:00010100(LOAD)00010110ADD00010101STORE00000000(STOP)LOAD、ADD和STORE指令需要一个内存地址,它总是下一个计数器地点。比如程序的前两条指令是:Counter0:00010100Counter1:某个内存地址,第一个值A存放在这里counter0中的指令是一个LOAD操作,counter1中的值就是你的A内存存储值的地址。这两条指令一起将一个值从内存复制到玩具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。您需要将这些分别输入ToyCPU:Counter7:00000001(1)Counter8:00000011(3)Counter9:00000000(0,后面会被覆盖)当你弄清楚所有的指令和A,B,C的内存位置之后,完整的程序就可以了被输入到玩具CPU中。本程序将值1和3相加得到4:计数器0:00010100计数器1:00000111(7)计数器2:00010110计数器3:00001000(8)计数器4:00010101计数器5:00001001(9)计数器6:00000000Counter7:00000001(1)Counter8:00000011(3)Counter9:00000000(0,稍后会被覆盖)要运行程序,在“Enter”模式下按R键。玩具CPU将在状态灯中显示“RUN”(“运行”模式)并从计数器0开始执行您的程序。玩具CPU有明显的延迟,因此您可以看到它执行程序中的每一步。随着程序的进行,您应该看到计数器从00000000(0)移动到00000110(6)。在计数器1之后,程序从内存位置7加载值1,并且累加器更新为00000001(1)。在计数器3之后,程序将增加值3并更新累加器以显示00000100(4)。累加器将保持此状态,直到程序将值存储到计数器5之后的内存位置9,然后在计数器6结束。在运行模式下使用玩具CPU探索机器语言编程您可以使用玩具CPU创建其他程序并进一步探索机器语言编程。通过用机器语言编写这些程序来测试您的创造力。使累加器闪烁的程序你能点亮累加器的右四位,然后是左四位,然后是所有位吗?您可以使用以下两种方式之一编写此程序。一个直接的做法是从不同的内存地址加载三个值,像这样:Counter0:LOADCounter1:"Right"Counter2:LOADCounter3:"Left"Counter4:LOADCounter5:"All"Counter6:STOPCounter7:00001111("Right")Counter8:11110000("Left")Counter9:11111111("All")编写这个程序的另一种方法是尝试使用NOTandOR二元运算。这导致一个更小的程序:计数器0:加载计数器1:“右”计数器2:NOT计数器3:OR计数器4:“右”计数器5:STOP计数器6:00001111(“右”)从数字开始倒计时你可以使用玩具CPU作为倒数计时器。此程序执行IFZERO测试,只有当累加器为零时,程序才会跳转到新的计数器:计数器0:LOAD计数器1:“初始值”计数器2:IFZERO(这也是倒计时“开始”)计数器3:“结束”计数器4:SUB计数器5:“1”计数器6:GOTO计数器7:“开始”计数器8:STOP计数器9:00000111(“初始值”)计数器10:00000001(“1”)玩具CPU学习机器语言的好方法。我在入门课程中使用了ToyCPU,学生们说他们发现很难编写第一个程序,但编写下一个程序要容易得多。学生们还表示,用这种方式编程其实很有趣,他们学到了很多关于计算机实际工作原理的知识。玩具CPU既有趣又有教育意义!
