用 逻辑电路 实现一个 开平方 算法

这篇文章 的 起因 是  《小梦 在 民科吧 发了一个 用 四则运算 开平方 的 帖》  https://www.cnblogs.com/KSongKing/p/13296121.html    。

 

《小梦 在 民科吧 发了一个 用 四则运算 开平方 的 帖》    也 发到了 反相吧  《小梦 在 民科吧 发了一个 用 四则运算 开平方 的 帖》  https://tieba.baidu.com/p/6811112759     。

 

我在 帖 里的  12 楼 说了,  小梦 的 算法 简单小巧,   适合 用在 计算器 上,   我们可以 设计一个 硬件电路 来 实现 它  。

 

这个 算法 是,   设 b 为 被开方数,   任取一个 正数 a,   令   c  =  ( b - a ² )  /  ( 2 a )  ,   令  a = a + c ,    重复若干次 这个过程,   a 就 很接近 b 的 平方根    了  。

 

这个 算法 可以 称为  “小梦算法”   。

 

 

先画 一个 逻辑电路图 :

 

                                        图 (1)

 

 

 

左边 的   a 、b 、c 、diff 、v1 、v2 、abs_diff 、max_diff   是  存储单元,  也就是 内存,   也就是 内存单元,    假设 每个 存储单元 是 64 位 的,   可以 存储  64 位 浮点数  。

 

右边 的   F1 、F2 、F3 、F4 、F5 、F6   是  控制单元,   具体 的 控制 和 运算 逻辑 就在 控制单元 里 实现  。

 

橙色线 和 橙色箭头 是  控制信号线路 和 信号传递方向,    蓝色线 是 数据线路,     一根 线 在 实际中 可能是 多位 的  。

绿色线 和 绿色箭头 也是 控制信号线路,    表示 和 橙色 不同 的 控制分支 。

 

F1 、F2 、F3 、F4 、F5 、F6    都 会有 数据线 和  相关的 存储单元 相连,    图中 用 蓝线 简略 的 表示,  并没有 画出 具体 的 连接 线路  。

 

我们 定义 :      有电压 为 1,  无电压 为 0     。

 

开始运算 时,    输入端  输入 一个  1 脉冲,  就可以 触发 电路 开始 进行 开平方 运算  。    注意 是  1 脉冲,   不是 持续 的 1  。

 

先介绍一下 存储单元,   

 

a   存放 a,    a 是 中间结果,  也是 最终结果

b   存放 b,    也就是 被开方数

v1   存放  a ²  

diff   存放   b  -  a ²   

v2    存放   2 * a   

c   存放   diff / v2   

abs_diff   存放  diff 的 绝对值

max_diff   存放 精度值,   当    b  -  a ²   的 绝对值 , 也就是  abs_diff   小于  max_diff  时,  a 为 达到 精度 的 开方结果 ,  可以输出  。

 

开始 运算 前,     先 把 被开方数  存到  b,    同时 任取一个 正数, 比如 1 ,  存到 a  。

 

然后,   向 输入端 输入 一个   1 脉冲,   F1  接收 到  1 脉冲 后 接通电路,  开始工作 。   F1 的 工作 是 发信号 给 运算器,  让 运算器 计算 a ²,  运算器 计算 结束后,   F1 把 计算结果 存放到  v1 ,   同时 发出 一个  1 脉冲,  触发  F2  开始工作  。

 

运算器 在  这个 图 里 没有 画出来,    运算器 是 一个 公共部件,     F1 、F2 、F3 、F4 、F5 、F6  都会去调用  。

 

F1  的 内部电路  会 在 下文 画出来,   里面 会 画出  F1  调用  运算器 的  电路 和 逻辑  。

 

F1  的 内部电路  如下 :

 

                           图 (2)

                          图 (3)

 

 

 

 

 

 

输入端 接收 到  1 脉冲,   这个 1 脉冲 会让  “让 寄存器 A 变成 写入状态”   电路 接通,  这个电路 会 向  寄存器 A  发出 信号,   告诉 寄存器 A 变成 写入状态,

同时,  输入端  的  1 脉冲  还会 让  “让 寄存器 B 变成 写入状态”  电路 接通,  这个电路 会 向 寄存器 B  发出 信号,   告诉 寄存器 B 变成 写入状态,

同时,  输入端  的  1 脉冲  会 触发   延时开关,    延时开关 在 一段时间 后 输出 一个  1 脉冲 ,   这个  1 脉冲 会 接通 下一个 操作 的 电路 。

 

这样 就可以 在 一个 操作 完成后 触发 下一个 操作 执行  。

 

为什么要用 延时开关 呢 ?    是 为了  确保 上一个 操作 完成 后,  才 触发 下一个 操作  。   因为  电路 的 运行 需要时间, 每一段电路 运作 需要 的 时间 也 不完全相同,  所以 需要 延时开关 在 一段时间 后 发出 1 脉冲 触发 下一个 操作,   这段时间 应该 足够 完成 当前操作,   这样 来 确保 触发 下一个 操作时,  当前 操作 已经 完成  。  

 

从 图上 可以看到,      “让 寄存器 A 变成 写入状态”    和    “让 寄存器 B 变成 写入状态”   是  F1  的  第一个 步骤,  这 2 个 操作 是 同时执行 的,  也可以说是 并行 执行 的  。

 

第 1 个 步骤 有 一个 延时开关,   当   “让 寄存器 A 变成 写入状态”    和    “让 寄存器 B 变成 写入状态”    完成 后,   延时开关 发出 1 脉冲,   触发 下一个 步骤  。

 

第 2 个 步骤 包含 2 个 操作,    “打开 a 和 寄存器 A 的 通路,  让 a 的 数据 写入 寄存器 A”  和  “打开 a 和 寄存器 B 的 通路,  让 a 的 数据 写入 寄存器 B”  ,

这 2 个 操作 也是 同时执行 的,    第 2 个 步骤 也 有 一个  延时开关,   这 2 个 操作 完成 后,     延时开关 发出 1 脉冲,   触发 下一个 步骤  。

 

到 目前为止,   每一个 操作 是 一段 电路,   这一段 电路 在 输入端 输入 1 脉冲 时 工作,   1 脉冲 结束 后 电路 停止  。

1 脉冲  是 有电压,  这个 电压 使得 电路 接通 并 工作,    1 脉冲 结束 后,   无电压,   电路不工作 。  延时开关 被 触发 后,   即使  输入端 的 1 脉冲 结束,  也会 在 设定好 的 时间 后 在 输出端  发出  1 脉冲   。

 

当然,  我们需要 知道 每一个 步骤 完成 的 最大时间,  以此 来 设置 这个 步骤 的 延时开关 的 延迟时间,    延迟时间 应该 比 步骤 完成 的 最大时间 更大一点,   这样 有一点 冗余,    有利于 电路 的 稳定运行  。

 

 

图 (3)   的 第一个 操作 是  “向 运算器 发出 指令 计算 乘法”,    同时 会 触发 延时开关,   延时开关 发出  1 脉冲 应该是在 运算器 完成 运算 之后  。

也就是说,  延时开关 的 延迟时间 应该是   “向 运算器 发出 指令 计算 乘法” 电路 的 运行时间  +   运算器 计算乘法 的 时间  +   冗余时间    。

 

这需要 设计者 了解 运算器 的 运算时间 ,    并以此 给 延时开关 设定 延迟时间   。

 

这个 设计 不算太好,     从 软件设计 的 角度来讲,  封装性 不太好,    高内聚 低耦合 不足 。   因为 这需要 运算器 将 运算时间 告知 其它 元件,  当  运算器 的 运算时间 发生变化,  与之相关 的 操作 的 延时开关  的 延迟时间 都要 修改  。

 

所以,  我们可以 把   图 (3)    的  这部分 设计 改一下 :

 

 

                          图 (4)

 

 

 

 

图 (4)  和  图 (3) 不同 的 地方 是 “向 运算器 发出 指令 计算 乘法”  的 下面 是  “一次性开关”,    图 (3) 中 此处 是 延时开关   。

 

运算器 计算 完成 时  会  通过 输出端  输出 1 脉冲, 表示 计算完成,  这个 1 脉冲 会 触发  外部电路  进行 下一步 操作  。

 

实际上 可以 不用 延时开关,  让 运算器 的 输出端 直接 连到 到 下一个 操作 的 电路,   运算器 计算完成 时 在 输出端 输出  1 脉冲  可以 直接 触发 下一个 操作 的 电路   。

 

为什么 这里 要用  一次性开关 呢  ?

 

因为   有 多个 元件 会 用到 运算器,   所以,   运算器 的 输出端 会 连接 到 多个 元件,   向 多个 元件 输出 完成信号  1 脉冲,  这就需要 区分  当前 调用 运算器 的 是 哪一个 元件,   运算器 输出 的  完成信号  应该 只 发给 当前 调用 运算器 的 元件  。

 

此时,    一次性开关   就 派上用场  了   。

 

一次性开关  是 这样 :

 

                         

 

 

开始时,    一次性开关 处于 停止状态,   输出端 输出 0, 也就是 无电压   。

当 控制端 输入 1 脉冲 时,  触发  一次性开关,  进入 工作状态,    进入 工作状态 后,   输出端 仍然 是 0,   当 输入端 输入 1 脉冲  时,   输出端 输出 1 脉冲,   同时,   停止 一次性开关,    让 一次性开关 恢复 停止状态  。

在 停止状态 下,    无论 输入端 输入 1 还是 0,    输出端 的 输出 都是 0   。      或者说,  在 停止状态 下,  输入端 的 输入 无效  。

 

 

图 (1) 里 的  F6  “判断 abs_diff < max_diff”   会 2 次 用到 运算器,  一次 是 计算  diff 的 绝对值 (diff 的 绝对值 会 存到 abs_diff ),  一次 是 比较  abs_diff 和 max_diff      的 大小   。

 

运算器 除了 加减乘除,  还 可 求 绝对值 和 比较大小,     把 要 求 绝对值 的 数 放到 寄存器 A,   再 发指令 告知 运算器 求 绝对值,  运算器 会 对 寄存器 A 里 的  数   求 绝对值,  并把 结果 存放到 寄存器 C  。

 

运算器  提供 比较 大小 的 功能,   把  要 比较 大小 的 2 个 数 存到 寄存器 A 和 寄存器 B,    发指令 告知 运算器 比较大小,  运算器 会 比较 寄存器 A 和 寄存器 B  里 2 个 数,    运算器 会 提供 3 个 输出端 来 返回 比较结果,   这 3 个 输出端 是 “大于” 、“小于” 、“等于” ,   若  A > B ,  大于端 输出  1 脉冲,  若 A < B, 小于端  输出  1 脉冲,   若 A = B ,   等于端 输出  1 脉冲  。

 

 

运算器  本身 是一个 复杂 的 部件,   基本原理 也是 上文 所讲 的 逻辑电路 原理 ,   给出 适当 的 算法,  用 上文 的 逻辑电路 原理 可以 设计出 运算器  。

 

到 目前为止,  运算器 提供  加减乘除 、求绝对值 、比较大小,   一共   6 个 功能,  对应 6 个 指令,   可以用 3 位 指令 来 表示  。

 

0 和 1 的 3 位 组合 有 8 种,   000, 001, 010, 011, 100, 101, 110, 111   ,    去掉   000 ,  还有 7 种,   足够表示 6 个 指令  。

 

为什么 要 去掉 000 呢 ?      因为  000  表示   无指令, 空闲  。

 

6 个 指令 可以这样 表示  :

 

001       加

010       减

011       乘

100       除

101       求 绝对值

110       比较大小

 

 

运算器 提供 3 位 线路  作为 指令 输入端 就可以 。     也可以说,   运算器 的 指令 输入端 是 3 个 引脚  。   也可以说,  运算器 的 指令 输入端 是 3 条 接线  。

 

 

我以前写过一篇文章   《漫谈 计算机硬件 的 设计 和 实现》   https://www.cnblogs.com/KSongKing/p/9866334.html  ,   介绍过 “指令开关”,  指令开关 的 学名 似乎  叫    “译码器” 和 “真值表”   。

 

 

具体 的 电路设计,   比如  上文 涉及到 的 一些 开关元件 :   延时开关  、 一次性开关,   我会 另外写一篇 文章 《设计 逻辑电路 的 开关元件》 https://www.cnblogs.com/KSongKing/p/13412340.html  来 介绍  。

 

严格的说,   上文 说 的  “延时开关” ,    应该是 “延时脉冲开关”,      延时开关 是 触发 后 延迟一段时间 后 导通,   导通 后 一直 保持 导通状态  。

 

延时脉冲开关  是  触发 后 延迟一段时间 后 输出 一个 脉冲,    脉冲 是 指 导通 一段时间 后 断开,  并非 一直 导通,   导通时间 就是 脉冲宽度  。

 

当然,  还有 一种 延时开关 是 触发 后 立即 导通,   延迟一段时间 后 断开  。 

 

原文地址:https://www.cnblogs.com/KSongKing/p/13385387.html