Harman 세미콘 아카데미/Verilog

2024.6.19 [Verilog]④ - 7세그먼트 디코더, 인코더, 멀티플렉서+디멀티플렉서, 플립플롭

U_Pong 2024. 6. 19. 19:38

2024.6.19 수업날

오늘은 어제 코드만 작성했던 7세그먼트 디코더를 실행시켜보고

조합 논리회로인 인코더, 멀티플렉서, 디멀테플렉서를 알아보고

순서 논리회로인 플립플롭의 종류에 대해 알아본다.


< 7세그먼트 디코더 >

어제 마지막 시간에 Basys3 보드에 있는 FND를 사용하여 숫자를 표현하기 위해 4개의 스위치를 누름에 따라 각각 다른 기능을 할 수 있도록 코딩했다. 보드에 나타나는 결과를 관찰하기 위해 vivado에서 몇 가지 설정해주어야 하는 것이 있다.

먼저 어제 작성했던 코드의 구조를 가볍게 살펴보자

7세그먼트 디코더 소스코드
////////////////////////////////////////// 7세그먼트 디코더
module decoder_7seg(
      input [3:0] hex_value,   //0~15까지의 16진수 값
      output reg [7:0] seg_7);
      
      always @(hex_value)begin
            case(hex_value)
            //basys3의 fnd는 0일때 켜지는 것으로 간주,  '_'는 몇자리인지 구분하려고 씀, 실제로는 값을 읽지 않음
                                                    //abcd_efgp 
                  4'b0000: seg_7 = 8'b0000_0011;           // 0
                  4'b0001: seg_7 = 8'b1001_1111;            // 1
                  4'b0010: seg_7 = 8'b0010_0101;            // 2
                  4'b0011: seg_7 = 8'b0000_1101;            // 3
                  4'b0100: seg_7 = 8'b1001_1001;            // 4
                  4'b0101: seg_7 = 8'b0100_1001;            // 5
                  
                  4'b0110: seg_7 = 8'b0100_0001;            // 6
                  4'b0111: seg_7 = 8'b0001_1111;            // 7
                  4'b1000: seg_7 = 8'b0000_0001;            // 8
                  4'b1001: seg_7 = 8'b0001_1001;            // 9
                  
                  4'b1010: seg_7 = 8'b0001_0001;            // A(10)
                  4'b1011: seg_7 = 8'b1100_0001;            // b(11)
                  4'b1100: seg_7 = 8'b0110_0011;            // C(12)
                  4'b1101: seg_7 = 8'b1000_0101;            // d(13)
                  4'b1110: seg_7 = 8'b0110_0001;            // E(14)
                  4'b1111: seg_7 = 8'b0111_0001;            // F(15)
            endcase      
      end
endmodule

주석에서도 알 수 있듯이 Basys3 보드에서의 7세그먼트는 0일때 불이 들어오는 것으로 인식한다.

\다음으로 ATmega 128a에서 7세그먼트에 대한 결과값을 관찰했을때는 7세그먼트의 a 위치를 0비트로 설정했다. (원래 비트는 오른쪽에서 왼쪽 방향으로 카운터를 한다.)

하지만 이번에는 보기 편하도록 7세그먼트의 a 위치를 7번비트로 설정하였다.

즉, 왼쪽에서 오른쪽 방향으로 카운터를 하는 것이다. 

 

그리고 보드에 연결하기 위한 코드를 새로 짜야한다.

여태 계속 한 파일에 누적해서 코드를 작성했기 때문에 새롭게 파일을 생성하여 코드를 작성했다.

Source에서 +를 눌러 Add Create Design Sources를 클릭하고 Create File로 이름을 정해준 다음 Finish를 선택한다.

 

7세그먼트 디코더 보드에 연결하기 소스코드
/////////////////////////////////////////////// 7세그먼트 디코더 보드에 연결하기
module decoder_7seg_top(
      input [3:0] sw_value,         //switch_value, 스위치 4개의 입력을 받음
      output [7:0] seg_7,
      output [3:0] com);
      
      assign com = 4'b0000;   //anode 타입이지만 0을 줘야함, 보드가 잘 작동하는지 확인하는용
    
      decoder_7seg fnd(.hex_value(sw_value), .seg_7( seg_7));

 

코드를 작성하고 나면, Design Sources에서 맨 위로 위치시켜준다.

 

그리고, Basys3_Master.xdc에 들어가서 아래와 같이 Basys3 보드에 작동시킬 부분의 주석을 풀고 수정해준다.

7세그먼트 디코더 소스코드에서 7세그먼트의 a 위치를 7번비트로 설정했기 때문에

7 Segment Display에서 번호를 큰 순서에서 작은 순서대로 적는다.

Basys-3-Master.xdc
0.01MB

 

이번에는 보드와 연결하여 직접 결과를 관찰해보자

이번에는 위의 사진처럼 Anode Common 방식을 사용한다.

 

Bitstream을 사용하면 바로 보드에서 결과를 관찰할 수 있다.

 

위 사진과같이 절차를 거치면 Basys3에서 결과를 확인할 수 있다.

7세그먼트 디코더 결과

 

칩에 부담을 안줄려고 따로 pnp를 com에 추가해서 fpga 칩에서 열을 대신 받아준다.
led를 켜는 전류는 외부에서 직접 들어가게끔 설정한다.

pnp를 썼기 때문에 anode 타입이여도 0을 써야 켜지는 걸로 인식하는 것이다.

 

 

< 인코더 >

인코더란 여러 개의 입력 중에서 신호가 주어진 입력의 정보를 코드로 변환하는 디지털 회로이다.

기본 인코더인 4x2 인코더는 결과값이 각각 4개의 입력 중 하나와 연결되어 해당 입력을 표시한다.

 

4*2인코더 소스코드
///////////////////////////////////////////////////////////// 2024.6.19
/// 4가지 경우의 입력만이 있다는 전제하에 만드는 4*2 인코더
module encoderr_4x2_behavioral(
      output reg [1:0] code,
      input [3:0] signal);
      
//      always @ (signal) begin                                           // if 문을 사용했을때의 예시, 입력은 4개뿐이기 때문에 이렇게 작성해도 됨
//           if(signal == 4'b0001) code = 2'b00;                // 주석처리 전체 해체 또는 설정 단축키: 컨트롤+윈도우+슬래쉬
//            else if(signal == 4'b0010) code = 2'b01;    // if문을 쓰려면 반드시 else를 써야함  
//            else if(signal == 4'b0100) code = 2'b10;
//           else if(signal == 4'b1000) code = 2'b11;
//           else code = 2'b00;
//      end
      
      always @(signal)begin           //case를 사용했을때의 예시 
            case(signal)      // case에서 default가 없으면  오류가 생김
                  4'b0001 : code = 2'b00;
                  4'b0010 : code = 2'b01;
                  4'b0100 : code = 2'b10;
                  4'b1000 : code = 2'b11;
                  default   : code = 2'b00;       
            endcase
       end     
endmodule


///////////////////////////////////////////////////////// 4*2 디코더 만들기, 데이터플로우
module encoderr_4x2_dataflow(
      output [1:0] code,
      input [3:0]signal);
      
      assign code = (signal == 4'b0001) ? 2'b00 : 
                                  ((signal == 4'b0010) ? 2'b01 : 
                                  ((signal == 4'b0100) ?  2'b10 : 
                                  ((signal == 4'b1000) ?  2'b11 : 2'b00)));          //조건 연산자를 중복으로 사용
endmodule

 

아래는 behavioral의 시뮬레이션 결과 값이다.

입력값인 signal에서 진리표대로 입력해주면, 해당 결과값이 나오고, 그 외의 값은 00이 나오는 것을 알 수 있다.

 

아래는 dataflow의 시뮬레이션 결과 값이다.

입력값인 signal에서 진리표대로 입력해주면, 해당 결과값이 나오고, 그 외의 값은 00이 나오는 것을 알 수 있다.

 

 

< 멀티플렉서 + 디멀티플렉서 >

1. 멀티플렉서(mux)

멀티플렉서는 2^n개의 입력 데이터 중에서 n개의 선택 신호를 이용하여, 입력 신호 중 하나를 선택하는 조합 논리회로이다.

 

1.1 mux_2_1

2^1개의 입력 중 하나를 s의 값에 따라 출력으로 보내주는 2x1 멀티플렉서이다.

 

mux_2_1 소스코드
//////////////////////////////////////////////////// 2x1 멀티플렉서
module mux_2_1(
      input [1:0] d,
      input s,
      output f);
      
      assign f = s ? d[1] : d[0];

endmodule

이를 풀어서 쓰면 s의 값이 1이면 D1을 출력하고, s의 값이 0이면 D0을 출력하라는 의미이다.

즉, 조건이 참이면 ? 바로 뒤의 값을 쓰는 것이고, 조건이 거짓이면 : 바로 뒤의 값을 쓰는 것이다.

 

아래는  mux_2_1의 Schemetic 회로도이다.

 

아래의 시뮬레이션 결과는 mux_2_1의 결과이다.

d[1]: 200ns, d[0]: 100ns, s: 800ns 값을 대입하고, 시뮬레이션 돌리는 시간을 800ns으로 설정하면 결과가 나오는 것을 확인할 수 있다.

 

 

1.2 mux_4_1

아래는 4x1 멀티플렉서이다.

4개의 입력 중의 하나를 선택선 S에 입력된 값에 따라 출력으로 보내주는 조합회로이다.

 

mux_4_1 소스코드
///////////////////////////////////////////////4x1 멀티플렉서
module mux_4_1(
      input [3:0] d,
      input [1:0] s,
      output f);

      assign f = d[s];        //s가 0이면 d의 자릿값으로 나타내어 f에 출력한다.
      
            //assign f = (s == 2'b00) ? d[0] : () ? d[1] : d[0]; 
      
      // assign f = (s[1] == 0 && s[0] == 0);
      
      // assign f = d[s]; 를 풀어쓰면 아래와 같음
//       assign f = (s == 2'b00) ? (d[0]) :                 
//                         (s == 2'b01) ? (d[1]) :
//                         (s == 2'b10) ? (d[2]) :
//                         (s == 2'b11) ? (d[3]) : (2'b00);        s가 2'b00일때, d([0])을 출력, 그렇지 않으면 2'b00을 출력

endmodule

assign f = d[s]; 라는 형식을 볼 수 있는데, s의 값에 따라 D0~D3 중에서 하나가 출력에 나타나는 것이다.

때문에 이를 풀어서 쓰면,

s가 2비트 00일때, D0을 출력하고,

s가 2비트 01일때는 D1을 출력하고,

s가 2비트 10일때는 D2을 출력하고,

s가 2비트 11일때는 D3을 출력한다는 것이다.

 

Vivado에서 SYNTHESIS-Schemetic에 들어가면, mux_4_1의 회로도를 볼 수 있다.

여기서는 논리회로 게이트를 쓰는 것이 아니라 LUT라는 것을 사용한다.

 

LUT의 회로도와 진리표는 아래와 같다.

출력값이 0이면 접지에 연결하고, 출력값이 1이면 VDD(3V3 = 3.3V)에 연결한다.

최근의 FPGA 칩에는 논리게이트 구조가 거의 없다. 대신에 mux회로가 잔뜩 들어있다.

때문에 코드를 작성할때 구조적 설계 모델링을 작성하지 않는 것이다.

예를 들어 8x1의 멀티플렉서를 조합회로로 구현한다면 LUT의 회로도와 진리표와 유사하다.

 

1.3 mux_8_1

8x1 멀티플렉서는 2^3 = 8개의 입력 중의 하나를 선택선 S에 입력된 값에 따라 출력으로 보내주는 조합회로이다.

 

 

mux_8_1 소스코드
//////////////////////////////////////////////// 8x1 멀티플렉서
module mux_8_1(
      input [7:0] d,
      input [2:0] s,          // 3개를 골라야하니까 s는 3비트
      output f);
      
      assign f = d[s];        

endmodule

여기서도 mux_4_1과 소스코드 구조가 거의 비슷한 것을 확인할 수 있다.

입력값의 d를 2^3=8개 -> [7:0], 입력값의 선택선 s를 3개 -> [2:0]으로 바꾸면 된다.

 

 

2. 디멀티플렉서(demux)

디멀티플렉서는 하나의 입력 데이터를 가지고 2^n개의 출력 중에서 하나를 선택하는 조합 논리회로이다.

 

1x4 디멀티플렉서 데이터플로우 소스코드
//////////////////////////////////////////////// 1x4 디멀티플렉서, 데이터플로우
module demux_1_4_dataflow(
      input d,
      input [1:0] s,
      output [3:0] f);
      
      //assign d = f[s];        //s가 00이면 f의 0번으로 d입력이 나옴, 나머지 f 1,2,3비트에서는 연결이 끊긴다(이러면 안됨)
      assign f = (s == 2'b00) ? {3'b000, d} : 
                 (s == 2'b01) ? {2'b00, d, 1'b0} :
                 (s == 2'b10) ? {1'b0, d, 2'b00} : {d, 3'b000};            //결합연산자
                  // 4비트의 자릿값을 채우기 위해 ? 뒤처럼 쓴 것임
                         
endmodule

예로 1x4 디멀티플렉서의 소스코드를 살펴보자. 위의 소스코드는 데이터플로우로 나타낸 것이다. 여기서는 결합연산자를 사용하였다.

입력값 2비트의 s가 s'b00을 만족하면 ?뒤의 조건을 실행시키고, 그렇지 않다면 : 뒤의 조건을 실행한다.

즉,

s가 2비트 00일때, 3'b000과 d를 출력하고,

s가 2비트 01일때는 2비트 00, d, 1비트 0을 출력하고,

s가 2비트 10일때는 1비트 0, d, 2비트 00을 출력하고,

s가 이외의 값이라면 d, 3비트 000을 출력한다는 것이다.

 

 

3. mux, demux 결합하기

이번에는 mux와 demux를 결합한 회로를 소스코드로 구현해보자.

먼저 둘을 결합한 회로도는 아래와 같다.

mux와 demux 아래에 선택선 s에 사선으로 그어져 있고 옆에 2라고 쓰여있는데, 이는 선택선 s가 2개 있다는 의미이다.

 

mux_demux 결합 소스코드
////////////////////////////////////////////////// 멀티플렉서(먹스), 디멀티플렉서(디먹스) 합쳐서 만들기
module mux_demux_test(
      input [3:0] d,
      input [1:0] mux_s,
      input [1:0] demux_s,
      output [3:0] f);

      wire mux_f;
      mux_4_1 mux4(.d(d),  .s(mux_s),  .f(mux_f));
      demux_1_4_dataflow demux4(.d(mux_f), .s(demux_s), .f(f));

아까 만들었던 4x1 멀티플렉서과 1x4 디멀티플렉서를 서로 연결하여 소스코드를 작성했다.

 

아래는 mux_demux_test 시뮬레이션 결과이다.

d[3]: 400ns,

d[2]: 300ns,

d[1]: 200ns,

d[0]: 100ns,

mux_s: 0(constant),

demux_s: 0(constant),

시뮬레이션 돌리는 시간: 800ns으로 하여 시뮬레이션을 실행하면된다.

 

아래의 시뮬레이션 결과는 mux_S: 2, demux_s: 3으로 설정하고 시뮬레이션을 실행시킨 것이다.

선택값 s에 따라 출력값 f의 값이 변하는 것을 알 수 있다.

 

 

< 플립플롭 >

플립플롭이란 1 비트의 정보를 보관, 유지할 수 있는 회로이며 순차 회로의 기본요소이다.

 

1. 래치

먼저 기본적인 플립플롭인 래치에 대해 알아보자.

위의 래치에서는 NOR, NAND가 쓰였다.

NOR 래치 위주로 알아보기 전에 NOR, NAND의 진리표는 아래와 같다.

1) NOR

입력값 중 하나가 1이면 출력이 0이 되고, 입력값이 모두 0인 경우에만 출력이 1이다.

 

2) NAND

입력값 중 하나라도 0이면 출력이 1이고, 입력이 모두 1인 경우에만 출력이 0이다.

 

이제 NOR 래치를 살펴보자. 
NOR 게이트의 진리표를 확인한 것처럼, 입력값 중 하나가 0이라는 것을 알게되면 다른 입력값에 따라 출력값이 결정된다.

S=0, R=1이면 Q=0, Q bar=1 이다.

S=1, R=0 이면 Q=1, Q bar=0 이다.

하지만 S와 R 둘다 1이면 출력인 Q, Q bar 모두 0일수 밖에 없다. Q와 Q bar는 서로 반대되는 관계를 가져야하는데, 이럴 경우에는 모순이 생겨서 입력값을 모두 1로 입력하는 것을 금지한다.

출력값의 관계가 모순되기 때문에 11의 입력값은 금지한다.

 

 

2. SR플립플롭

클록형 SR플립플롭은 NOR 래치 회로도에서 AND 게이트가 결합한 것이다.

CP 즉, 클록의 신호가 이 회로에서 중요한 요소를 한다. 클록이 0이면 동작을 하지 않고(불변), 클록이 1이면 SR래치와 같은 동작을 수행한다.

아래는 SR플립플롭의 진리표 이다.(cp가 1일때)

cp S R Q(T+1)
1 0 0 Q(T) 현재 값
1 0 1 0
1 1 0 1
1 1 1 금지

 

 

다음은 에지 트리거 SR 플립플롭이다.

 

논리회로도에 펄스 전이 검출기가 붙어있는 것을 볼 수 있다. 상승 에지 펄스전이검출기의 파형을 살펴보자

상승 에지 파형에서 X의 값이 지연되는 동안 글리치가 생긴다. 입력한 값이 바뀐다고 출력이 바로 바뀌지 않는다. 그 이유는 반도체의 잠깐동안 전류가 흐르는 성질을 가진 커패시터 때문에 약간의 pdt(전파지연시간)가 발생하는 것이다.

상승 에지 펄스전이 검출기에서 글리치가 일어나는 현상을 그린 그림

 

 

3. D플립플롭

D플립플롭은 SR플립플롭의 입력값을 NOT 게이트로 연결하여 입력에 D라는 기호를 붙인 것이다.

RS의 R=1, S=0 그리고 R=0, S=1인 입력에만 가능하게 되는 회로이다.

즉, S,R의 값이 서로 다를때만 D플립플롭에 의한 영향을 받고 같을때는 영향을 받지 않는다.

때문에 글리치가 생기지 않아 부정출력도 나타나지 않고 발진도 나타나지 않아 사용할 수 있다.

 

이제 D플립플롭의 하강에지에서 작동하는 것과 상승에지에서 작동하는 소스코드를 작성해보자.

D플립플롭 하강에지 소스코드
////////////////////////////////////////////////// D 플립플롭_n
module D_flip_flop_n(         //negedge에서 작동하는 플립플롭
      input d, 
      input clk,
      input reset_p,
      input enable,
      output reg q);
      
      //조합회로를 만들때는 always를 써도 되지만 래치가 발생하지 않도록 해야된다
      always @ (negedge clk or posedge reset_p) begin
            if(reset_p) q = 0;      // reset을 우선시하는 if 문
            else if (enable) q = d;      //enable 이 1일때만
      end
      
endmodule

위와같이 소스코드를 작성하면, RTP analysis와 Synthesized Design에서 논리기호처럼 간략하게 표현된다. always에서 에지 트리거를 쓰면 플립플롭이 되는 것이다.  즉, 입력값에 같은 값이 들어가지 않도록 조치한 것이다.

또한, always에서 레벨 트리거링을 쓰면 래치가될수 있으니 주의해야한다.

 

아래의 회로도는 RTP analysis 결과와 Synthesized Design 결과이다.

출력쪽의 buf는 역전류를 차단하기 위함이다.

D플립플롭 상승에지 소스코드
////////////////////////////////////////////////// D 플립플롭_p
module D_flip_flop_p(         //posedge에서 작동하는 플립플롭
      input d, 
      input clk,
      input reset_p,
      input enable,
      output reg q);
      
      //조합회로를 만들때는 always를 써도 되지만 래치가 발생하지 않도록 해야된다
      always @ (posedge clk or posedge reset_p) begin
            if(reset_p) q = 0;      // reset을 우선시하는 if 문
            else if (enable) q = d;      //enable 이 1일때만
      end
      
endmodule

하강에지 소스코드와 달리 always에 clk와 reset_p가 상승에지일때만 작동하는 D플립플롭이다.

 

 

4. JK플립플롭

JK플립플롭은 SR플립플롭을 보완한 형태이다.

S->J, R->K로 바꾸어서 사용한 것인데, J=1, K=0이면 Q는 1로 set하고, J=0, K=1이면 Q는 0으로 reset한다.

SR플립플롭에서 S,R이 모두 1일때는 사용을 금지했는데, JK플립플롭에서는 이 입력값을 사용할 수 있다.

JK플립플롭에서 J,K가 모두 1이면 출력은 이전 출력의 보수 즉, 반전으로 표시되기 때문에 부정출력이 나타나지 않는다.

그러므로 다용도로 쓸 수 있으며,

소스코드로 작성할 수는 있지만, D,T 플립플롭으로도 대체가 가능하기 때문에 굳이 소스코드로 작성하지는 않는다. 

파란글씨가 과거, 초록색 글씨가 현재의 값이며, 둘의 관계는 서로 토글된다.
JK플립플롭을 이용하여 D플립플롭을 구성하는 방법(다용도 사용 가능)

 

 

5. T플립플롭

토글(Toggle)플립플롭이라고 불리며, 카운터를 구성하는데 자주 활용된다.

JK플립플롭에서 입력값 J,K를 묶어서 하나의 입력신호 T로 동작시킨다.

기존 JK플립플롭의 동작 중에서 입력이 모두 0이거나 1인 경우만을 사용하는 경우가 있는데, T플립플롭이 이 경우에 해당한다.

즉, T=0이면 출력은 변하지 않고, T=1이면 출력은 보수가 된다.

 

T 플립플롭 하강에지, 상승에지 소스코드
////////////////////////////////////////////////// T 플립플롭_n
module T_flip_flop_n(         //네거티브에서 토글
      input clk, reset_p,
      input t,
      output reg q);      //q = 0 --> 초기값 초기화, wire는 이렇게 쓰면 안됨, 왠만하면 쓰지 말것
      
      always @(negedge clk, posedge reset_p)begin
            if(reset_p) q = 0;      //리셋이면 q는 0으로 clear
            else begin
                  if(t) q = ~q;     //t가 1이면 q는 반전 --> 토글
                  else q = q;       //t가 0이면 현재값 유지, edge트리거에서는 생략가능하지만 습관 들이기
            end
      end
      
endmodule      


////////////////////////////////////////////////// T 플립플롭_p
module T_flip_flop_p(         //포지티브에서 토글
      input clk, reset_p,
      input t,
      output reg q);
      
      always @(posedge clk, posedge reset_p)begin
            if(reset_p) q = 0;      //리셋이면 q는 0으로 clear
            else begin
                  if(t) q = ~q;     //t가 1이면 q는 반전
                  else q = q;       //t가 0이면 현재값 유지, edge트리거에서는 생략가능하지만 습관 들이기
            end
      end
      
endmodule

 

 

< 플립플롭의 동작 특성 >

플립플롭의 동작 특성은 아래와 같다.

pdt(전파지연시간)이 발생하기 때문에 이를 방지하기 위한 시간을 설정해줘야한다.

 

 

< 멀티바이브레이터 >

플립플롭에서 발진이 나타나면 사용하지 못한다고 했다.

아래는 발진기의 다른 사례에 대해 설명한다.

 

수정발진기는 ATmega 128a의 은색 타원부품에도 있는 것이며, 주파수가 16MHz이다. 하지만 주파수의 한계가 있는 것이 단점이다.

Basys3은 주파수가 100MHz이다. ATmega 128a 보다도 주파수가 훨씬 크다.


7세그먼트 디코더, 인코더, 멀티플렉서+디멀티플렉서, 플립플롭 끝!