从数字电路到CPU

总线

  • 总线(Bus),一路信号
    • 使用条件运算符(?:),实现三态缓冲器(Tristate Buffer)
      • 如果控制信号为1,那么输出信号等于输入信号
      • 如果控制信号为0,那么输出信号为高阻态,类似于断开连接
      • 在Verilog中,z表示高阻态,x表示未知
    • 输入信号为四路信号SW[3:0],控制信号为四路信号SW[7:4],总线信号为一路信号LEDR[0]
// ----- Bus -----

// (SW) -> (LEDR)
module TopLevel(SW, LEDR);
    input [9:0] SW;
    output [9:0] LEDR;

    assign LEDR[0] = SW[4] ? SW[0] : 1'bz;
    assign LEDR[0] = SW[5] ? SW[1] : 1'bz;
    assign LEDR[0] = SW[6] ? SW[2] : 1'bz;
    assign LEDR[0] = SW[7] ? SW[3] : 1'bz;
endmodule

复用器、解复用器

  • 复用器(Multiplexer,Mux),多路信号转一路信号;解复用器(Demultiplexer,Demux),一路信号转多路信号
    • 使用case语句,实现译码器(Decoder)
      • 上面的总线需要4个开关对应4种情形,并且可能选中多种情形,产生信号冲突
      • 译码器可以减少开关数量(2个开关对应4种情形)、防止信号冲突(只能选中1种情形)。因此,译码器通常用于选中1种情形
    • 信号转换
      • Mux通常用于多个设备连接到一个总线,比如四路信号SW[3:0],转一路信号LEDR[0]
      • Demux通常用于一个总线连接到多个设备,比如一路信号KEY[0],转四路信号LEDR[3:0]
    • 按钮KEY[0]未按下时为1,按下时为0,所以我们需要进行非运算(~)
// ----- Mux -----

// (SW) -> (LEDR)
module TopLevel(SW, LEDR);
    input [9:0] SW;
    output [9:0] LEDR;

    reg mux_signal;

    assign LEDR[0] = mux_signal;

    always @(*)
        case (SW[5:4])
            0: mux_signal = SW[0];
            1: mux_signal = SW[1];
            2: mux_signal = SW[2];
            3: mux_signal = SW[3];
        endcase
endmodule



// ----- Demux -----

// (SW, KEY) -> (LEDR)
module TopLevel(SW, KEY, LEDR);
    input [9:0] SW;
    input [1:0] KEY;
    output [9:0] LEDR;

    reg [9:0] demux_signal;

    assign LEDR = demux_signal;

    always @(*)
        case (SW[1:0])
            0: demux_signal[0] = ~KEY[0];
            1: demux_signal[1] = ~KEY[0];
            2: demux_signal[2] = ~KEY[0];
            3: demux_signal[3] = ~KEY[0];
        endcase
endmodule

寄存器

  • 寄存器(Register),存储数据
    • 输入信号A[3:0]、B[3:0],进行按位与,结果存入寄存器C[3:0]
    • wire变量和reg变量
      • wire变量表示导线,输入信号改变,则输出入信号改变
      • reg变量表示寄存器,输入信号改变,则只有敏感列表的事件发生后,输出信号改变
    • 敏感列表的事件为posedge(~KEY[0]),即~KEY[0]的上升沿
      • ~KEY[0],0 –> 按下按钮 –> 1 –> 放开按钮 –> 0
      • ~KEY[0]的上升沿为按下按钮,此时结果存入寄存器C[3:0]
    • 通常,我们使用一个固定时钟信号的上升沿。因此,寄存器的数据变化,必须和时钟信号的周期变化同步,这样的数字电路称为同步电路
      • 同步电路使用非阻塞赋值(<=)
      • CPU的频率,即为时钟频率,它反映了CPU的运算速度。比如CPU的频率为4GHz,1秒可以运算4G = 40亿次
// ----- Register -----

// (SW, KEY) -> (LEDR)
module TopLevel(SW, KEY, LEDR);
    input [9:0] SW;
    input [1:0] KEY;
    output [9:0] LEDR;

    wire [3:0] A, B;
    reg [3:0] C;

    assign A = SW[3:0];
    assign B = SW[7:4];
    assign LEDR[3:0] = C;

    always @(posedge(~KEY[0]))
        C <= A & B;
endmodule

算术逻辑单元

  • 算术逻辑单元(Arithmetic Logical Unit,ALU)
    • 使用NAND,实现RS锁存器(RS Latch)
      • RS锁存器是一种基于NAND的存储器。类似的还有D触发器(D Flip Flop),它在信号边沿触发数据改变,通常用于实现寄存器。关于基于NAND的存储器,可参见SSD架构和PCIe接口
      • w0,x –> 按下KEY[1] –> 0 –> 按下KEY[0] –> 1
      • 我们先按下KEY[1],再按下KEY[0],提供一个时钟信号clk的上升沿
    • CPU的算术运算、逻辑运算是在ALU中实现的
      • ALU使用一个译码器,将4位操作码opcode译为16种情形
      • ALU的第一个操作数为6位寄存器R,第二个操作数为6位op2,结果存入6位寄存器R
    • 这里,opcode和ARM指令集一致。因此,它可以作为实现ARM CPU的基础
  • 有了基于ALU的CPU,以及七段数码管的显示器,我们已经可以实现一个口袋计算器。关于口袋计算器,可参见液晶显示器的发展历程
// ----- Arithmetic Logical Unit (ALU) -----
    * TopLevel()
        * ALU()

// (SW, KEY) -> (LEDR)
module TopLevel(SW, KEY, LEDR);
    input [9:0] SW;
    input [1:0] KEY;
    output [9:0] LEDR;

    // RS latch as clock signal
    wire w0, w1;
    nand (w0, KEY[0], w1);
    nand (w1, KEY[1], w0);

    ALU (SW[9:6], SW[5:0], w0, LEDR[5:0]);
endmodule

// (opcode, op2, clk) -> (result)
module ALU(opcode, op2, clk, result);
    input [3:0] opcode;
    input [5:0] op2;
    input clk;
    output [5:0] result;

    reg [5:0] R;

    assign result = R;

    always @(posedge(clk))
        case (opcode)
            0: R <= R & op2;  // AND
            1: R <= R ^ op2;  // EOR (Exclusive OR)
            2: R <= R - op2;  // SUB
            3: R <= op2 - R;  // RSB (Reverse SUB)
            4: R <= R + op2;  // ADD
            12: R <= R | op2;  // ORR (Inclusive OR)
            13: R <= op2;  //  MOV
            14: R <= R & ~op2;  // BIC (Bit Clear)
            15: R <= ~op2;  // MVN (Move NOT)
            default: R <= 0;  // None of above
        endcase
endmodule