2024.10.21 수업날
< Block_ALU_ACC >
저번시간에 만든 ALU와 ACC를 합쳐서 코드를 만들고, 이를 이용한 Test Bench를 만들어서 연산 결과가 잘 나타나는지 시뮬레이션으로 확인해본다.
Block_ALU_ACC
module Block_ALU_ACC(
input clk, reset_p, acc_high_reset_p,
input rd_en, acc_in_select,
input [1:0] acc_high_select_in, acc_low_select,
input [3:0] bus_data,
input op_add, op_sub, op_mul, op_div, op_and,
input [3:0] bus_reg_data,
output zero_flag, sign_flag,
output [7:0] acc_data // 곱셈 결과가 8비트 이기 때문
);
wire fill_value;
wire [3:0] alu_data;
wire [3:0] high_data2bus, acc_high_data2alu;
wire [3:0] low_data2bus, acc_low_data;
wire alu_lsb;
wire [3:0] acc_high_data;
wire carry_flag;
wire c_out;
assign fill_value = carry_flag;
assign acc_data = {high_data2bus, low_data2bus};
wire [1:0] acc_high_select;
assign acc_high_select[1] = (op_mul | op_div) ? ((op_mul& acc_low_data[0]) | (op_div & c_out)) : acc_high_select_in[1];
assign acc_high_select[0] = (op_mul | op_div) ? ((op_mul& acc_low_data[0]) | (op_div & c_out)) : acc_high_select_in[0];
ACC block_acc(
clk, reset_p, acc_high_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);
ALU block_alu(
clk, reset_p,
op_add, op_sub, op_mul, op_div, op_and,
acc_high_data2alu[0],
acc_high_data2alu, bus_reg_data,
alu_data,
carry_flag,
zero_flag, sign_flag,
c_out);
endmodule
< ALU_ACC_Test Bench >
1. ALU_ACC_Test Bench 곱셈
예를 들어 1001(9), 1010(10)의 곱셈을 한다고 해보자.
처음에는 1001과 0을 곱해서 0000이 나오므로 일의자리부터 써주고,
다음으로 1001과 1을 곱해서 1001이 나오므로 십의자리부터 써주고...
이런 식으로 자릿수마다 값을 구한 다음 한거번에 더해서 결과값을 구한다.
이는 Right Shift한 후에 더하고, Right Shift한 후에 더하고.. 이런 방식과 유사하다.
이제 Test bench를 통해 저번에 만들었던 Block_ALU_ACC 모듈에서 곱셈이 잘 되는지 확인해본다.
시뮬레이션을 통해 결과를 확인해볼 것이므로 시뮬레이션 소스를 추가한다.
이 Test bench에서는 아래의 2진수 곱셈 계산을 확인해본다.
그림으로 과정을 확인해보자.
Test bench에서 초기화 했을 때의 상태이다.
ALU의 값이 0101이라고 해서 입력값이 바로 0101이 되는 것은 아니고, 0101이라는 출력값을 load 하거나 bus의 값을 load해야 값이 바뀐다.
데이터를 load 했을 때의 상태
상위 비트의 데이터를 하위 비트에 load하고 상위비트는 clear 한다.
하위 비트의 최하위 비트가 1이기 때문에 곱하기 신호에 1을 줘서 0101과 0101을 더한 값이 ALU의 출력부분에 출력된다.
Right Shift 하여 0010(2) 1101(2진수로 d) 값을 확인할 수 있다.
하위 비트의 최하위 비트가 1이기 때문에 0010과 0101을 더하면 0111이 ALU에 출력된다.
ALU에 출력된 0111 값이 상위 비트에 load되서 0111(7) 1101(d) 값을 확인할 수 있다.
Right Shift 해서 0011(3) 1110(e) 값을 확인할 수 있다.
하위 비트의 최하위 비트가 0이기 때문에 값이 그대로 유지된다.
하위 비트의 최하위 비트가 0이여서 ALU에는 0011값이 그대로 유지된다.
(만약 하위 비트의 최하위 비트가 1이면 ALU에는 1000이라는 값이 나올 것이다.)
Right Shift를 하면 0001(1) 1111(f) 값을 확인할 수 있다.
하위 비트의 최하위 비트가 1이여서
0001과 0101을 더하면 0110이 되기 때문에 ALU 출력으로 0110이 출력된다.
ALU 출력부분에 출력된 0110을 상위 비트에 load 시키면 0110(6) 1111(f) 값을 확인할 수 있다.
0110 1111를 Right Shift 하면 0011 0111이 출력된다. 이는 십진수로 55이다. 즉, 계산값이 제대로 나온 것이다.
아래는 0101(십진수로 5) X 1011(십진수로 11) 의 계산 과정을 시뮬레이션 그림으로 나타낸 것이다.
acc_data[7:0] 부분을 확인하면 된다.
초기화 -> 상위 비트에 데이터 load -> 상위 비트의 데이터를 하위 비트에 load -> 상위 비트 clear -> ALU에 출력된 결과를 상위 비트에 load -> Right shift -> ALU에 출력된 결과를 상위 비트에 load -> Right Shift -> Right Shift -> ALU에 출력된 결과를 상위 비트에 load -> Right shift 결과 도출
Test Bench Block ALU ACC - 곱셈
module tb_Block_ALU_ACC();
parameter IDLE = 2'b00;
parameter SHIFT_RIGHT = 2'b01;
parameter SHIFT_LEFT = 2'b10;
parameter LOAD = 2'b11;
reg clk, reset_p, acc_high_reset_p;
reg rd_en, acc_in_select;
reg [1:0] acc_high_select_in, acc_low_select;
reg [3:0] bus_data;
reg op_add, op_sub, op_mul, op_div, op_and;
reg [3:0] bus_reg_data;
wire zero_flag, sign_flag;
wire [7:0] acc_data;
Block_ALU_ACC DUT(
clk, reset_p, acc_high_reset_p,
rd_en, acc_in_select,
acc_high_select_in, acc_low_select,
bus_data,
op_add, op_sub, op_mul, op_div, op_and,
bus_reg_data,
zero_flag, sign_flag,
acc_data);
initial begin
clk = 0; reset_p = 1; acc_high_reset_p = 0;
rd_en = 1; acc_in_select = 0;
acc_high_select_in = 0; acc_low_select = 0;
bus_data = 4'b1011; bus_reg_data = 4'b0101;
op_add = 0; op_sub = 0; op_mul = 0; op_div = 0; op_and = 0;
end
always #5 clk = ~clk;
// 곱하기
initial begin
#10;
reset_p = 0; #10;
acc_in_select = 1; acc_high_select_in = LOAD; #10; // bus 데이터를 load하기
acc_in_select = 0; acc_high_select_in = IDLE; #10; // 데이터는 한번만 load 해주면 되기 때문에 끄기
acc_low_select = LOAD; #10; //데이터를 하위 비트에 load하기
acc_low_select = IDLE; acc_high_reset_p = 1; #10;
acc_high_reset_p = 0; // 상위 비트 클리어
op_mul = 1; #10; // 곱하기 신호에 1을 줘서 더한 값을 상위비트에 load
op_mul = 0; acc_low_select = SHIFT_RIGHT; acc_high_select_in = SHIFT_RIGHT; #10; // 곱하기 신호를 끄고 우shift하기
op_mul = 1; acc_low_select = IDLE; #10;
op_mul = 0; acc_low_select = SHIFT_RIGHT; acc_high_select_in = SHIFT_RIGHT; #10;
op_mul = 1; acc_low_select = IDLE; #10;
op_mul = 0; acc_low_select = SHIFT_RIGHT; acc_high_select_in = SHIFT_RIGHT; #10;
op_mul = 1; acc_low_select = IDLE; #10;
op_mul = 0; acc_low_select = SHIFT_RIGHT; acc_high_select_in = SHIFT_RIGHT; #10;
$stop;
end
endmodule
2. ALU_ACC_Test Bench 나눗셈
예를 들어 1010(10), 0010(2)의 나눗셈을 한다고 해보자.
0010과 최상위 자릿수인 1을 비교하여 뺄셈을 할 수 있는지 확인하고,
0010과 그 다음 자릿수인 0을 비교하여 뺄셈을 할 수 있는지 확인하고...
나눗셈도 마찬가지로 자릿수마다 값을 비교해서 빼면 된다.
나누어지는 수가 나누는 숫자에 들어갈 수 있으면 몫에 1을 적고, 들어갈 수 없으면 0을 적는다.
즉, Left Shift하고 빼고, Left Shift하고 빼고...를 반복한다.
이번 Test bench에서 아래의 2진수 나눗셈 계산을 확인해본다.
그림으로 과정을 확인해보자.
Test bench에서 초기화 했을 때의 상태이다.
Left Shift 해서 0001(1) 0101(6) 값을 확인할 수 있다.
ALU는 무조건 더하기 값만 출력된다.
Left Shift 한 후에 상위비트, 하위비트에 적고, 0010(2)과 0101을 더한 값인 0111(C)이 ALU의 출력부분에 출력된다.
다음으로 Left Shift 하면 0101(5) 1000(8)로 되고
이를 실제 나눗셈을 한 단계로 따지자면 아래의 파란색 부분까지 한 것이다.
파란색 부분까지 자릿값을 따져보자면 나누는 값(0101)이 들어갈 수 있기 때문에 몫에는 1이 들어갈 수 있다.
그러므로 0101에서 0101을 빼면 0000이 된다.
따라서 ALU의 출력단에 0000이 출력된다.
ALU의 출력부분에서 출력된 0000이 입력값으로 입력되었고, 0000(0) 1000(8) 값을 확인할 수 있다.
Left Shift를 한 후에 나누어지는 값(1011)의 최하위 비트에 1이 남았으므로 1을 하위 비트의 최하위 비트에 추가한다.
그러면 0001(1) 0001(1) 값을 확인할 수 있다.
모든 자릿수의 계산이 끝났으므로 상위비트 부분은 나머지가 된다.
여기서 끝난 것이 아니라 하위 비트 부분은 몫 부분이 되므로
하위 비트 부분만 한번 더 Left Shift를 하면 몫이 되는 것을 확인할 수 있다.
0001 0010은 이는 십진수로 1, 2이다. 즉, 나머지가 1이고 몫이 2이므로 계산값이 제대로 나온 것이다.
아래는 1011(십진수로 11) / 0101(십진수로 5) 의 계산 과정을 시뮬레이션 그림으로 나타낸 것이다.
acc_data[7:0] 부분을 확인하면 된다.
초기화 -> 상위 비트에 데이터 load -> 상위 비트의 데이터를 하위 비트에 load -> 상위 비트 clear -> Left shift -> Left Shift를 한 후, ALU에 출력된 결과 나타냄 -> Left Shift -> ALU에 출력된 결과가 상위 비트로 입력됨 -> Left Shift 를 한 후, 1을 하위 비트의 최하위 비트에 추가 -> 하위 비트 부분만 한번 더 Left Shift하여 결과 도출
Test Bench Block ALU ACC - 나눗셈
module tb_Block_ALU_ACC();
parameter IDLE = 2'b00;
parameter SHIFT_RIGHT = 2'b01;
parameter SHIFT_LEFT = 2'b10;
parameter LOAD = 2'b11;
reg clk, reset_p, acc_high_reset_p;
reg rd_en, acc_in_select;
reg [1:0] acc_high_select_in, acc_low_select;
reg [3:0] bus_data;
reg op_add, op_sub, op_mul, op_div, op_and;
reg [3:0] bus_reg_data;
wire zero_flag, sign_flag;
wire [7:0] acc_data;
Block_ALU_ACC DUT(
clk, reset_p, acc_high_reset_p,
rd_en, acc_in_select,
acc_high_select_in, acc_low_select,
bus_data,
op_add, op_sub, op_mul, op_div, op_and,
bus_reg_data,
zero_flag, sign_flag,
acc_data);
initial begin
clk = 0; reset_p = 1; acc_high_reset_p = 0;
rd_en = 1; acc_in_select = 0;
acc_high_select_in = 0; acc_low_select = 0;
bus_data = 4'b1011; bus_reg_data = 4'b0101;
op_add = 0; op_sub = 0; op_mul = 0; op_div = 0; op_and = 0;
end
always #5 clk = ~clk;
// 나누기
initial begin
#10;
reset_p = 0; #10;
acc_in_select = 1; acc_high_select_in = LOAD; #10; // bus 데이터를 load하기
acc_in_select = 0; acc_high_select_in = IDLE; #10; // 데이터는 한번만 load 해주면 되기 때문에 끄기
acc_low_select = LOAD; #10; //데이터를 하위 비트에 load하기
acc_low_select = IDLE; acc_high_reset_p = 1; #10;
acc_high_reset_p = 0;
acc_low_select = SHIFT_LEFT; acc_high_select_in = SHIFT_LEFT; #10;
acc_low_select = IDLE; op_div = 1; #10;
op_div = 0;
acc_low_select = SHIFT_LEFT; acc_high_select_in = SHIFT_LEFT; #10;
acc_low_select = IDLE; op_div = 1; #10;
op_div = 0;
acc_low_select = SHIFT_LEFT; acc_high_select_in = SHIFT_LEFT; #10;
acc_low_select = IDLE; op_div = 1; #10;
op_div = 0;
acc_low_select = SHIFT_LEFT; acc_high_select_in = SHIFT_LEFT; #10;
acc_low_select = IDLE; op_div = 1; #10;
op_div = 0;
acc_low_select = SHIFT_LEFT; acc_high_select_in = IDLE; #10;
$stop;
end
endmodule
cpu의 구조와 동작원리2 (Block_ALU_ACC, ALU_ACC_Test Bench) 끝!