[RTL] Register Slice의 3가지 선택지

Timing Report에 빨간불이 들어오면 당장 레지스터를 하나 더 넣어서 경로를 끊고 싶어집니다. 지난 글에서 다룬 Skid Buffer는 Ready 경로를 끊으면서도 처리량을 지키는 훌륭한 방법(Full-bandwidth Skid, 즉 Full Register Slice)이었습니다.

하지만 실무에서는 무작정 Full 슬라이스를 넣기 전에, 깨진 경로가 앞(Data/Valid)인지 뒤(Ready)인지부터 명확히 구분해야 합니다. 이 글에서는 그 선택지를 Forward, Backward, Full Register Slice로 나누어 살펴보겠습니다.

1. 두 개의 타이밍 경로

Ready/Valid 핸드셰이크는 두 방향으로 흐릅니다.

  • Forward path: i_data, i_valid → o_data, o_valid
  • Backward path: i_ready → o_ready

일반적인 A → B → C 모듈 연결을 생각해 보겠습니다. 데이터는 앞으로 진행하지만, C가 뱉어내는 Ready 신호는 B를 거쳐 A까지 조합 회로(Combinational logic)를 타고 역류합니다. 데이터 쪽 라우팅이 길거나 Ready 쪽 MUX가 넓다면 해당 경로의 타이밍이 깨지게 됩니다. 단, 예외적으로 두 경로 모두 길다면 결국 Full Slice를 사용해야 합니다.

2. 잘못된 첫 처방: 나이브 파이프라인 레지스터

주니어 시절 가장 흔하게 범하는 실수는 아래와 같이 코드를 작성하는 것입니다.

always @(posedge clk) if (i_ready) begin
  o_data  <= i_data;
  o_valid <= i_valid;
end

assign o_ready = i_ready;

이 코드는 후단 Ready가 1사이클만 떨어져도 입력 윈도우가 같이 닫혀버려 파이프라인 버블을 만듭니다. 즉, 처리량(Throughput)이 하락합니다. (이는 지난 글에서 다룬 Half-bandwidth Skid와 같은 증상입니다). 예외적으로 후단 모듈이 절대 백프레셔를 걸지 않는다면 동작하겠지만, 그 전제를 코드에 명시하지 않으면 나중에 반드시 문제가 발생합니다.

3. Forward Register Slice

목적: 긴 Data/Valid 콤보 경로를 끊습니다.

핵심식: assign o_ready = i_ready || !o_valid; (비어 있거나 후단이 받아줄 때만 새 값을 캡처합니다).

지연: 1 사이클 (데이터가 슬라이스를 통과할 때)

처리량: 연속 전송이면 매 사이클 가능합니다 (버블 없음).

못 고치는 것: o_ready가 i_ready에 조합 회로로 묶여 있습니다. 따라서 Ready 신호가 크리티컬 패스라면 이 슬라이스는 무효합니다.

4. Backward Register Slice

목적: 긴 Ready 콤보 경로를 끊습니다.

동작: 홀드 레지스터가 비어 있으면 Data/Valid는 바이패스(조합 회로, 지연 0) 됩니다. 후단이 스톨을 걸어 데이터를 받지 못할 때, 들어온 1비트만 홀드 레지스터에 저장합니다.

지연: 백프레셔가 없으면 0 사이클입니다. (스톨 중이거나 직후에는 홀드를 배출합니다).

처리량: 홀드 1개로 연속 전송 유지가 가능합니다.

못 고치는 것: 바이패스 구간의 Data/Valid는 여전히 조합 회로입니다. 넓은 데이터 버스에 MUX를 사용하면 이쪽 경로가 깨질 수 있습니다. 즉, 이는 ‘급정거용 1칸’이지 파이프라인 스테이지가 아닙니다.

5. Full Register Slice (= 기존 Full-bandwidth Skid)

목적: 양 경로를 모두 자릅니다. IP 경계나 긴 거리 라우팅에 주로 사용합니다.

구조: 메인 레지스터 + 스키드 레지스터. (기존 글 코드 참조)

지연/처리량: 지연 +1 사이클에 처리량을 유지하지만, 데이터 플롭이 약 2배로 면적이 가장 큽니다.

예외: 데이터 폭이 매우 크다면(예: 512-bit 버스) Full 슬라이스를 남발할 경우 플롭 카운트가 폭발하므로 필요할 때만 써야 합니다.

// --- Forward Register Slice ---
module fwd_slice #(
    parameter int DATA_W = 8
) (
    input  logic              clk,
    input  logic              rst_n,
    input  logic              i_valid,
    input  logic [DATA_W-1:0] i_data,
    output logic              o_ready,
    output logic              o_valid,
    output logic [DATA_W-1:0] o_data,
    input  logic              i_ready
);
    // Ready is comb. We can accept data if empty, or if downstream is accepting.
    assign o_ready = i_ready || !o_valid;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            o_valid <= 1'b0;
        end else if (o_ready) begin
            o_valid <= i_valid;
            if (i_valid) begin
                o_data <= i_data;
            end
        end
    end
endmodule

// --- Backward Register Slice ---
module bwd_slice #(
    parameter int DATA_W = 8
) (
    input  logic              clk,
    input  logic              rst_n,
    input  logic              i_valid,
    input  logic [DATA_W-1:0] i_data,
    output logic              o_ready,
    output logic              o_valid,
    output logic [DATA_W-1:0] o_data,
    input  logic              i_ready
);
    logic              hold_valid;
    logic [DATA_W-1:0] hold_data;

    // Bypass if hold is empty; output hold data if full.
    assign o_valid = hold_valid ? 1'b1      : i_valid;
    assign o_data  = hold_valid ? hold_data : i_data;

    // Ready is registered. Reject next cycle if hold is full.
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            o_ready    <= 1'b1;
            hold_valid <= 1'b0;
        end else begin
            if (!hold_valid) begin
                if (i_valid && !i_ready) begin
                    hold_valid <= 1'b1;
                    hold_data  <= i_data;
                    o_ready    <= 1'b0;
                end else begin
                    o_ready <= 1'b1;
                end
            end else if (i_ready) begin
                hold_valid <= 1'b0;
                o_ready    <= 1'b1;
            end
        end
    end
endmodule

참고: ARM

유사한 게시물