From 2c83d8dc2ecb8120ae26066e3d1a47e2a923687f Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Jan 2025 12:18:18 +0000 Subject: [PATCH 01/18] Initial support for 8b tx in API --- lib_ethernet/api/ethernet.h | 24 ++++++- lib_ethernet/src/rmii_ethernet_rt_mac.xc | 9 ++- lib_ethernet/src/rmii_master.h | 10 +-- lib_ethernet/src/rmii_master.xc | 83 +++++++++++++++++------- 4 files changed, 92 insertions(+), 34 deletions(-) diff --git a/lib_ethernet/api/ethernet.h b/lib_ethernet/api/ethernet.h index 44a5e418..d73b734a 100644 --- a/lib_ethernet/api/ethernet.h +++ b/lib_ethernet/api/ethernet.h @@ -618,11 +618,32 @@ typedef enum rmii_data_4b_pin_assignment_t{ USE_UPPER_2B = 1 /**< Use bit 2 and bit 3 of the four bit port */ } rmii_data_4b_pin_assignment_t; +/** ENUM to determine which two bits of an 8 bit port are to be used as data lines + * in the case that an eight bit port is specified for RMII Tx. The other six bits of the eight bit + * port cannot be used. The unused pins are always driven low. Use of an eight bit port for RMII Rx is not supported. */ +typedef struct rmii_data_8b_pin_assignment_t{ + unsigned short bit_pos_0; /**< Which bit of the port data 0 should be on: 0..7 */ + unsigned short bit_pos_1; /**< Which bit of the port data 0 should be on: 0..7 */ +} rmii_data_8b_pin_assignment_t; + +/** Union representing which pins of a 4b or 8b port to be used. */ +typedef union rmii_data_pin_assignment_t{ + rmii_data_4b_pin_assignment_t pins_4b; + rmii_data_8b_pin_assignment_t pins_8b; +}rmii_data_pin_assignment_t; + +/** Structure representing a four bit port used for RMII data transmission or reception */ +typedef struct rmii_data_8b_t +{ + port data; /**< Four bit data port */ + rmii_data_pin_assignment_t pins_used; /**< Which bits of the port data should be on.*/ +} rmii_data_8b_t; + /** Structure representing a four bit port used for RMII data transmission or reception */ typedef struct rmii_data_4b_t { port data; /**< Four bit data port */ - rmii_data_4b_pin_assignment_t pins_used;/**< Which two bits of the data port to use. + rmii_data_pin_assignment_t pins_used; /**< Which two bits of the data port to use. Unused Rx pins are ignored and unused Tx pins are driven low. */ } rmii_data_4b_t; @@ -639,6 +660,7 @@ typedef struct rmii_data_1b_t typedef union rmii_data_port_t { rmii_data_4b_t rmii_data_4b; /**< Four bit data port option */ + rmii_data_8b_t rmii_data_8b; /**< Four bit data port option. NOTE supported on Tx only */ rmii_data_1b_t rmii_data_1b; /**< One bit data port option */ } rmii_data_port_t; diff --git a/lib_ethernet/src/rmii_ethernet_rt_mac.xc b/lib_ethernet/src/rmii_ethernet_rt_mac.xc index edc3c2f0..3efaa589 100644 --- a/lib_ethernet/src/rmii_ethernet_rt_mac.xc +++ b/lib_ethernet/src/rmii_ethernet_rt_mac.xc @@ -128,13 +128,16 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati out buffered port:32 * unsafe tx_data_0 = NULL; out buffered port:32 * unsafe tx_data_1 = NULL; + // Extract port info unsigned tx_port_width = ((unsigned)(p_txd->rmii_data_1b.data_0) >> 16) & 0xff; - rmii_data_4b_pin_assignment_t tx_port_4b_pins = (rmii_data_4b_pin_assignment_t)(p_txd->rmii_data_1b.data_1); + unsigned tx_pins_used = (unsigned)(p_txd->rmii_data_1b.data_1); + rmii_data_pin_assignment_t tx_port_pins = {tx_pins_used}; switch(tx_port_width){ + case 8: case 4: tx_data_0 = enable_buffered_out_port((unsigned*)(&p_txd->rmii_data_1b.data_0), 32); - rmii_master_init_tx_4b(p_clk, tx_data_0, p_txen, txclk); + rmii_master_init_tx_4b_8b(p_clk, tx_data_0, p_txen, txclk); break; case 1: tx_data_0 = enable_buffered_out_port((unsigned*)&p_txd->rmii_data_1b.data_0, 32); @@ -191,7 +194,7 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati tx_port_width, tx_data_0, tx_data_1, - tx_port_4b_pins, + tx_port_pins, txclk, p_port_state, running_flag_ptr); diff --git a/lib_ethernet/src/rmii_master.h b/lib_ethernet/src/rmii_master.h index 35f6933a..4a36f46b 100644 --- a/lib_ethernet/src/rmii_master.h +++ b/lib_ethernet/src/rmii_master.h @@ -19,10 +19,10 @@ unsafe void rmii_master_init_rx_1b( in port p_clk, in port p_rxdv, clock rxclk); -unsafe void rmii_master_init_tx_4b( in port p_clk, - out buffered port:32 * unsafe tx_data, - out port p_txen, - clock txclk); +unsafe void rmii_master_init_tx_4b_8b( in port p_clk, + out buffered port:32 * unsafe tx_data, + out port p_txen, + clock txclk); unsafe void rmii_master_init_tx_1b( in port p_clk, out buffered port:32 * unsafe tx_data_0, @@ -56,7 +56,7 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned tx_port_width, out buffered port:32 * unsafe p_mii_txd_0, out buffered port:32 * unsafe p_mii_txd_1, - rmii_data_4b_pin_assignment_t tx_port_4b_pins, + rmii_data_pin_assignment_t tx_port_pins, clock txclk, volatile ethernet_port_state_t * unsafe p_port_state, volatile int * unsafe running_flag_ptr); diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index 87ddd8af..491a42dd 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -94,6 +94,7 @@ #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b (12) // In reference timer ticks #endif +#define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) //TODO #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_NO_TAIL_BYTES (96 + 62 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_TAIL_BYTES (96 + 38 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) @@ -183,10 +184,10 @@ static void rmii_master_init_tx_common( in port p_clk, } -unsafe void rmii_master_init_tx_4b( in port p_clk, - out buffered port:32 * unsafe tx_data, - out port p_txen, - clock txclk){ +unsafe void rmii_master_init_tx_4b_8b( in port p_clk, + out buffered port:32 * unsafe tx_data, + out port p_txen, + clock txclk){ *tx_data <: 0; // Ensure lines are low sync(*tx_data); // And wait to empty. This ensures no spurious p_txen on init @@ -776,6 +777,17 @@ static inline void tx_4b_byte(out buffered port:32 p_mii_txd, partout(p_mii_txd, 16, zipped & 0xffffffff); } +unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, + mii_packet_t * unsafe buf, + out buffered port:32 p_mii_txd, + unsigned lookup_8b_tx[256], + hwtimer_t ifg_tmr, + unsigned &ifg_time, + unsigned last_frame_end_time) +{ + return 0; +} + unsafe unsigned rmii_transmit_packet_4b(mii_mempool_t tx_mem, mii_packet_t * unsafe buf, @@ -974,7 +986,18 @@ unsafe unsigned rmii_transmit_packet_1b(mii_mempool_t tx_mem, return time; } - +void init_8b_tx_lookup(unsigned lookup[], int bitpos_0, int bitpos_1){ + for(int i = 0; i < 256; i++){ + lookup[i] |= (i & 0x1) << (bitpos_0 + 0); + lookup[i] |= (i & 0x2) << (bitpos_1 - 1); + lookup[i] |= (i & 0x4) << (bitpos_0 + 6); + lookup[i] |= (i & 0x8) << (bitpos_1 + 5); + lookup[i] |= (i & 0x10) << (bitpos_0 + 12); + lookup[i] |= (i & 0x20) << (bitpos_1 + 11); + lookup[i] |= (i & 0x40) << (bitpos_0 + 18); + lookup[i] |= (i & 0x80) << (bitpos_1 + 17); + } +} unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, mii_mempool_t tx_mem_hp, @@ -984,14 +1007,11 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned tx_port_width, out buffered port:32 * unsafe p_mii_txd_0, out buffered port:32 * unsafe p_mii_txd_1, - rmii_data_4b_pin_assignment_t tx_port_4b_pins, + rmii_data_pin_assignment_t tx_port_pins, clock txclk, volatile ethernet_port_state_t * unsafe p_port_state, volatile int * unsafe running_flag_ptr){ - // Flag for readability and faster comparison - const unsigned use_4b = (tx_port_width == 4); - int credit = 0; int credit_time; // Need one timer to be able to read at any time for the shaper @@ -1002,6 +1022,10 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned eof_time = 0; unsigned enable_shaper = p_port_state->qav_shaper_enabled; + // Lookup table for 8b transmit case + unsigned lookup_8b_tx[256] = {0}; + init_8b_tx_lookup(lookup_8b_tx, 6, 7); + if (!ETHERNET_SUPPORT_TRAFFIC_SHAPER) { enable_shaper = 0; } @@ -1050,25 +1074,34 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned time; - if(use_4b) { - time = rmii_transmit_packet_4b(tx_mem, buf, *p_mii_txd_0, tx_port_4b_pins, ifg_tmr, ifg_time, eof_time); - eof_time = ifg_time; - ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b; - } else { - time = rmii_transmit_packet_1b(tx_mem, buf, *p_mii_txd_0, *p_mii_txd_1, txclk, ifg_tmr, ifg_time, eof_time); - eof_time = ifg_time; - if((buf->length & 0x3)) - { - ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_TAIL_BYTES; - } - else - { - ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_NO_TAIL_BYTES; + switch(tx_port_width){ + case 8: { + time = rmii_transmit_packet_8b(tx_mem, buf, *p_mii_txd_0, lookup_8b_tx, ifg_tmr, ifg_time, eof_time); + eof_time = ifg_time; + ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b; // TODO WORK ME OUT + break; + } + case 4: { + time = rmii_transmit_packet_4b(tx_mem, buf, *p_mii_txd_0, tx_port_pins.pins_4b, ifg_tmr, ifg_time, eof_time); + eof_time = ifg_time; + ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b; + break; + } + case 1: { + time = rmii_transmit_packet_1b(tx_mem, buf, *p_mii_txd_0, *p_mii_txd_1, txclk, ifg_tmr, ifg_time, eof_time); + eof_time = ifg_time; + if((buf->length & 0x3)) + { + ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_TAIL_BYTES; + } + else + { + ifg_time += RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_NO_TAIL_BYTES; + } + break; } } - - const int packet_is_high_priority = (p_ts_queue == null); if (enable_shaper && packet_is_high_priority) { const int preamble_bytes = 8; From 596cef5dc856a05219046393b355c55e64b1f149 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 9 Jan 2025 17:25:43 +0000 Subject: [PATCH 02/18] Successful non-wrapped 8b Tx at 60MHz thread speed --- lib_ethernet/src/rmii_master.xc | 56 +++++++- lib_ethernet/src/rmii_master_tx_pins_8b.S | 165 ++++++++++++++++++++++ 2 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 lib_ethernet/src/rmii_master_tx_pins_8b.S diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index 491a42dd..1d4c78ef 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -777,6 +777,12 @@ static inline void tx_4b_byte(out buffered port:32 p_mii_txd, partout(p_mii_txd, 16, zipped & 0xffffffff); } +void rmii_master_tx_pins_8b_asm(unsigned * unsafe dptr, + int byte_count, + out buffered port:32 p_mii_txd, + unsigned lookup_8b_tx[256], + unsigned poly); + unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, mii_packet_t * unsafe buf, out buffered port:32 p_mii_txd, @@ -785,7 +791,45 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, unsigned &ifg_time, unsigned last_frame_end_time) { - return 0; + unsigned time; + const unsigned poly = 0xEDB88320; + unsigned * unsafe dptr; + + int word_count = buf->length >> 2; + int tail_byte_count = buf->length & 3; + unsigned * unsafe wrap_ptr; + dptr = &buf->data[0]; + wrap_ptr = mii_get_wrap_ptr(tx_mem); + + // Check that we are out of the inter-frame gap + unsigned now; + ifg_tmr :> now; + unsigned wait = check_if_ifg_wait_required(last_frame_end_time, ifg_time, now); + if(wait) + { + ifg_tmr when timerafter(ifg_time) :> ifg_time; + } + + // Check to see if we need to wrap or not + int wrap_required = (dptr + (word_count + (tail_byte_count ? 1 : 0))) >= wrap_ptr; + + // Tx all stuff incl preamble and CRC + if(wrap_required){ + printstrln("wrap_required"); + } else { + printstrln("rmii_master_tx_pins_8b_asm"); + rmii_master_tx_pins_8b_asm(dptr, buf->length, p_mii_txd, lookup_8b_tx, poly); + } + + + if (!MII_TX_TIMESTAMP_END_OF_PACKET && buf->timestamp_id) { + ifg_tmr :> time; + } + + + ifg_tmr :> ifg_time; + + return time; } @@ -829,6 +873,7 @@ unsafe unsigned rmii_transmit_packet_4b(mii_mempool_t tx_mem, dptr++; i++; crc32(crc, ~word, poly); + printhexln(crc); do { unsigned word = *dptr; @@ -838,6 +883,7 @@ unsafe unsigned rmii_transmit_packet_4b(mii_mempool_t tx_mem, } i++; crc32(crc, word, poly); + printhexln(crc); tx_4b_word(p_mii_txd, word, tx_port_4b_pins); } while (i < word_count); @@ -1024,7 +1070,13 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, // Lookup table for 8b transmit case unsigned lookup_8b_tx[256] = {0}; - init_8b_tx_lookup(lookup_8b_tx, 6, 7); + if(tx_port_width == 8){ + rmii_data_8b_pin_assignment_t pins; + memcpy(&pins, &tx_port_pins, sizeof(rmii_data_8b_pin_assignment_t)); + init_8b_tx_lookup(lookup_8b_tx, pins.bit_pos_0, pins.bit_pos_1); + printintln(pins.bit_pos_0); + printintln(pins.bit_pos_1); + } if (!ETHERNET_SUPPORT_TRAFFIC_SHAPER) { enable_shaper = 0; diff --git a/lib_ethernet/src/rmii_master_tx_pins_8b.S b/lib_ethernet/src/rmii_master_tx_pins_8b.S new file mode 100644 index 00000000..50e52e0b --- /dev/null +++ b/lib_ethernet/src/rmii_master_tx_pins_8b.S @@ -0,0 +1,165 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + + .section .cp.rodata, "ac", @progbits + .align 4 +.cc_top tx8consts.data +preamble_first: + .word 0x55555555 +preamble_second: + .word 0xD5555555 +.cc_bottom tx8consts.data + +#include + +#define STACK_WORDS 6 + +//Call: void rmii_master_tx_pins_8b_asm(unsigned * unsafe dptr, +// int byte_count, +// out buffered port:32 p_mii_txd, +// unsigned lookup_8b_tx[256], +// unsigned poly); + +#define DPTR r0 +#define NUM_BYTES r1 +#define PORT_RES_ID r2 +#define LOOKUP r3 +#define POLY_PASSED_IN sp[STACK_WORDS+1] +#define CRC r4 +#define POLY r5 +#define TMP1 r6 +#define TMP2 r7 +#define TMP3 r11 +#define COUNTER r8 +#define TMP4 r9 + +.cc_top rmii_master_tx_pins_8b_asm.func, rmii_master_tx_pins_8b_asm + + +.globl rmii_master_tx_pins_8b_asm.nstackwords +.globl rmii_master_tx_pins_8b_asm.maxthreads +.globl rmii_master_tx_pins_8b_asm.maxtimers +.globl rmii_master_tx_pins_8b_asm.maxchanends +.globl rmii_master_tx_pins_8b_asm.maxsync +.type rmii_master_tx_pins_8b_asm, @function +.linkset rmii_master_tx_pins_8b_asm.locnoside, 0 +.linkset rmii_master_tx_pins_8b_asm.nstackwords, STACK_WORDS +.linkset rmii_master_tx_pins_8b_asm.maxchanends, 0 +.linkset rmii_master_tx_pins_8b_asm.maxtimers, 0 +.linkset rmii_master_tx_pins_8b_asm.maxthreads, 0 +.linkset rmii_master_tx_pins_8b_asm.maxsync, 0 + +.globl rmii_master_tx_pins_8b_asm + + .align 8 + .issue_mode dual +rmii_master_tx_pins_8b_asm: + DUALENTSP_lu6 STACK_WORDS + + std r4, r5, sp[0] // Save r4 and r5 + std r6, r7, sp[1] // Save r6 and r7 + std r8, r9, sp[2] // Save r8 and r9 + + ldc CRC, 0 + ldw POLY, sp[STACK_WORDS+1] + + +tx_8_first_preamble: + // First preamble. No dual issue needed here + mkmsk TMP1, 8 + ldw TMP2, cp[preamble_first] + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 // Must get to next OUT 6 slots later + + // Do second preamble +tx_8_second_preamble: + ldw TMP2, cp[preamble_second] + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr TMP2, TMP2, 8 + and TMP3, TMP2, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 // Must get to loop OUT 6 slots later + +#define BYTE_TO_TX TMP1 +#define EXPANDED_WORD TMP3 +#define BREAK_LOOP TMP2 + + // Special case because we need to NOT the data value for the CRC +tx_8_first_word: + {ldw TMP2, DPTR[0]; mkmsk TMP4, 8} + and BYTE_TO_TX, TMP2, TMP4 + ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX] + {out res[PORT_RES_ID], EXPANDED_WORD; shr TMP2, TMP2, 8} + and BYTE_TO_TX, TMP2, TMP4 + ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX] + {out res[PORT_RES_ID], EXPANDED_WORD; shr TMP2, TMP2, 8} + and BYTE_TO_TX, TMP2, TMP4 + ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX] + {out res[PORT_RES_ID], EXPANDED_WORD; shr TMP2, TMP2, 8} + {and BYTE_TO_TX, TMP2, TMP4; ldw TMP2, DPTR[0]} // Now reload the data word + {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; not TMP2, TMP2} // Invert it + crc32 CRC, TMP2, POLY // And CRC it + out res[PORT_RES_ID], EXPANDED_WORD + + // Do main body +main_tx8_loop_init: + ldc COUNTER, 4 // We have already sent a word + {ld8u BYTE_TO_TX, DPTR[COUNTER]; add COUNTER, COUNTER, 1} // Load initial byte to tx + Inital increment + +main_tx8_loop: + {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; eq BREAK_LOOP, COUNTER, NUM_BYTES} + crc8 CRC, BYTE_TO_TX, BYTE_TO_TX, POLY // This trashes BYTE_TO_TX with BYTE_TO_TX >> 8 but we don't care now + {out res[PORT_RES_ID], EXPANDED_WORD; ld8u BYTE_TO_TX, DPTR[COUNTER]} + {add COUNTER, COUNTER, 1; bf BREAK_LOOP, main_tx8_loop} + + // CRC. CRC gets shifted away but we don't care now + {mkmsk TMP1, 8; mkmsk TMP2, 32} // Load byte mask and 0xFFFFFFFF + crc32 CRC, TMP2, POLY + and TMP3, CRC, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 // Must get here 6 slots after last loop OUT + shr CRC, CRC, 8 + and TMP3, CRC, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr CRC, CRC, 8 + and TMP3, CRC, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + shr CRC, CRC, 8 + and TMP3, CRC, TMP1 + ldw TMP3, LOOKUP[TMP3] + out res[PORT_RES_ID], TMP3 + + ldd r4, r5, sp[0] + ldd r6, r7, sp[1] + ldd r8, r9, sp[2] + + retsp STACK_WORDS + + + +.cc_bottom rmii_master_tx_pins_8b_asm.func From 6c0af60433bb765f4e23f5236249f1df1b9ec019 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Jan 2025 10:54:10 +0000 Subject: [PATCH 03/18] Refresh API to avoid extra braces on initialiser --- lib_ethernet/api/ethernet.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib_ethernet/api/ethernet.h b/lib_ethernet/api/ethernet.h index d73b734a..d35926e9 100644 --- a/lib_ethernet/api/ethernet.h +++ b/lib_ethernet/api/ethernet.h @@ -626,6 +626,10 @@ typedef struct rmii_data_8b_pin_assignment_t{ unsigned short bit_pos_1; /**< Which bit of the port data 0 should be on: 0..7 */ } rmii_data_8b_pin_assignment_t; +/** Macro to populate which bits of the 8b port are used in the initialiser. Unused bits are driven low. */ +#define RMII_8B_PINS_INITIALISER(pos_0, pos_1) ((unsigned)pos_0 | ((unsigned)pos_1) << 16) + + /** Union representing which pins of a 4b or 8b port to be used. */ typedef union rmii_data_pin_assignment_t{ rmii_data_4b_pin_assignment_t pins_4b; @@ -635,15 +639,15 @@ typedef union rmii_data_pin_assignment_t{ /** Structure representing a four bit port used for RMII data transmission or reception */ typedef struct rmii_data_8b_t { - port data; /**< Four bit data port */ - rmii_data_pin_assignment_t pins_used; /**< Which bits of the port data should be on.*/ + port data; /**< Eight bit data port */ + rmii_data_8b_pin_assignment_t pins_used; /**< Which bits of the port data should be on.*/ } rmii_data_8b_t; /** Structure representing a four bit port used for RMII data transmission or reception */ typedef struct rmii_data_4b_t { port data; /**< Four bit data port */ - rmii_data_pin_assignment_t pins_used; /**< Which two bits of the data port to use. + rmii_data_4b_pin_assignment_t pins_used;/**< Which two bits of the data port to use. Unused Rx pins are ignored and unused Tx pins are driven low. */ } rmii_data_4b_t; From e8c858c6b85909ce02eb6c7e15f3fe1669d57bd3 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Jan 2025 10:55:38 +0000 Subject: [PATCH 04/18] Implement wrap checking for tx8 --- lib_ethernet/src/rmii_master.xc | 58 +++++++++++-------- lib_ethernet/src/rmii_master_tx_pins_8b.S | 70 +++++++++++++++-------- 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index 1d4c78ef..31786877 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -77,6 +77,10 @@ // So the RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b is (96 + 30), and // RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b is (96 + 62) for frames with no tail bytes and (96 + 38) for frames with tail bytes. +// For 8b TXD port, we output 8b of data pins at a time (one byte on the wire). So after the last OUT we have TR and SR full which is two wire bytes, +// we have 16 timer ticks until it is empty. There are 4 slots after the last OUT in the ASM before we return to callee. +// TODO work this out properly. + // Further, there's an adjustment needed due to the fact that // 1. The instruction that reads the timer is in fact the next instruction adter the out of the CRC word. // 2. There's a delay between the timer wait for the next packet and the preamble actually showing up on the wire (TX_EN goes high when the first bit shows up) @@ -94,7 +98,11 @@ #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b (12) // In reference timer ticks #endif -#define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) //TODO +#ifndef RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b + #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b (12) // In reference timer ticks +#endif + +#define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) //TODO Work me out #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_NO_TAIL_BYTES (96 + 62 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_TAIL_BYTES (96 + 38 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) @@ -781,7 +789,9 @@ void rmii_master_tx_pins_8b_asm(unsigned * unsafe dptr, int byte_count, out buffered port:32 p_mii_txd, unsigned lookup_8b_tx[256], - unsigned poly); + unsigned poly, + unsigned * unsafe wrap_start_ptr, + int byte_count_wrapped); unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, mii_packet_t * unsafe buf, @@ -793,13 +803,8 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, { unsigned time; const unsigned poly = 0xEDB88320; - unsigned * unsafe dptr; - - int word_count = buf->length >> 2; - int tail_byte_count = buf->length & 3; - unsigned * unsafe wrap_ptr; - dptr = &buf->data[0]; - wrap_ptr = mii_get_wrap_ptr(tx_mem); + unsigned * unsafe dptr = &buf->data[0]; + unsigned * unsafe wrap_ptr = mii_get_wrap_ptr(tx_mem);; // Check that we are out of the inter-frame gap unsigned now; @@ -811,14 +816,24 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, } // Check to see if we need to wrap or not - int wrap_required = (dptr + (word_count + (tail_byte_count ? 1 : 0))) >= wrap_ptr; + int first_chunk_size = buf->length; + int wrap_size = buf->length - ((int)wrap_ptr - (int)dptr); + wrap_size = wrap_size < 0 ? 0 : wrap_size; - // Tx all stuff incl preamble and CRC - if(wrap_required){ + if(wrap_size > 0){ + first_chunk_size -= wrap_size; + wrap_ptr = (unsigned *)*wrap_ptr; // Dereference wrap pointer to get start of wrap memory printstrln("wrap_required"); - } else { - printstrln("rmii_master_tx_pins_8b_asm"); - rmii_master_tx_pins_8b_asm(dptr, buf->length, p_mii_txd, lookup_8b_tx, poly); + } + + if (!MII_TX_TIMESTAMP_END_OF_PACKET && buf->timestamp_id) { + ifg_tmr :> time; + } + + // Tx all stuff incl preamble and CRC + if(buf->length > 5){ // The ASM always transmits at least 5 bytes. Less than that will break the + // timing on the very tight loops so check in XC before we get there. + rmii_master_tx_pins_8b_asm(dptr, first_chunk_size, p_mii_txd, lookup_8b_tx, poly, wrap_ptr, wrap_size); } @@ -873,7 +888,6 @@ unsafe unsigned rmii_transmit_packet_4b(mii_mempool_t tx_mem, dptr++; i++; crc32(crc, ~word, poly); - printhexln(crc); do { unsigned word = *dptr; @@ -883,7 +897,6 @@ unsafe unsigned rmii_transmit_packet_4b(mii_mempool_t tx_mem, } i++; crc32(crc, word, poly); - printhexln(crc); tx_4b_word(p_mii_txd, word, tx_port_4b_pins); } while (i < word_count); @@ -1032,8 +1045,9 @@ unsafe unsigned rmii_transmit_packet_1b(mii_mempool_t tx_mem, return time; } -void init_8b_tx_lookup(unsigned lookup[], int bitpos_0, int bitpos_1){ +static void init_8b_tx_lookup(unsigned lookup[], int bitpos_0, int bitpos_1){ for(int i = 0; i < 256; i++){ + lookup[i] = 0; // All unused pins driven low lookup[i] |= (i & 0x1) << (bitpos_0 + 0); lookup[i] |= (i & 0x2) << (bitpos_1 - 1); lookup[i] |= (i & 0x4) << (bitpos_0 + 6); @@ -1069,13 +1083,9 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned enable_shaper = p_port_state->qav_shaper_enabled; // Lookup table for 8b transmit case - unsigned lookup_8b_tx[256] = {0}; + unsigned lookup_8b_tx[256]; if(tx_port_width == 8){ - rmii_data_8b_pin_assignment_t pins; - memcpy(&pins, &tx_port_pins, sizeof(rmii_data_8b_pin_assignment_t)); - init_8b_tx_lookup(lookup_8b_tx, pins.bit_pos_0, pins.bit_pos_1); - printintln(pins.bit_pos_0); - printintln(pins.bit_pos_1); + init_8b_tx_lookup(lookup_8b_tx, tx_port_pins.pins_8b.bit_pos_0, tx_port_pins.pins_8b.bit_pos_1); } if (!ETHERNET_SUPPORT_TRAFFIC_SHAPER) { diff --git a/lib_ethernet/src/rmii_master_tx_pins_8b.S b/lib_ethernet/src/rmii_master_tx_pins_8b.S index 50e52e0b..0cb010b8 100644 --- a/lib_ethernet/src/rmii_master_tx_pins_8b.S +++ b/lib_ethernet/src/rmii_master_tx_pins_8b.S @@ -18,20 +18,25 @@ preamble_second: // int byte_count, // out buffered port:32 p_mii_txd, // unsigned lookup_8b_tx[256], -// unsigned poly); - -#define DPTR r0 -#define NUM_BYTES r1 -#define PORT_RES_ID r2 -#define LOOKUP r3 -#define POLY_PASSED_IN sp[STACK_WORDS+1] -#define CRC r4 -#define POLY r5 -#define TMP1 r6 -#define TMP2 r7 -#define TMP3 r11 -#define COUNTER r8 -#define TMP4 r9 +// unsigned poly, +// unsigned * unsafe wrap_start_ptr, +// int byte_count_wrapped); + +#define DPTR r0 +#define NUM_BYTES r1 +#define PORT_RES_ID r2 +#define LOOKUP r3 +#define POLY_PASSED_IN sp[STACK_WORDS+1] +#define WRAP_START_PTR_PASSED_IN sp[STACK_WORDS+2] +#define WRAP_SZ_PASSED_IN sp[STACK_WORDS+3] // 0 or positive +#define CRC r4 +#define POLY r5 +#define TMP1 r6 +#define TMP2 r7 +#define COUNTER r8 +#define TMP4 r9 +#define TMP3 r11 + .cc_top rmii_master_tx_pins_8b_asm.func, rmii_master_tx_pins_8b_asm @@ -65,7 +70,7 @@ rmii_master_tx_pins_8b_asm: tx_8_first_preamble: - // First preamble. No dual issue needed here + // First preamble mkmsk TMP1, 8 ldw TMP2, cp[preamble_first] and TMP3, TMP2, TMP1 @@ -82,7 +87,7 @@ tx_8_first_preamble: shr TMP2, TMP2, 8 and TMP3, TMP2, TMP1 ldw TMP3, LOOKUP[TMP3] - out res[PORT_RES_ID], TMP3 // Must get to next OUT 6 slots later + out res[PORT_RES_ID], TMP3 // Ideally get to next OUT 6 slots later // Do second preamble tx_8_second_preamble: @@ -101,13 +106,13 @@ tx_8_second_preamble: shr TMP2, TMP2, 8 and TMP3, TMP2, TMP1 ldw TMP3, LOOKUP[TMP3] - out res[PORT_RES_ID], TMP3 // Must get to loop OUT 6 slots later + out res[PORT_RES_ID], TMP3 // Ideally get to loop OUT 6 slots later #define BYTE_TO_TX TMP1 #define EXPANDED_WORD TMP3 -#define BREAK_LOOP TMP2 +#define WRAP_SIZE TMP4 - // Special case because we need to NOT the data value for the CRC + // Special case for first word because we need to NOT the data value for the CRC tx_8_first_word: {ldw TMP2, DPTR[0]; mkmsk TMP4, 8} and BYTE_TO_TX, TMP2, TMP4 @@ -119,24 +124,39 @@ tx_8_first_word: and BYTE_TO_TX, TMP2, TMP4 ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX] {out res[PORT_RES_ID], EXPANDED_WORD; shr TMP2, TMP2, 8} - {and BYTE_TO_TX, TMP2, TMP4; ldw TMP2, DPTR[0]} // Now reload the data word + {and BYTE_TO_TX, TMP2, TMP4; ldw TMP2, DPTR[0]} // Now reload the data word since it has been shidted away {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; not TMP2, TMP2} // Invert it crc32 CRC, TMP2, POLY // And CRC it - out res[PORT_RES_ID], EXPANDED_WORD + {out res[PORT_RES_ID], EXPANDED_WORD; ldw WRAP_SIZE, WRAP_SZ_PASSED_IN} // Out final word and load in the wrap size + + +#define BREAK_LOOP TMP2 // Do main body -main_tx8_loop_init: +tx8_main_loop_init: ldc COUNTER, 4 // We have already sent a word {ld8u BYTE_TO_TX, DPTR[COUNTER]; add COUNTER, COUNTER, 1} // Load initial byte to tx + Inital increment -main_tx8_loop: +tx8_main_loop: {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; eq BREAK_LOOP, COUNTER, NUM_BYTES} crc8 CRC, BYTE_TO_TX, BYTE_TO_TX, POLY // This trashes BYTE_TO_TX with BYTE_TO_TX >> 8 but we don't care now {out res[PORT_RES_ID], EXPANDED_WORD; ld8u BYTE_TO_TX, DPTR[COUNTER]} - {add COUNTER, COUNTER, 1; bf BREAK_LOOP, main_tx8_loop} + {add COUNTER, COUNTER, 1; bf BREAK_LOOP, tx8_main_loop} + +tx_8_wrap_loop_check_and_init: + {bf WRAP_SIZE, tx8_crc; ldc COUNTER, 0} + ldw DPTR, WRAP_START_PTR_PASSED_IN + {ld8u BYTE_TO_TX, DPTR[COUNTER]; add COUNTER, COUNTER, 1} + +tx8_wrap_loop: + {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; eq BREAK_LOOP, COUNTER, WRAP_SIZE} + crc8 CRC, BYTE_TO_TX, BYTE_TO_TX, POLY // This trashes BYTE_TO_TX with BYTE_TO_TX >> 8 but we don't care now + {out res[PORT_RES_ID], EXPANDED_WORD; ld8u BYTE_TO_TX, DPTR[COUNTER]} + {add COUNTER, COUNTER, 1; bf BREAK_LOOP, tx8_wrap_loop} +tx8_crc: // CRC. CRC gets shifted away but we don't care now - {mkmsk TMP1, 8; mkmsk TMP2, 32} // Load byte mask and 0xFFFFFFFF + {mkmsk TMP1, 8; mkmsk TMP2, 32} // Load byte mask and 0xFFFFFFFF for final CRC calc crc32 CRC, TMP2, POLY and TMP3, CRC, TMP1 ldw TMP3, LOOKUP[TMP3] From 1d266e4badcba042c1dd6175b35e073ee5dea143 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 10 Jan 2025 11:14:44 +0000 Subject: [PATCH 05/18] Comments and minor DI opt --- lib_ethernet/src/rmii_master_tx_pins_8b.S | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib_ethernet/src/rmii_master_tx_pins_8b.S b/lib_ethernet/src/rmii_master_tx_pins_8b.S index 0cb010b8..dd5fae64 100644 --- a/lib_ethernet/src/rmii_master_tx_pins_8b.S +++ b/lib_ethernet/src/rmii_master_tx_pins_8b.S @@ -28,7 +28,7 @@ preamble_second: #define LOOKUP r3 #define POLY_PASSED_IN sp[STACK_WORDS+1] #define WRAP_START_PTR_PASSED_IN sp[STACK_WORDS+2] -#define WRAP_SZ_PASSED_IN sp[STACK_WORDS+3] // 0 or positive +#define WRAP_SZ_PASSED_IN sp[STACK_WORDS+3] // 0 or positive set by callee #define CRC r4 #define POLY r5 #define TMP1 r6 @@ -65,14 +65,14 @@ rmii_master_tx_pins_8b_asm: std r6, r7, sp[1] // Save r6 and r7 std r8, r9, sp[2] // Save r8 and r9 - ldc CRC, 0 - ldw POLY, sp[STACK_WORDS+1] + // Load poly and init CRC + {ldw POLY, sp[STACK_WORDS+1]; ldc CRC, 0} tx_8_first_preamble: // First preamble + ldw TMP2, cp[preamble_first] // We could in theory DI this but if we try we are too far from the CP so get link error mkmsk TMP1, 8 - ldw TMP2, cp[preamble_first] and TMP3, TMP2, TMP1 ldw TMP3, LOOKUP[TMP3] out res[PORT_RES_ID], TMP3 @@ -124,7 +124,7 @@ tx_8_first_word: and BYTE_TO_TX, TMP2, TMP4 ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX] {out res[PORT_RES_ID], EXPANDED_WORD; shr TMP2, TMP2, 8} - {and BYTE_TO_TX, TMP2, TMP4; ldw TMP2, DPTR[0]} // Now reload the data word since it has been shidted away + {and BYTE_TO_TX, TMP2, TMP4; ldw TMP2, DPTR[0]} // Now reload the data word since it has been shifted away {ldw EXPANDED_WORD, LOOKUP[BYTE_TO_TX]; not TMP2, TMP2} // Invert it crc32 CRC, TMP2, POLY // And CRC it {out res[PORT_RES_ID], EXPANDED_WORD; ldw WRAP_SIZE, WRAP_SZ_PASSED_IN} // Out final word and load in the wrap size @@ -143,6 +143,7 @@ tx8_main_loop: {out res[PORT_RES_ID], EXPANDED_WORD; ld8u BYTE_TO_TX, DPTR[COUNTER]} {add COUNTER, COUNTER, 1; bf BREAK_LOOP, tx8_main_loop} + // We take 7 slots to get to the next OUT - OK since we know TR and SR are full at the last OUT tx_8_wrap_loop_check_and_init: {bf WRAP_SIZE, tx8_crc; ldc COUNTER, 0} ldw DPTR, WRAP_START_PTR_PASSED_IN @@ -160,7 +161,7 @@ tx8_crc: crc32 CRC, TMP2, POLY and TMP3, CRC, TMP1 ldw TMP3, LOOKUP[TMP3] - out res[PORT_RES_ID], TMP3 // Must get here 6 slots after last loop OUT + out res[PORT_RES_ID], TMP3 // Must get here 6 slots after last loop OUT, which we do shr CRC, CRC, 8 and TMP3, CRC, TMP1 ldw TMP3, LOOKUP[TMP3] From d86ce135d35cad6fd31994424c27027f93698aac Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 14:47:34 +0000 Subject: [PATCH 06/18] Merge branch 'develop' Conflicts: lib_ethernet/api/ethernet.h lib_ethernet/src/rmii_ethernet_rt_mac.xc lib_ethernet/src/rmii_master.h lib_ethernet/src/rmii_master.xc --- CHANGELOG.rst | 13 +- Jenkinsfile | 15 +- README.rst | 22 +- doc/rst/images/10_100_1000_mac_tasks.png | Bin 0 -> 56354 bytes doc/rst/images/10_100_mac_tasks.png | Bin 0 -> 25760 bytes doc/rst/images/10_100_rt_mac_tasks.png | Bin 0 -> 44068 bytes doc/rst/images/XS2-RGMII.png | Bin 0 -> 84826 bytes doc/rst/images/mii_tasks.png | Bin 0 -> 9160 bytes doc/rst/lib_ethernet.rst | 243 +++++++++---- .../CMakeLists.txt | 3 +- .../src/icmp.h | 2 +- .../src/icmp.xc | 2 +- .../src/main.xc | 38 +- examples/app_mii_demo/src/main.xc | 2 +- examples/app_rmii_100Mbit_icmp/CMakeLists.txt | 24 ++ examples/app_rmii_100Mbit_icmp/README.rst | 73 ++++ .../doc/exclude_patterns.inc | 6 + .../app_rmii_100Mbit_icmp/doc/rst/AN00120.rst | 287 +++++++++++++++ .../doc/substitutions.inc | 0 examples/app_rmii_100Mbit_icmp/settings.yml | 21 ++ .../app_rmii_100Mbit_icmp/src/config.xscope | 2 + examples/app_rmii_100Mbit_icmp/src/icmp.h | 16 + examples/app_rmii_100Mbit_icmp/src/icmp.xc | 298 ++++++++++++++++ examples/app_rmii_100Mbit_icmp/src/main.xc | 73 ++++ .../src/xk-eth-xu316-dual-100m.xn | 92 +++++ examples/deps.cmake | 3 +- lib_ethernet/api/ethernet.h | 125 +++---- lib_ethernet/api/mii.h | 2 +- lib_ethernet/api/smi.h | 46 ++- lib_ethernet/lib_build_info.cmake | 2 +- lib_ethernet/module_build_info | 2 +- lib_ethernet/src/check_ifg_wait.h | 2 +- lib_ethernet/src/client_state.h | 2 +- lib_ethernet/src/client_state.xc | 2 +- lib_ethernet/src/default_ethernet_conf.h | 2 +- lib_ethernet/src/doxygen.h | 2 +- lib_ethernet/src/ethernet.xc | 2 +- lib_ethernet/src/macaddr_filter.h | 2 +- lib_ethernet/src/macaddr_filter.xc | 2 +- lib_ethernet/src/macaddr_filter_hash.c | 2 +- lib_ethernet/src/macaddr_filter_hash.h | 2 +- lib_ethernet/src/mii.xc | 2 +- lib_ethernet/src/mii_buffering_defines.h | 2 +- lib_ethernet/src/mii_common_lld.h | 2 +- lib_ethernet/src/mii_ethernet_mac.xc | 12 +- lib_ethernet/src/mii_ethernet_rt_mac.xc | 15 +- lib_ethernet/src/mii_impl.h | 2 +- lib_ethernet/src/mii_lite_driver.h | 2 +- lib_ethernet/src/mii_lite_driver.xc | 2 +- lib_ethernet/src/mii_lite_interrupt.S | 2 +- lib_ethernet/src/mii_lite_lld.S | 2 +- lib_ethernet/src/mii_lite_lld.h | 2 +- lib_ethernet/src/mii_master.h | 2 +- lib_ethernet/src/mii_master.xc | 46 +-- lib_ethernet/src/mii_ts_queue.c | 2 +- lib_ethernet/src/mii_ts_queue.h | 2 +- lib_ethernet/src/ntoh.h | 2 +- lib_ethernet/src/rgmii.h | 2 +- lib_ethernet/src/rgmii_10_100_master.h | 2 +- lib_ethernet/src/rgmii_10_100_master.xc | 2 +- lib_ethernet/src/rgmii_buffering.h | 2 +- lib_ethernet/src/rgmii_buffering.xc | 100 +++--- lib_ethernet/src/rgmii_buffering_c_support.c | 2 +- lib_ethernet/src/rgmii_consts.h | 2 +- lib_ethernet/src/rgmii_ethernet_mac.xc | 2 +- lib_ethernet/src/rgmii_rx_lld.S | 2 +- lib_ethernet/src/rgmii_speed_handlers.S | 2 +- lib_ethernet/src/rgmii_tx_lld.S | 2 +- lib_ethernet/src/rmii_ethernet_rt_mac.xc | 54 +-- lib_ethernet/src/rmii_master.h | 15 +- lib_ethernet/src/rmii_master.xc | 93 +++-- lib_ethernet/src/server_state.h | 3 +- lib_ethernet/src/server_state.xc | 5 +- lib_ethernet/src/shaper.h | 131 +++++++ lib_ethernet/src/shaper.xc | 33 ++ lib_ethernet/src/smi.xc | 248 ++++++++----- python/setup.py | 2 +- requirements.txt | 1 + tests/CMakeLists.txt | 2 + .../CMakeLists.txt | 28 ++ .../src/config.xscope | 2 + .../bringup_xk_eth_xu316_dual_100m/src/icmp.h | 16 + .../src/icmp.xc | 298 ++++++++++++++++ .../src/main.xc | 127 +++++++ .../src/xk-eth-xu316-dual-100m.xn | 92 +++++ tests/conftest.py | 2 +- tests/include/control.h | 2 +- tests/include/control.xc | 2 +- tests/include/helpers.h | 2 +- tests/include/helpers.xc | 2 +- tests/include/ports.h | 5 +- tests/include/ports_rmii.h | 25 +- tests/include/random.h | 2 +- tests/include/random.xc | 2 +- tests/include/random_init.c | 2 +- tests/mii_clock.py | 2 +- tests/mii_packet.py | 2 +- tests/mii_phy.py | 2 +- tests/rgmii_phy.py | 2 +- tests/rmii_phy.py | 2 +- tests/smi.py | 335 ++++++++++++++++++ tests/test_4_1_1.py | 2 +- tests/test_4_1_3.py | 2 +- tests/test_4_1_4.py | 2 +- tests/test_4_1_5.py | 2 +- tests/test_4_1_6.py | 2 +- tests/test_4_1_7.py | 2 +- tests/test_4_1_8.py | 2 +- tests/test_4_1_9.py | 2 +- tests/test_4_2_4.py | 2 +- tests/test_4_2_5.py | 2 +- tests/test_appdata.py | 2 +- tests/test_appdata/src/main.xc | 29 +- tests/test_appdata/src/xassert_conf.h | 2 +- tests/test_avb_traffic.py | 2 +- tests/test_avb_traffic/src/main.xc | 29 +- tests/test_check_ifg_wait.py | 2 +- tests/test_check_ifg_wait/src/main.xc | 2 +- tests/test_do_idle_slope_unit.expect | 11 + tests/test_do_idle_slope_unit.py | 22 ++ tests/test_do_idle_slope_unit/CMakeLists.txt | 21 ++ tests/test_do_idle_slope_unit/src/main.xc | 148 ++++++++ tests/test_etype_filter.py | 2 +- tests/test_etype_filter/src/main.xc | 29 +- tests/test_link_status.py | 2 +- tests/test_link_status/src/main.xc | 29 +- tests/test_rmii_restart/src/main.xc | 27 +- tests/test_rmii_timing.py | 2 +- tests/test_rmii_timing/src/main.xc | 29 +- tests/test_rx.py | 2 +- tests/test_rx/src/main.xc | 29 +- tests/test_rx_backpressure.py | 2 +- tests/test_rx_backpressure/src/main.xc | 29 +- tests/test_rx_err.py | 2 +- tests/test_rx_queues.py | 2 +- tests/test_rx_queues/src/main.xc | 31 +- tests/test_shaper.py | 2 +- tests/test_shaper/src/main.xc | 31 +- tests/test_smi.expect | 7 + tests/test_smi.py | 64 ++++ tests/test_smi/CMakeLists.txt | 53 +++ tests/test_smi/src/main.xc | 52 +++ tests/test_smi/test_params.json | 6 + tests/test_speed_change.py | 2 +- tests/test_speed_change/src/main.xc | 2 +- tests/test_time_rx.py | 2 +- tests/test_time_rx/src/main.xc | 2 +- tests/test_time_rx/src/main_mii_rt.h | 29 +- tests/test_time_rx/src/main_mii_standard.h | 2 +- tests/test_time_rx/src/main_rgmii.h | 2 +- tests/test_time_rx_tx.py | 2 +- tests/test_time_rx_tx/src/main.xc | 2 +- tests/test_time_rx_tx/src/main_mii_rt.h | 29 +- tests/test_time_rx_tx/src/main_mii_standard.h | 2 +- tests/test_time_rx_tx/src/main_rgmii.h | 2 +- tests/test_time_tx.py | 2 +- tests/test_time_tx/src/main.xc | 29 +- tests/test_timestamp_tx.py | 2 +- tests/test_timestamp_tx/src/main.xc | 24 +- tests/test_tx.py | 2 +- tests/test_tx/src/main.xc | 29 +- tests/test_vlan_strip.py | 2 +- tests/test_vlan_strip/src/main.xc | 29 +- tests/test_vlan_strip/src/xassert_conf.h | 2 +- 164 files changed, 3394 insertions(+), 719 deletions(-) create mode 100644 doc/rst/images/10_100_1000_mac_tasks.png create mode 100644 doc/rst/images/10_100_mac_tasks.png create mode 100644 doc/rst/images/10_100_rt_mac_tasks.png create mode 100644 doc/rst/images/XS2-RGMII.png create mode 100644 doc/rst/images/mii_tasks.png create mode 100644 examples/app_rmii_100Mbit_icmp/CMakeLists.txt create mode 100644 examples/app_rmii_100Mbit_icmp/README.rst create mode 100644 examples/app_rmii_100Mbit_icmp/doc/exclude_patterns.inc create mode 100644 examples/app_rmii_100Mbit_icmp/doc/rst/AN00120.rst create mode 100644 examples/app_rmii_100Mbit_icmp/doc/substitutions.inc create mode 100644 examples/app_rmii_100Mbit_icmp/settings.yml create mode 100644 examples/app_rmii_100Mbit_icmp/src/config.xscope create mode 100644 examples/app_rmii_100Mbit_icmp/src/icmp.h create mode 100644 examples/app_rmii_100Mbit_icmp/src/icmp.xc create mode 100644 examples/app_rmii_100Mbit_icmp/src/main.xc create mode 100644 examples/app_rmii_100Mbit_icmp/src/xk-eth-xu316-dual-100m.xn create mode 100644 lib_ethernet/src/shaper.h create mode 100644 lib_ethernet/src/shaper.xc create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/src/config.xscope create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/src/main.xc create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/src/xk-eth-xu316-dual-100m.xn create mode 100644 tests/smi.py create mode 100644 tests/test_do_idle_slope_unit.expect create mode 100644 tests/test_do_idle_slope_unit.py create mode 100644 tests/test_do_idle_slope_unit/CMakeLists.txt create mode 100644 tests/test_do_idle_slope_unit/src/main.xc create mode 100644 tests/test_smi.expect create mode 100644 tests/test_smi.py create mode 100644 tests/test_smi/CMakeLists.txt create mode 100644 tests/test_smi/src/main.xc create mode 100644 tests/test_smi/test_params.json diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 364b1938..beb475e6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,15 +4,24 @@ lib_ethernet change log 4.0.0 ----- - * ADDED: Support for running MII sim tests for XS3 architecture * ADDED: RMII Ethernet MAC support for XCORE-AI * ADDED: Extended sim tests for testing RMII applications + * ADDED: Support for running MII sim tests for XS3 architecture * ADDED: Support for XCommon CMake build system * ADDED: Exit command to RMII RT MAC - * RESOLVED: Build warnings even when compile successful + * ADDED: Improved MAC feature documentation + * ADDED: Tests for SMI + * ADDED: Optional credit limit for Qav shaper + * ADDED: Support for running MII sim tests for XS3 architecture + * CHANGED: SMI re-write. Single port version now functional and documented + * CHANGED: Moved example PHY drivers to lib_board_support * REMOVED: Slicekit based examples because hardware is obsolete * REMOVED: Support for waf build system * REMOVED: Support for XS1 devices + * RESOLVED: Build warnings even when compile successful + * RESOLVED: Qav shaper credit overflow causing negative credit on idle + * RESOLVED: Packets might not be transmitted for 21 seconds if no transmit + activity for 21 seconds (IFG timer) * Changes to dependencies: diff --git a/Jenkinsfile b/Jenkinsfile index 666cfd11..640ff1e9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ // This file relates to internal XMOS infrastructure and should be ignored by external users -@Library('xmos_jenkins_shared_library@v0.34.0') _ +@Library('xmos_jenkins_shared_library@v0.36.0') _ getApproval() @@ -20,7 +20,7 @@ pipeline { ) string( name: 'XMOSDOC_VERSION', - defaultValue: 'v6.1.3', + defaultValue: 'v6.2.0', description: 'The xmosdoc version' ) string( @@ -42,7 +42,7 @@ pipeline { steps { println "Stage running on: ${env.NODE_NAME}" dir("${REPO}") { - checkout scm + checkoutScmShallow() createVenv() installPipfile(false) } @@ -56,8 +56,7 @@ pipeline { script { echo "Test Stage: SEED is ${env.SEED}" // Build all apps in the examples directory - sh "cmake -B build -G\"Unix Makefiles\" -DDEPS_CLONE_SHALLOW=TRUE" - sh "xmake -j 32 -C build" + xcoreBuild() } // script } // dir } //withTools @@ -77,6 +76,9 @@ pipeline { dir("${REPO}") { warnError("Docs") { buildDocs() + dir("examples/app_rmii_100Mbit_icmp") { + buildDocs() + } } } } @@ -93,8 +95,7 @@ pipeline { dir("tests") { script { // Build all apps in the examples directory - sh "cmake -B build -G\"Unix Makefiles\" -DDEPS_CLONE_SHALLOW=TRUE" - sh "xmake -j 32 -C build" + xcoreBuild() if(params.TEST_TYPE == 'fixed_seed') { echo "Running tests with fixed seed ${env.SEED}" diff --git a/README.rst b/README.rst index 3a09d048..27e56de9 100644 --- a/README.rst +++ b/README.rst @@ -9,16 +9,17 @@ lib_ethernet: Ethernet library :scope: General Use :description: XMOS Ethernet Library :category: Networking -:keywords: Ethernet, MII, RGMII, AVB, SMI +:keywords: Ethernet, MII, RMII, RGMII, AVB, SMI :devices: xcore.ai, xcore-200 ******* Summary ******* - -The Ethernet MAC library provides a complete, software defined, Ethernet MAC that supports -10/100/1000 Mb/s data rates and is designed to IEEE Std 802.3-2002 specifications. +``lib_ethernet`` is a library providing implementations of the Ethernet MAC layer, +designed to support network communication by handling data transmission and reception at the Media Access Control level. +It provides complete, software defined, Ethernet MAC implementations that support +10/100/1000 Mb/s data rates and are designed to IEEE Std 802.3-2002 specifications. * 10/100 Mb/s Ethernet MAC * 10/100 Mb/s Ethernet MAC with real-time features @@ -30,7 +31,7 @@ Features ******** * 10/100/1000 Mb/s full-duplex operation - * Media Independent Interface (MII) and Reduced Gigabit Media Independent Interface (RGMII) to the physical layer + * Media Independent Interface (MII), Reduced Media Independent Interface (RMII) and Reduced Gigabit Media Independent Interface (RGMII) to the physical layer * Configurable Ethertype and MAC address filters for unicast, multicast and broadcast addresses * Frame alignment, CRC, and frame length error detection * IEEE 802.1Q Audio Video Bridging priority queueing and credit based traffic shaper @@ -60,9 +61,9 @@ Required tools Required libraries (dependencies) ********************************* - * `lib_locks `_ - * `lib_logging `_ - * `lib_xassert `_ + * `lib_locks `_ + * `lib_logging `_ + * `lib_xassert `_ ************************* Related application notes @@ -70,11 +71,12 @@ Related application notes The following application notes use this library: - * `AN00199: XMOS Gigabit Ethernet application note (eXplorerKIT) `_ + * `AN00199: XMOS Gigabit Ethernet application note (XK_EVK_XE216) `_ * `AN00120: How to use the Ethernet MAC library `_ ******* Support ******* -This package is supported by XMOS Ltd. Issues can be raised against the software at: http://www.xmos.com/support +This package is supported by XMOS Ltd. Issues can be raised against the software at +`http://www.xmos.com/support `_ diff --git a/doc/rst/images/10_100_1000_mac_tasks.png b/doc/rst/images/10_100_1000_mac_tasks.png new file mode 100644 index 0000000000000000000000000000000000000000..cb88e7d811f46282c4d641716334c4a5006aae78 GIT binary patch literal 56354 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 f|Egwh66 literal 0 HcmV?d00001 diff --git a/doc/rst/images/10_100_mac_tasks.png b/doc/rst/images/10_100_mac_tasks.png new file mode 100644 index 0000000000000000000000000000000000000000..d4c401e7be6b4a2db8203efc35a4f5e404aaf7f7 GIT binary patch literal 25760 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�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≀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 DDH_HR literal 0 HcmV?d00001 diff --git a/doc/rst/images/mii_tasks.png b/doc/rst/images/mii_tasks.png new file mode 100644 index 0000000000000000000000000000000000000000..d9db97e91aa79620fca37e438f03e124549a92a7 GIT binary patch literal 9160 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 literal 0 HcmV?d00001 diff --git a/doc/rst/lib_ethernet.rst b/doc/rst/lib_ethernet.rst index 1710a76c..f85f0169 100644 --- a/doc/rst/lib_ethernet.rst +++ b/doc/rst/lib_ethernet.rst @@ -8,12 +8,14 @@ lib_ethernet: Ethernet library Introduction ************ -``lib_ethernet`` allows interfacing to MII or RGMII Ethernet PHYs and provides the Media Access Control (MAC) function +``lib_ethernet`` allows interfacing to MII, RMII or RGMII Ethernet PHYs and provides the Media Access Control (MAC) function for the Ethernet stack. -Various MAC blocks are available depending on the XMOS architecture selected, desired PHY interface and line speed. +Various MAC blocks are available depending on the XMOS architecture selected, desired PHY interface and line speed, as described in :numref:`ethernet_supported_macs`. +.. _ethernet_supported_macs: .. list-table:: Ethernet MAC support by XMOS device family + :widths: 30 20 20 20 20 :header-rows: 1 * - XCORE Architecture @@ -37,16 +39,42 @@ Various MAC blocks are available depending on the XMOS architecture selected, de - Contact XMOS - N/A + +The MII MAC is available as two types; a low resource usage version which provides standard layer 2 data access to an array of clients, and a real-time version which offers additional hardware features including: + + * Hardware time-stamping of point of ingress and egress of frames supporting standards such as IEEE 802.1AS. + * Support for high priority send and receive queues and receive filtering. This allows time sensitive traffic to be prioritised over other traffic. + * Traffic shaping on egress using an IEEE 802.1Qav compliant credit based shaper. + * Configurable VLAN tag stripping on received frames. + +All RMII and RGMII implementations offer the 'real-time' features as standard. See the :ref:`rt_mac_section` section for more details. + +In addition, all MACs support client specific filtering for both source MAC address and Ethertype. See the :ref:`standard_mac_section` section for more details. + + +``lib_ethernet`` is intended to be used with `XCommon CMake `_ +, the `XMOS` application build and dependency management system. + +To use ``lib_ethernet`` in an application, add ``lib_ethernet``, to the list of dependent modules in the application's `CMakeLists.txt` file. + + set(APP_DEPENDENT_MODULES "lib_ethernet") + +All `lib_ethernet` functions can be accessed via the ``ethernet.h`` header file:: + + #include + |newpage| ********************** -Typical Resource Usage +Typical resource usage ********************** Instantiating Ethernet on the XCORE requires resources in terms of memory, threads (MIPS), ports and other resources. -The amount required depends on the feature set of the MAC. The table below summarises the main requirements. +The amount required depends on the feature set of the MAC. :numref:`ethernet_mac_resource_usage` summarises the main requirements. +.. _ethernet_mac_resource_usage: .. list-table:: Ethernet MAC XCORE resource usage + :widths: 25 7 25 7 9 7 :header-rows: 1 * - Configuration @@ -79,13 +107,13 @@ The amount required depends on the feature set of the MAC. The table below summa - 4 - ~102 k - 8 - * - Raw MII + * - Raw MII - 13 - 5 (1-bit), 2 (4-bit) - 2 - ~10 k - 1 - * - SMI (MDIO) + * - SMI (MDIO) - 2 - 2 (1-bit) or 1 (multi-bit) - 0 @@ -100,6 +128,88 @@ The amount required depends on the feature set of the MAC. The table below summa The SMI configuration API is a function call and so uses no dedicated threads. It blocks until the last bit of the transaction is complete. +|newpage| + + +.. _standard_mac_section: + +********************* +Standard MAC Features +********************* + +All MACs in this library support a number of useful features which can be configured by clients. + + * Support for multiple clients (Rx and Tx) allowing many tasks to share the MAC. + * Configurable Ethertype and MAC address filters for unicast, multicast and broadcast addresses and is configurable per client. The number of entries is configurable using ``ETHERNET_MACADDR_FILTER_TABLE_SIZE``. + * Configurable source MAC address. This may be used in conjunction with, for example, lib_otp to provide a unique MAC address per XMOS chip. + * Link state detection allowing action to be taken by higher layers in the case of link state change. + * Separately configurable Rx and Tx buffer sizes (queues). + * VLAN aware packet reception. If the VLAN tag (0x8100) is seen the header length is automatically extended by 4 octets to support the Tag Protocol Identifier (TPID) and Tag Control Information (TCI). + +Transmission of packets is via an API that blocks until the frame has been copied into the transmit queue. This means the buffer size should be appropriately sized for your application or the application should tolerate blocking. + +Reception of a packet blocks until a packet is available. It may however be combined with an asynchronous notification allowing the client to ``select`` on the XC interface whereupon it can then receive the waiting packet. This provides an efficient, event-driven API option. Please see the :ref:`api_section` for details on how to use the MAC. + +In addition, the RMII RT MAC supports an exit command. This tears down all of the tasks associated with the MAC and frees the memory and XCORE resources used, including ports. After exit, the MAC may be re-started again. This can be helpful in cases where ports may be shared (eg. Flash memory) allowing DFU support in package constrained systems. It may also be used to support multiple connect PHY devices where redundancy is required, without costing the chip resources to support multiple MACs. + +|newpage| + +.. _rt_mac_section: + +********************** +Real-Time MAC Features +********************** + +In addition to all of the features outlined in the :ref:`standard_mac_section` section, real-time (RT) MACs offer enhanced features which are useful in a number of applications such as industrial control, real-time networking and Audio/Video streaming cases. These specific features are introduced below. + + +Hardware Time Stamping +====================== + +The XCORE contains architectural features supporting precise timing measurements. Specifically, a 100 MHz timer is included and the RT MACs make use of this to timestamp packets at the point of ingress and egress. This 100 MHz, 32-bit timer value has a resolution of 10 nanoseconds and the provided timestamp can be converted to nanoseconds by multiplying by 10. + +When receiving packets, a reference to a structure of type ``ethernet_packet_info_t`` contains the timestamp of the received packet at point of ingress. + +When transmitting packets, an additional Tx API is provided for the RT MAC which blocks until the packet has been transmitted and returns the time of egress. + +These features, along with APIs to tune the ingress and egress latency offsets, can be used by higher layers such as IEEE 802.1AS (Timing and Synchronization) or PTP (IEEE 1588) to implement precise timing synchronisation across the network. + + +High Priority Queues +==================== + +The RT MACs extend the standard client interfaces with the support of a dedicated High Priority (HP) queue. This queue allows traffic to be received or transmitted before lower priority traffic, which is useful in real-time applications where the network is shared with normal, lower priority, traffic. The MAC logic always prioritises HP packets and queues over low priority. + +The dedicated HP client API uses streaming channels instead of XC interfaces which provide higher performance data transfer. A dedicated channel is used for each of the receive and transmit interfaces. Streaming channels offer higher performance at the cost of occupying a dedicated switch path which may require careful consideration if the client is placed on a different tile from the MAC. This is important due to the architectural limitation of a maximum of four inter-tile switch paths between tiles. A maximum of one HP receive and transmit client are be supported per MAC. + +A flag in the filter table can manually be set when making filter entries which is then used to determine the priority level when receiving packets. This determines which queue to use. + +The transmit HP queue is optionally rate limited using the Credit Based Shaper which is described below. Together, these features provide the mechanisms required by IEEE 802.1Qav enabling reliable, low-latency delivery of time-sensitive streams over Ethernet networks. + + +Credit Based Shaper +=================== + +The Credit Based Shaper (CBS), in conjunction with the HP queue, limits the bandwidth of non-time-sensitive traffic and ensures a reserved bandwidth for high-priority streams. + + +The CBS uses the following mechanisms to manage egress rate: + + * Credits: The high priority queue is assigned a "credit" that increases or decreases over time based on the network's traffic conditions. + * Idle Slope: Determines how quickly credit increases when the queue is idle (i.e., waiting to transmit). + * Transmission of data decreases credit proportionally to the number of bits sent. + +If the credit is positive, the high priority stream is eligible for transmission and will always be transmitted before any low priority traffic. If the credit is negative, the high priority stream is paused until the credit returns to a positive state. By spreading traffic out evenly over time using a CBS, the queue size in each bridge and endpoint can be shorter, which in turn reduces the latency experienced by traffic as it flows through the system. + +The RT MACs are passed an enum argument when instantiated which enables or disables the CBS. In addition the MAC provides an API which can adjust the high-priority transmit queue's CBS idle slope dynamically, for example, if a different bandwidth reservation is required. + +The idle slope passed is a fractional value representing the number of bits per reference timer tick in a Q16.16 format defined by ``MII_CREDIT_FRACTIONAL_BITS`` allowing very precise control over bandwidth reservation. Please see :ref:`api_section` for details and an example showing how to convert from bits-per-second to the slope argument. + + +VLAN Tag Stripping +================== + +In addition to standard MAC VLAN awareness of received packets when calculating payload length, the RT MAC also includes a feature to optionally strip VLAN tags. This is done inside the MAC so that the application can just treat the incoming packet as a standard Ethernet frame. VLAN stripping is dynamically controllable on a per-client basis. |newpage| @@ -120,8 +230,9 @@ The MII transfers data using 4 bit words (nibbles) in each direction, clocked at An enable signal (TXEN) is set active to indicate start of frame and remains active until it is completed. A clock signal (TXCLK) clocks nibbles (TXD[3:0]) at 2.5 MHz for 10 Mb/s mode and 25 MHz for 100 Mb/s mode. The RXDV signal goes active when a valid frame starts and remains active throughout a valid frame duration. -A clock signal (RXCLK) clocks the received nibbles (RXD[3:0]). Table 1 below describes the MII signals: +A clock signal (RXCLK) clocks the received nibbles (RXD[3:0]). :numref:`mii_signals` describes the MII signals: +.. _mii_signals: .. list-table:: MII signals :header-rows: 1 @@ -168,8 +279,8 @@ A clock signal (RXCLK) clocks the received nibbles (RXD[3:0]). Table 1 below des - RX0 - Receive data bit 0 -Any unused 1-bit and 4-bit xCORE ports can be used for MII providing that they are on the same Tile and there is enough -resource to instantiate the relevant Ethernet MAC component on that Tile. +Any unused 1-bit and 4-bit xCORE ports can be used for MII provided that they are on the same tile and there is enough +resource to instantiate the relevant Ethernet MAC component on that tile. .. _rmii_signals_section: @@ -183,11 +294,11 @@ similar functionality to MII however offers a reduced pin-count. The RMII transfers data using 2 bit words (half-nibbles) in each direction, clocked at 50 MHz to achieve 100 Mb/s data rate. An enable signal (TXEN) is set active to indicate start of frame and remains active until it is completed. -A common clock signal clocks nibbles (TXD[1:0]) at 5 MHz for 10 Mb/s mode and 50 MHz for 100 Mb/s mode. +A common clock signal clocks 2 bits (TXD[1:0]) at 50 MHz for 100 Mb/s mode. The RXDV signal goes active when a valid frame starts and remains active throughout a valid frame duration. -A common clock signal clocks the received nibbles (RXD[1:0]). +A common clock signal clocks the received half-nibbles (RXD[1:0]). -Note that either half of a 4-bit port (upper or lower pins) may be used for data or alternatively two 1-bit ports may be used. This +Note that either half of a 4-bit port (upper or lower pins) may be used for data or alternatively two 1-bit ports may be used. This provides additional pinout flexibility which may be important in applications which use low pin-count packages. Both Rx and Tx have their port type set independently and can be mixed. Unused pins on a 4-bit port are ignored for Rx and driven low for Tx. @@ -199,8 +310,9 @@ have their port type set independently and can be mixed. Unused pins on a 4-bit The RMII MAC requires a minimum thread speed of 75 MHz which allows all 8 hardware threads to be used on a 600 MHz xcore.ai device. -Table 1 below describes the RMII signals: +:numref:`rmii_signals` describes the RMII signals: +.. _rmii_signals: .. list-table:: RMII signals :header-rows: 1 @@ -260,8 +372,9 @@ The Ethernet MAC will expect RX clock from PHY to xCORE be delayed by 1.2-2ns as The pins and functions are listed in Table 2. When the 10/100/1000 Mb/s Ethernet MAC is instantiated these pins can no longer be used as GPIO pins, and will instead be driven directly from a Double Data Rate RGMII block, which in turn -is interfaced to a set of ports on Tile 1. +is interfaced to a set of ports on Tile 1. :numref:`rgmii_signals` describes the RGMII pins and signals: +.. _rgmii_signals: .. list-table:: RGMII pins and signals :header-rows: 1 @@ -311,15 +424,10 @@ Other IO pins and ports are unaffected. .. _rgmii_port_structure: -.. figure:: images/XS2-RGMII.pdf +.. figure:: images/XS2-RGMII.png RGMII port structure -PHY Serial Management Interface (MDIO) -====================================== - -The MDIO interface consists of clock (MDC) and data (MDIO) signals. Both should be connected to two one-bit ports that are -configured as open-drain IOs, using external pull-ups to either 3.3V or 2.5V (RGMII). |newpage| @@ -343,9 +451,10 @@ non-real-time configuration. Ethernet MAC components are instantiated as parallel tasks that run in a ``par`` statement. The application can connect via a transmit, receive and configuration interface connection using the :ref:`ethernet_tx_if`, -:ref:`ethernet_rx_if` and :ref:`ethernet_cfg_if` interface types: +:ref:`ethernet_rx_if` and :ref:`ethernet_cfg_if` interface types, as shown in :numref:`standard_mii_task_diagram` -.. figure:: images/10_100_mac_tasks.pdf +.. _standard_mii_task_diagram: +.. figure:: images/10_100_mac_tasks.png 10/100 Mb/s Ethernet MAC task diagram @@ -418,9 +527,10 @@ The real-time MAC may support the RMII interface described in :ref:`rmii_signals It is instantiated similarly to the non-real-time Ethernet MAC, with additional streaming channels for sending and -receiving high-priority Ethernet traffic: +receiving high-priority Ethernet traffic, as shown in :numref:`rt_mac_task_diagram`: -.. figure:: images/10_100_rt_mac_tasks.pdf +.. _rt_mac_task_diagram: +.. figure:: images/10_100_rt_mac_tasks.png 10/100 Mb/s real-time Ethernet MAC task diagram @@ -456,16 +566,16 @@ interfaces and connects to it:: -Similarly the RMII real-time MAC may be instantiated:: +Similarly the RMII real-time MAC may be instantiated (four bit port version shown):: port p_eth_clk = XS1_PORT_1J; - rmii_data_port_t p_eth_txd = {{XS1_PORT_4B, USE_LOWER_2B}}; - rmii_data_port_t p_eth_rxd = {{XS1_PORT_4A, USE_LOWER_2B}}; + port p_eth_txd = XS1_PORT_4B; + port p_eth_rxd = XS1_PORT_4A; port p_eth_rxdv = XS1_PORT_1K; port p_eth_txen = XS1_PORT_1L; clock eth_rxclk = XS1_CLKBLK_1; clock eth_txclk = XS1_CLKBLK_2; - + int main() { ethernet_cfg_if i_cfg[1]; @@ -474,23 +584,21 @@ Similarly the RMII real-time MAC may be instantiated:: streaming chan c_rx_hp; streaming chan c_tx_hp; par { - unsafe{rmii_ethernet_rt_mac(i_cfg, 1, i_rx_lp, 1, i_tx_lp, 1, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_ENABLE_SHAPER);} + rmii_ethernet_rt_mac(i_cfg, 1, i_rx_lp, 1, i_tx_lp, 1, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd, NULL, USE_UPPER_2B, + p_eth_rxdv, + p_eth_txen, + p_eth_txd, NULL, USE_UPPER_2B, + eth_rxclk, eth_txclk, + port_timing, + 4000, 4000, ETHERNET_ENABLE_SHAPER); application(i_cfg[0], i_rx_lp[0], i_tx_lp[0], c_rx_hp, c_tx_hp); } } -.. note:: - The call to rmii_ethernet_rt_mac() needs to be wrapped in ``unsafe{}`` because the rmii_data_port_t types are sent as references which translate to unsafe (array bounds unchecked) pointers. - - - The application can use the other end of the streaming channels to send and receive high-priority traffic e.g.:: @@ -529,9 +637,10 @@ interface as described in :ref:`rgmii_signals_section`. It is instantiated similarly to the real-time Ethernet MAC, with an additional combinable task that allows the configuration interface to be shared with another slow interface such as SMI/MDIO. It must be instantiated on Tile 1 -and the user application run on Tile 0: +and the user application run on Tile 0, as shown in :numref:`rgmii_mac_task_diagram`: -.. figure:: images/10_100_1000_mac_tasks.pdf +.. _rgmii_mac_task_diagram: +.. figure:: images/10_100_1000_mac_tasks.png 10/100/1000 Mb/s Ethernet MAC task diagram @@ -563,9 +672,8 @@ interfaces and connects to it:: .. _mii: -***************** Raw MII interface -***************** +================= The raw MII interface implements a MII layer component with a basic buffering scheme that is shared with the application. It provides a direct access to the MII pins as described in :ref:`mii_signals_section`. It does not implement the buffering and @@ -573,9 +681,10 @@ filtering required by a compliant Ethernet MAC layer, and defers this to the app The buffering of this task is shared with the application it is connected to. It sets up an interrupt handler on the logical core the application is running on (via the ``init`` function on the :ref:`mii_if` interface connection) and also -consumes some of the MIPs on that core in addition to the core :ref:`mii` is running on. +consumes some of the MIPs on that core in addition to the core :ref:`mii` is running on (:numref:`raw_mii_task`). -.. figure:: images/mii_tasks.pdf +.. _raw_mii_task: +.. figure:: images/mii_tasks.png MII task diagram @@ -608,6 +717,32 @@ More information on interfaces and tasks can be be found in the `XMOS Programmin |newpage| +.. _smi: + +SMI/MDIO interface +================== + +The SMI (Serial Management Interface) is used in Ethernet systems for the management and configuration of PHY (physical layer) devices. It is part of the MDIO (Management Data Input/Output) system defined by the IEEE 802.3 standard and provides a mechanism for communication between a MAC (Media Access Control) layer and PHY devices in an Ethernet system. + +The MDIO interface consists of clock (MDC) and data (MDIO) signals. MDC is always driven by the host whereas MDIO can be turned around so that it can read data from the PHY. + +The SMI task is marked as ``[[distributable]]`` which means, if it is called from the same tile, does not occupy a hardware thread. Instead the call to the ``read_reg()`` or ``write_reg()`` methods are treated as function calls which return when the last bit of the SMI transaction is complete. + +The interface uses two pins to communicate and there are two variants of the API provided, depending on whether you wish to use two one bit ports or two bits of a wider port. If you use two bits of a wider port, the remaining pins are not available for general use and should either be left disconnected or weakly pulled down. + +.. note:: + The standard SMI/MDIO specification requires use of a pull-up resistor on MDIO (typically 4.7 kOhm for a single PHY in a 3.3 V system). If using the single-port version then it is necessary to also connect a pull-up to the MDC line (typically 4.7 kOhm for 3.3 V systems). The reason for this is that xcore ports have only a single direction bit. So in order to sample the MDIO line with a known MDC state, an external resistor is required. + +The speed of the interface is set conservatively at 1.66 MHz which supports slower PHY SMI interfaces (eg. LAN8710A) that have a relatively slow time to data valid. This speed is also chosen to support the single port version which has to sample read data at the falling edge, effectively reducing the maximum bit clock by a factor of two. If faster access is required and supported by the PHY, or the two port version is used, then it is possible to adjust the following define in ``smi.xc`` up to a maximum of around 4 MHz for xCORE-200 and 5 MHz for xcore.ai:: + + #define SMI_BIT_CLOCK_HZ 1660000 + +Increasing the bit clock may require use of smaller pull-up resistor(s) depending on board layout to ensure that the signal rise time is sufficient. If in doubt, either test operation using lower the bit rate by setting a smaller ``SMI_BIT_CLOCK_HZ`` or check with an oscilloscope to ensure that the MDC and MDIO lines are fully reaching the logic high state. + +|newpage| + +.. _api_section: + *** API *** @@ -756,19 +891,3 @@ SMI PHY configuration helper functions .. doxygenfunction:: smi_phy_is_powered_down .. doxygenfunction:: smi_get_link_state - - -|newpage| - - -************ -Known Issues -************ - -Please see the active repo for `up to date known issues `_. - -********* -Changelog -********* - -Please see the active repo for the latest `changelog `_. diff --git a/examples/AN00199_gigabit_ethernet_demo_explorerkit/CMakeLists.txt b/examples/AN00199_gigabit_ethernet_demo_explorerkit/CMakeLists.txt index bedde69b..51863593 100644 --- a/examples/AN00199_gigabit_ethernet_demo_explorerkit/CMakeLists.txt +++ b/examples/AN00199_gigabit_ethernet_demo_explorerkit/CMakeLists.txt @@ -11,7 +11,8 @@ set(APP_PCA_ENABLE ON) set(COMPILER_FLAGS_COMMON -g -report -DDEBUG_PRINT_ENABLE - -DRGMII=1) + -DRGMII=1 + -DBOARD_SUPPORT_BOARD=XK_EVK_XE216) set(APP_COMPILER_FLAGS ${COMPILER_FLAGS_COMMON}) set(APP_COMPILER_FLAGS_icmp.xc ${COMPILER_FLAGS_COMMON} diff --git a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.h b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.h index afa69f1d..c11b413a 100644 --- a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.h +++ b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __icmp_h__ #define __icmp_h__ diff --git a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.xc b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.xc index 7aec8cdb..ed74ffda 100644 --- a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.xc +++ b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/icmp.xc @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/main.xc b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/main.xc index 5d58122d..244106b7 100644 --- a/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/main.xc +++ b/examples/AN00199_gigabit_ethernet_demo_explorerkit/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -6,6 +6,7 @@ #include "ethernet.h" #include "icmp.h" #include "smi.h" +#include "xk_evk_xe216/board.h" #include "debug_print.h" // These ports are for accessing the OTP memory @@ -15,7 +16,6 @@ rgmii_ports_t rgmii_ports = on tile[1]: RGMII_PORTS_INITIALIZER; port p_smi_mdio = on tile[1]: XS1_PORT_1C; port p_smi_mdc = on tile[1]: XS1_PORT_1D; -port p_eth_reset = on tile[1]: XS1_PORT_1N; static unsigned char ip_address[4] = {192, 168, 1, 178}; @@ -33,41 +33,7 @@ enum cfg_clients { NUM_CFG_CLIENTS }; -[[combinable]] -void ar8035_phy_driver(client interface smi_if smi, - client interface ethernet_cfg_if eth) { - ethernet_link_state_t link_state = ETHERNET_LINK_DOWN; - ethernet_speed_t link_speed = LINK_1000_MBPS_FULL_DUPLEX; - const int phy_reset_delay_ms = 1; - const int link_poll_period_ms = 1000; - const int phy_address = 0x4; - timer tmr; - int t; - tmr :> t; - p_eth_reset <: 0; - delay_milliseconds(phy_reset_delay_ms); - p_eth_reset <: 1; - while (smi_phy_is_powered_down(smi, phy_address)); - smi_configure(smi, phy_address, LINK_1000_MBPS_FULL_DUPLEX, SMI_ENABLE_AUTONEG); - - while (1) { - select { - case tmr when timerafter(t) :> t: - ethernet_link_state_t new_state = smi_get_link_state(smi, phy_address); - // Read AR8035 status register bits 15:14 to get the current link speed - if (new_state == ETHERNET_LINK_UP) { - link_speed = (ethernet_speed_t)(smi.read_reg(phy_address, 0x11) >> 14) & 3; - } - if (new_state != link_state) { - link_state = new_state; - eth.set_link_state(0, new_state, link_speed); - } - t += link_poll_period_ms * XS1_TIMER_KHZ; - break; - } - } -} int main() { diff --git a/examples/app_mii_demo/src/main.xc b/examples/app_mii_demo/src/main.xc index c79d72ad..21ce28ab 100644 --- a/examples/app_mii_demo/src/main.xc +++ b/examples/app_mii_demo/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/examples/app_rmii_100Mbit_icmp/CMakeLists.txt b/examples/app_rmii_100Mbit_icmp/CMakeLists.txt new file mode 100644 index 00000000..a601be8d --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_rmii_100Mbit_icmp) + +set(APP_HW_TARGET xk-eth-xu316-dual-100m.xn) + +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) + +set(APP_PCA_ENABLE ON) + +set(COMPILER_FLAGS_COMMON -g + -report + -DDEBUG_PRINT_ENABLE=1 + -Os + -Wno-reinterpret-alignment + -DBOARD_SUPPORT_BOARD=XK_ETH_XU316_DUAL_100M) + +set(APP_COMPILER_FLAGS ${COMPILER_FLAGS_COMMON}) + +set(APP_XSCOPE_SRCS src/config.xscope) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +XMOS_REGISTER_APP() diff --git a/examples/app_rmii_100Mbit_icmp/README.rst b/examples/app_rmii_100Mbit_icmp/README.rst new file mode 100644 index 00000000..327ac49f --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/README.rst @@ -0,0 +1,73 @@ +:orphan + +############################# +100Mbit Ethernet RMII example +############################# + +******* +Summary +******* + +Ethernet connectivity is an essential part of the explosion of connected +devices known collectively as the Internet of Things (IoT). XMOS technology is +perfectly suited to these applications - offering future proof and reliable +ethernet connectivity whilst offering the flexibility to interface to a huge +variety of "Things". + +This example that demonstrates the use +of the XMOS Ethernet library to create a layer 2 ethernet MAC +interface on an XMOS multicore microcontroller. + +The code associated with this application note provides an example of +using the Ethernet Library to provide a framework for the creation of an +ethernet Reduced Media Independent Interface (RMII) and MAC interface for +100Mbps. + +The applcation note uses XMOS libraries to provide a simple IP stack +capable of responding to an ICMP ping message. The code used in the +application note provides both RMII communication to the PHY and a MAC +transport layer for ethernet packets and enables a client to connect +to it and send/receive packets. + +***************** +Required hardware +***************** + +The example code provided with this application has been implemented and tested +on the xk_eth_xu316_dual_100m board. + +************** +Required tools +************** + + * XMOS XTC Tools: 15.3.0 + +********************************* +Required libraries (dependencies) +********************************* + + * `lib_ethernet `_ + * `lib_xua `_ + * `lib_board_support `_ + * `lib_otpinfo `_ + + +************* +Prerequisites +************* + + * This document assumes familarity with the XMOS xCORE architecture, + the Ethernet standards IEEE 802.3u (RMII), the XMOS tool chain and + the XC language. Documentation related to these aspects which are + not specific to this application note are linked to in the + references appendix. + + * For an overview of the Ethernet library, please see the Ethernet + library user guide. + + +******* +Support +******* + +This package is supported by XMOS Ltd. Issues can be raised against the software at: http://www.xmos.com/support diff --git a/examples/app_rmii_100Mbit_icmp/doc/exclude_patterns.inc b/examples/app_rmii_100Mbit_icmp/doc/exclude_patterns.inc new file mode 100644 index 00000000..ff3d88c3 --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/doc/exclude_patterns.inc @@ -0,0 +1,6 @@ +# The following patterns are to be excluded from the documentation build +examples +tests +.pytest_cache +CHANGELOG.rst +LICENSE.rst diff --git a/examples/app_rmii_100Mbit_icmp/doc/rst/AN00120.rst b/examples/app_rmii_100Mbit_icmp/doc/rst/AN00120.rst new file mode 100644 index 00000000..0af51c0b --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/doc/rst/AN00120.rst @@ -0,0 +1,287 @@ + +|newpage| + +############################# +100Mbit Ethernet RMII example +############################# + +******** +Overview +******** + +The application note shows the use of the XMOS Ethernet library. The +library allows multiple clients to access the Ethernet hardware. +This application note uses the real-time RMII MAC which uses +four cores and provides high performance streaming data, accurate +packet timestamping, priority queuing and 802.1Qav traffic shaping. + +RMII provides the data transfer signals +between the Ethernet PHY (Physical Layer Device or transceiver) and +the xCORE device. The RMII layer receives packets of data which are +then routed by an Ethernet MAC layer to multiple processes running on +the xCORE. SMI provides the management interface between the PHY and +the xCORE device. + +The application communicates with the Ethernet MAC that drives the RMII +data interface to the PHY. A separate PHY driver configures the PHY +via the SMI serial interface. The application is tested on the ``xk_eth_xu316_dual_100m`` +board which is an xcore.ai based board. It also has two `TI DP83826E `_ PHYs +connected to it and can run applications requiring up to two ethernet ports. +This application instantiates one RMII MAC port that connects to one of the two +DP83826E PHYs on the board. + + +************************* +100Mbit RMII ICMP example +************************* + + +Building the Application +======================== + +The following section assumes you have downloaded and installed the `XMOS XTC tools `_ +(see `README` for required version). Installation instructions can be found `here `_. +Be sure to pay attention to the section `Installation of required third-party tools +`_. + +The application uses the `xcommon-cmake `_ +build system as bundled with the XTC tools. + +The file `CMakeLists.txt` contains the application build configuration. + +To configure the build run the following from an XTC command prompt:: + + cd app_rmii_100Mbit_icmp + cmake -G "Unix Makefiles" -B build + +Any missing dependencies will be downloaded by the build system as part of this configure step. + +Finally, the application binaries can be built using ``xmake``:: + + xmake -C build + +This will build the application binary ``app_rmii_100Mbit_icmp.xe`` in the ``app_rmii_100Mbit_icmp/bin`` directory. + +The example uses the MAC layer implementation in the ``lib_ethernet`` library. It depends on +``lib_board_support`` for the PHY configuration on the ``xk_eth_xu316_dual_100m`` board. +It also gets the MAC address out of the xCORE OTP ROM, and depends on ``lib_otpinfo`` for that. + +These dependencies are specified in the ``deps.cmake`` file, which is included in the application ``CMakeLists.txt`` + +Allocating hardware resources +============================= + +The Ethernet library requires several ports to communicate with the +Ethernet PHY. These ports are declared in the main program file +(``main.xc``). In this examples the ports are set up for the PHY 1 on the ``xk_eth_xu316_dual_100m`` +board. + +Actual port names such as ``XS1_PORT_1A`` are specified in XN file and source refers +to them with symbolic names such as ``PHY_0_TX_EN``, with the actual to symbolic name mapping +specified in the xn file: + +.. literalinclude:: ../../src/main.xc + :start-at: rmii_data_port_t + :end-at: PHY_0_TX_EN + +In addition to the ports, two clock blocks are required, one each for the ethernet TX and RX clocks. + +.. literalinclude:: ../../src/main.xc + :start-at: eth_rxclk + :end-at: eth_txclk + + +The MDIO Serial Management Interface (SMI) is used to transfer +management information between MAC and PHY. This interface consists of +two signals which are connected to two ports: + +.. literalinclude:: ../../src/main.xc + :start-at: p_smi_mdio + :end-at: p_smi_mdc + +The final ports used in the application are the ones to access the internal OTP +memory on the xCORE. These ports are fixed and can be intialized with +the ``OTP_PORTS_INITIALIZER`` macro supplied by the ``lib_otpinfo`` +OTP reading library. + +.. literalinclude:: ../../src/main.xc + :start-at: // These ports are for accessing the OTP memory + :end-at: otp_ports + +The application main() function +=============================== + +The main function in the program sets up the tasks in the application. + +.. literalinclude:: ../../src/main.xc + :start-at: int main + :end-before: return 0; + +The ``rmii_ethernet_rt_mac`` creates a 100M RMII MAC instance that connects to the PHY on board. +It internally starts the four tasks, ``rmii_master_rx_pins_4b``, ``rmii_master_tx_pins``, ``mii_ethernet_filter`` +and ``mii_ethernet_server``, that make up the MAC implementation. +These tasks handle communicating with the PHY at the pin level (``rmii_master_rx_pins_4b``, ``rmii_master_tx_pins``), +filtering received packets based on a MAC address lookup table based filtering, moving them +into the receive queues ((``mii_ethernet_filter``) and communicating with the client processes, +facilitating packet transfer between the clients and the network (``mii_ethernet_server``). + +The ``rmii_ethernet_rt_mac`` tasks takes the previously declared ports as arguments as well as the required buffer size for the packet +buffer within the MAC. + +The ``smi`` task is part of the Ethernet library and controls the SMI protocol to configure the PHY. +It connects to the ``dp83826e_phy_driver`` task which connects configuration of the PHY. + +The ``dp83826e_phy_driver`` function is implemented in ``lib_board_support`` and it configures the PHY +over the SMI interface. + +The IP address that the ICMP code uses is declared as an array in ``main.xc``:: + + static unsigned char ip_address[4] = {192, 168, 1, 178}; + +This value can be altered to something that works on a given network. + +|newpage| + +The PHY driver +============== + +The PHY drive task ``dp83826e_phy_driver`` connects to both the Ethernet MAC (via the +``ethernet_cfg_if`` interface for configuration) and the SMI driver +(via the ``smi_if`` interface): + +.. literalinclude:: ../../src/main.xc + :start-at: on tile[1]: dp83826e_phy_driver + :end-at: p_smi_mdc); + +The first action the drive does is wait for the PHY to power up and +then configure the PHY. This is done via library functions provided by +the Ethernet library. + +The main body of the drive is an infinite loop that periodically +reacts to a timer event in an xC ``select`` statement. After a set period +it checks the state of the PHY over SMI and then informs the MAC of +this state via the ``i_eth.set_link_state`` call. This way the MAC can +know about link up/down events or change of link speed. + +ICMP packet processing +======================= + +The packet processing in the application is handled by the +``icmp_server`` task which is defined in the file ``icmp.xc``. This +function connects to the ethernet MAC via a transmit, receive and +configuration interface: + +.. literalinclude:: ../../src/icmp.xc + :start-at: [[combinable]] + :end-at: { + +The first thing the task performs is configuring its connection to the +MAC. The MAC address is configured by reading a MAC address out of OTP +(using the ``otp_board_info_get_mac`` function from the OTP reading +library) and then calling the ``set_macaddr`` interface function: + +.. literalinclude:: ../../src/icmp.xc + :start-at: unsigned char mac_address[MACADDR_NUM_BYTES]; + :end-at: cfg.set_macaddr + +After this, the task configures filters to determine which type of +packets is will receive from the MAC: + +.. literalinclude:: ../../src/icmp.xc + :start-at: memcpy(macaddr_filter.addr + :end-before: debug_printf + +The task then proceeds into an infinite loop that waits for a packet +from the MAC and then processes it: + +.. literalinclude:: ../../src/icmp.xc + :start-at: while (1) + +The xC ``select`` statement will wait for the event +``rx.packet_ready()`` which is a receive notification from the MAC +(see the Ethernet library user guide for details of the ethernet +receive interface). When a packet arrives the ``rx.get_packet`` call +will retreive the packet from the MAC. + +After the packet is processed the ``tx.send_packet`` call will send +the created reponse packet to the MAC. + +Details of the packet processing functions ``is_valid_arp_packet``, +``build_arp_response``, ``is_valid_icmp_packet`` and +``build_icmp_response`` can be found in the ``icmp.xc`` file. The +functions implement the ICMP protocol. + +|newpage| + +Demo Hardware Setup +=================== + +TODO + + +Running the application +======================= + +Once the ``app_rmii_100Mbit_icmp.xe`` application binary is compiled, it can be +executed on the ``xk_eth_xu316_dual_100m`` board. + +The ``xrun`` tool is used from the command line to download and run the code on the xCore device. +In a terminal with XTC tools sourced, from the ``app_rmii_100Mbit_icmp`` directory, run:: + + xrun bin/app_rmii_100Mbit_icmp.xe + +Once this command has executed hte application will be running on the xCore device. + + +|newpage| + +**** +FAQs +**** + +TODO + +|newpage| + +*************** +Further reading +*************** + +* XMOS XTC Tools Installation Guide + + https://xmos.com/xtc-install-guide + + * XMOS XTC Tools User Guide + + https://www.xmos.com/view/Tools-15-Documentation + + * XMOS application build and dependency management system; xcommon-cmake + + https://www.xmos.com/file/xcommon-cmake-documentation/?version=latest + + + * XMOS Layer 2 Ethernet MAC Component + + https://www.xmos.com/published/xmos-layer-2-ethernet-mac-component + + + * Ethernet Frame + + http://en.wikipedia.org/wiki/Ethernet_frame + + + * MAC address + + http://en.wikipedia.org/wiki/MAC_address + + * Ethernet Type + + http://en.wikipedia.org/wiki/EtherType + + * Internet Control Message Protocol + + http://en.wikipedia.org/wiki/Internet_Control_Message_Protocol + +|newpage| + + diff --git a/examples/app_rmii_100Mbit_icmp/doc/substitutions.inc b/examples/app_rmii_100Mbit_icmp/doc/substitutions.inc new file mode 100644 index 00000000..e69de29b diff --git a/examples/app_rmii_100Mbit_icmp/settings.yml b/examples/app_rmii_100Mbit_icmp/settings.yml new file mode 100644 index 00000000..10197321 --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/settings.yml @@ -0,0 +1,21 @@ +# This file relates to internal XMOS infrastructure and should be ignored by external users +--- +an_number: AN00120 +project: '{{an_number}}' +title: '{{an_number}}: 100Mbit RMII ping example' +version: 1.0.0 + +documentation: + substitutions_path: doc/substitutions.inc + exclude_patterns_path: doc/exclude_patterns.inc + root_doc: doc/rst/AN00120.rst + linkcheck_ignore_regex: ['.*xmos\.com\/file.*'] + pdfs: + doc/rst/AN00120.rst: + pdf_title: '{{title}}' + pdf_filename: '{{an_number}}_v{{version}}' + pdf_short: yes + README.rst: + pdf_title: '{{title}} (README)' + pdf_filename: '{{an_number}}_v{{version}}_readme' + pdf_short: yes diff --git a/examples/app_rmii_100Mbit_icmp/src/config.xscope b/examples/app_rmii_100Mbit_icmp/src/config.xscope new file mode 100644 index 00000000..1cef58cc --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/src/config.xscope @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/app_rmii_100Mbit_icmp/src/icmp.h b/examples/app_rmii_100Mbit_icmp/src/icmp.h new file mode 100644 index 00000000..c11b413a --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/src/icmp.h @@ -0,0 +1,16 @@ +// Copyright 2013-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __icmp_h__ +#define __icmp_h__ +#include +#include + + +[[combinable]] +void icmp_server(client ethernet_cfg_if cfg, + client ethernet_rx_if rx, + client ethernet_tx_if tx, + const unsigned char ip_address[4], + otp_ports_t &otp_ports); + +#endif // __icmp_h__ diff --git a/examples/app_rmii_100Mbit_icmp/src/icmp.xc b/examples/app_rmii_100Mbit_icmp/src/icmp.xc new file mode 100644 index 00000000..87b82dac --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/src/icmp.xc @@ -0,0 +1,298 @@ +// Copyright 2013-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include + +static unsigned short checksum_ip(const unsigned char frame[34]) +{ + int i; + unsigned accum = 0; + + for (i = 14; i < 34; i += 2) + { + accum += *((const uint16_t*)(frame + i)); + } + + // Fold carry into 16bits + while (accum >> 16) + { + accum = (accum & 0xFFFF) + (accum >> 16); + } + + accum = byterev(~accum) >> 16; + + return accum; +} + + +static int build_arp_response(unsigned char rxbuf[64], + unsigned char txbuf[64], + const unsigned char own_mac_addr[MACADDR_NUM_BYTES], + const unsigned char own_ip_addr[4]) +{ + unsigned word; + unsigned char byte; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + byte = rxbuf[22+i]; + txbuf[i] = byte; + txbuf[32 + i] = byte; + } + word = ((const unsigned int *) rxbuf)[7]; + for (int i = 0; i < 4; i++) + { + txbuf[38 + i] = word & 0xFF; + word >>= 8; + } + + txbuf[28] = own_ip_addr[0]; + txbuf[29] = own_ip_addr[1]; + txbuf[30] = own_ip_addr[2]; + txbuf[31] = own_ip_addr[3]; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[22 + i] = own_mac_addr[i]; + txbuf[6 + i] = own_mac_addr[i]; + } + txbuf[12] = 0x08; + txbuf[13] = 0x06; + txbuf[14] = 0x00; + txbuf[15] = 0x01; + txbuf[16] = 0x08; + txbuf[17] = 0x00; + txbuf[18] = 0x06; + txbuf[19] = 0x04; + txbuf[20] = 0x00; + txbuf[21] = 0x02; + + // Typically 48 bytes (94 for IPv6) + for (int i = 42; i < 64; i++) + { + txbuf[i] = 0x00; + } + + return 64; +} + + +static int is_valid_arp_packet(const unsigned char rxbuf[nbytes], + unsigned nbytes, + const unsigned char own_ip_addr[4]) +{ + if (rxbuf[12] != 0x08 || rxbuf[13] != 0x06) + return 0; + + debug_printf("ARP packet received\n"); + + if (((const unsigned int *) rxbuf)[3] != 0x01000608) + { + debug_printf("Invalid et_htype\n"); + return 0; + } + if (((const unsigned int *) rxbuf)[4] != 0x04060008) + { + debug_printf("Invalid ptype_hlen\n"); + return 0; + } + if ((((const unsigned int *) rxbuf)[5] & 0xFFFF) != 0x0100) + { + debug_printf("Not a request\n"); + return 0; + } + for (int i = 0; i < 4; i++) + { + if (rxbuf[38 + i] != own_ip_addr[i]) + { + debug_printf("Not for us\n"); + return 0; + } + } + + return 1; +} + + +static int build_icmp_response(unsigned char rxbuf[], unsigned char txbuf[], + const unsigned char own_mac_addr[MACADDR_NUM_BYTES], + const unsigned char own_ip_addr[4]) +{ + unsigned icmp_checksum; + int datalen; + int totallen; + const int ttl = 0x40; + int pad; + + // Precomputed empty IP header checksum (inverted, bytereversed and shifted right) + unsigned ip_checksum = 0x0185; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[i] = rxbuf[6 + i]; + } + for (int i = 0; i < 4; i++) + { + txbuf[30 + i] = rxbuf[26 + i]; + } + icmp_checksum = byterev(((const unsigned int *) rxbuf)[9]) >> 16; + for (int i = 0; i < 4; i++) + { + txbuf[38 + i] = rxbuf[38 + i]; + } + totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; + datalen = totallen - 28; + for (int i = 0; i < datalen; i++) + { + txbuf[42 + i] = rxbuf[42+i]; + } + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[6 + i] = own_mac_addr[i]; + } + ((unsigned int *) txbuf)[3] = 0x00450008; + totallen = byterev(28 + datalen) >> 16; + ((unsigned int *) txbuf)[4] = totallen; + ip_checksum += totallen; + ((unsigned int *) txbuf)[5] = 0x01000000 | (ttl << 16); + ((unsigned int *) txbuf)[6] = 0; + for (int i = 0; i < 4; i++) + { + txbuf[26 + i] = own_ip_addr[i]; + } + ip_checksum += (own_ip_addr[0] | own_ip_addr[1] << 8); + ip_checksum += (own_ip_addr[2] | own_ip_addr[3] << 8); + ip_checksum += txbuf[30] | (txbuf[31] << 8); + ip_checksum += txbuf[32] | (txbuf[33] << 8); + + txbuf[34] = 0x00; + txbuf[35] = 0x00; + + icmp_checksum = (icmp_checksum + 0x0800); + icmp_checksum += icmp_checksum >> 16; + txbuf[36] = icmp_checksum >> 8; + txbuf[37] = icmp_checksum & 0xFF; + + while (ip_checksum >> 16) + { + ip_checksum = (ip_checksum & 0xFFFF) + (ip_checksum >> 16); + } + ip_checksum = byterev(~ip_checksum) >> 16; + txbuf[24] = ip_checksum >> 8; + txbuf[25] = ip_checksum & 0xFF; + + for (pad = 42 + datalen; pad < 64; pad++) + { + txbuf[pad] = 0x00; + } + return pad; +} + + +static int is_valid_icmp_packet(const unsigned char rxbuf[nbytes], + unsigned nbytes, + const unsigned char own_ip_addr[4]) +{ + unsigned totallen; + if (rxbuf[23] != 0x01) + return 0; + + debug_printf("ICMP packet received\n"); + + if (((const unsigned int *) rxbuf)[3] != 0x00450008) + { + debug_printf("Invalid et_ver_hdrl_tos\n"); + return 0; + } + if ((((const unsigned int *) rxbuf)[8] >> 16) != 0x0008) + { + debug_printf("Invalid type_code\n"); + return 0; + } + for (int i = 0; i < 4; i++) + { + if (rxbuf[30 + i] != own_ip_addr[i]) + { + debug_printf("Not for us\n"); + return 0; + } + } + + totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; + if (nbytes > 60 && nbytes != totallen + 14) + { + debug_printf("Invalid size (nbytes:%d, totallen:%d)\n", nbytes, totallen+14); + return 0; + } + if (checksum_ip(rxbuf) != 0) + { + debug_printf("Bad checksum\n"); + return 0; + } + + return 1; +} + +[[combinable]] +void icmp_server(client ethernet_cfg_if cfg, + client ethernet_rx_if rx, + client ethernet_tx_if tx, + const unsigned char ip_address[4], + otp_ports_t &otp_ports) +{ + unsigned char mac_address[MACADDR_NUM_BYTES]; + ethernet_macaddr_filter_t macaddr_filter; + + // Get the mac address from OTP and set it in the ethernet component + otp_board_info_get_mac(otp_ports, 0, mac_address); + + size_t index = rx.get_index(); + cfg.set_macaddr(0, mac_address); + + memcpy(macaddr_filter.addr, mac_address, sizeof(mac_address)); + cfg.add_macaddr_filter(index, 0, macaddr_filter); + + // Add broadcast filter + memset(macaddr_filter.addr, 0xff, MACADDR_NUM_BYTES); + cfg.add_macaddr_filter(index, 0, macaddr_filter); + + // Only allow ARP and IP packets to the app + cfg.add_ethertype_filter(index, 0x0806); + cfg.add_ethertype_filter(index, 0x0800); + + debug_printf("Test started\n"); + while (1) + { + select { + case rx.packet_ready(): + unsigned char rxbuf[ETHERNET_MAX_PACKET_SIZE]; + unsigned char txbuf[ETHERNET_MAX_PACKET_SIZE]; + ethernet_packet_info_t packet_info; + rx.get_packet(packet_info, rxbuf, ETHERNET_MAX_PACKET_SIZE); + + if (packet_info.type != ETH_DATA) + continue; + + if (is_valid_arp_packet(rxbuf, packet_info.len, ip_address)) + { + int len = build_arp_response(rxbuf, txbuf, mac_address, ip_address); + tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES); + debug_printf("ARP response sent\n"); + } + else if (is_valid_icmp_packet(rxbuf, packet_info.len, ip_address)) + { + int len = build_icmp_response(rxbuf, txbuf, mac_address, ip_address); + tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES); + debug_printf("ICMP response sent\n"); + } + break; + } + } +} + + diff --git a/examples/app_rmii_100Mbit_icmp/src/main.xc b/examples/app_rmii_100Mbit_icmp/src/main.xc new file mode 100644 index 00000000..60d2ce4a --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/src/main.xc @@ -0,0 +1,73 @@ +// Copyright 2014-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include "otp_board_info.h" +#include "ethernet.h" +#include "icmp.h" +#include "smi.h" +#include "xk_eth_xu316_dual_100m/board.h" +#include "debug_print.h" + +rmii_data_port_t p_eth_rxd = {{PHY_0_RXD_4B, USE_UPPER_2B}}; +rmii_data_port_t p_eth_txd = {{PHY_0_TXD_4B, USE_UPPER_2B}}; + +port p_eth_clk = CLK_50M; +port p_eth_rxdv = PHY_0_RXDV; +port p_eth_txen = PHY_0_TX_EN; + +clock eth_rxclk = on tile[0]: XS1_CLKBLK_1; +clock eth_txclk = on tile[0]: XS1_CLKBLK_2; + +port p_smi_mdio = MDIO; +port p_smi_mdc = MDC; + + +// These ports are for accessing the OTP memory +otp_ports_t otp_ports = on tile[0]: OTP_PORTS_INITIALIZER; + +static unsigned char ip_address[4] = {192, 168, 1, 178}; + +// An enum to manage the array of connections from the ethernet component +// to its clients. +enum eth_clients { + ETH_TO_ICMP, + NUM_ETH_CLIENTS +}; + +enum cfg_clients { + CFG_TO_ICMP, + CFG_TO_PHY_DRIVER, + NUM_CFG_CLIENTS +}; + +#define ETH_RX_BUFFER_SIZE_WORDS 1600 +const int phy_address = 0x0; +int main() +{ + ethernet_cfg_if i_cfg[NUM_CFG_CLIENTS]; + ethernet_rx_if i_rx[NUM_ETH_CLIENTS]; + ethernet_tx_if i_tx[NUM_ETH_CLIENTS]; + smi_if i_smi; + + par { + on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_CLIENTS, + i_rx, NUM_ETH_CLIENTS, + i_tx, NUM_ETH_CLIENTS, + null, null, + p_eth_clk, + &p_eth_rxd, p_eth_rxdv, + p_eth_txen, &p_eth_txd, + eth_rxclk, eth_txclk, + port_timing, + 4000, 4000, ETHERNET_DISABLE_SHAPER);} + + on tile[1]: dp83826e_phy_driver(i_smi, i_cfg[CFG_TO_PHY_DRIVER], phy_address); + on tile[1]: smi(i_smi, p_smi_mdio, p_smi_mdc); + + on tile[0]: icmp_server(i_cfg[CFG_TO_ICMP], + i_rx[ETH_TO_ICMP], i_tx[ETH_TO_ICMP], + ip_address, otp_ports); + } + return 0; +} diff --git a/examples/app_rmii_100Mbit_icmp/src/xk-eth-xu316-dual-100m.xn b/examples/app_rmii_100Mbit_icmp/src/xk-eth-xu316-dual-100m.xn new file mode 100644 index 00000000..424762d1 --- /dev/null +++ b/examples/app_rmii_100Mbit_icmp/src/xk-eth-xu316-dual-100m.xn @@ -0,0 +1,92 @@ + + + Board + xk_eth_xu316_dual_100m + + tileref tile[2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/deps.cmake b/examples/deps.cmake index 34f5cebb..6a19decf 100644 --- a/examples/deps.cmake +++ b/examples/deps.cmake @@ -1,3 +1,4 @@ set(APP_DEPENDENT_MODULES lib_ethernet "lib_otpinfo(feature/xccm)" - ) \ No newline at end of file + "lib_board_support(feature/xk_eth_xu316_dual_100m)" + ) diff --git a/lib_ethernet/api/ethernet.h b/lib_ethernet/api/ethernet.h index d35926e9..6590cef1 100644 --- a/lib_ethernet/api/ethernet.h +++ b/lib_ethernet/api/ethernet.h @@ -9,10 +9,13 @@ #include "doxygen.h" // Sphynx Documentation Workarounds -#define ETHERNET_ALL_INTERFACES (-1) -#define ETHERNET_MAX_PACKET_SIZE (1518) /**< MAX packet size in bytes */ +#define ETHERNET_ALL_INTERFACES (-1) +#define ETHERNET_MAX_PACKET_SIZE (1518) /**< MAX packet size in bytes including src, dst, ether/tags but NOT preamble or CRC*/ + +#define MACADDR_NUM_BYTES (6) /**< Number of octets in MAC address */ + +#define MII_CREDIT_FRACTIONAL_BITS (16) /** Fractional bits for Qav credit based shaper setting */ -#define MACADDR_NUM_BYTES 6 /** Type representing the type of packet from the MAC */ typedef enum eth_packet_type_t { @@ -77,7 +80,7 @@ typedef interface ethernet_cfg_if { * \param ifnum The index of the MAC interface to set * \param mac_address The six-octet MAC address to set */ - void set_macaddr(size_t ifnum, uint8_t mac_address[MACADDR_NUM_BYTES]); + void set_macaddr(size_t ifnum, const uint8_t mac_address[MACADDR_NUM_BYTES]); /** Gets the source MAC address of the Ethernet MAC * @@ -166,14 +169,37 @@ typedef interface ethernet_cfg_if { */ void get_tile_id_and_timer_value(REFERENCE_PARAM(unsigned, tile_id), REFERENCE_PARAM(unsigned, time_on_tile)); - /** Set the high-priority TX queue's credit based shaper idle slope. + /** Set the high-priority TX queue's credit based shaper idle slope value. + * See also set_egress_qav_idle_slope_bps() where the argument is bits per second. * This function is only available in the 10/100 Mb/s real-time and 10/100/1000 Mb/s MACs. * - * \param ifnum The index of the MAC interface to set the slope - * \param slope The slope value + * \param ifnum The index of the MAC interface to set the slope (always 0) + * \param slope The slope value in bits per 100 MHz ref timer tick in MII_CREDIT_FRACTIONAL_BITS Q format. + * + * */ void set_egress_qav_idle_slope(size_t ifnum, unsigned slope); + /** Set the high-priority TX queue's credit based shaper idle slope in bits per second. + * This function is only available in the 10/100 Mb/s real-time and 10/100/1000 Mb/s MACs. + * + * \param ifnum The index of the MAC interface to set the slope (always 0) + * \param slope The maximum number of bits per second to be set + * + * + */ + void set_egress_qav_idle_slope_bps(size_t ifnum, unsigned bits_per_second); + + + /** Sets the the high-priority TX queue's Qav credit limit in units of frame size bytes + * + * \param ifnum The index of the MAC interface to set the slope (always 0) + * \param limit_bytes The credit limit in units of payload size in bytes to set as a credit limit, + * not including preamble, CRC and IFG. Set to 0 for no limit (default) + * + */ + void set_egress_qav_credit_limit(size_t ifnum, int payload_limit_bytes); + /** Set the ingress latency to correct for the offset between the timestamp * measurement plane relative to the reference plane. See 802.1AS 8.4.3. * @@ -421,8 +447,8 @@ inline void ethernet_send_hp_packet(streaming_chanend_t c_tx_hp, * on the egress MAC port. */ enum ethernet_enable_shaper_t { - ETHERNET_ENABLE_SHAPER, /**< Enable the credit based shaper */ - ETHERNET_DISABLE_SHAPER /**< Disable the credit based shaper */ + ETHERNET_DISABLE_SHAPER = 0, /**< Disable the credit based shaper */ + ETHERNET_ENABLE_SHAPER /**< Enable the credit based shaper */ }; /** Structure representing the port and clock resources required by RGMII @@ -614,60 +640,26 @@ void mii_ethernet_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), static_co * in the case that a four bit port is specified for RMII. The other two pins of the four bit * port cannot be used. For Rx the input values are ignored. For Tx, the unused pins are always driven low. */ typedef enum rmii_data_4b_pin_assignment_t{ - USE_LOWER_2B = 0, /**< Use bit 0 and bit 1 of the four bit port */ - USE_UPPER_2B = 1 /**< Use bit 2 and bit 3 of the four bit port */ + USE_LOWER_2B = 0, /**< Use bit 0 and bit 1 of the four bit port for data bits 0 and 1*/ + USE_UPPER_2B = 1 /**< Use bit 2 and bit 3 of the four bit port for data bits 0 and 1*/ } rmii_data_4b_pin_assignment_t; -/** ENUM to determine which two bits of an 8 bit port are to be used as data lines - * in the case that an eight bit port is specified for RMII Tx. The other six bits of the eight bit - * port cannot be used. The unused pins are always driven low. Use of an eight bit port for RMII Rx is not supported. */ -typedef struct rmii_data_8b_pin_assignment_t{ - unsigned short bit_pos_0; /**< Which bit of the port data 0 should be on: 0..7 */ - unsigned short bit_pos_1; /**< Which bit of the port data 0 should be on: 0..7 */ -} rmii_data_8b_pin_assignment_t; /** Macro to populate which bits of the 8b port are used in the initialiser. Unused bits are driven low. */ #define RMII_8B_PINS_INITIALISER(pos_0, pos_1) ((unsigned)pos_0 | ((unsigned)pos_1) << 16) -/** Union representing which pins of a 4b or 8b port to be used. */ -typedef union rmii_data_pin_assignment_t{ - rmii_data_4b_pin_assignment_t pins_4b; - rmii_data_8b_pin_assignment_t pins_8b; -}rmii_data_pin_assignment_t; - -/** Structure representing a four bit port used for RMII data transmission or reception */ -typedef struct rmii_data_8b_t -{ - port data; /**< Eight bit data port */ - rmii_data_8b_pin_assignment_t pins_used; /**< Which bits of the port data should be on.*/ -} rmii_data_8b_t; - -/** Structure representing a four bit port used for RMII data transmission or reception */ -typedef struct rmii_data_4b_t -{ - port data; /**< Four bit data port */ - rmii_data_4b_pin_assignment_t pins_used;/**< Which two bits of the data port to use. - Unused Rx pins are ignored and unused - Tx pins are driven low. */ -} rmii_data_4b_t; - -/** Structure type representing a pair of one bit ports used for RMII data transmission or reception. */ -typedef struct rmii_data_1b_t -{ - port data_0; /**< One bit data port for lower data line. */ - port data_1; /**< One bit data port for upper data line. */ -} rmii_data_1b_t; - - -/** Union representing a received data or control packet from the Ethernet MAC */ -typedef union rmii_data_port_t -{ - rmii_data_4b_t rmii_data_4b; /**< Four bit data port option */ - rmii_data_8b_t rmii_data_8b; /**< Four bit data port option. NOTE supported on Tx only */ - rmii_data_1b_t rmii_data_1b; /**< One bit data port option */ -} rmii_data_port_t; - +/** Struct containing the clock delay settings for the Rx and Tx pins. This is needed to adjust + * port timings to ensure that the data is captured with sufficient setup and hold margin. + * This is required due to the relatively fast 50 MHz clock. + * Please consult the documentation for further details and suggested settings. */ +typedef struct rmii_port_timing_t{ + unsigned clk_delay_tx_rising; + unsigned clk_delay_tx_falling; + unsigned clk_delay_rx_rising; + unsigned clk_delay_rx_falling; + unsigned pad_delay_rx; +}rmii_port_timing_t; /** 10/100 Mb/s real-time Ethernet MAC component to connect to an RMII interface. * @@ -691,12 +683,21 @@ typedef union rmii_data_port_t * \param c_tx_hp Streaming channel end for high priority transmit data * * \param p_clk RMII clock input port - * \param p_rxd Pointer to RMII RX data port union + * \param p_rxd_0 Port for data bit 0 (1 bit option) or entire port (4 bit option) + * \param p_rxd_1 Port for data bit 1 (1 bit option). Pass null if unused. + * \param rx_pin_map Which pins to use in 4 bit case. USE_LOWER_2B or USE_HIGHER_2B. Ignored if 1 bit ports used. * \param p_rxdv RMII RX data valid port * \param p_txen RMII TX enable port - * \param p_txd Pointer to RMII TX data port union + * \param p_txd_0 Port for data bit 0 (1 bit option) or entire port (4 or 8 bit option) + * \param p_txd_1 Port for data bit 1 (1 bit option). Pass null if unused. + * \param tx_pin_map Which pins to use in 4 bit case. USE_LOWER_2B or USE_HIGHER_2B. Ignored if 1 bit ports used. + * In the case of 8b port usage, the lower 16b word holds the position of the data bit 0 and + * the upper 16b word holds the position of data bit 1. Values 0..7 are valid. You can use the + * RMII_8B_PINS_INITIALISER(pos_0, pos_1) macro to initialise this value. * \param rxclk Clock used for RMII receive timing * \param txclk Clock used for RMII transmit timing + * \param port_timing Struct used for initialising the clock blocks to ensure setup and hold times are met + * * \param rx_bufsize_words The number of words to used for a receive buffer. * This should be at least 500 long words. * \param tx_bufsize_words The number of words to used for a transmit buffer. @@ -711,10 +712,14 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati SERVER_INTERFACE(ethernet_tx_if, i_tx_lp[n_tx_lp]), static_const_unsigned_t n_tx_lp, nullable_streaming_chanend_t c_rx_hp, nullable_streaming_chanend_t c_tx_hp, - in_port_t p_clk, rmii_data_port_t * unsafe p_rxd, in_port_t p_rxdv, - out_port_t p_txen, rmii_data_port_t * unsafe p_txd, + in_port_t p_clk, + port p_rxd_0, NULLABLE_RESOURCE(port, p_rxd_1), rmii_data_4b_pin_assignment_t rx_pin_map, + in_port_t p_rxdv, + out_port_t p_txen, + port p_txd_0, NULLABLE_RESOURCE(port, p_txd_1), rmii_data_4b_pin_assignment_t tx_pin_map, clock rxclk, clock txclk, + rmii_port_timing_t port_timing, static_const_unsigned_t rx_bufsize_words, static_const_unsigned_t tx_bufsize_words, enum ethernet_enable_shaper_t shaper_enabled); diff --git a/lib_ethernet/api/mii.h b/lib_ethernet/api/mii.h index efce769d..7fbc3f07 100644 --- a/lib_ethernet/api/mii.h +++ b/lib_ethernet/api/mii.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_h__ #define __mii_h__ diff --git a/lib_ethernet/api/smi.h b/lib_ethernet/api/smi.h index b3640244..a8d40640 100644 --- a/lib_ethernet/api/smi.h +++ b/lib_ethernet/api/smi.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _smi_h_ #define _smi_h_ @@ -8,24 +8,28 @@ #include "doxygen.h" // Sphynx Documentation Workarounds // SMI Registers -#define BASIC_CONTROL_REG 0x0 -#define BASIC_STATUS_REG 0x1 -#define PHY_ID1_REG 0x2 -#define PHY_ID2_REG 0x3 -#define AUTONEG_ADVERT_REG 0x4 -#define AUTONEG_LINK_REG 0x5 -#define AUTONEG_EXP_REG 0x6 -#define GIGE_CONTROL_REG 0x9 - -#define BASIC_CONTROL_LOOPBACK_BIT 14 -#define BASIC_CONTROL_100_MBPS_BIT 13 -#define BASIC_CONTROL_1000_MBPS_BIT 6 -#define BASIC_CONTROL_AUTONEG_EN_BIT 12 -#define BASIC_CONTROL_POWER_DOWN_BIT 11 -#define BASIC_CONTROL_RESTART_AUTONEG_BIT 9 -#define BASIC_CONTROL_FULL_DUPLEX_BIT 8 - -#define BASIC_STATUS_LINK_BIT 2 +#define BASIC_CONTROL_REG 0x0 +#define BASIC_STATUS_REG 0x1 +#define PHY_ID1_REG 0x2 +#define PHY_ID2_REG 0x3 +#define AUTONEG_ADVERT_REG 0x4 +#define AUTONEG_LINK_REG 0x5 +#define AUTONEG_EXP_REG 0x6 +#define GIGE_CONTROL_REG 0x9 +#define RMII_AND_STATUS_REG 0x17 +#define IO_CONFIG_1_REG 0x302 + +#define BASIC_CONTROL_LOOPBACK_BIT 14 +#define BASIC_CONTROL_100_MBPS_BIT 13 +#define BASIC_CONTROL_1000_MBPS_BIT 6 +#define BASIC_CONTROL_AUTONEG_EN_BIT 12 +#define BASIC_CONTROL_POWER_DOWN_BIT 11 +#define BASIC_CONTROL_RESTART_AUTONEG_BIT 9 +#define BASIC_CONTROL_FULL_DUPLEX_BIT 8 + +#define BASIC_STATUS_LINK_BIT 2 + +#define IO_CFG_CRS_RX_DV_BIT 8 #define AUTONEG_ADVERT_1000BASE_T_FULL_DUPLEX 9 #define AUTONEG_ADVERT_100BASE_TX_FULL_DUPLEX 8 @@ -86,10 +90,14 @@ void smi(SERVER_INTERFACE(smi_if, i_smi), /** SMI component that connects to an Ethernet PHY or switch via MDIO * on a shared multi-bit port. + * + * Important!! This version requires a pull-up resistor on MDC to function. * * This function implements a SMI component that connects to an * Ethernet PHY/ switch via MDIO/MDC connected on the same multi-bit port. * Interaction to the component is via the connected SMI interface. + * Unsed pins in the port are reserved and should be left unconnected or weakly + * pulled down. * * \param i_smi Client register read/write interface * \param p_smi The multi-bit port with MDIO/MDC pins diff --git a/lib_ethernet/lib_build_info.cmake b/lib_ethernet/lib_build_info.cmake index 2ed15d06..c7310078 100644 --- a/lib_ethernet/lib_build_info.cmake +++ b/lib_ethernet/lib_build_info.cmake @@ -15,7 +15,7 @@ set(LIB_COMPILER_FLAGS -g set(LIB_OPTIONAL_HEADERS ethernet_conf.h) -set(LIB_COMPILER_FLAGS_mii_master.xc ${LIB_COMPILER_FLAGS} -O3 -fschedule -g0 -mno-dual-issue) +set(LIB_COMPILER_FLAGS_mii_master.xc ${LIB_COMPILER_FLAGS} -O3 -fschedule -g -mno-dual-issue) set(LIB_COMPILER_FLAGS_macaddr_filter.xc ${LIB_COMPILER_FLAGS} -Wno-reinterpret-alignment) set(LIB_COMPILER_FLAGS_mii.xc ${LIB_COMPILER_FLAGS} -Wno-cast-align -Wno-unusual-code) set(LIB_COMPILER_FLAGS_mii_ethernet_mac.xc ${LIB_COMPILER_FLAGS} -Wno-cast-align -Wno-unusual-code) diff --git a/lib_ethernet/module_build_info b/lib_ethernet/module_build_info index 7a78e317..8ab7b9a7 100644 --- a/lib_ethernet/module_build_info +++ b/lib_ethernet/module_build_info @@ -14,7 +14,7 @@ OPTIONAL_HEADERS += ethernet_conf.h MODULE_XCC_FLAGS = $(XCC_FLAGS) -g -O3 -mno-dual-issue -XCC_FLAGS_mii_master.xc = $(XCC_FLAGS) -O3 -fschedule -g0 -mno-dual-issue +XCC_FLAGS_mii_master.xc = $(XCC_FLAGS) -O3 -fschedule -g -mno-dual-issue XCC_FLAGS_macaddr_filter.xc = $(MODULE_XCC_FLAGS) -Wno-reinterpret-alignment XCC_FLAGS_mii.xc = $(MODULE_XCC_FLAGS) -Wno-cast-align -Wno-unusual-code XCC_FLAGS_mii_ethernet_mac.xc = $(MODULE_XCC_FLAGS) -Wno-cast-align -Wno-unusual-code diff --git a/lib_ethernet/src/check_ifg_wait.h b/lib_ethernet/src/check_ifg_wait.h index e780f0da..5cbd43b6 100644 --- a/lib_ethernet/src/check_ifg_wait.h +++ b/lib_ethernet/src/check_ifg_wait.h @@ -1,4 +1,4 @@ -// Copyright 2024 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __check_ifg_wait_h__ #define __check_ifg_wait_h__ diff --git a/lib_ethernet/src/client_state.h b/lib_ethernet/src/client_state.h index 5cf95e2d..07bdd5d5 100644 --- a/lib_ethernet/src/client_state.h +++ b/lib_ethernet/src/client_state.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __client_state_h__ #define __client_state_h__ diff --git a/lib_ethernet/src/client_state.xc b/lib_ethernet/src/client_state.xc index 579e33c5..abfef6a4 100644 --- a/lib_ethernet/src/client_state.xc +++ b/lib_ethernet/src/client_state.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "client_state.h" diff --git a/lib_ethernet/src/default_ethernet_conf.h b/lib_ethernet/src/default_ethernet_conf.h index ca3abed1..9ef5f863 100644 --- a/lib_ethernet/src/default_ethernet_conf.h +++ b/lib_ethernet/src/default_ethernet_conf.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __default_ethernet_conf_h__ #define __default_ethernet_conf_h__ diff --git a/lib_ethernet/src/doxygen.h b/lib_ethernet/src/doxygen.h index 86bd6266..6c2edadc 100644 --- a/lib_ethernet/src/doxygen.h +++ b/lib_ethernet/src/doxygen.h @@ -1,4 +1,4 @@ -// Copyright 2024 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. // Contains macros and workarounds for both rendering sphynx docs (with XC) and compilation diff --git a/lib_ethernet/src/ethernet.xc b/lib_ethernet/src/ethernet.xc index 9bd87f61..e31028e7 100644 --- a/lib_ethernet/src/ethernet.xc +++ b/lib_ethernet/src/ethernet.xc @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "ethernet.h" #include "mii_impl.h" diff --git a/lib_ethernet/src/macaddr_filter.h b/lib_ethernet/src/macaddr_filter.h index b1e5364b..34c2a1bd 100644 --- a/lib_ethernet/src/macaddr_filter.h +++ b/lib_ethernet/src/macaddr_filter.h @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __macaddr_filter_h__ #define __macaddr_filter_h__ diff --git a/lib_ethernet/src/macaddr_filter.xc b/lib_ethernet/src/macaddr_filter.xc index e9ed2cbe..bbce24e4 100644 --- a/lib_ethernet/src/macaddr_filter.xc +++ b/lib_ethernet/src/macaddr_filter.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/lib_ethernet/src/macaddr_filter_hash.c b/lib_ethernet/src/macaddr_filter_hash.c index db7f61d0..c69f4605 100644 --- a/lib_ethernet/src/macaddr_filter_hash.c +++ b/lib_ethernet/src/macaddr_filter_hash.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/lib_ethernet/src/macaddr_filter_hash.h b/lib_ethernet/src/macaddr_filter_hash.h index c4872ea5..fe32c564 100644 --- a/lib_ethernet/src/macaddr_filter_hash.h +++ b/lib_ethernet/src/macaddr_filter_hash.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __macaddr_filter_hash_h__ #define __macaddr_filter_hash_h__ diff --git a/lib_ethernet/src/mii.xc b/lib_ethernet/src/mii.xc index 6b455a3e..fe8a9767 100644 --- a/lib_ethernet/src/mii.xc +++ b/lib_ethernet/src/mii.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/lib_ethernet/src/mii_buffering_defines.h b/lib_ethernet/src/mii_buffering_defines.h index 6f2e6ed9..625915de 100644 --- a/lib_ethernet/src/mii_buffering_defines.h +++ b/lib_ethernet/src/mii_buffering_defines.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_buffering_defines_h__ #define __mii_buffering_defines_h__ diff --git a/lib_ethernet/src/mii_common_lld.h b/lib_ethernet/src/mii_common_lld.h index 29410c8d..acdcc18f 100644 --- a/lib_ethernet/src/mii_common_lld.h +++ b/lib_ethernet/src/mii_common_lld.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _mii_common_lld_h_ #define _mii_common_lld_h_ diff --git a/lib_ethernet/src/mii_ethernet_mac.xc b/lib_ethernet/src/mii_ethernet_mac.xc index 837afe05..6f8625f0 100644 --- a/lib_ethernet/src/mii_ethernet_mac.xc +++ b/lib_ethernet/src/mii_ethernet_mac.xc @@ -160,7 +160,7 @@ static void mii_ethernet_aux(client mii_if i_mii, memcpy(r_mac_address, mac_address, sizeof mac_address); break; - case i_cfg[int i].set_macaddr(size_t ifnum, uint8_t r_mac_address[MACADDR_NUM_BYTES]): + case i_cfg[int i].set_macaddr(size_t ifnum, const uint8_t r_mac_address[MACADDR_NUM_BYTES]): memcpy(mac_address, r_mac_address, sizeof r_mac_address); break; @@ -221,6 +221,16 @@ static void mii_ethernet_aux(client mii_if i_mii, fail("Shaper not supported in standard MII Ethernet MAC"); break; + case i_cfg[int i].set_egress_qav_idle_slope_bps(size_t ifnum, unsigned bits_per_second): { + fail("Shaper not supported in standard MII Ethernet MAC"); + break; + } + + case i_cfg[int i].set_egress_qav_credit_limit(size_t ifnum, int payload_limit_bytes): { + fail("Shaper not supported in standard MII Ethernet MAC"); + break; + } + case i_cfg[int i].set_ingress_timestamp_latency(size_t ifnum, ethernet_speed_t speed, unsigned value): { fail("Timestamp correction not supported in standard MII Ethernet MAC"); break; diff --git a/lib_ethernet/src/mii_ethernet_rt_mac.xc b/lib_ethernet/src/mii_ethernet_rt_mac.xc index 6b81be4f..265fca2e 100644 --- a/lib_ethernet/src/mii_ethernet_rt_mac.xc +++ b/lib_ethernet/src/mii_ethernet_rt_mac.xc @@ -15,6 +15,7 @@ #include "print.h" #include "server_state.h" #include "rmii_rx_pins_exit.h" +#include "shaper.h" static inline unsigned int get_tile_id_from_chanend(chanend c) { @@ -322,7 +323,7 @@ unsafe void mii_ethernet_server(mii_mempool_t rx_mem, memcpy(r_mac_address, mac_address, sizeof mac_address); break; - case i_cfg[int i].set_macaddr(size_t ifnum, uint8_t r_mac_address[MACADDR_NUM_BYTES]): + case i_cfg[int i].set_macaddr(size_t ifnum, const uint8_t r_mac_address[MACADDR_NUM_BYTES]): memcpy(mac_address, r_mac_address, sizeof r_mac_address); break; @@ -404,11 +405,21 @@ unsafe void mii_ethernet_server(mii_mempool_t rx_mem, break; } + case i_cfg[int i].set_egress_qav_idle_slope_bps(size_t ifnum, unsigned bits_per_second): { + set_qav_idle_slope(p_port_state, bits_per_second); + break; + } + + case i_cfg[int i].set_egress_qav_credit_limit(size_t ifnum, int payload_limit_bytes): { + set_qav_credit_limit(p_port_state, payload_limit_bytes); + break; + } + case i_cfg[int i].set_ingress_timestamp_latency(size_t ifnum, ethernet_speed_t speed, unsigned value): { if (speed < 0 || speed >= NUM_ETHERNET_SPEEDS) { fail("Invalid Ethernet speed, must be a valid ethernet_speed_t enum value"); } - p_port_state->ingress_ts_latency[speed] = value / 10; + p_port_state->ingress_ts_latency[speed] = value / 10; // div by 10 to get to timer ticks from nanonseconds break; } diff --git a/lib_ethernet/src/mii_impl.h b/lib_ethernet/src/mii_impl.h index 893ac72d..3626ef10 100644 --- a/lib_ethernet/src/mii_impl.h +++ b/lib_ethernet/src/mii_impl.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_impl_h__ #define __mii_impl_h__ diff --git a/lib_ethernet/src/mii_lite_driver.h b/lib_ethernet/src/mii_lite_driver.h index bbf3c0c6..5e2cbb8e 100644 --- a/lib_ethernet/src/mii_lite_driver.h +++ b/lib_ethernet/src/mii_lite_driver.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_driver_h__ #define __mii_driver_h__ diff --git a/lib_ethernet/src/mii_lite_driver.xc b/lib_ethernet/src/mii_lite_driver.xc index 8dd2b1b5..eb8b4330 100644 --- a/lib_ethernet/src/mii_lite_driver.xc +++ b/lib_ethernet/src/mii_lite_driver.xc @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/lib_ethernet/src/mii_lite_interrupt.S b/lib_ethernet/src/mii_lite_interrupt.S index 14038ebc..6c520a80 100644 --- a/lib_ethernet/src/mii_lite_interrupt.S +++ b/lib_ethernet/src/mii_lite_interrupt.S @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .cc_top mii_lite_install_handler.func, mii_lite_install_handler diff --git a/lib_ethernet/src/mii_lite_lld.S b/lib_ethernet/src/mii_lite_lld.S index 013e368c..b0419b7b 100644 --- a/lib_ethernet/src/mii_lite_lld.S +++ b/lib_ethernet/src/mii_lite_lld.S @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. .cc_top mii_lite_lld.func, mii_lite_lld #include diff --git a/lib_ethernet/src/mii_lite_lld.h b/lib_ethernet/src/mii_lite_lld.h index 4f9309c5..0a0987b6 100644 --- a/lib_ethernet/src/mii_lite_lld.h +++ b/lib_ethernet/src/mii_lite_lld.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_lite_lld_h__ #define __mii_lite_lld_h__ diff --git a/lib_ethernet/src/mii_master.h b/lib_ethernet/src/mii_master.h index 8e4bf409..2774f680 100644 --- a/lib_ethernet/src/mii_master.h +++ b/lib_ethernet/src/mii_master.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_master_h__ #define __mii_master_h__ diff --git a/lib_ethernet/src/mii_master.xc b/lib_ethernet/src/mii_master.xc index 59d68e3b..6ecf24c9 100644 --- a/lib_ethernet/src/mii_master.xc +++ b/lib_ethernet/src/mii_master.xc @@ -1,4 +1,4 @@ -// Copyright 2013-2024 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "mii_master.h" #include @@ -13,6 +13,7 @@ #include "mii_common_lld.h" #include "string.h" #include "check_ifg_wait.h" +#include "shaper.h" #define QUOTEAUX(x) #x #define QUOTE(x) QUOTEAUX(x) @@ -471,9 +472,9 @@ unsafe void mii_master_tx_pins(mii_mempool_t tx_mem_lp, out buffered port:32 p_mii_txd, volatile ethernet_port_state_t * unsafe p_port_state) { - int credit = 0; // No. of bits allowed to send - int credit_time; - // Need one timer to be able to read at any time for the shaper + qav_state_t qav_state = {0, 0, 0}; // Set times and credit to zero so it can tx first frame + + // Need a timer to be able to read at any time for the shaper timer credit_tmr; // And a second timer to be enforcing the IFG gap hwtimer_t ifg_tmr; @@ -485,7 +486,8 @@ unsafe void mii_master_tx_pins(mii_mempool_t tx_mem_lp, enable_shaper = 0; if (ETHERNET_SUPPORT_HP_QUEUES && enable_shaper) { - credit_tmr :> credit_time; + credit_tmr :> qav_state.current_time; + qav_state.prev_time = qav_state.current_time; } ifg_tmr :> ifg_time; @@ -496,27 +498,15 @@ unsafe void mii_master_tx_pins(mii_mempool_t tx_mem_lp, mii_ts_queue_t *p_ts_queue = null; mii_mempool_t tx_mem = tx_mem_hp; - if (ETHERNET_SUPPORT_HP_QUEUES) + if (ETHERNET_SUPPORT_HP_QUEUES) { buf = mii_get_next_buf(packets_hp); + } if (enable_shaper) { - int prev_credit_time = credit_time; - credit_tmr :> credit_time; - - int elapsed = credit_time - prev_credit_time; - credit += elapsed * p_port_state->qav_idle_slope; // add bit budget since last transmission to credit. ticks * bits/tick = bits - - if (buf) { - if (credit < 0) { - buf = 0; // if out of credit drop this HP packet - } - } - else { - if (credit > 0) - credit = 0; - } + credit_tmr :> qav_state.current_time; + buf = shaper_do_idle_slope(buf, &qav_state, p_port_state); } - + if (!buf) { buf = mii_get_next_buf(packets_lp); p_ts_queue = &ts_queue; @@ -530,19 +520,15 @@ unsafe void mii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned time = mii_transmit_packet(tx_mem, buf, p_mii_txd, ifg_tmr, ifg_time, eof_time); - eof_time = ifg_time; // Setup the hardware timer to enforce the IFG + eof_time = ifg_time; ifg_time += MII_ETHERNET_IFS_AS_REF_CLOCK_COUNT; ifg_time += (buf->length & 0x3) * 8; + // Calculate the send slope (decrement credit) if enabled and was HP const int packet_is_high_priority = (p_ts_queue == null); if (enable_shaper && packet_is_high_priority) { - const int preamble_bytes = 8; - const int ifg_bytes = 96/8; - const int crc_bytes = 4; - int len = buf->length + preamble_bytes + ifg_bytes + crc_bytes; - // decrease credit by no. of bits transmitted - credit = credit - (len << (MII_CREDIT_FRACTIONAL_BITS+3)); // MII_CREDIT_FRACTIONAL_BITS+3 to convert from bytes to bits + shaper_do_send_slope(buf->length, &qav_state); } if (mii_get_and_dec_transmit_count(buf) == 0) { @@ -559,6 +545,6 @@ unsafe void mii_master_tx_pins(mii_mempool_t tx_mem_lp, mii_free_current(packets_hp); } } - } + } // while(1) } diff --git a/lib_ethernet/src/mii_ts_queue.c b/lib_ethernet/src/mii_ts_queue.c index 4a5dfb9d..9c8184a7 100644 --- a/lib_ethernet/src/mii_ts_queue.c +++ b/lib_ethernet/src/mii_ts_queue.c @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "mii_ts_queue.h" #include "mii_buffering.h" diff --git a/lib_ethernet/src/mii_ts_queue.h b/lib_ethernet/src/mii_ts_queue.h index 69f00791..113c855f 100644 --- a/lib_ethernet/src/mii_ts_queue.h +++ b/lib_ethernet/src/mii_ts_queue.h @@ -1,4 +1,4 @@ -// Copyright 2013-2021 XMOS LIMITED. +// Copyright 2013-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __mii_ts_queue_h__ #define __mii_ts_queue_h__ diff --git a/lib_ethernet/src/ntoh.h b/lib_ethernet/src/ntoh.h index 92e3579f..666abfea 100644 --- a/lib_ethernet/src/ntoh.h +++ b/lib_ethernet/src/ntoh.h @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __ntoh_h__ #define __ntoh_h__ diff --git a/lib_ethernet/src/rgmii.h b/lib_ethernet/src/rgmii.h index a11e9936..3ec9466a 100644 --- a/lib_ethernet/src/rgmii.h +++ b/lib_ethernet/src/rgmii.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef _RGMII_H_ #define _RGMII_H_ diff --git a/lib_ethernet/src/rgmii_10_100_master.h b/lib_ethernet/src/rgmii_10_100_master.h index 8887498f..6d21169b 100644 --- a/lib_ethernet/src/rgmii_10_100_master.h +++ b/lib_ethernet/src/rgmii_10_100_master.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __RGMII_10_100_MASTER_H__ #define __RGMII_10_100_MASTER_H__ diff --git a/lib_ethernet/src/rgmii_10_100_master.xc b/lib_ethernet/src/rgmii_10_100_master.xc index eed21c80..2a053a90 100644 --- a/lib_ethernet/src/rgmii_10_100_master.xc +++ b/lib_ethernet/src/rgmii_10_100_master.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/lib_ethernet/src/rgmii_buffering.h b/lib_ethernet/src/rgmii_buffering.h index 2a18d8ed..20c1588d 100644 --- a/lib_ethernet/src/rgmii_buffering.h +++ b/lib_ethernet/src/rgmii_buffering.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __RGMII_BUFFERING_H__ #define __RGMII_BUFFERING_H__ diff --git a/lib_ethernet/src/rgmii_buffering.xc b/lib_ethernet/src/rgmii_buffering.xc index fc26c2cc..65e6ad51 100644 --- a/lib_ethernet/src/rgmii_buffering.xc +++ b/lib_ethernet/src/rgmii_buffering.xc @@ -12,6 +12,7 @@ #include "xassert.h" #include "macaddr_filter_hash.h" #include "server_state.h" +#include "shaper.h" unsafe void notify_speed_change(int speed_change_ids[6]) { for (int i=0; i < 6; i++) { @@ -544,9 +545,9 @@ unsafe void rgmii_ethernet_tx_server(tx_client_state_t client_state_lp[n_tx_lp], int enable_shaper = p_port_state->qav_shaper_enabled; timer tmr; - int credit = 0; - int credit_time; - tmr :> credit_time; + qav_state_t qav_state = {0, 0, 0}; // Set times and credit to zero so it can tx first frame + tmr :> qav_state.current_time; + qav_state.prev_time = qav_state.current_time; int sender_count = 0; int work_pending = 0; @@ -648,63 +649,50 @@ unsafe void rgmii_ethernet_tx_server(tx_client_state_t client_state_lp[n_tx_lp], break; } - if (enable_shaper) { - int prev_credit_time = credit_time; - tmr :> credit_time; - - int elapsed = credit_time - prev_credit_time; - credit += elapsed * p_port_state->qav_idle_slope; - - if (buffers_used_empty(used_buffers_tx_hp)) { - // Keep the credit 0 when there are no high priority buffers - if (credit > 0) { - credit = 0; - } - } - } - + // This code handles fetching buffers using HP queues (if enabled) and doing the Qav shaper + mii_packet_t * unsafe buf = null; + int high_priority_packet = 0; + if(ETHERNET_SUPPORT_HP_QUEUES){ + high_priority_packet = !buffers_used_empty(used_buffers_tx_hp); // See if we have an HP packet ready + if(enable_shaper) { + // Cast "True" to mii_packet_t for idle_slope logic. + buf = (mii_packet_t * unsafe)high_priority_packet; + // Do shaper send sloper. buf will be nulled if there is not enough credit + tmr :> qav_state.current_time; + buf = shaper_do_idle_slope(buf, &qav_state, p_port_state); + high_priority_packet = (int) buf; // 0 if no credit, non-zero if credit + } // Shaper enabled + } // Queues enabled + + // Here, high_priority_packet flag will be set only if HP queues enabled, packet avaialable AND credit is good (if shaper enabled) + + // Do transmit logic if (work_pending && (sender_count < 2)) { - int packet_is_high_priority = 1; - mii_packet_t * unsafe buf = null; - - if (ETHERNET_SUPPORT_HP_QUEUES) { - if (enable_shaper) { - if (!buffers_used_empty(used_buffers_tx_hp)) { - // Once there is enough credit then take the next buffer - if (credit >= 0) { - buf = buffers_used_take(used_buffers_tx_hp, RGMII_MAC_BUFFER_COUNT_TX, 0); - } - } - } - else { - if (!buffers_used_empty(used_buffers_tx_hp)) { - buf = buffers_used_take(used_buffers_tx_hp, RGMII_MAC_BUFFER_COUNT_TX, 0); - } + if(high_priority_packet){ + // Get the packet + buf = buffers_used_take(used_buffers_tx_hp, RGMII_MAC_BUFFER_COUNT_TX, 0); + } else { + // No HP packet, check for a LP one instead + if (!buffers_used_empty(used_buffers_tx_lp)) { + buf = buffers_used_take(used_buffers_tx_lp, RGMII_MAC_BUFFER_COUNT_TX, 0); + high_priority_packet = 0; // We need this for shaper send slope later } } - if (!buf && !buffers_used_empty(used_buffers_tx_lp)) { - buf = buffers_used_take(used_buffers_tx_lp, RGMII_MAC_BUFFER_COUNT_TX, 0); - packet_is_high_priority = 0; - } - + // If we have any valid packet to transmit.. if (buf) { - // Send a pointer out to the outputter + // Send a pointer out to the outputter task. Packet en-route c_tx_to_mac <: buf; work_pending--; sender_count++; - if (enable_shaper && packet_is_high_priority) { - const int preamble_bytes = 8; - const int ifg_bytes = 96/8; - const int crc_bytes = 4; - int len = buf->length + preamble_bytes + ifg_bytes + crc_bytes; - credit = credit - (len << (MII_CREDIT_FRACTIONAL_BITS+3)); - } - } - } + if (enable_shaper && high_priority_packet) { + shaper_do_send_slope(buf->length, &qav_state); + } // shaper on and was HP + } // Have valid packet + } // Work pending and count < 2 - // Ensure there is always a high priority buffer + // Ensure there is always a high priority buffer for next loop if (!isnull(c_tx_hp) && (tx_buf_hp == null)) { tx_buf_hp = buffers_free_take(free_buffers_hp, 0); } @@ -747,7 +735,7 @@ void rgmii_ethernet_mac_config(server ethernet_cfg_if i_cfg[n], memcpy(r_mac_address, mac_address, sizeof mac_address); break; - case i_cfg[int i].set_macaddr(size_t ifnum, uint8_t r_mac_address[MACADDR_NUM_BYTES]): + case i_cfg[int i].set_macaddr(size_t ifnum, const uint8_t r_mac_address[MACADDR_NUM_BYTES]): memcpy(mac_address, r_mac_address, sizeof r_mac_address); break; @@ -816,6 +804,16 @@ void rgmii_ethernet_mac_config(server ethernet_cfg_if i_cfg[n], break; } + case i_cfg[int i].set_egress_qav_idle_slope_bps(size_t ifnum, unsigned bits_per_second): { + set_qav_idle_slope(p_port_state, bits_per_second); + break; + } + + case i_cfg[int i].set_egress_qav_credit_limit(size_t ifnum, int payload_limit_bytes): { + set_qav_credit_limit(p_port_state, payload_limit_bytes); + break; + } + case i_cfg[int i].set_ingress_timestamp_latency(size_t ifnum, ethernet_speed_t speed, unsigned value): { if (speed < 0 || speed >= NUM_ETHERNET_SPEEDS) { fail("Invalid Ethernet speed, must be a valid ethernet_speed_t enum value"); diff --git a/lib_ethernet/src/rgmii_buffering_c_support.c b/lib_ethernet/src/rgmii_buffering_c_support.c index 741b0cc2..39c5f1ee 100644 --- a/lib_ethernet/src/rgmii_buffering_c_support.c +++ b/lib_ethernet/src/rgmii_buffering_c_support.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "rgmii_buffering.h" diff --git a/lib_ethernet/src/rgmii_consts.h b/lib_ethernet/src/rgmii_consts.h index 99e2b8a5..23caa641 100644 --- a/lib_ethernet/src/rgmii_consts.h +++ b/lib_ethernet/src/rgmii_consts.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/lib_ethernet/src/rgmii_ethernet_mac.xc b/lib_ethernet/src/rgmii_ethernet_mac.xc index 62937f3b..3c874d73 100644 --- a/lib_ethernet/src/rgmii_ethernet_mac.xc +++ b/lib_ethernet/src/rgmii_ethernet_mac.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include "xassert.h" diff --git a/lib_ethernet/src/rgmii_rx_lld.S b/lib_ethernet/src/rgmii_rx_lld.S index 81269c76..69ea373e 100644 --- a/lib_ethernet/src/rgmii_rx_lld.S +++ b/lib_ethernet/src/rgmii_rx_lld.S @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "xs1.h" #include "mii_buffering_defines.h" diff --git a/lib_ethernet/src/rgmii_speed_handlers.S b/lib_ethernet/src/rgmii_speed_handlers.S index 462da9e1..970a7736 100644 --- a/lib_ethernet/src/rgmii_speed_handlers.S +++ b/lib_ethernet/src/rgmii_speed_handlers.S @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "xs1.h" diff --git a/lib_ethernet/src/rgmii_tx_lld.S b/lib_ethernet/src/rgmii_tx_lld.S index bee25de7..10ac86ba 100644 --- a/lib_ethernet/src/rgmii_tx_lld.S +++ b/lib_ethernet/src/rgmii_tx_lld.S @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "xs1.h" #include "mii_buffering_defines.h" diff --git a/lib_ethernet/src/rmii_ethernet_rt_mac.xc b/lib_ethernet/src/rmii_ethernet_rt_mac.xc index 3efaa589..c364648e 100644 --- a/lib_ethernet/src/rmii_ethernet_rt_mac.xc +++ b/lib_ethernet/src/rmii_ethernet_rt_mac.xc @@ -45,13 +45,16 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati nullable_streaming_chanend_t c_rx_hp, nullable_streaming_chanend_t c_tx_hp, in_port_t p_clk, - rmii_data_port_t * unsafe p_rxd, in_port_t p_rxdv, - out_port_t p_txen, rmii_data_port_t * unsafe p_txd, + port p_rxd_0, NULLABLE_RESOURCE(port, p_rxd_1), rmii_data_4b_pin_assignment_t rx_pin_map, + in_port_t p_rxdv, + out_port_t p_txen, + port p_txd_0, NULLABLE_RESOURCE(port, p_txd_1), rmii_data_4b_pin_assignment_t tx_pin_map, clock rxclk, clock txclk, + rmii_port_timing_t port_timing, static_const_unsigned_t rx_bufsize_words, static_const_unsigned_t tx_bufsize_words, - enum ethernet_enable_shaper_t enable_shaper) + enum ethernet_enable_shaper_t shaper_enabled) { // Establish types of data ports presented unsafe{ @@ -105,19 +108,18 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati in buffered port:32 * unsafe rx_data_1 = NULL; // Extract width and optionally which 4b pins to use - unsigned rx_port_width = ((unsigned)(p_rxd->rmii_data_1b.data_0) >> 16) & 0xff; - rmii_data_4b_pin_assignment_t rx_port_4b_pins = (rmii_data_4b_pin_assignment_t)(p_rxd->rmii_data_1b.data_1); + unsigned rx_port_width = ((unsigned)(p_rxd_0) >> 16) & 0xff; // Extract pointers to ports with correct port qualifiers and setup data pins switch(rx_port_width){ case 4: - rx_data_0 = enable_buffered_in_port((unsigned*)(&p_rxd->rmii_data_1b.data_0), 32); - rmii_master_init_rx_4b(p_clk, rx_data_0, p_rxdv, rxclk); + rx_data_0 = enable_buffered_in_port((unsigned*)(&p_rxd_0), 32); + rmii_master_init_rx_4b(p_clk, rx_data_0, p_rxdv, rxclk, port_timing); break; case 1: - rx_data_0 = enable_buffered_in_port((unsigned*)&p_rxd->rmii_data_1b.data_0, 32); - rx_data_1 = enable_buffered_in_port((unsigned*)&p_rxd->rmii_data_1b.data_1, 32); - rmii_master_init_rx_1b(p_clk, rx_data_0, rx_data_1, p_rxdv, rxclk); + rx_data_0 = enable_buffered_in_port((unsigned*)&p_rxd_0, 32); + rx_data_1 = enable_buffered_in_port((unsigned*)&p_rxd_1, 32); + rmii_master_init_rx_1b(p_clk, rx_data_0, rx_data_1, p_rxdv, rxclk, port_timing); break; default: fail("Invald port width for RMII Rx"); @@ -129,20 +131,18 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati out buffered port:32 * unsafe tx_data_1 = NULL; // Extract port info - unsigned tx_port_width = ((unsigned)(p_txd->rmii_data_1b.data_0) >> 16) & 0xff; - unsigned tx_pins_used = (unsigned)(p_txd->rmii_data_1b.data_1); - rmii_data_pin_assignment_t tx_port_pins = {tx_pins_used}; + unsigned tx_port_width = ((unsigned)(p_txd_0) >> 16) & 0xff; switch(tx_port_width){ case 8: case 4: - tx_data_0 = enable_buffered_out_port((unsigned*)(&p_txd->rmii_data_1b.data_0), 32); - rmii_master_init_tx_4b_8b(p_clk, tx_data_0, p_txen, txclk); + tx_data_0 = enable_buffered_out_port((unsigned*)(&p_txd_0), 32); + rmii_master_init_tx_4b(p_clk, tx_data_0, p_txen, txclk, port_timing); break; case 1: - tx_data_0 = enable_buffered_out_port((unsigned*)&p_txd->rmii_data_1b.data_0, 32); - tx_data_1 = enable_buffered_out_port((unsigned*)&p_txd->rmii_data_1b.data_1, 32); - rmii_master_init_tx_1b(p_clk, tx_data_0, tx_data_1, p_txen, txclk); + tx_data_0 = enable_buffered_out_port((unsigned*)&p_txd_0, 32); + tx_data_1 = enable_buffered_out_port((unsigned*)&p_txd_1, 32); + rmii_master_init_tx_1b(p_clk, tx_data_0, tx_data_1, p_txen, txclk, port_timing); break; default: fail("Invald port width for RMII Tx"); @@ -151,7 +151,7 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati // Setup server ethernet_port_state_t port_state; - init_server_port_state(port_state, enable_shaper == ETHERNET_ENABLE_SHAPER); + init_server_port_state(port_state, shaper_enabled == ETHERNET_ENABLE_SHAPER); ethernet_port_state_t * unsafe p_port_state = (ethernet_port_state_t * unsafe)&port_state; @@ -170,7 +170,7 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati p_rx_rdptr, p_rxdv, rx_data_0, - rx_port_4b_pins, + rx_pin_map, running_flag_ptr, c_rx_pins_exit); } else { @@ -194,7 +194,7 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati tx_port_width, tx_data_0, tx_data_1, - tx_port_pins, + tx_pin_map, txclk, p_port_state, running_flag_ptr); @@ -232,11 +232,11 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati stop_clock(rxclk); switch(rx_port_width){ case 4: - set_port_use_off(p_rxd->rmii_data_1b.data_0); + set_port_use_off(*rx_data_0); break; case 1: - set_port_use_off(p_rxd->rmii_data_1b.data_0); - set_port_use_off(p_rxd->rmii_data_1b.data_1); + set_port_use_off(*rx_data_0); + set_port_use_off(*rx_data_1); break; default: fail("Invald port width for RMII Rx"); @@ -247,11 +247,11 @@ void rmii_ethernet_rt_mac(SERVER_INTERFACE(ethernet_cfg_if, i_cfg[n_cfg]), stati stop_clock(txclk); switch(tx_port_width){ case 4: - set_port_use_off(p_txd->rmii_data_1b.data_0); + set_port_use_off(*tx_data_0); break; case 1: - set_port_use_off(p_txd->rmii_data_1b.data_0); - set_port_use_off(p_txd->rmii_data_1b.data_1); + set_port_use_off(*tx_data_0); + set_port_use_off(*tx_data_1); break; default: fail("Invald port width for RMII Tx"); diff --git a/lib_ethernet/src/rmii_master.h b/lib_ethernet/src/rmii_master.h index 4a36f46b..7b5d7b81 100644 --- a/lib_ethernet/src/rmii_master.h +++ b/lib_ethernet/src/rmii_master.h @@ -11,24 +11,33 @@ unsafe void rmii_master_init_rx_4b( in port p_clk, in buffered port:32 * unsafe rx_data, in port p_rxdv, - clock rxclk); + clock rxclk, + rmii_port_timing_t port_timing); unsafe void rmii_master_init_rx_1b( in port p_clk, in buffered port:32 * unsafe rx_data_0, in buffered port:32 * unsafe rx_data_1, in port p_rxdv, - clock rxclk); + clock rxclk, + rmii_port_timing_t port_timing); unsafe void rmii_master_init_tx_4b_8b( in port p_clk, out buffered port:32 * unsafe tx_data, out port p_txen, clock txclk); +unsafe void rmii_master_init_tx_4b_8b( in port p_clk, + out buffered port:32 * unsafe tx_data, + out port p_txen, + clock txclk, + rmii_port_timing_t port_timing); + unsafe void rmii_master_init_tx_1b( in port p_clk, out buffered port:32 * unsafe tx_data_0, out buffered port:32 * unsafe tx_data_1, out port p_txen, - clock txclk); + clock txclk, + rmii_port_timing_t port_timing); unsafe void rmii_master_rx_pins_4b(mii_mempool_t rx_mem, mii_packet_queue_t incoming_packets, diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index 31786877..7f3dd3bd 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -16,6 +16,7 @@ #include "string.h" #include "check_ifg_wait.h" #include "rmii_rx_pins_exit.h" +#include "shaper.h" #define QUOTEAUX(x) #x @@ -39,12 +40,6 @@ #define RECEIVE_PREAMBLE_WITH_SELECT_4b_ASM (1) // Call asm version of receive_full_preamble_4b_with_select #define RECEIVE_PREAMBLE_WITH_SELECT_1b_ASM (1) // Call asm version of receive_full_preamble_1b_with_select -// Timing tuning constants -// TODO THESE NEED SETTING UP -#define PAD_DELAY_RECEIVE 0 -#define PAD_DELAY_TRANSMIT 0 -#define CLK_DELAY_RECEIVE 0 -#define CLK_DELAY_TRANSMIT 0 // After-init delay (used at the end of rmii_init) #define PHY_INIT_DELAY 10000000 @@ -91,7 +86,7 @@ // These are overridable defines and can be overriden if the processor is running faster than what these were measured for. #ifndef RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b - #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b (8) // In reference timer ticks + #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b (7) // In reference timer ticks #endif #ifndef RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b @@ -119,7 +114,8 @@ static void rmii_master_init_rx_common(in port p_clk, in port p_rxdv, - clock rxclk){ + clock rxclk, + rmii_port_timing_t port_timing){ // Enable data valid. Data ports already on and configured to 32b buffered. set_port_use_on(p_rxdv); @@ -131,21 +127,25 @@ static void rmii_master_init_rx_common(in port p_clk, set_port_clock(p_rxdv, rxclk); // Connect to clock block set_clock_ready_src(rxclk, p_rxdv); // Enable data valid - set_clock_rise_delay(rxclk, CLK_DELAY_RECEIVE); - set_clock_fall_delay(rxclk, CLK_DELAY_RECEIVE); + set_clock_rise_delay(rxclk, port_timing.clk_delay_rx_rising); + set_clock_fall_delay(rxclk, port_timing.clk_delay_rx_falling); } -unsafe void rmii_master_init_rx_4b(in port p_clk, - in buffered port:32 * unsafe rx_data, - in port p_rxdv, - clock rxclk){ - rmii_master_init_rx_common(p_clk, p_rxdv, rxclk); +unsafe void rmii_master_init_rx_4b( in port p_clk, + in buffered port:32 * unsafe rx_data, + in port p_rxdv, + clock rxclk, + rmii_port_timing_t port_timing){ + rmii_master_init_rx_common(p_clk, p_rxdv, rxclk, port_timing); set_port_clock(*rx_data, rxclk); // Connect to rx clock block set_port_strobed(*rx_data); // Strobed slave (only accept data when valid asserted) set_port_slave(*rx_data); + set_pad_delay(*rx_data, port_timing.pad_delay_rx); + set_pad_delay(p_rxdv, port_timing.pad_delay_rx); + clearbuf(*rx_data); start_clock(rxclk); @@ -155,8 +155,9 @@ unsafe void rmii_master_init_rx_1b(in port p_clk, in buffered port:32 * unsafe rx_data_0, in buffered port:32 * unsafe rx_data_1, in port p_rxdv, - clock rxclk){ - rmii_master_init_rx_common(p_clk, p_rxdv, rxclk); + clock rxclk, + rmii_port_timing_t port_timing){ + rmii_master_init_rx_common(p_clk, p_rxdv, rxclk, port_timing); set_port_clock(*rx_data_0, rxclk); // Connect to rx clock block set_port_strobed(*rx_data_0); // Strobed slave (only accept data when valid asserted) @@ -166,6 +167,10 @@ unsafe void rmii_master_init_rx_1b(in port p_clk, set_port_strobed(*rx_data_1); set_port_slave(*rx_data_1); + set_pad_delay(*rx_data_0, port_timing.pad_delay_rx); + set_pad_delay(*rx_data_1, port_timing.pad_delay_rx); + set_pad_delay(p_rxdv, port_timing.pad_delay_rx); + clearbuf(*rx_data_0); clearbuf(*rx_data_1); @@ -174,7 +179,8 @@ unsafe void rmii_master_init_rx_1b(in port p_clk, static void rmii_master_init_tx_common( in port p_clk, out port p_txen, - clock txclk){ + clock txclk, + rmii_port_timing_t port_timing){ // Enable tx enable valid signal. Data ports already on and configured to 32b buffered. set_port_use_on(p_txen); p_txen <: 0; // Ensure is initially low so no spurious enables @@ -187,19 +193,20 @@ static void rmii_master_init_tx_common( in port p_clk, set_port_mode_ready(p_txen); set_port_clock(p_txen, txclk); - set_clock_rise_delay(txclk, CLK_DELAY_TRANSMIT); - set_clock_rise_delay(txclk, CLK_DELAY_TRANSMIT); + set_clock_rise_delay(txclk, port_timing.clk_delay_tx_rising); + set_clock_fall_delay(txclk, port_timing.clk_delay_tx_falling); } -unsafe void rmii_master_init_tx_4b_8b( in port p_clk, - out buffered port:32 * unsafe tx_data, - out port p_txen, - clock txclk){ +unsafe void rmii_master_init_tx_4b_8b(in port p_clk, + out buffered port:32 * unsafe tx_data, + out port p_txen, + clock txclk, + rmii_port_timing_t port_timing){ *tx_data <: 0; // Ensure lines are low sync(*tx_data); // And wait to empty. This ensures no spurious p_txen on init - rmii_master_init_tx_common(p_clk, p_txen, txclk); + rmii_master_init_tx_common(p_clk, p_txen, txclk, port_timing); // Configure so that tx_data controls the ready signal strobe set_port_strobed(*tx_data); @@ -214,13 +221,14 @@ unsafe void rmii_master_init_tx_1b( in port p_clk, out buffered port:32 * unsafe tx_data_0, out buffered port:32 * unsafe tx_data_1, out port p_txen, - clock txclk){ + clock txclk, + rmii_port_timing_t port_timing){ *tx_data_0 <: 0; // Ensure lines are low sync(*tx_data_0); // And wait to empty. This ensures no spurious p_txen on init *tx_data_1 <: 0; sync(*tx_data_1); - rmii_master_init_tx_common(p_clk, p_txen, txclk); + rmii_master_init_tx_common(p_clk, p_txen, txclk, port_timing); // Configure so that just tx_data_0 controls the read signal strobe // When we transmit we will ensure both port buffers are launched @@ -823,7 +831,7 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, if(wrap_size > 0){ first_chunk_size -= wrap_size; wrap_ptr = (unsigned *)*wrap_ptr; // Dereference wrap pointer to get start of wrap memory - printstrln("wrap_required"); + // printstrln("wrap_required"); } if (!MII_TX_TIMESTAMP_END_OF_PACKET && buf->timestamp_id) { @@ -1074,12 +1082,15 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, int credit = 0; int credit_time; + // Need one timer to be able to read at any time for the shaper timer credit_tmr; // And a second timer to be enforcing the IFG gap hwtimer_t ifg_tmr; unsigned ifg_time = 0; unsigned eof_time = 0; + + qav_state_t qav_state = {0, 0, 0}; // Set times and credit to zero so it can tx first frame unsigned enable_shaper = p_port_state->qav_shaper_enabled; // Lookup table for 8b transmit case @@ -1092,7 +1103,8 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, enable_shaper = 0; } if (ETHERNET_SUPPORT_HP_QUEUES && enable_shaper) { - credit_tmr :> credit_time; + credit_tmr :> qav_state.current_time; + qav_state.prev_time = qav_state.current_time; } ifg_tmr :> ifg_time; @@ -1107,21 +1119,8 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, } if (enable_shaper) { - int prev_credit_time = credit_time; - credit_tmr :> credit_time; - - int elapsed = credit_time - prev_credit_time; - credit += elapsed * p_port_state->qav_idle_slope; - - if (buf) { - if (credit < 0) { - buf = 0; - } - } else { - if (credit > 0) { - credit = 0; - } - } + credit_tmr :> qav_state.current_time; + buf = shaper_do_idle_slope(buf, &qav_state, p_port_state); } if (!buf) { @@ -1166,11 +1165,7 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, const int packet_is_high_priority = (p_ts_queue == null); if (enable_shaper && packet_is_high_priority) { - const int preamble_bytes = 8; - const int ifg_bytes = 96/8; - const int crc_bytes = 4; - int len = buf->length + preamble_bytes + ifg_bytes + crc_bytes; - credit = credit - (len << (MII_CREDIT_FRACTIONAL_BITS+3)); + shaper_do_send_slope(buf->length, &qav_state); } if (mii_get_and_dec_transmit_count(buf) == 0) { diff --git a/lib_ethernet/src/server_state.h b/lib_ethernet/src/server_state.h index c0c25dd0..cf5d62e3 100644 --- a/lib_ethernet/src/server_state.h +++ b/lib_ethernet/src/server_state.h @@ -6,8 +6,6 @@ #include "xccompat.h" #include "ethernet.h" -#define MII_CREDIT_FRACTIONAL_BITS 16 - // Server is shared for rmii/mii so pass in enum typedef enum phy_100mb_t { ETH_MAC_IF_MII = 0, @@ -21,6 +19,7 @@ typedef struct ethernet_port_state_t ethernet_speed_t link_speed; int qav_shaper_enabled; int qav_idle_slope; + int64_t qav_credit_limit; int ingress_ts_latency[NUM_ETHERNET_SPEEDS]; int egress_ts_latency[NUM_ETHERNET_SPEEDS]; } ethernet_port_state_t; diff --git a/lib_ethernet/src/server_state.xc b/lib_ethernet/src/server_state.xc index 09a5c8cf..5280d437 100644 --- a/lib_ethernet/src/server_state.xc +++ b/lib_ethernet/src/server_state.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "server_state.h" #include "string.h" @@ -8,5 +8,6 @@ void init_server_port_state(ethernet_port_state_t &state, int enable_qav_shaper) memset(&state, 0, sizeof(ethernet_port_state_t)); state.link_state = ETHERNET_LINK_DOWN; state.qav_shaper_enabled = enable_qav_shaper; - state.qav_idle_slope = (11< +#include "ethernet.h" +#include "server_state.h" +#include "mii_buffering.h" +#include "xassert.h" +#include + +#ifndef ETHERNET_SUPPORT_TRAFFIC_SHAPER_CREDIT_LIMIT +#define ETHERNET_SUPPORT_TRAFFIC_SHAPER_CREDIT_LIMIT 1 +#endif + +static const unsigned preamble_bytes = 8; +static const unsigned crc_bytes = 4; +static const unsigned ifg_bytes = 96 / 8; + +/** Type which stores the Qav credit based shaper state */ +typedef struct qav_state_t{ + int prev_time; /**< Previous time in hw_timer ticks (10ns, 32b). */ + int current_time; /**< Current time in hw_timer ticks (10ns, 32b). */ + int credit; /**< Credit in MII_CREDIT_FRACTIONAL_BITS fractional format. */ +} qav_state_t; + +/** Sets the Qav idle slope in units of bits per second. + * + * \param port_state Pointer to the port state to be modified + * \param limit_bps The idle slope setting in bits per second + * + */ +void set_qav_idle_slope(volatile ethernet_port_state_t * unsafe port_state, unsigned limit_bps); + + +/** Sets the Qav credit limit in units of frame size byte + * + * \param port_state Pointer to the port state to be modified + * \param limit_bytes The credit limit in units of payload size in bytes to set as a credit limit, + * not including preamble, CRC and IFG. Set to 0 for no limit (default) + * + */ +void set_qav_credit_limit(volatile ethernet_port_state_t * unsafe port_state, int payload_limit_bytes); + + +/** Performs the idle slope calculation for MII and RMII MACs. + * + * Adds credits based on time since last HP packet and bit rate. If credit after calculation is above zero then + * the HP packet is allowed to be transmitted. If credit is negative then the return buffer + * is null so that it waits until credit is sufficient. + * + * The credit is optionally limited to hiCredit which is initialised by set_qav_credit_limit() + * + * \param hp_buf Pointer to packet to be transmitted + * \param qav_state Pointer to Qav state struct + * \param port_state Pointer to MAC port state + * + * \returns The hp_buf passed to it which is either maintained if credit is sufficient + * or NULL if credit is not sufficient. + */ +static inline mii_packet_t * unsafe shaper_do_idle_slope(mii_packet_t * unsafe hp_buf, + qav_state_t * unsafe qav_state, + volatile ethernet_port_state_t * unsafe port_state){ + unsafe{ + // Make unsigned so we don't get negative numbers + uint32_t elapsed_ticks = qav_state->current_time - qav_state->prev_time; + +#if ETHERNET_SUPPORT_TRAFFIC_SHAPER_CREDIT_LIMIT + // We have to use 64b maths here as we will overflow in just 437 microseconds + int64_t credit64 = (int64_t)elapsed_ticks * (int64_t)port_state->qav_idle_slope + (int64_t)qav_state->credit; + + // cast qav_credit_limit as saves a cycle. It will never be more that 16b long anyway + if((unsigned)port_state->qav_credit_limit){ + // Apply limit + if(credit64 > port_state->qav_credit_limit) + { + // printf("limited credit: %lld limit: %lld\n", credit64, port_state->qav_credit_limit); + credit64 = port_state->qav_credit_limit; + } + } else { + // Clip to max_int to avoid overflow + const int64_t max_int = 0x7fffffff; + if(credit64 > max_int){ + // printf("clip credit: %lld limit: %lld\n", credit64, port_state->qav_credit_limit); + credit64 = max_int; + } + } + qav_state->credit = (int)credit64; +#else + // This is the old code from <4.0.0 + qav_state->credit += elapsed_ticks * (int)port_state->qav_idle_slope; // add bit budget since last transmission to credit. ticks * bits/tick = bits +#endif + + // If valid hp buffer + if (hp_buf) { + if (qav_state->credit < 0) { + hp_buf = 0; // if out of credit drop this HP packet + } + } + else + // Buffer invalid, no HP packet so reset credit as per Annex L of Qav + { + if (qav_state->credit > 0){ + qav_state->credit = 0; // HP ready to send next time + } + } + + // Ensure we keep track of state (time since last packet) for next time + qav_state->prev_time = qav_state->current_time; + + return hp_buf; + } +} + + +static inline void shaper_do_send_slope(int len_bytes, qav_state_t * unsafe qav_state){ + unsafe{ + // Calculate number of additional byte slots on wire over the payload + const int overhead_bytes = preamble_bytes + crc_bytes + ifg_bytes; + + // decrease credit by no. of bits transmitted, scaled by MII_CREDIT_FRACTIONAL_BITS + // Note we don't need to check for overflow here as we will only be here if credit + // was previously positive (worst case 1) and len_bytes <= ETHERNET_MAX_PACKET_SIZE so + // will only decrement by roughly -(1<<29) + qav_state->credit = qav_state->credit - ((len_bytes + overhead_bytes) << (MII_CREDIT_FRACTIONAL_BITS + 3)); // MII_CREDIT_FRACTIONAL_BITS+3 to convert from bytes to bits + } +} + + +#endif // __shaper_h__ diff --git a/lib_ethernet/src/shaper.xc b/lib_ethernet/src/shaper.xc new file mode 100644 index 00000000..16aeb2c5 --- /dev/null +++ b/lib_ethernet/src/shaper.xc @@ -0,0 +1,33 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include "ethernet.h" +#include "shaper.h" + +void set_qav_idle_slope(volatile ethernet_port_state_t * unsafe port_state, unsigned limit_bps) +{ + unsafe{ + // Scale for 16.16 representation + uint64_t slope = ((uint64_t)limit_bps) << MII_CREDIT_FRACTIONAL_BITS; + + // Calculate bits per tick per bit in 16.16 + slope = slope / XS1_TIMER_HZ; + + port_state->qav_idle_slope = (unsigned)slope; + } +} + + +void set_qav_credit_limit(volatile ethernet_port_state_t * unsafe port_state, int payload_limit_bytes) +{ + unsafe{ + int64_t max_interferring_frame_bits = (preamble_bytes + payload_limit_bytes + crc_bytes + ifg_bytes) * 8; + if(payload_limit_bytes > 0){ + port_state->qav_credit_limit = max_interferring_frame_bits; + } + else + { + // No limit + port_state->qav_credit_limit = 0; + } + } +} diff --git a/lib_ethernet/src/smi.xc b/lib_ethernet/src/smi.xc index dbbaf951..7aede85a 100644 --- a/lib_ethernet/src/smi.xc +++ b/lib_ethernet/src/smi.xc @@ -1,4 +1,4 @@ -// Copyright 2011-2021 XMOS LIMITED. +// Copyright 2011-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -8,82 +8,140 @@ // Constants used in calls to smi_bit_shift and smi_reg. -#define SMI_READ 1 -#define SMI_WRITE 0 +#define SMI_READ 1 +#define SMI_WRITE 0 -#ifndef SMI_MDIO_RESET_MUX -#define SMI_MDIO_RESET_MUX 0 -#endif +#define MMD_ACCESS_CONTROL 0xD +#define MMD_ACCESS_DATA 0xE -#ifndef SMI_MDIO_REST -#define SMI_MDIO_REST 0 -#endif +// This is setup to support a tVAL time of 300ns (LAN8710A) for read +// using the single port version. If using the two port version you +// may double this bit clock and still meet timing. +// Or if your PHY is faster than 300ns tVAL you may increase the BIT_CLOCK_HZ +#define SMI_BIT_CLOCK_HZ 1660000 +#define SMI_BIT_TIME_TICKS (XS1_TIMER_HZ / SMI_BIT_CLOCK_HZ) +#define SMI_HALF_BIT_TIME_TICKS (SMI_BIT_TIME_TICKS / 2) -#define MMD_ACCESS_CONTROL 0xD -#define MMD_ACCESS_DATA 0xE +/* Notes about single port implementation. +Sampling just before the rising edge is the correct thing to do for maximum performance +(min cycle time) so we do that in the two port version. +In the single port version we have a challenge due to not having full control of direction +on individual port bits: +The PHY will start driving data from the MDC rising edge so we must be high-Z on the data port +at that point (and clock gets pulled high externally). +We run slow enough so that the data presented on MDIO is valid by time of the falling edge, which is then sampled. +However we then need to drive the low clock to complete the cycle which we do actively. +We also drive back the read data so that it doesn't contend. This effectively doubles the +required cycle time but does work. +*/ // Shift in a number of data bits to or from the SMI port static int smi_bit_shift(port p_smi_mdc, port ?p_smi_mdio, - unsigned data, - unsigned count, unsigned inning, + unsigned write_data, + unsigned count, unsigned is_read, unsigned SMI_MDIO_BIT, unsigned SMI_MDC_BIT) { - int i = count, data_bit = 0, t; - - if (isnull(p_smi_mdio)) { - p_smi_mdc :> void @ t; - if (inning) { - while (i != 0) { - i--; - p_smi_mdc @ (t + 30) :> data_bit; - data_bit &= (1 << SMI_MDIO_BIT); - if (SMI_MDIO_RESET_MUX) - data_bit |= SMI_MDIO_REST; - p_smi_mdc <: data_bit; - data = (data << 1) | (data_bit >> SMI_MDIO_BIT); - p_smi_mdc @ (t + 60) <: 1 << SMI_MDC_BIT | data_bit; - p_smi_mdc :> void; - t += 60; - } - p_smi_mdc @ (t+30) :> void; - } else { - while (i != 0) { - i--; - data_bit = ((data >> i) & 1) << SMI_MDIO_BIT; - if (SMI_MDIO_RESET_MUX) - data_bit |= SMI_MDIO_REST; - p_smi_mdc @ (t + 30) <: data_bit; - p_smi_mdc @ (t + 60) <: 1 << SMI_MDC_BIT | data_bit; - t += 60; - } - p_smi_mdc @ (t+30) <: 1 << SMI_MDC_BIT | data_bit; - } - return data; + timer t; + int t_trigger; + + unsigned read_data = 0; + + // Single port version. Note this requires that MDC is pulled up externally. + if (isnull(p_smi_mdio)) { + // Start of MDC clock cycle + // port Hi-Z. Clock will rise via pull-up, data is Hi-Z + t :> t_trigger; + p_smi_mdc :> void; + if (is_read) { + while (count != 0) { + count--; + // Wait til half cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + + // Read port with PHY driven data bit just before falling edge of clock + unsigned port_data; + p_smi_mdc :> port_data; + port_data &= ~(1 << SMI_MDC_BIT); // clear clock bit to zero + // Assert clock low and drive back previously read data (to avoid contention) + p_smi_mdc <: port_data; // drive back data and assert clock low + read_data |= (port_data >> SMI_MDIO_BIT) << count; + + // Wait til end of cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + + // At end of cycle allow clock to be pulled high, data is Hi-Z + p_smi_mdc :> void; + } } - else { - p_smi_mdc <: ~0 @ t; - while (i != 0) { - i--; - p_smi_mdc @ (t+30) <: 0; - if (!inning) { - int data_bit; - data_bit = ((data >> i) & 1) << SMI_MDIO_BIT; - if (SMI_MDIO_RESET_MUX) - data_bit |= SMI_MDIO_REST; - p_smi_mdio <: data_bit; - } - p_smi_mdc @ (t+60) <: ~0; - if (inning) { - p_smi_mdio :> data_bit; - data_bit = data_bit >> SMI_MDIO_BIT; - data = (data << 1) | data_bit; - } - t += 60; + else + // Write + { + // port is hi-z so MDC is pulled high, data undriven + while (count != 0) { + count--; + unsigned data_bit = ((write_data >> count) & 1) << SMI_MDIO_BIT; + + // Wait til half cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + // drive required data bit and MDC low halfway through + p_smi_mdc <: data_bit; + + // Wait til end of cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + + // Continue to drive data bit and clock high at end of cycle + p_smi_mdc <: 1 << SMI_MDC_BIT | data_bit; } - p_smi_mdc @ (t+30) <: ~0; - return data; } + + return read_data; + } + else + // Two port version + { + // Clock high + t :> t_trigger; + p_smi_mdc <: 1; + while(count != 0){ + count--; + + // Wait til half cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + // Falling edge and data assert or hi-z + if(is_read){ + p_smi_mdio :> void; // Hi-Z + } + else + { + unsigned data_bit = (write_data >> count) & 1; + p_smi_mdio <: data_bit; + } + p_smi_mdc <: 0; + + // Wait til end of cycle + t_trigger += SMI_HALF_BIT_TIME_TICKS; + t when timerafter(t_trigger) :> void; + if(is_read){ + unsigned data_bit; + p_smi_mdio :> data_bit; + read_data |= (data_bit << count); + } + else + { + // Keep previous data bit asserted + } + // Rising edge + p_smi_mdc <: 1; + } // count != 0 + + return read_data; + } // Two port } @@ -91,44 +149,45 @@ static int smi_bit_shift(port p_smi_mdc, port ?p_smi_mdio, void smi(server interface smi_if i, port p_smi_mdio, port p_smi_mdc) { - if (SMI_MDIO_RESET_MUX) { - timer tmr; - int t; - p_smi_mdio <: 0x0; - tmr :> t;tmr when timerafter(t+100000) :> void; - p_smi_mdio <: SMI_MDIO_REST; - } p_smi_mdc <: 1; while (1) { select { case i.read_reg(uint8_t phy_addr, uint8_t reg_addr) -> uint16_t res: - int inning = 1; + int is_read = 1; int val; // Register access: lots of 1111, then a code (read/write), phy address, // register, and a turn-around, then data. smi_bit_shift(p_smi_mdc, p_smi_mdio, 0xffffffff, 32, SMI_WRITE, 0, 0); // Preamble - smi_bit_shift(p_smi_mdc, p_smi_mdio, (5+inning) << 10 | phy_addr << 5 | reg_addr, + smi_bit_shift(p_smi_mdc, p_smi_mdio, (5+is_read) << 10 | phy_addr << 5 | reg_addr, 14, SMI_WRITE, 0, 0); - smi_bit_shift(p_smi_mdc, p_smi_mdio, 2, 2, inning, + smi_bit_shift(p_smi_mdc, p_smi_mdio, 2, 2, is_read, 0, 0); - res = smi_bit_shift(p_smi_mdc, p_smi_mdio, val, 16, inning, 0, 0); + res = smi_bit_shift(p_smi_mdc, p_smi_mdio, val, 16, is_read, 0, 0); + + // Ensure MDIO is pull high at end after 100ns at end of transaction + delay_ticks(10); + p_smi_mdio :> void; break; case i.write_reg(uint8_t phy_addr, uint8_t reg_addr, uint16_t val): - int inning = 0; + int is_read = 0; // Register access: lots of 1111, then a code (read/write), phy address, // register, and a turn-around, then data. smi_bit_shift(p_smi_mdc, p_smi_mdio, 0xffffffff, 32, SMI_WRITE, 0, 0); // Preamble - smi_bit_shift(p_smi_mdc, p_smi_mdio, (5+inning) << 10 | phy_addr << 5 | reg_addr, + smi_bit_shift(p_smi_mdc, p_smi_mdio, (5+is_read) << 10 | phy_addr << 5 | reg_addr, 14, SMI_WRITE, 0, 0); - smi_bit_shift(p_smi_mdc, p_smi_mdio, 2, 2, inning, + smi_bit_shift(p_smi_mdc, p_smi_mdio, 2, 2, is_read, 0, 0); - (void) smi_bit_shift(p_smi_mdc, p_smi_mdio, val, 16, inning, 0, 0); + (void) smi_bit_shift(p_smi_mdc, p_smi_mdio, val, 16, is_read, 0, 0); + + // Ensure MDIO is pull high at end after 100ns at end of transaction + delay_ticks(10); + p_smi_mdio :> void; break; } } @@ -138,44 +197,43 @@ void smi(server interface smi_if i, void smi_singleport(server interface smi_if i, port p_smi, unsigned SMI_MDIO_BIT, unsigned SMI_MDC_BIT) { - if (SMI_MDIO_RESET_MUX) { - timer tmr; - int t; - p_smi <: 0x0; - tmr :> t;tmr when timerafter(t+100000) :> void; - p_smi <: SMI_MDIO_REST; - } p_smi <: 1 << SMI_MDC_BIT; while (1) { select { case i.read_reg(uint8_t phy_addr, uint8_t reg_addr) -> uint16_t res: - int inning = 1; + int is_read = 1; int val; // Register access: lots of 1111, then a code (read/write), phy address, // register, and a turn-around, then data. smi_bit_shift(p_smi, null, 0xffffffff, 32, SMI_WRITE, SMI_MDIO_BIT, SMI_MDC_BIT); // Preamble - smi_bit_shift(p_smi, null, (5+inning) << 10 | phy_addr << 5 | reg_addr, + smi_bit_shift(p_smi, null, (5+is_read) << 10 | phy_addr << 5 | reg_addr, 14, SMI_WRITE, SMI_MDIO_BIT, SMI_MDC_BIT); - smi_bit_shift(p_smi, null, 2, 2, inning, + smi_bit_shift(p_smi, null, 2, 2, is_read, SMI_MDIO_BIT, SMI_MDC_BIT); - res = smi_bit_shift(p_smi, null, val, 16, inning, SMI_MDIO_BIT, SMI_MDC_BIT); + res = smi_bit_shift(p_smi, null, val, 16, is_read, SMI_MDIO_BIT, SMI_MDC_BIT); + + // port already high so MDC and MDIO will be pulled high break; case i.write_reg(uint8_t phy_addr, uint8_t reg_addr, uint16_t val): - int inning = 0; + int is_read = 0; // Register access: lots of 1111, then a code (read/write), phy address, // register, and a turn-around, then data. smi_bit_shift(p_smi, null, 0xffffffff, 32, SMI_WRITE, SMI_MDIO_BIT, SMI_MDC_BIT); // Preamble - smi_bit_shift(p_smi, null, (5+inning) << 10 | phy_addr << 5 | reg_addr, + smi_bit_shift(p_smi, null, (5+is_read) << 10 | phy_addr << 5 | reg_addr, 14, SMI_WRITE, SMI_MDIO_BIT, SMI_MDC_BIT); - smi_bit_shift(p_smi, null, 2, 2, inning, + smi_bit_shift(p_smi, null, 2, 2, is_read, SMI_MDIO_BIT, SMI_MDC_BIT); - (void) smi_bit_shift(p_smi, null, val, 16, inning, SMI_MDIO_BIT, SMI_MDC_BIT); + (void) smi_bit_shift(p_smi, null, val, 16, is_read, SMI_MDIO_BIT, SMI_MDC_BIT); + + // Ensure MDIO is pull high at end after 100ns at end of transaction + delay_ticks(10); + p_smi :> void; break; } } diff --git a/python/setup.py b/python/setup.py index 6e58e4be..bb8d7e3c 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,4 +1,4 @@ -# Copyright 2020-2024 XMOS LIMITED. +# Copyright 2020-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import setuptools diff --git a/requirements.txt b/requirements.txt index da1a0412..9ef24564 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,7 @@ flake8==7.0.0 pytest==8.2.2 pytest-xdist==3.6.1 filelock==3.16.1 +bitstring==4.3.0 # Development dependencies # diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c6844c1c..33bbacf4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,4 +20,6 @@ add_subdirectory(test_rx_queues) add_subdirectory(test_check_ifg_wait) add_subdirectory(test_rmii_timing) add_subdirectory(test_rmii_restart) +add_subdirectory(test_smi) +add_subdirectory(test_do_idle_slope_unit) diff --git a/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt new file mode 100644 index 00000000..85326168 --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_bringup_xk_eth_xu316_dual_100m) + +set(APP_HW_TARGET xk-eth-xu316-dual-100m.xn) + +set(APP_DEPENDENT_MODULES lib_ethernet + lib_otpinfo + "lib_board_support(feature/xk_eth_xu316_dual_100m)") +set(APP_PCA_ENABLE ON) + +set(COMPILER_FLAGS_COMMON -g + -report + -DDEBUG_PRINT_ENABLE=1 + -Os + -Wno-reinterpret-alignment + -DBOARD_SUPPORT_BOARD=XK_ETH_XU316_DUAL_100M) + +set(APP_COMPILER_FLAGS_PHY_1 -DPHY1=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_PHY_1_SINGLE_SMI -DPHY1=1 -DSINGLE_SMI=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_PHY_2 -DPHY2=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_PHY_2_8B_TX -DPHY2=1 -DPHY2_8B_TX=1 ${COMPILER_FLAGS_COMMON}) + +set(APP_XSCOPE_SRCS src/config.xscope) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +XMOS_REGISTER_APP() diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/config.xscope b/tests/bringup_xk_eth_xu316_dual_100m/src/config.xscope new file mode 100644 index 00000000..1cef58cc --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/config.xscope @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h new file mode 100644 index 00000000..c11b413a --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h @@ -0,0 +1,16 @@ +// Copyright 2013-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#ifndef __icmp_h__ +#define __icmp_h__ +#include +#include + + +[[combinable]] +void icmp_server(client ethernet_cfg_if cfg, + client ethernet_rx_if rx, + client ethernet_tx_if tx, + const unsigned char ip_address[4], + otp_ports_t &otp_ports); + +#endif // __icmp_h__ diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc new file mode 100644 index 00000000..87b82dac --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc @@ -0,0 +1,298 @@ +// Copyright 2013-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include +#include +#include +#include + +static unsigned short checksum_ip(const unsigned char frame[34]) +{ + int i; + unsigned accum = 0; + + for (i = 14; i < 34; i += 2) + { + accum += *((const uint16_t*)(frame + i)); + } + + // Fold carry into 16bits + while (accum >> 16) + { + accum = (accum & 0xFFFF) + (accum >> 16); + } + + accum = byterev(~accum) >> 16; + + return accum; +} + + +static int build_arp_response(unsigned char rxbuf[64], + unsigned char txbuf[64], + const unsigned char own_mac_addr[MACADDR_NUM_BYTES], + const unsigned char own_ip_addr[4]) +{ + unsigned word; + unsigned char byte; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + byte = rxbuf[22+i]; + txbuf[i] = byte; + txbuf[32 + i] = byte; + } + word = ((const unsigned int *) rxbuf)[7]; + for (int i = 0; i < 4; i++) + { + txbuf[38 + i] = word & 0xFF; + word >>= 8; + } + + txbuf[28] = own_ip_addr[0]; + txbuf[29] = own_ip_addr[1]; + txbuf[30] = own_ip_addr[2]; + txbuf[31] = own_ip_addr[3]; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[22 + i] = own_mac_addr[i]; + txbuf[6 + i] = own_mac_addr[i]; + } + txbuf[12] = 0x08; + txbuf[13] = 0x06; + txbuf[14] = 0x00; + txbuf[15] = 0x01; + txbuf[16] = 0x08; + txbuf[17] = 0x00; + txbuf[18] = 0x06; + txbuf[19] = 0x04; + txbuf[20] = 0x00; + txbuf[21] = 0x02; + + // Typically 48 bytes (94 for IPv6) + for (int i = 42; i < 64; i++) + { + txbuf[i] = 0x00; + } + + return 64; +} + + +static int is_valid_arp_packet(const unsigned char rxbuf[nbytes], + unsigned nbytes, + const unsigned char own_ip_addr[4]) +{ + if (rxbuf[12] != 0x08 || rxbuf[13] != 0x06) + return 0; + + debug_printf("ARP packet received\n"); + + if (((const unsigned int *) rxbuf)[3] != 0x01000608) + { + debug_printf("Invalid et_htype\n"); + return 0; + } + if (((const unsigned int *) rxbuf)[4] != 0x04060008) + { + debug_printf("Invalid ptype_hlen\n"); + return 0; + } + if ((((const unsigned int *) rxbuf)[5] & 0xFFFF) != 0x0100) + { + debug_printf("Not a request\n"); + return 0; + } + for (int i = 0; i < 4; i++) + { + if (rxbuf[38 + i] != own_ip_addr[i]) + { + debug_printf("Not for us\n"); + return 0; + } + } + + return 1; +} + + +static int build_icmp_response(unsigned char rxbuf[], unsigned char txbuf[], + const unsigned char own_mac_addr[MACADDR_NUM_BYTES], + const unsigned char own_ip_addr[4]) +{ + unsigned icmp_checksum; + int datalen; + int totallen; + const int ttl = 0x40; + int pad; + + // Precomputed empty IP header checksum (inverted, bytereversed and shifted right) + unsigned ip_checksum = 0x0185; + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[i] = rxbuf[6 + i]; + } + for (int i = 0; i < 4; i++) + { + txbuf[30 + i] = rxbuf[26 + i]; + } + icmp_checksum = byterev(((const unsigned int *) rxbuf)[9]) >> 16; + for (int i = 0; i < 4; i++) + { + txbuf[38 + i] = rxbuf[38 + i]; + } + totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; + datalen = totallen - 28; + for (int i = 0; i < datalen; i++) + { + txbuf[42 + i] = rxbuf[42+i]; + } + + for (int i = 0; i < MACADDR_NUM_BYTES; i++) + { + txbuf[6 + i] = own_mac_addr[i]; + } + ((unsigned int *) txbuf)[3] = 0x00450008; + totallen = byterev(28 + datalen) >> 16; + ((unsigned int *) txbuf)[4] = totallen; + ip_checksum += totallen; + ((unsigned int *) txbuf)[5] = 0x01000000 | (ttl << 16); + ((unsigned int *) txbuf)[6] = 0; + for (int i = 0; i < 4; i++) + { + txbuf[26 + i] = own_ip_addr[i]; + } + ip_checksum += (own_ip_addr[0] | own_ip_addr[1] << 8); + ip_checksum += (own_ip_addr[2] | own_ip_addr[3] << 8); + ip_checksum += txbuf[30] | (txbuf[31] << 8); + ip_checksum += txbuf[32] | (txbuf[33] << 8); + + txbuf[34] = 0x00; + txbuf[35] = 0x00; + + icmp_checksum = (icmp_checksum + 0x0800); + icmp_checksum += icmp_checksum >> 16; + txbuf[36] = icmp_checksum >> 8; + txbuf[37] = icmp_checksum & 0xFF; + + while (ip_checksum >> 16) + { + ip_checksum = (ip_checksum & 0xFFFF) + (ip_checksum >> 16); + } + ip_checksum = byterev(~ip_checksum) >> 16; + txbuf[24] = ip_checksum >> 8; + txbuf[25] = ip_checksum & 0xFF; + + for (pad = 42 + datalen; pad < 64; pad++) + { + txbuf[pad] = 0x00; + } + return pad; +} + + +static int is_valid_icmp_packet(const unsigned char rxbuf[nbytes], + unsigned nbytes, + const unsigned char own_ip_addr[4]) +{ + unsigned totallen; + if (rxbuf[23] != 0x01) + return 0; + + debug_printf("ICMP packet received\n"); + + if (((const unsigned int *) rxbuf)[3] != 0x00450008) + { + debug_printf("Invalid et_ver_hdrl_tos\n"); + return 0; + } + if ((((const unsigned int *) rxbuf)[8] >> 16) != 0x0008) + { + debug_printf("Invalid type_code\n"); + return 0; + } + for (int i = 0; i < 4; i++) + { + if (rxbuf[30 + i] != own_ip_addr[i]) + { + debug_printf("Not for us\n"); + return 0; + } + } + + totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; + if (nbytes > 60 && nbytes != totallen + 14) + { + debug_printf("Invalid size (nbytes:%d, totallen:%d)\n", nbytes, totallen+14); + return 0; + } + if (checksum_ip(rxbuf) != 0) + { + debug_printf("Bad checksum\n"); + return 0; + } + + return 1; +} + +[[combinable]] +void icmp_server(client ethernet_cfg_if cfg, + client ethernet_rx_if rx, + client ethernet_tx_if tx, + const unsigned char ip_address[4], + otp_ports_t &otp_ports) +{ + unsigned char mac_address[MACADDR_NUM_BYTES]; + ethernet_macaddr_filter_t macaddr_filter; + + // Get the mac address from OTP and set it in the ethernet component + otp_board_info_get_mac(otp_ports, 0, mac_address); + + size_t index = rx.get_index(); + cfg.set_macaddr(0, mac_address); + + memcpy(macaddr_filter.addr, mac_address, sizeof(mac_address)); + cfg.add_macaddr_filter(index, 0, macaddr_filter); + + // Add broadcast filter + memset(macaddr_filter.addr, 0xff, MACADDR_NUM_BYTES); + cfg.add_macaddr_filter(index, 0, macaddr_filter); + + // Only allow ARP and IP packets to the app + cfg.add_ethertype_filter(index, 0x0806); + cfg.add_ethertype_filter(index, 0x0800); + + debug_printf("Test started\n"); + while (1) + { + select { + case rx.packet_ready(): + unsigned char rxbuf[ETHERNET_MAX_PACKET_SIZE]; + unsigned char txbuf[ETHERNET_MAX_PACKET_SIZE]; + ethernet_packet_info_t packet_info; + rx.get_packet(packet_info, rxbuf, ETHERNET_MAX_PACKET_SIZE); + + if (packet_info.type != ETH_DATA) + continue; + + if (is_valid_arp_packet(rxbuf, packet_info.len, ip_address)) + { + int len = build_arp_response(rxbuf, txbuf, mac_address, ip_address); + tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES); + debug_printf("ARP response sent\n"); + } + else if (is_valid_icmp_packet(rxbuf, packet_info.len, ip_address)) + { + int len = build_icmp_response(rxbuf, txbuf, mac_address, ip_address); + tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES); + debug_printf("ICMP response sent\n"); + } + break; + } + } +} + + diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc new file mode 100644 index 00000000..0e4db813 --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc @@ -0,0 +1,127 @@ +// Copyright 2014-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. +#include +#include +#include "otp_board_info.h" +#include "ethernet.h" +#include "icmp.h" +#include "smi.h" +#include "xk_eth_xu316_dual_100m/board.h" +#include "debug_print.h" + +// Shared +port p_eth_clk = CLK_50M; +port p_smi_mdio = MDIO; +port p_smi_mdc = MDC; +#define MDC_BIT 2 +#define MDIO_BIT 3 +port p_smi_mdc_mdio = MDC_MDIO_4B; + + +#if PHY1 +port p_eth_rxd_0 = PHY_1_RXD_4B; +#define p_eth_rxd_1 null +#define RX_PINS USE_UPPER_2B +port p_eth_txd_0 = PHY_1_TXD_4B; +#define p_eth_txd_1 null +#define TX_PINS USE_UPPER_2B +port p_eth_rxdv = PHY_1_RXDV; +port p_eth_txen = PHY_1_TX_EN; +clock eth_rxclk = on tile[0]: XS1_CLKBLK_1; +clock eth_txclk = on tile[0]: XS1_CLKBLK_2; +#define PHY_ADDR 0x05 +#endif + +#if PHY2 +port p_eth_rxd_0 = PHY_2_RXD_1B_0; +port p_eth_rxd_1 = PHY_2_RXD_1B_1; +#define RX_PINS 0 +#if PHY2_USE_8B +#define TX8_BIT_0 7 +#define TX8_BIT_1 8 +#define TX_PINS ((TX8_BIT_0 << 16) | (TX8_BIT_1)) +port p_eth_txd_0 = PHY_2_TXD_8B; +#define p_eth_txd_1 null +#else +port p_eth_txd_0 = PHY_2_TXD_1B_0; +port p_eth_txd_1 = PHY_2_TXD_1B_1; +#define TX_PINS 0 +#endif +#define PHY_ADDR 0x07 +port p_eth_rxdv = PHY_2_RXDV; +port p_eth_txen = PHY_2_TX_EN; +clock eth_rxclk = on tile[0]: XS1_CLKBLK_1; +clock eth_txclk = on tile[0]: XS1_CLKBLK_2; +#endif + + +// These ports are for accessing the OTP memory +otp_ports_t otp_ports = on tile[0]: OTP_PORTS_INITIALIZER; + +static unsigned char ip_address[4] = {192, 168, 1, 178}; + +// An enum to manage the array of connections from the ethernet component +// to its clients. +enum eth_clients { + ETH_TO_ICMP, + NUM_ETH_CLIENTS +}; + +enum cfg_clients { + CFG_TO_ICMP, + CFG_TO_PHY_DRIVER, + NUM_CFG_CLIENTS +}; + +void put_mac_ports_in_hiz(port p_eth_rxd_0, port p_eth_rxd_1, port p_eth_txd_0, port p_eth_txd_1, port p_eth_rxdv, port p_eth_txen){ + p_eth_rxd_0 :> int _; + if(!isnull(p_eth_rxd_1)) p_eth_rxd_1 :> int _; + p_eth_txd_0 :> int _; + if(!isnull(p_eth_txd_1)) p_eth_txd_1 :> int _; + p_eth_rxdv :> int _; + p_eth_txen :> int _; +} + +int main() +{ + ethernet_cfg_if i_cfg[NUM_CFG_CLIENTS]; + ethernet_rx_if i_rx[NUM_ETH_CLIENTS]; + ethernet_tx_if i_tx[NUM_ETH_CLIENTS]; + smi_if i_smi; + + par { + on tile[0]: { + // To ensure PHY pin boot straps are read correctly at exit from reset + put_mac_ports_in_hiz(p_eth_rxd_0, p_eth_rxd_1, p_eth_txd_0, p_eth_txd_1, p_eth_rxdv, p_eth_txen); + delay_milliseconds(5); // Wait until PHY has come out of reset + rmii_ethernet_rt_mac( i_cfg, NUM_CFG_CLIENTS, + i_rx, NUM_ETH_CLIENTS, + i_tx, NUM_ETH_CLIENTS, + null, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); + } + on tile[1]: dp83826e_phy_driver(i_smi, i_cfg[CFG_TO_PHY_DRIVER], PHY_ADDR); + +#if SINGLE_SMI + on tile[1]: smi_singleport(i_smi, p_smi_mdc_mdio, MDIO_BIT, MDC_BIT); +#else + on tile[1]: smi(i_smi, p_smi_mdio, p_smi_mdc); +#endif + on tile[0]: icmp_server(i_cfg[CFG_TO_ICMP], + i_rx[ETH_TO_ICMP], i_tx[ETH_TO_ICMP], + ip_address, otp_ports); + } + return 0; +} diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/xk-eth-xu316-dual-100m.xn b/tests/bringup_xk_eth_xu316_dual_100m/src/xk-eth-xu316-dual-100m.xn new file mode 100644 index 00000000..424762d1 --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/xk-eth-xu316-dual-100m.xn @@ -0,0 +1,92 @@ + + + Board + xk_eth_xu316_dual_100m + + tileref tile[2] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/conftest.py b/tests/conftest.py index 0962a7d1..7bbb6eb9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2024 XMOS LIMITED. +# Copyright 2024-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import pytest import random diff --git a/tests/include/control.h b/tests/include/control.h index a4a491a4..55014d63 100644 --- a/tests/include/control.h +++ b/tests/include/control.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __CONTROL_H__ diff --git a/tests/include/control.xc b/tests/include/control.xc index 77e1e2be..cbd6e17a 100644 --- a/tests/include/control.xc +++ b/tests/include/control.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "control.h" diff --git a/tests/include/helpers.h b/tests/include/helpers.h index b76c7efe..ae3899c8 100644 --- a/tests/include/helpers.h +++ b/tests/include/helpers.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __HELPERS_H__ #define __HELPERS_H__ diff --git a/tests/include/helpers.xc b/tests/include/helpers.xc index feafb297..052c57e7 100644 --- a/tests/include/helpers.xc +++ b/tests/include/helpers.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "helpers.h" diff --git a/tests/include/ports.h b/tests/include/ports.h index 02a5a244..29ba4d5c 100644 --- a/tests/include/ports.h +++ b/tests/include/ports.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __ports_h__ #define __ports_h__ @@ -26,5 +26,8 @@ clock eth_txclk = on tile[0]: XS1_CLKBLK_2; port p_smi_mdio = on tile[0]: XS1_PORT_1M; port p_smi_mdc = on tile[0]: XS1_PORT_1N; +port p_smi_mdc_mdio = on tile[0]: XS1_PORT_4B; +port p_phy_rst_n = on tile[0]: XS1_PORT_4A; + #endif // __ports_h__ diff --git a/tests/include/ports_rmii.h b/tests/include/ports_rmii.h index a023ec0d..a583a193 100644 --- a/tests/include/ports_rmii.h +++ b/tests/include/ports_rmii.h @@ -1,4 +1,4 @@ -// Copyright 2024 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __ports_rmii_h__ #define __ports_rmii_h__ @@ -26,14 +26,18 @@ #error Both RX_USE_LOWER_2B and RX_USE_UPPER_2B are 0 when RX_WIDTH is 4 #endif +port p_eth_rxd_0 = on tile[0]:XS1_PORT_4A; +#define p_eth_rxd_1 null #if RX_USE_LOWER_2B -rmii_data_port_t p_eth_rxd = {{on tile[0]:XS1_PORT_4A, USE_LOWER_2B}}; + #define RX_PINS USE_LOWER_2B #elif RX_USE_UPPER_2B -rmii_data_port_t p_eth_rxd = {{on tile[0]:XS1_PORT_4A, USE_UPPER_2B}}; + #define RX_PINS USE_UPPER_2B #endif #elif RX_WIDTH == 1 -rmii_data_port_t p_eth_rxd = {{on tile[0]:XS1_PORT_1A, XS1_PORT_1B}}; +port p_eth_rxd_0 = on tile[0]:XS1_PORT_1A; +port p_eth_rxd_1 = on tile[0]:XS1_PORT_1B; +#define RX_PINS 0 #else #error invalid RX_WIDTH #endif @@ -48,14 +52,18 @@ rmii_data_port_t p_eth_rxd = {{on tile[0]:XS1_PORT_1A, XS1_PORT_1B}}; #error Both TX_USE_LOWER_2B and TX_USE_UPPER_2B are 0 when TX_WIDTH is 4 #endif +port p_eth_txd_0 = on tile[0]:XS1_PORT_4B; +#define p_eth_txd_1 null #if TX_USE_LOWER_2B - rmii_data_port_t p_eth_txd = {{on tile[0]:XS1_PORT_4B, USE_LOWER_2B}}; + #define TX_PINS USE_LOWER_2B #elif TX_USE_UPPER_2B - rmii_data_port_t p_eth_txd = {{on tile[0]:XS1_PORT_4B, USE_UPPER_2B}}; + #define TX_PINS USE_UPPER_2B #endif #elif TX_WIDTH == 1 -rmii_data_port_t p_eth_txd = {{on tile[0]:XS1_PORT_1C, XS1_PORT_1D}}; +port p_eth_txd_0 = on tile[0]:XS1_PORT_1C; +port p_eth_txd_1 = on tile[0]:XS1_PORT_1D; +#define TX_PINS 0 #else #error invalid TX_WIDTH #endif @@ -68,4 +76,7 @@ port p_test_ctrl = on tile[0]: XS1_PORT_1M; clock eth_rxclk = on tile[0]: XS1_CLKBLK_1; clock eth_txclk = on tile[0]: XS1_CLKBLK_2; +//TODO tune me +rmii_port_timing_t port_timing = {0, 0, 0, 0, 0}; + #endif diff --git a/tests/include/random.h b/tests/include/random.h index 71028513..1a827dfe 100644 --- a/tests/include/random.h +++ b/tests/include/random.h @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __RANDOM_H__ #define __RANDOM_H__ diff --git a/tests/include/random.xc b/tests/include/random.xc index f24230f2..aae6cfb5 100644 --- a/tests/include/random.xc +++ b/tests/include/random.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "random.h" #include diff --git a/tests/include/random_init.c b/tests/include/random_init.c index 0638c0d5..b6d25342 100644 --- a/tests/include/random_init.c +++ b/tests/include/random_init.c @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include "random.h" #include diff --git a/tests/mii_clock.py b/tests/mii_clock.py index 0dad6811..58d6ad2d 100644 --- a/tests/mii_clock.py +++ b/tests/mii_clock.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px import sys diff --git a/tests/mii_packet.py b/tests/mii_packet.py index 1f8c890e..f3d9f530 100644 --- a/tests/mii_packet.py +++ b/tests/mii_packet.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/mii_phy.py b/tests/mii_phy.py index e199d626..5c073314 100644 --- a/tests/mii_phy.py +++ b/tests/mii_phy.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random import Pyxsim as px diff --git a/tests/rgmii_phy.py b/tests/rgmii_phy.py index 0437906c..fc810611 100644 --- a/tests/rgmii_phy.py +++ b/tests/rgmii_phy.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random import Pyxsim as px diff --git a/tests/rmii_phy.py b/tests/rmii_phy.py index c67cf9b3..3339397c 100644 --- a/tests/rmii_phy.py +++ b/tests/rmii_phy.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random import Pyxsim as px diff --git a/tests/smi.py b/tests/smi.py new file mode 100644 index 00000000..5d6c7d85 --- /dev/null +++ b/tests/smi.py @@ -0,0 +1,335 @@ +# Copyright 2025 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import Pyxsim as px +from bitstring import BitArray, BitStream + +VERBOSE = False + +class smi_master_checker(px.SimThread): + """" + This simulator thread will act as SMI slave and check any transactions + sent by the master. + """ + + def __init__(self, mdc_port, mdio_port, rst_n_port, expected_speed_hz, tx_data=[], mdc_mdio_bit_pos=None): + # ports and data + self._mdc_port = mdc_port + self._mdio_port = mdio_port + self._mdc_mdio_bit_pos = mdc_mdio_bit_pos + self._rst_n_port = rst_n_port + + # Data to send + self._tx_data =[[int(bit) for bit in BitArray(uint=tx_word, length=16).bin] for tx_word in tx_data] + + # Bit rate + self._expected_speed_hz = expected_speed_hz + + # These values are used to simulate pull-ups + self._external_mdc_value = 1 + self._external_mdio_value = 1 + + # pin states + self._mdc_change_time = None + self._mdio_change_time = None + self._last_mdio_change_time = None + self._mdc_value = 0 + self._mdio_value = 0 + + # Test state + self._test_running = False + + self._reset_smi_state_machine() + + print("Checking SMI: MDC=%s, MDIO=%s RST_N=%s" % (self._mdc_port, self._mdio_port, self._rst_n_port)) + + def _reset_smi_state_machine(self): + # State of transaction + self._bit_num = 0 + self._bit_times = [] + self._prev_fall_time = None + + # Packet info + self._data = [] # Running data accumulator + self._state = "idle" + self._preamble = None + self._start_of_frame = None + self._op_code = None + self._phy_addr = None + self._reg_addr = None + self._turnaround = None + self._read_data = None + self._write_data = None + + def _calculate_ave_bit_time(self): + # we have 63 periods in between the bit times here (fencepost thing) not 64 + max_bit_time = max(self._bit_times) + min_bit_time = min(self._bit_times) + ave_bit_time = sum(self._bit_times)/self._bit_num + + if VERBOSE: + print(f"Average bit time: {ave_bit_time/1e6:.2f}ns ({1e9/ave_bit_time:.2f}MHz)") + print(f"Min bit time: {min_bit_time/1e6:.2f}ns ({1e9/min_bit_time:.2f}MHz)") + print(f"Max bit time: {max_bit_time/1e6:.2f}ns ({1e9/max_bit_time:.2f}MHz)") + + max_bit_freq_hz = 1e15 / min_bit_time + if(max_bit_freq_hz > self._expected_speed_hz * 1.07): # Allow some wiggle + self.error(f"Max MDC rate {max_bit_freq_hz} higher than expected {self._expected_speed_hz}") + + def error(self, str): + print("ERROR: %s @ %s" % (str, self.xsi.get_time())) + + # We use this helper to implement a virtual pull-up resistor + def read_port(self, port, external_value): + driving = self.xsi.is_port_driving(port) + if driving: + value = self.xsi.sample_port_pins(port) + else: + value = external_value + # Maintain the weak external drive + self.xsi.drive_port_pins(port, external_value) + # print("READ {}: drive {}, val: {} (ext: {}) @ {}".format(port, driving, value, external_value, self.xsi.get_time())) + return value + + def read_mdc_value(self): + return self.read_port(self._mdc_port, self._external_mdc_value) + + def read_mdio_value(self): + return self.read_port(self._mdio_port, self._external_mdio_value) + + def read_rst_n_value(self): + port_val = self.xsi.sample_port_pins(self._rst_n_port) + if port_val & 0x1: + return 1 + else: + return 0 + + def drive_mdio(self, value): + # Cache the value that is currently being driven + self._external_mdio_value = value + self.xsi.drive_port_pins(self._mdio_port, value) + + def wait_for_change(self): + """ Wait for either the MDIO/MDC port to change and return which one it was. + Need to also maintain the drive of any value set by the user. + """ + mdc_changed = False + mdio_changed = False + + mdc_value = self._mdc_value + mdio_value = self._mdio_value + + # The value might already not be the same if both signals transitioned + # simultaneously previously + new_mdc_value = self.read_mdc_value() + new_mdio_value = self.read_mdio_value() + while new_mdc_value == mdc_value and new_mdio_value == mdio_value: + self.wait_for_port_pins_change([self._mdc_port, self._mdio_port, self._rst_n_port]) + + # Logic to exit test when reset driven low by FW + if self._test_running and self.xsi.sample_port_pins(self._rst_n_port) == 0x0: + self.xsi.terminate() + if not self._test_running and self.xsi.sample_port_pins(self._rst_n_port) == 0xf: + self._test_running = True + + new_mdc_value = self.read_mdc_value() + new_mdio_value = self.read_mdio_value() + + time_now = self.xsi.get_time() + + if VERBOSE: + print(f"wait_for_change mdc:{mdc_value}, mdio:{mdio_value} -> mdc:{new_mdc_value}, mdio:{new_mdio_value} @ {time_now}") + + + # Check to see if we are in reset. If so, ignore all and reset state machine + if self.read_rst_n_value() == 0: + self._reset_smi_state_machine() + self._mdc_value = new_mdc_value + self._mdio_value = new_mdio_value + + return mdc_changed, mdio_changed + + + # MDC changed + if mdc_value != new_mdc_value: + mdc_changed = True + + self._mdc_change_time = time_now + self._mdc_value = new_mdc_value + + # Record the time of the rising edges + if new_mdc_value == 1: + fall_time = self.xsi.get_time() + if self._prev_fall_time is not None: + self._bit_times.append(fall_time - self._prev_fall_time) + self._prev_fall_time = fall_time + + # MDIO changed - don't detect simultaneous changes and have the clock be higher priority if they do. + if not mdc_changed and (mdio_value != new_mdio_value): + mdio_changed = True + self._last_mdio_change_time = self._mdio_change_time + self._mdio_change_time = time_now + self._mdio_value = new_mdio_value + + # if new_mdc_value == 0: + # self.check_data_valid_time(time_now - self._mdc_change_time) + + return mdc_changed, mdio_changed + + # This all happens on the rising edge + def decode_frame_on_rising(self): + # start of transaction + if self._bit_num == 0: + self._state = "preamble" + + # end of preamble + elif self._bit_num == 31: + self._preamble = self._data + self._data = [] + if self._preamble != [1] * 32: + self.error(f"Invalid preamble: {self._preamble}") + self._state = "start_of_frame" + + # end of SoF + elif self._bit_num == 33: + self._start_of_frame = self._data + self._data = [] + if self._preamble != [1] * 32: + self.error(f"Invalid start_of_frame: {self._start_of_frame}") + self._state = "op_code" + + # end of opcode + elif self._bit_num == 35: + self._op_code = self._data + self._data = [] + + # read + if self._op_code == [1, 0]: + if(len(self._tx_data) < 1): + self.error("Run out of data to transmit / SMI read") + self._tx_word = self._tx_data[0] + del self._tx_data[0] + # write + elif self._op_code == [0, 1]: + pass + else: + self.error(f"Invalid opcode: {self._op_code}") + self._state = "phy_address" + + # end of phy_address + elif self._bit_num == 40: + self._phy_addr = self._data + self._data = [] + self._state = "reg_address" + + #end of reg address + elif self._bit_num == 45: + self._reg_addr = self._data + self._data = [] + self._state = "turnaround" + + # Turnaround + elif self._bit_num == 47: + self._turnaround = self._data + self._data = [] + if self._op_code == [1, 0]: + self._state = "read" + elif self._op_code == [0, 1]: + self._state = "write" + else: + self._state = "invalid_op_code" + + elif self._bit_num == 63: + if self._state == "write": + self._write_data = self._data + self._data = [] + written_data = BitArray(self._write_data).uint + print(f"DUT WRITE: 0x{written_data:x}") + + + elif self._state == "read": + pass + + self._calculate_ave_bit_time() + self._reset_smi_state_machine() + self._bit_num = -1 + + elif self._bit_num > 63: + self.error("Bit number exceed 63") + + + def drive_frame_on_rising(self): + # Start read + if self._bit_num >= 46 and self._state == "read": + value = self._tx_word[0] + self.drive_mdio(value); + # print(f"sending mdio: {value} {self.xsi.get_time()/1e9:.2f}us driving: {self.xsi.is_port_driving(self._mdio_port)}") + del self._tx_word[0] + + def move_to_next_state(self, mdc_changed, mdio_changed): + if mdc_changed: + # Rising edge of MDC + if self._mdc_value == 1: + # print(f"Got MDIO bit {self._bit_num}: {self._mdio_value} at time {self._mdc_change_time}") + self._data.append(self._mdio_value) + self.decode_frame_on_rising() + self.drive_frame_on_rising() + self._bit_num += 1 + + if self._mdc_value == 0: + pass # Do nothing on falling edge + + + def run(self): + # Simulate external pullup + self.drive_mdio(1) + + self._mdc_value = self.read_mdc_value() + self._mdio_value = self.read_mdio_value() + + # self.wait_for_stopped() + + self._tx_data_index = 0 + + + while True: + mdc_changed, mdio_changed = self.wait_for_change() + + if mdc_changed and mdio_changed: + self.error("Unsupported having MDC & MDIO changing simultaneously") + + self.move_to_next_state(mdc_changed, mdio_changed) + + +# Will make an SMI packet. If write_data is not specified we assume a read. +# Data is returned as a BitArray - get that as 1s and 0s using header.bin and wr_data.bin +def smi_make_packet(phy_address, reg_address, write_data=None): + if phy_address > 0x1f or reg_address > 0x1f: + print(f"Error: phy address 0x{phy_address:x} and reg address 0x{reg_address:x} must be less than 31") + + preamble = BitArray(bin='1' * 32) + sof = BitArray(bin='01') + opcode = BitArray(bin='10' if write_data == None else '01') + phy_addr = BitArray(uint=phy_address, length=5) + reg_addr = BitArray(uint=reg_address, length=5) + + header = preamble + sof + opcode + phy_addr + reg_addr + + # TODO debug only + print(opcode.bin) + print(phy_addr) + print(reg_addr) + print(header.bin) + + # Note we need a two cycle Hi-Z turnaround between header and read or write data + + print(write_data) + if write_data is None: + wr_data = None + else: + # Data is always 16bits + if write_data > 0xffff: + print(f"Error: write_data 0x{write_data} must be 16 bits") + wr_data = BitArray(uint=write_data, length=16) + + return header, wr_data \ No newline at end of file diff --git a/tests/test_4_1_1.py b/tests/test_4_1_1.py index 21a17b6d..d2ef884b 100644 --- a/tests/test_4_1_1.py +++ b/tests/test_4_1_1.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_3.py b/tests/test_4_1_3.py index e1fa6b22..591c677b 100644 --- a/tests/test_4_1_3.py +++ b/tests/test_4_1_3.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_4.py b/tests/test_4_1_4.py index 095be4df..0be701a8 100644 --- a/tests/test_4_1_4.py +++ b/tests/test_4_1_4.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_5.py b/tests/test_4_1_5.py index 20cedbaf..cf92071c 100644 --- a/tests/test_4_1_5.py +++ b/tests/test_4_1_5.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_6.py b/tests/test_4_1_6.py index c7c3a9e7..240e5f9b 100644 --- a/tests/test_4_1_6.py +++ b/tests/test_4_1_6.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_7.py b/tests/test_4_1_7.py index 29dffb66..2c9884fd 100644 --- a/tests/test_4_1_7.py +++ b/tests/test_4_1_7.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_8.py b/tests/test_4_1_8.py index 35b0dc0d..0973ee97 100644 --- a/tests/test_4_1_8.py +++ b/tests/test_4_1_8.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_1_9.py b/tests/test_4_1_9.py index ac124a0b..cd9bdcf2 100644 --- a/tests/test_4_1_9.py +++ b/tests/test_4_1_9.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_2_4.py b/tests/test_4_2_4.py index a329a0af..632a9593 100644 --- a/tests/test_4_2_4.py +++ b/tests/test_4_2_4.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_4_2_5.py b/tests/test_4_2_5.py index 38bea0bd..fde11477 100644 --- a/tests/test_4_2_5.py +++ b/tests/test_4_2_5.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_appdata.py b/tests/test_appdata.py index 7fa6b5db..a2f06a03 100644 --- a/tests/test_appdata.py +++ b/tests/test_appdata.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import os diff --git a/tests/test_appdata/src/main.xc b/tests/test_appdata/src/main.xc index f82ae4cd..eb9c5359 100644 --- a/tests/test_appdata/src/main.xc +++ b/tests/test_appdata/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -125,15 +125,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x77); diff --git a/tests/test_appdata/src/xassert_conf.h b/tests/test_appdata/src/xassert_conf.h index 6773c617..d746b905 100644 --- a/tests/test_appdata/src/xassert_conf.h +++ b/tests/test_appdata/src/xassert_conf.h @@ -1,3 +1,3 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define XASSERT_ENABLE_DEBUG 1 \ No newline at end of file diff --git a/tests/test_avb_traffic.py b/tests/test_avb_traffic.py index 66f87f33..3008f5a7 100644 --- a/tests/test_avb_traffic.py +++ b/tests/test_avb_traffic.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 XMOS LIMITED. +# Copyright 2015-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. # import os diff --git a/tests/test_avb_traffic/src/main.xc b/tests/test_avb_traffic/src/main.xc index 855c85af..26dfa47e 100644 --- a/tests/test_avb_traffic/src/main.xc +++ b/tests/test_avb_traffic/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -199,15 +199,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_check_ifg_wait.py b/tests/test_check_ifg_wait.py index 9b4d31c6..bbc22dbe 100644 --- a/tests/test_check_ifg_wait.py +++ b/tests/test_check_ifg_wait.py @@ -1,4 +1,4 @@ -# Copyright 2024 XMOS LIMITED. +# Copyright 2024-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. # import os diff --git a/tests/test_check_ifg_wait/src/main.xc b/tests/test_check_ifg_wait/src/main.xc index c3372e7d..da0e4140 100644 --- a/tests/test_check_ifg_wait/src/main.xc +++ b/tests/test_check_ifg_wait/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2024 XMOS LIMITED. +// Copyright 2024-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/tests/test_do_idle_slope_unit.expect b/tests/test_do_idle_slope_unit.expect new file mode 100644 index 00000000..a32e3d16 --- /dev/null +++ b/tests/test_do_idle_slope_unit.expect @@ -0,0 +1,11 @@ +Idle_slope: \d+ +credit_limit: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +Ticks: \d+ +PASS \ No newline at end of file diff --git a/tests/test_do_idle_slope_unit.py b/tests/test_do_idle_slope_unit.py new file mode 100644 index 00000000..e47f29eb --- /dev/null +++ b/tests/test_do_idle_slope_unit.py @@ -0,0 +1,22 @@ +# Copyright 2025 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. +# +import os +import sys +from pathlib import Path +import pytest +import Pyxsim as px + +pkg_dir = Path(__file__).parent +def test_do_idle_slope_unit(capfd): + testname = 'test_do_idle_slope_unit' + binary = pkg_dir / testname / 'bin' / f'{testname}.xe' + assert os.path.isfile(binary) + expect_filename = pkg_dir / f'{testname}.expect' + tester = px.testers.ComparisonTester(open(expect_filename), regexp=True) + result = px.run_on_simulator_( binary, + tester=tester, + do_xe_prebuild=False, + capfd=capfd) + + assert result is True, f"{result}" diff --git a/tests/test_do_idle_slope_unit/CMakeLists.txt b/tests/test_do_idle_slope_unit/CMakeLists.txt new file mode 100644 index 00000000..530622f2 --- /dev/null +++ b/tests/test_do_idle_slope_unit/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(test_do_idle_slope_unit) + +set(APP_PCA_ENABLE ON) +include(../test_deps.cmake) + +set(APP_HW_TARGET XCORE-AI-EXPLORER) + +set(APP_PCA_ENABLE ON) + +set(COMPILER_FLAGS_COMMON -g + -report + -DDEBUG_PRINT_ENABLE + -Os) + +set(APP_COMPILER_FLAGS ${COMPILER_FLAGS_COMMON}) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +XMOS_REGISTER_APP() diff --git a/tests/test_do_idle_slope_unit/src/main.xc b/tests/test_do_idle_slope_unit/src/main.xc new file mode 100644 index 00000000..fac1bf49 --- /dev/null +++ b/tests/test_do_idle_slope_unit/src/main.xc @@ -0,0 +1,148 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include "debug_print.h" +#include +#include "ethernet.h" +#include "mii_buffering.h" +#include "shaper.h" + +/* +{mii_packet_t * unsafe, int} static inline shaper_do_idle_slope(mii_packet_t * unsafe hp_buf, + int prev_time, + int credit, + int current_time, + unsigned qav_idle_slope){ +return {hp_buf, previous_time, credit}; + +*/ + +int test_num = 1; + +int do_test(int last_time, int current_time, qav_state_t *qav_state, ethernet_port_state_t *port_state){ + unsafe{ + int result = 0; + int t0; + int t1; + timer tmr; + + mii_packet_t mii_packet; + mii_packet_t * unsafe buff = &mii_packet; + + qav_state->prev_time = last_time; + qav_state->current_time = current_time; + tmr :> t0; + buff = shaper_do_idle_slope(buff, qav_state, port_state); + tmr :> t1; + debug_printf("Ticks: %d\n", t1-t0); + if(!buff) + { + debug_printf("Error test (%d): Unexpected buff %s, last_end_time 0x%8x, next_start_time 0x%8x, credit: %d\n", test_num, buff != 0 ? "True" : "False", last_time, qav_state->current_time, qav_state->credit); + result = -1; + } + + test_num++; + return result; + } +} + +// For dev only not main test. This is to observe the shaper behaviour +void do_characterise(void){unsafe{ + ethernet_port_state_t port_state = {0}; + qav_state_t qav_state = {0, 0, 0}; + set_qav_idle_slope(&port_state, 75000000); // Set to max reservation to test for overflows + // set_qav_credit_limit(&port_state, ETHERNET_MAX_PACKET_SIZE); + set_qav_credit_limit(&port_state, 0); + + mii_packet_t mii_packet; + mii_packet_t * unsafe buff = &mii_packet; + + qav_state.prev_time = 0; + qav_state.current_time = 0; + debug_printf("credit: %d\n", qav_state.credit); + + int time_inc = 20 * 8; + + for(int i = 0; i < 20; i++){ + int ishp = ((i % 3) == 0); + if(ishp) buff = &mii_packet; else buff = 0; + qav_state.current_time += 10; + buff = shaper_do_idle_slope(buff, &qav_state, &port_state); + debug_printf("%d %s credit: %d - ", i, ishp == 0 ? "LP":"HP", qav_state.credit); + debug_printf("HP Tx: %s\n", buff != 0 ? "True" : "False"); + + if(ishp && buff){ + debug_printf("do send slope\n"); + shaper_do_send_slope(40, &qav_state); + } + qav_state.current_time += time_inc; + + if(i == 10) time_inc = 40 * 8; + } +}} + +// To make volatile +in port p_vol = XS1_PORT_1A; + +int main(void){ + // do_characterise(); + + unsafe{ + ethernet_port_state_t port_state; + qav_state_t qav_state = {0, 0, 0}; + + int result = 0; + + int vol, vol2; + p_vol :> vol; // This will be zero. We add these to force the compiler to call shaper_do_idle_slope fpor the timing analysis + p_vol :> vol2;// otherwise the const expressions get optimised away + + // GROUP1: last_frame_time is not wrapped around wrt next_frame_time + int last_copy = 0; + + port_state.link_speed = LINK_100_MBPS_FULL_DUPLEX; + set_qav_idle_slope(&port_state, 75000000); // Set to max reservation to test for overflows + set_qav_credit_limit(&port_state, ETHERNET_MAX_PACKET_SIZE); + + debug_printf("Idle_slope: %u\n", port_state.qav_idle_slope); + debug_printf("credit_limit: %d\n", port_state.qav_credit_limit); + + // case 1: Small increment + result -= do_test(0x00000000 + vol, 0x00000100 + vol2, &qav_state, &port_state); + + + // case2: Small increment across halfway though + qav_state.credit = 0; + result -= do_test(0x7ffff800 + vol, 0x80000800 + vol2, &qav_state, &port_state); + + // case3: Small increment over halfway though + qav_state.credit = 0; + result -= do_test(0x90000000 + vol, 0x90001000 + vol2, &qav_state, &port_state); + + // case4: Reset and do big increment, will definitely overflow credit + qav_state.credit = 0; + result -= do_test(0x00000000 + vol, 0x90000000 + vol2, &qav_state, &port_state); + + // case5: Close to max + qav_state.credit = 0; + result -= do_test(0 + vol, 0x7fffffff + vol2, &qav_state, &port_state); + + // case6: Push case5 past overflow + result -= do_test(0 + vol, 20 + vol2, &qav_state, &port_state); + + // GROUP2: next_frame_time has wrapped around wrt last_frame_time + // case7: now is before next_frame_time + qav_state.credit = 0; + result -= do_test(0xfffff800 + vol, 0x00000800 + vol2, &qav_state, &port_state); + + // case8: wrapping but REALLY big + qav_state.credit = 0; + result -= do_test(0xffffffff + vol, 0xfffffffe + vol2, &qav_state, &port_state); + + debug_printf("%s\n", result == 0 ? "PASS" : "FAIL"); + return result; + } +} diff --git a/tests/test_etype_filter.py b/tests/test_etype_filter.py index 65268a89..7a177132 100644 --- a/tests/test_etype_filter.py +++ b/tests/test_etype_filter.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import os diff --git a/tests/test_etype_filter/src/main.xc b/tests/test_etype_filter/src/main.xc index fc065034..657c51cf 100644 --- a/tests/test_etype_filter/src/main.xc +++ b/tests/test_etype_filter/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -102,15 +102,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - null, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]:rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + null, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x77); diff --git a/tests/test_link_status.py b/tests/test_link_status.py index 2fce9204..f3cd7e9a 100644 --- a/tests/test_link_status.py +++ b/tests/test_link_status.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/test_link_status/src/main.xc b/tests/test_link_status/src/main.xc index 2b6986a8..5c82cfd1 100644 --- a/tests/test_link_status/src/main.xc +++ b/tests/test_link_status/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -120,15 +120,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - null, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + null, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_rmii_restart/src/main.xc b/tests/test_rmii_restart/src/main.xc index cd932352..59189a6f 100644 --- a/tests/test_rmii_restart/src/main.xc +++ b/tests/test_rmii_restart/src/main.xc @@ -79,15 +79,24 @@ int main() par { { #if RMII - unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - NULL, NULL, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + NULL, NULL, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #elif MII mii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, i_rx_lp, NUM_RX_LP_IF, diff --git a/tests/test_rmii_timing.py b/tests/test_rmii_timing.py index ffb22084..7d7b50a5 100644 --- a/tests/test_rmii_timing.py +++ b/tests/test_rmii_timing.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_rmii_timing/src/main.xc b/tests/test_rmii_timing/src/main.xc index 7853d6bd..375d7e48 100644 --- a/tests/test_rmii_timing/src/main.xc +++ b/tests/test_rmii_timing/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -60,15 +60,24 @@ int main() par { // 5 threads total so thread speed = f/5 - unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - NULL, NULL, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + null, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); loopback(i_cfg[0], i_rx_lp[0], i_tx_lp[0]); } diff --git a/tests/test_rx.py b/tests/test_rx.py index 69104d5f..49fbeb95 100644 --- a/tests/test_rx.py +++ b/tests/test_rx.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_rx/src/main.xc b/tests/test_rx/src/main.xc index e0128faa..155d9140 100644 --- a/tests/test_rx/src/main.xc +++ b/tests/test_rx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -213,15 +213,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_rx_backpressure.py b/tests/test_rx_backpressure.py index a6b41b82..47517628 100644 --- a/tests/test_rx_backpressure.py +++ b/tests/test_rx_backpressure.py @@ -1,4 +1,4 @@ -# Copyright 2018-2024 XMOS LIMITED. +# Copyright 2018-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_rx_backpressure/src/main.xc b/tests/test_rx_backpressure/src/main.xc index 34b57581..53b3863e 100644 --- a/tests/test_rx_backpressure/src/main.xc +++ b/tests/test_rx_backpressure/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. /* @@ -205,15 +205,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_rx_err.py b/tests/test_rx_err.py index 09ce53d5..c1b7e0a7 100644 --- a/tests/test_rx_err.py +++ b/tests/test_rx_err.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import random diff --git a/tests/test_rx_queues.py b/tests/test_rx_queues.py index c8ac9d4a..64051e4e 100644 --- a/tests/test_rx_queues.py +++ b/tests/test_rx_queues.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 XMOS LIMITED. +# Copyright 2015-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. # # A test for the high and low priority traffic queues in the MAC. It sends different diff --git a/tests/test_rx_queues/src/main.xc b/tests/test_rx_queues/src/main.xc index f274af31..b211af52 100644 --- a/tests/test_rx_queues/src/main.xc +++ b/tests/test_rx_queues/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -179,7 +179,7 @@ int main() #else // !RGMII #if MII - on tile[0]: mii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, + on tile[0]: mii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, i_rx_lp, NUM_RX_LP_IF, i_tx_lp, NUM_TX_LP_IF, c_rx_hp, null, @@ -188,15 +188,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif #endif // RGMII diff --git a/tests/test_shaper.py b/tests/test_shaper.py index d6277026..059b45e7 100644 --- a/tests/test_shaper.py +++ b/tests/test_shaper.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 XMOS LIMITED. +# Copyright 2015-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import pytest from pathlib import Path diff --git a/tests/test_shaper/src/main.xc b/tests/test_shaper/src/main.xc index fcd39fc8..939c07f3 100644 --- a/tests/test_shaper/src/main.xc +++ b/tests/test_shaper/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -18,8 +18,6 @@ port p_test_ctrl = on tile[0]: XS1_PORT_1C; #define VLAN_TAGGED 1 -#define MII_CREDIT_FRACTIONAL_BITS 16 - static int calc_idle_slope(int bps) { long long slope = ((long long) bps) << (MII_CREDIT_FRACTIONAL_BITS); @@ -200,15 +198,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_ENABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - null, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_ENABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + null, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_ENABLE_SHAPER); #endif on tile[0]: filler(0x1111); on tile[0]: filler(0x3333); diff --git a/tests/test_smi.expect b/tests/test_smi.expect new file mode 100644 index 00000000..f472ef7f --- /dev/null +++ b/tests/test_smi.expect @@ -0,0 +1,7 @@ +Checking SMI: .* +DUT WRITE: 0x0 +DUT WRITE: 0x1111 +DUT WRITE: 0x2222 +DUT READ: 0x1234 +DUT READ: 0xaaaa +DUT READ: 0x4321 diff --git a/tests/test_smi.py b/tests/test_smi.py new file mode 100644 index 00000000..5dd0e62c --- /dev/null +++ b/tests/test_smi.py @@ -0,0 +1,64 @@ +# Copyright 2025 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import Pyxsim as px +import pytest +import os +from pathlib import Path + +from smi import smi_master_checker, smi_make_packet +from helpers import generate_tests, create_if_needed + + +def do_test(capfd, ptype, arch): + testname = 'test_smi' + + with capfd.disabled(): + print(f"Running {testname}:port type {ptype}, arch {arch}") + + profile = f"{ptype}_{arch}" + + capfd.readouterr() # clear capfd buffer + binary = f'{testname}/bin/{profile}/{testname}_{profile}.xe' + assert os.path.isfile(binary) + + tester = px.testers.ComparisonTester(open(f'{testname}.expect'), regexp=True, ordered=False) + + log_folder = create_if_needed("logs") + filename = f"{log_folder}/xsim_trace_{testname}_{ptype}_{arch}" + sim_args = ['--trace-to', f'{filename}.txt', '--enable-fnop-tracing'] + + vcd_args = f'-o {filename}.vcd -tile tile[0] -ports -ports-detailed -instructions' + vcd_args+= f' -functions -cycles -clock-blocks -pads' + sim_args += ['--vcd-tracing', vcd_args] + + if "single" in ptype: + mdc_port = "tile[0]:XS1_PORT_4B.0" + mdio_port = "tile[0]:XS1_PORT_4B.1" + else: + mdc_port = "tile[0]:XS1_PORT_1N" + mdio_port = "tile[0]:XS1_PORT_1M" + rst_n_port = "tile[0]:XS1_PORT_4A" + expected_speed_hz = 1.666667e6 + + smi_harness = smi_master_checker(mdc_port, mdio_port, rst_n_port, expected_speed_hz, [0x1234, 0xaaaa, 0x4321]) + result = px.run_on_simulator_( binary, + simthreads=[smi_harness], + tester=tester, + simargs=sim_args, + do_xe_prebuild=False, + capfd=capfd + ) + + assert result is True, f"{result}" + +test_params_file = Path(__file__).parent / "test_smi/test_params.json" +@pytest.mark.parametrize("params", generate_tests(test_params_file)[0], ids=generate_tests(test_params_file)[1]) +def test_smi(capfd, params): + + debug = 0 + if debug: + with capfd.disabled(): + do_test(capfd, params["type"], params["arch"]) + else: + do_test(capfd, params["type"], params["arch"]) diff --git a/tests/test_smi/CMakeLists.txt b/tests/test_smi/CMakeLists.txt new file mode 100644 index 00000000..539c7543 --- /dev/null +++ b/tests/test_smi/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(test_smi) + +set(APP_PCA_ENABLE ON) + +include(../helpers.cmake) +include(../test_deps.cmake) + +file(GLOB_RECURSE SOURCES_XC RELATIVE ${CMAKE_CURRENT_LIST_DIR} "src/*.xc") +set(APP_XC_SRCS ${SOURCES_XC}) +set(APP_INCLUDES ../include src) + + +set(COMPILER_FLAGS_COMMON -g + -report + -DDEBUG_PRINT_ENABLE=1 + -Os + -D__SIMULATOR__=1) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +file(READ ${CMAKE_CURRENT_LIST_DIR}/test_params.json JSON_CONTENT) + +get_json_list(${JSON_CONTENT} PROFILES profile_list) + +set(done_configs "") +foreach(PROFILE ${profile_list}) + string(JSON type GET ${PROFILE} type) + get_json_list(${PROFILE} arch arch_list) # Get archs to build for, for this particular profile + foreach(arch ${arch_list}) + set(config "${type}_${arch}") + message(STATUS "Building cfg_name: ${config}") + + set_app_hw_target(arch) + set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) + + string(FIND "${PROFILE}" "single" position) + if(position GREATER -1) + list(APPEND APP_COMPILER_FLAGS_${config} -DSINGLE_PORT=1) + else() + list(APPEND APP_COMPILER_FLAGS_${config} -DTWO_PORTS=1) + endif() + + + message(STATUS APP_COMPILER_FLAGS_${config} ${APP_COMPILER_FLAGS_${config}}) + + XMOS_REGISTER_APP() + unset(APP_COMPILER_FLAGS_${config}) + + endforeach() # foreach(arch ${arch_list}) + unset(arch_list) +endforeach() # foreach(PROFILE ${profile_list}) diff --git a/tests/test_smi/src/main.xc b/tests/test_smi/src/main.xc new file mode 100644 index 00000000..0da81b46 --- /dev/null +++ b/tests/test_smi/src/main.xc @@ -0,0 +1,52 @@ +// Copyright 2014-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include "ethernet.h" +#include "smi.h" +#include +#include +#include "syscall.h" + +#include "ports.h" + +#define SMI_SINGLE_PORT_MDC_BIT 0 +#define SMI_SINGLE_PORT_MDIO_BIT 1 + +void test_smi(client interface smi_if i_smi){ + delay_microseconds(1); + p_phy_rst_n <: 0xf; + delay_microseconds(1); + + for(int i = 0; i < 3; i++){ + uint16_t word_to_tx = i + (i << 4) + (i << 8) + (i << 12); + i_smi.write_reg(0x03, 0x0a, word_to_tx); + } + + for(int i = 0; i < 3; i++){ + uint16_t read = i_smi.read_reg(0x10, 0x11); + printf("DUT READ: 0x%x\n", read); + } + + p_phy_rst_n <: 0; // This terminates the test +} + + +int main() +{ + interface smi_if i_smi; + p_phy_rst_n <: 0; + par { + test_smi(i_smi); + [[distribute]] +#if TWO_PORTS + smi(i_smi, p_smi_mdio, p_smi_mdc); +#elif SINGLE_PORT + smi_singleport(i_smi, p_smi_mdc_mdio, SMI_SINGLE_PORT_MDIO_BIT, SMI_SINGLE_PORT_MDC_BIT); +#endif + par(int i = 0; i < 7; i++){while(1);} + } + return 0; +} + diff --git a/tests/test_smi/test_params.json b/tests/test_smi/test_params.json new file mode 100644 index 00000000..f32d689f --- /dev/null +++ b/tests/test_smi/test_params.json @@ -0,0 +1,6 @@ +{ + "PROFILES": [ + {"type": "two_port", "arch":["xs2", "xs3"]}, + {"type": "single_port", "arch":["xs2", "xs3"]} + ] +} diff --git a/tests/test_speed_change.py b/tests/test_speed_change.py index 112d0a88..2662b729 100644 --- a/tests/test_speed_change.py +++ b/tests/test_speed_change.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 XMOS LIMITED. +# Copyright 2015-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import os diff --git a/tests/test_speed_change/src/main.xc b/tests/test_speed_change/src/main.xc index 598822f9..8e0e3194 100644 --- a/tests/test_speed_change/src/main.xc +++ b/tests/test_speed_change/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2021 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/tests/test_time_rx.py b/tests/test_time_rx.py index 9cd01f0c..b0a09dee 100644 --- a/tests/test_time_rx.py +++ b/tests/test_time_rx.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/test_time_rx/src/main.xc b/tests/test_time_rx/src/main.xc index b476e73f..e959edc8 100644 --- a/tests/test_time_rx/src/main.xc +++ b/tests/test_time_rx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include diff --git a/tests/test_time_rx/src/main_mii_rt.h b/tests/test_time_rx/src/main_mii_rt.h index b4944d7c..fef893e1 100644 --- a/tests/test_time_rx/src/main_mii_rt.h +++ b/tests/test_time_rx/src/main_mii_rt.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define NUM_CFG_IF 1 @@ -31,15 +31,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x222); on tile[0]: filler(0x333); diff --git a/tests/test_time_rx/src/main_mii_standard.h b/tests/test_time_rx/src/main_mii_standard.h index 3e697c95..69301775 100644 --- a/tests/test_time_rx/src/main_mii_standard.h +++ b/tests/test_time_rx/src/main_mii_standard.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. void test_rx(client interface mii_if i_mii, diff --git a/tests/test_time_rx/src/main_rgmii.h b/tests/test_time_rx/src/main_rgmii.h index 9b01ef8d..ce537194 100644 --- a/tests/test_time_rx/src/main_rgmii.h +++ b/tests/test_time_rx/src/main_rgmii.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define NUM_CFG_IF 1 diff --git a/tests/test_time_rx_tx.py b/tests/test_time_rx_tx.py index 15ea56ed..2e9bf0c4 100644 --- a/tests/test_time_rx_tx.py +++ b/tests/test_time_rx_tx.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 XMOS LIMITED. +# Copyright 2015-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/test_time_rx_tx/src/main.xc b/tests/test_time_rx_tx/src/main.xc index 8acb53d9..7a31f02c 100644 --- a/tests/test_time_rx_tx/src/main.xc +++ b/tests/test_time_rx_tx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include diff --git a/tests/test_time_rx_tx/src/main_mii_rt.h b/tests/test_time_rx_tx/src/main_mii_rt.h index 8745814a..107bc760 100644 --- a/tests/test_time_rx_tx/src/main_mii_rt.h +++ b/tests/test_time_rx_tx/src/main_mii_rt.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define NUM_PACKET_LENGTHS 64 @@ -183,15 +183,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_time_rx_tx/src/main_mii_standard.h b/tests/test_time_rx_tx/src/main_mii_standard.h index 2ff250f5..58523852 100644 --- a/tests/test_time_rx_tx/src/main_mii_standard.h +++ b/tests/test_time_rx_tx/src/main_mii_standard.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define NUM_PACKET_LENGTHS 64 diff --git a/tests/test_time_rx_tx/src/main_rgmii.h b/tests/test_time_rx_tx/src/main_rgmii.h index c6c89dd5..09ae2268 100644 --- a/tests/test_time_rx_tx/src/main_rgmii.h +++ b/tests/test_time_rx_tx/src/main_rgmii.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define NUM_PACKET_LENGTHS 64 diff --git a/tests/test_time_tx.py b/tests/test_time_tx.py index 386d820f..d31f0e5f 100644 --- a/tests/test_time_tx.py +++ b/tests/test_time_tx.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px import os diff --git a/tests/test_time_tx/src/main.xc b/tests/test_time_tx/src/main.xc index 5974b8e2..29503a90 100644 --- a/tests/test_time_tx/src/main.xc +++ b/tests/test_time_tx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -131,15 +131,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, 1, - i_rx_lp, 1, - i_tx_lp, 1, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); on tile[0]: filler(0x2222); diff --git a/tests/test_timestamp_tx.py b/tests/test_timestamp_tx.py index c29e99e1..e1834204 100644 --- a/tests/test_timestamp_tx.py +++ b/tests/test_timestamp_tx.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px import os diff --git a/tests/test_timestamp_tx/src/main.xc b/tests/test_timestamp_tx/src/main.xc index 63e022d1..3f814d7a 100644 --- a/tests/test_timestamp_tx/src/main.xc +++ b/tests/test_timestamp_tx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -105,15 +105,23 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, 1, - i_rx_lp, 1, - i_tx_lp, 1, + on tile[0]:rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, null, null, p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); on tile[0]: filler(0x2222); diff --git a/tests/test_tx.py b/tests/test_tx.py index 735d7a1a..7dd525af 100644 --- a/tests/test_tx.py +++ b/tests/test_tx.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import os import sys diff --git a/tests/test_tx/src/main.xc b/tests/test_tx/src/main.xc index a52338cb..b6d361e6 100644 --- a/tests/test_tx/src/main.xc +++ b/tests/test_tx/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include #include @@ -135,15 +135,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, 1, - i_rx_lp, 1, - i_tx_lp, 1, - c_rx_hp, c_tx_hp, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + c_rx_hp, c_tx_hp, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x1111); diff --git a/tests/test_vlan_strip.py b/tests/test_vlan_strip.py index d226c3d6..f9e5fbdb 100644 --- a/tests/test_vlan_strip.py +++ b/tests/test_vlan_strip.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 XMOS LIMITED. +# Copyright 2014-2025 XMOS LIMITED. # This Software is subject to the terms of the XMOS Public Licence: Version 1. import Pyxsim as px diff --git a/tests/test_vlan_strip/src/main.xc b/tests/test_vlan_strip/src/main.xc index 592326f0..71d1d2b1 100644 --- a/tests/test_vlan_strip/src/main.xc +++ b/tests/test_vlan_strip/src/main.xc @@ -1,4 +1,4 @@ -// Copyright 2014-2024 XMOS LIMITED. +// Copyright 2014-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #include @@ -109,15 +109,24 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: unsafe{rmii_ethernet_rt_mac(i_cfg, NUM_CFG_IF, - i_rx_lp, NUM_RX_LP_IF, - i_tx_lp, NUM_TX_LP_IF, - null, null, - p_eth_clk, - &p_eth_rxd, p_eth_rxdv, - p_eth_txen, &p_eth_txd, - eth_rxclk, eth_txclk, - 4000, 4000, ETHERNET_DISABLE_SHAPER);} + on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + i_rx_lp, NUM_RX_LP_IF, + i_tx_lp, NUM_TX_LP_IF, + null, null, + p_eth_clk, + p_eth_rxd_0, + p_eth_rxd_1, + RX_PINS, + p_eth_rxdv, + p_eth_txen, + p_eth_txd_0, + p_eth_txd_1, + TX_PINS, + eth_rxclk, + eth_txclk, + port_timing, + 4000, 4000, + ETHERNET_DISABLE_SHAPER); #endif on tile[0]: filler(0x77); diff --git a/tests/test_vlan_strip/src/xassert_conf.h b/tests/test_vlan_strip/src/xassert_conf.h index 56a32bfe..d746b905 100644 --- a/tests/test_vlan_strip/src/xassert_conf.h +++ b/tests/test_vlan_strip/src/xassert_conf.h @@ -1,3 +1,3 @@ -// Copyright 2015-2021 XMOS LIMITED. +// Copyright 2015-2025 XMOS LIMITED. // This Software is subject to the terms of the XMOS Public Licence: Version 1. #define XASSERT_ENABLE_DEBUG 1 \ No newline at end of file From 93248b91c605790aecce12d12e062982d0c33ad9 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 15:25:13 +0000 Subject: [PATCH 07/18] Tidy merge --- lib_ethernet/src/rmii_master.xc | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index f341b891..5fc9c1c7 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -1092,9 +1092,6 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, volatile ethernet_port_state_t * unsafe p_port_state, volatile int * unsafe running_flag_ptr){ - int credit = 0; - int credit_time; - // Need one timer to be able to read at any time for the shaper timer credit_tmr; // And a second timer to be enforcing the IFG gap From 8f64867586f685473f2a1e9c9c7c3b6c98144dc2 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 15:37:37 +0000 Subject: [PATCH 08/18] Tidy bringup app --- .../CMakeLists.txt | 28 +++++++++++++++++++ .../bringup_xk_eth_xu316_dual_100m/src/icmp.h | 9 ------ .../src/icmp.xc | 19 ------------- 3 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt diff --git a/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt new file mode 100644 index 00000000..3e88bbe4 --- /dev/null +++ b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_bringup_xk_eth_xu316_dual_100m) + +set(APP_HW_TARGET xk-eth-xu316-dual-100m.xn) + +set(APP_DEPENDENT_MODULES lib_ethernet + "lib_board_support(feature/xk_eth_xu316_dual_100m)") +set(APP_PCA_ENABLE ON) + +set(COMPILER_FLAGS_COMMON -g + -report + -DDEBUG_PRINT_ENABLE=1 + -Os + -Wno-reinterpret-alignment + -DBOARD_SUPPORT_BOARD=XK_ETH_XU316_DUAL_100M) + +set(APP_COMPILER_FLAGS_MAC_0 -DPHY0=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_MAC_0_SINGLE_SMI -DPHY0=1 -DSINGLE_SMI=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_MAC_1 -DPHY1=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_MAC_1_8B_TX -DPHY1=1 -DPHY1_8B_TX=1 ${COMPILER_FLAGS_COMMON}) +set(APP_COMPILER_FLAGS_MAC_0_MAC_1 -DPHY0=1 -DPHY1=1 ${COMPILER_FLAGS_COMMON}) + +set(APP_XSCOPE_SRCS src/config.xscope) + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) + +XMOS_REGISTER_APP() diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h index 3ecdb7e5..886890d2 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.h @@ -3,21 +3,12 @@ #ifndef __icmp_h__ #define __icmp_h__ #include -<<<<<<< HEAD -#include -======= ->>>>>>> hw_test - [[combinable]] void icmp_server(client ethernet_cfg_if cfg, client ethernet_rx_if rx, client ethernet_tx_if tx, const unsigned char ip_address[4], -<<<<<<< HEAD - otp_ports_t &otp_ports); -======= const unsigned char mac_address[MACADDR_NUM_BYTES]); ->>>>>>> hw_test #endif // __icmp_h__ diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc index c0ec5e7d..12cc4d7f 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc @@ -4,10 +4,6 @@ #include #include #include -<<<<<<< HEAD -#include -======= ->>>>>>> hw_test #include static unsigned short checksum_ip(const unsigned char frame[34]) @@ -246,21 +242,10 @@ void icmp_server(client ethernet_cfg_if cfg, client ethernet_rx_if rx, client ethernet_tx_if tx, const unsigned char ip_address[4], -<<<<<<< HEAD - otp_ports_t &otp_ports) -{ - unsigned char mac_address[MACADDR_NUM_BYTES]; - ethernet_macaddr_filter_t macaddr_filter; - - // Get the mac address from OTP and set it in the ethernet component - otp_board_info_get_mac(otp_ports, 0, mac_address); - -======= const unsigned char mac_address[MACADDR_NUM_BYTES]) { ethernet_macaddr_filter_t macaddr_filter; ->>>>>>> hw_test size_t index = rx.get_index(); cfg.set_macaddr(0, mac_address); @@ -275,13 +260,9 @@ void icmp_server(client ethernet_cfg_if cfg, cfg.add_ethertype_filter(index, 0x0806); cfg.add_ethertype_filter(index, 0x0800); -<<<<<<< HEAD - debug_printf("Test started\n"); -======= debug_printf("ICMP server started at MAC: %x:%x:%x:%x:%x:%x, on IP: %d:%d:%d:%d\n", mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], ip_address[0], ip_address[1], ip_address[2], ip_address[3]); ->>>>>>> hw_test while (1) { select { From e2d1c9c597f7dc8028991161af75e21e72f05360 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 15:46:51 +0000 Subject: [PATCH 09/18] Update deps.cmake --- examples/deps.cmake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/deps.cmake b/examples/deps.cmake index 6d20ed95..fbd2d767 100644 --- a/examples/deps.cmake +++ b/examples/deps.cmake @@ -1,8 +1,4 @@ set(APP_DEPENDENT_MODULES lib_ethernet "lib_otpinfo(feature/xccm)" -<<<<<<< HEAD - "lib_board_support(feature/xk_eth_xu316_dual_100m)" -======= "shuchitak/lib_board_support(sync_link_state_with_mac)" ->>>>>>> hw_test ) From 415904a7702f0f0cd933c92c561b0efb30641548 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 16:00:55 +0000 Subject: [PATCH 10/18] Tidy up items - merge cleanup and use 8b port initialiser --- Jenkinsfile | 4 ++-- lib_ethernet/api/ethernet.h | 10 ---------- tests/bringup_xk_eth_xu316_dual_100m/src/main.xc | 2 +- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 33a51b5d..0e0d382f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,6 @@ @Library('xmos_jenkins_shared_library@v0.36.0') _ - def clone_test_deps() { dir("${WORKSPACE}") { sh "git clone git@github.com:xmos/test_support" @@ -89,6 +88,7 @@ pipeline { } } } + stage('Documentation') { steps { dir("${REPO}") { @@ -226,4 +226,4 @@ pipeline { } // parallel } // stage('Tests') } // stages -} // pipeline +} // pipeline \ No newline at end of file diff --git a/lib_ethernet/api/ethernet.h b/lib_ethernet/api/ethernet.h index e55ef645..ba3f5b16 100644 --- a/lib_ethernet/api/ethernet.h +++ b/lib_ethernet/api/ethernet.h @@ -722,7 +722,6 @@ typedef struct rmii_port_timing_t{ * \param rx_pin_map Which pins to use in 4 bit case. USE_LOWER_2B or USE_HIGHER_2B. Ignored if 1 bit ports used. * \param p_rxdv RMII RX data valid port * \param p_txen RMII TX enable port -<<<<<<< HEAD * \param p_txd_0 Port for data bit 0 (1 bit option) or entire port (4 or 8 bit option) * \param p_txd_1 Port for data bit 1 (1 bit option). Pass null if unused. * \param tx_pin_map Which pins to use in 4 bit case. USE_LOWER_2B or USE_HIGHER_2B. Ignored if 1 bit ports used. @@ -733,15 +732,6 @@ typedef struct rmii_port_timing_t{ * \param txclk Clock used for RMII transmit timing * \param port_timing Struct used for initialising the clock blocks to ensure setup and hold times are met * -======= - * \param p_txd_0 Port for data bit 0 (1 bit option) or entire port (4 bit option) - * \param p_txd_1 Port for data bit 1 (1 bit option). Pass null if unused. - * \param tx_pin_map Which pins to use in 4 bit case. USE_LOWER_2B or USE_HIGHER_2B. Ignored if 1 bit ports used. - * \param rxclk Clock used for RMII receive timing - * \param txclk Clock used for RMII transmit timing - * \param port_timing Struct used for initialising the clock blocks to ensure setup and hold times are met - * ->>>>>>> hw_test * \param rx_bufsize_words The number of words to used for a receive buffer. * This should be at least 500 long words. * \param tx_bufsize_words The number of words to used for a transmit buffer. diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc index b721b2ec..9e8d613a 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc @@ -35,7 +35,7 @@ port p_phy1_rxd_1 = PHY_1_RXD_1; in port p_unused_1 = PHY_1_TXD_1; #define TX8_BIT_0 6 #define TX8_BIT_1 7 - #define TX_PINS ((TX8_BIT_0 << 16) | (TX8_BIT_1)) + #define TX_PINS RMII_8B_PINS_INITIALISER(TX8_BIT_0, TX8_BIT_1) #define p_phy1_txd_1 null #else port p_phy1_txd_0 = PHY_1_TXD_0; From 8e73bfdbafdb1053994c2b2c974aaa59c8933aa3 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 16:06:19 +0000 Subject: [PATCH 11/18] Fix cmake in tests --- tests/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b604e1d..cfc6470a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,12 +21,8 @@ add_subdirectory(test_check_ifg_wait) add_subdirectory(test_rmii_timing) add_subdirectory(test_rmii_restart) add_subdirectory(test_smi) -<<<<<<< HEAD -add_subdirectory(test_do_idle_slope_unit) -======= add_subdirectory(hw_test_mii) add_subdirectory(test_do_idle_slope_unit) add_subdirectory(hw_test_mii_tx) add_subdirectory(test_tx_ifg) ->>>>>>> hw_test From d7d33c4a0b7a7b35dca98200ff6b17fc8aae2840 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 28 Feb 2025 16:52:17 +0000 Subject: [PATCH 12/18] Copyright --- tests/hw_test_mii/src/xu316_dual_100m_ports/rmii_port_defines.h | 2 ++ tests/include/log_tx_ts.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/hw_test_mii/src/xu316_dual_100m_ports/rmii_port_defines.h b/tests/hw_test_mii/src/xu316_dual_100m_ports/rmii_port_defines.h index 972e2187..1c39dfbb 100644 --- a/tests/hw_test_mii/src/xu316_dual_100m_ports/rmii_port_defines.h +++ b/tests/hw_test_mii/src/xu316_dual_100m_ports/rmii_port_defines.h @@ -1,3 +1,5 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __RMII_PORT_DEFINES_H__ #define __RMII_PORT_DEFINES_H__ diff --git a/tests/include/log_tx_ts.h b/tests/include/log_tx_ts.h index 80fb8a1f..62ff9650 100644 --- a/tests/include/log_tx_ts.h +++ b/tests/include/log_tx_ts.h @@ -1,3 +1,5 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. #ifndef __LOG_TX_TS_H__ #define __LOG_TX_TS_H__ From 8ccb40f8d4f94606eb64f96be53c043a59c1ca62 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 12 Mar 2025 10:47:14 +0000 Subject: [PATCH 13/18] Fix duplicate IP --- examples/app_rmii_100Mbit_icmp/src/main.xc | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/app_rmii_100Mbit_icmp/src/main.xc b/examples/app_rmii_100Mbit_icmp/src/main.xc index 5ea2854c..f32c16c0 100644 --- a/examples/app_rmii_100Mbit_icmp/src/main.xc +++ b/examples/app_rmii_100Mbit_icmp/src/main.xc @@ -19,9 +19,6 @@ port p_phy_rxdv = PHY_0_RXDV; port p_phy_txen = PHY_0_TX_EN; port p_phy_clk = PHY_1_CLK_50M; -static unsigned char ip_address[4] = {192, 168, 2, 178}; -static unsigned char mac_address_phy[MACADDR_NUM_BYTES] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; - clock phy_rxclk = on tile[0]: XS1_CLKBLK_1; clock phy_txclk = on tile[0]: XS1_CLKBLK_2; From a42308c4689ba9d6499754c9c573ef1f259428d4 Mon Sep 17 00:00:00 2001 From: Humphrey Bucknell Date: Thu, 28 Aug 2025 11:07:49 +0100 Subject: [PATCH 14/18] Updating bringup test to latest lib --- tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt index 252a291e..ac14915d 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt +++ b/tests/bringup_xk_eth_xu316_dual_100m/CMakeLists.txt @@ -2,10 +2,12 @@ cmake_minimum_required(VERSION 3.21) include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) project(app_bringup_xk_eth_xu316_dual_100m) +include(${CMAKE_CURRENT_LIST_DIR}/../board_support/board_support.cmake) + set(APP_HW_TARGET xk-eth-xu316-dual-100m.xn) set(APP_DEPENDENT_MODULES lib_ethernet - "lib_board_support(1.2.1)") + "lib_board_support(1.3.0)") set(APP_PCA_ENABLE ON) set(COMPILER_FLAGS_COMMON -g @@ -13,7 +15,7 @@ set(COMPILER_FLAGS_COMMON -g -DDEBUG_PRINT_ENABLE=1 -Os -Wno-reinterpret-alignment - -DBOARD_SUPPORT_BOARD=XK_ETH_XU316_DUAL_100M) + -DTEST_BOARD_SUPPORT_BOARD=XK_ETH_XU316_DUAL_100M) set(APP_COMPILER_FLAGS_MAC_0 -DPHY0=1 ${COMPILER_FLAGS_COMMON}) set(APP_COMPILER_FLAGS_MAC_0_SINGLE_SMI -DPHY0=1 -DSINGLE_SMI=1 ${COMPILER_FLAGS_COMMON}) @@ -23,6 +25,9 @@ set(APP_COMPILER_FLAGS_MAC_0_MAC_1 -DPHY0=1 -DPHY1=1 ${COMPILER_FLAGS_C set(APP_XSCOPE_SRCS src/config.xscope) +set(APP_XC_SRCS src/main.xc src/icmp.xc ${test_board_support_SRCS}) +set(APP_INCLUDES ${test_board_support_INC}) + set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..) XMOS_REGISTER_APP() From 5236397644cb55b99c4f8d6e26e689581bdf4b06 Mon Sep 17 00:00:00 2001 From: Humphrey Bucknell Date: Thu, 28 Aug 2025 14:05:51 +0100 Subject: [PATCH 15/18] Documentation update --- doc/rst/lib_ethernet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/rst/lib_ethernet.rst b/doc/rst/lib_ethernet.rst index 8edc0cbc..84679ead 100644 --- a/doc/rst/lib_ethernet.rst +++ b/doc/rst/lib_ethernet.rst @@ -1041,7 +1041,7 @@ Real-time Ethernet MAC supporting typedefs .. doxygenenum:: ethernet_enable_shaper_t .. doxygenstruct:: rmii_port_timing_t -.. doxygenenum:: rmii_data_4b_pin_assignment_t +.. doxygenenum:: rmii_data_pin_assignment_t |newpage| From 9302ed08337c7d2d1faf8b54938d09b67cebf3b5 Mon Sep 17 00:00:00 2001 From: Humphrey Bucknell Date: Thu, 28 Aug 2025 15:30:05 +0100 Subject: [PATCH 16/18] icmp client refactored, brought in from app-note --- .../src/icmp.xc | 319 ++++++++++-------- 1 file changed, 170 insertions(+), 149 deletions(-) diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc index 12cc4d7f..c18c9c35 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/icmp.xc @@ -6,186 +6,210 @@ #include #include +#define ETH_FRAME_TYPE_ARP 0x0806 +#define ETH_FRAME_TYPE_IP 0x0800 + +/* Network to Host byte-ordered short */ +#define NTOHS(array, idx) ((unsigned short)((array[idx] << 8) | array[idx + 1])) +/* Host to Network byte-ordered short */ +#define HTONS(array, idx, value) \ +do { \ + array[idx] = value >> 8; \ + array[idx + 1] = value & 0xFF; \ +} while (0) + +#define DST_MAC_IDX 0 +#define SRC_MAC_IDX 6 +#define ETH_TYPE_IDX 12 +#define ETH_FRAME_MIN_SIZE 64 + +#define ARP_HW_TYPE_IDX 14 +#define ARP_PROTO_TYPE_IDX 16 +#define ARP_HW_LEN_IDX 18 +#define ARP_PROTO_LEN_IDX 19 +#define ARP_OPCODE_IDX 20 +#define ARP_SENDER_MAC_IDX 22 +#define ARP_SENDER_IP_IDX 28 +#define ARP_TARGET_MAC_IDX 32 +#define ARP_TARGET_IP_IDX 38 +#define ARP_HEADER_END 42 // (ARP_TARGET_IP_IDX + IP_ADDR_SIZE) + +#define IP_VERSION_IDX 14 +#define IP_TOS_IDX 15 +#define IP_TOTAL_LEN_IDX 16 +#define IP_ID_IDX 18 +#define IP_FLAGS_IDX 20 +#define IP_TTL_IDX 22 +#define IP_PROTOCOL_IDX 23 +#define IP_CHECKSUM_IDX 24 +#define IP_SRC_ADDR_IDX 26 +#define IP_DST_ADDR_IDX 30 +#define IP_HEADER_LEN 20 // (IP_DST_ADDR_IDX + IP_ADDR_SIZE - IP_VERSION_IDX) + +#define ICMP_TYPE_CODE_IDX 34 +#define ICMP_CHECKSUM_IDX 36 +#define ICMP_ID_IDX 38 +#define ICMP_SEQ_IDX 40 +#define ICMP_DATA_IDX 42 +#define ICMP_HEADER_LEN 8 // (ICMP_DATA_IDX - ICMP_TYPE_CODE_IDX) + +#define IP_ADDR_SIZE 4 + +#define IP_ICMP_PROTOCOL 0x01 + +static const unsigned char eth_arp_type[] = { + 0x08, 0x06, // Ethernet type: ARP +}; + +static const unsigned char eth_ip_type[] = { + 0x08, 0x00, // Ethernet type: IP +}; + +static unsigned char arp_body[] = { + 0x00, 0x01, // Hardware type: Ethernet + 0x08, 0x00, // Protocol type: IPv4 + 0x06, 0x04, // Hardware size: 6 bytes, +}; + +static unsigned char arp_reply[] = { + 0x00, 0x02, // Opcode: reply +}; + +static unsigned char arp_request[] = { + 0x00, 0x01, // Opcode: request +}; + +static unsigned char icmp_echo_request[] = { + 0x08, 0x00, // ICMP type: echo request +}; + +static unsigned char icmp_echo_reply[] = { + 0x00, 0x00, // ICMP type: echo reply +}; + +static unsigned char ip_icmp_ping_version[] = { + 0x45, 0x00, // IP version and header length +}; + +static unsigned char ip_body[] = { + 0x00, 0x00, // Identification and flags + 0x00, 0x00, // Fragment offset + 0x40, 0x01, // TTL and protocol (ICMP) +}; + static unsigned short checksum_ip(const unsigned char frame[34]) { - int i; - unsigned accum = 0; - - for (i = 14; i < 34; i += 2) + unsigned accum1 = 0; + unsigned accum2 = 0; + for (int i = IP_VERSION_IDX; i < (IP_DST_ADDR_IDX + IP_ADDR_SIZE); i += 2) { - accum += *((const uint16_t*)(frame + i)); + accum1 += frame[i]; + accum2 += frame[i + 1]; } - // Fold carry into 16bits - while (accum >> 16) + unsigned accum = accum1 + (accum2 << 8); + + // Fold in carry into 16bits + accum = (accum & 0xFFFF) + (accum >> 16); + unsigned extra_carry = (accum >> 16); + if (extra_carry) { - accum = (accum & 0xFFFF) + (accum >> 16); + accum += extra_carry; } - accum = byterev(~accum) >> 16; return accum; } -static int build_arp_response(unsigned char rxbuf[64], - unsigned char txbuf[64], +static int build_arp_response(unsigned char rxbuf[ETH_FRAME_MIN_SIZE], + unsigned char txbuf[ETH_FRAME_MIN_SIZE], const unsigned char own_mac_addr[MACADDR_NUM_BYTES], - const unsigned char own_ip_addr[4]) + const unsigned char own_ip_addr[IP_ADDR_SIZE]) { - unsigned word; - unsigned char byte; + memcpy(&txbuf[DST_MAC_IDX], &rxbuf[22], MACADDR_NUM_BYTES); + memcpy(&txbuf[SRC_MAC_IDX], own_mac_addr, MACADDR_NUM_BYTES); + memcpy(&txbuf[ETH_TYPE_IDX], eth_arp_type, sizeof(eth_arp_type)); - for (int i = 0; i < MACADDR_NUM_BYTES; i++) - { - byte = rxbuf[22+i]; - txbuf[i] = byte; - txbuf[32 + i] = byte; - } - word = ((const unsigned int *) rxbuf)[7]; - for (int i = 0; i < 4; i++) - { - txbuf[38 + i] = word & 0xFF; - word >>= 8; - } + memcpy(&txbuf[ARP_HW_TYPE_IDX], arp_body, sizeof(arp_body)); + memcpy(&txbuf[ARP_OPCODE_IDX], arp_reply, sizeof(arp_reply)); - txbuf[28] = own_ip_addr[0]; - txbuf[29] = own_ip_addr[1]; - txbuf[30] = own_ip_addr[2]; - txbuf[31] = own_ip_addr[3]; + memcpy(&txbuf[ARP_SENDER_MAC_IDX], own_mac_addr, MACADDR_NUM_BYTES); + memcpy(&txbuf[ARP_SENDER_IP_IDX], own_ip_addr, IP_ADDR_SIZE); - for (int i = 0; i < MACADDR_NUM_BYTES; i++) - { - txbuf[22 + i] = own_mac_addr[i]; - txbuf[6 + i] = own_mac_addr[i]; - } - txbuf[12] = 0x08; - txbuf[13] = 0x06; - txbuf[14] = 0x00; - txbuf[15] = 0x01; - txbuf[16] = 0x08; - txbuf[17] = 0x00; - txbuf[18] = 0x06; - txbuf[19] = 0x04; - txbuf[20] = 0x00; - txbuf[21] = 0x02; - - // Typically 48 bytes (94 for IPv6) - for (int i = 42; i < 64; i++) - { - txbuf[i] = 0x00; - } + memcpy(&txbuf[ARP_TARGET_MAC_IDX], &rxbuf[22], MACADDR_NUM_BYTES); + memcpy(&txbuf[ARP_TARGET_IP_IDX], &rxbuf[28], IP_ADDR_SIZE); - return 64; -} + memset(&txbuf[ARP_HEADER_END], 0, (ETH_FRAME_MIN_SIZE - ARP_HEADER_END)); + return ETH_FRAME_MIN_SIZE; +} static int is_valid_arp_packet(const unsigned char rxbuf[nbytes], unsigned nbytes, const unsigned char own_ip_addr[4]) { - if (rxbuf[12] != 0x08 || rxbuf[13] != 0x06) + if (memcmp(&rxbuf[ETH_TYPE_IDX], eth_arp_type, sizeof(eth_arp_type)) != 0) return 0; debug_printf("ARP packet received\n"); - if (((const unsigned int *) rxbuf)[3] != 0x01000608) - { - debug_printf("Invalid et_htype\n"); - return 0; - } - if (((const unsigned int *) rxbuf)[4] != 0x04060008) + if (memcmp(&rxbuf[ARP_HW_TYPE_IDX], arp_body, sizeof(arp_body)) != 0) { - debug_printf("Invalid ptype_hlen\n"); + debug_printf("Invalid htype_ptype_hlen\n"); return 0; } - if ((((const unsigned int *) rxbuf)[5] & 0xFFFF) != 0x0100) + if (memcmp(&rxbuf[ARP_OPCODE_IDX], arp_request, sizeof(arp_request)) != 0) { debug_printf("Not a request\n"); return 0; } - for (int i = 0; i < 4; i++) + if (memcmp(&rxbuf[ARP_TARGET_IP_IDX], own_ip_addr, IP_ADDR_SIZE) != 0) { - if (rxbuf[38 + i] != own_ip_addr[i]) - { - debug_printf("Not for us\n"); - return 0; - } + debug_printf("Not for us\n"); + return 0; } return 1; } - static int build_icmp_response(unsigned char rxbuf[], unsigned char txbuf[], const unsigned char own_mac_addr[MACADDR_NUM_BYTES], const unsigned char own_ip_addr[4]) { - unsigned icmp_checksum; - int datalen; - int totallen; - const int ttl = 0x40; - int pad; + int totallen = NTOHS(rxbuf, IP_TOTAL_LEN_IDX); + int datalen = totallen - (IP_HEADER_LEN + ICMP_HEADER_LEN); - // Precomputed empty IP header checksum (inverted, bytereversed and shifted right) - unsigned ip_checksum = 0x0185; + memcpy(&txbuf[DST_MAC_IDX], &rxbuf[SRC_MAC_IDX], MACADDR_NUM_BYTES); + memcpy(&txbuf[SRC_MAC_IDX], own_mac_addr, MACADDR_NUM_BYTES); + memcpy(&txbuf[ETH_TYPE_IDX], eth_ip_type, sizeof(eth_ip_type)); - for (int i = 0; i < MACADDR_NUM_BYTES; i++) - { - txbuf[i] = rxbuf[6 + i]; - } - for (int i = 0; i < 4; i++) - { - txbuf[30 + i] = rxbuf[26 + i]; - } - icmp_checksum = byterev(((const unsigned int *) rxbuf)[9]) >> 16; - for (int i = 0; i < 4; i++) - { - txbuf[38 + i] = rxbuf[38 + i]; - } - totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; - datalen = totallen - 28; - for (int i = 0; i < datalen; i++) - { - txbuf[42 + i] = rxbuf[42+i]; - } + memcpy(&txbuf[IP_VERSION_IDX], ip_icmp_ping_version, sizeof(ip_icmp_ping_version)); + txbuf[IP_TOTAL_LEN_IDX] = (totallen >> 8) & 0xFF; + txbuf[IP_TOTAL_LEN_IDX + 1] = totallen & 0xFF; - for (int i = 0; i < MACADDR_NUM_BYTES; i++) - { - txbuf[6 + i] = own_mac_addr[i]; - } - ((unsigned int *) txbuf)[3] = 0x00450008; - totallen = byterev(28 + datalen) >> 16; - ((unsigned int *) txbuf)[4] = totallen; - ip_checksum += totallen; - ((unsigned int *) txbuf)[5] = 0x01000000 | (ttl << 16); - ((unsigned int *) txbuf)[6] = 0; - for (int i = 0; i < 4; i++) - { - txbuf[26 + i] = own_ip_addr[i]; - } - ip_checksum += (own_ip_addr[0] | own_ip_addr[1] << 8); - ip_checksum += (own_ip_addr[2] | own_ip_addr[3] << 8); - ip_checksum += txbuf[30] | (txbuf[31] << 8); - ip_checksum += txbuf[32] | (txbuf[33] << 8); + memcpy(&txbuf[IP_ID_IDX], ip_body, sizeof(ip_body)); - txbuf[34] = 0x00; - txbuf[35] = 0x00; + memset(&txbuf[IP_CHECKSUM_IDX], 0, 2); - icmp_checksum = (icmp_checksum + 0x0800); - icmp_checksum += icmp_checksum >> 16; - txbuf[36] = icmp_checksum >> 8; - txbuf[37] = icmp_checksum & 0xFF; + memcpy(&txbuf[IP_SRC_ADDR_IDX], own_ip_addr, IP_ADDR_SIZE); + memcpy(&txbuf[IP_DST_ADDR_IDX], &rxbuf[IP_SRC_ADDR_IDX], IP_ADDR_SIZE); - while (ip_checksum >> 16) - { - ip_checksum = (ip_checksum & 0xFFFF) + (ip_checksum >> 16); - } - ip_checksum = byterev(~ip_checksum) >> 16; - txbuf[24] = ip_checksum >> 8; - txbuf[25] = ip_checksum & 0xFF; + unsigned ip_checksum = checksum_ip(txbuf); + HTONS(txbuf, IP_CHECKSUM_IDX, ip_checksum); - for (pad = 42 + datalen; pad < 64; pad++) + memcpy(&txbuf[ICMP_TYPE_CODE_IDX], icmp_echo_reply, sizeof(icmp_echo_reply)); + memcpy(&txbuf[ICMP_ID_IDX], &rxbuf[ICMP_ID_IDX], 4); + memcpy(&txbuf[ICMP_DATA_IDX], &rxbuf[ICMP_DATA_IDX], datalen); + + unsigned icmp_checksum = NTOHS(rxbuf, ICMP_CHECKSUM_IDX); + // Calculate ICMP checksum, 0x800 adjusts for change in type between request and reply + icmp_checksum = (icmp_checksum + 0x0800); + icmp_checksum += (icmp_checksum >> 16); + HTONS(txbuf, ICMP_CHECKSUM_IDX, icmp_checksum); + + int pad; + for (pad = (ICMP_DATA_IDX + datalen); pad < ETH_FRAME_MIN_SIZE; pad++) { txbuf[pad] = 0x00; } @@ -197,32 +221,28 @@ static int is_valid_icmp_packet(const unsigned char rxbuf[nbytes], unsigned nbytes, const unsigned char own_ip_addr[4]) { - unsigned totallen; - if (rxbuf[23] != 0x01) + if ((memcmp(&rxbuf[ETH_TYPE_IDX], eth_ip_type, sizeof(eth_ip_type)) != 0) || (rxbuf[IP_PROTOCOL_IDX] != IP_ICMP_PROTOCOL)) return 0; debug_printf("ICMP packet received\n"); - if (((const unsigned int *) rxbuf)[3] != 0x00450008) + if (memcmp(&rxbuf[IP_VERSION_IDX], ip_icmp_ping_version, sizeof(ip_icmp_ping_version)) != 0) { - debug_printf("Invalid et_ver_hdrl_tos\n"); + debug_printf("Invalid IP version\n"); return 0; } - if ((((const unsigned int *) rxbuf)[8] >> 16) != 0x0008) + if (memcmp(&rxbuf[ICMP_TYPE_CODE_IDX], icmp_echo_request, sizeof(icmp_echo_request)) != 0) { debug_printf("Invalid type_code\n"); return 0; } - for (int i = 0; i < 4; i++) + if (memcmp(&rxbuf[IP_DST_ADDR_IDX], own_ip_addr, IP_ADDR_SIZE) != 0) { - if (rxbuf[30 + i] != own_ip_addr[i]) - { - debug_printf("Not for us\n"); - return 0; - } + debug_printf("Not for us\n"); + return 0; } - totallen = byterev(((const unsigned int *) rxbuf)[4]) >> 16; + unsigned totallen = NTOHS(rxbuf, IP_TOTAL_LEN_IDX); if (nbytes > 60 && nbytes != totallen + 14) { debug_printf("Invalid size (nbytes:%d, totallen:%d)\n", nbytes, totallen+14); @@ -257,12 +277,14 @@ void icmp_server(client ethernet_cfg_if cfg, cfg.add_macaddr_filter(index, 0, macaddr_filter); // Only allow ARP and IP packets to the app - cfg.add_ethertype_filter(index, 0x0806); - cfg.add_ethertype_filter(index, 0x0800); + cfg.add_ethertype_filter(index, ETH_FRAME_TYPE_ARP); + cfg.add_ethertype_filter(index, ETH_FRAME_TYPE_IP); + + debug_printf("ICMP server started at MAC %x:%x:%x:%x:%x:%x, IP %d.%d.%d.%d\n", + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5], + ip_address[0], ip_address[1], ip_address[2], ip_address[3]); - debug_printf("ICMP server started at MAC: %x:%x:%x:%x:%x:%x, on IP: %d:%d:%d:%d\n", - mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], - ip_address[0], ip_address[1], ip_address[2], ip_address[3]); while (1) { select { @@ -273,9 +295,10 @@ void icmp_server(client ethernet_cfg_if cfg, rx.get_packet(packet_info, rxbuf, ETHERNET_MAX_PACKET_SIZE); if (packet_info.type != ETH_DATA) - continue; - - if (is_valid_arp_packet(rxbuf, packet_info.len, ip_address)) + { + debug_printf("Link: %s, speed %d\n", rxbuf[0] ? "up" : "down", rxbuf[1]); + } + else if (is_valid_arp_packet(rxbuf, packet_info.len, ip_address)) { int len = build_arp_response(rxbuf, txbuf, mac_address, ip_address); tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES); @@ -291,5 +314,3 @@ void icmp_server(client ethernet_cfg_if cfg, } } } - - From 0e4e7cd4ec3570c6b826ee2e9299ec0dbbf4137b Mon Sep 17 00:00:00 2001 From: Humphrey Bucknell Date: Fri, 29 Aug 2025 09:24:59 +0100 Subject: [PATCH 17/18] Fixing bringup test 8b select --- tests/bringup_xk_eth_xu316_dual_100m/src/main.xc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc index 1923e673..958505d9 100644 --- a/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc +++ b/tests/bringup_xk_eth_xu316_dual_100m/src/main.xc @@ -29,7 +29,7 @@ port p_phy0_clk = PHY_0_CLK_50M; // PHY 1 - Clock slave port p_phy1_rxd_0 = PHY_1_RXD_0; port p_phy1_rxd_1 = PHY_1_RXD_1; -#if PHY1_USE_8B +#if PHY1_8B_TX port p_phy1_txd_0 = PHY_1_TXD_8BIT; in port p_unused_0 = PHY_1_TXD_0; // set to Hi-Z in port p_unused_1 = PHY_1_TXD_1; @@ -133,7 +133,7 @@ int main() #if PHY1 { // If Tx pins for 8b and 1b commoned, then ensure unused ports are Hi-Z -#if PHY1_USE_8B +#if PHY1_8B_TX p_unused_0 :> void; p_unused_1 :> void; #else From acad58b6b96ce975d0eae6f5c40fd484242232d8 Mon Sep 17 00:00:00 2001 From: Shuchita Khare Date: Thu, 11 Sep 2025 10:09:16 +0100 Subject: [PATCH 18/18] Add support for 8b tx in sim tests - Extend test_tx, test_time_tx and test_rmii_timing to test 8b TX configs - Updated the TX IFG calculation for 8b TX, although, the turnaround time between consecutive TX is greater than the required IFG wait so the IFG wait is never really exercised to 8b TX --- lib_ethernet/src/rmii_master.xc | 37 ++++++++++---------- tests/helpers.cmake | 8 ++++- tests/helpers.py | 22 +++++++++++- tests/include/ports_rmii.h | 46 +++++++++++++++---------- tests/rmii_phy.py | 26 +++++++------- tests/test_rmii_restart/CMakeLists.txt | 2 +- tests/test_rmii_timing/CMakeLists.txt | 2 +- tests/test_rmii_timing/test_params.json | 8 +++-- tests/test_rx/CMakeLists.txt | 2 +- tests/test_shaper/CMakeLists.txt | 2 +- tests/test_time_rx_tx/CMakeLists.txt | 2 +- tests/test_time_tx/CMakeLists.txt | 2 +- tests/test_time_tx/test_params.json | 1 + tests/test_timestamp_tx/CMakeLists.txt | 2 +- tests/test_tx/CMakeLists.txt | 2 +- tests/test_tx/src/main.xc | 18 +++++----- tests/test_tx/test_params.json | 4 ++- tests/test_tx_ifg/CMakeLists.txt | 2 +- 18 files changed, 116 insertions(+), 72 deletions(-) diff --git a/lib_ethernet/src/rmii_master.xc b/lib_ethernet/src/rmii_master.xc index 89f411de..3db618ea 100644 --- a/lib_ethernet/src/rmii_master.xc +++ b/lib_ethernet/src/rmii_master.xc @@ -77,9 +77,11 @@ // So the RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b is (96 + 30), and // RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b is (96 + 62) for frames with no tail bytes and (96 + 38) for frames with tail bytes. -// For 8b TXD port, we output 8b of data pins at a time (one byte on the wire). So after the last OUT we have TR and SR full which is two wire bytes, -// we have 16 timer ticks until it is empty. There are 4 slots after the last OUT in the ASM before we return to callee. -// TODO work this out properly. + +// For 8b TXD port, 32 bits are output on the port, 8bits per 20ns RMII clock tick, so 4 ticks to output one 32 bit word. +// when the last OUT for the CRC word returns, there's 3 ticks before the last word goes from the transfer register to shift register +// and another 4 ticks before the full shift register is shifted out on the wire. So, a total of (3+4)*20ns = 140ns = 14 reference timer ticks +// between the last OUT for CRC word returning and the last bit shifted on the wire, which is when TX_EN goes low. // Further, there's an adjustment needed due to the fact that // 1. The instruction that reads the timer is in fact the next instruction adter the out of the CRC word. @@ -99,10 +101,10 @@ #endif #ifndef RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b - #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b (12) // In reference timer ticks + #define RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b (0) // In reference timer ticks #endif -#define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) //TODO Work me out +#define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_8b (96 + 14 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_8b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_4b (96 + 30 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_4b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_NO_TAIL_BYTES (96 + 62 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) #define RMII_ETHERNET_IFG_AS_REF_CLOCK_COUNT_1b_TAIL_BYTES (96 + 38 - RMII_ETHERNET_IFG_DELAY_ADJUSTMENT_1b) @@ -819,15 +821,6 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, unsigned * unsafe dptr = &buf->data[0]; unsigned * unsafe wrap_ptr = mii_get_wrap_ptr(tx_mem);; - // Check that we are out of the inter-frame gap - unsigned now; - ifg_tmr :> now; - unsigned wait = check_if_ifg_wait_required(last_frame_end_time, ifg_time, now); - if(wait) - { - ifg_tmr when timerafter(ifg_time) :> ifg_time; - } - // Check to see if we need to wrap or not int first_chunk_size = buf->length; int wrap_size = buf->length - ((int)wrap_ptr - (int)dptr); @@ -843,20 +836,27 @@ unsafe unsigned rmii_transmit_packet_8b(mii_mempool_t tx_mem, ifg_tmr :> time; } + // Check that we are out of the inter-frame gap + unsigned now; + ifg_tmr :> now; + unsigned wait = check_if_ifg_wait_required(last_frame_end_time, ifg_time, now); + if(wait) + { + ifg_tmr when timerafter(ifg_time) :> ifg_time; + } + // Tx all stuff incl preamble and CRC if(buf->length > 5){ // The ASM always transmits at least 5 bytes. Less than that will break the // timing on the very tight loops so check in XC before we get there. rmii_master_tx_pins_8b_asm(dptr, first_chunk_size, p_mii_txd, lookup_8b_tx, poly, wrap_ptr, wrap_size); } + ifg_tmr :> ifg_time; - if (!MII_TX_TIMESTAMP_END_OF_PACKET && buf->timestamp_id) { + if (MII_TX_TIMESTAMP_END_OF_PACKET && buf->timestamp_id) { ifg_tmr :> time; } - - ifg_tmr :> ifg_time; - return time; } @@ -1107,6 +1107,7 @@ unsafe void rmii_master_tx_pins(mii_mempool_t tx_mem_lp, unsigned bit_pos_0 = (unsigned)tx_port_pins & 0xffff; unsigned bit_pos_1 = (unsigned)tx_port_pins >> 16; if(tx_port_width == 8){ + //printf("bit_pos_0 = %d, bit_pos_1 = %d\n", bit_pos_0, bit_pos_1); init_8b_tx_lookup(lookup_8b_tx, bit_pos_0, bit_pos_1); } diff --git a/tests/helpers.cmake b/tests/helpers.cmake index 8d22a769..242f7d2c 100644 --- a/tests/helpers.cmake +++ b/tests/helpers.cmake @@ -23,7 +23,7 @@ macro(set_app_hw_target arch) set(APP_HW_TARGET ${target}) endmacro() -macro(set_app_tx_width tx_width) +macro(set_app_tx_width) if(tx_width STREQUAL "4b_lower") list(APPEND APP_COMPILER_FLAGS_${config} -DTX_WIDTH=4) list(APPEND APP_COMPILER_FLAGS_${config} -DTX_USE_LOWER_2B=1) @@ -32,6 +32,12 @@ macro(set_app_tx_width tx_width) list(APPEND APP_COMPILER_FLAGS_${config} -DTX_USE_UPPER_2B=1) elseif(tx_width STREQUAL "1b") list(APPEND APP_COMPILER_FLAGS_${config} -DTX_WIDTH=1) + elseif(tx_width MATCHES "^8b_") + string(REPLACE "_" ";" parts ${tx_width}) + list(GET parts 0 width) + list(GET parts 1 bit0_pos) + list(GET parts 2 bit1_pos) + list(APPEND APP_COMPILER_FLAGS_${config} -DTX_WIDTH=8 -DTX8_BIT_0=${bit0_pos} -DTX8_BIT_1=${bit1_pos}) endif() endmacro() diff --git a/tests/helpers.py b/tests/helpers.py index 41c2c75a..e244aaa6 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -343,6 +343,13 @@ def get_rmii_rx_phy(tx_width, clk, **kwargs): rx_rmii_phy = get_rmii_1b_port_rx_phy(clk, **kwargs ) + elif tx_width.startswith("8b_"): + bit0_pos = int(tx_width.split("_")[1]) + bit1_pos = int(tx_width.split("_")[2]) + rx_rmii_phy = get_rmii_8b_port_rx_phy(clk, + [bit0_pos, bit1_pos], + **kwargs + ) else: assert False, f"get_rmii_rx_phy(): Invalid tx_width {tx_width}" return rx_rmii_phy @@ -369,10 +376,15 @@ def get_rmii_1b_port_tx_phy(clk, **kwargs): return phy def get_rmii_4b_port_rx_phy(clk, txd_4b_port_pin_assignment, **kwargs): + assert txd_4b_port_pin_assignment in ["lower_2b", "upper_2b"], f"Invalid txd_4b_port_pin_assignment {txd_4b_port_pin_assignment}. Only 'lower_2b' and 'upper_2b' are supported" + if txd_4b_port_pin_assignment == "lower_2b": + bit_positions = [0,1] + else: + bit_positions = [2,3] phy = RMiiReceiver('tile[0]:XS1_PORT_4B', 'tile[0]:XS1_PORT_1L', clk, - txd_4b_port_pin_assignment=txd_4b_port_pin_assignment, + pin_assignment=bit_positions, **kwargs ) return phy @@ -385,3 +397,11 @@ def get_rmii_1b_port_rx_phy(clk, **kwargs): ) return phy +def get_rmii_8b_port_rx_phy(clk, bit_positions, **kwargs): + phy = RMiiReceiver('tile[0]:XS1_PORT_8C', + 'tile[0]:XS1_PORT_1L', + clk, + pin_assignment=bit_positions, + **kwargs + ) + return phy diff --git a/tests/include/ports_rmii.h b/tests/include/ports_rmii.h index a583a193..746252e6 100644 --- a/tests/include/ports_rmii.h +++ b/tests/include/ports_rmii.h @@ -10,7 +10,7 @@ #define RX_USE_UPPER_2B (0) #endif -#if (!defined TX_WIDTH || (TX_WIDTH != 4 && TX_WIDTH != 1)) +#if (!defined TX_WIDTH || (TX_WIDTH != 4 && TX_WIDTH != 1 && TX_WIDTH != 8)) #warning TX_WIDTH not defined. Setting default to TX_WIDTH = 4 and USE_LOWER_2B #define TX_WIDTH (4) #define TX_USE_LOWER_2B (1) @@ -44,28 +44,38 @@ port p_eth_rxd_1 = on tile[0]:XS1_PORT_1B; #if TX_WIDTH == 4 -#if ((TX_USE_LOWER_2B == 1) && (TX_USE_UPPER_2B == 1)) - #error Both TX_USE_LOWER_2B and TX_USE_UPPER_2B set -#endif + #if ((TX_USE_LOWER_2B == 1) && (TX_USE_UPPER_2B == 1)) + #error Both TX_USE_LOWER_2B and TX_USE_UPPER_2B set + #endif -#if ((TX_USE_LOWER_2B == 0) && (TX_USE_UPPER_2B == 0)) - #error Both TX_USE_LOWER_2B and TX_USE_UPPER_2B are 0 when TX_WIDTH is 4 -#endif + #if ((TX_USE_LOWER_2B == 0) && (TX_USE_UPPER_2B == 0)) + #error Both TX_USE_LOWER_2B and TX_USE_UPPER_2B are 0 when TX_WIDTH is 4 + #endif -port p_eth_txd_0 = on tile[0]:XS1_PORT_4B; -#define p_eth_txd_1 null -#if TX_USE_LOWER_2B - #define TX_PINS USE_LOWER_2B -#elif TX_USE_UPPER_2B - #define TX_PINS USE_UPPER_2B -#endif + port p_eth_txd_0 = on tile[0]:XS1_PORT_4B; + #define p_eth_txd_1 null + #if TX_USE_LOWER_2B + #define TX_PINS USE_LOWER_2B + #elif TX_USE_UPPER_2B + #define TX_PINS USE_UPPER_2B + #endif #elif TX_WIDTH == 1 -port p_eth_txd_0 = on tile[0]:XS1_PORT_1C; -port p_eth_txd_1 = on tile[0]:XS1_PORT_1D; -#define TX_PINS 0 + port p_eth_txd_0 = on tile[0]:XS1_PORT_1C; + port p_eth_txd_1 = on tile[0]:XS1_PORT_1D; + #define TX_PINS 0 +#elif TX_WIDTH == 8 + port p_eth_txd_0 = on tile[0]:XS1_PORT_8C; + #ifndef TX8_BIT_0 + #error TX8_BIT_0 not defined + #endif + #ifndef TX8_BIT_1 + #error TX8_BIT_1 not defined + #endif + #define TX_PINS RMII_8B_PINS_INITIALISER(TX8_BIT_0, TX8_BIT_1) + #define p_eth_txd_1 null #else -#error invalid TX_WIDTH + #error invalid TX_WIDTH #endif port p_eth_clk = on tile[0]: XS1_PORT_1J; diff --git a/tests/rmii_phy.py b/tests/rmii_phy.py index 3339397c..caa0903d 100644 --- a/tests/rmii_phy.py +++ b/tests/rmii_phy.py @@ -253,7 +253,7 @@ def run(self): class RMiiRxPhy(px.SimThread): - def __init__(self, name, txd, txen, clock, txd_4b_port_pin_assignment, print_packets, packet_fn, verbose, test_ctrl): + def __init__(self, name, txd, txen, clock, pin_assignment, print_packets, packet_fn, verbose, test_ctrl): self._name = name # Check if txd is a string or an array of strings if not isinstance(txd, (list, tuple)): @@ -262,7 +262,7 @@ def __init__(self, name, txd, txen, clock, txd_4b_port_pin_assignment, print_pac self._txd = txd self._txen = txen self._clock = clock - self._txd_4b_port_pin_assignment = txd_4b_port_pin_assignment + self._pin_assignment = pin_assignment self._print_packets = print_packets self._verbose = verbose self._test_ctrl = test_ctrl @@ -274,11 +274,11 @@ def __init__(self, name, txd, txen, clock, txd_4b_port_pin_assignment, print_pac port_width_check = get_port_width_from_name(self._txd[1]) assert self._txd_port_width == port_width_check, f"When specifying 2 ports, both need to be of width 1bit. {self._txd}" else: - assert self._txd_port_width == 4, f"Only 4bit port allowed when specifying only 1 port. {self._txd}" + assert self._txd_port_width in [4,8], f"Only 4bit or 8bit port allowed when specifying only 1 port. {self._txd}" - if self._txd_port_width == 4: - assert self._txd_4b_port_pin_assignment == "lower_2b" or self._txd_4b_port_pin_assignment == "upper_2b", \ - f"Invalid txd_4b_port_pin_assignment (self._txd_4b_port_pin_assignment). Allowed values lower_2b or upper_2b" + if self._txd_port_width == 4: # extra checks for 4b port + assert self._pin_assignment == [0,1] or self._pin_assignment == [2,3], \ + f"Invalid pin assignment {self._pin_assignment} for 4b port. Only lower 2 pins [0,1] and upper 2 pins [2,3] supported. " self.expected_packets = None self.expect_packet_index = 0 @@ -287,7 +287,6 @@ def __init__(self, name, txd, txen, clock, txd_4b_port_pin_assignment, print_pac self.expected_packets = None self.expect_packet_index = 0 self.num_expected_packets = 0 - #print(f"self._txd = {self._txd}, self._txd_port_width = {self._txd_port_width}, self._txd_4b_port_pin_assignment = {self._txd_4b_port_pin_assignment}") def get_name(self): return self._name @@ -307,10 +306,10 @@ def set_expected_packets(self, packets): class RMiiReceiver(RMiiRxPhy): def __init__(self, txd, txen, clock, - txd_4b_port_pin_assignment="lower_2b", + pin_assignment = None, print_packets=False, packet_fn=None, verbose=False, test_ctrl=None): - super(RMiiReceiver, self).__init__('rmii', txd, txen, clock, txd_4b_port_pin_assignment, + super(RMiiReceiver, self).__init__('rmii', txd, txen, clock, pin_assignment, print_packets, packet_fn, verbose, test_ctrl) self._txen_val = None @@ -361,15 +360,18 @@ def run(self): if self._txen_val == 1: # Sample data if self._txd_port_width == 4: - if self._txd_4b_port_pin_assignment == "lower_2b": + if self._pin_assignment == [0,1]: # lower_2b crumb = xsi.sample_port_pins(self._txd[0]) & 0x3 #print(f"crumb = {crumb}") - else: + else: # upper_2b crumb = (xsi.sample_port_pins(self._txd[0]) >> 2) & 0x3 - else: # 2, 1bit ports + elif self._txd_port_width == 1: # 2, 1bit ports cr0 = xsi.sample_port_pins(self._txd[0]) & 0x1 cr1 = xsi.sample_port_pins(self._txd[1]) & 0x1 crumb = (cr1 << 1) | cr0 + elif self._txd_port_width == 8: # always lower 2 bits for 8bit port + data = xsi.sample_port_pins(self._txd[0]) + crumb = ((data >> self._pin_assignment[0]) & 0x1) | (((data >> self._pin_assignment[1]) & 0x1) << 1) nibble = nibble | (crumb << (crumb_index*2)) if crumb_index == 1: if self._verbose: diff --git a/tests/test_rmii_restart/CMakeLists.txt b/tests/test_rmii_restart/CMakeLists.txt index a8c24a3f..21ca160e 100644 --- a/tests/test_rmii_restart/CMakeLists.txt +++ b/tests/test_rmii_restart/CMakeLists.txt @@ -53,7 +53,7 @@ foreach(PROFILE ${profile_list}) set_app_rx_width(rx_width) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_rmii_timing/CMakeLists.txt b/tests/test_rmii_timing/CMakeLists.txt index b8e48af2..e61d58a0 100644 --- a/tests/test_rmii_timing/CMakeLists.txt +++ b/tests/test_rmii_timing/CMakeLists.txt @@ -53,7 +53,7 @@ foreach(PROFILE ${profile_list}) set_app_rx_width(rx_width) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_rmii_timing/test_params.json b/tests/test_rmii_timing/test_params.json index 5e66e0ea..c202efa5 100644 --- a/tests/test_rmii_timing/test_params.json +++ b/tests/test_rmii_timing/test_params.json @@ -3,13 +3,15 @@ {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_lower", "tx_width":"4b_lower"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_upper", "tx_width":"4b_lower"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"1b", "tx_width":"4b_lower"}, - + {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_lower", "tx_width":"4b_upper"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_upper", "tx_width":"4b_upper"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"1b", "tx_width":"4b_upper"}, - + {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_lower", "tx_width":"1b"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_upper", "tx_width":"1b"}, - {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"1b", "tx_width":"1b"} + {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"1b", "tx_width":"1b"}, + + {"phy":"rmii", "mac":"rt", "arch":["xs3"], "rx_width":"4b_lower", "tx_width":"8b_3_1"} ] } diff --git a/tests/test_rx/CMakeLists.txt b/tests/test_rx/CMakeLists.txt index 8bf3bc19..fcb4036d 100644 --- a/tests/test_rx/CMakeLists.txt +++ b/tests/test_rx/CMakeLists.txt @@ -53,7 +53,7 @@ foreach(PROFILE ${profile_list}) set_app_rx_width(rx_width) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_shaper/CMakeLists.txt b/tests/test_shaper/CMakeLists.txt index ef2ba37f..09a87349 100644 --- a/tests/test_shaper/CMakeLists.txt +++ b/tests/test_shaper/CMakeLists.txt @@ -51,7 +51,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_time_rx_tx/CMakeLists.txt b/tests/test_time_rx_tx/CMakeLists.txt index 3bd16781..2b633e35 100644 --- a/tests/test_time_rx_tx/CMakeLists.txt +++ b/tests/test_time_rx_tx/CMakeLists.txt @@ -54,7 +54,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) set_app_rx_width(rx_width) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_time_tx/CMakeLists.txt b/tests/test_time_tx/CMakeLists.txt index 4f7ef4ef..ad0c0535 100644 --- a/tests/test_time_tx/CMakeLists.txt +++ b/tests/test_time_tx/CMakeLists.txt @@ -47,7 +47,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_time_tx/test_params.json b/tests/test_time_tx/test_params.json index 19cc511a..6874203b 100644 --- a/tests/test_time_tx/test_params.json +++ b/tests/test_time_tx/test_params.json @@ -11,6 +11,7 @@ {"phy":"rmii", "mac":"rt", "arch":["xs3"], "tx_width":"4b_lower"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "tx_width":"4b_upper"}, {"phy":"rmii", "mac":"rt", "arch":["xs3"], "tx_width":"1b"}, + {"phy":"rmii", "mac":"rt", "arch":["xs3"], "tx_width":"8b_1_5"}, {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"4b_lower"}, {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"4b_upper"}, diff --git a/tests/test_timestamp_tx/CMakeLists.txt b/tests/test_timestamp_tx/CMakeLists.txt index 61549fec..b48bc0ad 100644 --- a/tests/test_timestamp_tx/CMakeLists.txt +++ b/tests/test_timestamp_tx/CMakeLists.txt @@ -47,7 +47,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_tx/CMakeLists.txt b/tests/test_tx/CMakeLists.txt index edcfae60..e9b3817f 100644 --- a/tests/test_tx/CMakeLists.txt +++ b/tests/test_tx/CMakeLists.txt @@ -48,7 +48,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1) diff --git a/tests/test_tx/src/main.xc b/tests/test_tx/src/main.xc index b6d361e6..beb78549 100644 --- a/tests/test_tx/src/main.xc +++ b/tests/test_tx/src/main.xc @@ -12,16 +12,14 @@ port p_test_ctrl = on tile[0]: XS1_PORT_1C; #endif - - struct test_packet { int len; int step; int tagged; } test_packets[] = - { - { 60, 1, 0 }, - { ETHERNET_MAX_PACKET_SIZE, 5, 0 }, - { 60, 1, 1 }, - { ETHERNET_MAX_PACKET_SIZE, 5, 1 }, - }; +{ + { 60, 1, 0 }, + { ETHERNET_MAX_PACKET_SIZE, 5, 0 }, + { 60, 1, 1 }, + { ETHERNET_MAX_PACKET_SIZE, 5, 1 }, +}; void test_tx(client ethernet_tx_if tx, streaming chanend ? c_tx_hp) { @@ -135,7 +133,8 @@ int main() eth_rxclk, eth_txclk, 4000, 4000, ETHERNET_DISABLE_SHAPER); #elif RMII - on tile[0]: rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, + on tile[0]: { + rmii_ethernet_rt_mac( i_cfg, NUM_CFG_IF, i_rx_lp, NUM_RX_LP_IF, i_tx_lp, NUM_TX_LP_IF, c_rx_hp, c_tx_hp, @@ -153,6 +152,7 @@ int main() port_timing, 4000, 4000, ETHERNET_DISABLE_SHAPER); + } #endif on tile[0]: filler(0x1111); diff --git a/tests/test_tx/test_params.json b/tests/test_tx/test_params.json index 19cc511a..3bd4610b 100644 --- a/tests/test_tx/test_params.json +++ b/tests/test_tx/test_params.json @@ -14,6 +14,8 @@ {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"4b_lower"}, {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"4b_upper"}, - {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"1b"} + {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"1b"}, + {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"8b_6_7"}, + {"phy":"rmii", "mac":"rt_hp", "arch":["xs3"], "tx_width":"8b_5_1"} ] } diff --git a/tests/test_tx_ifg/CMakeLists.txt b/tests/test_tx_ifg/CMakeLists.txt index 492ec0a1..6f1c69f7 100644 --- a/tests/test_tx_ifg/CMakeLists.txt +++ b/tests/test_tx_ifg/CMakeLists.txt @@ -49,7 +49,7 @@ foreach(PROFILE ${profile_list}) set(APP_COMPILER_FLAGS_${config} ${COMPILER_FLAGS_COMMON}) - set_app_tx_width(tx_width) + set_app_tx_width() string(FIND "${PROFILE}" "rt" position) if(position GREATER -1)