- Published on
FPGA 온칩 메모리 읽기 및 쓰기
- Authors
- Name
- JaeHyeok CHOI
- none
FPGA 온칩 RAM 읽기 및 쓰기
ip Catalog > Block Memory Generator > SD port RAM 선택
Port A 세팅 Port A Witdh: 16 Port A Width: 512 Enable Port Type: Always Enalbed
Port B 세팅 Port B Witdh: 16 Port B Width: 512 Enable Port Type: Always Enalbed Primitives Output Register: 체크 해제
RAM 포트 정의 및 타이밍
신호명 | 방향 | 설명 |
---|---|---|
clka | in | A클록입력 |
wea | in | A활성화됨 |
addra | in | A주소입력 |
dina | in | A데이터 입력 |
clkb | in | B클록입력 |
addrb | in | B주소입력 |
doutb | out | B데이터출력 |
RAM 쓰기 타이밍
RAM 읽기 타이밍
테스트 벤치
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
module ram_test(
input sys_clk_p, //system clock 200Mhz on board
input sys_clk_n, //system clock 200Mhz on board
input rst_n
);
//-----------------------------------------------------------
reg[8:0] w_addr;
reg[15:0] w_data;
reg wea;
reg[8:0] r_addr;
wire[15:0] r_data;
wire clk ;
IBUFDS #(
.DIFF_TERM("FALSE"), // Differential Termination
.IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_inst (
.O(clk), // Buffer output
.I(sys_clk_p), // Diff_p buffer input (connect directly to top-level port)
.IB(sys_clk_n) // Diff_n buffer input (connect directly to top-level port)
);
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
r_addr <= 9'd0;
else if (|w_addr)
r_addr <= r_addr+1'b1;
else
r_addr <= 9'd0;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wea <= 1'b0;
else
begin
if(&w_addr)
wea <= 1'b0;
else
wea <= 1'b1;
end
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
w_addr <= 9'd0;
w_data <= 16'd1;
end
else
begin
if(wea)
begin
if (&w_addr)
begin
w_addr <= w_addr ;
w_data <= w_data ;
end
else
begin
w_addr <= w_addr + 1'b1;
w_data <= w_data + 1'b1;
end
end
end
end
//-----------------------------------------------------------
ram_ip ram_ip_inst (
.clka (clk ), // input clka
.wea (wea ), // input [0 : 0] wea
.addra (w_addr ), // input [8 : 0] addra
.dina (w_data ), // input [15 : 0] dina
.clkb (clk ), // input clkb
.addrb (r_addr ), // input [8 : 0] addrb
.doutb (r_data ) // output [15 : 0] doutb
);
ila_0 ila_0_inst (
.clk (clk ),
.probe0 (r_data ),
.probe1 (r_addr )
);
endmodule
FPGA 온칩 ROM 읽기 및 쓰기
FPGA 자체는 SRAM 아키텍처를 기반으로 하며 전원이 거지면 프로그램이 사라진다. FPGA 내부의 RAM 리소스를 사용하여 ROM을 구현할 수는 있지만 ROM이 아니다. 이는 전원을 켤 때마다 이미 초기화된 값을 RAM에 쓰면서 동작하는 방식이다.
ROM 초기화 파일 생성
Xilinx FPGA의 온칩 ROM은 초기화 데이터 구성을 지원한다. rom_init.coe라는 파일을 만들어서 사용해야 한다.
ROM 초기화 파일의 내용 형식은 아래와 같다.
MEMORY_INITIALIZATION_RADIX=16; // 각 데이터를 쉼표/공백/줄바꿈으로 구분, 마지막 데이터 뒤에 `세미콜론` 추가
MEMORY_INITIALIZATION_VECTOR=
11,
22,
33,
44,
55,
66,
77,
88,
99,
aa,
bb,
cc,
dd,
ee,
ff,
00,
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
b1,
b2,
b3,
b4,
b5,
b6,
b7,
b8;
ROM IP 코어
Block Memory Generator > Basic > Single Port ROM
Port A Oprtions > 필요한 데이터 width, depth 설정 > Enable Port Type : Always Enalbed
Other Oprtions > Memory Initialization: Load init file: 에서 .coe
파일을 등록해준다.
ROM 테스트 프로그램
`timescale 1ns / 1ps
module rom_test(
input sys_clk_p, //system clock 200Mhz postive pin
input sys_clk_n, //system clock 200Mhz negetive pin
input rst_n //리셋, 액티브 로우
);
wire [7:0] rom_data; //ROM 읽기 데이터
reg [4:0] rom_addr; //ROM 입력 주소
wire sys_clk ;
IBUFDS #(
.DIFF_TERM("FALSE"), // Differential Termination
.IBUF_LOW_PWR("TRUE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("DEFAULT") // Specify the input I/O standard
) IBUFDS_inst (
.O(sys_clk), // Buffer output
.I(sys_clk_p), // Diff_p buffer input (connect directly to top-level port)
.IB(sys_clk_n) // Diff_n buffer input (connect directly to top-level port)
);
// 데이터를 읽을 ROM 주소 생성
always @ (posedge sys_clk or negedge rst_n)
begin
if(!rst_n)
rom_addr <= 10'd0;
else
rom_addr <= rom_addr+1'b1;
end
// ROM 인스턴스화
rom_ip rom_ip_inst
(
.clka (sys_clk ), //inoput clka
.addra (rom_addr ), //input [4:0] addra
.douta (rom_data ) //output [7:0] douta
);
// 로직 분석기 인스턴스화
ila_0 ila_m0
(
.clk (sys_clk),
.probe0 (rom_addr),
.probe1 (rom_data)
);
endmodule
AX7010 용 핀 바인딩
############## clock and reset define##################
create_clock -period 20 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports {sys_clk}]
set_property PACKAGE_PIN U18 [get_ports {sys_clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {rst_n}]
set_property PACKAGE_PIN N15 [get_ports {rst_n}]
시뮬레이션 결과
FPGA 온칩 FIFO 읽기 및 쓰기
FIFO는 FPGA 애플리케이션에서 매우 중요한 모듈이며, 데이터 캐싱, 크로스 클럭 도메인 데이터 처리 등에 널리 사용된다. FIFO를 잘 배우는 것이 FPGA의 핵심이며, FIFO를 유연하게 사용하는 것은 FPGA 엔지니어에게 필수적인 기술이다.
Xilinx는 Vivado에 FIFO IP 코어를 제공한다. IP 코어를 통해 FIFO를 인스턴스화하고 FIFO의 읽기 및 쓰기 타이밍에 따라 FIFO에 저장된 데이터를 쓰고 읽으면 된다.
실제로 FIFO는 RAM을 기반으로 많은 기능을 추가한다. FIFO의 일반적인 구조는 주로 읽기 및 쓰기의 두 부분으로 나뉜다. 다른 하나는 상태 신호, 빈 신호 및 전체 신호이다. RAM과 관련된 데이터 상태 신호의 가장 큰 차이점은 FIFO에는 주소라인이 없으며 임의의 주소에서 데이터를 읽을 수 없다는 것. 즉, FIFO는 특정 주소의 데이터를 마음대로 읽을 수 없다. 대신 이런 구조의 장점은 주소 라인을 직접 혹은 자주 제어할 필요가 없다는 것이다.
FIFO에 데이터를 쓴 후 스기 주소가 1씩 증가하며, FIFO에서 데이터를 읽은 후 읽기 주소가 1씩 증가한다. 이때 하나의 데이터를 쓰고 다른 데이터를 읽기 때문에 FIFO 상태는 비어있게 된다.
이번 섹션에서는 읽기 클럭이 75MHz, 쓰기 클럭이 100MHz인 비동기식 FIFO의 제어를 진행한다.
FIFO 포트 정의 및 타이밍
신호명 | 방향 | 설명 |
---|---|---|
rst | in | 재설정 신호 |
wr_clk | in | 쓰기 클럭 입력 |
rd_clk | in | 읽기 클럭 입력 |
din | in | 데이터 쓰기 |
wr_en | in | 쓰기 가능 |
rd_en | in | 읽기 가능 |
dout | out | 데이터 읽기 |
full | out | FIFO Full |
empty | out | FIFO Empty |
rd_data_count | out | 읽을 수 있는 데이터의 양 |
wr_data_count | out | 기록된 데이터의 양 |
위 표를 보면 FIFO 외부에 FIFO 상태를 표시하는 output이 존재한다. Xilinx FIFO는 비동기 FIFO를 지원하며, 내부적으로 비동기 클럭 도메인간 동기화 로직을 포함한다.
FIFO 데이터 쓰기 및 읽기는 모두 클럭의 상승 에지에서 동작한다.
표준 FIFO 쓰기 타이밍
표준 FIFO 읽기 타이밍
FWFT FIFO 읽기 타이밍
테스트 프로그램
`timescale 1ns / 1ps
module fifo_test
(
input sys_clk_p, //system clock 200Mhz postive pin
input sys_clk_n, //system clock 200Mhz negetive pin
input rst_n
);
reg [15:0] w_data;
wire wr_en;
wire rd_en;
wire [15:0] r_data;
wire full;
wire empty;
wire [8:0] rd_data_count;
wire [8:0] wr_data_count;
wire clk_100M;
wire clk_75M;
wire locked;
wire fifo_rst_n;
wire wr_clk;
wire rd_clk;
reg [7:0] wcnt;
reg [7:0] rcnt;
clk_wiz_0 fifo_pll
(
// Clock out ports
.clk_out1(clk_100M), // output clk_out1
.clk_out2(clk_75M), // output clk_out2
// Status and control signals
.reset(~rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1_p(sys_clk_p), // input clk_in1
.clk_in1_n(sys_clk_n) // input clk_in1
);
assign fifo_rst_n = locked ;
assign wr_clk = clk_100M ;
assign rd_clk = clk_75M ;
localparam W_IDLE = 1;
localparam W_FIFO = 2;
reg[2:0] write_state;
reg[2:0] next_write_state;
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
write_state <= W_IDLE;
else
write_state <= next_write_state;
end
always@(*)
begin
case(write_state)
W_IDLE:
begin
if(wcnt == 8'd79)
next_write_state <= W_FIFO;
else
next_write_state <= W_IDLE;
end
W_FIFO:
next_write_state <= W_FIFO;
default:
next_write_state <= W_IDLE;
endcase
end
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
wcnt <= 8'd0;
else if (write_state == W_IDLE)
wcnt <= wcnt + 1'b1 ;
else
wcnt <= 8'd0;
end
assign wr_en = (write_state == W_FIFO) ? ~full : 1'b0;
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
w_data <= 16'd1;
else if (wr_en)
w_data <= w_data + 1'b1;
end
localparam R_IDLE = 1 ;
localparam R_FIFO = 2 ;
reg[2:0] read_state;
reg[2:0] next_read_state;
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
read_state <= R_IDLE;
else
read_state <= next_read_state;
end
always@(*)
begin
case(read_state)
R_IDLE:
begin
if (rcnt == 8'd59)
next_read_state <= R_FIFO;
else
next_read_state <= R_IDLE;
end
R_FIFO:
next_read_state <= R_FIFO ;
default:
next_read_state <= R_IDLE;
endcase
end
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
rcnt <= 8'd0;
else if (write_state == W_IDLE)
rcnt <= rcnt + 1'b1 ;
else
rcnt <= 8'd0;
end
assign rd_en = (read_state == R_FIFO) ? ~empty : 1'b0;
//-----------------------------------------------------------
fifo_ip fifo_ip_inst
(
.rst (~fifo_rst_n ), // input rst
.wr_clk (wr_clk ), // input wr_clk
.rd_clk (rd_clk ), // input rd_clk
.din (w_data ), // input [15 : 0] din
.wr_en (wr_en ), // input wr_en
.rd_en (rd_en ), // input rd_en
.dout (r_data ), // output [15 : 0] dout
.full (full ), // output full
.empty (empty ), // output empty
.rd_data_count (rd_data_count ), // output [8 : 0] rd_data_count
.wr_data_count (wr_data_count ) // output [8 : 0] wr_data_count
);
ila_m0 ila_wfifo (
.clk(wr_clk),
.probe0(w_data),
.probe1(wr_en),
.probe2(full),
.probe3(wr_data_count)
);
ila_m0 ila_rfifo (
.clk(rd_clk),
.probe0(r_data),
.probe1(rd_en),
.probe2(empty),
.probe3(rd_data_count)
);
endmodule
이 코드에서는 PLL의 Locked 신호를 FIFO의 재설정으로 사용하고 100MHz 클럭을 쓰기 클럭에 할당하며 75MHz 클럭을 읽기 클럭에 할당한다.
주의할 점은 FIFO 설정이 기본적으로 안전 회로(?)를 사용한다는 것이다. 이 기능은 내부 RAM에 도착하는 입력 신호가 동기식인지 확인한다. 이 경우 비동기식으로 재설정되면 60사이클 동안 기다려야 한다. 75MHz의 60 사이클이므로 100MHz 클럭에서는 대략 (100/75)x60 = 60 사이클 정도가 필요하다.
따라서 쓰기 상태 머신에서는 쓰기 FIFO 상태에 들어갈 때까지 80 사이클을 기다린다.
always@(*)
begin
case(write_state)
W_IDLE:
begin
if(wcnt == 8'd79)
next_write_state <= W_FIFO;
else
next_write_state <= W_IDLE;
end
W_FIFO:
next_write_state <= W_FIFO;
default:
next_write_state <= W_IDLE;
endcase
end
always@(*)
begin
case(read_state) // 읽기 상태 머신에서 읽기 상태가 될 때까지 60사이클을 기다린다.
R_IDLE:
begin
if (rcnt == 8'd59)
next_read_state <= R_FIFO;
else
next_read_state <= R_IDLE;
end
R_FIFO:
next_read_state <= R_FIFO ;
default:
next_read_state <= R_IDLE;
endcase
end
FIFO가 가득 차지 않으면 FIFO에 계속 데이터를 쓴다. FIFO가 비어 있지 않으면 FIFO에서 데이터를 계속 읽는다.
assign wr_en = (write_state == W_FIFO) ? ~full : 1'b0;
assign rd_en = (read_state == R_FIFO) ? ~empty : 1'b0;