2023. 3. 15. 16:18ㆍVerilog
FPGA 보드 실습
FPGA LED 카운터 실습
`timescale 1ns / 1ps
module counter_mode_16(
input clk, rst,
output reg [3 : 0] q,
output tco //carry out
);
always @(posedge clk, posedge rst) begin
if(rst) begin
q<= 0;
end
else q <= q+4'b1;
end
assign tco = (q == 4'hf) ? 1'b0 : 1'b1;
endmodule
0~16까지 카운트하고, LED로 결과를 출력하는 카운터이다.
그러나 CPU의 클럭이 100 Mhz기 때문에, 너무 빨리 토글 되기 때문에 모두 켜져 있는 것처럼 보인다.
따라서 클럭을 분주해줘야한다.
1초마다 한번씩 카운트하려면, 0.5초마다 신호가 반전되게 설정하면 된다. 덧셈기를 하나 만들어 counter 레지스터가 5천만이 될 때마다 신호를 반전시키면 된다.
reg [31 : 0] counter;
reg clock_clk;
always @(posedge clk, posedge rst) begin
if(rst) begin
counter <= 0;
clock_clk <= 0;
end
else begin
if(counter < 32'd49999999)
counter <= counter + 32'd1;
else begin
counter <= 32'd0;
clock_clk <= ~clock_clk;
end
end
end
전체 코드는 다음과 같다. LED를 토글 하는 코드는 clk의 라이징 엣지가 아닌 새롭게 만든 신호의 라이징 엣지에 동작시키면 된다.
`timescale 1ns / 1ps
module counter_mode_16(
input clk, rst,
output reg [3 : 0] q,
output tco //carry out
);
reg [31 : 0] counter;
reg clock_clk;
always @(posedge clk, posedge rst) begin //분주 코드
if(rst) begin
counter <= 0;
clock_clk <= 0;
end
else begin
if(counter < 32'd49999999)
counter <= counter + 32'd1;
else begin
counter <= 32'd0;
clock_clk <= ~clock_clk;
end
end
end
always @(posedge clock_clk, posedge rst) begin //카운터 코드
if(rst) begin
q<= 0;
end
else q <= q+4'b1;
end
assign tco = (q == 4'hf) ? 1'b0 : 1'b1;
endmodule
내부 신호들을 실제 회로에 매핑시켜준다.
set_property PACKAGE_PIN W5 [get_ports clk]
set_property PACKAGE_PIN U16 [get_ports {q[0]}]
set_property PACKAGE_PIN E19 [get_ports {q[1]}]
set_property PACKAGE_PIN U19 [get_ports {q[2]}]
set_property PACKAGE_PIN V19 [get_ports {q[3]}]
set_property PACKAGE_PIN U18 [get_ports rst]
set_property PACKAGE_PIN U15 [get_ports tco]
set_property IOSTANDARD LVCMOS33 [get_ports {q[3]}] //3.3V 사용
set_property IOSTANDARD LVCMOS33 [get_ports {q[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {q[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {q[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports tco]
시프트 레지스터
한클럭에 한 비트씩 오른쪽으로 이동하는 모듈을 시프트 레지스터라고 한다.
4비트 Right 시프터는 시프트가 될 때마다 값을 2로 나누는 나눗셈기로 동작할 수 있다. 원래 4'b1010 (4'D10)이 들어있던 레지스터에 0을 넣어 우측 쉬프트하면 4'b0101(4'D5)가 될 것이다.
N비트 병렬 좌우 시프트 레지스터
module shiftReg_Nbit #(parameter WD = 4)(
input clk, rst, Lshift, Rshift, load,
input [WD-1 : 0] in,
output reg[WD-1:0] shift
);
always @(posedge clk) begin
if(rst)
shift <= {WD{1'b0}};
else if (load)
shift <= in;
else if (Rshift)
shift <= {in[WD-1], shift[WD-1 : 1]};
else if (Lshift)
shift <= {shift[WD-2 : 0], in[0]};
end
endmodule
테스트 벤치
`timescale 1ns / 1ps
module shift_n_tb;
parameter N = 8;
reg[N-1:0] in;
wire [N-1:0] out;
reg clk, rst, Lshift, Rshift, load;
integer i;
shiftReg_Nbit #(N) U1(.clk(clk), .rst(rst), .Lshift(Lshift), .Rshift(Rshift), .load(load), .in(in), .shift(out));
initial begin
clk = 0; rst = 0; Lshift = 0; Rshift = 0; load = 0;
#100 load = 1;
#50 in = 8'h14;
#100 load = 0; in = 0; Lshift = 1;
#400 Lshift = 0;
#100 in = 8'h1A; load = 1;
#100 load = 0; in = 0; Rshift = 1;
#400 Rshift = 0;
end
//always #100 Lshift = ~ Lshift;
always #40 clk = ~clk;
endmodule
Load신호가 들어온 후 데이터를 저장한 후, Lshift신호가 켜지면 왼쪽 쉬프트, Rshift 신호가 켜지면 한비트씩 우측 쉬프트를 하는 모습을 볼 수 있다.
테스트벤치의 파라미터를 변경해도 잘 실행되는것을 볼 수 있다.
left 쉬프트를 하면 2씩 곱하는 곱셈기로 동작하는 것을 볼 수 있고, right 쉬프트를 하면 2씩 나누는 나눗셈기로 동작하는 것을 볼 수 있다.
module shiftReg_Nbit #(parameter WD = 4)(
input clk, rst, Lshift, Rshift, load,hold,
input [WD-1 : 0] in,
output reg[WD-1:0] shift
);
always @(posedge clk) begin
if(rst)
shift <= {WD{1'b0}};
else if (load)
shift <= in;
else if (Rshift)
shift <= {in[WD-1], shift[WD-1 : 1]};
else if (Lshift)
shift <= {shift[WD-2 : 0], in[0]};
else if (hold)
shift <= shift;
end
endmodule
BCD 카운터를 이용한 시계 실습
시계는 분 : 초 와 같은 형식으로 되어있고, 10진수를 사용하며 분, 초는 60이 되면 초기화 해야한다. 따라서 BCD(이진화 10진코드)를 사용해 10진수로 수를 세고, 초가 60이 되면 분을 1증가시켜야한다.
1. BCD 카운터
`timescale 1ns / 1ps
module binary_counter #(parameter N = 10)(
input clk, rst, carry_in,
output reg [3 : 0] q,
output reg carry //carry out
);
always @(posedge clk, posedge rst) begin
if(rst) begin
q <= 0;
carry <= 0;
end
else begin
if(q < N-1) begin
q <= q + 1;
carry <= 0;
end
else begin
q <= 0;
carry <= 1;
end
end
end
endmodule
파라미터로 값을 변경할 수 있는 BCD 카운터이다. 파라미터를 6으로 설정하면 0-1-2-3-4-5 까지 수를 센다.
`timescale 1ns / 1ps
module counter_clock(
input clk, rst,
output [15:0] data //min_ten, min_one, sec_ten, sec_one
);
reg [31 : 0] counter;
reg clock_clk;
reg sec_clk;
wire carry_sec_one;
wire carry_sec_ten;
wire carry_min_one;
wire carry_min_ten;
always @(posedge clk, posedge rst) begin
if(rst) begin
counter <= 0;
clock_clk <= 0;
end
else begin
if(counter < 32'd50000000) //50000000
counter <= counter + 32'd1;
else begin
counter <= 32'd0;
clock_clk <= ~clock_clk;
end
end
end
binary_counter #(10) SEC_ONE(.clk(clock_clk), .rst(rst), .q(data[3:0]), .carry(carry_sec_one));
binary_counter #(6) SEC_TEN(.clk(carry_sec_one), .rst(rst), .q(data[7:4]), .carry(carry_sec_ten));
binary_counter #(10) MIN_ONE(.clk(carry_sec_ten), .rst(rst), .q(data[11:8]), .carry(carry_min_one));
binary_counter #(6) MIN_TEN(.clk(carry_min_one), .rst(rst), .q(data[15:12]), .carry(carry_min_ten));
endmodule
실제 시간을 세는 시계 모듈이다. cpu 클럭 100Mhz를 받아 분주기를 거쳐 1초마다 초를 1씩 증가시키고, BCD카운터를 이용해 1초마다 수를 증가시켜 16비트의 데이터에 값을 나눠 집어넣는다.
시뮬레이터를 이용해 확인한 data의 모습이다. 시간이 잘 가는것을 볼 수 있다.
시간을 세는 코드는 완성했으니, 이제 7-세크먼트 드라이버를 작성해야 한다.
7세그먼트는 공통 양극 타입으로 cpu와 연결되어있다. 따라서 각 자리를 enable하고 싶으면 0으로 만들어야한다.
`timescale 1ns / 1ps
module hex_to_seg(
input wire[3:0] data,
output reg[7:0] seg_out
);
parameter ZERO = 7'h01;
parameter ONE = 7'h4F;
parameter TWO= 7'h12;
parameter THREE = 7'h06;
parameter FOUR = 7'h4C;
parameter FIVE = 7'h24;
parameter SIX = 7'h20;
parameter SEVEN = 7'h0D;
parameter EIGHT = 7'h00;
parameter NINE = 7'h04;
parameter ERR = 7'h7E;
always @(data) begin
case (data)
4'd0: begin
seg_out <= {8'b0};
seg_out <= {ZERO,1'b1};
end
4'd1: begin
seg_out <= {8'b0};
seg_out <= {ONE,1'b1};
end
4'd2: begin
seg_out <= {8'b0};
seg_out <= {TWO,1'b1};
end
4'd3: begin
seg_out <= {8'b0};
seg_out <= {THREE,1'b1};
end
4'd4: begin
seg_out <= {8'b0};
seg_out <= {FOUR,1'b1};
end
4'd5: begin
seg_out <= {8'b0};
seg_out <= {FIVE,1'b1};
end
4'd6: begin
seg_out <= {8'b0};
seg_out <= {SIX,1'b1};
end
4'd7: begin
seg_out <= {8'b0};
seg_out <= {SEVEN,1'b1};
end
4'd8: begin
seg_out <= {8'b0};
seg_out <= {EIGHT,1'b1};
end
4'd9: begin
seg_out <= {8'b0};
seg_out <= {NINE,1'b1};
end
default : seg_out <= {7'b1,1'b1};
endcase
end
endmodule
BCD 데이터를 받아 세그먼트의 데이터로 변환해주는 세그먼트 드라이버이다. 4비트 데이터를 받아 데이터에 따른 숫자를 받아 세그먼트의 데이터 핀으로 출력한다.
`timescale 1ns / 1ps
module seven_seg(
input clk, rst,
input [15:0] data,
output reg [7:0] seg, //seg out
output reg [3:0] com_out //digit bit
);
reg [19:0] cnt;
reg [1:0] com_cnt;
wire[7:0] seg0;
wire[7:0] seg1;
wire[7:0] seg2;
wire[7:0] seg3;
hex_to_seg h1(.data(data[3:0]), .seg_out(seg0));
hex_to_seg h2(.data(data[7:4]), .seg_out(seg1));
hex_to_seg h3(.data(data[11:8]), .seg_out(seg2));
hex_to_seg h4(.data(data[15:12]), .seg_out(seg3));
always @(posedge clk, posedge rst) begin //to make 600us signal to toggle 7seg's digit
if(rst) begin
cnt <= 20'd0;
com_cnt <= 2'd0;
end
else begin
if(cnt >150000)begin
cnt <= 0;
com_cnt <= com_cnt + 1;
end
else begin
cnt <= cnt + 1;
end
end
end
always @(com_cnt) begin
case (com_cnt)
2'b00 : begin seg <= seg0; com_out <= 4'b1110; end
2'b01 : begin seg <= seg1; com_out <= 4'b1101; end
2'b10 : begin seg <= seg2; com_out <= 4'b1011; end
2'b11 : begin seg <= seg3; com_out <= 4'b0111; end
default: begin seg <= seg0; com_out <= 4'b1111; end
endcase
end
endmodule
7세그먼트에 출력할 자리수를 선택하는 모듈이다. 7세그먼트는 각 자리수를 짧은 시간동안 반복해 4자리를 출력하는 방법인 다이나믹 드라이버를 쓴다. 0번->1번->2번->3번 순으로 계속 반복해 적은 핀 수로도 다양한 자리를 컨트롤 할 수 있다는 장점이 있다. 하지만 10ns마다 출력하기에는 자원의 낭비가 너무 심하니, 데이터시트에 나온대로 1~15ms 사이의 값을 주었다.
`timescale 1ns / 1ps
module top(
input clk, rst,
output [7:0] seg_out,
output [3:0] com_out
);
wire [15:0] data;
counter_clock clock(.clk(clk), .rst(rst), .data(data));
seven_seg seg(.clk(clk), .rst(rst), .data(data), .seg(seg_out), .com_out(com_out));
endmodule
드라이버는 모두 작성했으니 이제 드라이버를 컨트롤할 최상위 모듈을 만들어야한다.
set_property PACKAGE_PIN W5 [get_ports clk]
set_property PACKAGE_PIN V7 [get_ports seg_out[0]]
set_property PACKAGE_PIN U7 [get_ports seg_out[1]]
set_property PACKAGE_PIN V5 [get_ports seg_out[2]]
set_property PACKAGE_PIN U5 [get_ports seg_out[3]]
set_property PACKAGE_PIN V8 [get_ports seg_out[4]]
set_property PACKAGE_PIN U8 [get_ports seg_out[5]]
set_property PACKAGE_PIN W6 [get_ports seg_out[6]]
set_property PACKAGE_PIN W7 [get_ports seg_out[7]]
set_property PACKAGE_PIN U2 [get_ports com_out[0]]
set_property PACKAGE_PIN U4 [get_ports com_out[1]]
set_property PACKAGE_PIN V4 [get_ports com_out[2]]
set_property PACKAGE_PIN W4 [get_ports com_out[3]]
set_property PACKAGE_PIN U18 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[0]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[1]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[2]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[3]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[4]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[5]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[6]]
set_property IOSTANDARD LVCMOS33 [get_ports seg_out[7]]
set_property IOSTANDARD LVCMOS33 [get_ports com_out[0]]
set_property IOSTANDARD LVCMOS33 [get_ports com_out[1]]
set_property IOSTANDARD LVCMOS33 [get_ports com_out[2]]
set_property IOSTANDARD LVCMOS33 [get_ports com_out[3]]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst]
이제 코드와 실제 핀을 매핑해주면 완성이다.
'Verilog' 카테고리의 다른 글
verilog 11 cpu 제작_1 (0) | 2023.03.20 |
---|---|
verilog 10 FSM/ASM (0) | 2023.03.17 |
verilog7 (0) | 2023.03.14 |
verilog 5 (0) | 2023.03.13 |
verilog 3 (0) | 2023.03.10 |