[Embedded System Lab] Chapter 2 - Combinational & Sequential Logic

"Combinational & Sequential Logic"

[Objective]

Floating-Point Multiplier를 구현하여 Combinational Logic을 이해하고, 7-Segment를 구현하여 Board를 통해 결과를 확인하며 Sequential Logic을 이해하기

[Theory]

▣ Blocking vs Non-Blocking
· Blocking(=) : 순차적으로 실행되는 구문으로, 동시에 여러 변수에 값을 할당 불가
· Non-Blocking(<=) : 같은 블록 안에 있는 모든 Non-Blocking 구문이 동시에 실행되는 구문으로, 한 번에 여러 변수에 값을 할당 가능

▣ Combinational Logic
· 출력이 오로지 현재의 입력에 의해 결정되는 회로로 과거의 입력은 출력에 영향 X
→ MUX, DEMUX, Encode, Decoder 등에 사용

<MUX(Multiplexer)>
· 제어 신호를 바탕으로 여러 개의 입력 신호 중 하나를 골라 출력하며, 일반적으로 2n개의 입력 신호와 n-bit 제어 신호로 구성

<DEMUX(Demultiplexer)>
· 제어 신호를 바탕으로 입력 신호를 여러 개의 출력 채널 중 하나에 출력하며, 일반적으로 2n개의 출력 채널과 n-bit 제어 신호로 구성

<Decoder Truth Table>
· 입력에 따라 미리 정의한 값을 출력하며, n-bit의 입력 신호를 최대 2n개의 서로 다른 값으로 출력 가능

<Encoder Truth Table>
· 입력에 따라 미리 정의한 값을 출력하지만, Decoder와 반대로, 최대 2n개의 서로 다른 입력 신호를 n-bit 출력으로 변환

▣ Sequential Logic
· 출력이 현재의 입력과 과거의 입력들에 의해 결정되는 회로로 D-FF, Shift Register, Finite State Machine 등에 사용

<Edge-Triggered D Flip-Flop>
· 1-bit의 데이터를 저장하는 회로로 Clock의 Positive/Negative Edge마다 입력 D를 출력 Q로 전달

<Shift Register>
· n-bit의 데이터를 저장하는 회로로 n개의 D-FF으로 구성되며, Clock의 Edge마다 각 D-FF에 저장된 데이터가 이웃한 D-FF으로 전달

▣ Floating-Point 32 Format
· MSB는 부호, 8-bit의 Exponent, 23-bit의 Mantissa로 구성 → (-1)Sign x 2Exponent - 127 x (1.Mantissa)2

Ex. 0_10000001_01110000000000000000000 = (-1)0 x 2129 - 127 x (1.0111)2 = 1 x 22 x 1.4375 = 5.75

▣ 7-Segment
· Common-Anode Type은 Pin에 0 할당 시 LED On / Common-Cathode Type은 Pin에 1 할당 시 LED On

[Code Description]

▣ Floating-Point Multiplier(Combinational Logic)
> fp32_multiplier.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
`timescale 1ns / 1ps
module fp32_multiplier(
    input [31:0] a,                                                         // 32-bit 입력
    input [31:0] b,                                                         // 32-bit 입력
    output reg [31:0] result                                                // 32-bit 결과
);
    // Sign ([MSB])
    wire a_sign, b_sign;                                                    // 1-bit 변수
    assign a_sign = a[31];                                                  // a의 MSB 할당
    assign b_sign = b[31];                                                  // b의 MSB 할당
    
    // Exponent ([30:23])
    wire [7:0] a_exponent, b_exponent;                                      // 8-bit 변수
    assign a_exponent = a[30:23- 7'b1111111;                              // (Exponent - 127) 저장
    assign b_exponent = b[30:23] - 7'b1111111;                              // (Exponent - 127) 저장
    
    // Mantissa ([22:0])
    wire [23:0] a_mantissa, b_mantissa;                                     // 24-bit 변수
    wire [47:0] mul_mantissa;                                               // 24-bit x 24bit = 48-bit 변수
    assign a_mantissa = {1'b1, a[22:0]};                                    // 정수부 1을 추가해서 24-bit로 저장
    assign b_mantissa = {1'b1, b[22:0]};                                    // 정수부 1을 추가해서 24-bit로 저장
    assign mul_mantissa = a_mantissa * b_mantissa;                          // a_mantissa x b_mantissa 연산
    
    always @ (*begin
        if (a == 0 || b == 0begin                                         // a 또는 b가 0인 경우
            result[31= a_sign ^ b_sign;                                   // Sign은 별도 계산
            result[30:0= 0;                                               // 곱셈 결과 = 0
        end else if (mul_mantissa[47== 1begin                           // Mantissa 곱셈 결과 정수부가 2 이상인 경우
            result[31= a_sign ^ b_sign;                                   // Sign 계산
            result[30:23= a_exponent + b_exponent + 7'b1111111 + 1'b1;    // Mantissa 곱셈 결과 정수부가 2 이상이므로  Exponent + 1
            result[22:0= mul_mantissa[46:24];                             // Mantissa 곱셈 결과 정수부가 (10) 이상이므로 [46:24] & 하위 bit 버림
        end else begin                                                      // Mantissa 곱셈 결과 정수부가 2 미만인 경우 Exponent 변화 X
            result[31= a_sign ^ b_sign;                                   // Sign 계산
            result[30:23= a_exponent + b_exponent + 7'b1111111;           // Mantissa 곱셈 결과 정수부가 2 미만이므로 Exponent + 1 X
            result[22:0] = mul_mantissa[45:23];                             // Mantissa 곱셈 결과 정수부가 (10) 미만이므로 [45:23] & 하위 bit 버림
        end
    end
endmodule
cs

· Sign : a, b의 MSB는 부호에 해당하며, XOR 연산으로 result의 부호를 계산
· Exponent : a, b의 Exponent - 127을 하여 실제 지수 값을 구한 후, 둘을 더하여 곱셈 연산
    → result는 다시 형식을 맞춰줘야 하므로 + 127
· Mantissa : 생략된 정수부 1을 포함한 후, 곱셈 진행 (24-bit x 24-bit이므로 48-bit 변수에 저장 후 상위 23-bit만 사용)

※ Logic Flow
① a = 0 또는 b = 0인 경우 곱셈 결과가 0이므로, result = 0을 저장 (단, result의 부호는 별도 계산)
② Mantissa 곱셈 결과 정수부가 2 이상인 경우 정수부가 (10) 이상이 되므로 Exponent + 127 + 1 & Mantissa는 [46:24] 사용
③ Mantissa 곱셈 결과 정수부가 2 미만인 경우 정수부가 (10) 미만이 되므로 Exponent + 127 & Mantissa는 [45:23] 사용

> tb_fp32_multiplier.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
`timescale 1ns/1ps
module tb_fp32_multiplier;
    reg [31:0] a, b;                                            // 32-bit 입력 a, b
    wire [31:0] result;                                         // 32-bit 결과
 
    // Instantiation을 통한 Module Port 연결
    fp32_multiplier fp32_multiplier_inst (
        .a(a),                                                  // a 연결
        .b(b),                                                  // b 연결
        .result(result)                                         // result 연결
    );
    
    // Real(64-bit 실수) Type을 받아 32-bit 비트열로 변환
    function automatic logic [31:0] real_to_f32bits(input real r);
        shortreal sr;                                           // shortreal = 32-bit 실수를 의미
        begin
            sr = r;                                             // 64-bit 실수를 32-bit로 정밀도 조절
            real_to_f32bits = $shortrealtobits(sr);             // 실수 값을 그대로 32-bit 이진수로 변환
        end
    endfunction
 
    // 32-bit 비트열을 다시 사람이 읽기 편한 실수 형태로 변환
    function automatic shortreal f32bits_to_shortreal(input logic [31:0] bits);
        begin
            f32bits_to_shortreal = $bitstoshortreal(bits);       // 이진수를 실수값으로 해석
        end
    endfunction
 
    // 테스트 수행
    task automatic run_case(input int id, input real ra, input real rb);
        logic [31:0] aa, bb, exp_bits;
        shortreal sa, sb, sy;
        begin
            // 입력받은 실수 ra, rb를 32-bit 비트열로 (aa, bb)로 변환
            aa = real_to_f32bits(ra);
            bb = real_to_f32bits(rb);
 
            // "fp32_multiplier" Module의 입력 Port에 값을 할당
            a = aa;
            b = bb;
            #1;                                                 // 연산 대기
 
            // SW로 정답 계산 (검증용)
            sa = f32bits_to_shortreal(aa);
            sb = f32bits_to_shortreal(bb);
            sy = sa * sb;                                       // SW로 계산
            exp_bits = $shortrealtobits(sy);                    // 정답을 비트열로 변환
 
            // 결과 비교 (HW 결과 got vs SW 결과 expected)
            if (result !== exp_bits) begin                      // 결과가 다르면 FAIL 출력
                $display("FAIL[%0d]", id);
                $display("  ra=%f rb=%f", ra, rb);
                $display("  a_bits=%h (a=%f)", aa, sa);
                $display("  b_bits=%h (b=%f)", bb, sb);
                $display("  got=%h (=%f) expected=%h (=%f)", result, f32bits_to_shortreal(result), exp_bits, sy);
                $stop;
            end else begin                                      // 결과가 같으면 PASS 출력
                $display("PASS[%0d] ra=%f rb=%f  => result=%h (=%f)", id, ra, rb, result, f32bits_to_shortreal(result));
            end
        end
    endtask
 
    // 다양한 입력 조합으로 Test 수행
    initial begin
        $display("---- FP32 MUL (auto real->FP32 via shortreal) ----");
        run_case(1,  1.5,    2.0);
        run_case(2-1.5,    2.0);
        run_case(3,  0.5,   -0.5);
        run_case(4,  1.25,   1.5);
        run_case(5-2.5,   -4.0);
        run_case(6,  6.0,    0.25);
        run_case(7,  3.0,    3.0);
        run_case(8,  0.75,   8.0);
        run_case(9,  1.3,    4.0);
        run_case(100.0,   -7.25);
        $display("---- ALL PASSED ----");
        $finish;
    end
endmodule
cs

· Module을 통한 HW 계산과 SW 계산 결과를 비교
· System Verilog이므로 Properties에서 Type을 Verilog → System Verilog로 변경

▣ 7-Segment(Sequential Logic)
> top.xdc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
set_property IOSTANDARD "LVCMOS33" [get_ports "clk_in"
set_property PACKAGE_PIN "M19" [get_ports "clk_in"]
 
set_property IOSTANDARD "LVCMOS33" [get_ports "resetn"
set_property PACKAGE_PIN "Y18" [get_ports "resetn"]
 
set_property IOSTANDARD "LVCMOS33" [get_ports "segout[*]"
set_property PACKAGE_PIN "Y6" [get_ports "segout[7]"]
set_property PACKAGE_PIN "Y5" [get_ports "segout[6]"]
set_property PACKAGE_PIN "AA7" [get_ports "segout[5]"]
set_property PACKAGE_PIN "AA6" [get_ports "segout[4]"]
set_property PACKAGE_PIN "AB2" [get_ports "segout[3]"]
set_property PACKAGE_PIN "AB1" [get_ports "segout[2]"]
set_property PACKAGE_PIN "AB5" [get_ports "segout[1]"]
set_property PACKAGE_PIN "AB4" [get_ports "segout[0]"]
 
set_property IOSTANDARD "LVCMOS33" [get_ports "segcom[*]"
set_property PACKAGE_PIN "AB7" [get_ports "segcom[7]"]
set_property PACKAGE_PIN "AB6" [get_ports "segcom[6]"]
set_property PACKAGE_PIN "Y4" [get_ports "segcom[5]"]
set_property PACKAGE_PIN "AA4" [get_ports "segcom[4]"]
set_property PACKAGE_PIN "R6" [get_ports "segcom[3]"]
set_property PACKAGE_PIN "T6" [get_ports "segcom[2]"]
set_property PACKAGE_PIN "T4" [get_ports "segcom[1]"]
set_property PACKAGE_PIN "U4" [get_ports "segcom[0]"]
cs

· Parts에서 우리가 사용하는 Board인 "xc7z020clg484-1" 추가
· Board를 사용하여 7-Segment를 확인하기 위해 "Constraints Source"로 위 Code를 사용

> seven_segment.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
`timescale 1ns / 1ps
module seven_segment(
    input [3:0] num,                        // 4-bit 10진수 Input
    output reg [7:0] segout                 // 8-bit 2진수 Output
);
 
    // 입력에 해당하는 10진수 숫자를 LED로 표현하기 위해 8-bit 2진수를 할당
    // 8'b{a, b, c, d, e, f, g, dp}
    always @ (*begin
        case (num)                          // 0 = LED On, 1 = LED Off
            4'd0 : segout = 8'b00000011;    // Display 0
            4'd1 : segout = 8'b10011111;    // Display 1
            4'd2 : segout = 8'b00100101;    // Display 2
            4'd3 : segout = 8'b00001101;    // Display 3
            4'd4 : segout = 8'b10011001;    // Display 4
            4'd5 : segout = 8'b01001001;    // Display 5
            4'd6 : segout = 8'b01000001;    // Display 6
            4'd7 : segout = 8'b00011011;    // Display 7
            4'd8 : segout = 8'b00000001;    // Display 8
            default : segout = 8'b11111111; // All LED Off
        endcase
    end
endmodule
cs

· "num" 변수로 10진수를 받은 후, LED로 해당 숫자를 표현 할 수 있도록 case 구문을 사용하여 8-bit 2진수로 출력

** LED 순서 : 8'b{a, b, c, d, e, f, g, dp}
** Common-Anode이므로 Pin이 0일 때 LED On

> seven_segment_display.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
`timescale 1ns / 1ps
module seven_segment_display(
    input clk_in,                            // clock 신호
    input resetn,                            // resetn = 0일 때 reset되는 신호
    output reg  [7:0] segcom,               // 숫자 8개의 위치
    output [7:0] segout                     // LED를 통한 10진수 표현
);
 
    reg [15:0] clkdiv;                      // 16-bit Clock Divider 변수
    reg [2:0] digit_sel;                    // 3-bit 위치 설정 변수
    reg [3:0] num;                          // 4-bit 10진수 변수
 
    // Clock Divider용 Counter
    always @ (posedge clk_in or negedge resetn) begin
        if (!resetn)                        // resetn = 0인 경우
            clkdiv <= 16'd0;                // clkdiv = 0으로 초기화
        else                                // resetn = 1인 경우
            clkdiv <= clkdiv + 16'd1;       // clkdiv를 구문 실행마다 1씩 증가
    end
 
    // 위치 선택용 Counter
    always @ (posedge clk_in or negedge resetn) begin
        if (!resetn)                        // resetn = 0인 경우
            digit_sel <= 3'd0;              // 위치 선택 변수를 0으로 초기화
        else if (clkdiv == 16'd0)           // resetn = 1, clkdiv = 0인 경우 (clkdiv가 한 바퀴 돌아서 0)
            digit_sel <= digit_sel + 3'd1;  // 위치 선택 변수를 1씩 증가
    end
    // LED On 자리와 숫자 선택
    always @ (*) begin
        case (digit_sel)
            3'd0: begin                     // 위치 선택 변수 = 0인 경우
                segcom      = 8'b00000001;  // 0번 위치만 On (아래 오른쪽 1번째)
                num = 4'd8;                 // 숫자 8 출력
            end
            3'd1: begin                     // 위치 선택 변수 = 1인 경우
                segcom      = 8'b00000010;  // 1번 위치만 On (아래 오른쪽 2번째)
                num = 4'd6;                 // 숫자 6 출력
            end
            3'd2: begin                     // 위치 선택 변수 = 2인 경우
                segcom      = 8'b00000100;  // 2번 위치만 On (아래 오른쪽 3번째)
                num = 4'd4;                 // 숫자 4 출력
            end
            3'd3: begin                     // 위치 선택 변수 = 3인 경우
                segcom      = 8'b00001000;  // 3번 위치만 On (아래 오른쪽 4번째 = 아래 왼쪽 1번째)
                num = 4'd2;                 // 숫자 2 출력
            end
            3'd4: begin                     // 위치 선택 변수 = 4인 경우
                segcom      = 8'b00010000;  // 4번 위치만 On (위 오른쪽 1번째)
                num = 4'd7;                 // 숫자 7 출력
            end
            3'd5: begin                     // 위치 선택 변수 = 5인 경우
                segcom      = 8'b00100000;  // 5번 위치만 On (위 오른쪽 2번째)
                num = 4'd5;                 // 숫자 5 출력
            end
            3'd6: begin                     // 위치 선택 변수 = 6인 경우
                segcom      = 8'b01000000;  // 6번 위치만 On (위 오른쪽 3번째)
                num = 4'd3;                 // 숫자 3 출력
            end
            3'd7: begin                     // 위치 선택 변수 = 7인 경우
                segcom      = 8'b10000000;  // 7번 위치만 On (위 오른쪽 4번째 = 위 왼쪽 1번째)
                num = 4'd1;                 // 숫자 1 출력
            end
            default: begin                  // 아무 위치도 해당하지 않는 경우
                segcom      = 8'b11111111;  // 모든 위치 On
                num = 4'd0;                 // 숫자 0 출력
            end
        endcase
    end
    // Instantiation을 통한 Module Port 연결
    seven_segment seven_segment_inst (
        .num(num),                          // num 연결
        .segout(segout)                     // segout 연결
    );
endmodule
cs

** xdc에 작성된 Port로 작성해야 Board와 연결 가능 (clk_in, resetn, …)

· "resetn"이므로 Low(0)일 때 초기화 실행
    → 평상 시 Button을 누르지 않으면 Board 내부의 Pull-Up 회로 등에 의해 resetn Pin에 지속적으로 전압이 공급되어 High 상태
    → 초기화를 위해 Button을 누르면 접지와 연결되어 Low 상태

· "clkdiv"를 사용하여 LED On/Off 시간 조절 (16-bit이므로 0~65,536까지 Counter 증가 후, 초과 시 다시 0부터 시작)
    → Board의 Clock을 그대로 사용 시 구동 시간이 지나치게 짧아 LED가 밝아지기도 전에 다음 자리로 넘어가 숫자가 제대로 표시 X
    → 0부터 (최대 표현 가능 수)까지 Count 후, Overflow 시 0으로 돌아가는 성질을 이용하여 구동 시간 조절

· "digit_sel"은 clkdiv가 한 바퀴 돌 때마다 자리를 한 칸씩 움직이며, 위치 Index는 아래 그림에서 확인 가능

· 위치에 따라 출력하는 10진수를 지정하여 "seven_segment.v"에서 이 10진수를 받아 LED 위치를 표현하는 8-bit 이진수를 출력

[Result]

▣ Floating-Point Multiplier(Combinational Logic)

▣ 7-Segment(Sequential Logic)

[Discussion]

▣ Floating-Point Multiplier(Combinational Logic)
· Floating-Point 32 곱셈 연산에서 Mantissa 곱셈 결과 정수부가 2 초과 시 Exponent를 +1
· 0을 곱하더라도 부호를 나타내는 MSB는 별도로 계산

▣ 7-Segment(Sequential Logic)
코드를 일부 작성 후 먼저 실행하여 다음을 확인
· 8-bit segcom의 각 bit가 어느 위치를 나타내는지 파악 → 오른쪽 아래 = Index 0
· 8-bit segout의 각 bit가 어느 LED를 나타내는지 파악 → 8'b{a, b, c, d, e, f, g, dp}
· 8-bit segout이 Common-Anode 방식인지, Common-Cathode 방식인지 확인 → 0일 때 LED On
· 20-bit clkdiv 사용 시 위치 변환 속도가 느려 잔상 효과 X → 16-bit clkdiv 사용 시 위치 변환 속도가 적절하여 잔상 효과 O
Ex. Board Clock이 50MHz = Clock이 한 번 진동하는 데 걸리는 시간은 1/50,000,000 = 20ns
    → 20-bit clkdiv인 경우 다음 위치로 넘어갈 때까지 걸리는 시간은 20ns x 1,048,576 ≒ 21ms

[Reference]

· Verilog_Basics_2 (Embedded System Lab : Chapter2) - William J. Song

댓글

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)