DG0040:生不逢时的末代国产4位CPU
赫尔戈兰-图林根
编辑于 2023年10月01日 05:56
收录于文集
共4篇

在本文正式开始之前,首先

鸣谢微博用户:@一个V_C空天仔 提供的资料支持,以及帮助进行实机程序验证。

鸣谢微博用户:@EE_Archeology 提供的图片影像和其他情报资料支持。

鸣谢B站用户:@SEEDREES 提供的设备、元器件和相关物料支持。

零、关于四位机存在的意义

        显然的,在计算机科学、微电子、材料等领域飞速发展,以至32位微控制器、64位微处理器大行其道的今日,四位计算机、或者说以四位微处理机为核心的控制器件和设备,已经极大程度上失去了其最早出现时所被赋予和期望的那些实用意义。然而,我们不得不承认的一个事实是,无论其实用意义是否一息尚存,仍有少量新架构或老架构的4位微处理器或内核在生产生活的各个方面继续服役,比如一些较早确定架构并沿用至今的液晶屏驱动器、某些外围芯片自带的控制器、一些结构较为简单的科学计算器等。

        从60年代末到80年代末,虽然已经出现了诸多较为经典的8位微处理器,例如8008、8080、8085、Z80、6800、6502等,以及16位微处理器,例如8086,MC68000等。但以当时的制造成本角度来看,四位机因器位宽低、结构更简单、晶体管数量更少,制造成本相对更低。

       此外,以四则运算为例,4位无符号二进制数能够表示0-15,而4bit是能够表示0-9这十个十进制数所需要的最小二进制位宽,因此,四位机只要设计得当,仅靠加减等简单的算数逻辑运算指令就可以处理二进制表达的十进制数(BCD码)相关的科学运算,因而多应用于运算量不大,但对成本较为敏感的系统中,比如计算器。而今天我们要介绍的DG0040,就是一款专为科学计算和数据处理设计的高性能4位CPU。

        因此,在当时的背景下,在许多4位计算机能够胜任的嵌入式应用领域,就没有必要使用成本更高的8位机。此外还有位数更少的CPU/微处理器,比如摩托罗拉MC14500、GI-SBA、国产DJS-010等系列一位计算机(没错,就是1bit计算机),当然,这又是另一个故事了。

        总之,希望各位读者在了解和分析本文章所阐述的观点时,请站在40年前的历史背景上,结合当时社会生产实践需求来看待4位CPU的相关问题。否则一切都将是空谈。

一、序言

        1969年,美国西格尼蒂克(Signetics)公司推出了世界上第一款商用化的单片4位算数逻辑单元(ALU):N8260,开启了运算部件小型化、集成化的序幕。 它拥有1种加法功能和2种逻辑功能,应用于早期的小型计算机(柜式机)。(下图:70年代后期国产仿制版N8260算数逻辑单元和N8261快速进位拓展器,该仿制型号应用于国产的DJS-140机)

N8260/SD400的静态电源电流可达100mA

      1970年,德州仪器公司推出了著名的74181算数逻辑单元,拥有16种算数功能和16种逻辑功能。它主要应用于拥有数据处理功能的数字电路系统之中,包括中小型计算机和台式电子设备、仪器仪表等。74181最经典的应用之一是NOVA系列小型机,国产的某些系列计算机也参考了NOVA系列,也同样使用了74181或与其类似的运算芯片。

SN74S181 采用高速肖特基工艺,静态功耗也很感人,工作起来热到烫手

        1971年9月,德州仪器公司推出了一款商用化4位单片计算器芯片:TMS1802,它将CPU、ROM、RAM、IO等集成在一颗晶片上,被认为是现代MCU、MPU的起源。

        1971年11月,英特尔推出了第一款商业化CPU:4004,它主要应用于Busicom-141PF计算器以及其他不需要太大数据处理量的控制系统中。同年早些时间,英特尔为CTC公司制造了著名的8008 CPU,这两款CPU标志着英特尔公司设计的的微处理器正式进入MOSFET时代;不过,英特尔还是在4004发布之后,推出了一款双极型(TTL)系列微处理机,即intel 3000系列。

(下:英特尔P4004 与民主德国仿制版8008)

P4004与仿制版8008

        1972年,德州仪器推出了一款4位单片计算器芯片:TMS0100。

        1974年,英特尔推出了4004 CPU的改进版:4040。4040相比于4004,增加到了7级硬件指针栈,增加了50%的通用寄存器堆,增肌了一倍直接寻址范围。  同年,德州仪器推出了世界上第一款商用化MCU:TMS1000。(下图:英特尔P4004)

P4040 CPU

        ......

        相比于欧美在集成微处理器科技上的高歌猛进,国产处理器也在努力追赶。

        1968-1969年前后,国产PMOS芯片:5G600系列中小规模逻辑集成电路问世。该系列芯片采用24V供电,拥有功耗较低,稳定性好,抗干扰强的特点。有资料称,东方红一号人造地球卫星就使用了国产的PMOS集成电路。

右下角的18脚芯片就是国产的PMOS数码管译码器芯片,左边三个镀金的是国产数码管驱动器,上面的K155是白俄罗斯产的辉光管驱动器

        1972年,国产的TQ系列推出了一款面向桌面应用的台式电子计算器:TQ-12A,它采用了100余块小规模PMOS工艺芯片,构建了一个包括4位运算器、4位寄存器、微程序存储器、输入输出接口等的微型计算机(实际上是计算器,当时国内对计算机和计算器的区分还不是太严格);到70年代中期,该型计算器进行了简化,将原来分散的集成电路整合到不到20个大中规模集成电路中,比较著名的型号是TQ-12G。(下图:TQ12G计算器中后期版本的主板)

TQ12-G主板,1978年中期版本

        同时期,国产分片改进版英特尔8008 CPU(国产型号DJS-050)、国产分片版8080 CPU(DJS-051)也相继问世。不过因为工艺、集成度、良率等原因,这些型号都将一个完整的CPU打散了分到多个芯片中。直到1979年仿制成功了单片8080,也就是著名的5G8080,由上海元件五厂生产。进入80年代后,也有8086的仿制版:L80C86-2。(下图:无线电杂志上的5G8080)

无线电杂志 1980.5

        同为社会主义阵营的东德和苏联,也分别在1978年和70年代末期仿制成功了单片8008和8080cpu。(下图:8008克隆版,德意志民主共和国制造)

较为早期版本的U808D

        相比于8位及以上的处理器芯片,4位微处理器的关注度显然更低一些,但也不乏体系相对完善的系列产品。其中以上海无线电十四厂等单位联合研制的DJS-020系列(原DJS-040,后因重名而改为DJS-020)是其中较为有名的一个系列。

DJS-020书影。此书错印成了DJS-010,但实际上DJS-010是指国产的MC14500系列微机

        DJS-020本质上是仿制日本夏普公司于70年代中期研制的的SM - 2单片微型计算机。采用P沟MOS工艺,电源电压-12V,数据位宽4位,ROM空间1008*8bit,RAM空间64*4bit。拥有外部同步信号、双向I/O口、单向I/O口、串行移位信号输出等。由于技术限制,DJS-020采用5片分片式结构,将一个单片机分为2个CPU单元,一个RAM单元,一个接口单元,一个专用ROM单元。

        作为DJS-020的改进版,在80年代初,由由合肥晶体管厂、邵阳无线电厂、88101/88200部队、北京东光电工厂联合研制,由北京东光电工厂(国营878厂)负责版图和生产的DG0040,于1983年面向国内市场发售。

        1983年时的参考售价为:一片DG0040 + 一片DG0041参考价格为200元(150+50)。(下图:3片DG0040、一片DG0041、一片DG0046。其中一片DG0040实为DG0044)

DG0040与DG0041.DG0046

        DG0040为单+5V供电、采用6微米硅栅N沟道E/D MOS工艺,在一个晶片上集成了1.1万个晶体管(远大于Z80、6502等,约为8086的40%),采用静态、动态混合逻辑,因而时钟周期有最大值和最小值的限制,无法手动单步运行。

        DG0040采用4位数据宽度,属于复杂指令集,指令一共48条,其中5条为双字节指令,其他均为单字节单周期,一般指令周期为10us(参考:4004指令周期10.8us,8008指令周期20us),理论最短指令周期为6.66us。部分指令最多由多达7个微码组成,并且很多指令联用时,有特殊效果,联用指令最多3条,这也造就了该机超级复杂的控制电路。0040有外部双向数据读写总线,但没有读写脉冲输出,只有同步信号输出。CPU内部只有ACC(累加器)一个通用寄存器,另有256*4bit SRAM,这也让该CPU的制造难度大大增加,根据记载,良品率只有不到25%。部分寄存器采用准静态触发器结构。(下图:无线电杂志上的DG0040处理器评估板)

无线电1984.5封面,左上角是以DG0040为核心的评估板

同系列配套芯片包括:

DG0040:4位CPU

DG0041:时钟发生器(几个逻辑门和触发器)+ 串口转并口开漏输出(等效两个高压版74HC595)+ 数码管译码驱动器(约等效为高压版74LS48)

DG0042 / DG0043:2016*8bit 掩膜ROM程序存储器

DG0044 / DG0045:4位CPU,比DG0040的RAM 减少1/2 和 减少3/4

DG0046:多功能专用I/O接口电路

DG0047:通用译码器(类似74ls139)

DG0048:8同相高反压驱动器(类似ULN2804)

DG00401:4位CPU(带中断和定时器),直接寻址范围减半。

DG0040 反面图

DG0040 基本参数:

生产时间:1983-1985;月产量:≤ 500 片

封装:CDIP-40(白陶瓷镀金封装)

存储器组织架构:数据、指令分离(类似哈佛架构)

ROM寻址范围8K * 8bit。分为PS(3根地址线)、PU(4根地址线)、PL(6根地址线),共8*16*63=8064字节(注意!不是8192)。

指令存储器数据宽度:8bit

指令存储器模式;大端模式

硬件指针栈:5级13bit

RAM:静态6管SRAM结构,256 * 4bit,直接寻址80 * 4bit,间接寻址176 * 4bit,全部RAM空间支持按位操作。

输入端口:按键输入口K

双向端口:G(有方向同步信号),可单独使用,也可联合DG0046进行I/O口拓展

输出端口:L寄存器取反输出

输出端口:移位寄存器D1-D15可屏蔽输出(需要DG0041)

输出端口:L寄存器经数码管译码输出(需要DG0041)

电源电压:单5V±5%

工作温度:民品:-10~+70℃,军品:-40~+85℃

设计主频:80kHz -- 150kHz

运行功耗:在5V电源电压时,80mA / 400mW(100kHz主频)

一套DG0040,DG0041,DG0046,均为1984年生产

        DG0040广泛应用于工业、农业、控制、科研等领域。比较著名的产品有:四川峨眉无线电厂生产的 EMC-5粮油收购计算机、温州市电子技术研究所生产的MQJ-0401型精密RLC电桥等;八七八厂在推广该CPU的同时,也在考虑生产塑封版本,以降低成本。虽然该CPU技术相对成熟,但由于出现时间太晚,很快到了80年代后期,进口低成本8位CPU大量占领国内市场,DG0040因性能逐渐落后,且生产成本居高不下,产能不足等原因,最终退出市场。

EMC-5粮油收购计算机 使用手册

二、DJS-020与DG0040的硬件基本构成

        由于DJS-020本质上是日本SM-2微处理器的仿制版。因此,在介绍DJS-020之前,不妨先看一下他的原版设计,这有助于我们的理解。

    1.日本SM-2/SM-4单片微机 与 DJS-020微机 架构

        下面这张简表中,我们可以看到SM-2和SM-4的基本参数:SM-2采用PMOS工艺,约1KB寻址范围,64*4bit RAM空间。

    然后我们再来看一下DJS-020的基本结构:它也是采用PMOS工艺,1008Byte的ROM,64*4bit的RAM,但是其他结构部分就很难比对了。

DJS-020结构图

        因此,我们找到了SM-4微处理器的结构图(下图),它是SM-2的改进版,有很大参考价值。可见:

1. SM-4采用高位地址寄存器BM和地位地址寄存器BL对RAM进行寻址,而DJS020也相应地拥有同功能的BU和BL

2. SM-4采用PU和PL对ROM进行寻址,DJS-020也一样

3. SM-4只有一个累加器作为数据中转和运算寄存器,DJS-020也一样

4. SM-4用α、β两个端口作为打印机异步输入信号,DJS-020也一样

......因此,除了SM-4额外挂载了一个LCD驱动器,SM-4和DJS-020有诸多相似之处,因而可以判断出DJS-020是SM-2的仿制版。

SM-4A结构图

        至于为什么要仿制日本的这种微控制器,个人认为有几点原因:

        1.相比于4004/4040的时序,SM-2/4不需要太多配套芯片的支持,对外接口非常友好,可拓展性强,是非常适合我国各个领域的生产实践需要的。这也是为什么我国也仿制了美国的COP420系列单片机,它和SM-2的特性较为相似。

        2.它的寄存器寻址方式较为特殊,可以以极高的效率进行科学运算,效率高于4004。这一点在后文中,运算程序示例部分会有所体现。

    2. DG0040 硬件架构

        虽然脱胎自DJS-020,DG0040在逻辑重新设计后,与原来有着很多不同,主要在于:取消了异步输入信号、增加了RAM寻址范围、程序寻址范围增加到8KB。架构图如下:

一套最基本的DG0040系统,由 DG0040、DG0041、单片/多片 DG0042/EPROM 组成

        总的来看,DG0040的硬件部分有如下几个特点:

        1. 5级硬件指针栈

        2. 单ACC,无其他通用寄存器

       3. 有四个1bit状态寄存器:F Z W R 和一个进位寄存器 C

        4. 有两个专用寄存器:L和G,其中L仅输出,G可以进行输出和输入,G功能由F状态寄存器决定。

        5. 256*4bit RAM,由BS BU BL三个寄存器共同寻址

        6. 程序计数器分为三段,PS PU PL,其中PS  PU 不会像一般的CPU一样,接收来自低位程序计数器PL的溢出而自加;而PL也不是采用自加方式,而是采用同或+移位产生一个类似伪随机数的地址进行指令的寻址。

        下面解释一下DG0040中,程序计数器PL段的运行方式,这也是整个CPU中最有意思的部分。PL一共分为6位,即PL5-PL0(或者说PL6-PL1),每一位是一个单独的触发器,高位的触发器的输出接到次低位的输入,最高位的输入来自最低位和次低位输出进行的同或操作,硬件结构如下图:

        假设所有触发器初始上电状态均为0,即0x00,那么根据同或和移位运算,下一个地址不是0x01,而实际是0x20,再下一个为0x30......为了方便编程,一般编写程序时,需要按照转换表中的真实运行顺序来放置需要执行的指令,而涉及到立即数的跳转指令,也需要将其转换成真实地址,然后编入程序中。对照表如下:

        同时,我们可以看到,0x3F的位置没有对照值,这是因为该程序计数器,虽然有6bit,但是只能寻址63个地址,最后一个0x3F是寻不到的。所以,使用了该程序计数器的DJS020和DG0040,寻址范围都不是2的正整数幂次,而是63的整数倍。

        至于为什么该CPU采用了如此不人性化的程序计数器,目前有两种主流猜测:一是为了节省晶体管数量,毕竟6位同步计数器还是要在D触发器基础上加了一些逻辑门,但问题是整个CPU有11K个晶体管,即使刨除SRAM占用的6000个,还剩5000个晶体管,这依然可以媲美许多8位CPU了,所以为了节省晶体管这个说法我个人持保留意见。第二个观点认为,这种近乎无规律跳转的寻址方式,可以增加保密性,毕竟资料上写过该CPU也有一定的军事用途(火控计算设备等)。但问题是,世界上绝大多数军用版CPU都是+1寻址的,所以该寻址方式是否能够有效增加保密性,依然存疑。

        另外,DG0040的计数器高7位(PS 3位、PU 4位)均不会因为PL寻址完63个地址就产生自加,而是必须在这63条指令上限运行完之前,手动切换到下一个页(PS指定)或区(PU指定),因此该机每个扇区的实际可执行的有效指令数只有60-62个。由于DG0040的单周期指令宽度为8,而双周期的长指令全部留给了G端口以及DG0046,这注定了该CPU在需要跳转时,无法一次填充PS PU PL,所以它们都有一个预存储寄存器进行缓冲,即SPS和SPU。用户可以先行填充SPS SPU,然后在填充PL时,将SPS SPU一起打入PS PU,完成跳转。当然也可以选择跳转时不考虑SPS和SPU,这由多条程序的组合方式决定。

PS PU有相对应的暂存器:SPS SPU,而PL只能从立即数中接收数据,或者自位移

         DG0040采用5级13bit硬件指针栈,支持五级子程序。相比之下,DJS-020只有一级指针栈;在DG00401中,采用了4级12bit堆栈,但由于DG00401有一级可屏蔽中断,因此其资源更加紧张。

        接下来介绍一下DG0040的RAM寻址方式。该CPU能以极高的单指令效率运行,RAM的寻址方式功不可没。

        DG0040的内部RAM为256*4bit,按照4页*4区*16单元的方式组织,因此RAM拥有三个寄存器来寻址:BS BU BL

       BS为2bit,可置立即数,可通过异或操作修改,将整个RAM分为4个页

      BU为2bit,类似BS,将每个页分为4个区。

       BL为4bit,可置部分立即数,可自加/自减,将每个RAM区 分为16个4bit单元

        从实际应用角度来讲,RAM中可以存储BCD码的多位十进制数。一般的计算器为8-12位,而DG0040的一个RAM区(BS BU不变,BL从0-15)可以存储16个十进制数,剩余的空间还可以存放小数点、符号等信息。

        对于某些特定应用场景,比如计算某一户人家的棉花、粮油、蔬果收购价格,因为人民币只能精确到分(不考虑厘),使用6位数就可以表示1分钱直到9999.99人民币的价格,在80年代中期,作为单户的农作物收购的计算是足够了。这时,DG0040的一个RAM区如果能保存两组数据,就可以提高存储效率。因此,DG0040在设计上,有一个R寄存器(1bit)。当R=0时,每个区是正常的16位长度;当R=1时,每个区虽然长度不变,但是它会在寻址到6/8号单元时产生跳判条件,而不是开头和结尾。假设我们需要连续计算两个区的和,那么当处于短数据类型时(R=1),程序执行到半个区结束处,就会知道已经到达半区的边界,从而退出数据处理函数。因此,R寄存器=1时,相当于在上下半区之间增加了一道无形的屏障,程序会看到这个屏障,从而让上下半区分别存储两个数据而互不干扰。

        此外,由于每次执行完数据传递后,若处在短数据模式下(R=1),需要涉及到同一扇区或不同扇区高低半区之间的传递,就需要BL的最高位在0 1之间不断跳跃。因此DG0040拥有一个BL高位异或修改条件寄存器W(1bit),当R=1且W=1时,每次进行与RAM有关的数据传递时,会在BL加减1的基础上,对BL的最高位(BL3)进行一次翻转,从而达到切换高低半区的效果。

        上图展示了,利用R寄存器以及W寄存器的不同取值,相同的数据传递程序,会产生不同的效果。当R=0时,系统认为一整个区是一个整体,会进行16单元与另外16单元的交换。而R=1时,系统会认为高低8单元是分开的,从而对某个单独的半区(8单元)进行操作。在此基础上,每一次两个单元交换完数据时,都会根据交换语句来选择BL加一还是减一,从而使RAM指针指向下一对需要交换的单元,交换完之后继续这样的操作,直到遇到“屏障”,程序从出口退出。

        说完了BL的操作方式,BS和BU就相对简单了,它可以分别置立即数,也可以通过异或操作进行修改。例如进行ACC与某RAM单元进行交换时,BS会与L寄存器的低2位进行异或,而BU则与指令所提供的2位立即数进行异或。

        异或完一次之后,RAM指针就跳到了下一个需要操作的区,然后再进行一次数据交换(同时伴随着异或操作),若立即数条件和L寄存器都不变,则RAM指针就会跳回原来的区......这样,在两个区之间进行往复跳转,同时伴随BL的自加/自减,可以很便捷的完成数据交换、加减乃至乘除法。

        虽然DG0040除了ACC之外,没有通用寄存器堆,但通过某个RAM单元、BL、ACC和进位寄存器C之间的配合,也可以完成循环操作,完成诸如软件延时、等待等功能。一般可由BL充当循环变量。

         就如同时代的大部分以A累加器为核心的微处理器,DG0040的数据交换也基本以ACC(4位)为核心。涉及到ACC得操作主要有5类:装载立即数、数据移入ACC、数据移出ACC、RAM数据与ACC互换、运算操作。

         在ACC之外,其实还有一个功能较多的寄存器,即L寄存器(4位)。L寄存器的功能主要有:段扫描显示输出(直接输出到芯片外)、PS装载缓冲地址、BS异或修改等。L寄存器可以和BS寄存器一起直接装填立即数(这种操作只改变最低2位),也可以在需要修改整个L寄存器时,选择先将立即数装载到ACC,然后再从ACC移入L寄存器。

         DG0040有一个双向4位IO口,G端口。但和8080,8051之类的双向总线不同,DG0040的双向端口是完全手动控制的:需要在程序中设定该端口的状态,以及输出缓冲寄存器的值,并手动进行数据传递操作。而以8051为例,它只需要dptr相关操作指令,就可以让数据总线、读写信号、锁存信号等自动组织起一个时序,完成对外部存储器或映射的读写。相对的,DG0040的G端口是真双向的,当处于输出态时(使用状态控制寄存器F,F=1为输出态),G端口由内部的推挽结构驱动,外部信号无法进入,此时如果读取G端口的输入值,实际上读取的是此时G端口输出缓冲器的值。DG0040的其他外部接口都是单向的,比如L输出端,K输入段,D移位寄存器输出端,因而有些人认为DG0040更像是一个没有ROM的单片机。

        DG0040还有一个游离的状态字寄存器,即Z寄存器(1bit),它只能由特定的置位和复位指令操作,而且只能用于特定的跳判指令读取。除上述三条之外,其他任何指令都不影响Z寄存器,也不在乎Z寄存器的状态。

        其他硬件结构部分其实并无特别,这里先省略。

        本段最后介绍一下DG0040的引脚。由于DG0040系统需要涉及到串口转并口、数码管译码等功能,一片DIP-40芯片肯定装不下,所以需要一片DG0041与其配合。DG0040内含晶体管数量明显少得多,主要负责三种功能:时钟和复位、数码管译码器、串转并输出。DG0041在有些时候也可以用少量TTL逻辑器件代替。DG0040和DG0041的引脚图如下:

DG0040与DG0041的引脚分布

DG0040引脚:

1RF/跳步输入/输出引脚(开漏)

2-4:PS /程序存储器区地址输出

5-8:PU /程序存储器页地址输出

9-14:PL /程序存储器单元地址输出

15-19、21-23:ROM /程序存储器输入端

20:VSS /接地

24-25:CLKF1、CLKF2 /双相位时钟输入端

26-29:G /双向端口输入/输出

30-33:K /并行输入端

34:CLR /复位输入端,低有效

35-38:L  /L寄存器取反输出端

39:ND /移位寄存器打入脉冲输出端

40:VDD /正5V电源输入端

DG0041引脚:

1-4:L  /L寄存器取反输入端

5-19:D /D移位寄存器输出端(高压、开漏可直驱VFD)

20:VSS /接地

21:TEST /测试引脚,正常使用时接VDD

22:SYNC /同步信号引脚,使用DG0041片内VCO时,接VDD;片外时钟输入时,接时钟信号

23:RF /跳步信号输入端,用于在DG0040跳步时屏蔽程序

24:VF /压控震荡器控制端

25:ROM0 /接ROM D0,决定写入移位寄存器的数字是0还是1

26:ROM2 /接ROM D2,决定写入移位寄存器的数据还是使能端

27:Nd /移位寄存器时钟信号输入

28:ACL /复位信号输入端

29:CL /复位信号输出端

30-31:CLKF1、CLKF2 /双相位时钟输入端

32-39:A-G、DP / L输入的数码管译码输出端(七段+小数点,高压开漏,可直驱VFD)

40:VDD /正5V电源输入端

DG0040的时钟时序:

        DG0040部分采用了准静态触发器,即PH触发器,除此之外还有D、RS、RSS等。因而需要一个速度适中的时钟,保证数据能够有足够的反应时间来传递,同时又要保证及时刷新,不至于丢失数据。

        当CPU主频处于100KHz时,将每一个周期均等分为10份,第1-3份为F1高电平,第5-7份为F2高电平。

        同时,为了对内部RAM数据存储器做读写的时序同步,在DG0040内部会根据F1和F2的下降沿生成另一个同步信号:#1R。该信号有效电平(低电平)时间自某一次F2下降沿开始,到下一次F1下降沿结束。在同步信号有效器件,F1为低电平时,对RAM进行读取;F1若为高电平则对RAM进行写。如果在不需要写RAM的周期,由指令控制地只对RAM发出写指令而不对RAM的写操作进行使能。时序图如下所示:

输入输出相关时序:

        DG0040的输入端口K(和G)在F1上升沿采样,需要留出1us的建立时间。输出端口(应该是L和G)在CPU收到操作指令后需要5us建立。

        由于还有移位寄存器D和L段译码输出存在,这两个输出端口显然要在DG0040控制信号输出延时的基础上再增加一个延迟。但由于这二者均为开漏输出结构,其具体的建立时间与上拉电阻、负载电容等有关,因此手册中没有给出明确的技术参数。

DG0041的L译码器:8421编码与段译码输出表:

        0xA 为小数点,0xF 为消隐。

        值得注意的是,DG0040的L寄存器输出到片外是取反了的。DG0041是按照取反前的L值进行段译码。如果需要自己接其他类型的译码器,请先将L取反,或按L反逻辑设计译码器。

连接扫描数码管、按键,并使用IO口模拟对2K RAM的读写 的示例电路如下:

DG0040经典最系统(扩展2KB RAM)示例图

该系统中,对RAM进行读写的示例程序如下:

代码块
clike
自动换行
复制代码
//------------ RAM写示例-------------//
SHD0;		//选择读

SHD0;
SHD0;		//0x0**

SHD1;
SHD0;
SHD0;
SHD0;		//0x08*

SHD0;
SHD1;
SHD0;
SHD1;		//0x085

ENGO;		//G处于输出模式
SNP;

ATG;		//G放置需要写的数据

SSP $S+2;
SSP $U;
JMP $L+1;	//翻转PS2,PS2=1

SSP $S;
SSP $U;
JMP $L+1;	//翻转PS2,PS2=0

RNP;

//------------ RAM读示例-------------//

SHD1;		//选择写

SHD0;
SHD0;		//0x0**

SHD1;
SHD0;
SHD0;
SHD0;		//0x08*

SHD0;
SHD1;
SHD0;
SHD1;		//0x085

ENGI;		//G处于输入模式
SNP;

SSP $S+2;
SSP $U;
JMP $L+1;	//翻转PS2,PS2=1

GTA			//读取端口

SSP $S;
SSP $U;
JMP $L+1;	//翻转PS2,PS2=0

RNP;
复制成功

            这段程序对应的硬件,CPU连接的ROM只有4KB,利用最高位地址线翻转而不改变其他低位正常寻址的特性,利用PS2作为读写使能信号。并利用D移位寄存器输出RAM地址信号、RAM片选信号。同时配合双向的G端口,模拟RAM读写时序,完成对RAM的读写。

不使用DG0041,而使用分立逻辑代替DG0041的最小系统如下:

分立逻辑代替DG0041示例图

       在DG0041上有一个串转并移位寄存器,即D0-D14输出口。本质上是因为DG0040引脚数量不够,因此使用移位寄存器来扩展输出。这个移位寄存器与74HC595类似,但没有输出缓冲器,只有输出使能。且采用高反压开漏结构,带负载能力较强,可以直接驱动LED数码管、VFD荧光屏等。

        另外,DG00401带中断和定时器版CPU 和 DG0046接口芯片 内部都有定时器。如果需要较为精确的计时场合,DG0041的压控振荡器不够稳定,频率不够精准。可将TEST引脚拉低至GND/VSS,将外部TTL电平高精度时钟信号从SYNC引脚输入到DG0041.

三、DG0040的指令集

         粗略地讲,DG0040的指令主要分为五大类:1.数据传递与立即数传递指令;2.算数运算指令;3.跳步判断指令;4.位操作指令;5.程序分支指令。

        程序分支指令比较好理解,无非是JMP/Branch,CALL/Branch Subroutine with Save,RET/Branch Back以及其相关的变体。但是跳步操作在现代的CPU上不太常见,这里稍作解释: 

        假设有一个跳步指令,SKIP,它的下一个指令为MOV A,B。这两条指令如果连在一起,当执行到SKIP指令时,程序会跳过下一条MOV指令,而是从MOV的下一条指令开始执行。当然,DG0040里的跳步指令,绝大多数都是有条件判断是否进行跳步的,这也达成了该机最重要的的图灵完备。

        因为DG0040的JMP、CALL、RET都是无条件的。跳步指令与程序分支指令联用,可以达成条件分支的效果,这样就不需要很多带着立即数的条件分支指令,因为这些带条件和较长立即数的分支指令一般都是多字节的,较长的取指不利于快速取指、运行;然而不幸的是,即使DG0040只有一条JMP和一条CALL,它们所拥有的立即数,也使得仅这两条指令就占满了50%的指令空间(0x80-0xFF),迫使其他单字节指令和带有立即数的单字节指令在剩余50%的空间内艰难生存。

        原版DG0040 CPU 指令集总表如下:(如有刊误,请指正)

DG0040指令表

       下面分别简要介绍一下五大类指令:

1.数据传递与立即数装载指令;

        0x01: ATG    //ACC数据装载到G输出缓冲器,只有在G端口处于输出态时,该缓冲器内的数据送出片外。

        0x08: KTA    //K输入端口数据传送到A寄存器

        0x0A: GTA    //当G处于输入态时,外部数据通过G端口传递到ACC;当G端口处于输出态时,G端口输出缓冲器的数据传送到ACC。

        0x10-0x13: EXC,Y(Y是立即数,0-3)    //1.交换ACC和当前指针指向的RAM单元数据;2.BU与立即数异或,指令结束时重新写入BU;3.BS与L低2位异或,指令结束时重新写入BS;4.若R=1,则BL3与W异或后,指令结束时重新写入BL3,BL2-0不变。

        0x14-0x17: EXCI,Y(Y是立即数,0-3)    //1.交换ACC和当前指针指向的RAM单元数据;2.BU与立即数异或,指令结束时重新写入BU;3.BS与L低2位异或,指令结束时重新写入BS;4.若R=1,则BL3与W异或后,指令结束时重新写入BL3,BL2-0不变 ; 5. BL自加1(其实这个自加1与第四条的异或修改不冲突,无论顺序先后,结果都一样,请读者自行分析);6.如果R=0,则修改前BL若=B则跳步;7.若R=1,则修改前BL=6或E时,执行跳步。

        0x18-0x1A: LDA,Y(Y是立即数,0-3) //1.当前指针指向的RAM单元数据写入ACC;2.BU与立即数异或,指令结束时重新写入BU;3.BS与L低2位异或,指令结束时重新写入BS;4.若R=1,则BL3与W异或后,指令结束时重新写入BL3,BL2-0不变。

        0x1C-0x1F: EXCD,Y(Y是立即数,0-3)    //1.交换ACC和当前指针指向的RAM单元数据;2.BU与立即数异或,指令结束时重新写入BU;3.BS与L低2位异或,指令结束时重新写入BS;4.若R=1,则BL3与W异或后,指令结束时重新写入BL3,BL2-0不变; 5. BL自减1(其实这个自减1与第四条的异或修改不冲突,无论顺序先后,结果都一样,请读者自行分析);6.如果R=0,则修改前BL若=0则跳步;7.若R=1,则修改前BL=0或8时,执行跳步。

        0x20 - 0x2F: LAM Y(Y是立即数,0-F)  //将立即数送到ACC;若该指令连续出现,则从第二条开始执行:Y送到RAM,然后RAM的BL指针自加1,BL溢出就从0循环。

        0x40-0x53: LB X,Y   (X是立即数,0-4;Y是立即数,0-3) //向BU装载立即数Y,向BL装载立即数(X的映射)。BL实际装载的立即数与R有关,见下表:当处于长数据格式和短数据格式时,利用X立即数可以指定整区和半区的头尾,这样节省了带有立即数的指令空间。同时,如果多条LB连续出现,第二条及以后的LB均不会被执行。利用这一特点,可以利用函数入口传递需要操作的扇区位置。

        0x60-0x63: LBS Y (Y是立即数,0-3)//将立即数Y送到BS,同时送到L寄存器的最低2位。L寄存器最高2位不变。

        0x6C: LTSPU  //L寄存器赋值给SPU暂存器,然后L清零。

        0x6D: ATL  //ACC赋值给L寄存器。

        0x70 - 0x7F: SSP Y(Y是立即数,0-F)  //若此条指令的上一条既不是SSP,也不是LTSPU,则将立即数送往SPU暂存器。反之,将立即数的低三位送往SPS寄存器,最高位立即数被抛弃。(当LTSPU或SSP与SSP连用是,可以指定与当前程序运行页、区不同的程序块)。

2.运算和加减指令;

        0x0B: CADCSC  //其实就是SUB,这个指令缩写前也许是:Complement  A  (add RAM) Decline C and Set C,执行的实际操作是A先取反,然后+RAM+C,结果写回ACC和C。这种减法指令的特点是,对减数没有取补码,因而借位输入和借位输出的有效电平都是0,而不是加法常用的1.

        0x0C: ADD  //ACC+RAM写回ACC,不看C,也不影响C

        0x0E: ADC  //ACC+RAM+C写回ACC和C,看C,也影响C

        0x0F: ADT  //在ADC指令基础上,若结果C=0,则跳步

        0x31-0x35;0x37-0x3F: ADX, Y (Y是立即数,1-5,7-F) // A+Y结果写回A,若产生的进位为0则跳步。但产生的进位不更新C,指令结束后即抛弃

        0x36: DAA    //十进制调整指令,若C=1或ACC大于等于10,则A+6写回A,C强制置1,否则C、ACC都不变

        0x54: INCB   //BL+1;如果R=0,则修改前BL若=B则跳步;若R=1,则修改前BL=6或E时,执行跳步。

        0x5C: DECB   //BL-1;如果R=0,则修改前BL若=0则跳步;若R=1,则修改前BL=0或8时,执行跳步。

3.单独跳步判断指令;

        0x09: TC    //若C=0,则跳步

        0x0D: TAM    //若ACC=RAM,则跳步

        0x30: SKZ    //若Z=1,则跳步

        0x64-0x67: SKM,Y(Y是立即数,0-3)//若RAM当前单元的第Y位(从低往高数)为1,则跳步。

        0x5764-0x5767: SKG,Y(Y是立即数,0-3)//若G端口输入信号的第Y位(从低往高数)为1,则跳步。

4.位操作和单bit寄存器操作指令;

        0x00: NOP    //啥也不干

        0x02: RC    //C标志复位

        0x03: SC    //C标志置位

        0x04 - 0x07: RMP,Y(Y是立即数,0-3)//复位当前指向的RAM单元的第Y位

        0x55: RZ    //Z标志复位。

        0x56: CMPR    //R寄存器翻转,数据长短格式切换。

        0x58 - 0x5B: SMP,Y(Y是立即数,0-3)//置位当前指向的RAM单元的第Y位。

        0x5C: SZ    //Z标志置位。

        0x68: RW    //W标志复位。

        0x69: SW    //W标志置位。

        0x6A: SHD0    //向移位寄存器组D打入0,并使整个移位寄存器右移一位。

        0x6B: SHD1    //向移位寄存器组D打入1,并使整个移位寄存器右移一位。

        0x6E: RNp    //Np标志复位,禁止移位寄存器组拉高。

        0x6F: SNp    //Np标志置位,允许移位寄存器组拉高。

        0x5702: ENGI    //F标志复位,允许G端口输入,禁止G输出缓冲器输出

        0x5703: ENGO   //D标志置位,允许G输出缓冲器输出,允许G输入读取缓冲区输出数据

        0x5704 - 0x5707: RGp,Y(Y是立即数,0-3) //复位G输出缓冲器的第Y位

        0x5758 - 0x575B: SGp,Y(Y是立即数,0-3) //置位G输出缓冲器的第Y位

5.程序分支指令。

        0x5E: RET    //子程序无条件返回,并将堆栈高三位打入SPS;堆栈指针-1,栈顶抛弃

        0x5F: RETSK    //子程序无条件返回,并再跳一步,并将堆栈高三位打入SPS;堆栈指针-1,栈顶抛弃

        0x80-0xBE: JMP,Y(Y是立即数,0x00 - 0x3E)//若上一条指令不是LTSPU或者SSP,则PL跳转到Y地址,PS PU不变,进行扇区内跳转;否则同时执行SPS和SPU分别打入PS和PU,进行跨页和跨区跳转。

        0xC0-0xFE: CALL,Y(Y是立即数,0x00 - 0x3E)//若上一条指令不是LTSPU或者SSP,则PL跳转到Y地址,PS不变,PU置F,并保存原来的PS、PU、PL,以进行页内快速子程序调用,并定位在页内的F区;否则同时执行SPS和SPU分别打入PS和PU,进行跨页和跨区子程序调用,同样保存原程序计数器。堆栈指针+1,栈底丢失。

        从上面的指令集,我们可以看出DG0040指令的几个特点:

        1.没有乘除法指令(其实大部分4位机都没有),只有加减法(有些8位机都没有减法。对吧,8048)和十进制调整。且有些算数指令可以直接根据运算结果进行跳步判断。

        2.丰富的位操作指令,涵盖了RAM、G输出缓冲器。

        3.直接驱动串转并硬件,无需软件模拟时序。

        4.没有条件分支语句,需要利用跳步语句和无条件分支语句配合,才能达到有条件分支指令的效果。

        5.涉及到RAM和ACC的数据交换时,涉及到丰富的指针修改操作。

        6.R寄存器不能手动置0或置1,其在开机时默认为0。但在少数需要切换长短数据格式时,又需要记住之前对R进行的所有操作,一旦遇到就会非常麻烦。

        为了更好地了解DG0040的指令集及其应用,下一章我们将在硬件上测试一下部分指令和一些基本程序。

四、分立逻辑复刻DG0040,并编写加减法等测试程序

        由于笔者在写这一段的时候,手里没有实际芯片,所以考虑复刻一个CPU顺带测试一下一些程序。实际芯片由于非常少见,不一定弄得到,所以暂时不考虑进行实际芯片的测试。

1. DG0040的行为兼容级复刻

        首先要说明一下这个“行为兼容级复刻”是什么意思。DG0040的硅片布线图和内部晶体管级设计图目前还没有公布(以后估计也不会了),而且DG0040的外部引脚时序图并不多。因此我们需要设计一个黑盒模型,参考原版DG0040的指令集和少的可怜的波形图资料,制作一台每个指令执行效果与原机相同或类似的机器。也就是说,对于每个指令,复刻版都执行了与原版相同的操作,但我们不在乎具体实现用了几个逻辑门,或者用了时钟的哪个边沿。当然复刻版还是要实现单周期的,毕竟这是这个黑盒模型的主要特性之一。这就是本小节所要达成的目标:行为兼容级复刻的意义。

        首先分析和整理了该机的指令集:

指令集按机器码顺序排列

        根据原版的硬件结构和时钟相位特点,初步绘制了内部几个关键信号与输入时钟关系的波形图:

主要信号波形图

        然后根据此波形图所确定的内部几个关键信号的时序(如指令寄存器IR、RAM输出缓冲器、RAM输入缓冲器的时序),手绘波形图,论证了几个不同种类,且较为复杂指令的可行性:(EXCI和EXCD是最复杂的两个指令,CAL和RET是时序最乱的指令)

几个指令对应的硬件信号波形图

        从时钟相位来看,DG0040的一个指令周期分别有两个上升沿和两个下降沿,但如果单独利用,就只能执行4个可以并行的操作。但有些时候还是不够的:取值和PC自加可以占掉一个边沿,RAM的保存需要占一个边沿,ACC输出到RAM的写入操作需要一个边沿,BU BS BL的修改以及更新......所以在复刻版上,围绕异步时序RAM的读写缓冲器问题,将缓冲器的写入时间、与地址缓冲器的更新时间延展到取指令时和下一条指令开始执行时,也就是进行了流水线化的设计,这样能够保证复刻版DG0040在一个周期内完成从取值到执行的全过程。

        复刻版电路图如下:它采用74芯片、CPLD和GAL芯片。其中一颗CPLD只负责硬件堆栈的存储,等效为13颗74LS194,;另一颗CPLD充当CU(解码、控制单元),一共使用了277个乘积项资源、80%以上的引脚资源、另有8个D触发器用于缓存上一条指令,用于多指令连用的判断条件。

        CPU主体部分:其实核心就是围绕ACC、L构建,另外就是程序计数器、SPS、SPU暂存器。由于堆栈放到了CPLD里面,节省了很大一块PCB面积。而且CPLD的单位逻辑功耗要比那些原版74LS加起来更低。

CPU体部分和ROM

        RAM及RAM指针部分:这部分没有使用任何可编程器件,完全使用逻辑门芯片搭建。另外给RAM的读写信号加入了模拟延迟期,用于改善异步RAM写操作的保持时间限制。

RAM部分,采用分立逻辑

        DG0041部分(包含DG0040的G端口和Z寄存器)。时钟采用四种输入方式,分别是:有源晶振、NE555高频振荡、NE555低频震荡、手动单步。原版DG0040主频最高150KHz,复刻版采用纯静态逻辑,理论最高主频超过1MHz,但后文实际测试中,采用的是32.768KHz晶振,指令频率为8192Hz。

DG0041和G端口、Z标志位部分,两颗GAL分别负责G端口相关和0x57双字节相关指令解码

        电源部分(包括两路高低压辅助电源)

电源:+5V、+1.2V荧光管灯丝电源、+20V荧光管阳极和栅极电源

        为了简化外部连接,将DG0040和DG0041集成在一张板子上,集成时钟发生器和复位电路。仅对外保留必要的接口(主要是K输入和L、L段译码、D移位寄存器输出口、G双向口)。接口采用双列2*20简牛插座引出,引脚排布如下:

        PCB布局缩影:(尺寸为15cm * 25cm)

嘉立创EDA专业版 绘板

        另外编写了CPLD中的移位寄存器和控制器CU代码,对CPLD进行烧录

XILINX ISE 14.7

        前后历时四个月,最终制作成复刻版如下图:经过基本指令集的验证和时序验证,该复刻版DG0040已经基本满足测试一些简单程序的需要了,因此可以开始正式的程序编写。如果在编程过程中发现控制单元哪里写错了,还可以很方便地对硬件进行修改(主要修改PLD里的代码)。

初步试机

        再放一张镀金芯片满配图 :(〜 ̄▽ ̄)〜

土豪金版本

       由于DG0040的寻址方式,手搓机器码过于麻烦。因此还写了一个较为简单的的伪汇编编译器,编译时还能将DG0040的虚拟地址(按二进制大小顺序)转换为真实地址(按DG0040寻址顺序)。手写排列表,并更正了原版材料里的两个笔误。

手推真实地址跳转表

        DG0040的编译器借鉴了DJS-08的编译器,现在能够按照输入的程序,正常输出机器码了。编译输出界面:

 2. 数码管扫描输出程序

        测试数码管扫描输出程序,大概思路是先向RAM里装载立即数,然后依次将RAM中的数据送往ACC,经过L输出口,再经译码,显示在LED上。此外还需D移位寄存器的扫描配合。

        首先,向RAM指定的区填入数据。利用LB和LBS语句,将RAM指针移动到某个区的起始位置。然后利用LAM指令连用的效果,第一次LAM将立即数装载到ACC,从第二次以后,LAM将立即数装载到RAM,并将RAM指针BL+1。因此只需要连续的13个LAM指令,就能将RAM一个区的0-11号单元填充立即数。程序如下:

代码块
clike
自动换行
复制代码
 	 LB(0,0);
     LBS(0);

     LAM(3); //装填到ACC,没什么用
     LAM(3);
     LAM(2);
     LAM(0);
     LAM(2);
     LAM(0);
     LAM(4);
     LAM(0);
     LAM(0);
     LAM(3);
     LAM(8);
     LAM(9);
     LAM(1);
复制成功

        数码管采用了12位共阴极7段数码管,将所有数码管的同一段(阳极)连接到一起。进行扫描时,给段加上有效电平,然后将需要点亮的数码管位的共阴极拉低,该位数码管就会亮起。采用扫描,可利用视觉暂留效果,让所有位显示不同的数字。流程图如下:

数码管扫描程序流程图

        相应的,其程序如下:

代码块
clike
自动换行
复制代码
     SSP(1);
     SSP(0);
     JMP(0);	//定位到 0页,1区,0单元

     org(1,0,0);	//起始地址 0页,1区,0单元
     LB(0,2);

    RNP();
     SHD1();
     LDA(0);
     SNP();
     NOP();
     NOP();
     INCB();

    RNP();  //(地址 +0x8)
     SHD0();
     LDA(0);
     SNP();
     INCB();
     JMP(8);	//BL没有溢出 继续BL自加,扫描

    RNP();
     SSP(1);
     SSP(0);
     JMP(0);	//NL溢出,BL清空,程序重新开始
复制成功

        运行结果如下:

数码管输出测试结果

        测试完数码管输出程序,就可以进行加减法等运算的程序了。

3. 12位*BCD数无符号加法程序

        参考资料《四位微型计算机的功能及其应用》给出了BCD加减法的示例程序。我们主要参考该资料进行测试。程序流程如下:实现的效果是RAM的0页0区内低12位BCD数,加上RAM的0页1区内低12位数,并将结果保存到0页1区。

12位BCD加法流程图和占用的RAM空间

        其基本流程是:先选择一个RAM的区,然后将RAM当前单元的数字移动到ACC,同时修改BU和BS修改到被加数的区,然后进行加法和十进制调整,同时将结果覆盖掉被加数。然后BL指针+1,BS、BU修改回加数所在页区,再开始下一次加法......直到BL加到12,超过数据存储范围,程序结束。编写的程序如下:

代码块
clike
自动换行
复制代码
     org(4,0,0);	//程序起始位置

     LBS(0);		//选择页
     LB(0,1);		//选择区,BL初始化
     RC();			//C清零

     LDA(3);		//(地址 +0x3)
     ADC();
     DAA();			//十进制调整
     EXCI(3);
     JMP(3);		//若BL没有溢出,跳转到LDA指令,形成循环

     RET();			//程序出口
复制成功

        可以看出,主循环体即使算上JMP也只有5个指令。实际计算一次十进制BCD加法只需要5个指令周期,在四位机里效率已经很高了。由于尚未编写按键扫描程序,为了配合加法,我们在RAM的0页1区和0页2区放置两个加数,并指定结果存放位置为2区。

代码块
clike
自动换行
复制代码
org(3,0,0);
        LB(0,1);
    LAM(0);

    LAM(0);
    LAM(4);
    LAM(0);
    LAM(0);

    LAM(0);
    LAM(0);
    LAM(0);
    LAM(0);

    LAM(0);
    LAM(0);
    LAM(0);
    LAM(0);

        LB(0,2);
    LAM(0);
    LAM(3);
    LAM(2);
    LAM(0);
    LAM(2);

    LAM(0);
    LAM(0);
    LAM(0);
    LAM(0);

    LAM(0);
    LAM(0);
    LAM(0);
    LAM(0);
复制成功

        因此,这里计算的是1983+0040,结果应该为2023,如下图所示:

        这里发现高位的无效的0没有消隐。因此考虑利用L段译码器的F消隐特性,利用程序将高位无效的0全置F,以达到消隐的效果。消0子程序如下:

代码块
clike
自动换行
复制代码
org(5,0,0);
     LBS(0);
     LAM(0);
     ATL();
     LB(1,2);

     DECB();	//(地址 +0x4)
     LAM(0);
     TAM();
     RET();
     LAM(15);
     EXC(0);
     JMP(4);

     RET();
复制成功

        运行结果如下:

完美

4. 12位*BCD数无符号减法程序

        减法程序的准备工作与加法相同。这里只给出差异部分,首先是减法的流程图:

减法流程图

        相应的,减法子程序编写如下:值得注意的是,这里在开算之前,需要先将C置一,因为在减法过程中,C=1代表没有借位。

代码块
JavaScript
自动换行
复制代码
     org(4,0,10);

     LBS(0);
     LB(0,1);
     SC();		//C 置一

     LDA(3);	//(地址 +0d13)
     CADCSC();	//减法
     ADX(10);	//十进制调整
     EXCI(3);

     JMP(13);

     RET();
复制成功

        但这里,开始算减法之前还有一个问题,那就是如果一个小的无符号数减去一个大的无符号数,会发生溢出。同样,两个大于1/2满量程的无符号数相加也必然产生溢出。但如果减出来是负数,这个是正常的情况,所以在没做符号处理程序时,负数是以补码显示的:(计算无符号数123减123456,结果应该是-123333)

减法溢出

        所以考虑利用计算完成后的C(C=0则表示为负数的补码)来对数据进行调整,并利用段译码器的负号功能,为这个数据的最高位加上符号。调整程序如下:

代码块
clike
自动换行
复制代码
	  org(4,0,10);	//减法子程序

     LBS(0);
     LB(0,1);
     SC();

     LDA(3);
     CADCSC();
     ADX(10);
     EXCI(3);

     JMP(13);

     TC();
     JMP(42);		//如果不是负数,则直接消零

     //----------------------
     org(4,0,20);	//取补码子程序

     LBS(0);
     LB(0,2);
     SC();

     LDA(1);		//取补码
     CADCSC();
     ADX(10);
     EXCI(1);

     JMP(23);

     LBS(0);
     LB(4,2);

     LDA(1);		//将补码换回到原先减法结果位置
     EXC(1);
     EXC(0);
     LDA(0);
     DECB();
     JMP(30);

     SSP(5);		//消0
     CALL(0);

     INCB();		//加负号程序
     LAM(12);
     EXC(0);

     RET();

     SSP(5);  		//地址42,如果减法结果是正数,跳到这里
     CALL(0);		//消0

     RET();
复制成功

        运行结果,完美。

带符号运行结果

       截止目前,还没有编写键盘扫描程序。今后会考虑加入键盘输入功能,并编写浮点数乘除法,做一个基本功能的四则计算器。

五、简单谈谈DG0040与INTEL-4004的区别与联系

       有些观点认为,DG0040就是“国产版”的4004.但无论是从硬件架构,还是指令结构,实际上都有着较大的差异。

        上文已经较为系统地阐述了DG0040的软硬件特性,下面首先描述一下intel-4004的硬件结构与指令特点:

1. INTEL-4004的硬件结构

INTEL 4004 硬件结构图

        英特尔4004采用P沟道MOS工艺,供电电压-15V,逻辑1为低电平,逻辑0为高电平。DIP-16封装,采用一个高度复用的4bit总线与外部设备交换地址、指令和数据。4位总线连接4001/4002。

        时钟为双相位,时钟频率范围约500-750KHz。

        程序存储器直接寻址范围:4kB*8bit。指针栈为3级12bit。

        寄存器资上,以ACC为中心,有一个C标志位寄存器,可以存放乘法的进位和减法的借位。拥有16个4bit通用直接寻址寄存器,可按照8*8bit组织,一次传递8bit。ALU一次进行4bit运算,支持加减法。

        外部引脚上,有一个test端,参与内部条件跳转的条件构成。有一个存储器片选信号、一个RAM直接片选信号、三个RAM编码片选信号(需要外接解码器)。

4004时序图

       4004的时序没有DG0040那么简洁。它的一个机械周期由8个时钟周期构成。在前三个抽泣,由4004分别分三次吐出12位地址数据,由4001、4002进行锁存。4004在A3周期会给出存储器读信号,这时由4001将12位地址所指向的指令,分两次在M1 M2周期内送到总线,由4004读取。当涉及到IO操作信号时,4004会给出IO/RAM操作高字节的提示,在4004取IO/RAM操作低半字节指令时,该指令会被总线上所有片选有效的4001/4002接收,并独立于4004进行解码,然后在周期的X2 X3配合4004在总线上进行数据的传递的工作。

        如果不涉及IO操作,4004在X1-X3周期,在芯片内部完成指令规定的所有操作。

        MCS-4专用芯片组:

        4001(专用ROM+IO端口)4001上有一个256*8bit的程序存储器,和一个4位双向可编程IO端口。

        4002-1、4002-2(专用RAM+O端口)当4004需要连接多片4002时,通过三种方式来指定某一个特定的4002:1.使能信号,2.4002上的地址选择引脚,3.4002的内部ID位(-1和-2的ID不一样),因此一片4004+一片3205专用使能端译码器,再加上4002的-1和-2,再加上4002的外部地址选择引脚,可以一共指定32片4002,也就是2560*4bit的RAM存储空间。

        4003(移位寄存器)4003不需要与4004的时钟同步,实际运行时更像一个74HC595.而4001和4002的时钟必须与4004接在一起

        4004(CPU)

        4008(地址锁存器)

        4009(8080时序ROM接口指令输入与RAM存储器接口复选器)

        注:诸如 4201、4265、4289 等芯片,虽然也常出现在复古4004硬件DIY制作中,但这些芯片都是MCS-40/4040的官配。在4004刚问世的时候,没有这些芯片,就连时钟发生器都是逻辑门+晶体管手搓的。

2. INTEL-4004的指令集

        4004的指令集主要分为三个部分:基础指令集部分(0x00-0xDF)、I/O与RAM操作指令(0xE0-0xEF)、累加器操作指令(0xF0-0xFD)

        基础指令集部分:

               NOP:空跑

               JCN:有条件页内跳转(条件包括C、ACC是否为0,TEST)

               FIM:选定一个寄存器对,向寄存器对装载8位立即数

               SRC:选定一个寄存器对,将寄存器对内的数据作为地址,发送给总线上的地址锁存器

               FIN:选定一个寄存器对,将寄存器对的数据作为页内寻址的地址,将ROM对应地址的数据(8bit)装填到原来的寄存器对。原数据抛弃。

               JIN:选定一个寄存器对,将寄存器对内的数据作为地址,进行页内跳转

               JUN:无条件跳转,立即数12bit

               JMS:无条件跳转,立即数12bit,并保存原进程,压入堆栈,栈向下压1

               INC:选定一个通用寄存器,寄存器堆中的数据+1

               ISZ:选定一个通用寄存器,寄存器堆中的数据-1。若结果不等于0,则跳转到同一页、由8位立即数指定的地址;否则不跳转。

               ADD:选定一个通用寄存器,与ACC相加,结果存入ACC,C作为进位输入,也作为进位输出

               SUB:选定一个通用寄存器,与ACC相减(ACC-r),结果存入ACC,C作为借位输入,也作为借位输出

               LD:选定一个通用寄存器,将其数据移入ACC

               XCH:选定一个通用寄存器,将其数据与ACC交换

               BBL:子程序返回,抛弃PC内现在的进程,从栈顶取出原进程继续执行。栈向上弹1;并将一个立即数转载到ACC

               LDM:将立即数4bit移入ACC

        I/O与RAM相关指令:

               WRM:将ACC的数据写入选中的4002芯片的主寄存器

               WMP:将ACC写入选中的4002的输出端口

               WRR:将ACC写入选中的4001的输出端口(或通过4009写入异步存储器)

               WPM:空(后推出4008/4009时,定义为写8bit存储器的高/低半字节)

               WR0:将ACC写入到选中的4002的0号直接寻址寄存器

               WR1:将ACC写入到选中的4002的1号直接寻址寄存器

               WR2:将ACC写入到选中的4002的2号直接寻址寄存器

               WR3:将ACC写入到选中的4002的3号直接寻址寄存器

               SBM:用ACC减掉选中的主寄存器内数据,结果存到ACC(带进位)

               RDM:将选中的4002芯片的主寄存器内数据送到ACC

               RDR:读取选中的4001的输入端口数据到ACC(或通过4009读取异步存储器)

               ADM:用ACC加上选中的主寄存器内数据,结果存到ACC(带借位)

               RD0:读取选中的4002的0号直接寻址寄存器读取到ACC

               RD1:读取选中的4002的1号直接寻址寄存器读取到ACC

               RD2:读取选中的4002的2号直接寻址寄存器读取到ACC

               RD3:读取选中的4002的3号直接寻址寄存器读取到ACC

        累加器相关指令:

               CLB:清空ACC,C置0

               CLC:C置0

               ICA:ACC+1

               CMC:C翻转

               CMA:ACC按位取反

               RAL:ACC带进位左移1位

               RAR:ACC带进位右移1位

               TCC:将C传送到ACC并清空C

               DAC:ACC-1

               TCS:如果C=0,则ACC装载9;如果C=1,则ACC装载10(0xA),C清零

               STC:C置1

               DAA:对ACC内的数据进行十进制调整(C标志位也参与)

               KBP:将ACC内数据调整为独热码,用于键盘扫描

               DCL:根据ACC中的数据,选择RAM片选信号

        不得不说,4004的指令集比DG0040简单很多,很少有某个指令内存在很多的操作,也基本不存在连用指令。而且4004指令的通用性很强,多个指令的组合非常灵活,这也是为什么4004和8004的架构与指令集,直接或间接影响了x86的硬件结构和指令集。

3. DG0040与4004的联系

        1. 都有加减法和十进制调整指令。作为以计算功能为主的处理器,4位数据总线可以很方便地表示十进制数。但加法器一般都是二进制加法器(十进制加法器可能会消耗更多资源,且输入数据也需要避免大于9),得出来的十六进制数据需要进行十进制调整。十进制调整可以用比较、条件跳转等指令模拟实现,但现成的指令显然更方便。

        2. 都采用了哈弗架构,即指令存储器和数据存储器分离。无法从数据存储器中取指、执行。但是,4004可以将程序存储器空间的高256字节作为数据存储器空间,使用WPM指令对其进行半字节写入,使用FIN读出;但这样,该RAM的读映射和写映射就不一样了,会给硬件和编程带来一定麻烦。不过至少从理论上,4004可以用这种方式,从RAM存储器里执行指令,但实际意义不大。

        3. 芯片组内,都设计了专用的串转并移位寄存器,用来扩展输出端口。

        4. 采用双相位时钟

4. DG0040与4004的区别

        1. 由于甲方的要求,受到引脚数量的限制,4004采用一个高度复用的4bit总线,需要在一个指令周期内吞吐包括高、中、低位地址、高、低位指令、需要传递的数据,4040还在X1周期吐出ACC的值,用于debug调试。因此,4004/4040需要连接专用的ROM芯片或存储器接口芯片以对接其特殊的时序。而DG0040由于引脚数量的限制较小,地址总线、指令总线和数据通路是完全分开的,没有复用,时序更简单,可以直接连接8080/6800时序的程序存储器,DG0041更是可以直接用少量的逻辑芯片直接代替。

        2. 4004有16个可直接寻址的寄存器堆,且可以组成8个寄存器对。DG0040只有片内RAM,且只有部分单元支持直接寻址。

        3. 4004的RAM支持寄存器寻址。DG0040由于没有通用寄存器,RAM不支持寄存器寻址。

        4. 4004的程序存储器支持间接寻址,DG0040使用ATL + LTSPU可以实现有限限度的间接寻址,但只能用来指定一个区块的起始地址。

        5. 4004一个指令周期需要8个或更多时钟周期,DG0040只需一个时钟周期。4004双字节指令需要16或更多时钟周期,DG0040的双字节指令只需要两个时钟周期。

        6. 4004有条件分支指令,DG0040没有。但是DG0040可以使用条件跳步指令与无条件分支指令连用,达到条件分支的效果。

        7. DG0040的运算效率高于4004。以8位BCD数加法为例,4004需要占用全部的通用寄存器来达到最快850us的处理时间(见参考文献 4 正文第3页);而DG0040只需要400us就可以计算两个12位BCD数的加法(按照上文的实测程序计算),而这时的DG0040时钟频率比4004低了接近5倍(750KHz对150KHz)。

        8. 4004最小系统中(以一个4004 + 一个4001计算),不是所有的指令都在4004内部解码,在IO指令部分需要4001参与解码;而在连接了4002的系统中,4002也要参与RAM操作和IO指令的解码,才能完成正常的数据传递。而在DG0040最小系统中(一个DG0040,一个DG0041,一个EPROM),只在涉及移位寄存器的指令中,将指令存储器的某2位作为直接打入移位寄存器的数据,DG0041不参与指令的解码过程。因此,4004更像是一个较为庞大的微机系统的一个功能块,自身并不能完成所有的指令解码和操作;而DG0040更像是一个去掉了内部ROM的单片机。

六、一些小细节

    1. DG0040的第41个引脚

        在前文介绍DG0040引脚的章节,我们看到DG0040其实有一个引脚可能有两种内部连接:

2号引脚

        2号引脚的名称是PS2/F,PS2是程序计数器的最高位,F是G双向端口的方向同步输出信号,显然这两个信号没有任何相关性,不可能连在一起。参考材料中是这么说的:

原资料中对2号引脚的解释

        因此我们可以推断,在DG0040的晶圆上,存在至少41个对外连接点,其中包括PS2和F,但在进行晶片与管壳压线操作时,再另行决定是将PS2引出还是F引出。在资料中,我们尚未发现有用户对该引脚进行定制的记录。而大多数情况下,F寄存器都不需要向外引出,因为如果使用G端口对外部进行读写,完全可以利用D输出口拓展方式将同步信号手动输出片外;即使涉及到DG0046,DG0040和DG0046的接口行为也是完全由用户程序定义的,只要编程得当,不存在冲突。因而笔者猜测,为了获得更大的ROM空间以进行更复杂的键盘处理程序以及科学运算程序,2号引脚一般压焊的是PS2,保证8KB的直接寻址范围。但这一观点目前无法证实。

    2. DG0040的双字节指令跳步问题

        DG0040的指令集中,有5条是0x57开头的双字节指令,而DG0046的指令中,全部是双字节指令。同时,DG0040与DG0046的指令解码互不干涉。

        但DG0040指令中的跳步指令,从描述上看,是跳过下一条指令,也就是将下一字节强制复位成0x00h,执行一次NOP。而绝大多数DG0040的指令都是单字节的,所以跳步一般只跳一步,但0x57开头的指令都是双字节,跳步能否一次跳两个字节呢?原版资料中没有这个答案。

        直到在朋友的帮助下,进行了原版CPU的测试,证实了:DG0040的跳步只跳过一个字节,而0x57开头的指令,其剩余的一个字节并不会被屏蔽,而是当做了普通的单字节指令错误执行,从而导致程序运行混乱。

代码块
clike
自动换行
复制代码
//原版不正常的跳步
RC; // C reset
TC;	// if (C == 0) skip;
ENGO;  // ENGO 机器码是0x57 0x02 ,跳步只跳过了0x57,但0x02 仍正常执行
END;

//等效于执行下面这段
RC; // C reset
TC;	// if (C == 0) skip
NOP; // 0x57
SC;  // 0x02
END;
复制成功

        针对此,笔者制作的分立复刻版DG0040采用单双字节混合跳步模式,即遇到单字节指令只跳一步,遇到双字节指令就进行两连跳。不过在实际的编程过程中,考虑到原版DG0040很少有配备DG0046的情况,且设计程序时,可以刻意避免双字节指令出现在带跳判指令的后方,因此原版DG0040没有这个功能也是可以理解的,至少不会对实用性产生显著影响。

    3. DG0044和DG0045是怎么来的

       在我们查阅资料都还在时候,发现DG0040系列不止DG0040一个CPU,还有DG00401这样的带中断和定时器版本。另外还有两个很特殊的型号:DG0044和DG0045,他们和DG0040最大的区别是:DG0044的RAM空间为128*4bit,DG0045的RAM空间为64*4bit。表面上看,实际上是减RAM版的DG0040。(有点像手机的内存有2GB、4GB、8GB之分)

        但是,手册上并未给DG0044和DG0045的地址范围等信息。从理论讲,有可能是分别删掉BS的一根线变成DG0044,或者删掉整个BS,变成DG0045。这样还能减少RAM的规模,间接提高良品率。

        另一种猜测来自于资料上对于DG0040良品率的记载:“使用2英寸晶圆,良率约20%......”。由于DG0040的SRAM占用了超过50%的晶体管数量,最容易出现故障从而导致整个芯片报废。那么有没有可能,如果RAM只报废了一小部分,就写上DG0044,或者DG0045,当做简配版卖,有点类似于多核CPU中有某一个坏了,就当成减核版出售。但这也带来了另一个问题:RAM的坏区不一定在哪,有可能是所有区的某一位全坏了,或者只有一整个页/区损坏,但可能出现在任何位置;但若是上面说的前一种损坏状况,所有的扇区就都不是完整的。如果进行统一出售,就不能编写统一的用户手册,因为不同的DG0044/DG0045的损坏情况不可能完全一致。由于没有实物,这个问题也就无从而知了。(参考英特尔以前还生产8051的时候,就把ROM作废的8051当成8031来卖)

        直到这次,我们收到了一颗比较特殊的DG0040,在后面写了一个“44”字样,后面还紧跟着一个“6#”,可能代表6号扇区或者6号单元:

一颗写着44字样的DG0040

        那么这为什么可以判定这个44字样就代表DG0044呢?因为之前见过另外一位大佬展示了他所收藏的DG0040计算器,该计算器内部使用了一颗DG0046,但该芯片上没有丝印,而是只用铅笔写了一个46字样。

DG0046无字版

        同时期,国产芯片除了喜欢在背面印生产日期之外,最喜欢印的就是型号,比如5G7556:

5G7556背面的7556字样

        同时,考虑到44字样后面跟了一个可能是扇区或者单元的标识,标志着应该就是这里(6号单元,或者BS BU合起来指定的一个6号扇区)坏掉了。不过目前笔者还没有能够测试该芯片内部RAM的设备,这个情况只能说可能性很大,但尚未验证。(这又挖了一个大坑啊)

    4. 漏网之鱼 or 沙漠绿洲?

        在上文硬件结构介绍章节,我们介绍了DG0040的低位程序计数器PL,它的寻址范围是0-0x3E,因此在JMP和CALL的时候,是不能向扇区的0x3F地址跳转的,即使跳过去也会死循环。这导致原机的指令空间中,0xBF和0xFF是无效的。

        但这两个空的指令还有另一个用途,首先,该机没有停机(HLT)指令,虽然该指令在计算器上没什么作用,但是对于调试操作有很大帮助。因此,本人在设计复刻版时,允许在JMP操作时,跳到该扇区的0x3F处,以至程序不再向下运行,达成死循环、停机的效果。

        另外,在原机指令集中,R状态寄存器只有一个取反指令。当程序较长且涉及到复杂分支时,很难记住程序运行到某处时的R寄存器值。因此,我们可以利用0xFF指令,作为R寄存器置0或置1的操作。而原来的R寄存器取反指令不变。这样的好处是,当我们需要改变R值,有记不住R当前状态时,我们可以先用0xFF指令将R寄存器置0或1,假如我们需要的是1或0,则执行一次R取反指令就可以了。这对于程序设计有很大帮助。

代码块
clike
自动换行
复制代码
//demo set R flag to 1
SR; //set R
END;

//demo set R flag to 0
SR	//set R
CMPR; // R comp and R to 0
END;
复制成功

        基于此,笔者设计的DG0040复刻版,将原先指令集的空闲处加以利用:将0xBF分配给HLT指令,将0xFF分配给setR指令。下图是笔者改进之后的DG0040指令表,在0xBF和0xFF处新增了实用指令(其实大多数情况用不到),并保证其他部分指令兼容原版DG0040。

复刻版DG0040的指令表,增加了0xBF和0xFF处的有效指令。

七、结语

        在中国计算机和半导体科技发展历史上,DG0040系列无疑是浓墨重彩的一笔。它称得上是第一款真正走入民用市场的国产商用化单片CPU。一方面,它很好地证明了70-80年代国营单位设计、制造、生产大规模计算机集成电路的能力,为后来的技术发展奠定了一定的经验基础,服务于部分领域的生产实践,一定程度上完成了它作为国产CPU的历史任务。 另一方面,DG0040的架构较为固定,改进和发展空间较小,对于现代微型处理器设计的参考意义几乎为0;且因产能、工艺问题,以及问世时间过晚,并未在商业上取得成功,不可避免地被性能更优秀、成本更低的CPU所取代。

        本文通过查阅相关资料,阐述了DG0040的起源及其出现时的历史背景,介绍了DG0040微处理器和DG0041辅助芯片的硬件、软件概况;简要对比了著名的英特尔4004与DG0040的区别和联系;使用CPLD和74LS芯片复刻并改进了DG0040 CPU,并初步在复刻版CPU上编写了少量测试程序;最后,针对与DG0040有关的次要细节,给出了合理的推测,并在一定程度上予以证明。

复刻版DG0040与原版芯片组合影

参考文献:

1. 中国集成电路大全-微型计算机集成电路(国防工业出版社)

2. 半导体集成电路产品性能汇编(第四机械工业出版社)

3. 四位微型计算机的功能及其应用(温州电子技术研究所-缪晓胜)

4. INTEL MCS-4 Usermanual

5. 《无线电》1980年5月刊、1984年5月刊

6. 国产DG0040系列四位微型计算机电路简介(北京878厂 仲进才)

7. SHARP SEMICONDUCTOR DATA BOOK 1986(Sharp)

8. Microprocessors-in-Japan-Status-in-1978(筑波大学 Ryoichi Mori 等)

9. 微博文章:DG 0040 CPU ——中国最早的4位CPU DG 0040 https://weibo.com/ttarticle/p/show?id=2309404875696668541099

10. 微博文章:国产70年代,TQ-12G计算器电路分析 https://weibo.com/ttarticle/p/show?id=2309404798096956850345

-- 赫尔戈兰-图林根( BH2VGM )

-- 最后编辑于2023年9月30日

cut-off