NJU数字电路实验

18 October 2026
8 mins
Contents目录

这个是一生一芯E4部分的数字电路实验,记录一下我的完成过程。

Decoder and Encoder

译码器是将n个状态翻译成$2^n$个信号

pasted-image-1792337661436.webp

编码器是将$2^n$个信号编码成n个状态

pasted-image-1792337675291.webp

通过这两个器件我们就可以把信息/状态编码成信号在电路中传输(我的世界中的编码全物品实际上就是将物品信息转换成信号)

我们可以用Verilog来模拟器件的实现:

module decode24(x,en,y);
  input  [1:0] x;
  input  en;
  output reg [3:0]y;

  always @(x or en)
    if (en)
    begin
      case (x)
            2'd0 : y = 4'b0001;
            2'd1 : y = 4'b0010;
            2'd2 : y = 4'b0100;
            2'd3 : y = 4'b1000;
      endcase
    end
    else  y = 4'b0000;
endmodule
module encode42(x,en,y);
  input  [3:0] x;
  input  en;
  output reg [1:0]y;

  always @(x or en) begin
    if (en) begin
      case (x)
          4'b0001 : y = 2'b00;
          4'b0010 : y = 2'b01;
          4'b0100 : y = 2'b10;
          4'b1000 : y = 2'b11;
          default: y = 2'b00;
      endcase
    end
    else  y = 2'b00;
  end
endmodule

其中编码器中有一种特殊的编码器叫做优先编码器,有的时候我们可能会同时收到多个输入信号,普通的编码器是接受一个信号输入然后编码成一个信息。但是优先编码器能同时接受多个信号,然后按优先级选择一个有效信号,所以我们称之为“优先”编码器,可能概念上很难解释,但是看到具体的实现就很好理解了:

module encode42(x,en,y);
  input  [3:0] x;
  input  en;
  output reg [1:0]y;
  integer i;
  always @(x or en) begin
    if (en) begin
      y = 0;
      for( i = 0; i <= 3; i = i+1)
          if(x[i] == 1)  y = i[1:0];
    end
    else  y = 0;
  end
endmodule

lab2

要求如下:

功能描述

查找8-3优先编码器相关原理和实现方法,实现一个8-3编码器,完成8-3编码器的设计、功能仿真和硬件实现。

输入一个8位二进制数,对此8位二进制数进行高位优先编码成一个3位二进制值,并根据是否有输入增加一位输入指示位,即8个输入全0时指示位为0,有任何一个输入为1时指示位为1。编码器的使能端可选实现。将此编码结果及指示位以二进制形式显示在四个发光二极管LED上。再将此结果跟据七段数码管的显示进行译码,将二进制的优先编码结果以十进制的形式显示在数码管上。

输入输出建议

输入可以使用拨动开关SW7-SW0。使能端可以用SW8。输出为LED2-0,输出指示可以是LED4,数码管输出为HEX0。

例:我们从SW7—SW0输入00001110,因为我们设计的是一个高位优先的优先编码器,从左(高位)开始,第一位为1的是第3号位,那么优先编码器的编码二进制结果就为011,将这个值显示在发光二极管上,并且指示位也同时置为1。再对这个数值跟据七段数码管的显示进行译码,此时应显示为 3 ,用HEX0显示,所以HEX0[6:0]应该译码为0110000(注意高低位顺序),那么在七段数码管上就会显示 3 这个字符。

首先我们可以定义出我们需要用的元件,然后在top.v中实例化:

module encode83(x,en,y);
  input  [7:0] x;
  input  en;
  output reg [2:0]y;
  
  integer i;
  always @(x or en) begin
    if (en) begin
      y = 0;
      for( i = 0; i <= 7; i = i+1)
          if(x[i] == 1)  y = i[2:0];
    end
    else  y = 0;
  end
endmodule

module bcd7seg(
  input  [3:0] in,
  output reg [6:0] out
);

  //      A B C D E F G
  // reg  0 1 2 3 4 5 6 
  always @(*) begin
    case(in)
      4'd0: out = 7'b0000001; 
      4'd1: out = 7'b1001111; 
      4'd2: out = 7'b0010010; 
      4'd3: out = 7'b0000110; 
      4'd4: out = 7'b1001100; 
      4'd5: out = 7'b0100100; 
      4'd6: out = 7'b0100000; 
      4'd7: out = 7'b0001111; 
      4'd8: out = 7'b0000000; 
      4'd9: out = 7'b0000100; 
      default: out = 7'b1111111; 
    endcase
  end

endmodule

module top(
    input[8:0] sw,
    output [6:0] seg,
    output [2:0] led,
    output in
);
    
    wire[2:0] encout;

    encode83 u_encode83(
        .x(sw[7:0]),
        .en(sw[8]),
        .y(encout)
    );

    assign led = encout[2:0];
    
    bcd7seg u_bcd7seg(
        .in({1'b0,encout}),
        .out(seg)
    );

    assign in = sw[8] && (|sw[7:0]);
endmodule

然后用nvboard_bind_pin()接口绑定信号和引脚即可正常使用:

pasted-image-1792550700084.webp

ADD and ALU

在Verilog语言中,可以使用算术赋值语句和向量来执行这种算术运算。如果我们定义如下向量:

input  [n-1:0]  in_x, in_y;
output [n-1:0]  out_s;
out_s = in_x + in_y;

就可以实现n位加法器,其他运算符号也是如此,可以生成对应的运算电路。

但是这个电路中并不会生成计算过程中的信号,例如加法电路不会生成对应的溢出信号和输出信号,所以我们需要显式的完成这些信号的计算和提取:

module adder #(
    parameter N = 8
)(
    input  [N-1:0] in_x,
    input  [N-1:0] in_y,
    output [N-1:0] out,
    
    output         carry, 
    output         overflow,  
    output         zero      
);

    assign {carry, out} = in_x + in_y;
    assign overflow = (in_x[N-1] == in_y[N-1]) && (out[N-1] != in_x[N-1]);
    assign zero = ~|out; 
endmodule

我们可以根据补码原理,在加法器的基础上实现减法功能,具体的电路如图:

pasted-image-1792553787709.webp

我们可以实现:

module addsuber #(
    parameter N = 32
)(
    input  SubAdd,			//用于加减法控制开关 Sub 1 / Add 0
    input  [N-1:0] in_x,
    input  [N-1:0] in_y,
    output [N-1:0] out,
    
    output         carry, 
    output         overflow,  
    output         zero      
);
    
    wire [N-1:0] xortmp;
    assign xortmp = in_y ^ {N{SubAdd}};

    assign {carry, out} = SubAdd + in_x + xortmp;
    assign overflow = (in_x[N-1] == xortmp[N-1]) && (out[N-1] != in_x[N-1]);
    assign zero = ~|out; 
endmodule

在此基础之上,我们可以给我们的运算器加入更多的基本运算电路,从而综合成CPU中的运算模块ALU。

我们这里按照RISC-V中基础指令集RV32I的ALU的设计要求来进行介绍。

RISC-V基础指令集RV32I只支持32位整型数值的操作。操作数可以是带符号补码整数或无符号数。ALU不需要完成乘除法,不需要进行溢出判断,相关操作由软件来完成。RV32I的ALU需要完成以下操作:

lab3

上板实验: 实现一个带有逻辑运算的简单ALU

设计一个能实现如下功能的4位带符号位的 补码 ALU:

功能选择功能操作
000加法A+B
001减法A-B
010取反not A
011与A and B
100或A or B
101异或A xor B
110比较大小If A<B then out=1; else out=0;
111判断相等If A==B then out=1; else out=0;

ALU进行加减运算时,需要能够判断结果是否为0,是否溢出,是否有进位等。这里,输入的操作数A和B都已经是补码。比较大小请按带符号数的方式设置。

执行逻辑操作时不需要考虑overflow和进位。

由于开发板上输入有限,可以使用SW作为数据输入,button作为选择端。

以下是我们ALU的具体实现:

module alu(
    input   [3:0] A,
    input   [3:0] B,
    input   [2:0] mode,
    output reg [3:0] res,
    output reg carry,
    output reg overflow,
    output zero
);

    localparam ADD  =3'b000;
    localparam SUB  =3'b001;
    localparam NOT  =3'b010;
    localparam AND  =3'b011;
    localparam OR   =3'b100;
    localparam XOR  =3'b101;
    localparam LT   =3'b110;
    localparam EQUL =3'b111;

    wire isSub = (mode == 3'b001);
    wire [3:0] Bmux = isSub ? ~B : B;
    
    always @(*) begin
        {carry,overflow} = 0;
        case(mode)
            ADD,SUB: begin 
                {carry,res} = A + Bmux + isSub;
                overflow = (A[3]==Bmux[3])&&(A[3]!=res[3]);
            end
            NOT: res = ~A;
            AND: res = A & B;
            OR:  res = A | B;
            XOR: res = A ^ B;
            LT:  begin 
                if($signed(A) < $signed(B))
                    res = 4'b1;
                else
                    res = 4'b0;
            end
            EQUL:begin 
                if(A==B)
                    res = 4'b0001;
                else
                    res = 4'b0000;
            end
            default: res = 4'b0;
        endcase 
    end
    assign zero = ~|(res);
endmodule

然后我们用top.v封装起来,再在main.cpp中绑定信号引脚就可以正常使用了(这里就不细说信号绑定了),下图是一个减法示例:

pasted-image-1792592031349.webp

Shift and Barrel Register

我们已经实现了编解码器,现在我们在此基础之上又进一步实现了ALU。现在我们需要加入移位的组件,来完善我们的ALU功能。

为了实现数据的移位功能,我们有两种实现方式,一是基于时序电路和D触发器实现的移位寄存器,二是基于组合逻辑实现的桶型寄存器。

移位寄存器

移位寄存器在时钟的触发沿,根据其控制信号,将存储在其中的数据向某个方向移动一位。移位寄存器也是数字系统的常用器件。

pasted-image-1792723087509.webp

随着时序,D触发器每激活一次,寄存器的值就会输出当前值,然后输入前一个的信号。我们可以在此基础上实现算数移位和逻辑移位。

以右移为例,用verilog实现就是:

Q <= {Q[0],Q[7:1]}; //循环右移
Q <= {Q[7],Q[7:1]}; //算术右移

对于移位寄存器我们通常有以下功能:

控制位工作方式
0 0 0清0
0 0 1置数
0 1 0逻辑右移
0 1 1逻辑左移
1 0 0算术右移
1 0 1左端串行输入1位值,并行输出8位值
1 1 0循环右移
1 1 1循环左移

我们可以用Verilog实现:

module shift_register (
    input  wire       clk,    // 时钟信号
    input  wire       rst_n,  // 异步复位信号(低电平有效)
    input  wire       load,   // 并行加载使能(1:加载 din,0:执行移位)
    input  wire       dir,    // 移位方向(1:左移,0:右移)
    input  wire       sin,    // 串行输入数据(用于移位时填充空位)
    input  wire [7:0] din,    // 并行输入数据
    output reg  [7:0] dout    // 寄存器输出
);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            dout <= 8'b0; 
        end else if (load) begin
            dout <= din;  
        end else begin
            if (dir) begin
                dout <= {dout[6:0], sin};
            end else begin
                dout <= {sin, dout[7:1]};
            end
        end
    end
endmodule

桶型移位器

移位寄存器虽然能够按照我们的设想工作,但是一个8位的移位寄存器需要我们等待8个时间周期来获取输入,在32位64位的情况下效果并不理想。所以我们可以引入一种组合逻辑实现的桶型寄存器来解决这个问题。

桶形移位器采用组合逻辑的方式来实现同时移动多位,在效率上优势极大。因此,桶形移位器常常被用在ALU中来实现移位。 图 Fig. 50 为8位桶形移位器的输入输出引脚图。 其中输入数据din和输出数据dout均为8位,移位位数shamt为3位。 选择端L/R表示左移和右移,置为1为左移,置为0为右移。 选择端A/L为算术逻辑选择,置为1为算术移位,置为0为逻辑移位。

pasted-image-1792737602007.webp

我们可以用verilog来实现他:

module barrel_shifter_behavioral (
    input  wire [7:0] din,
    input  wire [2:0] shamt,  // 移位量 0~7
    input  wire       dir,    // 1: 左移 (Left), 0: 右移 (Right)
    input  wire       arith,  // 1: 算术移位 , 0: 逻辑移位 
    output reg  [7:0] dout
);

    always @(*) begin
        if (dir) begin
            dout = din << shamt;
        end else begin
            if (arith) begin
                dout = $signed(din) >>> shamt;
            end else begin
                dout = din >> shamt;
            end
        end
    end

endmodule

lab6

我们可以利用8位移位寄存器来实现一个简单的随机数发生器。经典的LFSR(线性反馈移位寄存器,Linear-feedback shift register)可以使用 $n$ 位移位寄存器生成长度为 $2^n - 1$ 的二进制循环序列。这类序列的片段在表观上是随机的,所以被广泛用于通信中的随机序列生成。例如,在CDMA通信中的长码的长度就是 $2^{42} - 1$ 的伪随机序列。

具体实现时,可以用一个8位右移移位寄存器,从左到右的比特以 $x_7x_6x_5x_4x_3x_2x_1x_0$ 表示。每个时钟周期右移一位,$x_0$ 被移出,最左边移入的位按照上一周期的值计算 $^1$:

$$x_8 = x_4 \oplus x_3 \oplus x_2 \oplus x_0$$

例如,初始二进制值为00000001时,移位寄存器的状态将按 $00000001 \rightarrow 10000000 \rightarrow 01000000 \rightarrow 00100000 \rightarrow 00010000 \rightarrow 10001000 \dots$ 变化。该序列的周期为255。当然,当初始值为全零时,系统将一直停留在全零状态,所以需要对全零状态进行特殊处理。

请实现一个8位的周期为255的伪随机序列,以按钮为时钟信号,并请将8位二进制数以十六进制显示在数码管上,在DE10-Standard开发板上观察生成的随机数序列。

FSM and Keyboard input


← 一生一芯E1