본문 바로가기

Verilog HDL 설계

generate와 반복문

이번 포스트는 generate와 반복문을 다룬다.

 

Verilog를 쓰다보면 1000개쯤 되는 reg 변수들을 초기화한다고 하는 반복적인 상황에 처할 때가 있다.

전체 코드에서 대부분을 재활용하고 나머지 부분을 용도에 따라 다르게 사용해야 하는 경우도 있다.

 

전자의 상황에서는 반복문을 쓰는게 쓰기도 편하고 가독성도 좋다. 후자에서는 generate를 사용하면 편하게 사용이 가능하다.

 

반복문의 경우 흔히 C에서 사용하는 for, while 등과 생소한 forever, repeat가 Verilog에서 사용된다.

4개 가운데 for문이 자주 사용되는데, 오늘은 이 for문을 주로 살펴볼 것이다. 

합성 가능한 for문은 아래와 같은 경우에 사용한다.

1. 반복적인 병렬 assignment 구문

2. 반복적인 병렬 모듈 인스턴스 선언

 

아래와 같이 사용하면 된다.

 

module gen_ff_0(
    output [5:0] Q,
    input clk, rst,
    input [8*6-1:0] Data
);

wire [7:0] DataChunk [0:5];

genvar i;
// generate
for (i=0;i<6;i=i+1) begin : FF_chunk
    ff8b FF(DataChunk[i], clk, rst, Data[i*8 +: 8]);
end
// endgenerate

/* another assignments */

endmodule

module gen_ff_1(
    output [5:0] Q,
    input clk, rst,
    input [8*6-1:0] Data
);

reg [7:0] DataChunk [0:5];

genvar i;
// generate
for (i=0;i<6;i=i+1) begin
    always @ (posedge clk) begin
        if (rst) begin
            DataChunk[i] <= 8'b0;
        end
        else begin
            DataChunk[i] <= Data[i*8 +: 8];
        end
    end
end
// endgenerate

/* another assignments */

endmodule

module for_assign(
    output [7:0] Mux_out, 
    input [8*16-1:0] Data,
    input [3:0] sel
);    // Mux example

wire [7:0] Data_Array [0:15];

genvar i;
// generate
for (i=0;i<16;i=i+1) begin
    assign Data_Array[i] = Data[i*8 +: 8];
end
// endgenerate

assign Mux_out = Data_Array[sel];

endmodule

 

generate는 endgenerate와 같이 쓰이며, 반복적인 assignment를 쓰는데는 굳이 필요없어서 주석처리했다.

genvar는 generate하고자 하는 assignment들을 구분하는데 쓰이는 index용 변수다.

첫번째 모듈의 for loop의 begin 옆에 ": FF_chunk"는 해당 loop에서 선언된 모듈 인스턴스에 접근하기 위한 block의 name을 선언한 것이다. block name을 선언하면 루프 내의 모듈 인스턴스를 접근할 수 있게된다. "FF_chunk[2].FF"는 DataChunk[2]를 가지고 있는 플립플롭에 접근하는 것이다. 

for 루프의 begin 블록 하나당 name을 정할 수 있으며 점(.)을 통해 하위 인스턴스로 접근 가능하다. 

 

Generate를 파라미터 조건에 따라 사용하는 방법도 있다. 서로 다른 모듈들을 조건에 따라 바꿔가며 사용하는 경우에 쓰인다. if-else-if 또는 case문에서 사용이 가능하며 아래와 같이 작성하면 된다. 

 

module gen_if
#(parameter MODE = 0)
(
    output [3:0] S,
    output Cout,
    input Cin,
    input [3:0] A, B
);

generate
    if (MODE == 0) begin : ADD4
        RCA4b ADD(S, Cout, Cin, A, B);
    end
    else if (MODE == 1) begin : ADD4
        CLA4b ADD(S, Cout, Cin, A, B);
    end
endgenerate

endmodule

module gen_case
#(parameter MODE = 0)
(
    output [3:0] S,
    output Cout,
    input Cin,
    input [3:0] A, B
);

generate
    case(MODE)
        0: begin: ADD4 
            RCA4b ADD(S, Cout, Cin, A, B);
        end
        1: begin: ADD4
            CLA4b ADD(S, Cout, Cin, A, B);
        end
    endcase
endgenerate

endmodule

 

공식 문서에서는 각 조건에서의 block name을 같은 것으로 사용하는 것으로 나오는데, 다른 이름을 사용하면 파라미터가 다른 모듈에 접근하기가 힘들기 때문인 것으로 보인다.

모듈 인스턴스를 선언할때 기존 선언과 같은 문법을 따르므로(파라미터 변경 등이 가능하다) 참고하자.

 

always block 안에서의 반복문도 가능하다. 차이점은 인스턴스화가 불가능하다는 것과 genvar를 사용하지 못한다는 것이다.

최근 포스트 중 하나인 dct 구현에서도 반복적인 초기화 구문 64개가 tp_mem.v에 포함되어있다. tp_mem_old와 tp_mem 간 always block을 비교하며 보면 된다.

 

module tp_mem
#(parameter BW = 8)
(
    output [BW-1:0] mem_out,
    output reg enb_out,
    input  [BW-1:0] mem_in,
    input  clk, rst, enb
);

/* ------------------------- */

integer i, j;

always @ (posedge clk) begin
    if (rst) begin
        for (i=0;i<8;i=i+1) begin
            for (j=0;j<8;j=j+1) begin
                data[j][i] <= 0;
            end
        end
    end
    else if (odd) begin
        data[cnt_row][cnt_col] <= mem_in;
    end
    else begin
        data[cnt_col][cnt_row] <= mem_in;
    end
end

endmodule

module tp_mem_old
#(parameter BW = 8)
(
    output [BW-1:0] mem_out,
    output reg enb_out,
    input  [BW-1:0] mem_in,
    input  clk, rst, enb
);

/* ------------------------- */

always @ (posedge clk) begin
    if (rst) begin
        data[0][0] <= 0;
        data[1][0] <= 0;
        data[2][0] <= 0;
        data[3][0] <= 0;
        data[4][0] <= 0;
        data[5][0] <= 0;
        data[6][0] <= 0;
        data[7][0] <= 0;
        data[0][1] <= 0;
        data[1][1] <= 0;
        data[2][1] <= 0;
        data[3][1] <= 0;
        data[4][1] <= 0;
        data[5][1] <= 0;
        data[6][1] <= 0;
        data[7][1] <= 0;
        data[0][2] <= 0;
        data[1][2] <= 0;
        data[2][2] <= 0;
        data[3][2] <= 0;
        data[4][2] <= 0;
        data[5][2] <= 0;
        data[6][2] <= 0;
        data[7][2] <= 0;
        data[0][3] <= 0;
        data[1][3] <= 0;
        data[2][3] <= 0;
        data[3][3] <= 0;
        data[4][3] <= 0;
        data[5][3] <= 0;
        data[6][3] <= 0;
        data[7][3] <= 0;
        data[0][4] <= 0;
        data[1][4] <= 0;
        data[2][4] <= 0;
        data[3][4] <= 0;
        data[4][4] <= 0;
        data[5][4] <= 0;
        data[6][4] <= 0;
        data[7][4] <= 0;
        data[0][5] <= 0;
        data[1][5] <= 0;
        data[2][5] <= 0;
        data[3][5] <= 0;
        data[4][5] <= 0;
        data[5][5] <= 0;
        data[6][5] <= 0;
        data[7][5] <= 0;
        data[0][6] <= 0;
        data[1][6] <= 0;
        data[2][6] <= 0;
        data[3][6] <= 0;
        data[4][6] <= 0;
        data[5][6] <= 0;
        data[6][6] <= 0;
        data[7][6] <= 0;
        data[0][7] <= 0;
        data[1][7] <= 0;
        data[2][7] <= 0;
        data[3][7] <= 0;
        data[4][7] <= 0;
        data[5][7] <= 0;
        data[6][7] <= 0;
        data[7][7] <= 0;
    end
    else if (odd) begin
        data[cnt_row][cnt_col] <= mem_in;
    end
    else begin
        data[cnt_col][cnt_row] <= mem_in;
    end
end

endmodule

 

두 always block은 동일하게 동작한다. 주의할 점은 always 내부 루프에는 genvar 대신 integer를 써야한다는 것이다. genvar를 사용하면 컴파일 단계에서부터 에러가 난다.

 

공식 문서 12.4Genarate constructs을 참고하면 된다.