엔지니어링 17G 네트워크 모듈 예비 테스트(PS EMIO 기준)
Smart ZYNQ보드(XC7Z020CLG484-1)
ZYNQ 네트워크 기능은 4가지 방식으로 구현 가능
- 하나의 PS 측 MIO 기능으로 이더넷 포트를 직접 확장
- 두 번째 PS 끝은 EMIO를 통해 이더넷 포트를 PL 포트에 매핑합니다.
- 3개의 PL 포트의 하드웨어 이더넷 기능은 AXI를 통해 PS 측에서 호출됩니다.
- 4개의 PL 터미널은 로직을 사용하여 이더넷 기능을 실행합니다(PS 터미널 참여가 필요하지 않음)
여기서 네트워크 포트가 PL 측에 연결되어 있기 때문에 이론적으로 2, 3, 4를 구현할 수 있습니다. 두 번째와 세 번째 방법은 실제로 세 번째 방법이 더 많은 논리적 리소스를 차지한다는 점을 제외하고는 유사한 효과가 있습니다. FPGA는 네트워크를 직접 제어하므로 테스트를 용이하게 하기 위해 먼저 방법 2 EMIO를 사용하여 테스트합니다.
첫번째로 VIVADO상에서 프로젝트를 만들어줍니다.
BLOCK 디자인을 생성하고 ZYNQ7 PROCESSING SYSTEM 모듈을 추가하면 아래 그림과 같이 소프트웨어가 자동으로 zynq 블록을 생성합니다.
더블클릭
DDR Configuration→DDR Controller Configuration→DDR3을 찾아 Memory Part 드롭다운 메뉴에서 보드의 DDR에 따라 해당 DDR3을 선택합니다. 이 실험에 사용된 모델은 MT41K256M16RE-125입니다. , 데이터 비트 너비는 16비트입니다. 마지막으로 아래 그림과 같이 "확인"을 클릭합니다.
다른 모델을 사용하면 시리얼통신이 안됩니다.
프로젝트에서는 일시적으로 AXI 기능을 사용하지 않기 때문에 먼저 AXI 기능을 비활성화할 수 있습니다.
여기서 ENET0을 EMIO로 유도하고 MDIO 기능을 선택합니다.
GMII to RGMII 모듈은 네트워크 장치에서 사용되는 인터페이스를 변환하는 장치입니다. GMII는 Gigabit Media Independent Interface의 약어이고, RGMII는 Reduced Gigabit Media Independent Interface의 약어입니다. 이 모듈은 네트워크 장치에서 GMII 인터페이스를 사용하는 경우를 RGMII 인터페이스로 변환하여 다른 장치와의 통신을 가능하게 합니다. RGMII는 GMII에 비해 전력 소모를 줄이고 인터페이스를 단순화하여 더 빠르고 효율적인 통신을 가능하게 합니다. 이러한 모듈은 네트워크 장치의 호환성을 유지하면서 다양한 통신 요구 사항에 대응할 수 있도록 도와줍니다.
설정 변경
설정 페이지를 not(인버터)로 변경하고 비트 폭을 1로 변경합니다.
모듈 연결합니다.
마우스 오른쪽 버튼을 클릭하고 외부 만들기를 선택하여 RGMII 및 MDIO_PHY 기능 핀을 가져옵니다.
온보드 네트워크 부분의 25M 클럭은 수정 발진기만으로 제어되므로 여기에 추가로 25M 클럭을 도입할 필요가 없습니다.
여기서는 Z7 모듈을 수정해야 합니다. gmii에서 rgmii 모듈까지의 clkin 요구 사항이 매뉴얼에서 200MHz이기 때문에 여기에서 ZYNQ 코어의 출력 클럭 주파수를 수정해야 합니다.
ZYNQ 코어를 다시 두 번 클릭하고 FCLK_CLK0의 주파수를 200으로 변경합니다.
또한 Vitis의 네트워크 기능 루틴은 디버깅을 위해 UART 기능이 필요하므로 ZYNQ 코어에 추가 직렬 포트 기능이 있습니다 (EMIO 방법 선택).
설정 완료 후 Run Block Automation
EMIO의 UART를 추가했으므로 여기에서 UART 핀을 꺼내고 마우스 오른쪽 버튼을 클릭하여 ZYNQ 모듈의 UART_0을 선택한 다음 외부 만들기를 선택해야 합니다.
새로고침과 F6(Validate Design)을 해줍니다.
XDC 파일 추가 해줍니다.
set_property PACKAGE_PIN G21 [get_ports MDIO_PHY_0_mdc]
set_property PACKAGE_PIN H22 [get_ports MDIO_PHY_0_mdio_io]
set_property PACKAGE_PIN A22 [get_ports {RGMII_0_rd[0]}]
set_property PACKAGE_PIN A18 [get_ports {RGMII_0_rd[1]}]
set_property PACKAGE_PIN A19 [get_ports {RGMII_0_rd[2]}]
set_property PACKAGE_PIN B20 [get_ports {RGMII_0_rd[3]}]
set_property PACKAGE_PIN A21 [get_ports RGMII_0_rx_ctl]
set_property PACKAGE_PIN B19 [get_ports RGMII_0_rxc]
set_property PACKAGE_PIN E21 [get_ports {RGMII_0_td[0]}]
set_property PACKAGE_PIN F21 [get_ports {RGMII_0_td[1]}]
set_property PACKAGE_PIN F22 [get_ports {RGMII_0_td[2]}]
set_property PACKAGE_PIN G20 [get_ports {RGMII_0_td[3]}]
set_property PACKAGE_PIN G22 [get_ports RGMII_0_tx_ctl]
set_property PACKAGE_PIN D21 [get_ports RGMII_0_txc]
set_property PACKAGE_PIN M17 [get_ports UART_0_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports MDIO_PHY_0_mdc]
set_property IOSTANDARD LVCMOS33 [get_ports MDIO_PHY_0_mdio_io]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_rd[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_rd[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_rd[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_rd[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports RGMII_0_rx_ctl]
set_property IOSTANDARD LVCMOS33 [get_ports RGMII_0_rxc]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_td[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_td[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_td[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {RGMII_0_td[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports RGMII_0_tx_ctl]
set_property IOSTANDARD LVCMOS33 [get_ports RGMII_0_txc]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_txd]
set_property SLEW FAST [get_ports {RGMII_0_td[0]}]
set_property SLEW FAST [get_ports {RGMII_0_td[1]}]
set_property SLEW FAST [get_ports {RGMII_0_td[2]}]
set_property SLEW FAST [get_ports {RGMII_0_td[3]}]
set_property SLEW FAST [get_ports RGMII_0_tx_ctl]
set_property SLEW FAST [get_ports RGMII_0_txc]
create_clock -period 8.000 -name RGMII_0_rxc -waveform {0.000 4.000} [get_ports RGMII_0_rxc]
set_clock_groups -logically_exclusive -group [get_clocks -include_generated_clocks {gmii_clk_25m_out gmii_clk_2_5m_out}] -group [get_clocks -include_generated_clocks gmii_clk_125m_out]
set_property PACKAGE_PIN L17 [get_ports UART_0_0_txd]
비트스트림 생성 및 합성
Export 비트스트림 파일
Vitis 플랫폼 생성
Vitis 예제로는 lwip211 예제를 쓸거기때문에 BSP 세팅에서 체크를 해줍니다.
LWIP는 Lightweight IP의 줄임말로, 임베디드 시스템 및 네트워크 기기에서 사용되는 경량 TCP/IP 스택입니다. LWIP는 작고 메모리 효율적인 디자인으로 유명하며, 다양한 플랫폼과 임베디드 시스템에서 네트워크 기능을 구현하는 데 널리 사용됩니다. 이 스택은 TCP, UDP, IP, ICMP 등의 프로토콜을 구현하며, 다양한 네트워크 인터페이스와 장치에 대한 드라이버를 지원합니다. LWIP는 소규모 임베디드 시스템에서도 사용할 수 있도록 설계되었으며, 메모리 요구 사항을 최소화하여 자원 한정 환경에서도 효율적으로 동작할 수 있습니다.
체크 해준 후 Build Project를 해줍니다.
빌드 후 Application Project를 만들어줍니다.
플랫폼 프로젝트를 만들었던거와 동일한 플랫폼 선택
템플릿 설정에서 lwIP Echo Server 를 선택해줍니다.
만들어진 app project를 빌드해줍니다
Run 하기 전에 설정을 들어가서 확인해줍니다.
다운로드하기 전에 RUN AS를 설정하고 전체 시스템 재설정 및 프로그램 FPGA를 확인하는 것이 가장 좋습니다. 그렇지 않으면 네트워크에 정상적으로 액세스하지 못할 수 있습니다.
하드웨어 보드 Ethernet 케이블을 연결해줍니다.
라우터에 연결해줍니다.
zynq 설정에 Baud Rate를 확인해줍니다. 보통 115200으로 설정되어있습니다.
저는 ComPortMaster로 시리얼 연결을 해줬습니다.
Run을 해줍니다.
시리얼 포트를 통해 네트워크가 시작된 것으로 관찰되며, 속도는 1000M, 네트워크 IP는 192.168.1.10입니다.
( 보드와 PC가 IP를 할당받으려면 보드가 라우터에 연결되어야 합니다.)
여기서 나타난 속도 1000M는
vitis 상 플랫폼 bsp에서 설정할 수 있으며 Auto로 설정 결과 1000Mbps로 설정되었습니다.
컴퓨터의 네트워크 설정을 해줍니다
다음 IP 주소 사용: 글머리 기호를 클릭하고 IP 주소 "192.168.1.XX"를 입력합니다. 여기서 XX는 10이 아닌 2에서 255 사이의 값입니다. 이 IP는 네트워크에 이미 있는 다른 IP와 동일하지 않아야 합니다. 서브넷 마스크 필드 내부를 클릭하여 255.255.255.0 마스크가 자동으로 채워지도록 합니다. 확인을 클릭하면 고정 IP 주소가 생깁니다.
OK(확인)를 클릭하고 이더넷 콘솔을 닫습니다.
ipconfig 로 핑을 날려봅니다.
동일한 라우터 네트워크 세그먼트에 있는 WINDOWS 컴퓨터를 사용하여 이 IP를 ping합니다. ping이 성공하면 네트워크 연결이 성공한 것입니다.
네트워크 도우미를 통해 연결한 후 전송되는 모든 데이터는 네트워크를 통해 수신됩니다
(설정된 포트 번호는 7이고 IP 주소는 시리얼에서 읽은 보드 IP 주소에 따라 설정됩니다).
이제 템플릿에 있는 TCP Perf 중 Server에 대해서 알아봅니다.
lwIP TCP Perf Server로 템플릿을 선택하게 되면 보드가 Server가 되고 PC가 Client가 됩니다.
기존에 있는 코드로 실행을 하게 되면 메시지는 따로 보낼 수는 없고 데이터가 5초마다 전송되는 것을 확인할 수 있습니다.
UART로 메시지를 받는 것을 해보고 특정 메시지를 받았을 때 바로 메시지를 보내주도록
tcp_perf_server.c 에 있는 코드를 수정해줍니다.
/** Connection handle for a TCP Server session */
#include "tcp_perf_server.h"
#include "lwip/tcp.h" //추가
#include <stdio.h>
#include <string.h>
extern struct netif server_netif;
static struct tcp_pcb *c_pcb;
static struct perf_stats server;
void print_app_header(void)
{
xil_printf("TCP server listening on port %d\r\n",
TCP_CONN_PORT);
#if LWIP_IPV6==1
xil_printf("On Host: Run $iperf -V -c %s%%<interface> -i %d -t 300 -w 2M\r\n",
inet6_ntoa(server_netif.ip6_addr[0]),
INTERIM_REPORT_INTERVAL);
#else
xil_printf("On Host: Run $iperf -c %s -i %d -t 300 -w 2M\r\n",
inet_ntoa(server_netif.ip_addr),
INTERIM_REPORT_INTERVAL);
#endif /* LWIP_IPV6 */
}
static void print_tcp_conn_stats(void)
{
#if LWIP_IPV6==1
xil_printf("[%3d] local %s port %d connected with ",
server.client_id, inet6_ntoa(c_pcb->local_ip),
c_pcb->local_port);
xil_printf("%s port %d\r\n",inet6_ntoa(c_pcb->remote_ip),
c_pcb->remote_port);
#else
xil_printf("[%3d] local %s port %d connected with ",
server.client_id, inet_ntoa(c_pcb->local_ip),
c_pcb->local_port);
xil_printf("%s port %d\r\n",inet_ntoa(c_pcb->remote_ip),
c_pcb->remote_port);
#endif /* LWIP_IPV6 */
xil_printf("[ ID] Interval\t\tTransfer Bandwidth\n\r");
}
static void stats_buffer(char* outString,
double data, enum measure_t type)
{
int conv = KCONV_UNIT;
const char *format;
double unit = 1024.0;
if (type == SPEED)
unit = 1000.0;
while (data >= unit && conv <= KCONV_GIGA) {
data /= unit;
conv++;
}
/* Fit data in 4 places */
if (data < 9.995) { /* 9.995 rounded to 10.0 */
format = "%4.2f %c"; /* #.## */
} else if (data < 99.95) { /* 99.95 rounded to 100 */
format = "%4.1f %c"; /* ##.# */
} else {
format = "%4.0f %c"; /* #### */
}
sprintf(outString, format, data, kLabel[conv]);
}
/** The report function of a TCP server session */
static void tcp_conn_report(u64_t diff,
enum report_type report_type)
{
u64_t total_len;
double duration, bandwidth = 0;
char data[16], perf[16], time[64];
if (report_type == INTER_REPORT) {
total_len = server.i_report.total_bytes;
} else {
server.i_report.last_report_time = 0;
total_len = server.total_bytes;
}
/* Converting duration from milliseconds to secs,
* and bandwidth to bits/sec .
*/
duration = diff / 1000.0; /* secs */
if (duration)
bandwidth = (total_len / duration) * 8.0;
stats_buffer(data, total_len, BYTES);
stats_buffer(perf, bandwidth, SPEED);
/* On 32-bit platforms, xil_printf is not able to print
* u64_t values, so converting these values in strings and
* displaying results
*/
sprintf(time, "%4.1f-%4.1f sec",
(double)server.i_report.last_report_time,
(double)(server.i_report.last_report_time + duration));
xil_printf("[%3d] %s %sBytes %sbits/sec\n\r", server.client_id,
time, data, perf);
if (report_type == INTER_REPORT)
server.i_report.last_report_time += duration;
}
/** Close a tcp session */
static void tcp_server_close(struct tcp_pcb *pcb)
{
err_t err;
if (pcb != NULL) {
tcp_recv(pcb, NULL);
tcp_err(pcb, NULL);
err = tcp_close(pcb);
if (err != ERR_OK) {
/* Free memory with abort */
tcp_abort(pcb);
}
}
}
/** Error callback, tcp session aborted */
static void tcp_server_err(void *arg, err_t err)
{
LWIP_UNUSED_ARG(err);
u64_t now = get_time_ms();
u64_t diff_ms = now - server.start_time;
tcp_server_close(c_pcb);
c_pcb = NULL;
tcp_conn_report(diff_ms, TCP_ABORTED_REMOTE);
xil_printf("TCP connection aborted\n\r");
}
/** Send a message to the client */
static void send_message_to_client(struct tcp_pcb *pcb, const char *message)
{
err_t err;
u16_t len = strlen(message);
err = tcp_write(pcb, message, len, TCP_WRITE_FLAG_COPY);
if (err == ERR_OK) {
tcp_output(pcb);
} else {
xil_printf("Error sending message to client: %d\n", err);
}
}
/** Receive data on a tcp session */
static err_t tcp_recv_perf_traffic(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
if (p == NULL) {
u64_t now = get_time_ms();
u64_t diff_ms = now - server.start_time;
tcp_server_close(tpcb);
tcp_conn_report(diff_ms, TCP_DONE_SERVER);
xil_printf("TCP test passed Successfully\n\r");
return ERR_OK;
}
/* Record total bytes for final report */
server.total_bytes += p->tot_len;
if (server.i_report.report_interval_time) {
u64_t now = get_time_ms();
/* Record total bytes for interim report */
server.i_report.total_bytes += p->tot_len;
if (server.i_report.start_time) {
u64_t diff_ms = now - server.i_report.start_time;
if (diff_ms >= server.i_report.report_interval_time) {
tcp_conn_report(diff_ms, INTER_REPORT);
/* Reset Interim report counters */
server.i_report.start_time = 0;
server.i_report.total_bytes = 0;
}
} else {
/* Save start time for interim report */
server.i_report.start_time = now;
}
}
{
if (p == NULL) {
// 클라이언트가 연결을 종료하면 실행될 코드
return ERR_OK;
}
// 클라이언트로부터 받은 데이터를 UART를 통해 출력
char* data = (char*)p->payload;
for (int i = 0; i < p->len; i++) {
xil_printf("%c", data[i]);
}
// 받은 데이터가 특정 명령어라면 클라이언트에 메시지 전송
if (strncmp(data, "send", 4) == 0) {
send_message_to_client(tpcb, "This is a message from the server.\n");
}
}
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
return ERR_OK;
}
static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
if ((err != ERR_OK) || (newpcb == NULL)) {
return ERR_VAL;
}
/* Save connected client PCB */
c_pcb = newpcb;
/* Save start time for final report */
server.start_time = get_time_ms();
server.end_time = 0; /* ms */
/* Update connected client ID */
server.client_id++;
server.total_bytes = 0;
/* Initialize Interim report parameters */
server.i_report.report_interval_time =
INTERIM_REPORT_INTERVAL * 1000; /* ms */
server.i_report.last_report_time = 0;
server.i_report.start_time = 0;
server.i_report.total_bytes = 0;
print_tcp_conn_stats();
/* setup callbacks for tcp rx connection */
tcp_arg(c_pcb, NULL);
tcp_recv(c_pcb, tcp_recv_perf_traffic);
tcp_err(c_pcb, tcp_server_err);
/* Send a welcome message to the client */
send_message_to_client(newpcb, "Welcome to the TCP server!\n");
return ERR_OK;
}
void start_application(void)
{
err_t err;
struct tcp_pcb *pcb, *lpcb;
/* Create Server PCB */
pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
if (!pcb) {
xil_printf("TCP server: Error creating PCB. Out of Memory\r\n");
return;
}
err = tcp_bind(pcb, IP_ADDR_ANY, TCP_CONN_PORT);
if (err != ERR_OK) {
xil_printf("TCP server: Unable to bind to port %d: "
"err = %d\r\n" , TCP_CONN_PORT, err);
tcp_close(pcb);
return;
}
/* Set connection queue limit to 1 to serve
* one client at a time
*/
lpcb = tcp_listen_with_backlog(pcb, 1);
if (!lpcb) {
xil_printf("TCP server: Out of memory while tcp_listen\r\n");
tcp_close(pcb);
return;
}
/* we do not need any arguments to callback functions */
tcp_arg(lpcb, NULL);
/* specify callback to use for incoming connections */
tcp_accept(lpcb, tcp_server_accept);
return;
}
tcp_recv_perf_traffic 안에 클라이언트로부터 받은 데이터를 uart를 통해 출력하고
출력받은 데이터 중 send 라는 메시지가 온다면 This is a message from the server. 라는 메시지를 바로 보내주도록 하였습니다.
보드가 server가 되어 메시지를 보내주는건 잘 되었으나 보드가 client가 되면 uart에서 메시지를 보내는 것이 잘 안됩니다.
계속 진행하고 있으나 벽에 막혔습니다.
명령프롬프트cmd 를 통해서 ping을 보내면 연결이 되어 0123456789를 제가 설정한 타임에 맞춰서 데이터 전송이 되는거같은데 설정한 타임이 끝나면 바로 연결이 끊어지고 uart로 메시지는 보낼 수 없습니다.
이런식으로 정해놓은 10초의 시간에 1초에 한번씩 0123456789를 5*1024 만큼 보내고 10초뒤에는 연결이 끊어지고 연결할 수가 없습니다.
통신은 되는거같으나 뭔가 문제가 있는 거 같습니다.
'VERILOG' 카테고리의 다른 글
Setup time, Hold Time, Violation, Slack (0) | 2024.06.28 |
---|---|
Serial통신, Uart, RS232, RS422, RS485, I2C, SPI, TCP, UDP 통신 방식에 대한 정리 (0) | 2024.04.15 |
Rotary Encoder(RSI 503) 및 MAX33076E(수신기) VERILOG 동작 (0) | 2024.04.05 |
DMA(Direct Memory Access) Loop test (1) | 2024.03.06 |
Hook script file 에러 시 해결 방법 (0) | 2024.02.27 |