注: 本文原作者Andy Lithia,译者Wenting Zhang,原文发布于http://lithcore.cn/?p=2036,转载请注明出处,仅限于用于教育或其它非商业用途。头图来源:HP35 technical data

本篇中,作为整个旅程的起点,我会简单介绍一下整体架构。为了更好的理解本文,你需要有数字电路和计算机架构的基础知识。如果没有,个人建议可以阅读《数字设计以及计算机架构》学习一下。

hp_35_internal_arch

图1. HP 35 内部架构(出自HPJ 1972 第6期)

hp_35_motherboard

图2. HP 35 主板 (出自The HP35 saga)

相比较常见的计算机/计算器架构,HP35的设计非常独特,而这也就是本文即将要介绍的。在hpmuseum.org上有一个页面对HP35的ISA做了较为详尽的介绍。

在本文中,寄存器、元件等的命名以专利文档中出现的为准。

运算寄存器

HP35拥有7个56位运算寄存器:A、B、C(X)、D(Y)、E(Z)、F(T)和M。这些寄存器每个可以存储14个4位BCD数。这些寄存器位于ARC芯片之中,ARC是Arithmetic and Register Circuit(算数与寄存器电路)的缩写,这个芯片也被称为A&R芯片。不过不同于常见的通用寄存器,这些寄存器之间的数据传输途径非常有限,具体如图:

reg_transfer

图3. ARC中的寄存器传输图

D,E,F是栈寄存器,只能被压入或者推出。A、B在屏幕开启的时候会直接控制屏幕的显示。

控制寄存器(部分)

之前介绍了ARC中的寄存器。不难注意到其中没有出现任何的程序计数器(PC/IP)或者标志位(Flags)寄存器一类的CPU所必须的东西。这是因为在原始的设计当中,这些寄存器是位于CTC当中的。而CTC是Control and Timing Ciruit(控制与时序电路)的缩写,这个芯片也被称为C&T芯片。以下就介绍一下CTC中的寄存器。

  • P(4): 偏移指针寄存器
  • STATUS BITS(12): 功能等效于其它CPU上的标志位寄存器。其中每个位都可以在单个机器周期内被单独置位、复位或者测试。
  • ROM ADDRESS(8): 程序计数器,存储了当前周期读取的指令的地址。这个寄存器只能通过跳转指令修改。
  • RETURN ADDRESS(8): 在通过JSB跳转到子程序之后可以使用RTN跳转回RETURN ADDRESS指向的地址。
  • SYSTEM COUNTER(6): 计时寄存器,不能被直接访问
  • KEY-CODE BUFFER(6): 当有按键按下的时候,系统计时器(SYSTEM COUNTER)的内容会被复制到这个寄存器当中。因为键盘扫描是和整体计时同步的,所以可以根据当前的时间来确定按下的按键。在早期的型号中,这个寄存器不能被用户直接访问。

其它

整个设计中有许多一位的寄存器(触发器)。值得注意的是这个:

[ROM ENABLE FF]: HP35中有3片256x10bit的ROM芯片,而每个ROM芯片中都有一个这样的触发器。当CPU发出ROM SELECT信号的时候,每个ROM会根据自己的选通状态来设置或者清除这个使能位。设计上,系统中最多可以有8个ROM,而在计算器主板上放8个ROM芯片并不是那么可取,所以后期的型号使用了QUADROM芯片,也就是4合1胶水ROM。

算数功能

BCD算数:在早期的HP计算器当中,内部的数据表示使用的都是BCD,而算术运算也都是使用的十进制运算。后期的型号中有加入二进制模式,才可以进行二进制计算。

就如同IEEE754浮点数一样,HP使用的56位BCD数中各个部分也是有明确的定义划分的。具体定义如下:

bcd_fields

  • 图4. BCD数定义 *

因为一个BCD数需要占用4个bit,所以图里的各个部分的范围也都是以4-bit或者1个BCD位为单位的。图中的两个符号位也各需要占用一个4位的BCD数位,数字9用来表示负号。虽然说并没有直接的浮点计算指令,浮点计算需要软件辅助实现,但是因为硬件的字选择功能是按照这个设定来实现的,为此最好还是遵循这个规定。

值得注意的是,虽然图里只有4个部分,但是实际上定义了8个部分。除了4个独立的部分,剩下的部分是这4个部分的组合:

名称 标号 含义
M 001 底数
S 111 底数符号
MS 101 底数和符号
X 010 指数
XS 110 指数符号
P 000 P寄存器指向的BCD位
W 011 整个寄存器
WP 100 从右开始到P寄存器指向为止的部分

前面提到了字选择功能,字选择(WS/ Word Select)是HP计算器架构中非常重要的一个部分。在绝大部分的指令中,程序员可以通过使用字选择功能来选择那一部分的寄存器会被指令更新(没有选中的部分则保持不变),概念类似于编程中常用到的“mask”。举个例子,比如说使用左移指令,让寄存器内所有数向左移动一个BCD位:

指令:SHIFT LEFT A
操作前:01234567890123
操作后:12345678901230

如果使用字选择功能的话,就可以指定哪一部分会被更新,比如只更新底数部分:

指令:SHIFT LEFT A [M]
操作前:01234567890123
操作后:02345678900123

而这个功能的主要意义就是极大的加快了浮点数的运算速度。所以前面才会说“最好遵循这个规定”,因为否则的话就没有办法利用到这个功能了。

不过,这个功能是怎么实现的呢?确实,听起来就像是一个简单的并行mask,写入的时候AND一下就好。然而看起来并不存在这一的mask,甚至于,负责控制指令执行的CTC和负责存储数据的ARC之间都没有这样的并行连接线。还记得这个系列文章的标题么,字选择就是串行处理过程的一部分,这个信号当然也就是串行的了。不过抱歉,具体的实现还是得等下一期讲了串行处理的原理之后才能介绍了。

寻址

计算器的地址线宽度是8位,加上一共支持最多8个ROM页,所以最多可以达到(2^8)*8=2048字的ROM寻址能力。后期的型号还加入了ROM分块功能,每个块可以支持8个ROM页。

HP的架构只支持直接寻址而不支持间接寻址,具体的寻址方式有:

  • JSB <地址>
  • RETURN
  • BRH <地址>
  • ROM SELECT n
  • KEY -> ROM ADDRESS

其中BRH也被称为GOTO,但是其实际含义是当没有发生进位的时候进行跳转。前四种大致都还是比较常见的操作,而最后一个就比较特殊。它的行为是会把当前KEYCODE BUFFER中记录的数字,补足到8位后存入ROM ADDRESS,以此来跳转到按键扫描码指向的地址。有一些中断向量表的意思。

按照今天的标准来看,这个寻址方式支持是有些怪异了。首先,它并没有间接寻址模式,而且受限于寄存器配置也不能做到手动间接跳转。其次,通过ROM分页的时候,PC寄存器依然会保持自加。这两点加在一起,就使得编写程序变得十分痛苦。不过HP的工程师后来自己也忍不下去了,在后续的型号中增加了DELAYED SELECT指令,允许在进行DELAYED ROM SELECT/ DELAYED GROUP SELECT之后等执行到跳转指令后再进行跳转。

动手体验

teenix.org的Tony编写的CCE33是一个完整的HP LED系列计算器微码模拟器及开发环境。使用CCE33你可以直接浏览、修改执行的汇编代码,监视内存情况,以及直接修改寄存器,类似一般的带debugger的模拟器。另外在他的网站上提供了几乎所有HP计算器的微码文件。这个作者还做了一个基于PIC的实体微码模拟器,用户可以把它连接到电脑,并通过CCE33烧录微码进去。如果你对这个计算器感兴趣,可以在他的网站上购买,或者自己做一个。

HP35指令集

完整的指令列表还是比较长的。以下的内容是根据George Weight在他的论坛文章里面提供的资料整理的。这个列表并不包括HP-45专有的指令(内存访问)。

译注:参考助记是我加上的,HP35的助记符和通常汇编的助记符很不一样,于是写了一下对应的常见汇编的助记符,格式使用Z80格式。

  • Type 00 杂类
操作码 助记符 说明 参考助记
0000_0000_00 NO OPERATION 无操作 NOP
nnnn_0001_00 1 -> Sn 设置状态位n SET n, S
nnnn_0011_00 n -> P 设置P寄存器 LD P, n
nnn0_0100_00 ROM SELECT n 切换到ROM页n -
0011_0100_00 KEY -> ROM ADDRESS 跳转到按键地址 JP [KEY]
nnnn_0101_00 IF Sn = 0 检查状态位n BIT n, S
0000_0111_00 P - 1 -> P 自减P寄存器 DEC P
nnnn_1001_00 0 -> Sn 清除状态位n RES n, S
00001_010_00 DISPLAY TOGGLE 切换显示开关 -
00101_010_00 C EXCHANGE M C和M交换 -
01001_010_00 C -> STACK 将C压入栈 PUSH C
01101_010_00 STACK -> A 从栈推出到A POP A
10001_010_00 DISPLAY OFF 关闭显示 -
10101_010_00 M -> C 从M复制到C LD C, M
11001_010_00 DOWN ROTATE 栈向下滚动 -
11101_010_00 CLEAR REGISTERS 清除寄存器 -
nnnn_1011_00 IF p # n 检查P是否等于n CP P, n
0000_1100_00 RETURN 从子程序返回 RET
0000_1101_00 CLEAR STATUS 清除状态位 LD S, 0
0000_1111_00 P + 1 -> P 自加P寄存器 INC P
  • Type 01 & 11 跳转指令
操作码 助记符 说明 参考助记
aaaaaaaa_01 JSB addr 跳转到并保存返回地址 CALL a
aaaaaaaa_11 (THEN) GO TO addr 如果没有进位则跳转 JP NC, a
  • Type 10 算数指令
操作码 助记符 参考助记(HP41格式)
00000_fff_10 IF B[f] = 0 ?B=0
00001_fff_10 0 -> B[f] B=0
00010_fff_10 IF A >= C[f] ?A>=C
00011_fff_10 IF C[f] >= 1 ?C>=1
00100_fff_10 B -> C[f] C=B
00101_fff_10 0 - C -> C[f] C=-C
00110_fff_10 0 -> C[f] C=0
00111_fff_10 0 - C - 1 -> C[f] C=-C-1
01000_fff_10 SHIFT LEFT A[f] A SL
01001_fff_10 A -> B[f] B=A
01010_fff_10 A - C -> C[f] C=A-C
01011_fff_10 C - 1 -> C[f] C=C-1
01100_fff_10 C -> A[f] A=C
01101_fff_10 IF C[f] = 0 ?C=0
01110_fff_10 A + C -> C[f] C=A+C
01111_fff_10 C + 1 -> C[f] C=C+1
10000_fff_10 IF A >= B[f] ?A>=B
10001_fff_10 B EXCHANGE C[f] BC EX
10010_fff_10 SHIFT RIGHT C[f] C SR
10011_fff_10 IF A[f] >= 1 ?A>=1
10100_fff_10 SHIFT RIGHT B[f] B SR
10101_fff_10 C + C -> C[f] C=C+C
10110_fff_10 SHIFT RIGHT A[f] A SR
10111_fff_10 0 -> A[f] A=0
11000_fff_10 A - B _> A[f] A=A-B
11001_fff_10 A EXCHANGE B[f] AB EX
11010_fff_10 A - C -> A[f] A=A-C
11011_fff_10 A - 1 -> A[f] A=A-
11100_fff_10 A + B -> A[f] A=A+B
11101_fff_10 A EXCHANGE C[f] AC EX
11110_fff_10 A + C -> A[f] A=A+C
11111_fff_10 A + 1 -> A[f] A=A+1