오늘은 잘못된 설계 예시를 살펴보면서 뭐가 문제인지 알아보도록 하겠다.
내가 운영하는 소규모 오픈채팅방에서 받은 질문을 토대로 작성하였다.
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는 보통 한번에 여러 세그먼트 디스플레이에 점등을 하지 않고, 하나씩 빠르게 점멸하며 잔상으로 여러 디스플레이가 켜진것 처럼 보이게 한다.
위의 타이밍 다이어그램처럼 한 세그먼트씩 점등한다.