[FPGA] 12. 디지털 논리 회로 레지스터
1. 레지스터란?
레지스터(register)는 순차논리회로에서 사용되는 기본적인 구성 요소 중 하나이다. 레지스터는 디지털 시스템에서 데이터를 저장하고 처리하는데 사용된다. 레지스터는 보통 비트(bit) 단위로 구성되며, 각 비트는 0 또는 1의 값을 저장할 수 있다. 이를 이용하여 카운터, 가산기 등을 이용한 다양한 데이터 처리 회로를 만들 수 있다.
2. 레지스터의 종류
D플립플롭을 이용한 레지스터는 크게 직렬입력 - 직렬출력, 직렬입력-병렬입력, 병렬입력-직렬출력, 병렬입력-병렬입력 4가지가 있다.
직렬의 경우는 단 하나의 신호선으로 입,출력하는 것이고, 병렬은 여러개의 신호선으로 입,출력 하는 것이다. 자세한 내용과 구조는 아래에서 알아보자.
3. Verilog에서 직렬입력-직렬출력 D플립플롭 4개를 이용한 Shift_register 구현
아래 회로도는 4비트 직렬입력-직렬출력의 구조이다. I값을 넣고, 이전 플립플롭의 결과 값이 다음 플립플롭의 입력으로 들어간다.
클럭이 발생할 때마다 순차적으로 숫자를 쉬프트하여 O에서 결과를 출력한다.
코드는 다음과 같다.
`timescale 1ns / 1ps
module shift_register_SISO(
input D,
input clk,
input reset_n,
output Q
);
wire [2:0] w;
D_flip_flop_negedge DFF0(.D(D), .E(clk), .Q(w[0]));
D_flip_flop_negedge DFF1(.D(w[0]), .E(clk), .Q(w[1]));
D_flip_flop_negedge DFF2(.D(w[1]), .E(clk), .Q(w[2]));
D_flip_flop_negedge DFF3(.D(w[2]), .E(clk), .Q(Q));
endmodule
첫번째 D플립플롭에 DFF0에 입력 값 D를 넣어주고 나온 출력 Q 값을 다음 플립플롭에 저장하는 방식이다. 이를 테스트하기 위해 테스트 벤치 파일을 만든다.
`timescale 1ns / 1ps
module shift_register_SISO_tb();
reg D, clk;
wire Q;
reg [3:0] Data ;
shift_register_SISO DUT(.D(D), .clk(clk), .reset_n(1), .Q(Q));
initial begin
D = 0;
clk = 0;
Data = 4'b1101;
end
initial begin
#100 clk = 1;
#100 D = Data[0];
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#10 D = Data[1];
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#100 D = Data[2];
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#100 D = Data[3];
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#10 clk = 0; //set-up time 10나노 뒤 클락 떨어트림(데이터 유지시간)
#10 clk = 1; //hold-time 10나노 뒤 다시 클락 상승
#100 $finish;
end
endmodule
테스트 벤치 파일에서는 1비트 입력 값 D, 클럭 clk, 입력할 4비트 Data를 선언하고 이 모든 레지스터들을 먼저 초기화해준다. 다음 클럭 clk을 이용하여 클럭을 발생시킨다. 이 때, 하강엣지에서 동작하는 D플립플롭이므로 클럭을 발생시키고 난 후에 데이터를 입력하고, set-up time을 가져 안정적으로 비트를 입력시킨다. 또한 클락이 떨어지자마자 상승하여 오류를 발생시키는 일을 없게 하기 위해 hold time, 10ns 후에 클럭을 상승시킨다. 이와 같은 방법으로 4비트를 레지스터에 저장한다. 시뮬레이션을 통해 결과를 확인해보자.
빨간색 V로 표시한 하강엣지에서 만난 D값을 Q에 순차적으로 저장시킨다. 4번의 하강엣지(4비트 입력)가 발생한 후에 Q에 출력으로 뜨는 모습을 볼 수 있다. 4번의 하강엣지 이후에 출력 Q가 정상적으로 뜨는 이유는 결과 Q는 중간마다 나오는 것이 아니라 4비트가 레지스터에 모두 저장된 이후에 최종적으로 맨 끝 레지스터에 저장된 값부터 순차적으로 Q값이 출력된다. 따라서 4번의 Shift가 끝나고서야 결과 Q가 나오는 것이다. 이렇게 데이터가 플립플롭을 거쳐 Q로 나오기 까지의 시간을 전파 지연 시간이라고 한다.
4. Verilog에서 직렬입력-병렬출력 D플립플롭 4개를 이용한 Shift_register 구현
.
직렬입력 - 병렬출력 4비트 레지스터이다. 단 1개의 input을 I를 입력하면 플립플롭을 거쳐 각 결과 값이 다음 플립플롭의 Input과 RD값에 따라 3상 버퍼를 거쳐 각 비트를 출력한다. (3상 버퍼에 대한 설명은 맨 아래에 써놓았다.) 이를 Verilog로 구현해보자.
module shift_register_SIPO(
input D, //직렬 출력
input clk,
input rd_e_n,
output [3:0] Q //병렬 출력
);
wire [3:0] w;
D_flip_flop_negedge DFF0(.D(D), .E(clk), .Q(w[3]));
D_flip_flop_negedge DFF1(.D(w[3]), .E(clk), .Q(w[2]));
D_flip_flop_negedge DFF2(.D(w[2]), .E(clk), .Q(w[1]));
D_flip_flop_negedge DFF3(.D(w[1]), .E(clk), .Q(w[0]));
bufif0 (Q[0], w[0],rd_e_n); //3상 버퍼 : 0일 때 출력이 나온다.
bufif0 (Q[1], w[1], rd_e_n);
bufif0 (Q[2], w[2], rd_e_n);
bufif0 (Q[3], w[3], rd_e_n);
// always @(posedge clk) begin //입력은 negedge출력은 posedge에서
// if(!rd_e_n)begin //rd값이 0이면 그대로 입력을 출력
// Q = w;
// end
// else begin //rd값이 1이면 임피던스 출력
// Q = 4'bz; //4;b = 다 임피던스, 4'1 = 0001
// end
// end
// assgin Q = rd_e_n ? 4'bz : w; //rd_e_n가 1(참)이면 임피던스, 0(거짓)이면 w
endmodule
.
.
먼저 rd_e_n이 0일 때는 저장 되어있는 Q값이 그대로 출력된다. 하지만 rd_e_n이 1일 때는 Q값이 임피던스로 출력되고, 읽기모드로 변경된다.
5. Verilog에서 병렬입력-직렬출력 D플립플롭 4개를 이용한 Shift_register 구현
.
다음은 4개의 input을 가지고, 하나의 output 값을 출력하는 병렬입력-직렬출력 4비트 쉬프트 레지스터이다. SH/LD는 각 입출력 구간에 MUX의 선택 신호선으로 이어져 SH/LD의 값에 따라 Shift를 할지, 저장된 값을 출력할지 결정한다.
module shift_register_PISO(
input [3:0] D, //병렬 입력
input clk,
input shift_load,
output Q //직렬 출력
);
wire [2:0] dff_o;
wire [2:0] mux_o;
D_flip_flop_negedge DFF3(.D(D[3]), .E(clk), .Q(dff_o[2]));
D_flip_flop_negedge DFF2(.D(mux_o[2]), .E(clk), .Q(dff_o[1]));
D_flip_flop_negedge DFF1(.D(mux_o[1]), .E(clk), .Q(dff_o[0]));
D_flip_flop_negedge DFF0(.D(mux_o[0]), .E(clk), .Q(Q));
mux_2_1 mux2(.D({dff_o[2], D[2]}), .S(shift_load), .F(mux_o[2]));
mux_2_1 mux1(.D({dff_o[1], D[1]}), .S(shift_load), .F(mux_o[1]));
mux_2_1 mux0(.D({dff_o[0], D[0]}), .S(shift_load), .F(mux_o[0]));
endmodule
앞서 말했듯, shift_load(SH/LD)의 기능은 shift_load가 0일 때, 클럭이 하강엣지라면 비트를 읽고 출력을 한다. shift_load가 1일 때, 클럭이 하강엣지라면 비트 쉬프트가 이루어진다. 최하위 비트부터 최상위 비트 순으로 출력된다. 즉 D값이 반전되어 Q로 출력된다. 모든 출력이 끝난 이후에 shift_load가 0일 때와 클럭이 하강엣지가 만나지 않는다면 계속해서 최상위 비트의 출력이 유지된다.
.
빨간색 팬으로 표시한 첫 번째 하강엣지에서 shift_load가 0일 때 매치되었으므로 읽자마자 출력이 시작된다. 다음 비트 출력은 클럭이 하강엣지일 때, 1을 만나야한다. 그 다음 클럭의 하강엣지에서는 shift_load가 1이므로 Q에 다음 비트가 출력되는 것을 알 수 있다.
분홍색 팬으로 표시한 부분도 마찬가지로 같은 과정에 의해 1001이 출력됨을 알 수 있다.
6. Verilog에서 병렬입력-병렬출력 D플립플롭 4개를 이용한 register 구현
.
병렬입력-병렬출력 레지스터는 입력한 값이 이동하지 않고 그대로 4비트가 출력되기 때문에 더이상 Shift를 하지 않으므로 그냥 레지스터이다.
WR input이 0이 되면 AND 게이트와 입력이 묶여 게이트의 출력을 입력으로 넣는다. 그래서 WR이 0이면 WR Off상태, 1이면 On상태이다.
WR이 0일 때, AND가 0을 출력하게 되므로 0000이 출력될 것이다. 그러나 1일 때는 입력 값이 그대로 출력되어 4비트 그대로 출력된다.
RD는 레지스터의 출력 값과 3상버퍼에 묶여서 출력 값이 다시 출력된다. RD가 0일 때는 읽어오는 것이 활성화 되지 않는다고 생각하면 된다. 따라서 그대로 출력 값이 출력될 것이다. 그러나 1일 때는 읽고 있으므로 임피던스가 출력된다. 코드를 보자.
module shift_register_PIPO(
input [3:0] D, //병렬 입력
input clk,
input wr_e_p, rd_e_n, //wr는 posedge에서 rd는 negedge에서
output [3:0] Q //병렬 출력
);
wire [3:0] dff_o;
wire [3:0] and_o;
and (and_o[3], D[3], wr_e_p);
and (and_o[2], D[2], wr_e_p);
and (and_o[1], D[1], wr_e_p);
and (and_o[0], D[0], wr_e_p);
D_flip_flop_negedge DFF3(.D(and_o[3]), .E(clk), .Q(dff_o[3]));
D_flip_flop_negedge DFF2(.D(and_o[2]), .E(clk), .Q(dff_o[2]));
D_flip_flop_negedge DFF1(.D(and_o[1]), .E(clk), .Q(dff_o[1]));
D_flip_flop_negedge DFF0(.D(and_o[0]), .E(clk), .Q(dff_o[0]));
bufif0 (Q[0], dff_o[0], rd_e_n); //3상 버퍼 : 0일 때 출력이 나온다.
bufif0 (Q[1], dff_o[1], rd_e_n);
bufif0 (Q[2], dff_o[2], rd_e_n);
bufif0 (Q[3], dff_o[3], rd_e_n);
endmodule
게이트 레벨 모델링으로 병렬입력-병렬출력을 구현해보았다. 테스트벤치를 통해 동작을 확인해보자.
.
출력 Q가 언제 값이 변화하는지 보자. Q가 처음에 임피던스가 나오는 이유는 rd_e_n의 값이 1에서 0으로 바뀌었다. 이 때 읽어오기를 안해도 되기 때문에 임피던스가 아닌 출력 값이 출력된다고 앞서 말한 적이 있다. 하지만 wr_e_p값이 0이므로 AND게이트에서는 0을 출력할 것이다. 따라서 0 0 0 0이 출력된다. 이후에 클럭이 wr_e_p가 1일 때 하강엣지가 발생되어 읽은 값을 그대로 출력한다.다시 wr_e_p가 0일 때는 하강엣지에서 바로 0 0 0 0으로 바뀌는 것을 알 수 있다.
7. 3상 버퍼란?
전자회로 분야에서의 3상 버퍼는 간단하게 말하면 다음과 같다. 어떠한 회로가 있다고 가정하자. 버스를 통해 수많은 모듈의 데이터가 송수신되는 것은 누구나 알고 있다. 하지만 사용하지 않는 모듈로부터 데이터가 지속적으로 전송된다면 하나의 버스에서 여러 모듈의 데이터를 공유하는 시스템에게는 매우 치명적이다. 이를 위해 모듈을 사용하지 않을 때, 출력에 Z(임피던스)를 보내주어 모듈의 output 선이 버스와 차단되도록 한다.
따라서 이러한 일을 수행하기 위해서 어떠한 모듈의 output 단에 3상 버퍼를 달아주면, 사용하지 않을 때는 Z(임피던스)를 출력하고, 사용할 때는 모듈의 출력 값을 내보낸다. 결국 3상 버퍼에는 출력이 될 데이터와 출력을 할지 말지 결정할 enable 비트, 총 2개의 input을 받아 enable 비트에 따라 출력이 결정(출력 데이터 or 임피던스)된다.