我的小接力:三人性交,或什么是疯狂


图1:作者背景中的BrainfuckPC中继计算机

延续我最疯狂的计算机项目的年度摘要的光荣传统,我向您介绍BrainfuckPC中继计算机项目的第三篇也是最后一篇文章。

在过去的系列中:


经过十多年的梦想和思考,经过两年多的不懈工作和组装,我可以自信地说继电器计算机项目已经开展。 尽管从实用的角度来看,计算机是无用的,而且经常崩溃,但它已成为下一个疯狂的网络项目的起点。

切割机下方是振铃的继电器块,世界上最快的继电器计算,包装,真空指示器等。

Brainfuck编程语言


疯子编程语言可能是世界上最流行的深奥编程语言。 同时,真正的图灵是一个完全的泥潭 。 只有8条指令可以写任何东西,但是时间很长。
例如,我花了三天时间编写和调试355/113除法程序,该程序在终端上打印出6位小数。

图2:动脑语言说明

该语言的整个语法围绕RAM构建,可容纳3万个存储单元,容量为8位。

  • 使用两个+-指令我们将当前数据单元格中的值向上或向下更改一个。
  • 通过两条指令<>,我们将指向当前数据单元的指针改变1,从而在存储器中向左或向右移动。
  • 另外两个说明[] -允许我们组织循环。 括号内的所有内容都是循环的主体。 允许嵌套循环。 指令的逻辑很简单-如果当前数据单元的值不等于零-我们将执行循环的一次迭代,如果相等,则退出循环。
  • 最后两条指令 和-可让您在控制台中显示当前单元格的值,或输入其RAM。 由此实现交互性。

是的,这足以编写任何程序。 Brainfuck中C编译器的存在似乎暗示了这一点。 但是代码的密度是不存在的。 为了执行简单的操作,例如将两个存储单元的值相加,您需要执行数百条“脑力激荡”指令。

脑干++


让我们尝试至少增加一点代码密度。 在学习用这种语言编写的程序时,您应该注意,它们大部分由相同指令+-<>的序列组成。 这导致许多人想到将这样的指令序列折叠为一个,并导致生产率的小幅提高,这从一个程序到另一个程序可能高达百分之几十,并且使速度成倍增长。 例如,我们用+10运算代替了10个增量运算。 将指针向右移动到20的操作> 20等。


图3:brainfuck ++语言说明

从理论到实践


如您所知,一个人不能只用脑力激荡的语言来写Rb = Ra + Rb,其中Ra和Rb是存储单元。 我们所能做的就是将一个单元格的内容更改为一个常数,并检查它是否为零。 结果,要将两个数字相加,剩下的就是对Rb单元执行+1,对Ra单元执行-1,直到Ra单元的内容变为零。 我们用C语言的代码形式编写:

/*(mem+RbPos) += *mem */ void addMov(Memory &mem, uint16_t RbPos) { while (*mem) { mem += RbPos; (*mem)++; mem -= RbPos; (*mem)--; } } 

结果,旧值将显示在RbPos单元格中,并加上源地址中的值。 class Memory-具有65k整数单元的容器。 它的主要属性是,溢出指针值会将其返回到数组的开头。 就像在我真正的硬件中一样。

所描述功能的缺点是丢失了原始值,它将被重置为零。 添加另一个Rc变量以保存它:

 /* *(mem+RcPos) = *mem *(mem+RbPos) += *mem */ void addCpy(Memory &mem, uint16_t RbPos, uint16_t RcPos) { while (*mem) { mem += RbPos; (*mem)++; mem -= RbPos; (*mem)--; mem += RcPos; (*mem)++; mem -= RcPos; } } 

结果,复制的术语将位于RcPos单元格中。 好吧,如果以前是零。

由于我使用的表示法非常类似于Brainfuck ++-我们仅以bfpp字符重写函数,以RbPos为4和RcPos为5为例:

[
>>>>
+
<<<<
-
>>>>>
+
<<<<<
]

在描述了所有原语之后,您可以开始将它们组合为更复杂的结构,并获得具有必要功能的程序。 如此一来,您可以获得一个将355除以113(或16位之内的任何其他数字)的程序

浮点程序
 class Memory { public: Memory() { memset(m_mem, 0, sizeof(m_mem)); memPtr = 0; } Memory& operator += (uint16_t ptr) { memPtr += ptr; return *this; } Memory& operator -= (uint16_t ptr) { memPtr -= ptr; return *this; } uint16_t& operator [] (uint16_t ptr) { return this->m_mem[ptr]; } Memory& operator = (uint16_t ptr) { memPtr = ptr; return *this; } uint16_t & operator * () { return m_mem[memPtr]; } Memory& operator ++ () { memPtr++; return *this; } Memory& operator -- () { memPtr--; return *this; } private: uint16_t memPtr; uint16_t m_mem[65536]; }; void calcPi() { Memory mem; *mem = 22; mem += 1; *mem = 7; while (*mem) { mem += 1; (*mem)++; mem -= 1; (*mem)--; mem += 2; (*mem)++; mem -= 2; }//correct -909 while (*mem) { (*mem)--; } mem -= 1; while (*mem) { mem += 2; while (*mem) { mem += 4; (*mem)++; mem -= 4; (*mem)--; } mem += 4; while (*mem) { (*mem)--; mem -= 4; (*mem)++; mem -= 2; (*mem)--; if (!*mem) { //JZ out goto out; } mem += 6; } mem -= 6; out: mem += 6; if (*mem) { while (*mem) { mem -= 6; (*mem)++; mem += 6; (*mem)--; } mem -= 6; if (*mem) goto out1; } mem -= 5; (*mem)++; mem -= 1; } out1: mem += 10; *mem += 0x50;//P putc(*mem, stderr); *mem += 0x19;//i putc(*mem, stderr); *mem -= 0x2C;//= putc(*mem, stderr); mem -= 9; *mem += 0x30; putc(*mem, stderr); mem += 9; *mem -= 0xf; putc(*mem, stderr); mem -= 10; *mem += 6; while (*mem) { (*mem)--; mem += 1;//mem == 1 //nullCell(mem); while (*mem) { (*mem)--; } mem += 1; //mulConst(mem, 1, 10); while (*mem) { (*mem)--; mem -= 1; (*mem) += 10; mem += 1; } mem += 1; //addCpy(mem, 1, 2); while (*mem) { mem += 1; (*mem)++; mem -= 1; (*mem)--; mem += 2; (*mem)++; mem -= 2; } mem += 3; //nullCell(mem); while (*mem) { (*mem)--; } mem -= 5; while (*mem) { mem += 4; while (*mem) { mem += 6; (*mem)++; mem -= 6; (*mem)--; } mem += 6; while (*mem) { (*mem)--; mem -= 6; (*mem)++; mem -= 4; (*mem)--; if (!*mem)//,  ,      { goto out2; } mem += 10; } mem -= 10; out2: mem += 10; if (*mem) { //addMov(mem, -(6+ 4)); while (*mem) { mem -= 10; (*mem)++; mem += 10; (*mem)--; } mem -= 10; if (*mem) goto out3; } mem -= 5; (*mem)++; mem -= 5; } out3: mem += 4; while (*mem) { mem -= 3; (*mem)++; mem += 3; (*mem)--; } mem += 1;//mem == 6 *mem += 0x30; putc(*mem, stderr); mem += 1;//mem == 7 while (*mem) { (*mem)--; } mem -= 3; while (*mem) { mem -= 1; (*mem)++; mem += 1; (*mem)--; } mem -= 4;// mem == 0 } *mem += 0x0a; putc(*mem, stderr); *mem += 3; putc(*mem, stderr); } 


中继计算机架构


中继处理器的中心元件是带有并行进位的16位全加法器。 在输入处将两个寄存器连接到该寄存器。 TMP是放置旧值的临时寄存器,而CMD是命令寄存器,其中存储了将更改旧值的指令和常数。

因此,我可以执行优化的Brainfuck ++操作,并同时获得完整的条件跳转-如果为零则跳转,如果不为零则跳转至程序的任何一侧。

求和运算的结果可以与当前数据单元的编号一起上载到上下文寄存器之一AP(或AP)中,或者与当前指令的编号一起上载至IP寄存器中。 此外,如果涉及指令+- ,则可以将结果上传到当前RAM单元。


图4:运行中的中继计算机体系结构。 加载新指令的阶段被其执行阶段所取代。

首先,我们需要计算下一条指令的数量-即 执行IP ++操作。 为此,将一个值添加到IP寄存器的旧值,将结果写回到IP寄存器,并将下一条指令加载到CMD寄存器中的该新地址。

第二步是执行新加载的指令。 如果它与加法器一起工作,则其执行过程类似于加载新指令的过程-旧值位于临时寄存器中,我们将位于CMD寄存器低位的常量相加,然后将结果写回到寄存器或当前数据单元中。
因此,指令在时钟发生器的一滴答声中执行。 在下降的前端,我们加载下一条指令,而在递增的指令上执行。

不是错误,而是功能
此处显示了一项功能。 打开计算机电源后,时钟发生器的第一前端将增加,因此-我们将必须执行当前指令,而该指令尚未加载到CMD寄存器中-零。

遵循空指令,然后...做IP ++!

结果,程序的零存储单元包含零,将永远不会执行。 从存储器加载的第一条指令将是0x0001处的指令。

指令集



图5:中继计算机指令集

指令为16位,其中4个高位位负责指令的类型,低12位位是有效载荷。 在大多数情况下,这是一个常数。

  • NOP指令-忽略。
  • CTRLIO指令是一种特殊的指令,其行为由有效负载位掩码编码。 首先,它实现命令以写入控制台和从控制台读取的命令(以同步或异步模式)。 其次,它允许您设置机器的16位或8位操作模式。 第三,使用CTRLIO.HALT指令,可以停止计算机。 有趣的是,面具位
    非阻塞。 您可以至少一次设置所有设置,但是计算机的行为是不确定的。
  • ADD指令是数据单元操作。 通过常数的值更改单元格中的值。 在这种情况下,位12是带符号的位,并被复制到位13-15。 因此,指令0x2ffe变为运算* AP + = 0x0ffe,指令0x3ffe变为* AP + = 0xfffe。 减法运算由负数加法代替。
  • ADA指令-实现操作AP + = const,并允许您在内存中导航。
  • 说明JZ和JNZ是有条件的。 根据Z标志,您可以向前或向后跳转一些指令,也可以留在原地。 根据机器的工作模式(16位或8位),Z标志的状态取决于最低有效数据字节或整个字。

技术指标


BrainfuckPC是一台16位计算机,具有簧片中继处理器,冯·诺依曼体系结构和Brainfuck ++指令集

  • 继电器总数:578件
  • 逻辑元素总数:157个
  • 地址总线宽度:16位
  • 寻址:逐词
  • 内存:128KB(64 Kslov)
  • 数据总线宽度:16bit / 8bit
  • 时钟频率(当前/最大):25Hz / 40Hz
  • 消耗功率:70W
  • 外形尺寸:110kh650kh140mm
  • 重量:15公斤


最初,假定计算机可以在高达100 Hz的频率下运行...而且-一分钟-4个钢琴八度音阶。 不幸的是,最初的测试表明40Hz是上限,但是对于继电器电路来说,有很多这样的上限。 尤其是当需要外部时钟每个周期施加两个脉冲时,这是由于同步电路具有外部信号的特殊性。 80Hz的音乐已经足够了。

电脑组成



图6:中继计算机的主要组件。

让我们仔细看一下计算机。 机器的几乎整个体积都由中继处理器单元占用。 目前,所有内容都可以容纳5个块,但最多可以容纳6个块-因此,如果您确实愿意,那么以后可以扩展处理器的功能。
每个这样的模块包含32个模块,每个模块中有3个或4个干簧继电器RES55和RES64。 每个单元的电源为5V,3A。


图7:中继处理器的一组块和模块,准备安装在框架上。

每个模块是统一的。 60x44mm 16针连接器。 组装逻辑块时,我将所需的模块插入了空闲插槽,并刷新了连接。


图8:检查D触发器模块的可操作性。

中央行-加法器块和寄存器块。 它们上方和下方是基于RES43的16位锁存器,可在模块之间切换数据流。 所有数据都在这里旋转。

最下面的一行是处理器逻辑块的行。 现在,部分填充了两个块,但是如果您确实愿意,那么由于可用空间而进行的修改和扩展功能就远远超过了可能。


图9:框架是由2毫米铝板组装而成,并经过激光切割。 在照片中-已经焊接并涂上底漆的框架,准备进行绘画。

上部是指示器。 机器状态块位于左侧-基于IV-6的指示器显示当前存储单元的编号和当前指令的夜晚,指令本身以及已执行指令的常规计数器。 后者非常有用,因为例如,如果仿真器说控制台中的第一个字符需要执行3万条指令,则计数器将清楚地显示机器现在所在的位置以及何时完成计数。


图10:指示器区域的最终视图。 在制造过程中。

右侧是存储卡-设备中最具争议的元素。 尽管我相信计算机仍在中继,但处理器绝对是100%中继。 外围更现代。 特别地,RAM是静态存储器芯片。 但是几乎所有中继计算机的现代创造者也是如此。


图11:编程器 16条地址线,16条数据线,电源线,地线和写入读取线。 总计36个联系人。

由于程序和数据的内存是共享的,因此每次打开计算机时,某人或某物必须将程序加载到RAM中。 该任务已分配给程序员。 目前,编程器位于内存板上。 现在他正好有两个任务。

  1. 将程序下载到RAM中,因为每次打开电源使用拨动开关手动进行操作都是过时的,尽管存在这种可能性。
  2. 监视某个内存区域的状态,并将其显示在32x16 LED矩阵上。

这不会影响处理器,并且在调试时实时查看RAM中发生的情况非常有用。 随后,当编程器位于外部时,LED面板将用作指示器模块之一。 他已经知道地址了,剩下的就是给他输入数据。


图12:处理器外围设备的框图。

因此,在不久的将来,处理器外围设备的电路将看起来像。 只有内存芯片和带有继电器电路的信号匹配电路将保留在内存板上。

通过36针编程连接器,您可以连接编程器并将固件下载到计算机。 除了编程器外,还具有必需的接口转换器,您可以使用任何其他设备。 至少有一个打孔磁带阅读器(顺便说一句,我有一个,配有打孔器,甚至还有一个磁带盘),至少有一个带拨动开关的面板。

结果,中继逻辑提供了一定的接口,并且接口转换器可以是任何接口。 顺便说一下,并行接口连接器将与LPT ...

演示工作和当前状态


首先,在计算机上执行了Wikipedia文章中的Hello World程序。

源代码如下:

++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++
.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.
------.--------.>+.>.

借助LED面板,您可以清楚地看到数据如何变化:


尽管在25 Hz的频率下,很难跟踪RAM中发生的情况。

一个更有用和实用的任务是计算小数点后的数字Pi的符号。 显然,现代计算机已经解决了多达31.4万亿个字符的问题 。 但是BrainfuckPC能够执行此操作的事实表明,中继计算机不是100%无用的,而只是99.9。

首先,我找到了用Brainfuck编写的现成的计算算法

> ++++ (4 digits)
[<+>>>>>>>>++++++++++<<<<<<<-]>+++++[<+++++++++>-]+>>>>>>+[<<+++[>>[-<]<[>]<-]>>
[>+>]<[<]>]>[[->>>>+<<<<]>>>+++>-]<[<<<<]<<<<<<<<+[->>>>>>>>>>>>[<+[->>>>+<<<<]>
>>>>]<<<<[>>>>>[<<<<+>>>>-]<<<<<-[<<++++++++++>>-]>>>[<<[<+<<+>>>-]<[>+<-]<++<<+
>>>>>>-]<<[-]<<-<[->>+<-[>>>]>[[<+>-]>+>>]<<<<<]>[-]>+<<<-[>>+<<-]<]<<<<+>>>>>>>
>[-]>[<<<+>>>-]<<++++++++++<[->>+<-[>>>]>[[<+>-]>+>>]<<<<<]>[-]>+>[<<+<+>>>-]<<<
<+<+>>[-[-[-[-[-[-[-[-[-<->[-<+<->>]]]]]]]]]]<[+++++[<<<++++++++<++++++++>>>>-]<
<<<+<->>>>[>+<<<+++++++++<->>>-]<<<<<[>>+<<-]+<[->-<]>[>>.<<<<[+.[-]]>>-]>[>>.<<
-]>[-]>[-]>>>[>>[<<<<<<<<+>>>>>>>>-]<<-]]>>[-]<<<[-]<<<<<<<<]++++++++++.

一个问题-尽管据说该程序比其他程序要快得多,但它仍会计算下一个字符,但速度非常慢。


图13:小数点后输出Pi的N位数字所需的时间。

小数点后四个位将不得不等待近一个半小时...


图14:-Pi = 3! -太粗鲁了!

但是,甚至不能真正推断出两个字符;相反,计算机指出Pi是4,并完成了工作。


图15:他清楚地知道在戒严令下pi可以上升到4的笑话。

我决定走另一条路,写了一个分数计算器
 frac355113=3.141592...。 准确性-小数点后6位! 对于具有足够大小的数字的分数,这是最准确的结果。

经过三夜的不眠之夜,我确实编写了一个关于Brainfuck的程序,该程序能够将两个数字互相除,然后将带有浮点数的结果输出到终端。 模拟器的判断如下-将需要6万条指令才能执行。 最后,每个符号一万:


图16:计算分数时输出下一个小数位所花费的时间。

下一个值将多快出现一次。 与以前的程序相比,我必须说得很快!

但是幸福是短暂的-计算机在16位模式下开始出现故障。 诊断表明存储卡在欺骗-它不断设置第13位。 我会换一张新的存储卡,一切都会顺利进行,但现在我将自己限制在很小的范围内  frac227=3.14...小数点后两位和8位操作模式。 最重要的是,它只需要遵循1600条指令即可! 在25 Hz的频率下,这只是一分钟多一点。


电脑反复鸣叫,以应付任务。

待续...


现在,您可以在计算机上运行不需要用户输入的程序。 到目前为止,我还没有轻易搞错CTRLIO.CIN指令:)而且我也不会在不久的将来这样做。 目前计算机已完成98%。 经过两年的工作,许多项目已经积累起来,正在等待我处理它们的那一刻。

因此,我切换到其他项目


首先,这是基于换向器十进制的电子管计算机。 我已经有拳打手和十足了(尽管大多数情况下是A101,但计算机的输出速度甚至比继电器上的继电器慢,我们需要A103)。 甚至已有700个真空管可用,而且还有更多...



我已经为此准备了一个内存-16个集成内存块 ,每个 128个16位字。 内部-多孔铁氧体板,一种在铁氧体环上的记忆分支。

我也不会忘记肺炎 -我的朋友安东(Anton)从事大自然。 实验,但下次会更多。

...留下以下缺陷。 我将在5月底解决节日的部分问题,部分-不:

  • 一个新的存储卡,仅安装RAM芯片及其线束。 存储卡有一块电路板,该电路板尚未离婚。 在家里,这样做太懒惰了(相当密集的双向),因此,当我为其他几个项目订购电路板时,我将按顺序包括该电路板-继电器上的机械时钟和气压计。
  • 除新的存储板外,还将提供拨号指示器,普通的终端显示硬件和用于更新LED面板的独立逻辑。
  • 程序员,或者说,固件的开发。 通常,如果您有旧的存储卡,则是多余的,但是由于可以使用编程连接器,因此您已经可以用它加载程序了。
  • 时序逻辑。 在这里,我完全是懒惰的驴子,因为实际上有3个逻辑模块。 我一定会在五月下旬的节日上演出。
  • 从控制台读取指令。 它与定时逻辑相关联(在同步操作中,计算机必须停止操作并等待数据到达)。
  • 向“吉尼斯纪录”发送请求...作为最快的中继处理器,同时读取速度最慢。 16 milliFlops不适合您“将皮大衣塞在内裤中”(来自youtube上的评论)。

图片

中继计算机上的所有文档都在GitHub存储库中 ,您可以使用我的个人资料中的链接来监视其在任何社交网络上的状态。

UPD:我拒绝参加音乐节。
然而,5月25日至26日,在面包厂地区的莫斯科,将举行首个手工艺品生产和DIY文化工厂节。 我将在那里配备一台中继计算机,还将带来一台用于自动浇水中继控制器 该活动免费入场,因此这些天您将在莫斯科-不要错过看我的接力怪兽直播的机会。 如果安全可靠地携带它,我一定会在工作中演示它。

Source: https://habr.com/ru/post/zh-CN442732/


All Articles