[23.03.23] CPU 설계 - datapath
1. function unit
1-1
만들어 놓은 ALU와 Shifter를 합쳐 아래와 같이 function unit을 작성했다.
module Func_unit (oprA, oprB, FS, SA, Fout, v_flag, c_flag, n_flag, z_flag);
parameter BW = 16;
input [BW-1:0] oprA, oprB;
input [4:0] FS;
input [2:0] SA;
output [BW-1:0] Fout;
output v_flag, c_flag, n_flag, z_flag;
wire [BW-1:0] sft_out, alu_out;
wire [3:0] mux_sel;
assign mux_sel = {FS[0],SA[2:0]};
shifter shifter_u1 (.mux_sel(mux_sel), .oprB(oprB), .sel_mode(FS[3:1]), .Y(sft_out));
ALU alu_u1 (.oprA(oprA), .oprB(oprB), .Cin(FS[0]), .sel(FS[3:1]), .gout(alu_out), .v_flag(v_flag), .c_flag(c_flag), .n_flag(n_flag), .z_flag(z_flag));
assign Fout = FS[4] ? sft_out : alu_out; //FS[4] = MF_select
endmodule
MUX F를 보면 MF 값이 0이면 alu의 출력이 최종출력되고, 1이면 shifter의 출력이 최종출력된다. 이를 assign문으로 간단하게 나타내주었다.
1-2 시뮬레이션
module tb_func_unit;
reg [15:0] oprA, oprB;
reg [4:0] FS;
reg [2:0] SA;
wire [15:0] Fout;
wire v_flag, c_flag, n_flag, z_flag;
Func_unit u1 (oprA, oprB, FS, SA, Fout, v_flag, c_flag, n_flag, z_flag);
initial begin
FS = 5'b00000; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00001; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00010; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00011; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00100; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00101; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00110; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b00111; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b01000; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b01011; oprA = 16'h4; oprB = 16'h8;
#10 FS = 5'b01100; oprA = 16'h4; oprB = 16'h8; //ALU finish
#20 FS = 5'b10000; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011;// SFT - hold
#10 FS = 5'b10010; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; //1bit shift left (ignore oprA, SA)
#10 FS = 5'b10100; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; //1bit shift right
#10 FS = 5'b10110; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; //arith shift right
#10 FS = 5'b11000; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; //1bit rotate left
#10 FS = 5'b11010; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; //1bit rotate right
#10 FS = 5'b11100; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; // SA_3bit rotate right
#10 FS = 5'b11110; oprA = 16'h4; oprB = 16'b1000_0000_0000_1111; SA = 2'b011; // //SA_3bit rotate left //SFT finish
#10;
$stop;
end
endmodule
시뮬레이션 결과가 정상적으로 나온 것을 확인하였다.
2. Register file
2-1
Register file을 구현하기 위해 Decoder 와 D-f/f, 8x1 MUX를 먼저 만들었다. 이때 load enable을 decoder 블럭에서 enable로써 사용하였다.
module reg_16b (clk, start, enb, din, dout);
parameter DW = 16;
input clk, start, enb;
input [DW-1:0] din;
output reg [DW-1:0] dout;
always @ (posedge clk or negedge start)
begin
if (!start)
dout <= {DW{1'b0}};
else if (enb)
dout <= din;
end
endmodule
module mux8x1 (sel,d0,d1,d2,d3,d4,d5,d6,d7,dout);
parameter BW = 16;
input [2:0] sel;
input [BW-1:0] d0, d1, d2, d3, d4, d5, d6, d7;
output reg [BW-1:0] dout;
always @ (sel,d0,d1,d2,d3,d4,d5,d6,d7)
begin
if (sel==3'b000) dout = d0;
else if (sel==3'b001) dout = d1;
else if (sel==3'b010) dout = d2;
else if (sel==3'b011) dout = d3;
else if (sel==3'b100) dout = d4;
else if (sel==3'b101) dout = d5;
else if (sel==3'b110) dout = d6;
else dout = d7;
end
endmodule
D-f/f, 8x1 MUX이다.
다음 디코더 구현이다.
기본 컨셉은, 일단 8개의 레지스터의 Din에는 func unit의 output값이 들어온다. 이 때 R/W 신호에 따라 레지스터에 저장할 수 있도록 디코더에 en을 만들어줬다. (R/W = load enable) decoder의 출력은 8비트인데, 이게 한 비트씩 레지스터의 en으로 들어간다. 예를 들어 000을 디코더의 sel신호로 주면, 00000001이 출력되고 dec_out[0]만 1이기 때문에 R0만 Din이 Q로 나가서 MUX로 들어가게 된다.
module dec_3to8 (load_en, sel, dec_out);
input load_en;
input [2:0] sel;
output reg [7:0] dec_out;
always @ (load_en, sel)
begin
if (load_en)
case (sel)
3'b000 : dec_out = 8'b0000_0001;
3'b001 : dec_out = 8'b0000_0010;
3'b010 : dec_out = 8'b0000_0100;
3'b011 : dec_out = 8'b0000_1000;
3'b100 : dec_out = 8'b0001_0000;
3'b101 : dec_out = 8'b0010_0000;
3'b110 : dec_out = 8'b0100_0000;
default : dec_out = 8'b1000_0000;
endcase
else
dec_out <= 8'b0;
end
endmodule
2-2 Reg_file
module Reg_file (clk, SA, SB, DR, RW, start, Fout, oprB, oprA, r0_reg); //r0_reg
parameter BW = 16;
input clk, RW, start;
input [2:0] SA, SB, DR;
input [BW-1:0] Fout;
output [BW-1:0] oprA, oprB;
output wire [BW-1:0] r0_reg;
wire [7:0] dec_out;
wire [15:0] reg_out [0:7];
assign r0_reg = reg_out[0];
dec_3to8 dec_3to8_u (.load_en(RW), .sel(DR), .dec_out(dec_out));
mux8x1 MUX_A (.sel(SA), .d0(reg_out[0]), .d1(reg_out[1]), .d2(reg_out[2]), .d3(reg_out[3]), .d4(reg_out[4]), .d5(reg_out[5]), .d6(reg_out[6]), .d7(reg_out[7]), .dout(oprA));
mux8x1 MUX_B (.sel(SB), .d0(reg_out[0]), .d1(reg_out[1]), .d2(reg_out[2]), .d3(reg_out[3]), .d4(reg_out[4]), .d5(reg_out[5]), .d6(reg_out[6]), .d7(reg_out[7]), .dout(oprB));
genvar i;
generate
for (i=0; i<8; i=i+1) begin : REG
reg_16b reg_16b_u (.clk(clk), .start(start), .enb(dec_out[i]), .din(Fout), .dout(reg_out[i]));
end
endgenerate
endmodule
위에서 만든 세 모듈을 인스턴시에이션하여 register file unit을 완성하였다. r0_reg 는 R0의 출력값을 FND로 확인하고자 포트로 빼준 것이다. 유의할 점은 16비트 reg_out을 8개를 만들어야 하는데 array로 작성하지 않아서 에러가 발생했었다.
구조를 잘 생각하고 문법에 맞게 작성하는 것이 중요하다는 것을 느꼈다.
3. Datapath
3-1
module datapath_unit (clk, start, rw, sa, sb, dr, fs, pc, mb, md, mm, data_in, vflg, cflg, nflg, zflg, data_out, addr_out, r0_reg);
parameter BW = 16;
input clk, start, rw;
//input ta, tb, td;
input [2:0] sa, sb, dr;
input [4:0] fs;
input [7:0] pc; //512-words LPM_RAM_DQ
input mb, md, mm;
input [BW-1:0] data_in;
output reg vflg, cflg, nflg, zflg;
output wire [BW-1:0] data_out;
output wire [8:0] addr_out;
output wire [BW-1:0] r0_reg;
wire s_vflg, s_cflg, s_nflg, s_zflg;
wire [BW-1:0] oprA, Fout;
wire [BW-1:0] Reg_oprB, MUXB_out, MUXD_out;
assign MUXB_out = mb ? {{(BW-3){1'b0}},sb} : Reg_oprB; // Const_in = {{(BW-3){1'b0}},sb}
assign MUXD_out = md ? data_in : Fout;
assign addr_out = mm ? {1'b0,pc}: oprA[8:0]; //MUXM_out
assign data_out = MUXB_out;
Reg_file Reg_u (.clk(clk), .SA(sa), .SB(sb), .DR(dr), .RW(rw), .start(start), .Fout(MUXD_out), .oprB(Reg_oprB), .oprA(oprA), .r0_reg(r0_reg));
Func_unit Func_u (.oprA(oprA), .oprB(MUXB_out), .FS(fs), .SA(sa), .Fout(Fout), .v_flag(v_flag), .c_flag(c_flag), .n_flag(n_flag), .z_flag(z_flag));
always @(posedge clk)
begin
if (start==1'b0) begin
vflg <= 1'b0;
cflg <= 1'b0;
nflg <= 1'b0;
zflg <= 1'b0;
end
else if (rw) begin
vflg <= s_vflg;
cflg <= s_cflg;
nflg <= s_nflg;
zflg <= s_zflg;
end
end
endmodule
마지막으로 register file과 function unit을 연결하고, MUXB, MUXD, MUXM을 assign문으로 적절히 만들어주었다. flag 포트들 앞에 레지스터를 다 달아주었는데, memory에 access할 때 등은 flag값이 바뀌지 않고, fout이 register file로 들어와서
rw할 때만 flag값이 로딩될 수 있도록 하기 위함이다. 레지스터를 달아주지 않으면 예컨데, memory에 값을 저장하기 위한 oprA, B값에 대한 flag도 쓸데없이 계속 로딩되는 것이다.
3-2 시뮬레이션
module tb_datapath_unit;
reg clk = 1'b1, start, rw;
reg [2:0] sa, sb, dr;
reg [4:0] fs;
reg [7:0] pc; //512-words LPM_RAM_DQ
reg mb, md, mm;
reg [15:0] data_in;
wire vflg, cflg, nflg, zflg;
wire [15:0] data_out;
wire [8:0] addr_out;
wire [15:0] r0_reg;
datapath_unit u1 (clk, start, rw, sa, sb, dr, fs, pc, mb, md, mm, data_in, vflg, cflg, nflg, zflg, data_out, addr_out, r0_reg);
always #10 clk = ~clk;
initial begin
start = 1'b0; rw = 1'b1;
#20 start = 1'b1;
#20 sb = 3'b001; mb = 1'b1; fs = 5'b10010; md = 1'b0; dr = 3'b001;
//#20 mb = 1'b0; fs =5'b11100; sa = 2'b010; md = 1'b0; sa = 3'b010; dr = 3'b010;
#40 mb = 1'b0; fs =5'b10100; md = 1'b0; sa = 3'b010; dr = 3'b010;
#40 mm = 1'b0;
#20;
$stop;
end
endmodule
구조가 복잡하여 필요한 변수에만 값을 주어 결과를 확인해보았다. 위의 회로 그림에서 첫 시작은 constant값을 주고 MUXB는 1로 셀렉하여 function unit에서 oprB shift 시키고 register file로 들어와서 oprB에 쉬프트된 값이 제대로 들어오는지 확인한다.
그 다음은 다시 func unit을 거쳐 register file로 들어와서 oprA에 값을 저장하고 확인하는 것이 목적이었다. 그 후 address out이 나오도록 MUXM을 0으로 셀렉하여 oprA값이 잘 나오는지 확인하였다.