Files
Jungfraujoch/tests/FPGAPTPTest.cpp
2025-09-08 20:28:59 +02:00

219 lines
7.7 KiB
C++

#include <catch2/catch_all.hpp>
#include <cstdint>
#include <cstring>
#include <arpa/inet.h> // htons, htonl
#include "../fpga/hls_simulation/hls_cores.h"
#include "../fpga/hls/ptp.cpp"
// Minimal 64-byte frame layout for a single-beat 512-bit AXI packet.
// We only care about:
// - Ethernet header (14 bytes) as padding
// - PTP common header fields: messageType nibble, flagField
// - preciseOriginTimestamp: ns(32) + seconds(48)
// Other fields are left as zero.
#pragma pack(push, 1)
struct EthernetPTP512 {
uint8_t eth_pad[14]; // Ethernet dest/src/type (unused in tests)
// PTP common header (we only fill what we need)
uint8_t ts_msgType; // transportSpecific (upper nibble) | messageType (lower nibble)
uint8_t versionPTP; // not used, keep zero
uint16_t messageLength; // not used
uint8_t domainNumber; // not used
uint8_t reserved1; // not used
uint16_t flagField; // network order
uint64_t correctionField; // not used
uint32_t reserved2; // not used
uint8_t sourcePortIdentity[10]; // not used
uint16_t sequenceId; // not used
uint8_t controlField; // not used
int8_t logMessageInterval; // not used
// payload for Sync/Follow_Up we care about: preciseOriginTimestamp
// Offset chosen to match your parser: ns first (32-bit), then seconds (48-bit).
uint32_t preciseOriginTimestamp_ns_be; // network order
uint8_t preciseOriginTimestamp_sec_be[6]; // 48-bit, big-endian
};
#pragma pack(pop)
// Helper: host-to-big-endian for 48-bit seconds into a 6-byte array.
static inline void htobe48(uint64_t host_val, uint8_t out[6]) {
// Keep low 48 bits
uint64_t v = host_val & 0x0000FFFFFFFFFFFFULL;
out[0] = static_cast<uint8_t>((v >> 40) & 0xFF);
out[1] = static_cast<uint8_t>((v >> 32) & 0xFF);
out[2] = static_cast<uint8_t>((v >> 24) & 0xFF);
out[3] = static_cast<uint8_t>((v >> 16) & 0xFF);
out[4] = static_cast<uint8_t>((v >> 8) & 0xFF);
out[5] = static_cast<uint8_t>(v & 0xFF);
}
// Build a 512-bit single-beat AXI PTP packet via a C struct, then cast to ap_uint<512>.
static packet_512_t make_ptp_packet(uint8_t message_type, bool two_step, uint64_t sec48, uint32_t ns32) {
ap_uint<512> word = 0;
auto frame = reinterpret_cast<EthernetPTP512 *>(&word);
// Byte 0 of PTP: transportSpecific=0, messageType in low nibble
frame->ts_msgType = (0x0 << 4) | (message_type & 0x0F);
// twoStepFlag: bit 1 (0x0002) in flagField (big-endian)
uint16_t flags = two_step ? 0x0002 : 0x0000;
frame->flagField = htons(flags);
// preciseOriginTimestamp: ns (32-bit BE), seconds (48-bit BE)
frame->preciseOriginTimestamp_ns_be = htonl(ns32);
htobe48(sec48, frame->preciseOriginTimestamp_sec_be);
packet_512_t pkt{};
pkt.data = word;
pkt.last = 1;
return pkt;
}
static void run_cycles(AXI_STREAM& s, int cycles,
volatile ap_uint<80>& counter_ns,
volatile ap_uint<32>& counter_error,
volatile ap_uint<1>& synced_to_ptp) {
for (int i = 0; i < cycles; ++i) {
ptp(s, counter_ns, counter_error, synced_to_ptp);
}
}
TEST_CASE("HLS_PTP_OneStep") {
AXI_STREAM eth_in;
volatile ap_uint<80> counter_ns = 0;
volatile ap_uint<32> counter_error = 0;
volatile ap_uint<1> synced_to_ptp = 0;
run_cycles(eth_in, 10, counter_ns, counter_error, synced_to_ptp);
uint64_t sec = 56789;
uint32_t ns = 999999999U;
auto pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/false, sec, ns);
eth_in.write(pkt);
run_cycles(eth_in, 1, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> combined = counter_ns; // upper 48 bits = sec, lower 32 bits = ns
uint64_t sec_read = (uint64_t)(combined >> 32);
uint32_t ns_read = (uint32_t)(combined & 0xFFFFFFFFu);
REQUIRE(synced_to_ptp.read() == 1);
CHECK(sec_read == sec);
CHECK(ns_read == ns);
}
TEST_CASE("HLS_PTP_OneStep_incr_1sec") {
AXI_STREAM eth_in;
volatile ap_uint<80> counter_ns = 0;
volatile ap_uint<32> counter_error = 0;
volatile ap_uint<1> synced_to_ptp = 0;
run_cycles(eth_in, 10, counter_ns, counter_error, synced_to_ptp);
uint64_t sec = 56789;
uint32_t ns = 999999999U;
auto pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/false, sec, ns);
eth_in.write(pkt);
run_cycles(eth_in, 2, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> combined = counter_ns; // upper 48 bits = sec, lower 32 bits = ns
uint64_t sec_read = (uint64_t)(combined >> 32);
uint32_t ns_read = (uint32_t)(combined & 0xFFFFFFFFu);
REQUIRE(synced_to_ptp.read() == 1);
CHECK(sec_read == 56789 + 1);
CHECK(ns_read == 4);
}
TEST_CASE("HLS_PTP_TwoStep") {
AXI_STREAM eth_in;
volatile ap_uint<80> counter_ns = 0;
volatile ap_uint<32> counter_error = 0;
volatile ap_uint<1> synced_to_ptp = 0;
run_cycles(eth_in, 5, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> before = counter_ns;
// Two-step Sync
auto sync_pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/true, /*sec*/5, /*ns*/500);
eth_in.write(sync_pkt);
run_cycles(eth_in, 1, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> after_sync = counter_ns;
REQUIRE(after_sync > before); // clock advanced by tick, but no snap to sec=5
uint64_t sec_sync = (uint64_t)(after_sync >> 32);
REQUIRE(sec_sync != 5);
// Follow_Up updates to its timestamp
uint64_t fu_sec = 123456;
uint32_t fu_ns = 3456;
auto follow_up_pkt = make_ptp_packet(/*message_type=*/0x8, /*two_step=*/true, fu_sec, fu_ns);
eth_in.write(follow_up_pkt);
run_cycles(eth_in, 1, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> combined = counter_ns;
uint64_t sec_read = (uint64_t)(combined >> 32);
uint32_t ns_read = (uint32_t)(combined & 0xFFFFFFFFu);
REQUIRE(synced_to_ptp.read() == 1);
CHECK(sec_read == fu_sec);
CHECK(ns_read == fu_ns);
}
TEST_CASE("HLS_PTP_OneStep_error") {
AXI_STREAM eth_in;
volatile ap_uint<80> counter_ns = 0;
volatile ap_uint<32> counter_error = 0;
volatile ap_uint<1> synced_to_ptp = 0;
run_cycles(eth_in, 10, counter_ns, counter_error, synced_to_ptp);
uint64_t sec = 100;
uint32_t ns = 100;
auto pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/false, sec, ns);
eth_in.write(pkt);
run_cycles(eth_in, 11, counter_ns, counter_error, synced_to_ptp);
ap_uint<80> combined = counter_ns; // upper 48 bits = sec, lower 32 bits = ns
uint64_t sec_read = (uint64_t)(combined >> 32);
uint32_t ns_read = (uint32_t)(combined & 0xFFFFFFFFu);
REQUIRE(synced_to_ptp.read() == 1);
CHECK(sec_read == 100);
CHECK(ns_read == 100 + 5 * 10);
ns = 100 + 5 * 11 - 7;
pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/false, sec, ns);
eth_in.write(pkt);
run_cycles(eth_in, 1, counter_ns, counter_error, synced_to_ptp);
CHECK(counter_error.read() == 7);
combined = counter_ns; // upper 48 bits = sec, lower 32 bits = ns
sec_read = (uint64_t)(combined >> 32);
ns_read = (uint32_t)(combined & 0xFFFFFFFFu);
CHECK(sec_read == 100);
CHECK(ns_read == ns);
}
TEST_CASE("HLS_PTP_2s_out_of_sync") {
AXI_STREAM eth_in;
volatile ap_uint<80> counter_ns = 0;
volatile ap_uint<32> counter_error = 0;
volatile ap_uint<1> synced_to_ptp = 0;
auto pkt = make_ptp_packet(/*message_type=*/0x0, /*two_step=*/false, /*sec*/100, /*ns*/0);
eth_in.write(pkt);
run_cycles(eth_in, 1, counter_ns, counter_error, synced_to_ptp);
REQUIRE(synced_to_ptp.read() == 1);
const uint64_t ticks_2s = (2ULL * 1000000000ULL) / 5ULL;
run_cycles(eth_in, (int)(ticks_2s + 2), counter_ns, counter_error, synced_to_ptp);
REQUIRE(synced_to_ptp.read() == 0);
}