본문 바로가기

Verilog HDL 설계/틀린 코드 예시

integer, case문 및 always문 오류 피드백

오늘은 잘못된 설계 예시를 살펴보면서 뭐가 문제인지 알아보도록 하겠다.

내가 운영하는 소규모 오픈채팅방에서 받은 질문을 토대로 작성하였다.

 

module SwitchEncoder(
   input            clk,
   input  [15:0]   key,
   input  [3:0]    a, b,
   output [6:0]   FNDa,
   output [6:0]   FNDb,
   output         FNDSel2,FNDSel1
);
   reg            clk100Hz;
   reg [6:0]      FNDa, FNDb;

   // 에러 1
   integer         m=0;
   
   always @ (posedge clk) 
   begin
     if (m >= 4999)
     begin
       m <= 0;
       clk100Hz <= ~clk100Hz;
      end else
        m <= m+1;
   end
   
   // 에러 2
   always @(posedge clk)
   begin
      case(key)
         16'b0000000000000001 : keyVal = 4'b0000;
         16'b0000000000000010 : keyVal = 4'b0001;
         16'b0000000000000100 : keyVal = 4'b0010;
         16'b0000000000001000 : keyVal = 4'b0011;
         16'b0000000000010000 : keyVal = 4'b0100;
         16'b0000000000100000 : keyVal = 4'b0101;
         16'b0000000001000000 : keyVal = 4'b0110;
         16'b0000000010000000 : keyVal = 4'b0111;
         16'b0000000100000000 : keyVal = 4'b1000;
         16'b0000001000000000 : keyVal = 4'b1001;
         16'b0000010000000000 : keyVal = 4'b1010;
         16'b0000100000000000 : keyVal = 4'b1011;
         16'b0001000000000000 : keyVal = 4'b1100;
         16'b0010000000000000 : keyVal = 4'b1101;
         16'b0100000000000000 : keyVal = 4'b1110;
         16'b1000000000000000 : keyVal = 4'b1111;
         default               :;
      endcase
      
      case(a)
         4'b0000 : FNDa = 7'b1111110;
         4'b0001 : FNDa = 7'b0110000;
         4'b0010 : FNDa = 7'b1101101;
         4'b0011 : FNDa = 7'b1111001;
         4'b0100 : FNDa = 7'b0110011;
         4'b0101 : FNDa = 7'b1011011;
         4'b0110 : FNDa = 7'b1011111;
         4'b0111 : FNDa = 7'b1110000;
         4'b1000 : FNDa = 7'b1111111;
         4'b1001 : FNDa = 7'b1111011;
         4'b1010 : FNDa = 7'b1110111;
         4'b1011 : FNDa = 7'b1111111;
         4'b1100 : FNDa = 7'b1001110;
         4'b1101 : FNDa = 7'b1111110;
         4'b1110 : FNDa = 7'b1001111;
         4'b1111 : FNDa = 7'b1000111;
      endcase
      
      case(b)
         4'b0000 : FNDb = 7'b1111110;
         4'b0001 : FNDb = 7'b0110000;
         4'b0010 : FNDb = 7'b1101101;
         4'b0011 : FNDb = 7'b1111001;
         4'b0100 : FNDb = 7'b0110011;
         4'b0101 : FNDb= 7'b1011011;
         4'b0110 : FNDb = 7'b1011111;
         4'b0111 : FNDb = 7'b1110000;
         4'b1000 : FNDb = 7'b1111111;
         4'b1001 : FNDb = 7'b1111011;
         4'b1010 : FNDb = 7'b1110111;
         4'b1011 : FNDb = 7'b1111111;
         4'b1100 : FNDb = 7'b1001110;
         4'b1101 : FNDb = 7'b1111110;
         4'b1110 : FNDb = 7'b1001111;
         4'b1111 : FNDb = 7'b1000111;
      endcase
   end
   
   // 에러 3
   always @ (clk100Hz, FNDa, FNDb)
   begin
      if(clk100Hz) 
      begin
         FNDSel1 = 1'b0;
         FNDSel2 = 1'b1;
         FND = FNDb;
      end else 
      begin
         FNDSel1 = 1'b1;
         FNDSel2 = 1'b0;
         FND = FNDa;
      end
   end
      
endmodule

 

이 설계에는 3개의 큰 오류가 있다.

 

1. 카운터 대신 integer 선언

2. clock triggerd always 문에 =로 register 값 할당 시도

3. clock triggerd always 문에 clock 값을 조건으로 활용하려는 시도

 

1번

integer는 여러개의 반복된 assign 혹은 모듈 코드들을 짧게 표시하기 위해 쓰이는 for문 혹은 generate문, 테스트벤치에서 연속된 데이터셋을 입력하기 위해 쓰인다.

그런데 이걸 카운터처럼 쓰면 합성툴이 어떻게 합성할 지 알 수가 없다.

따라서 해당 오류는 다음과 같이 수정되야 한다.

 

reg [15:0] m;

always @ (posedge clk) 
begin
    if (rst) begin
        m <= 16'b0;
    end
    else begin
        if (m == 16'd4999) begin
            m <= 16'b0;
        end
        else begin
            m <= m + 16'b1;
      end
    end
end

 

예전에 포스팅한 카운터 코드처럼 설계하면 된다.

 

2번

register 변수를 =로 할당한다는 것은 always @ (*)을 쓴다는 말과 같다.

저번에도 말했지만 이런 방식은 별로 추천하지 않는 설계방식이다.

잘못 설계 시 합성에서 추가적으로 의도하지 않은 latch나 flip flop이 추가되기 때문이다.

해당 코드에는 case문을 썼는데, default 조건이 없는 상태의 case문을 썼다.

그러면 위에서 언급한 latch가 생긴다.

예시 코드는 그냥 참고만 하는게 좋다.

 

always @(posedge clk) begin
    case(key)
        16'b0000000000000001 : keyVal = 4'b0000;
        // 생략
        16'b1000000000000000 : keyVal = 4'b1111;
        default              : keyVal = 4'b0;
    endcase
end

 

default를 비워두면 안된다. 큰일난다.

 

3번

clock 신호를 조건으로 활용하려 하면 합성 시에 오류가 생긴다.

에러 로그가 따로 있는 녀석인데, 본지 오래되서 기억이 잘 안난다;;

대신에 매 클락마다 0과 1을 왔다갔다 하는 건 가능하다.

 

reg double;

always @ (posedge clk) begin
    if (rst) begin
        double <= 1'b0;
    end
    else begin
        double <= ~double;
    end
end

endmodule

 

플립플롭을 반전시키기만 하면 된다.

이런 류의 신호는 fpga에서 7 segment display를 표시할때 자주 쓰인다.

fpga는 보통 한번에 여러 세그먼트 디스플레이에 점등을 하지 않고, 하나씩 빠르게 점멸하며 잔상으로 여러 디스플레이가 켜진것 처럼 보이게 한다.

 

위의 타이밍 다이어그램처럼 한 세그먼트씩 점등한다.