verilog 12 cpu설계-2

2023. 3. 27. 16:30Verilog

Function unit

앞서 cpu의 연산 결과와 RAM에서 온 데이터를 잠시 저장하는 레지스터 파일을 제작하였다. 이번에는 연산을 수행하는 연산장치인 Function unit 설계이다. Function unit은 ALU와 Shifter로 구성되어 있다. 

ALU

산술논리연산장치는 산술연산기와 논리연산기로 나뉜다. 산술연산장치는 1비트 전가산기 16개를 연결하여 16비트 산술연산 기를 구현하였다. 

산술 연산기

산술연산장치

module arithmetic #(parameter WD = 16)(
    input[WD-1:0] A,B,
    input carry_in, select_0, select_1,
    output carry_out, ovf_flag,
    output[WD-1 : 0] sum
);

wire carry_wire[WD : 0];
genvar i;

assign carry_wire[0] = carry_in;

generate
    for(i=0; i<WD; i= i+1)begin : full_adder
        full_adder_1bit fa_for(.a(A[i]), .b(B[i]), .select_0(select_0), .select_1(select_1), .carry_in(carry_wire[i]), .carry_out(carry_wire[i+1]), .sum(sum[i]));
    end
endgenerate

assign carry_out = carry_wire[WD];
assign ovf_flag = carry_wire[WD];
endmodule

1비트 전가산기를 통해 여러가지 연산을 구현할 수 있다. 

진리표
4비트 산술 연산기 회로

선택비트를 토글해 A값을 그대로 전달하는 MVA(Move A), ADD(덧셈), SUB(뻴셈), INC(증가), DEC(감소) 연산을 수행한다. 

 논리연산기

진리표
논리 연산기 RTL 분석

`timescale 1ns / 1ps

module logical #(parameter WD = 16)(
    input[WD-1 : 0] a, b,
    input s0, s1,
    output [WD-1 : 0] g
    );
    wire[1:0] tmp;
    assign tmp = {s1, s0};
    reg[WD-1 : 0] out;
    assign g = out;
    always @(*) begin
        case(tmp)
            2'b00 : out = a & b;
            2'b01 : out = a | b;
            2'b10 : out = a ^ b;
            2'b11 : out = ~a;
            default : out = 0;
        endcase
    end
endmodule

논리 연산기는 간단하게 구현이 가능하다. 논리 연산기 4개와, 논리 연산의 출력을 선택하는 4X1 디코더를 연결하였다. 

 

ALU

ALU

module ALU #(parameter WD = 16)(
    input[WD-1 : 0] A, B,
    input carry_in, select_0, select_1, select_2,
    output carry_out, z_flag, n_flag , v_flag, //have to invert carry_out at unsigned calculation;
    output[WD-1 : 0] ALU_OUT
);

wire cout;
wire[WD-1 : 0] arith_out, logic_out;
arithmetic #(WD) arith(.A(A), .B(B), .carry_in(carry_in), .select_0(select_0), .select_1(select_1), .carry_out(cout), .sum(arith_out), .ovf_flag(v_flag));
logical #(WD) logic(.a(A), .b(B), .s0(select_0), .s1(select_1), .g(logic_out));

assign ALU_OUT = (select_2) ? (logic_out) : (arith_out);
assign z_flag = ~(|ALU_OUT);
assign n_flag = arith_out[WD-1];
assign carry_out = cout;
endmodule

ALU도 간단하게 구현하였다. 선택비트 2는 산술연산 결과를 내보낼지, 논리 연산결과를 내보낼지 선택하는 비트이다. ALU_OUT MUX를 통해 산술과 논리 연산 중 하나의 값을 내보내준다. 

z플래그는 연산 결과가 0이면 1이 되는 플래그이고, v는 오버플로우 발생 플래그, c는 캐리 발생 플래그, n은 부호 플래그이다. 현재 연산기는 unsigned 연산기 이므로, 오버플로우 발생 플래그와 캐리 발생 플래그는 같은 결과를 보이며, n 플래그는 사용하지 않는다. 

 

Shift 연산기

연산기 회로

`timescale 1ns / 1ps

module barrel_shifter #(parameter WD = 4)(
    input wire rotate, left_right, //rotate = 1 = rotate
    input wire[WD-2 : 0] select,
    input wire[(1<<WD)-1 : 0] data,
    output wire[(1<<WD)-1 : 0] out
);

localparam w = 1<<WD;

reg[WD-1:0] select_temp;
reg [2*w-2 : 0] tmp; //30
always @(*) begin
    case(left_right)
        1'b0 : begin
            select_temp = {left_right, select[WD-2 : 0]};
            if(rotate)
            tmp = {data[w-2:0], data[w-1 : 0]};
            else 
            tmp = {15'd0, data[w-1 : 0]};
        end
           
        default : 
        begin
            if((select == 3'b0))
                select_temp = {left_right, select};
            else 
                select_temp = {left_right, ~(select-1)};
            if(rotate)
            tmp = {data[w-2:0], data[w-1 : 0]};
            else 
            tmp = {data[w-2:0],16'd0};
        end 
    endcase
end




genvar i;

generate
    for(i = 0; i<w; i= i+1)begin : barrel
        mux #4 m(.select(select_temp), .data(tmp[i+w-1 : i]), .mux_out(out[i]));  
    end
endgenerate
    
endmodule

module mux #(parameter WD = 4)(
    input wire[WD-1 : 0] select,
    input wire[(1<<WD)-1 : 0] data,
    output mux_out
);

assign mux_out = data[select];
    
endmodule

쉬프트 레지스터 동작 방법
우측 시프트연산 예

 

좌측 시프트 연산 예

 

좌/우측 시프트 연산

쉬프트 회로는 위 그림과 같이 동작한다. N비트 쉬프트 레지스터라 할 때, N + N-1비트의 임시 변수를 만들어 준다. 

mux는 N개만큼 생성해준다. 0번 MUX에는 임시 변수의 0~15번째, 1번 MUX에는 임시변수의 1~16번째, X번 MUX에는

X부터 X+(N-1)번째 데이터를 MUX의 데이터 입력 포트에 연결해준다. 다음으로, Select 신호를 이용해서 몇번 쉬프트 할 지를 정해준다. 

하나의 시프터 모듈로 원형, 논리 좌우 시프트를 구현하기 위해, 제어장치에서 제어신호를 받아 쉬프트 모드가 결정되면, 모듈 내에서 임시 변수를 변경하여 연산을 진행한다. 위 회로로는 오른쪽 연산만을 수행하기 때문에, 왼쪽으로 Y만큼 시프트하려면, N-Y만큼 우측 시프트 연산을 한 결과가 Y만큼 좌측 시프트한 결과와 같다는 것을 이용하여 구현하였다. 

 

Fuction unit

 

진리표

module fuction_t #(parameter WD = 16)(
        input[WD-1 : 0] A, B,
        input[4:0] opcode, 
        input [2:0] SA,
        output[WD-1 : 0] F,
        output v, c, n ,z
    );

    wire[WD-1 : 0] alu_out, shift_out;
    reg rotate, left;
    reg shift_one;
    reg hold_b;
    wire [2:0] reg_select_bit;
    assign F = (opcode[4]) ? shift_out : alu_out;
    assign reg_select_bit = shift_one ? 3'd1 : ((hold_b) ? 3'b0 : SA);
    always @(*) begin
        casex (opcode)
            5'b10000 :
                begin
                    left = 1'b0; rotate = 1'b0; shift_one = 1'b0; hold_b = 1'b1;
                    end
            5'b10010 : 
                begin
                    left = 1'b1; rotate = 1'b0; shift_one = 1'b1; hold_b = 1'b0;
                end 
            5'b10100 : 
                begin
                    left = 1'b0; rotate = 1'b0; shift_one = 1'b1; hold_b = 1'b0;
                end 
            5'b10110 :
                begin
                    left = 1'b1; rotate = 1'b0; shift_one = 1'b1; hold_b = 1'b0;
                end 
            5'b11000 :
                begin
                    rotate = 1'b1; left = 1'b1; shift_one = 1'b1;hold_b = 1'b0;
                end
            5'b11010 :
                begin
                    rotate = 1'b1; left = 1'b0; shift_one = 1'b1;hold_b = 1'b0;
                end
            5'b1110x :
                begin
                    rotate = 1'b1; left = 1'b0; shift_one = 1'b0;hold_b = 1'b0;
                end
            5'b1111x : 
                begin
                    rotate = 1'b1; left = 1'b1; shift_one = 1'b0;hold_b = 1'b0;
                end
            default :
                begin
                    rotate = 0; left = 0; shift_one =0;hold_b = 1'b0;
                end

        endcase
    end
    ALU #16 alu(.A(A), .B(B), .carry_in(opcode[0]), .select_0(opcode[1]), .select_1(opcode[2]), .select_2(opcode[3]), .carry_out(c), .z_flag(z), .n_flag(n), .v_flag(v), .ALU_OUT(alu_out));
    barrel_shifter #4 sr(.select(reg_select_bit), .rotate(rotate), .left_right(left), .data(B), .out(shift_out));
    
endmodule

Function unit은 제어유닛에서 제어 코드를 받아 ALU 연산을 수행할지, 시프트 연산을 수행할지 정해주는 모듈이다. 

 

RAM - 512 word 램

Vivado의 기본 IP 생성기중 Block memory generator을 이용하여 메모리를 생성하였다. 메모리의 0~255번지는 명령어 영역이고, 256~512번지는 데이터 영역으로 설정하였다. 메모리는 Memory mux로부터 값을 받아, PC 주소값을 받거나, 레지스터 파일의 A 출력을 주소로 받을 수 있다. 

전체 데이터패스

module datapath #(parameter  WD = 16)(
    input clk, rst, W_mem,
    input [7:0] pc_to_address,
    input wire RW, MM, MB, MD, //load
    input wire[2:0] DA, AA, BA,
    input wire[4:0] FS,
    input [WD-1 : 0] constant_in,
    output reg  v,c,n,z,
    output[WD-1 : 0] ram_data_out, r0_out
);
localparam R_N = 8;
wire c_w, v_w, n_w, z_w;

always @(posedge clk)begin
    if(!rst)begin
    v<= 0;
    c<= 0;
    n<= 0;
    z<= 0;
    end
    else if(RW) begin
    v<=v_w;
    c<=c_w;
    n<=n_w;
    z<=z_w;
    end
     
end

wire[WD-1 : 0] A_DATA, B_DATA;
wire[WD-1 : 0] function_to_regfile;
reg[WD-1 : 0] muxB_to_FU;
reg[WD-1 : 0] muxD_to_Ddata;
wire[WD-1 : 0] ram_to_dmux;

reg[8:0] to_mem_address;

assign ram_data_out = ram_to_dmux;
always @(*) begin
    case(MM)
        1'b0 : to_mem_address = A_DATA[8:0];
        default : to_mem_address = {1'b0, pc_to_address};
    endcase
end


always @(*) begin
    case(MB)
        1'b0 : muxB_to_FU = B_DATA;
        default : muxB_to_FU = constant_in;
    endcase
end

always @(*) begin
    case(MD)
        1'b0 : muxD_to_Ddata = function_to_regfile;
        default : muxD_to_Ddata = ram_to_dmux;
    endcase
end
regFile #(WD,8) regfile_1(.Load_enable(RW), .clk(clk), .rst(rst), .D_data(muxD_to_Ddata),
.A_select(AA), .B_select(BA), .Destination_select(DA), .Adata(A_DATA), .Bdata(B_DATA), .r0_out(r0_out)
);

fuction_t #(WD) f (.A(A_DATA), .B(muxB_to_FU), .opcode(FS), .SA(DA), .F(function_to_regfile), .v(v_w), .c(c_w), .n(n_w), .z(z_w));

memory m512
(.addra_0(to_mem_address),
    .clka_0(clk),
    .dina_0(B_DATA),
    .douta_0(ram_to_dmux),
    .ena_0(1'b1),
    .wea_0(W_mem));
    
endmodule

이제 전체 데이터패스가 완성되었다. 

 

Control unit 

//module up_control (clk, start, ta, sa, tb, sb, td, dr, fs, pc, rw, mw, mb, md, mm, data_in, vflg, cflg, nflg, zflg);
module up_control (clk, start, sa, sb, dr, fs, pc, rw, mw, mb, md, mm, data_in, vflg, cflg, nflg, zflg);
parameter	BW = 16;
parameter	S_ADDR = 8'h0;
parameter	IF_ADDR = 8'hFE;

input						clk, start;
//output						ta, tb, td;
output	reg	[2:0]			sa, sb, dr;
output	reg		[4:0]		fs;
output	wire	[7:0]		pc;				//512-words LPM_RAM_DQ 

output	reg				rw, mw, mb, md, mm;
input			[BW-1:0]	data_in;
input						vflg, cflg, nflg, zflg;

wire						ta, tb, td;

reg				[6:0]		ir_ca_opr;

wire			[27:0]		cm_out;

reg							pc_ctrl;

reg						pl;
reg						pi;
reg							il;
reg							mc;
reg				[2:0]		ms;
reg				[7:0]		na;

reg				[7:0]		t_pc;
reg				[7:0]		s_pc;

reg				[7:0]		t_ca_reg;
reg				[7:0]		s_ca_reg;
wire			[7:0]		ca_reg;

always @(start, pc_ctrl, pl, pi, s_pc, dr[1:0], sa, sb, t_pc)
begin
	if (!start) 
		t_pc = S_ADDR;
	else if (pl) begin
		if (pc_ctrl) begin
			if (pi)
				t_pc = s_pc+{dr[1:0],sa,sb};
			else
				t_pc = {dr[1:0],sa,sb};
		end
		else
			t_pc = s_pc;
	end
	else
		t_pc = s_pc + pi;
end

always @(posedge clk, negedge start)
begin
	if (!start) 
		s_pc <= 8'b0;
	else if (pl | pi)
		s_pc <= t_pc;
end

assign pc = t_pc;//s_pc;//

always @(posedge clk, negedge start)
begin
	if (!start) begin
		sb <= 3'b0;
		sa <= 3'b0;
		dr <= 3'b0;
	end
	else if (il) begin
		sb <= data_in[2:0];
		sa <= data_in[5:3];
		dr <= data_in[8:6];
	end
end

always @(data_in[15:9])
begin
	ir_ca_opr = data_in[15:9];
end

always @(posedge clk, negedge start)
begin
	if (!start)
		pc_ctrl <= 1'b0;
	else begin
		case (ms)
			3'h0 : pc_ctrl <= 1'b0;
			3'h1 : pc_ctrl <= 1'b1;
			3'h2 : pc_ctrl <= cflg;
			3'h3 : pc_ctrl <= vflg;
			3'h4 : pc_ctrl <= zflg;
			3'h5 : pc_ctrl <= nflg;
			3'h6 : pc_ctrl <= ~cflg;
			default : pc_ctrl = ~zflg;	//3'h7
		endcase
	end
end

always @(start ,mc, ir_ca_opr, na)
begin
	if (!start) 
		t_ca_reg = IF_ADDR;
	else begin
		if (mc==1'b1)
			t_ca_reg = {1'b0,ir_ca_opr};
		else
			t_ca_reg = na;
	end
end

assign ca_reg = t_ca_reg;

//ram_256x28b inst_ram_256x28b (.address(ca_reg), .clock(clk), .data(28'b0), .wren(1'b0), .q(cm_out)); 
control_mem_wrapper inst_rom_256x28b(.addra_0(ca_reg), .clka_0(clk), .douta_0(cm_out), .ena_0(1'b1));

/*
// mem_test
reg		[7:0]	test_cnt;
wire	[27:0]	test_cm_out;
always @(posedge clk, negedge start)
	if (!start)
		test_cnt <= 8'b0;
	else
		test_cnt <= test_cnt + 1'b1;

rom_256x28b test_rom_256x28bnn (.clka(clk), .ena(1'b1), .addra(test_cnt), .douta(test_cm_out)); 
*/
/*
rw, mw, mb, md, mm;
assign mw = start & cm_out[0];
assign mm = start & cm_out[1];
assign rw = start & cm_out[2];
assign md = start & cm_out[3];
assign mb = start & cm_out[9];
assign fs = {5{start}} & cm_out[8:4];
*/
	
assign tb = start & cm_out[10];
assign ta = start & cm_out[11];
assign td = start & cm_out[12];

always @(posedge clk, negedge start)
begin
	if (!start) begin
		il <= 1'b0;
		pl <= 1'b0;
		pi <= 1'b0;
		fs <= 5'b0;
//		mw <= 1'b0;
	    rw <= 1'b0;
//	    mm <= 1'b0;
	    md <= 1'b0;
	    mb <= 1'b0;		
		//ms <= 3'b0;
	end
	else begin
		il <= cm_out[15];
		pl <= cm_out[13];
		pi <= cm_out[14];		
		fs <= cm_out[8:4];		
		
//		mw <= cm_out[0];
	    rw <= cm_out[2];
//	    mm <= cm_out[1];
		md <= cm_out[3];
		mb <= cm_out[9];	
		//ms <= cm_out[19:17];		
	end
end


always @(start, cm_out[27:16])
begin
	if (!start) begin
		mc = 1'b0;
		ms = 3'b0;
		na = 8'b0;
		mw = 1'b0;
	    //rw = 1'b0;		
	    mm = 1'b0;
	    //md = 1'b0;
	    //mb = 1'b0;			
	end
	else begin
		mc = cm_out[16];
		ms = cm_out[19:17];
		na = cm_out[27:20];
		mw = cm_out[0];
	    //rw = cm_out[2];		
	    mm = cm_out[1];
		//md = cm_out[3];
		//mb = cm_out[9];		
	end
end


endmodule
/*
fs, rw, mb, md


*/

CU는 소스를 제공받아 사용하였다. 

Control rom

RAM과 마찬가지로 Block memory generator를 사용하여 제작하였다. 

TOP

module cpu_top (clk100M, push_sw, seg_drv, an_sel);
parameter	BW = 16;
input				clk100M, push_sw;
output	[7:0]		seg_drv;	// muxed signal
output	[3:0]		an_sel;

reg [6:0]			seg_k;
reg [6:0]			seg_h;
reg [6:0]			seg_t;
reg [6:0]			seg_o;

//wire				ta, tb, td;
wire				clk;
wire				start;
wire	[7:0]		pc;
wire	[8:0]		addr_out;
wire	[BW-1:0]	data_out;
wire	[BW-1:0]	data_in;
wire	[2:0]		dr, sa, sb;
wire	[4:0]		fs;
wire				rw, mb, mw, mm, md;
wire				vflg, cflg, nflg, zflg;
wire	[BW-1:0]	r0_out;


//assign clk = clk100M;
//assign start = push_sw;

//up_control inst_up_control (.clk(clk), .start(s_start), .ta(ta), .sa(sa), .tb(tb), .sb(sb), .td(td), .dr(dr), 
up_control inst_up_control (.clk(clk), .start(start), .sa(sa), .sb(sb), .dr(dr), 
							.fs(fs), .pc(pc), .rw(rw), .mw(mw), .mb(mb), .md(md), .mm(mm), .data_in(data_in), 
							.vflg(vflg), .cflg(cflg), .nflg(nflg), .zflg(zflg));

//datapath_unit inst_datapath_unit (.clk(clk), .start(s_start), .ta(ta), .sa(sa), .tb(tb), .sb(sb), .td(td), .dr(dr), 
datapath_wrapper inst_datapath_unit (.clk(clk), .start(start), .sa(sa), .sb(sb), .dr(dr), 
								  .fs(fs), .pc(pc), .rw(rw), .mb(mb), .md(md), .mm(mm), 
								  .vflg(vflg), .cflg(cflg), .nflg(nflg), .zflg(zflg), .mw(mw),
								  .ram_data_out(data_in), .r0_out(r0_out));

//ram_512x16b inst_ram_512x16b (.address(addr_out), .clock(clk), .data(data_out), .wren(mw), .q(data_in));
//ram_512x16b inst_ram_512x16b_1 (.addra(addr_out), .clka(clk), .ena(1'b1), .dina(data_out), .wea(mw), .douta(data_in));
/*
cpu_debug u0 (
    .acq_data_in    ({mw, addr_out, data_out, data_in}),    //     tap.acq_data_in
    .acq_trigger_in (start), 		//        .acq_trigger_in
    .acq_clk        (clk)         	// acq_clk.clk
);
*/

misc_blk inst_misc_blk_1 (push_sw, clk100M, r0_out, start, clk, seg_drv, an_sel);

 ILA ILA (
.clk_0(clk),
.probe0_0(rst),
.probe1_0(pc),
.probe2_0(addr_out),
.probe3_0(data_out),
.probe4_0(data_in),
.probe5_0(dr),
.probe6_0(sa),
.probe7_0(sb),
.probe8_0(fs),
.probe9_0(rw),
.probe10_0(mb),
.probe11_0(mw),
.probe12_0(mm),
.probe13_0(md),
.probe14_0(vflg),
.probe15_0(cflg),
.probe16_0(nflg),
.probe17_0(zflg),
.probe18_0(r0_out)
);

endmodule

모든 모듈을 연결해주는 TOP모듈이다. ILA는 Intergrated Logical Analizer 모듈로, 동작하는 FPGA 내부의 신호를 모니터할 수 있는 모듈이고, Misc 모듈은 버튼과 FND 디코더가 포함된 모듈이다. 

 

Sorting App의 일부
테스트벤치 일부

메모리에 프로그램 카운터값을 넣는 신호인 mm신호가 1로 뛴 다음 제어 유닛의 데이터 입력에는 데이터 명령어가 들어와 IR에 로드된다. 위의 테스트벤치는 SORTING APP의 일부를 실행한 결과이다. 15번째 연산을 마친 이후 레지스터를 살펴보면 R7에 0x17f(383)이 저장되어 있고, R5에는 0x100(256)이 저장된 모습을 볼 수 있다. 

 

 

더보기
C83E
5F8C
9B23
8278
09FD
BBCC
0FF7
3A0A
778B
EFC4
8D12
EE69
8DF9
6F6C
7597
CF52
7BB7
D0B7
7877
7546
A5DB
89A3
F98A
472A
C277
A928
EF97
27AD
98FC
5957
A359
D69C
ECDE
D125
3AD5
6688
C0E9
FE9E
4AFD
8E62
0DFD
2BAE
22EF
71EB
5974
C6A7
2DC6
0139
EE10
0992
18F9
3367
2530
3E1F
C294
29D2
1134
20AE
C1DE
CE7E
C60D
C327
FA0A
68BA
2702
6F08
E760
8BCA
7276
AB88
4690
E100
06FC
71B5
E2BA
456D
851A
9F4B
21DE
2D8C
352A
13CD
891C
B45E
EA89
7B9A
4066
9425
65DD
BCE2
22C9
15A4
59BB
A26B
49B7
40AD
96E0
022C
235A
A50A
368B
83C3
7151
CF66
1242
7F5D
195D
0AB8
FC76
3867
DC39
C93A
2729
DCD7
335D
1658
D9EE
973C
BB82
98FA
AA6C
11B3
4646
C0B6
DAB8
2830
F15B
439E

아래는 램에 저장되어있는 랜덤한 데이터들이다. 영상을 보면 이 데이터들이 오름차순으로 잘 정렬된 것을 볼 수 있다. 

ram_16x512.coe
0.00MB
rom_28x256.coe
0.00MB

 

'Verilog' 카테고리의 다른 글

verilog 11 cpu 제작_1  (0) 2023.03.20
verilog 10 FSM/ASM  (0) 2023.03.17
verilog 9 카운터 실습  (0) 2023.03.15
verilog7  (0) 2023.03.14
verilog 5  (0) 2023.03.13