본문 바로가기

Verilog HDL 설계

코드 작성을 위한 몇가지 팁(1)

이때까지 회로설계를 하면서 편하게 그리고 팀원과 같이 프로젝트를 진행할때 썼던 팁을 써본다.

 

1. always @ (*) 사용 자제

 

앞선 글에서 언급했듯이 always @ (*)을 통해 회로를 설계를 하는 것은 좋지 않다.

always문 안에서는 reg 변수만이 쓰일 수 있기 때문이다. reg 변수는 특성상 값을 hold하게 되는데 if else나 case문 같은 조건문을 사용할 때 예외경우를 명확하게 정의하지 않으면 값을 hold하게 된다.

 

module mux_example(
    input [1:0] in0, in1, in2,
    input [1:0] sel,
    output reg [1:0] out
);

always @ (*) begin
    if (sel == 2'b00) begin
        out = in0;
    end
    else if (sel == 2'b10) begin
        out = in1;
    end
    else if (sel == 2'b11) begin
        out = in2;
    end
end
// unexpected latch problem occurs!

endmodule

 

위의 예시의 경우 else를 통한 예외 경우 처리가 없게 되어 예외 경우(이때는 sel == 2'b01) 상황에서 out의 값이 이전 값으로 hold된다. 이로 인해 합성 툴은 latch가 필요한 것으로 판단하여 합성 시에 latch를 해당 모듈에 사용한다. 그 결과 pre-layout simulation을 하게 되면 combinational logic이어야 할 MUX가 한 클락 신호에 의해 지연되는 것을 확인할 수 있다.

 

이 문제를 해결하기 위해서는 조건문 if else 또는 case 문을 쓰는게 아닌 조건 연산자를 사용하거나, 혹은 조건문에서 아래 예시와 같이 예외 경우를 반드시 포함하도록 설계해야한다.

 

module mux_fixed_1(
    input [1:0] in0, in1, in2,
    input [1:0] sel,
    output reg [1:0] out
);

always @ (*) begin
    if (sel == 2'b00) begin
        out = in0;
    end
    else if (sel == 2'b01) begin
        out = in1;
    end
    else if (sel == 2'b10) begin
        out = in2;
    end
    else begin
        out = 2'b0;
    end
end

endmodule

module mux_fixed_2(
    input [1:0] in0, in1, in2,
    input [1:0] sel,
    output reg [1:0] out
);

always @ (*) begin
    case (sel)
        2'b00: out = in0;
        2'b01: out = in1;
        2'b10: out = in2;
        default: out = 2'b0;
    endcase
end

endmodule

module mux_fixed_3(
    input [1:0] in0, in1, in2,
    input [1:0] sel,
    output [1:0] out
);

wire [1:0] tmp0, tmp1;

assign tmp0 = (sel[0]) ? in1 : in0;
assign tmp1 = (sel[0]) ? 2'b0 : in2;
assign out = (sel[1]) ? tmp1 : tmp0;

endmodule

 

2. -: 와 +:

 

어떤 32비트 변수를 8비트씩 나눠서 사용한다고 할때, -: 와 +: 를 통해 가독성이 좋게 설계할 수 있다. 아래의 두 예시는 똑같은 코드다.

 

module conf(
    input [31:0] data_in,
    output [7:0] data_out;
);

assign data_out = data_in[31:24] ^
                  data_in[23:16] ^
                  data_in[15:8]  ^
                  data_in[7:0];
                  
endmodule

module same(
    input [31:0] data_in,
    output [7:0] data_out;
);

assign data_out = data_in[31 -: 8] ^
                  data_in[23 -: 8] ^
                  data_in[15 -: 8] ^
                  data_in[7  -: 8];
/*                  
assign data_out = data_in[24 +: 8] ^
                  data_in[16 +: 8] ^
                  data_in[8  +: 8] ^
                  data_in[0  +: 8];
*/                
endmodule

 

data_in[31 -: 8]은 data_in의 31번 비트부터 24번 비트까지 총 8개의 비트를 사용한다는 의미다.

마찬가지로 data_in[24 +: 8]은 data_in의 24번 비트부터 31번 비트까지 총 8개의 비트를 사용한다는 의미다. 역순인것처럼 보이지만 실제로 둘 다 data[31:24]를 나타낸다.

 

3. begin end를 명확하게 명시

 

always문 안에서는 주로 조건문을 사용하게 되는데, 어떤 조건이 한 줄의 코드만 실행시킨다고 했을때에도 begin end를 써주는 것이 좋다. Indent(들여쓰기)와 함께 사용하면 어느 if문이 어떤 if에 nested된건지 파악하기 쉬워진다.

 

4. 조건 연산자를 너무 깊게 사용하지 않기

 

조건 연산자를 너무 남용하면 다른 사람이 볼때 이해할 수 없는 코드가 되버리곤 한다. 아래의 예시를 보자.

 

assign test = (in0 == 4'd7) ? ((in1 == 4'd3) ? 4'b0 : data_a) : ((in2 == 4'd1) ? ((in3 == 4'd15) ? 4'b0001: data_b) : 4'b0010);

 

코드를 작성한 당사자 또한 헷갈릴 수 있을만한 예시다. 조건문이 4개가 있는 코드다. 가독성이 나쁜 이런 예시를 해결하기 위해서는 중간값을 가지는 변수가 필요하다.

 

wire [3:0] tmp0, tmp1, tmp2;

assign tmp0 = (in1 == 4'd3)  ? 4'b0    : data_a;
assign tmp1 = (in3 == 4'd15) ? 4'b0001 : data_b;
assign tmp2 = (in2 == 4'd1)  ? tmp1    : 4'b0010;
assign test = (in0 == 4'd7)  ? tmp0    : tmp2;

 

어느 조건이 어느 조건문에 nested되어 있는지 파악이 가능할 것이다.

'Verilog HDL 설계' 카테고리의 다른 글

D latch, Master-Slave D flip-flop 구현 (gate level)  (0) 2021.04.17
8bit Binary to BCD  (0) 2021.04.14
MUX  (0) 2021.04.13
Clock frequency divider  (0) 2021.04.11
Counter  (2) 2021.04.11