본문 바로가기

실전! Verilog HDL RTL Design

[Verilog HDL] 13. Register File

반응형

이번 장에서는 레지스터 파일(register file)이 무엇인지 알아보자. 결론부터 말하면 레지스터 파일은 작은 메모리라고 생각하면 된다. 이전 장에서 D flip-flop이 무엇인지 배웠다. D flip-flop (D F/F)은 1bit의 정보를 저장하는 저장소라고 생각할 수 있다. 아래와 같은 심볼로 표시되며 클록의 상승 엣지(positive edge)에서 입력 포트 D로 들어오는 데이터를 출력 포트 Q로 출력한다. Q 값은 D 입력이 변하기 전까지 계속 같은 값을 유지하게 된다. RSTn과 같은 리셋 핀이 있는 경우 RSTn이 '0'일 경우 출력 Q는 '0'으로 초기화 된다.

 

이러한 D F/F을 여러개 연결한 것을 레지스터(register)라고 한다. 아래의 예는 D F/F 4개를 연결한 것으로 4bit의 데이터를 입력으로 받아 저장할 수 있기 때문에 4bit register라고 할 수 있다. 레지스터는 아래 그림의 화살표 아래의 심볼 처럼 간략화 하여 표시할 수 있다.

위의 심볼은 입력 포트 DIN으로 4bit의 데이터를 받아 클록의 상승 엣지에서 출력 포트 DOUT으로 출력하는 레지스터를 의미한다. 마찬가지로 입력이 클록의 상승에지에서 변하기 전까지 DOUT 값이 유지된다.

 

이러한 레지스터를 여러개 모아 놓은 것이 레지스터 파일이다. 아래 그림은 4bit register 4개를 모아 놓은 레지스터 파일을 나타낸다. 각각의 레지스터를 지정하기 위하여 주소 값이 필요하고 이는 ADDR이라고 하는 입력 포트를 통해 받는다. 0부터 3까지 4개의 주소로 각각의 레지스터를 지정할 수 있으므로 어드레스는 2bit가 필요하다. (2^2 = 4) 이렇게 보면 4bit 데이터 4개를 저장할 수 있는 메모리의 기능을 할 수 있는 것을 알 수 있다.

즉, 설계자는 ADDR 포트로 어드레스를 지정하여 원하는 레지스터에 입력 데이터를 써 넣을 수 있고  읽을 수 있는데 어떤 경우에 쓰고 어떤 경우에 읽는지를 구분하기 위하여 WE(Write Enable)같은 신호를 사용한다. 즉 WE가 '1'이면 write, '0'이면 read 기능을 하도록 정의할 수 있고 이는 설계자가 정하기 나름이다.

 

실제 레지스터 파일을 구현하기 위해서는 이처럼 입력 어드레스와 write enable신호에 따라서 쓰기일 경우 입력 데이터를 어떤 레지스터에 쓸 것인지, 읽기 일 경우 어떤 레지스터의 데이터 출력을 DOUT으로 출력할지 결정하는 로직을 설계하여야 한다. 

 

그러면 먼저 쓰기 로직을 설계하여 보기로 한다.

 

우선 verilog에서 배열을 선언하는 방법에 대하여 알아보자.  예를 들어 4bit의 wire 변수 10개를 원소로 갖는 배열을 선언한다면 아래와 같다.

wire [3:0] aaa [0:9];

[3:0]는 4bit 변수라는 것을 나타내고 [0:9]는 인덱스가 0에서 부터 9라는 것을 나타낸다. 각각의 원소를 나타내는 방법은 

aaa[0], aaa[1], aaa[2]...이런 식으로 나타낸다. 8번 째 원소는 aaa[7]이 되는 것이다. (0부터 시작하므로)

 

배열은 wire, reg, integer등의 데이터 타입에 동일하게 사용할 수 있다.

 

위의 예에서 레지스터 4개가 있으므로 각각의 레지스터 입력에 연결할 4bit data 신호 4개를 wire 타입으로 아래와 같이 선언 할 수 있다.

wire [3:0] reg_din[0:3];

reg_din[0]는 첫번 째 레지스터 데이터 입력, reg_din[1]은 두번째 레지스터의 데이터 입력, 이런 식으로 연결할 것이다. 이러한 코드는 case문을 이용하면 쉽게 작성할 수 있다.  


always @(*)
begin
case(ADDR)
   2'b00:
      reg_din[0] = DIN;
   2'b01:
      reg_din[1] = DIN;
   2'b10:
      reg_din[2] = DIN;
   2'b11:
      reg_din[3] = DIN;
   default :
      reg_din[0] = 0;
      reg_din[1] = 0;
      reg_din[2] = 0;
      reg_din[3] = 0;
endcase
end

이러한 로직은 입력 하나를 여러 개의 출력 신호 중 하나로 연결하여 주는 것으로 de-multiplex라고도 한다. 이는 mux의 반대 개념이다. 여기서 주의할 것은 반드시 default문을 지정하여 주어야 한다. 왜냐하면 각 reg_din 변수에 대해 모든 case에 대해 assign을 지정해 주지 않으면 합성 과정에서 latch로 합성 되기 때문이다. 조홥회로로 설계하고자 한 것과 다른 결과이므로 항상 조합회로를 설계할 때 case문에는 default문을 사용하는 것이 좋다.

 

다음는 4개의 레지스터 출력 중 하나늘 ADDR에 따라 하나의 출력 포트에 연결하는 로직을 설계해 보자. 이는 이전에도 설계했던 mux 로직과 동일하다.

wire [3:0] reg_dout [0:3];
always@(*)
begin
case(ADDR)
   2'b00 :
      DOUT = reg_dout[0];
   2'b01 :
      DOUT = reg_dout[1];
   2'b10 :
      DOUT = reg_dout[2];
   2'b11 :
      DOUT = reg_dout[3];
   default :
      DOUT = 0;
endcase
end

다음은 실제 4bit 레지스터 4개 묶음을 어떻게 만드는 지 살펴보자.

reg 타입의 배열로 아래와 같이 선언한다.

reg [3:0] mem [0:3];

그리고 각각의 레지스터에 쓰고 읽는 로직은 아래와 같다.

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[0] <= 0;
   else if (WE & (ADDR == 2'b00))
      mem[0] <= reg_din[0];
end

assign reg_dout[0] = mem[0];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[1] <= 0;
   else if (WE & (ADDR == 2'b01))
      mem[1] <= reg_din[1];
end

assign reg_dout[1] = mem[1];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[2] <= 0;
   else if (WE & (ADDR == 2'b10))
      mem[2] <= reg_din[2];
end

assign reg_dout[2] = mem[2];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[3] <= 0;
   else if (WE & (ADDR == 2'b11))
      mem[3] <= reg_din[3];
end

assign reg_dout[3] = mem[3];

전체 코드는 아래와 같다.

module regfile (
	input wire CLK,
	input wire RSTn,
	input wire [1:0] ADDR,
	input wire WE,
	input wire [3:0] DIN,
	output reg [3:0] DOUT
	);

reg [3:0] reg_din [0:3];
reg [3:0]  mem [0:3];
wire [3:0] reg_dout [0:3];

always @(*)
begin
	case(ADDR)
	   2'b00:
	      reg_din[0] = DIN;
	   2'b01:
	      reg_din[1] = DIN;
	   2'b10:
	      reg_din[2] = DIN;
	   2'b11:
	      reg_din[3] = DIN;
	   default :
	   begin
	      reg_din[0] = 0;
	      reg_din[1] = 0;
	      reg_din[2] = 0;
	      reg_din[3] = 0;
	   end
	endcase
end


always @(*)
begin
	case(ADDR)
	   2'b00 :
	      DOUT = reg_dout[0];
	   2'b01 :
	      DOUT = reg_dout[1];
	   2'b10 :
	      DOUT = reg_dout[2];
	   2'b11 :
	      DOUT = reg_dout[3];
	   default :
	      DOUT = 0;
	endcase
end

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[0] <= 0;
   else if (WE & (ADDR == 2'b00))
      mem[0] <= reg_din[0];
end

assign reg_dout[0] = mem[0];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[1] <= 0;
   else if (WE & (ADDR == 2'b01))
      mem[1] <= reg_din[1];
end

assign reg_dout[1] = mem[1];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[2] <= 0;
   else if (WE & (ADDR == 2'b10))
      mem[2] <= reg_din[2];
end

assign reg_dout[2] = mem[2];

always @(posedge CLK or negedge RSTn)
begin
   if (!RSTn)
      mem[3] <= 0;
   else if (WE & (ADDR == 2'b11))
      mem[3] <= reg_din[3];
end

assign reg_dout[3] = mem[3];

endmodule

이를 시뮬레이션 하기 위한 테스트벤치 코드는 아래와 같다.

`timescale 1ns/1ns
 
module test;

// delare variables
reg clock;
reg resetn;
reg [1:0] addr;
reg [3:0] din;
reg we;
 
wire [3:0] dout;
 
// clock generation
always #10 clock = ~clock;
 
regfile u_regfile (
    .CLK      (clock),
    .RSTn     (resetn),
    .ADDR     (addr),
    .WE       (we),
    .DIN      (din),
    .DOUT     (dout)
);
 
 
// create wave dump file
initial
begin
    $dumpfile("regfile.vcd");
    $dumpvars(0, test);
end
 
// input stimulus
initial
begin
    clock <= 0;
    resetn <= 0;
    we <= 0;
    #100;
    resetn <= 1;
    @(posedge clock);
    addr <= 2'd0;
    din <= 4'd1;
    we  <= 1;
    @(posedge clock);
    addr <= 2'd1;
    din <= 4'd2;
    @(posedge clock);
    addr <= 2'd2;
    din <= 4'd3;
    @(posedge clock);
    we <= 0;
    addr <= 0;
    @(posedge clock);
    addr <= 1;
    @(posedge clock);
    addr <= 2;
    #100;
    $finish();
end
 
endmodule

시뮬레이션 하기 위해 아래와 같이 우분투 터미널에 입력한다.

>>iverilog regfile.v test.v

그러면 a.out이라는 실행 파일이 생성되고 이를 실행시키면 시뮬레이션이 진행되고 regfile.vcd wavform file이 생성된다.

>>./a.out

아래와 같이 gtkwave를 이용하여 vcd file을 열어본다.

>>gtkwave regfile.vcd

신호들을 찍어 보면 아래와 같은 waveform을 볼 수 있을 것이다.

참고로 멀티비트 데이터는 십진수로 표시하게 하였다. 위 그림에서 클록의 상승 엣지에서 0,1,2 주소에 1,2,3을 쓰고(WE=1) WE가 0일 때 출력 DOUT으로 주소에 해당하는 레지스터 값이 출력 되는 것을 볼 수 있다.

반응형