본문 바로가기

실전! Verilog HDL RTL Design

[Verilog HDL] 20. 제어 가능한 ALU IP 만들기 - 최종 테스트

반응형

앞에서 수정한 레지스터 파일을 ALU core와 연결하여 최종 ALU IP를 만들어 보자. ALU core 에 필요한 모든 입출력은 apb_regfile module에서 만들어 주기 때문에 apb_regfile과 alu module을 연결하고 apb_regfile의 APB interface 신호들을 top으로 연결해 주면 alu_ip module이 완성된다. 최종 alu_ip module의 code는 아래와 같다. 파일 이름은 alu_ip.v로 저장한다.

module alu_ip  #(
	parameter ADDR_WIDTH=8,
	parameter DATA_WIDTH=16
	)
	(
	input wire PCLK,
	input wire PRESETn,
	input wire [ADDR_WIDTH-1:0] PADDR,
	input wire PSEL,
	input wire PENABLE,
	input wire PWRITE,
	input wire [DATA_WIDTH-1:0] PWDATA,
	output wire [DATA_WIDTH-1:0] PRDATA,
	output wire PREADY
	);

    wire         start;
    wire [ 7: 0] opA;
    wire [ 7: 0] opB;
    wire [ 1: 0] op;
    wire         shift_dir;
    wire [ 2: 0] shift_num;
    wire [ 1: 0] result_op;
    wire [ 7: 0] result;

    apb_regfile #(
	    .ADDR_WIDTH(ADDR_WIDTH),
	    .DATA_WIDTH(DATA_WIDTH)
    ) u_apb_regfile
	(
    .PCLK         (PCLK        ),
    .PRESETn      (PRESETn     ),
    .PADDR        (PADDR       ),
    .PSEL         (PSEL        ),
    .PENABLE      (PENABLE     ),
    .PWRITE       (PWRITE      ),
    .PWDATA       (PWDATA      ),
    .PRDATA       (PRDATA      ),
    .PREADY       (PREADY      ),
    .start        (start       ),
    .opA          (opA         ),
    .opB          (opB         ),
    .op           (op          ),
    .shift_dir    (shift_dir   ),
    .shift_num    (shift_num   ),
    .result_op    (result_op   ),
    .result       (result      )
	);

    alu u_alu (
    .clock      (PCLK     ),
    .resetn     (PRESETn  ),
    .start      (start    ),
    .opA        (opA      ),
    .opB        (opB      ),
    .opcode     (op       ),        
    .shift_num  (shift_num),
	.shift_dir  (shift_dir),
    .y          (result   ),
    .opOut      (result_op)
    );

endmodule

다음으로 alu_ip module을 테스트하기 위한 테스트 벤치를 만들어 보자. 테스트 벤치는 지난 번 apb_regfile을 테스트하기 위해 만들었던 것을 재활용 할 수 있다. 왜냐하면 alu_ip 도 APB interface를 동일하게 사용하기 때문이다. 그 때 테스트 벤치에서 apb_write와 apb_read라고 하는 task를 활용하여 apb_regfile module에 특정 값을 쓰거나 읽을 수 있었다. alu_ip를 제어하는 것도 마찬가지로 alu_ip내에 있는 register file에 쓰고 읽는 과정이기 때문에 동일한 task를 활용할 수 있다. 우리는 alu_ip의 register map을 참고하여 필요한 레지스터에 필요한 값을 쓰고 읽음으로써 alu operation을 수행하고 결과 값을 읽어 올 수 있는 것이다.

 

그러면 alu operatoin을 위한 새로운 task를 만들어 보자. 우리가 하고자 하는 alu operation에 필요한 파라메터를 생각해 보면 opcode와 opcode에 필요한 operand 값이 필요할 것이다.

 

필요한 파라메터를 입력 받아 이를 APB interface를 통해 쓰고 읽는 task를 만들어 ALU operation을 수행해 보자. 그러면 다음과 같은 task를 만들 수 있다.


parameter CONTROL   = 8'h0;
parameter OPERAND   = 8'h1;
parameter OPCODE    = 8'h2;
parameter SHIFTCON  = 8'h3;
parameter STATUS    = 8'h4;
parameter RESULT    = 8'h5;

task alu_task(
	input [1:0] op,
	input [7:0] opA,
	input [7:0] opB,
	output reg [1:0] result_op,
	output reg [7:0] result
);
begin
    apb_write(OPERAND, {opA,opB});
    apb_write(OPCODE, {14'b0, op});
    if (op == LSHIFT || op ==RSHIFT)
	    apb_write(SHIFTCON, {8'h0,opB});
    apb_write(CONTROL, 16'b1);
    apb_read(STATUS);
    result_op = prdata[1:0];
    apb_read(RESULT);
    result = prdata[7:0];
end
endtask

입력으로 2bit opcode 'op', 그리고 8bit operand 'opA', 'opB'를 받는다.

그리고 출력으로 2bit 결과 값을 연산한 'result_op'와 8bit 결과 값 'result'를 출력한다.

 

레지스터 파일의 주소를 parameter로 선언하여 가독성을 좋게 한다.

 

17 line

apb_write task를 이용하여 OPERAND register에 operand A와 operand B값을 쓴다.

18 line

apb_write task를 이용하여 OPCODE register에 opcode를 쓴다.

19 line

opcode가 left shift이거나 right shift일 경우 apb_write task를 이용하여 shift number를 SHIFTCON레지스터에 써준다.

21 line

CONTROL register 의 start bit에 '1'을 써서 ALU를 동작시킨다.

22 line

연산 결과를 수행한 opcode를 가져오기 위해 STATUS register를 읽는다.

apb_read task를 수행하고 prdata를 통해 값을 가져올 수 있다. STATUS 레지스터의 [1:0] bit가 결과 값을 수행한 op code이므로 이를 result_op output으로 출력한다.

24 line

연산 결과 값을 가져오기 위하여 RESULT 레지스터를 읽는다. 마찬가지로 apb_read task를 이용한다. 레지스터 맵을 참조하면 [7:0] bit가 결과 값이므로 이를 result output으로 출력한다.

 

위의 task를 이용한 전체 testbench는 아래와 같다. 파일 이름은 test.v로 저장한다.

`timescale 1ns/1ns
 
module test;

parameter ADDR_WIDTH = 8;
parameter DATA_WIDTH = 16;

// delare variables
reg pclk;
reg presetn;
reg [ADDR_WIDTH-1:0] paddr;
reg [DATA_WIDTH-1:0] pwdata;
reg psel;
reg penable;
reg pwrite;
wire pready;
wire [DATA_WIDTH-1:0] prdata;
reg [1:0] result_op;
reg [7:0] result;
 


parameter CONTROL   = 8'h0;
parameter OPERAND   = 8'h1;
parameter OPCODE    = 8'h2;
parameter SHIFTCON  = 8'h3;
parameter STATUS    = 8'h4;
parameter RESULT    = 8'h5;

parameter ADD    = 2'b00;
parameter SUB    = 2'b01;
parameter RSHIFT = 2'b10;
parameter LSHIFT = 2'b11;


// clock generation
always #10 pclk = ~pclk;

task apb_write (
	input  [ADDR_WIDTH-1:0] addr,
	input  [DATA_WIDTH-1:0] data
);
begin
	psel <= 1;
	pwrite <= 1;
	@(posedge pclk);
	penable <= 1;
	paddr <= addr;
	pwdata <= data;
	@(posedge pclk);
	while(pready == 0) @(posedge pclk);
	psel <= 0;
	penable <= 0;
	pwrite <= 0;
end
endtask

task apb_read (
	input  [ADDR_WIDTH-1:0] addr
	);
begin
	psel <= 1;
	pwrite <= 0;
	@(posedge pclk);
	penable <= 1;
	paddr <= addr;
	@(posedge pclk);
	while(pready == 0) @(posedge pclk);
	psel <= 0;
	penable <= 0;
end
endtask

task alu_task(
	input [1:0] op,
	input [7:0] opA,
	input [7:0] opB,
	output reg [1:0] result_op,
	output reg [7:0] result
);
begin
	apb_write(OPERAND, {opA,opB});
	apb_write(OPCODE, {14'b0, op});
	if (op == LSHIFT || op ==RSHIFT)
	    apb_write(SHIFTCON, {8'h0,opB});
    apb_write(CONTROL, 16'b1);
    apb_read(STATUS);
	result_op = prdata[1:0];
	apb_read(RESULT);
	result = prdata[7:0];
end
endtask
 
alu_ip #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(DATA_WIDTH)) u_alu_ip (
	.PCLK		(pclk),
	.PRESETn	(presetn),
	.PADDR		(paddr),
	.PSEL		(psel),
	.PENABLE	(penable),
	.PWRITE		(pwrite),
	.PWDATA		(pwdata),
	.PRDATA		(prdata),
	.PREADY		(pready)
);
 
 
// create wave dump file
initial
begin
    $dumpfile("alu_ip.vcd");
    $dumpvars(0, test);
end
 
// input stimulus
initial
begin
    pclk <= 0;
    presetn <= 0;
	paddr<=0;
	pwdata<=0;
	psel<=0;
	penable<=0;
	pwrite<=0;
    #100;
    presetn <= 1;
	alu_task(ADD,    8'h1,8'h3,result_op, result);
	if (result != 4)
		$display("Error");
	else
		$display("OK : result is %d", result);
	alu_task(SUB,    8'h5,8'h2,result_op, result);
	if (result != 3)
		$display("Error");
	else
		$display("OK : result is %d", result);
	alu_task(RSHIFT, 8'h4,8'h1,result_op, result);
	if (result != 2)
		$display("Error");
	else
		$display("OK : result is %d", result);
	alu_task(LSHIFT, 8'h4,8'h1,result_op, result);
	if (result != 8)
		$display("Error");
	else
		$display("OK : result is %d", result);
	#200;
    $finish();
end
 
endmodule

 

alu_task를 이용하여 손쉽게 필요한 연산을 수행할 수 있다. 연산 수행후 결과 값이 맞는 지 검사하는 코드를 추가하였다. if 문과 $display함수를 사용하여 결과 값이 맞는 지 쉽게 확인할 수 있는 메시지를 출력하도록 하였다.

 

테스트를 수행하기 위한 script는 아래와 같이 만들 수 있다. 아래의 내용을 파일에 저장하고 이름을 'run'으로 저장한다. 그리고 리눅스 코맨드 상에서 chmod +x run으로 실행 파일을 만든 뒤 리눅스 코맨드 창에서 ./run 으로 테스트를 수행한다. 필요한 verilog code는 testbench와 alu를 구성하고 있는 RTL code file이고 alu.v adder.v subtractor.v barrel_shifter.v apb_regfile.v regfile.v는 이전 장에서 작성한 code를 사용하면 된다.

다음은 ./run 을 수행한 뒤 출력되는 메시지이다.

alu_ip.vcd 파일을 gtkwave로 열어서 wave form을 확인해 보면 아래와 같다.

alu_task를 이용하여 이 밖에 다른 연산들도 테스트 해 보시기 바랍니다.

 

반응형