Geek漫游指南

构建基础逻辑门

date
slug
build-basic-logic-gates
author
status
Public
tags
技术分享
summary
type
Post
thumbnail
category
🤖 Computer Science
updatedAt
Oct 29, 2023 10:07 AM

第1章 布尔逻辑

各种数字设备都是基于一组用于储存和处理信息的芯片构建而成的,这些芯片都是由基础的逻辑门构成的。本章从一个给定的最基础的逻辑门Nand gate(与非门)出发,构建其他各种逻辑门。
  • 布尔代数
布尔代数其实就是对生活中关于“真和假”的逻辑的抽象。假如我们使用的True表示真,False表示假。
  1. 如果我们断言“A和B都是真的”这句话是真的,那就表示A是真的,B也是真的。如果“A和B都是真的”是假的,那A和B中至少有一个是假的,用符号表示“A和B都是真的”就是:A and B 。
  1. 如果我们断言“A或者B是真的”这句话是真的,那就表示A和B中至少有一个是真的。如果“A或者B是真的”这句话是假的,那就表示A和B没有一个是真的,即A和B都是假的。用符号表示“A或者B是真的”就是:A or B。
  1. 我们常说的“不对”表示错,“不错”表示对。同样在逻辑世界也是很简单的,不真就一定假,这种“不”的逻辑让真和假可以转换,可以用符号not表示这种转换。
形如A and B这种式子叫做布尔表达式,这个式子的值(真假情况)由A和B的值来确定,所以它描述了一个关于A和B的函数,记作f(A,B)=A and B,这种用布尔表达式描述的函数叫做布尔函数。我们除了可以用布尔表达式描述一个布尔函数,还可以列举布尔函数所有可能的变量值(输入)与函数值(输出),构成布尔函数的真值表。
  • 门逻辑
门是用来实现布尔函数的物理设备。所以我们可以用真值表或布尔表达式来表述门的逻辑,这种方式描述的是门的接口,只规定了门的逻辑功能,并不关注它具体是怎么由基础的门逻辑组合而成的。门的接口是唯一的,但是门的内部实现是不唯一的。如果门的接口用布尔代数描述,门的实现说白了就是使用布尔运算的运算法则把布尔代数归约为基本的布尔代数的组合,如:有三个输入的与门可以归约为(A and B) and C ,其中括号表示布尔代数所描述的门逻辑的输出值。这个布尔代数就表达了将一个二元and门的输出作为另一个二元and门的输入的含义,以此构建出了一个三元and门。
  • HDL语言
HDL语言是一种硬件描述语言,就是一种描述各种门电路如何组合连接的语言。设计者可以通过编写HDL程序来描述芯片的结构,并通过对HDL程序进行虚拟仿真测试来反应所设计的芯片的功能状况(正确性,速度,功耗,成本…)。

构建计算机Hack所需要的16种芯片:

1
Nand
9
And16
2
Not
10
Or16
3
And
11
Mux16
4
Or
12
Or8Way
5
Xor
13
Mux4Way16
6
Mux
14
Mux8Way16
7
DMux
15
DMux4Way
8
Not16
16
DMux8Way
  • Nand 门
这是项目使用的最基本的门电路,我们使用它来组成其余所有需要的门电路。
真值表
a
b
out
0
0
1
0
1
1
1
0
1
1
1
0
  • Not 门
真值表
in
out
0
1
1
0
布尔代数
Not(in) = Nand(in, in)
HDL实现
----Nand(a=in, b=in, out=out);----
  • And 门
真值表
a
b
out
0
0
0
0
1
0
1
0
0
1
1
1
布尔代数
And(a, b) = Not(Nand(a, b)) = Nand(Nand(a, b), Nand(a, b))
HDL实现
---Nand(a=a, b=b, out=aNandB);Not(in=aNandB, out=out);---
  • Or 门
真值表
a
b
out
0
0
0
0
1
1
1
0
1
1
1
1
布尔代数
Or(a, b) = Nand(Not(a), Not(b))
HDL实现
Not(in=a, out=notA);Not(in=b, out=notB);Nand(a=notA, b=notB, out=out);
  • Xor 门
真值表
a
b
out
0
0
0
0
1
1
1
0
1
1
1
0
布尔代数
Xor(a, b) = Or(And(Not(a), b), And(a, Not(b)))
HDL实现
Not(in=a, out=notA);Not(in=b, out=notB);And(a=notA, b=b, out=w1);And(a=a, b=notB, out=w2);Or(a=w1, b=w2, out=out);
  • Mux 门
真值表
a
b
sel
out
a
b
0
a
a
b
1
b
如上表所示,Mux是一种从多路输入中选择一路的门。表中的a和b可以为0或者1。
布尔代数
Mux(a, b, sel) = Or(And(a, Not(sel)),And(b, sel))
HDL实现
Not(in=sel, out=notSel);And(a=a, b=notSel, out=w1);And(a=b, b=sel, out=w2);Or(a=w1, b=w2, out=out);
  • DMux 门
真值表
in
sel
a
b
in
0
in
0
in
1
0
in
如上表所示,DMux门是将一路输入选择性的输出到多路中的一路中,并保持其余输出路为0的门电路。
布尔代数
a = And(in, Not(sel))b = And(in, sel)
HDL实现
Not(in=sel, out=notSel);And(a=in, b=notSel, out=a);And(a=in, b=sel, out=b);
  • Not16 门
只需要将16个Not放在一起就是一个Not16门了。
HDL实现
Not(in = in[0], out = out[0]);Not(in = in[1], out = out[1]);Not(in = in[2], out = out[2]);Not(in = in[3], out = out[3]);Not(in = in[4], out = out[4]);Not(in = in[5], out = out[5]);Not(in = in[6], out = out[6]);Not(in = in[7], out = out[7]);Not(in = in[8], out = out[8]);Not(in = in[9], out = out[9]);Not(in = in[10], out = out[10]);Not(in = in[11], out = out[11]);Not(in = in[12], out = out[12]);Not(in = in[13], out = out[13]);Not(in = in[14], out = out[14]);Not(in = in[15], out = out[15]);
  • And16 门
HDL实现
And(a=a[0], b=b[0], out=out[0]);And(a=a[1], b=b[1], out=out[1]);And(a=a[2], b=b[2], out=out[2]);And(a=a[3], b=b[3], out=out[3]);And(a=a[4], b=b[4], out=out[4]);And(a=a[5], b=b[5], out=out[5]);And(a=a[6], b=b[6], out=out[6]);And(a=a[7], b=b[7], out=out[7]);And(a=a[8], b=b[8], out=out[8]);And(a=a[9], b=b[9], out=out[9]);And(a=a[10], b=b[10], out=out[10]);And(a=a[11], b=b[11], out=out[11]);And(a=a[12], b=b[12], out=out[12]);And(a=a[13], b=b[13], out=out[13]);And(a=a[14], b=b[14], out=out[14]);And(a=a[15], b=b[15], out=out[15]);
  • Or16 门
HDL实现
Or(a=a[0], b=b[0], out=out[0]);Or(a=a[1], b=b[1], out=out[1]);Or(a=a[2], b=b[2], out=out[2]);Or(a=a[3], b=b[3], out=out[3]);Or(a=a[4], b=b[4], out=out[4]);Or(a=a[5], b=b[5], out=out[5]);Or(a=a[6], b=b[6], out=out[6]);Or(a=a[7], b=b[7], out=out[7]);Or(a=a[8], b=b[8], out=out[8]);Or(a=a[9], b=b[9], out=out[9]);Or(a=a[10], b=b[10], out=out[10]);Or(a=a[11], b=b[11], out=out[11]);Or(a=a[12], b=b[12], out=out[12]);Or(a=a[13], b=b[13], out=out[13]);Or(a=a[14], b=b[14], out=out[14]);Or(a=a[15], b=b[15], out=out[15]);
  • Mux16 门
HDL实现
Mux(a=a[0], b=b[0], sel=sel, out=out[0]);Mux(a=a[1], b=b[1], sel=sel, out=out[1]);Mux(a=a[2], b=b[2], sel=sel, out=out[2]);Mux(a=a[3], b=b[3], sel=sel, out=out[3]);Mux(a=a[4], b=b[4], sel=sel, out=out[4]);Mux(a=a[5], b=b[5], sel=sel, out=out[5]);Mux(a=a[6], b=b[6], sel=sel, out=out[6]);Mux(a=a[7], b=b[7], sel=sel, out=out[7]);Mux(a=a[8], b=b[8], sel=sel, out=out[8]);Mux(a=a[9], b=b[9], sel=sel, out=out[9]);Mux(a=a[10], b=b[10], sel=sel, out=out[10]);Mux(a=a[11], b=b[11], sel=sel, out=out[11]);Mux(a=a[12], b=b[12], sel=sel, out=out[12]);Mux(a=a[13], b=b[13], sel=sel, out=out[13]);Mux(a=a[14], b=b[14], sel=sel, out=out[14]);Mux(a=a[15], b=b[15], sel=sel, out=out[15]);
  • Or8Way 门
这是个8路或门,8个输入中只要有一个是1,输出就是1。
HDL实现
Or(a=in[0], b=in[1], out=o1);Or(a=o1, b=in[2], out=o2);Or(a=o2, b=in[3], out=o3);Or(a=o3, b=in[4], out=o4);Or(a=o4, b=in[5], out=o5);Or(a=o5, b=in[6], out=o6);Or(a=o6, b=in[7], out=out);
  • Mux4Way16 门
与Mux16门类似,Mux4Way16是从4路输入中选择一路,每路的宽度是16位。Mux16是从2路输入中选择一路,所以我们可以先想办法在2个Mux16中选择的一个作为输出,再选择该Mux16的两个输入中的一个,一共需要3个Mux16门。
HDL实现
Mux16(a=a, b=b, sel=sel[0], out=o1);Mux16(a=c, b=d, sel=sel[0], out=o2);Mux16(a=o1, b=o2, sel=sel[1], out=out);
  • Mux8Way16
Mux8Way16门是从8路输入中选取一路,类似的我们可以用2个Mux4Way16门和一个Mux16门组成Mux8Way16门。
HDL实现
Mux4Way16(a=a, b=b, c=c, d=d, sel=sel[0..1], out=o1);Mux4Way16(a=e, b=f, c=g, d=h, sel=sel[0..1], out=o2);Mux16(a=o1, b=o2, sel=sel[2], out=out);
  • DMux4Way
DMux4Way可以将1路输入输出到4路中指定的1路上。类似的可以用3个DMux门组成,第一个DMux负责选择输出到另外两个DMux中的哪一个,再从被选中的DMux中选择一路输出。
HDL实现
DMux(in=in, sel=sel[1], a=d1, b=d2);DMux(in=d1, sel=sel[0], a=a, b=b);DMux(in=d2, sel=sel[0], a=c, b=d);
  • DMux8Way
DMux4Way可以将1路输入输出到8路中指定的1路上。类似的可以用2个DMux4Way门和一个DMux组成,DMux负责选择输出到另外两个DMux4Way中的哪一个,再从被选中的DMux4Way中选择一路输出。
HDL实现
DMux(in=in, sel=sel[2], a=d1, b=d2);DMux4Way(in=d1, sel=sel[0..1], a=a, b=b, c=c, d=d);DMux4Way(in=d2, sel=sel[0..1], a=e, b=f, c=g, d=h);

第二章 布尔运算

本章的目标是使用上一章构建的逻辑门来组合出一个具备完整功能的算术逻辑单元(ALU)。
首先需要了解的是如何利用二进制码来表示有符号整数,然后是如何使用布尔运算或者说逻辑运算来计算用二进制码表示的有符号整数的加法。
  • 二进制数
我们常使用的形如123(读作壹佰,贰拾,叁),从右向左数第一个数3表示3个壹;第二个数2表示2个拾;第三个数1表示1个佰。每一位数字都有其权重,越向左边权重越大,相领两位之间的权重比是“拾”。
而二进制数,就是形如11001(2)这种。它的每一位要么是0要么是1,而且也有其权重,从右向左数第一位也表示有多少个壹,但第二位表示有多少个贰,第三位表示有多少个肆,第四位表示有多少个捌……相邻两位之间的权重比是“贰”。
  • 有符号二进制数
为了表示二进制数的正负,我们只需要留出一位作为符号们即可,一般用最左边的一位。如果它是0则表示是正数,如果是1则表示是负数。但是这样做就导致一个问题,100和000都表示0,这就白白浪费了一种状态。我们知道111表示的是-3,所以我们可以把100表示为-4。这样需要可以但是如果把这些数按二进制编码的顺序排列开来就是这样的:
000
001
010
011
100
101
110
111
0
1
2
3
-4
-1
-2
-3
这样很不和谐,不美观。多半不那么好用,因为这个-4位置太特殊了,如果用这种二进制序列来表示有符号整数,在设计其算术逻辑时候肯定很复杂。怎么优化呢?正常习惯来说,在一个数轴上-4,-3,-2,-1它们应该依次从左往右排列,而加1就是在沿着数轴向右移动1步。所以如果我们这样排列:
000
001
010
011
100
101
110
111
0
1
2
3
-4
-3
-2
-1
就更加和谐了,结果也是出乎意料的好。我们知道二进制的加法中111 + 1=1000,但如果我们只有三位空间,那第四个1就超出空间了,也就是溢出了。这就会产生111+1=000的效果,仔细比较上表,111不正表示-1吗?-1+1=0这好像一个巧合一样。
怎么理解呢?我是这样想的,溢出把表格的最右边和最左边联系起来了,就仿佛围成了一个圆,现在假如有8个桩子围成一个圆,向左走1步不就相当于向右走7步吗?所以111 = 7 可以用来表示 -1;同样的向左走2步等价于向右走6步,即110=6可以表示-2;
最后的效果就是,如果你想要计算2-1,也就是当你站在代号为2的桩子上你想要向左走1步,就可以用向右走7步来代替了。就样我们就可以一根筋的一直向右走(一直使用加法)来替代向左走(减法)了。还有一个问题就是如果如果我们计算3+1,就会得到-4,这显然是错误的。因为我们把100用来表示-4了,所以这种编码下3个二进制数能表示的最大整数是3,最小整数是-4。
  • 二进制加法
由简到难,先讨论两个一位二进制数加法:
加数
加数
和
进位
0
0
0
0
0
1
1
0
1
0
1
0
1
1
0
1
查找第一章的内容可以发现两个一位二进制数的和与它们的Xor门的结果一模一样,而它们的进位与And门的结果一样,所以我们可以用一个Xor门和And门来进行两个一位二进制数的加法计算。
那么如何进行两个二位二进制数的加法计算呢?(如:A2A1 + B2B1)
  1. 我们可以先计算第一位,即A1+B1,然后得出当前位的和S1与进位C2。
  1. C2需要与第二位上的两个数即A2,B2相加,所以现在我们需要计算三个一位二进制数的加法即计。
  1. 我们可以先计算其中两位比如A2和B2,来得到一个和s2 与进位C3,然后用s2与C2相加得到当前位上最终的和S2(注意C3已经是更高位的数了,在当前位也就是第二位上就不用考虑它了),以及另一个进位C3’。
  1. 按惯性思维来说,我们应该继续计算出C3与C3’的和与进位,来确定进位到底是多少,以及是否有可能会产生进位的进位。
  1. 但是从实际来说,三个一位二进制数相加不可能产生进位的进位。可以观察上面的表格,如果进位为1,那和一定是0,而0再与另外任意一个一位二进制数相加进位都不可能为1。
  1. 所以C3与C3’最多只有一个是1,它们的和可以简单的使用一个Or门来表示。
  • 半加器
用来进行两位加法。
HDL实现
CHIP HalfAdder { IN a, b; // 1-bit inputs OUT sum, // Right bit of a + b carry; // Left bit of a + b PARTS: // Put you code here: Xor(a=a, b=b, out=sum); And(a=a, b=b, out=carry);}
  • 全加器
用来进行三位加法
HDL实现
CHIP FullAdder { IN a, b, c; // 1-bit inputs OUT sum, // Right bit of a + b + c carry; // Left bit of a + b + c PARTS: // Put you code here: HalfAdder(a=a, b=b, sum=s, carry=c1); HalfAdder(a=s, b=c, sum=sum, carry=c2); Or(a=c1, b=c2, out=carry);}
  • 16位加法器
用来进行两个16位加法
HDL实现
CHIP Add16 { IN a[16], b[16]; OUT out[16]; PARTS: // Put you code here: FullAdder(a=a[0], b=b[0], c=false, sum=out[0], carry=c0); FullAdder(a=a[1], b=b[1], c=c0, sum=out[1], carry=c1); FullAdder(a=a[2], b=b[2], c=c1, sum=out[2], carry=c2); FullAdder(a=a[3], b=b[3], c=c2, sum=out[3], carry=c3); FullAdder(a=a[4], b=b[4], c=c3, sum=out[4], carry=c4); FullAdder(a=a[5], b=b[5], c=c4, sum=out[5], carry=c5); FullAdder(a=a[6], b=b[6], c=c5, sum=out[6], carry=c6); FullAdder(a=a[7], b=b[7], c=c6, sum=out[7], carry=c7); FullAdder(a=a[8], b=b[8], c=c7, sum=out[8], carry=c8); FullAdder(a=a[9], b=b[9], c=c8, sum=out[9], carry=c9); FullAdder(a=a[10], b=b[10], c=c9, sum=out[10], carry=c10); FullAdder(a=a[11], b=b[11], c=c10, sum=out[11], carry=c11); FullAdder(a=a[12], b=b[12], c=c11, sum=out[12], carry=c12); FullAdder(a=a[13], b=b[13], c=c12, sum=out[13], carry=c13); FullAdder(a=a[14], b=b[14], c=c13, sum=out[14], carry=c14); FullAdder(a=a[15], b=b[15], c=c14, sum=out[15], carry=c15);}
  • 增量器
专门用来给指定数字加1的电路
HDL实现
CHIP Inc16 { IN in[16]; OUT out[16]; PARTS: // Put you code here: Add16(a=in, b[1..15]=false, b[0]=true, out=out);}
  • 算术逻辑单元ALU
书中的Hack计算机使用的ALU是可以进行两个16位二进制数函数运算的部件,它可以通过6个参数来控制输入。zx与zy分别用来表示是否使两个输入变成0,nx与ny用来取反两个输入,f来决定是进行加法+运算还是与&运算。no用来决定是否取反计算结果后再输出。Hack的ALU除出有一个最终运算结果输出out外,还有两个输出zr与ng,分别用来表示输出是否为0,以及输出是否为负数。
根据之前所学的知识,特别是利用MUX门我们完全有能力实现一个上述功能的ALU,下面是我的代码。
HDL实现
CHIP ALU { IN x[16], y[16], // 16-bit inputs zx, // zero the x input? nx, // negate the x input? zy, // zero the y input? ny, // negate the y input? f, // compute out = x + y (if 1) or x & y (if 0) no; // negate the out output? OUT out[16], // 16-bit output zr, // 1 if (out == 0), 0 otherwise ng; // 1 if (out < 0), 0 otherwise PARTS: // Put you code here: Mux16(a=x, b=false, sel=zx, out=xOrZero); Mux16(a=y, b=false, sel=zy, out=yOrZero); Not16(in=xOrZero, out=notXOrZero); Not16(in=yOrZero, out=notYOrZero); Mux16(a=xOrZero, b=notXOrZero, sel=nx, out=fX); Mux16(a=yOrZero, b=notYOrZero, sel=ny, out=fY); Add16(a=fX, b=fY, out=xAddy); And16(a=fX, b=fY, out=xAndy); Mux16(a=xAndy, b=xAddy, sel=f, out=andOradd); Not16(in=andOradd, out=notAndOrAdd); Mux16(a=andOradd, b=notAndOrAdd, sel=no, out=out, out[15]=ng,out[0..7]=lowOut, out[8..15]=highOut); Or8Way(in=lowOut, out=lz); Or8Way(in=highOut, out=hz); Or(a=lz, b=hz, out=notZr); Not(in=notZr, out=zr);}
notion image
img

第三章 时序逻辑

前面学习的都是所谓组合芯片,它们的特点是当输入变化时,输出立刻马上跟着变。但是它们不能维持自己的状态,这与我们理解的计算机是不一致的,显然计算机不仅有计算功能,更可以存取数据。所以我们还需要另一种芯片,具有记忆功能的芯片,也就是所谓的时序芯片。
  • 触发器
这是一种将某种结构的组合电路的输出与输入相连的特殊电路,现在我们考虑这样的电路:
notion image
img
A
B
Q1
Q2
0
0
1
1
0
1
1
0
1
0
0
1
1
1
?
?
当A和B都是1的时候,可以发现我们没法确定了这个电路此时是什么状态。因为Q1和Q2是什么居然要取决于Q1和Q2是什么。Q1和Q2能是什么呢?无非是0和1这两种状态,它们俩搞不出什么幺蛾子,最多有4种状态,我们一种一种分析。以下都默认A与B为1。
Q1
Q2
Q1
Q2
0
0
不可能
不可能
0
1
0
1
1
0
1
0
1
1
不可能
不可能
构建上表的时候,可以发现让Q1与Q2相同是不可能的,因为如果A与B置为1,那么对于Q1和Q2来说,与非门就变成了一个Not门,上述电路可以解释为Not(Q1) = Q2 同时Not(Q2)=Q1,这两个条件是其实是等价的,所以如果A与B都置为1,那么Q1与Q2的值只要相反就可以稳定下去,至于它们到底是01还是10就取决于它们之前是什么了。
现在整理一下这个电路的特性:
  1. 如果AB都是1,那么Q1与Q2都是1。
  1. 如果A是0,B是1,那么Q1是1,Q2是0。
  1. 如果A是1,B是0,那么Q1是0,Q2是1。
  1. 如果A是1,B是1,那么Q1还是Q1,Q2还是Q2,但是Q1与Q2一定相反。
考虑一下,现在电路可以有上述4种状态,可以发现状态2和3它们的输出结果Q1与Q2满足状态4中的Q1与Q2相反。想象一下,如果电路从状态2或者3切换到状态4会发生什么?答案是Q1与Q2保持之前的结果不变化。
如果我们有一种方法能让A与B要么相反,要么都是1,不就可以控制数据的输入与储存了吗?当然我这样说是因为我看了答案,其实我也觉得太奇妙了。
来看下面的 电路:
notion image
img
D
C
Q1
Q2
1
0
1
0
0
0
0
1
1
1
Q1
Q2
0
1
Q1
Q2
上述电路的特点就是,如果我们将C转为0时,如果D是1,那么Q1就是1,如果D是0那么Q1就是0;而如果我们把C置为1,那么Q1和Q2将始终维持之前的状态不变。Q1与Q2是始终相反的。因为Q1与Q2的状态始终有稳定的联系所以现在开始我们只考虑Q1。 可以把输入C 理解为阀门,而D理解为输入端的物料,如下图所示:
notion image
img
如果阀门C1关闭,也就是说将C1置为0,那么如图所示,阀门C1右边的输出将仍然是过去的状态,就好像它把数据D1卡在左边一样,而如果C1置为1,那么数据D1就会通过第一个阀门,至于它能不能通过第二个阀门那就是C2的事了。当然也有一些不同的地方,就是阀门打开后,阀门的左边和左边都会变成D1,这个阀门就像是在控制输出端是否克隆输入端的值。阀门打开,输出端克隆输入端的值,输入端也还存在。信息这个东西就是这样,我告诉你一件事后,我们两个人都知道了,而不是单纯的转移给你了。
还是继续讨论上面的示意图,如果我们把C1和C2用某种方式联系起来,具体来说就是让C2与C1保持相反,也就是C1打开时C2就关闭,而C1关闭时C2就打开。这会有什么效果呢?(这谁想的?呜呼真NB)效果就是生活充满节奏感~~~
好了,现在我们开始,我们从最开始轨道上啥也没有开始考虑(其实啥也没有也可以认为是0,但是这个0不是我们刻意为之的,所以不算数):
  1. 如果C1是0,那么D1被卡在最左边,C2是1但是没有什么用,它左边啥也没有,所以阀门C2左右两边信号再好它俩也没有好聊的。
  1. 如果C1变成1,那么D1被传送到中间遇到了C2,而此时C2变成了0所以D1就老老实实呆在中间了。
  1. 然后如果C1又变成0了,那么C2自然变成了1,所以D1最终通过了阀门C2。
好了,现在让我们抛开所有细节,感受一下,你看数据D是不是就像跟着口号在一步一步向前走一样?而喊口号的正是C1和C2,它俩是始终相反了,所以我们以后只考虑C1并把它简化为C,也叫做时钟信号。时钟信号喊到01010101……然后数据D在喊0时候向前走一步,喊1时候又向前走一步,注意这两步是不一样的就像是先左脚再右脚一样,所以我们以一个时钟周期为一个节拍01然后再01。可能你会想着为什么不能以半个周期为节拍呢?明明喊0喊1时候数据D都在向前走啊?我的想法是就像前面我们说了,这两步是不一样的,如果理解成喊0迈左脚,喊1迈右脚。那中间可能会出现有人左脚在前,有人处于正常站姿。此时再喊口号时候就乱了,有人正常走,有人劈叉了……所以我们简单一些,一个周期为节拍,大家先左脚再右脚,一步一个脚印,你有事先走几步也没事,反正你也是按节拍走的,两腿肯定处于立定状态。
上面说的这个玩意就是所谓D触发器,也就是D-FlipFlop。如果我们抽象的看它,它提供了一种功能:它让此刻的输入,在下一个节拍或者说下一个时钟周期出现在输出端