Harman 세미콘 아카데미/Verilog

2024.6.21 [Verilog]⑥ - 링카운터 활용 + basys3 보드 연결, 레지스터

U_Pong 2024. 6. 21. 22:47

2024.6.21 수업날

오늘은 어제 이어서 배운 링카운터를 활용해보고 basys3보드에 연결해서 결과를 관찰해본다.

그리고 직렬출력 레지스터 직렬입력 병렬출력 레지스터, 병렬입력 직렬출력 레지스터를 알아본다.


< 링카운터 활용 >

1. Basys3 보드에서 FND 링카운터 실행하기

먼저 Basys3에 있는 fnd에 어제 배운 링카운터를 활용하여 4자리의 숫자가 각각 따로 작동되는것을 확인한다.

아래 사진에서 4번의 fnd, 5번의 스위치를 사용한다.

 

Fnd용 링카운터 소스코드
//////////////////////////////////////////////////////////////// 2024.6.21
/////////////////////////fnd용 링카운터
module ring_counter_fnd(
      input clk, reset_p,
      output reg [3:0] q);
      
      reg [16:0] clk_div;                 // 주파수를 나누어주는 회로(분주기), 시뮬레이션 돌릴때는 reg에서만 =0을 주어야함
      always @(posedge clk) clk_div = clk_div + 1;          // 1증가하는 카운터 추가, 주파수는 반으로 줄고 주기는 2배로 늘고
      
      wire clk_div_16_p;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[16]), 
            .p_edge(clk_div_16_p));
      
      always @(posedge clk or posedge reset_p) begin      //1.3ms 마다 증가하는 것, 2^16 = 1310...ns --> 1.3ms
            if(reset_p) q = 4'b1110;
            else if(clk_div_16_p) begin
                  if(q == 4'b0111) q = 4'b1110; 
                  else q = {q[2:0], 1'b1};
            end
      end
            
endmodule

ATmega 128a에서도 fnd를 사용했듯이 이번네도 같은 원리로 fnd의 각 자리의 숫자를 킬 수 있도록 한다.

원래는 fnd의 각 자리 숫자를 따로따로 조절하지 못한다. 하지만 사람의 눈에는 잔상효과라는 것이 있어서 fnd의 각 자리에 딜레이가 오도록 하면 모든 자리의 숫자가 켜진것처럼 보이게 된다.

저번에도 설명했듯이, Basys3에서는 fnd가 0일때를 켜짐으로 간주하기 때문에 4비트의 수들을 모두 반대로 썼다.

그리고 분주기를 17비트로 줬기 때문에 2^16=1310...ns-->1.3ms마다 증가하도록 설정했다.

 

Basys3 - FND 컨트롤러 소스코드
/////////////////////////////////////////////////////////2024.6.21
//////fnd 컨트롤러
module fnd_4digit_cntr(
      input clk, reset_p,
      input [15:0] value,
      output [7:0] seg_7,
      output [3:0] com);
      
      ring_counter_fnd rc(.clk(clk), .reset_p(reset_p), .q(com));
      
      reg [3:0] hex_value;
      decoder_7seg dec(.hex_value(hex_value), .seg_7(seg_7));
      
      always @ (posedge clk) begin
            case(com)
                  //case에서는  case()안에 있는 값이  : 앞의 값이 된다면 : 뒤의 조건을 실행시키는 것으로 이해하자
                  4'b1110 : hex_value = value[3:0];   //스위치를 4개씩 끊어서 입력값을 주어짐
                  4'b1101 : hex_value = value[7:4];
                  4'b1011 : hex_value = value[11:8];
                  4'b0111 : hex_value = value[15:12];
            endcase
      end      
    
    
endmodule

위의 소스코드는 Basys3에 있는 16개의 스위치를 4개로 나누어서 2진수로 나타나게 설정했다.

fnd의 가장 왼쪽의 자리는 스위치의 15~12번,

fnd의 왼쪽부터 두번째 자리는 스위치의 11~8번,

fnd의 오른쪽부터 두번째 자리는 스위치의 7번~4번,

fnd의 가장 왼쪽의 자리는 스위치 3~0번으로 지정했다.

 

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의 fnd가 작동하는지 확인하기 위해 위와 같은 소스코드가 필요하다.

이 소스코드는 2024.6.28일에 작성한 코드인데, 각 자리에 스위치로 어떤 2진수 값을 주느냐에 따라

어떤 숫자나 알파벳으로 표현할지를 설정한 코드이다.

 

Basys3 - fnd 디코더 연결 소스코드
/////////////////////////////////////////////// 2024.6.21
// FND 컨트롤러 보드에 연결하기
module fnd_test_top (
      input clk, reset_p,
      input [15:0] value,
      output [7:0] seg_7,
      output [3:0] com);
      
      //순서대로 연결하는 경우에는 .으로 지정안해줘도 됨, 다만 순서는 꼭 지킬것
      fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);

endmodule

다음으로 Basys3의 fnd가 잘 작동하는지를 확인해보기 위해 0000의 4비트 입력값을 줘서 스위치 작동을 하지 않아도 불이 들어오는지 확인하는 소스코드를 작성한다.

 

마지막으로, fnd_test_top을 Sources - Design Sources에서 set top을 설정해준다.

그리고 xdc파일에서 Clock signal, Switches, Button의 값을 아래와 같이 변경해준다.

7 Segment Display는 2024.6.28에 보드와 연결하면서 수정했기 때문에 따로 수정하지는 않는다.

Button들 중에서도 중앙에 있는 Button을 사용한다.

이는 reset이라고 이름을 설정했지만, 실제로 초기화 하는 기능을 수행하지는 않는다.

읽어준 값만 남기고 나머지는 reset을 해주는 것이다.

 

참고로 .xdc에서 주석을 달고 싶으면

줄 끝의 ;를 달고 #을 추가해서 주석을 써야 오류가 생기지 않는다.

아니면 줄바꿈을 하여 #을 추가하여 주석을 쓰면 된다.

 

아래는 16개의 스위치를 사용하여 fnd에 CAFE라는 단어를 출력한 것이다.

실제로는 각 자리가 12, 10, 15, 14를 의미하는 것인데 fnd에서는 한자리의 숫자 밖에 표현하지 못하기 때문에

10이상의 숫자는 알파벳이 출력되도록 설정하였다.

 

 

2. Basys3 보드에서 LED 링카운터 실행하기

이번에는 Basys3에 있는 led에 링카운터를 활용하여 16개의 led가 순차적으로 작동되는 것을 확인한다.

아래 사진에서 6번의 led를 사용한다.

 

먼저, basys3에 있는 led는 16개 이기 때문에, 16비트로 설정해주어야한다.

분주기를 설정하는 fnd용 링카운터의 소스코드에 합치기 전에,  16비트 링카운터 소스코드를 작성한다.

 

16비트 링카운터 소스코드
////////////////////////////////////////////// 16비트 링카운터
module ring_counter_16bit(
      input clk, reset_p,
      output reg [15:0] q);
      
      //always 문에서 쉬프트연산을 사용할때
      always @(posedge clk or posedge reset_p) begin
            if(reset_p) q = 16'b0000_0000_0000_0001;
            else begin
                  if(q == 16'b1000_0000_0000_0000) q = 16'b0000_0000_0000_0001; 
                  else q = {q[14:0], 1'b0};      
            end
      end

endmodule

fnd용 링카운터에서도 else q = {q[14:0], 1'b0}; 이런 형식을 썼다.

이는 q의 현재 값이 16'b1000_0000_0000_0000가 아닌경우,

q의 값을 왼쪽으로 한 비트씩 쉬프트 하고 최하위 비트에 0을 추가하여 새로운 값을 설정하는 것이다.

 

21비트 분주기(20분주)로 설정한 LED용 링카운터
module ring_counter_led(
      input clk, reset_p,
      output reg [15:0] q);
      
       reg [20:0] clk_div;                 // 주파수를 나누어주는 회로(분주기), 시뮬레이션 돌릴때는 reg에서만 =0을 주어야함
       always @(posedge clk) clk_div = clk_div + 1;          // 1증가하는 카운터 추가, 주파수는 반으로 줄고 주기는 2배로 늘고
      
       wire clk_div_20_p;
       edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[20]),       // .cp(clk_div[18]
            .p_edge(clk_div_20_p));
  
 //      // LED 1개씩 켜서 움직이려면 링카운터는 이렇게 하면 됨     
//      always @(posedge clk or posedge reset_p) begin      //20ms 마다 증가하는 것, 2^20= 20,971,520ns --> 20ms
//            if(reset_p) q = 16'b0000_0000_0000_0001;
//            else if(clk_div_20_p) begin
//                  if(q == 16'b1000_0000_0000_0000) q = 16'b0000_0000_0000_0001; 
//                  else q = {q[14:0], 1'b0};
//            end
//      end
      
//      // LED 3개씩 켜서 움직이려면 링카운터는 이렇게 하면 됨
//      always @(posedge clk or posedge reset_p) begin      //20ms 마다 증가하는 것, 2^20= 20,971,520ns --> 20ms
//            if(reset_p) q = 16'b0000_0000_0000_0111;
//            else if(clk_div_20_p) begin
//                  q = {q[14:0], q[15]};
//            end
//      end
      
      // LED 12개씩 켜서 움직이려면 링카운터는 이렇게 하면 됨
      always @(posedge clk or posedge reset_p) begin      //20ms 마다 증가하는 것, 2^20= 20,971,520ns --> 20ms
            if(reset_p) q = 16'b0000_1111_1111_1111;
            else if(clk_div_20_p) begin
                  q = {q[14:0], q[15]};
            end
      end
      
      
endmodule

 

led는 총 16개이므로 fnd에서 설정한 분주기 보다 더 느리게 코드를 설정했다.

led를 1개만 켜서 카운터가 되게 할 때와,

led를 3개만 켜서 카운터가 되게 할 때,

led를 12개만 켜서 카운터가 되게 할때를 각각 소스코드로 작성하면 위와 같다.

위의 소스코드는 led를 12개를 켜서 카운터가 되게 하는 소스코드를 작성했다.

 

led 링카운터 basys3 보드에 연결하는 소스코드
/////////////////////////////////////////////// LED 링카운터 보드에 연결하기
module led_test_top (
      input clk, reset_p,
      output [15:0] q);
      
      //순서대로 연결하는 경우에는 .으로 지정안해줘도 됨, 다만 순서는 꼭 지킬것
     ring_counter_led  rc(clk, reset_p, q);

endmodule

이제 Basys3 보드에 연결하기 위해 led 링카운터와 연결하여 코드를 작성한다.

 

basys-3-Master.xdc 수정한 부분
## LEDs
set_property -dict { PACKAGE_PIN U16   IOSTANDARD LVCMOS33 } [get_ports {q[0]}]
set_property -dict { PACKAGE_PIN E19   IOSTANDARD LVCMOS33 } [get_ports {q[1]}]
set_property -dict { PACKAGE_PIN U19   IOSTANDARD LVCMOS33 } [get_ports {q[2]}]
set_property -dict { PACKAGE_PIN V19   IOSTANDARD LVCMOS33 } [get_ports {q[3]}]
set_property -dict { PACKAGE_PIN W18   IOSTANDARD LVCMOS33 } [get_ports {q[4]}]
set_property -dict { PACKAGE_PIN U15   IOSTANDARD LVCMOS33 } [get_ports {q[5]}]
set_property -dict { PACKAGE_PIN U14   IOSTANDARD LVCMOS33 } [get_ports {q[6]}]
set_property -dict { PACKAGE_PIN V14   IOSTANDARD LVCMOS33 } [get_ports {q[7]}]
set_property -dict { PACKAGE_PIN V13   IOSTANDARD LVCMOS33 } [get_ports {q[8]}]
set_property -dict { PACKAGE_PIN V3    IOSTANDARD LVCMOS33 } [get_ports {q[9]}]
set_property -dict { PACKAGE_PIN W3    IOSTANDARD LVCMOS33 } [get_ports {q[10]}]
set_property -dict { PACKAGE_PIN U3    IOSTANDARD LVCMOS33 } [get_ports {q[11]}]
set_property -dict { PACKAGE_PIN P3    IOSTANDARD LVCMOS33 } [get_ports {q[12]}]
set_property -dict { PACKAGE_PIN N3    IOSTANDARD LVCMOS33 } [get_ports {q[13]}]
set_property -dict { PACKAGE_PIN P1    IOSTANDARD LVCMOS33 } [get_ports {q[14]}]
set_property -dict { PACKAGE_PIN L1    IOSTANDARD LVCMOS33 } [get_ports {q[15]}]

마지막으로 .xdc 파일에서 led 부분의 주석을 풀고 위와 같이 코드를 수정한다.

 

led를 1개만 켰을때의 링카운터 결과

 

led를 3개만 켰을때의 링카운터 결과

 

led를 12개 켰을때의 링카운터 결과

 

 

< 레지스터 >

 

 

1. 레지스터의 분류

레지스터는 플립플롭 여러 개를 일렬로 배열하여 여러 비트로 구성된 2진수를 저장할 수 있게 한 것이다. 1개의 플립폴롭은 1비트에 해당한다.

이번에는 저장된 비트를 좌측으로 하나씩 쉬프트하는 레지스터에 대해 알아본다.

직렬입력-직렬출력, 직렬입력-병렬출력, 병렬입력-직렬출력, 병렬입력-병렬출력의 종류가 있다.

 

비트를 저장한다고 하니, 잠시 메모리 이야기를 해보자.

메모리는 크게 ROM(Read Only Memory), RAM(Random Access Memory)가 있다.

rom은 조합회로이며, 전원을 제거해도 메모리가 지워지지 않는다.

rom의 종류는 아래와 같다.

PROM: rom의 내용을 한 번 덮어쓸 수 있으며, 내용을 지울 수 없다. 내용을 바꾸려면 떼내고 다시 붙여야한다.

EPROM: 내용을 지울 수는 있지만, 자외선을 쬐야 내용을 지울 수 있기에 불편함이 있다.

EEPROM: 전기적으로 내용을 지울 수 있다.

 

다음으로 ram은 주소가 있는 메모리이며, 전원을 제거하면 메모리가 날라가는 휘발성 메모리이다.

마이크로프로세서(CPU)가 프로그램을 실행하고있을 때, 입출력하드웨어 등의 장치에 예외상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것을 인터럽트라고 하는데, 이럴 때 ram은 문제가 된다.

우리가 사용하는 것은 ram이다. 보드에 전원을 연결하지 않으면 소스코드를 다시 프로그래밍 해야한다.

 

 

2. 직렬입력-직렬출력 레지스터

직렬입력-직렬출력 레지스터는 하나의 라인에서 한번에 한 비트씩 받아들인다.

클럭이 주어질 때 입력값이 들어가기 때문에 하나씩 밀리게 된다.

즉, 최상위 비트로 입력된 값이 밀려서 제일 오른쪽으로 가게 되며, 결국 이 값은 최하위 비트가 된다.

 

직렬입력-직렬출력 레지스터
//////////////////////////////////////////////// 직렬출력 레지스터
module shift_register_SISO_n(
      input clk, reset_p,
      input d,
      output q);
      
      reg [3:0] siso_reg;     //레지스터 변수=reg
      
      always @ (negedge clk or posedge reset_p) begin
            if(reset_p) siso_reg <= 0;
            else begin 
                  siso_reg[3] <= d;                    // 논블로킹, 논블로킹 왼쪽의 수식을 설정한 후, 넣는건 순서대로, 부등호 의미가 아님
                  siso_reg[2] <= siso_reg[3];
                  siso_reg[1] <= siso_reg[2];
                  siso_reg[0] <= siso_reg[1];
            end
      end
      
      assign q = siso_reg[0];

endmodule

always문에서 '<=' 라는 형식이 쓰인 것을 볼 수 있다. 이는 논블로킹이라고 불린다.(부등호의 의미가 아님)

먼저 논블로킹의 왼쪽 수식을 설정한 후, 논블로킹의 오른쪽 수식에 대입하여 위에서부터 순서대로 진행한다.

 

만약 논블로킹을 쓰지 않았을때의 RTP analysis 결과와 Synthesized Design 결과가 어떤지 살펴보고 어떤 차이가 있는지도 알아보자.

아래는 논블로킹을 쓰지 않았을 때의 RTP analysis 결과이다. D플립플롭들이 겹쳐있는 것을 볼 수 있다.

 

아래는 논블로킹을 쓰지 않았을때의 Synthesized Design결과이다.

플립플롭 레지스터가 하나만 출력되는 것을 확인할 수 있다.

최상위 비트가 맨 처음 입력되면, 나머지 레지스터들은 입력값이 없기 때문에, 일명 쓰레기 값이라고 하는것을 모두 생략한 것이다.

 

아래는 논블로킹을 추가한 후의 RTP analysis결과이다.

논블로킹을 추가하여 굵기가 진한 선이 추가되었는데, 이는 bus라고 부른다.

bus라는 것은 전용선 또는 디바이스간 일대일 연결이 아닌 여러개의 디바이스가 공유할 수 있는 신호선을 말한다. 

 

아래는 논블로킹을 추가한 후의 Synthesized Design 결과이다.

논블로킹을 추가하여 플립플롭이 4개로 출력된 것을 볼 수 있다.

또한, buf(버퍼)라고 된 것이 입력부분과 출력부분에 있는 것을 볼 수 있다.

출력쪽의 버퍼는 역전류를 막기 위해 있는 것이다.

입력쪽의 버퍼는 npn, pnp 트랜지스터 상관없이 입력전압이 낮아도 버퍼를 거치면 3.3V가 되도록 하기 위해 있는 것이다.

클록쪽에는 버퍼가 두개가 있는데, 클록의 속도가 빠르다보니 잡음을 제거하기 위해 있는 것이다.

 

 

3. 직렬입력 병렬출력 레지스터

직렬입력 병렬출력 레지스터는 레이싱 현상에서 주로 쓰인다.  레이스 현상은 하나의 게이트에 대한 두개의 입력이 동시에 변할 때 일어나는 문제이다.

회로도에 RD bar가 있는 것을 볼 수 있는데,

RD bar는 0일때만 메모리의 출력을 내보내는 의미를 가지고 있다.(3상버퍼)

그리고 RD bar가 1이면 차단시키는 역할을 한다.

 

직렬입력 병렬출력 레지스터 소스코드
///////////////////////////////////// 직렬입력 병렬 출력 레지스터
module shift_register_SIPO_n(
      input clk, reset_p,
      input d, rd_en,
      output [3:0] q);        //병렬출력이라 4개
      
      reg [3:0] sipo_reg;
      
      always @(negedge clk or posedge reset_p) begin
            if(reset_p)sipo_reg = 0;
            else sipo_reg = {d, sipo_reg[3:1]};       //결합연산자 이용해도 됨
      end
       
       //assign q = sipo_reg;          // 병렬출력이라 4비트 내보냄
      assign q = rd_en ? 4'bz : sipo_reg;       // 4비트가 다 인피던쓰 일때만 4'bz라고 씀
      
      // bufif0 (q[0], sipo_reg[0], rd_en);        //fpg에서는 안씀
      
endmodule

 

 

4. 병렬입력 직렬출력 레지스터

클록을 주면 로드가 되고 입력이 2개인데, 출력될때는 하나하나씩 내보내야하니까 그 중에서 하나 선택해서 출력으로 내보내야 하는것이 mux(멀티플렉서)인데, 이를 사용하여 구현한다.

쉬프트를 할 때는 상위비트에서로부터 받는다.

쉬프트 언더바 로드가 회로도에 있는 것을 볼 수 있는데,

1일 때 쉬프트 하고, 0일 때 로드하겠다는 의미이다.

 

병렬입력 직렬출력 레지스터
///////////////////////////////////////////////////// 병렬입력 직렬출력 레지스터
module shift_registerr_PISO(
      input clk, reset_p,
      input [3:0] d,                // 병렬입력이기 때문에 4비트
      input shift_load,
      output q);
      
      reg [3:0] piso_reg;
      
      always @(posedge clk, posedge reset_p) begin          // 베릴로그에서 or 대신 , 도  허용함
            if(reset_p) piso_reg = 0;
            else begin
                  if(shift_load) piso_reg = {1'b0, piso_reg[3:1]};
                  else piso_reg = d;
            end
      end
      
      assign q = piso_reg[0];

endmodule

 

아래는 병렬입력 직렬출력 레지스터 시뮬레이션 결과이다.

우선

clk: 10ns,

reset: 1(constant),

d: 9(constant),

shift_load: 0 (constant),

시뮬 돌리는 시간: 10ns

으로 설정하여 리셋을 설정해 준 후 시뮬레이션을 실행한다.

 

다음으로 

reset: 0 (constant),

시뮬 돌리는 시간: 10ns

으로 설정하여 리셋을 0으로 설정해 준후 시뮬레이션을 실행한다.

 

마지막으로

shift_load: 1 (constant),

시뮬 돌리는 시간 40ns

으로 설정하여 시뮬레이션을 실행하면 piso_reg의 비트가 하나씩 밀리는 것을 알 수 있다.

 

 

5. 병렬입력 병렬출력 레지스터

병렬입력 병렬출력 레지스터에도 RD bar가 있는 것을 확인할 수 있다.

RD bar는 0일때만 메모리의 출력을 내보내는 의미를 가지고 있다.(3상버퍼)

그리고 RD bar가 1이면 차단시키는 역할을 한다.

 

 

 

 


링카운터 활용 + basys3 보드 연결, 레지스터 끝!