[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 등에 사용
· 제어 신호를 바탕으로 여러 개의 입력 신호 중 하나를 골라 출력하며, 일반적으로 2n개의 입력 신호와 n-bit 제어 신호로 구성
· 제어 신호를 바탕으로 입력 신호를 여러 개의 출력 채널 중 하나에 출력하며, 일반적으로 2n개의 출력 채널과 n-bit 제어 신호로 구성
· 입력에 따라 미리 정의한 값을 출력하며, n-bit의 입력 신호를 최대 2n개의 서로 다른 값으로 출력 가능
· 입력에 따라 미리 정의한 값을 출력하지만, Decoder와 반대로, 최대 2n개의 서로 다른 입력 신호를 n-bit 출력으로 변환
▣ Sequential Logic
<Shift Register>
· n-bit의 데이터를 저장하는 회로로 n개의 D-FF으로 구성되며, Clock의 Edge마다 각 D-FF에 저장된 데이터가 이웃한 D-FF으로 전달▣ Floating-Point 32 Format
Ex. 0_10000001_01110000000000000000000 = (-1)0 x 2129 - 127 x (1.0111)2 = 1 x 22 x 1.4375 = 5.75
▣ 7-Segment
[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 == 0) begin // a 또는 b가 0인 경우 result[31] = a_sign ^ b_sign; // Sign은 별도 계산 result[30:0] = 0; // 곱셈 결과 = 0 end else if (mul_mantissa[47] == 1) begin // 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(10, 0.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는 아래 그림에서 확인 가능
[Result]
▣ Floating-Point Multiplier(Combinational Logic)
▣ 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
댓글
댓글 쓰기