1010 程序是怎么来的
第 5 站 · 动手 三种输入方式

玩具 CPU:亲手体验一次“没有翻译官”

这是一台假想的小计算机,我们叫它小豆 CPU。它有 64 个字节的内存、一个累加器 A、一个输出口, 一共只有 12 条指令。你可以用三种方式给它输入同一段程序,然后数一数你按了多少下—— 这个数字,就是“汇编器”这份工作省下来的力气。

怎么玩(推荐顺序)
  1. 在汇编模式里点一个示例(比如「① 3 + 4」),按「运行」,看输出。
  2. 切到十六进制模式:刚才那段程序变成了数字。这就是没有汇编器时你要亲手敲的东西。
  3. 切到拨开关模式:数字又变成了一格一格的 0/1 开关。当年 Altair 8800 就是这样开机的。
  4. 看一眼右上角的按键计数:同一个程序,三种方式的代价差多少。
Altair 8800 接上 Model 33 电传打字机:拨完引导程序、把 BASIC 读进去之后,人终于能“打字”了。
Altair 8800 接上 Model 33 电传打字机:拨完引导程序、把 BASIC 读进去之后,人终于能“打字”了。图片:Tim Colegrove · CC BY-SA 4.0 · Wikimedia Commons

输入程序

写助记符,让汇编器替你查表、算地址。示例可以直接点。

    (还没有汇编出任何东西)

    按键:汇编 0 · 十六进制 0 · 拨开关 0

    CPU 状态

    状态还没有装载程序
    累加器 A0
    程序计数器 PC0
    已执行步数0

    输出口

      内存(64 字节,橙色是程序占用,绿色是刚执行的字节)

      这台小机器的指令表

      一条指令 2 个字节:先写操作码,再写操作数。所有数字都可以写成十进制或 0x 开头的十六进制。

      写法编码意思
      HLT0停机(内存里没写过的地方都是 0,所以程序跑飞了会自己停下)
      LDA m1把内存第 m 个字节装进 A
      STA m2把 A 存进内存第 m 个字节
      ADD m3A = A + 内存[m]
      SUB m4A = A − 内存[m]
      JMP m5跳到地址 m
      JZ m6A 等于 0 就跳到地址 m
      OUT7把 A 打印出来
      JNZ m8A 不等于 0 就跳到地址 m
      LDI n9A = n(把一个立刻数装进 A)
      ADI n10A = A + n
      SBI n11A = A − n
      DB n—直接放一个字节的数据(0 – 255)

      五个挑战

      1. 热身:让机器打印出 42。(三条指令就够:LDI 42 / OUT / HLT)
      2. 算数:打印 7 + 3 的结果。
      3. 循环:从 10 倒数到 1(提示:看示例②怎么做的)。
      4. 内存:算出 1+2+…+10(提示:示例③把 5 改成 10,答案应该是 55)。
      5. 最硬的一个:切到拨开关模式,用开关拨出「打印 6」这个程序,然后看看你按了多少下; 再切回汇编模式做同一件事,比较两个数字。这个差值,就是第 3 站讲的“翻译官”省下来的力气。
      你会发现一件事:机器其实很笨

      这台小机器不会乘法、不会比较大小、连“把两个数加起来存起来”都要绕好几步。 但它有跳转和条件跳转——就靠这两件事,它能做循环、能做判断, 也就是能做任何计算机能做的事。第 4 站讲的“256 字节种子”之所以可能,就是因为最底下的小程序只需要这么一点点能力。

      想一想:这台机器缺什么?

      它缺一个“把 A 变成 A + A”的指令,所以示例④要靠内存绕一圈。 真实 CPU 的指令表有几百上千条,而每一条都要由硬件电路去实现。 那么——你觉得是“加一条指令”难,还是“写一个汇编器”难?

      回到你的问题

      你一开始问的是:没有程序的时候,人们怎么编程? 玩过这一站,你其实已经亲手做过一遍了: 拨开关、敲数字,就是那个年代的“编程”。而自举的意思是—— 用最笨的方式写下第一段能干活的东西,然后让它替你写出更好的工具,一层一层往上走。 走到今天,你打开编辑器写 print("hi") 的时候,脚下踩着的是七十多年的台阶。