2024.10.14 수업날
< cpu의 구조와 동작원리 >
ALU와 ACC의 구조 및 동작 원리
vivado 진행 순서
새로운 project 만들어서 RTL project로 설정한 후, basys3 보드로 설정한다.
Create File에서 ALU, ACC 생성
Add Files에서 아래와 같이 추가
ALU 소스코드
////////////////////////////////////////////////////// 2024.10.14
module ALU(
input clk, reset_p,
input op_add, op_sub, op_mul, op_div, op_and,
input alu_lsb, // 연산 결과의 최하위 비트를 보기 위함
input [3:0] acc_high_data, bus_reg_data,
output [3:0] alu_data,
output carry_flag, zero_flag, sign_flag, c_out);
wire [3:0] sum;
fadd_sub_8bit_dataflow fadd_sub(
.a(acc_high_data), .b(bus_reg_data),
.s(op_sub | op_div), // 나누기 할 때도 뺄셈을 하도록
.sum(sum),
.carry(c_out));
assign alu_data = op_and ? (acc_high_data & bus_reg_data) : sum;
register_Nbit_p # (.N(1)) zero_f(
.d(~(|sum)), // | 앞에 아무것도 없음 -> 4비트를 각각 or 연산 , (!sum)으로 써도 됨
.clk(clk),
.reset_p(reset_p),
.wr_en(op_sub), .rd_en(1), //WR Enable, Read Enable
.q(zero_flag)); // 뺄셈의 결과가 0일 때만 zero_flag가 1
register_Nbit_p # (.N(1)) sign_f( // 빼기 했을 때만->값이 음수가 될때만 sign_flag가 1이 됨
.d(~c_out & (op_sub)),
.clk(clk),
.reset_p(reset_p),
.wr_en(op_sub), .rd_en(1),
.q(sign_flag));
register_Nbit_p # (.N(1)) carry_f(
.d(c_out & (op_add | (op_mul & alu_lsb) | op_div)),
.clk(clk),
.reset_p(reset_p),
.wr_en(1), .rd_en(1),
.q(carry_flag)); // 덧셈, 곱셈 했을 때 올림수 확인
endmodule
시뮬레이션 결과를 확인하기 전에 verilog 수업시간에 만들었던 파일에서 아래와 같이 코드를 추가해준다.
시뮬레이션 결과를 확인하기 위해 clk 10ns, reset 1ns, add 0, op는 다 0으로 설정해서 초기설정을 맞춘다.
아래의 시뮬레이션에는 op_add(덧셈), op_sub(뺄셈) 결과를 확인할 수 있다.
시뮬레이션 결과에서 op_sub(뺄셈)에서의 의미는 다음과 같다.
1. c_out이 1이면 a가 b보다 크거나 a와 b가 같고, c_out이 0이면 a가 b보다 작다.
2. zero_flag, sign_flag(결과값이 음수일때 1)가 한 클록 늦게 1이 되는 이유는 상승 엣지에 맞춰서 결과값이 나타나는 것이기 때문이다.
3. zero_flag, sign_flag는 op_sub(뺄셈)일때만 의미가 유효하다. 때문에 나머지 연산에서는 안봐도 된다.
아래는 op_and를 활성화 시켰을 때의 결과이다.
acc_high_data[3:0], bus_reg_data[3:0] 에서 4,3 입력값을 줬을 때와 4,7 입력값을 줬을 때의 결과이다.
여기서 op_and일 때 zero_flag가 1인 이유는 op_sub에서의 결과값이 0이기 때문에 그 값이 유지되서 계속 1인 것이다. 따라서 zero_flag는 신경 안써도 된다.
다음으로 shift 연산을 확인하기 위해 ACC에서 소스코드를 작성한다.
ACC 소스코드
///////////////////////////////////////////////// 2024.10.14
/////////////////////////////////////// shift 연산은 acc에서 함
module ACC(
input clk, reset_p, acc_high_reset_p, // acc_high_reset_p는 상위 비트만 리셋
input fill_value, rd_en, acc_in_select,
input [1:0] acc_high_select, acc_low_select,
input [3:0] bus_data, alu_data,
output [3:0] high_data2bus, acc_high_data2alu,
output [3:0] low_data2bus, acc_low_data);
wire [3:0] acc_high_in;
assign acc_high_in = acc_in_select ? bus_data : alu_data;
half_acc acc_high( // 상위 4비트
.clk(clk), .reset_p(reset_p | acc_high_reset_p),
.load_msb(fill_value), .load_lsb(acc_low_data[3]),
.rd_en(rd_en),
.s(acc_high_select),
.data_in(acc_high_in),
.data2bus(high_data2bus), .register_data(acc_high_data2alu));
half_acc acc_low( // 하위 4비트
.clk(clk), .reset_p(reset_p),
.load_msb(acc_high_data2alu[0]), .load_lsb(fill_value),
.rd_en(rd_en),
.s(acc_low_select),
.data_in(acc_high_data2alu),
.data2bus(low_data2bus), .register_data(acc_low_data));
endmodule
/////////////////////////////////////////////////////////
module half_acc(
input clk, reset_p,
input load_msb, load_lsb, // msb: 최상위 비트, lsb: 최하위 비트
input rd_en, // 1 줬을 때만 bus로부터 데이터를 받음
input [1:0] s,
input [3:0] data_in,
output [3:0] data2bus, register_data);
parameter IDLE = 2'b00;
parameter SHIFT_RIGHT = 2'b01;
parameter SHIFT_LEFT = 2'b10;
parameter LOAD = 2'b11;
reg [3:0] d;
always @* begin
case(s)
IDLE: d = register_data; // 현재값 유지
SHIFT_RIGHT: d = {load_msb, register_data[3:1]}; // 우shift
SHIFT_LEFT: d = {register_data[2:0], load_lsb}; // 좌shift
LOAD: d = data_in; // bus 데이터를 받으면 데이터 load
endcase
end
register_Nbit_p # (.N(4)) half_acc( // acc에서는 상위 4비트, 하위 4비트를 사용하기 때문
.clk(clk),
.reset_p(reset_p),
.d(d),
.wr_en(1), .rd_en(rd_en),
.register_data(register_data),
.q(data2bus));
endmodule
이제 ACC의 testbench를 위해 simulation sources를 추가로 생성한다.
testbench_acc 소스코드(우 shift, 좌 shift 확인)
//////////////////////////////////////////////// 2024.10.14
module tb_acc();
parameter IDLE = 2'b00;
parameter SHIFT_RIGHT = 2'b01;
parameter SHIFT_LEFT = 2'b10;
parameter LOAD = 2'b11;
parameter DATA_ALU = 0;
parameter DATA_BUS = 1;
// 원래 코드에서의 input -> reg , output -> wire로 설정
reg clk, reset_p, acc_high_reset_p;
reg fill_value, rd_en, acc_in_select;
reg [1:0] acc_high_select, acc_low_select;
reg [3:0] bus_data, alu_data;
wire [3:0] high_data2bus, acc_high_data2alu;
wire [3:0] low_data2bus, acc_low_data;
ACC DUT(
clk, reset_p, acc_high_reset_p,
fill_value, rd_en, acc_in_select,
acc_high_select, acc_low_select,
bus_data, alu_data,
high_data2bus, acc_high_data2alu,
low_data2bus, acc_low_data);
// 초기화 설정
initial begin
clk = 0; reset_p = 1; acc_high_reset_p = 0;
fill_value = 0;
rd_en = 1;
acc_in_select = 0;
acc_high_select = 0;
acc_low_select = 0;
bus_data = 4'b0010;
alu_data = 4'b0101;
end
// 클록 설정
always #5 clk = ~clk;
// 결과값 확인
initial begin
#10;
reset_p = 0; #10;
acc_high_select = LOAD; #10; //alu 데이터 로드
acc_high_select = IDLE; #10;
acc_in_select = DATA_BUS; acc_high_select = LOAD; #10; // bus 데이터 로드
acc_high_select = IDLE; acc_low_select = LOAD; #10; // 상위 비트 데이터 로드
acc_low_select = IDLE; #10;
acc_in_select = DATA_ALU; acc_high_select = LOAD; #10;
acc_high_select = SHIFT_RIGHT; acc_low_select = SHIFT_RIGHT; #40; // 우shift
acc_in_select = DATA_BUS; acc_high_select = LOAD; #10; //bus 데이터를 상위 비트 데이터에 load
acc_high_select = IDLE; acc_low_select = LOAD; #10; // 상위 비트 데이터 로드
acc_low_select = IDLE; #10;
acc_in_select = DATA_ALU; acc_high_select = LOAD; #10;
acc_high_select = SHIFT_LEFT; acc_low_select = SHIFT_LEFT; #40; // 좌shift
$stop;
end
endmodule
shift 연산 시뮬레이션 결과
80ns~120ns -> 우shift
160ns~200ns -> 좌shift
testbench_acc 소스코드(좌 shift, 상위 4비트 초기화 확인)
//////////////////////////////////////////////// 2024.10.14
module tb_acc();
parameter IDLE = 2'b00;
parameter SHIFT_RIGHT = 2'b01;
parameter SHIFT_LEFT = 2'b10;
parameter LOAD = 2'b11;
parameter DATA_ALU = 0;
parameter DATA_BUS = 1;
// 원래 코드에서의 input -> reg , output -> wire로 설정
reg clk, reset_p, acc_high_reset_p;
reg fill_value, rd_en, acc_in_select;
reg [1:0] acc_high_select, acc_low_select;
reg [3:0] bus_data, alu_data;
wire [3:0] high_data2bus, acc_high_data2alu;
wire [3:0] low_data2bus, acc_low_data;
ACC DUT(
clk, reset_p, acc_high_reset_p,
fill_value, rd_en, acc_in_select,
acc_high_select, acc_low_select,
bus_data, alu_data,
high_data2bus, acc_high_data2alu,
low_data2bus, acc_low_data);
// 초기화 설정
initial begin
clk = 0; reset_p = 1; acc_high_reset_p = 0;
fill_value = 0;
rd_en = 1;
acc_in_select = 0;
acc_high_select = 0;
acc_low_select = 0;
bus_data = 4'b0010;
alu_data = 4'b0101;
end
// 클록 설정
always #5 clk = ~clk;
// 결과값 확인
initial begin
#10;
reset_p = 0; #10;
acc_high_select = LOAD; #10; //alu 데이터 로드
acc_high_select = IDLE; #10;
acc_in_select = DATA_BUS; acc_high_select = LOAD; #10; // bus 데이터 로드
acc_high_select = IDLE; acc_low_select = LOAD; #10; // 상위 비트 데이터 로드
acc_low_select = IDLE; #10;
acc_in_select = DATA_ALU; acc_high_select = LOAD; #10;
acc_high_select = SHIFT_RIGHT; acc_low_select = SHIFT_RIGHT; #40; // 우shift
acc_in_select = DATA_BUS; acc_high_select = LOAD; #10; //bus 데이터를 상위 비트 데이터에 load
acc_high_select = IDLE; acc_low_select = LOAD; #10; // 상위 비트 데이터 로드
acc_low_select = IDLE; #10;
acc_in_select = DATA_ALU; acc_high_select = LOAD; #10;
acc_high_select = SHIFT_LEFT; acc_low_select = SHIFT_LEFT; #20; // 좌shift
acc_high_select = IDLE; acc_low_select = IDLE; // 상위 비트 리셋을 위해 좌shift 초기화
// 상위 비트 리셋
acc_high_reset_p = 1; #10;
acc_high_reset_p = 0; #10;
$stop;
end
endmodule
아래의 상자 부분 처럼 수정하여 좌shift 되는 시간을 변경하였고 상위 비트를 초기화 하는 것을 추가하였다.
shift 연산 및 비트 초기화 시뮬레이션 결과
160ns~180ns -> 좌shift
180ns~200ns -> 상위 4비트 초기화
cpu의 구조와 동작원리1(ALU, ACC) 끝!