ALU 입력 피연산자 선택기 설계
중앙 처리 장치(CPU)의 핵심 구성 요소인 산술 논리 장치(ALU)는 산술 및 논리 연산을 담당합니다. 효율적인 ALU를 설계하려면 먼저 연산에 사용될 피연산자(operand)를 적절히 선택하는 메커니즘이 필요하며, 이후 실제 연산을 수행하는 로직이 구현되어야 합니다. 이 글에서는 RISC-V 기반 프로세서의 ALU 유닛 설계에 대해 다루며, 특히 피연산자 선택기와 기본적인 산술 연산(덧셈, 뺄셈)을 수행하는 ALU 모듈의 구현을 살펴봅니다.
ALU는 레지스터, 즉시값(immediate value), 프로그램 카운터(PC) 등 다양한 소스로부터 데이터를 입력받을 수 있습니다. 따라서 ALU의 입력으로 어떤 데이터를 사용할지 결정하는 선택 로직이 필요합니다. 아래 Verilog 코드는 세 가지 주요 시나리오에 따라 ALU의 두 입력 피연산자(alu_operand_a, alu_operand_b)를 선택하는 모듈을 보여줍니다. 현재 프로젝트의 RISC-V 구현은 특정 명령어 세트(예: U-타입 명령어 중 lui만 구현)에 최적화되어 있으므로, PC + 4와 같은 특정 상황을 제외하고는 레지스터 또는 즉시값을 주된 입력으로 사용합니다.
module OperandSelector (
input wire [`CONTROL_SIG_WIDTH-1:0] select_control, // 피연산자 선택 제어 신호
input wire [`DATA_PATH_WIDTH-1:0] reg_data_a, // 레지스터 A의 값
input wire [`DATA_PATH_WIDTH-1:0] reg_data_b, // 레지스터 B의 값
input wire [`DATA_PATH_WIDTH-1:0] immediate_val, // 즉시값
input wire [`DATA_PATH_WIDTH-1:0] current_pc, // 현재 프로그램 카운터(PC) 값
output reg [`DATA_PATH_WIDTH-1:0] alu_operand_a, // ALU 입력 1
output reg [`DATA_PATH_WIDTH-1:0] alu_operand_b // ALU 입력 2
);
// 데이터 경로의 너비 (예: 32비트)
localparam DATA_PATH_WIDTH = 32;
// 제어 신호의 너비 (예: 2비트)
localparam CONTROL_SIG_WIDTH = 2;
// 피연자 선택 모드 정의
localparam SRC_SELECT_REG_REG = 2'b00; // ALU 입력: 레지스터 A, 레지스터 B
localparam SRC_SELECT_REG_IMM = 2'b01; // ALU 입력: 레지스터 A, 즉시값
localparam SRC_SELECT_PC_ADD4 = 2'b10; // ALU 입력: 4, 현재 PC (PC + 4 계산용)
always @(*) begin
// 기본값 설정: 레지스터 A와 B를 사용
alu_operand_a = reg_data_a;
alu_operand_b = reg_data_b;
case (select_control)
SRC_SELECT_REG_REG: begin
// 레지스터 A와 B를 그대로 사용 (기본값으로 이미 설정됨)
end
SRC_SELECT_REG_IMM: begin
// 레지스터 A는 유지하고, 레지스터 B 대신 즉시값 사용
alu_operand_b = immediate_val;
end
SRC_SELECT_PC_ADD4: begin
// PC + 4 계산을 위해 첫 번째 피연산자를 4로, 두 번째 피연산자를 현재 PC로 설정
alu_operand_a = DATA_PATH_WIDTH'd4;
alu_operand_b = current_pc;
end
default: begin
// 정의되지 않은 제어 신호의 경우 0으로 초기화
alu_operand_a = DATA_PATH_WIDTH'b0;
alu_operand_b = DATA_PATH_WIDTH'b0;
end
endcase
end
endmodule
기본 산술 연산기(ALU) 모듈 구현
피연산자가 선택되면, 다음 단계는 실제 산술 및 논리 연산을 수행하는 ALU 모듈을 구현하는 것입니다. 현재 설계는 간단한 덧셈과 뺄셈 연산만을 지원합니다. 이 모듈은 연산 결과와 함께 결과가 0인지 여부를 나타내는 result_is_zero 플래그를 출력합니다. 오버플로우나 부호 있는/없는 연산에 대한 복잡한 처리는 이 초기 설계 단계에서는 고려하지 않습니다.
module BasicALU (
input wire [`OP_CODE_WIDTH-1:0] operation_code, // ALU 연산 지정 코드
input wire [`DATA_PATH_WIDTH-1:0] input_val_x, // 첫 번째 피연산자
input wire [`DATA_PATH_WIDTH-1:0] input_val_y, // 두 번째 피연산자
output reg result_is_zero, // 연산 결과가 0이면 1, 아니면 0
output reg [`DATA_PATH_WIDTH-1:0] alu_output_result // ALU 연산 결과
);
// 데이터 경로의 너비 (예: 32비트)
localparam DATA_PATH_WIDTH = 32;
// 연산 코드의 너비 (예: 2비트)
localparam OP_CODE_WIDTH = 2;
// ALU 연산 코드 정의
localparam ALU_OP_ADD = 2'b00; // 덧셈
localparam ALU_OP_SUB = 2'b01; // 뺄셈
always @(*) begin
// 출력 레지스터 초기화
alu_output_result = DATA_PATH_WIDTH'b0;
result_is_zero = 1'b0;
case (operation_code)
ALU_OP_ADD: begin
alu_output_result = input_val_x + input_val_y;
end
ALU_OP_SUB: begin
alu_output_result = input_val_x - input_val_y;
// 뺄셈 결과가 0이면 'result_is_zero' 플래그를 설정
if (alu_output_result == DATA_PATH_WIDTH'b0) begin
result_is_zero = 1'b1;
end
end
default: begin
// 정의되지 않은 연산 코드의 경우 결과 0으로 처리하고 플래그 설정
alu_output_result = DATA_PATH_WIDTH'b0;
result_is_zero = 1'b1;
end
endcase
end
endmodule