[Verilog 문법] 2.3 Procedural block, 할당 방식

YUN·2026년 1월 21일

디지털 회로 설계

목록 보기
8/20
post-thumbnail

1. Procedural block(절차적 블록)

어떤 이벤트(신호 변화/클록 에지)나 시뮬레이션 시작 시점에 의해 실행이 트리거되고,
블록 내부 문장이 위에서 아래로 순서대로 수행되는 코드 영역

Verilog 에서는

  • initial ... end
  • always ... end

블록이 Procedural block으로 동작한다.

이 블록 안에서는 if, case, for, repeat 같은 제어문을 사용해 조건/반복 로직을 기술하고,

=(blocking), <=(non-blocking) 같은 절차적 대입(procedural assignment)을 수행한다.

(Procecural block 내부에서 reg 에만 값을 대입할 수 있고, wire 연결은 불가하다)

2. 할당 방식

값을 할당하는 방식

Verilog 에는 2가지 할당 방식 (Blocking, Non Blocking)이 있고, 둘의 동작 방식이 완전히 다르다.

3. Blocking 할당 (=)

순차적으로 실행되는 할당

코드가 위에서부터 아래로 순서대로 실행되고, 할당 즉시 값이 반영된다.

다음 문장의 실행을 막고(block) 현재 문장을 완료한 후 진행하기 때문에 Blocking 할당이라 부른다.

예를들어, 아래와 같이 동작한다.

always @(*) begin
    a = 1;      // 1) a에 1 할당 (즉시 반영)
    b = a;      // 2) b에 a(=1) 할당
    c = b;      // 3) c에 b(=1) 할당
end
// 결과: a=1, b=1, c=1

4. Non-blocking 할당 (<=)

Non-blocking = 동시 실행, 지연 반영

모든 우변을 동시에 읽고, 블록 끝에서 한꺼번에 좌변에 Update 한다.

정확히 말하면 우변을 동시에 읽고, NBA Region 에서 값을 업데이트한다.

NBA Region 이란 같은 시뮬레이션 시간(time slot) 안에서 이벤트들을 처리할 때 쓰는 “실행 단계” 중 하나로,

Non-Blocking Assignment (<=)의 좌변 업데이트를 수행하는 단계이다.

다음 문장의 실행을 막지 않고(non-block) 동시에 진행하므로 Non-blocking 할당이라 부른다.

예를들어, 아래와 같이 동작한다.

always @(posedge clk) begin
    a <= 1;     // 1) 우변 평가: 1
    b <= a;     // 2) 우변 평가: a의 "현재" 값 (아직 이전 값!)
    c <= b;     // 3) 우변 평가: b의 "현재" 값 (아직 이전 값!)
end
// 블록 끝에서 동시에 할당

이때 헷갈리기 쉬운점이 Non-blocking을 쓰더라도 Procedural block 에서 위에서

아래로 코드가 실행된다는 점은 변하지 않는다.

정확히말하면 우변 평가후, Update위->아래 순서로 일어나게된다.

always @(posedge clk) begin
  a <= b;
  a <= c;
end

위의 코드를 실행하면 결국 a 에는 c가 저장된다.

Non-blocking 으로 a에 값을 대입해도 결국 더욱 뒤에 실행되는 a <= c; 에 의해

앞에서 실행 되는 a <= b;가 덮어씌워지게된다.

5. 사용 예시 : 3단 시프트

3단 시프트를 Blocing 할당, Non-blocking 할당으로 구현해보자

// Blocking (=)
always @(posedge clk) begin
    a = d;
    b = a;
    c = b;
end

// Non-blocking (<=)
always @(posedge clk) begin
    a <= d;
    b <= a;
    c <= b;
end

Blocking으로 구현시 a,b,c가 전부 d값이 되므로 3단 시프트로 볼 수없다.

반면, 아래의 코드는 Non blocking Assignment 덕분에 3단 시프트가 성공적으로 구현된다.

6. 중요 규칙 🌟🌟🌟🌟🌟🌟🌟

  • 조합 논리: always @(*) 에서는 Blocking 할당(=) 사용
  • 순차 논리: always @(posedge clk) 에서는 Non-Blocking 할당(<=) 사용
  • wire 타입은 Procedural block 밖에서 assign 또는 Gate primitive 출력으로만 값 대입한다
  • Procedural block(initial 이나 always block) 내부에서는 reg 에만 값을 할당한다.
    • wire에 값 할당하는거 문법적으로는 가능한데 사용하지 않는다.
    • 대입 방식은 Blocking 이든, Non-Blocking 이든 관계없다

예를들어

// 조합 논리 - Blocking (=) ✅
always @(*) begin
    sum = a + b;
    carry = (a + b) > 255;
end

// 순차 논리 - Non-blocking (<=) ✅
always @(posedge clk) begin
    q1 <= d;
    q2 <= q1;
    q3 <= q2;
end

위와 같이 조합이면 Blocking, 순차면 Non-Blocking 사용해야 올바르게 기능이 구현된다.

(1) 잘못된 예시

// ❌ 순차 논리에 Blocking
always @(posedge clk) begin
    a = d;      // ❌ 위험!
    b = a;      // ❌ 의도와 다르게 동작
end

// ❌ 조합 논리에 Non-blocking
always @(*) begin
    sum <= a + b;   // ❌ 시뮬레이션 문제 발생
end

// ❌ 같은 블록에서 혼용
always @(posedge clk) begin
    a = d;      // ❌ Blocking
    b <= a;     // ❌ Non-blocking 혼용!
end

(2) 상황에 따라 다른 할당문을 쓰는 이유

그렇다면 굳이 복잡하게 왜 이렇게 나누어야할까??

이는 하드웨어적 관점에서 이해할 수 있다.

조합 논리는 Clock 없이 연속적 할당으로 구현되고, 이는 HW적 연결을 의미한다.

따라서 Blocking으로 각각의 신호선에 연속적으로 값을 할당하는 것이 실제 동작과 일치한다.

출력 <---------AND_Gate--OR_Gate---- 입력

를 생각해보면 된다.

순차 논리(예를들어 카운터)는 모든 FF가 동시에 클럭 엣지에서 동작한다.

따라서 Non-blocking 으로 한 번에 레지스터 값들을 업데이트 하는것이 실제 동작과 일치한다.

7. 스펙보고 조합/순차 판단법

스펙상 이전 입력/이전 출력을 기억해야 동작하면 순차 논리이고, 그렇지 않으면 조합 논리이다.

8. 예시

(1) 조합 회로 예시

조합 회로의 대표적인 예시로 ALU가 있다.

module alu (
    input  wire [7:0] a, b,
    input  wire [1:0] op,
    output reg  [7:0] result,
    output reg        zero
);

    always @(*) begin
        // Blocking 사용 ✅
        case (op)
            2'b00: result = a + b;
            2'b01: result = a - b;
            2'b10: result = a & b;
            2'b11: result = a | b;
        endcase
        
        zero = (result == 8'b0);  // result 값 사용 가능!
    end

endmodule

(2) 순차 회로 예시

순차 회로의 대표적인 예시로 파이프라인(Pipeline)이 존재한다.

module pipeline_3stage (
    input  wire       clk,
    input  wire       rst_n,
    input  wire [7:0] data_in,
    output reg  [7:0] data_out
);

    reg [7:0] stage1, stage2;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            stage1   <= 8'b0;
            stage2   <= 8'b0;
            data_out <= 8'b0;
        end
        else begin
            // Non-blocking 사용 ✅
            stage1   <= data_in;    // 1단계
            stage2   <= stage1;     // 2단계
            data_out <= stage2;     // 3단계
        end
    end

endmodule

이렇게 각 stage의 레지스터가 동시에 이전 값을 샘플한다 -> 3단 파이프라인 정상 동작 ✅

profile
안녕하세요. 전자공학부 학부생의 공부 기록입니다.

0개의 댓글