2024.6.26 수업날
오늘부터 베릴로그 수업을 다시 진행한다.
저번에 배웠던 레지스터를 이어서 배워보고, 메모리에 대해 알아보며 스톱워치 만들기 초기단계를 진행한다.
< 병렬입력-병렬출력 레지스터 >
병렬입력-병렬출력 레지스터는 입력을 AND 게이트로 입력값으로 연결하여
WR가 1이면 AND게이트를 통해 D플립플롭의 입력으로 들어간다. 만약 WR가 0이면 0이 써지므로 초기화가 된다.
병렬입력 병렬 출력 레지스터에도 3상버퍼가 쓰인다.
E의 값이 0이면 출력값이 입력값을 따라가고, E의 값이 1이면 출력값은 모두 임피던스가 된다.
병렬입력-병렬출력 레지스터
/////////////////////////////////////////////////// 2024.6.26
//병렬입력 병렬출력 레지스터
module register_n(
input [7:0]d,
input clk,
input reset_p,
input wr_en, //WR Enable
output reg [7:0] q);
always @ (negedge clk or posedge reset_p) begin
if(reset_p) q = 0; // reset을 우선시하는 if 문
else if (wr_en) q = d; //enable 이 1일때만
end
endmodule
먼저 8비트 병렬입력, 8비트 병렬출력 레지스터 소스코드를 살펴보자.
always문에서 wr_en이 1일때만 d의 입력을 q에 출력한다.
n비트 병렬입력 병렬출력 레지스터 소스코드
/////////////////////////////////////////////// n비트 병렬입력 병렬출력 레지스터, 여기서는 8비트
module register_Nbit_n # (parameter N = 8) (
input [N-1:0] d,
input clk,
input reset_p,
input wr_en, rd_en, //WR Enable, Read Enable
output [N-1:0] q);
reg [N-1:0] register;
always @ (negedge clk or posedge reset_p) begin
if(reset_p) register = 0;
else if (wr_en) register = d;
end
assign q = rd_en ? register : 'bz; // rd_en이 1일때만 데이터가 출력으로 나오고 0일때는 연결을 끊음
endmodule
다음으로 n비트 병렬입력 병렬출력 레지스터 소스코드를 살펴보자.
assign 이 추가되었는데
이 뜻은 rd_en이 1일때, 데이터가 register 출력으로 나오고, 0일 때는 연결을 끊는다는 의미이다.
< 메모리 >
레지스터의 종류를 살펴보았으니, 이제 메모리에 대해 알아보자.
예를 들어 8비트 D플립플롭 레지스터가 있다고 가정해보자.
한 열에는 8개의 D플립플롭이 연결되어 있으며, 입력을data_in, rd_en, clk, wr_en으로 설정하고 출력을 data_out으로 설정한다.
그리고 그 아래로 8비트 D플립플롭이 아래의 그림처럼 4세트가 더 있다고 가정해보자
여기서 data_in끼리, data_out끼리 하나로 묶어보자.
입력과 출력을 bus 선으로 묶으면 레이싱현상이 발생하게된다. 어느 출력은 0, 어느 출력은 1이 나타나게 된다. 즉, 하나의 값으로 통일되지 않는다는 것이다.
이를 해결하기 위해 디멀티플렉서(demux)를 사용해서 선택선 s에 따라 rd_en에 어떤 값이 입력되느냐에 따라 어떤 8비트 D 플립플롭 레지스터를 사용할 것인지를 정한다.
즉, read할 때 해당되는 8비트 레지스터만 출력으로 내보내고, 나머지는 차단하는 인터럽트가 일어나는 것이다.
이렇게 en, demux의 rd_en 값이 어떤가에 따라 어느 레지스터 출력을 내보낼지 제어가 되는 것을 데이터버스라고 부른다.
예를 들어, 8비트 D플립플롭 레지스터가 100개가 있다고 가정해보자. 그러면 demux의 선택선 s를 최소 7비트로 만들어야 한다.(2^7=128) 그리고 선택선 7비트짜리 s에 따라 rd_en 값을 조정하여 100개의 레지스터를 제어할 수 있는 것이다.
이처럼 메모리는 레지스터들의 배열로 이루어져 있다.
그리고 어떤 경우는 입출력을 bus선으로 묶어서 공통으로 쓰기도 한다.
다음으로 RAM에 대해 알아보자.
저번에도 RAM에 대해서 설명한 적이 있다. 이번에는 SRAM과 DRAM을 비교해본다.
DRAM에는 SDRAM(싱글), DDRAM(더블, 양면)이 있다. 한쪽 면에만 있으면 SDRAM이고, 양쪽 면에만 있으면 DDRAM이다.
8비트 SRAM 소스코드
//////////////////////////////////////////////////// 8비트 sram
module sram_8bit_1024( // 메모리는 리셋이 없음, 할 방법도 없음, 전원 키면 0으로 초기화됨
input clk,
input wr_en, rd_en,
input [9:0] addr, // 1024개-> 10비트 필요
inout [7:0] data); //inout: 입력과 출력을 하나로 묶음, 출력 할 때만 입력에 인피던스 하면 됨
reg [7:0] mem [0:1023]; // mem = memory, [비트선언] 변수 [배열선언] 형식 의미
always @ (posedge clk) begin
if(wr_en)mem[addr] = data; // 먹스 대신에 주소 인덱싱-> 여러개 중에 몇번 주소가 나갈건지
end
assign data = rd_en ? mem[addr] : 'bz; // rd_en이 1일때만 데이터가 출력으로 나오고 0일때는 연결을 끊음
endmodule
8비트 SRAM 소스코드에 대해 알아보자.
메모리는 리셋이 없고 할 방법도 없다. 전원을 키면 0으로 초기화되기 때문에 리셋을 쓰지 않아도 된다.
SRAM 소스코드에도 마찬가지로 rd_en이 1일때만 데이터가 mem[addr]출력으로 나오고 0일때는 연결을 끊는다.
다음으로 플래시 메모리에 대해 알아보자.
플래시 메모리는 읽기,쓰기,지우기가 가능한 EEPORM의 한 종류이다.
전원이 끊겨도 데이터를 보존하여 USB 메모리라고도 불린다.
우리가 흔히 쓰는 USB도 USB를 뺐다가 다시 전원부에 연결해도 메모리는 저장된다.
다음으로 프로그래머블 논리장치에 대해 알아보자.
PLD에는 SPLD, 그리고 우리가 흔히 쓰는 FPGA가 있다.
< 스톱워치 만들기 초기단계 >
이제 논리회로, 조합회로, 순서회로에 대해 알아보았으니 그동안 배운 내용을 가지고 스톱워치를 만들어본다.
우선 복습할겸 Basys3 보드에 있는 버튼을 가지고 버튼을 누를때 led가 들어오도록하는 확인용 소스코드를 작성한다.
버튼 컨트롤러 확인용 소스코드
/////////////////////////////////////////////////// 2024.6.26
////////// 버튼 컨트롤러 확인용
module button_cntr(
input btn,
output led);
assign led = btn;
endmodule
소스코드는 확인용이기 때문에 간략하게 적었다.
이는, Basys3의 btnU(가장 상단에 있는 버튼)를 눌렀을 때만 led 0번에 불이 들어오도록 하는 것이다.
이번에도 Basys3과 연결하기 위해 xdc 파일을 수정해야한다.
Buttons에서 btnL을 btn으로, LEDs에서 가장 상단의 LED를 led로 수정한다.
코드 수정을 한 후에는 Sources-Design Sources에서 button_cntr를 set top으로 설정한다.
이번에도 바로 Basys3에서 결과를 확인하기 위해 Bitstream을 선택한다.
로딩이 완료되면 Hardware Manager를 선택한다.
다음으로 상단의 Program device를 선택해도 되고, 바로 아래의 빨간 밑줄로 친 기호를 선택해도 된다.
그러면 Name이 아래의 사진처럼 뜨는데, 이때 두번째 줄이 우리가 연결한 보드라는 의미이다.
그러면 아래처럼 btnL을 눌렀을 때만 led가 불이 들어오는 결과를 확인할 수 있다.
다음으로, 스톱워치에서 버튼을 한 번 누르면 계속 초가 카운트가 되고 버튼을 다시 한 번 누르면 일시정지가 되거나 리셋되는 것처럼 버튼을 한 번 누르면 계속 켜지고, 다시 한 번 누르면 꺼지는 소스코드를 만들어보자. 이는 값이 토글이되는 T플립플롭과 유사하다.
채터링을 제거한 button_cntr_reg, 상승에지 하강에지에서 작동하는 button_cntr_reg_n_p 소스코드
///////////////////// 버튼을 한 번 누르면 켜지는것이 유지되고, 또 한번 누르면 led가 꺼짐--> 레지스터-->토글--> t플립플롭
module button_cntr_reg(
input clk, reset_p,
input btn,
output led);
reg [16:0] clk_div; //16번비트가 1.3ms에 한번씩 에지가 나옴, 채터링을 없애기 위하여
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_16_pedge;
edge_detector_n ed_div(
.clk(clk), .reset_p(reset_p),
.cp(clk_div[16]), .p_edge(clk_div_16_pedge));
reg debounced_btn; // debounced가 채터링을 막는다, bounced가 채터링
always @ (posedge clk or posedge reset_p)begin //always에서는 섞어서 쓰질 못함, 엣지면 엣지 레벨이면 레벨
if(reset_p) debounced_btn = 0;
else if(clk_div_16_pedge)debounced_btn = btn;
end
wire btn_pedge;
edge_detector_n ed(.clk(clk), .reset_p(reset_p), .cp(debounced_btn), .p_edge(btn_pedge)); // 버튼 입력을 받고 1사이클에 1을 한번만
T_flip_flop_p t0(.clk(clk), .reset_p(reset_p), .t(btn_pedge), .q(led));
endmodule
///////////////////// 버튼을 한 번 누르면 켜지는것이 유지되고, 또 한번 누르면 led가 꺼짐--> 레지스터-->토글--> t플립플롭
module button_cntr_reg_n_p(
input clk, reset_p,
input btn,
output btn_pedge, btn_nedge);
reg [16:0] clk_div; //16번비트가 1.3ms에 한번씩 에지가 나옴, 채터링을 없애기 위하여
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_16_pedge;
edge_detector_n ed_div(
.clk(clk), .reset_p(reset_p),
.cp(clk_div[16]), .p_edge(clk_div_16_pedge));
reg debounced_btn; // debounced가 채터링을 막는다, bounced가 채터링
always @ (posedge clk or posedge reset_p)begin //always에서는 섞어서 쓰질 못함, 엣지면 엣지 레벨이면 레벨
if(reset_p) debounced_btn = 0;
else if(clk_div_16_pedge)debounced_btn = btn;
end
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(debounced_btn),
.p_edge(btn_pedge), .n_edge(btn_nedge)); // 버튼 입력을 받고 1사이클에 1을 한번만
endmodule
위와같이 소스코드를 작성하지 않으면 채터링 현상이 발생한다.
채터링 현상이 발생하면 실질적으로 원하는 값이 안나오고 잡음 현상이 발생하여 잘못된 값이 초반에 출력된다.
때문에 1.3ms 마다 한번씩 edge가 발생하도록 딜레이 시간을 주면 채터링 현상이 지난 결과를 읽으므로 딜레이 시간을 주도록 코드를 작성한 것이다.
채터링 현상이 나타나지 않도록 설정하면 아래와 같이 버튼을 한 번 눌렀을 때는 led가 켜지고
다시 한 번 누르면 led가 꺼지는 원하는 결과가 나오는 것을 확인할 수 있다.
< 분주기 카운터 >
다음으로 분주기 카운터에 대해 알아보자.
+ 기호를 클릭하여 새로운 파일을 만든 후 Create File을 클릭하여 아래와 같이 이름을 만들고, Finish를 클릭한다.
분주기 카운터 소스코드
//////////////////////////////////////////// 2024.6.26 클록 세기
module clock_usec(
input clk, reset_p,
output clk_usec, // 1마이크로 세크마다 1 펄스를 하나씩 발생시킴
output cp_usec ); // 클락 펄스 마이크로세크
// reg [6:0] cnt_sysclk; //100분주기, 시스템 클락 카운터, 100개-> 최소 7비트(2^7=128)
reg [9:0] cnt_sysclk; //1000분주기, 시스템 클락 카운터, 1000개-> 최소 7비트(2^10=1024)
always @(posedge clk or posedge reset_p) begin
if(reset_p) cnt_sysclk = 0;
else begin
//if(cnt_sysclk >= 99) cnt_sysclk = 0; // 100분주기, 99보다 크거나 같으면 카운터를 0으로 리셋, 그렇지 않으면 1증가
if(cnt_sysclk >= 999) cnt_sysclk = 0; // 1000분주기, 999보다 크거나 같으면 카운터를 0으로 리셋, 그렇지 않으면 1증가
else cnt_sysclk = cnt_sysclk + 1; // 1씩 증가하는 카운터
end
end
//assign cp_usec = (cnt_sysclk < 50) ? 0 : 1; // 100분주기 일때
assign cp_usec = (cnt_sysclk < 500) ? 0 : 1; // 1000분주기
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(cp_usec),
.n_edge(clk_usec));
endmodule
먼저 10ns의 입력카운터를 입력하고 100분주기로 설정한 시뮬레이션 결과를 살펴보자.
clk: 10ns, reset_p: 1(constant), 시뮬레이션 돌리는 시간: 10ns으로 설정하고 시뮬레이션을 한 번 실행한다.
다음으로 reset_p: 0(constant), 시뮬레이션 돌리는 시간: 1000us으로 하면 아래와 같이 결과가 나온다.
그리고 clk_usec를 살펴보자. 커서를 추가하면 원하는 지점 사이의 값이 어떤지 알 수 있다.
추가한 커서를 기준으로(파란색) 노랑색 커서와 1ns 차이가 있다는 것을 알 수 있다.
다음으로 1us의 입력카운터를 입력하고 1000분주기로 설정한 시뮬레이션 결과를 살펴보자.
clk: 1us, reset_p: 1(constant), 시뮬레이션 돌리는 시간: 1us으로 설정하고 시뮬레이션을 한 번 실행한다.
다음으로 reset_p: 0(constant), 시뮬레이션 돌리는 시간: 1000ms으로 하면 아래와 같이 결과가 나온다.
그리고 clk_usec를 살펴보자. 커서를 추가하면 원하는 지점 사이의 값이 어떤지 알 수 있다.
추가한 커서를 기준으로(파란색) 노랑색 커서와 1ms 차이가 있다는 것을 알 수 있다.
병렬입력-병렬출력 레지스터, 메모리, 스톱워치 만들기 초기 진행단계 끝!
'Harman 세미콘 아카데미 > Verilog' 카테고리의 다른 글
2024.6.28[Verilog]⑨ 스톱워치 만들기 기능추가+응용 (0) | 2024.06.28 |
---|---|
2024.6.27 [Verilog]⑧ - 스톱워치 만들기 (0) | 2024.06.27 |
2024.6.21 [Verilog]⑥ - 링카운터 활용 + basys3 보드 연결, 레지스터 (0) | 2024.06.21 |
2024.6.20 [Verilog]⑤ - 비동기 카운터, 동기카운터, BCD 카운터, 업다운 카운터, 링카운터, 주종형 D플립플롭 (0) | 2024.06.20 |
2024.6.19 [Verilog]④ - 7세그먼트 디코더, 인코더, 멀티플렉서+디멀티플렉서, 플립플롭 (0) | 2024.06.19 |