From a10420e015e93826e83c9e11906631b8e508b4c5 Mon Sep 17 00:00:00 2001 From: Cedric Roux Date: Sun, 22 Jun 2025 18:59:38 +0200 Subject: [PATCH] T hacks: add ofdm-plot Used to... surprise... plot OFDM data as output (or input) by gnb! Limitation: only one antenna. --- common/utils/T/tracer/hacks/Makefile | 11 +- common/utils/T/tracer/hacks/plot-ofdm.c | 351 ++++++++++++++++++++++++ 2 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 common/utils/T/tracer/hacks/plot-ofdm.c diff --git a/common/utils/T/tracer/hacks/Makefile b/common/utils/T/tracer/hacks/Makefile index f693094fb6..2c96e67823 100644 --- a/common/utils/T/tracer/hacks/Makefile +++ b/common/utils/T/tracer/hacks/Makefile @@ -3,7 +3,7 @@ CFLAGS=-Wall -g -pthread -DT_TRACER -I. -I.. LIBS=-lX11 -lm -lpng -lXft -all: dump_nack_signal time_meas timeplot multi-rru-clean ant0 +all: dump_nack_signal time_meas timeplot multi-rru-clean ant0 plot-ofdm dump_nack_signal: ../utils.o ../database.o ../configuration.o ../event.o \ dump_nack_signal.o @@ -26,6 +26,13 @@ ant0: ant0.o ../utils.o ../database.o ../configuration.o ../event.o ant0.o: ant0.c $(CC) $(CFLAGS) -c -o $@ $< -march=native -ffast-math -O3 +plot-ofdm: plot-ofdm.o ../utils.o + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) -lfftw3f + +#plot-ofdm needs good compilation +plot-ofdm.o: plot-ofdm.c + $(CC) $(CFLAGS) -c -o $@ $< -march=native -ffast-math -O3 + .PHONY: all %.o: %.c @@ -33,4 +40,4 @@ ant0.o: ant0.c clean: rm -f *.o core dump_nack_signal time_meas timeplot multi-rru-clean \ - ant0 + ant0 plot-ofdm diff --git a/common/utils/T/tracer/hacks/plot-ofdm.c b/common/utils/T/tracer/hacks/plot-ofdm.c new file mode 100644 index 0000000000..3f6e0b9dce --- /dev/null +++ b/common/utils/T/tracer/hacks/plot-ofdm.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +const int width = 14 * 20 * 4; +int height; + +/* when vzoom = 1 each pixel represents 1 RE + * when 2, it is max of 2 vertical REs + * useful if height of plot is to big + * (with 106 RB we already reach 106*12 = 1272 pixels, so with even bigger + * bandwidths...) + */ +int vzoom = 1; +/* when hzoom = 1 each pixel represents 1 RE + * when 2, it is max of 2 horizontal REs + * useful to slow down display + */ +int hzoom = 1; +/* intensity is to scale the input + * normally, as float, cabs(input) is in [0..1] + * to get pixel color we *255 + * but maybe it's too intense, or not enough, + * so let's have a control the user can set at will + */ +float intensity = 255; + +pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + +void lock(void) +{ + pthread_mutex_lock(&m); +} + +void unlock(void) +{ + pthread_mutex_unlock(&m); +} + +typedef struct { + Display *d; + Window w; + XImage *img; + unsigned char *img_data; + int img_pos; +} gui_t; + +/* delay management begin */ + +long double frame_time; + +void time_start(void) +{ + struct timespec l; + if (clock_gettime(CLOCK_REALTIME, &l)) abort(); + frame_time = l.tv_sec * 1000000000. + l.tv_nsec; +} + +void delay(int fps) +{ + struct timespec t; + struct timespec delay; + long double cur_time; + + frame_time += 1000000000. / fps; + + if (clock_gettime(CLOCK_REALTIME, &t)) abort(); + + cur_time = t.tv_sec * 1000000000. + t.tv_nsec; + + if (frame_time < cur_time) { fprintf(stderr, "ERROR: we late\n"); return; } + + delay.tv_nsec = ((uint64_t)(frame_time-cur_time)) % 1000000000; + delay.tv_sec = ((uint64_t)(frame_time-cur_time)) / 1000000000; + + if (pselect(0, NULL, NULL, NULL, &delay, NULL)) { fprintf(stderr, "ERROR: pselect fails\n"); exit(1); } +} + +/* delay management end */ + +static void *gui_thread(void *_g) +{ + time_start(); + + gui_t *g = _g; + while (1) { + XEvent e; while (XPending(g->d)) XNextEvent(g->d, &e); + lock(); + XPutImage(g->d, g->w, DefaultGC(g->d, DefaultScreen(g->d)), g->img, 0, 0, 0, 0, + width, height/vzoom); + unlock(); + delay(10); + } + return 0; +} + +unsigned char re_to_pixel(float re) +{ + float v = fminf(255, re * intensity); + return v; +} + +unsigned char mix(unsigned char a, unsigned char b) +{ + return a > b ? a : b; +} + +void usage(void) +{ + printf("options:\n"); + printf(" -f input is float, not s16\n"); + printf(" -srate set sampling rate (default 61440000)\n"); + printf(" -prb set number of PRBs (default 106)\n"); + printf(" -mu set mu (default 1, 0 is for 15KHz, 1 for 30KHz, etc.)\n"); + printf(" -no-fft don't do fft, input is in frequency domain\n"); + printf(" -vzoom add some sort of vertical zoom, default 1, passing 2 divides height by 2, etc.\n"); + printf(" -hzoom add some sort of speed control, default 1, passing 2 means 2 REs per pixel, etc.\n"); + printf(" -delay delay 'delay' microseconds between processing of each symbol\n"); + printf(" -intensity set intensity of data pixel (default 255.)\n"); + printf(" -skip skip 'count' samples at beginning\n"); + exit(0); +} + +int main(int n, char **v) +{ + int input_is_float = 0; + int no_fft = 0; + int delay = 0; + int skip = 0; + int symbols_per_subframe; + long sampling_rate = 61440000; + int mu = 1; + int prb = 106; + + /* parse command line arguments */ + for (int i = 1; i < n; i++) { + if (!strcmp(v[i], "-f")) { input_is_float = 1; continue; } + if (!strcmp(v[i], "-no-fft")) { no_fft = 1; continue; } + if (!strcmp(v[i], "-vzoom")) { if (i>n-2) usage(); vzoom = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-hzoom")) { if (i>n-2) usage(); hzoom = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-delay")) { if (i>n-2) usage(); delay = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-intensity")) { if (i>n-2) usage(); intensity = atof(v[++i]); continue; } + if (!strcmp(v[i], "-srate")) { if (i>n-2) usage(); sampling_rate = atol(v[++i]); continue; } + if (!strcmp(v[i], "-prb")) { if (i>n-2) usage(); prb = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-mu")) { if (i>n-2) usage(); mu = atoi(v[++i]); continue; } + if (!strcmp(v[i], "-skip")) { if (i>n-2) usage(); skip = atoi(v[++i]); continue; } + usage(); + } + + symbols_per_subframe = 14 * (1 << mu); + + /* compute durations from sampling rate and mu (scs) */ + /* this is 38.211 5.3.1, a bit simplified/reordered */ + int ofdm_duration = sampling_rate / 15000 / (1 << mu); + int l0_cycle_prefix = sampling_rate / 480000 * (144*64 / (1 << mu) + 16*64) / 4096; + int cycle_prefix = sampling_rate / 480000 * (144*64 / (1 << mu)) / 4096; + + /* small sanity check */ + if (prb * 12 > ofdm_duration) { + printf("fatal: too many PRBs for this sampling rate / mu\n"); + exit(1); + } + + height = prb * 12; + + if (height % vzoom) { fprintf(stderr, "bad vzoom for this height, try 1, 2, 4, ...\n"); return 1; } + + gui_t g = { 0 }; + + /* open X11 display */ + g.d = XOpenDisplay(0); + if (!g.d) { + fprintf(stderr, "could not open display\n"); + return 1; + } + + /* create window */ + g.w = XCreateSimpleWindow(g.d, DefaultRootWindow(g.d), 0, 0, + width, height / vzoom, 0, WhitePixel(g.d, DefaultScreen(g.d)), + WhitePixel(g.d, DefaultScreen(g.d))); + XStoreName(g.d, g.w, "IQ"); + /* enable backing store */ + { + XSetWindowAttributes att; + att.backing_store = Always; + XChangeWindowAttributes(g.d, g.w, CWBackingStore, &att); + } + XSelectInput(g.d, g.w, + KeyPressMask | + ButtonPressMask | + ButtonReleaseMask | + PointerMotionMask | + ExposureMask | + StructureNotifyMask); + XMapWindow(g.d, g.w); + + g.img_data = calloc(1, width * height/vzoom * 4); if (!g.img_data) abort(); + g.img = XCreateImage(g.d, DefaultVisual(g.d, DefaultScreen(g.d)), 24 /*depth*/, + ZPixmap, 0 /*offset*/, (char*)g.img_data, width, height/vzoom, 8, 0); + + new_thread(gui_thread, &g); + + /* fft data */ + int fft_size = ofdm_duration; + float complex *in = fftwf_malloc(sizeof(float complex) * fft_size); + float complex *out = fftwf_malloc(sizeof(float complex) * fft_size); + fftwf_plan p_fft = fftwf_plan_dft_1d(fft_size, in, out, + FFTW_FORWARD, FFTW_ESTIMATE); + memset(in, 0, sizeof(float complex) * fft_size); + fftwf_execute(p_fft); + + int symbol = 0; + int back_color = 0; + int hpixel = 0; + + if (skip) { + if (input_is_float) { + float intime[skip * 2]; + if (fread(intime, skip * 8, 1, stdin) != 1) abort(); + } else { + short intime[skip * 2]; + if (fread(intime, skip * 4, 1, stdin) != 1) abort(); + } + } + + while (1) { + int cur_cycle_prefix; + if (symbol == 0 || symbol == 7 * (1 << mu)) + cur_cycle_prefix = l0_cycle_prefix; + else + cur_cycle_prefix = cycle_prefix; + + if (no_fft) goto without_fft; + + /* input signal is in time domain, timing aligned, do FFT */ + + /* read time domain data */ + if (input_is_float) { + float intime[(ofdm_duration + cur_cycle_prefix) * 2]; + if (fread(intime, (ofdm_duration + cur_cycle_prefix) * 8, 1, stdin) != 1) break; + + for (int i = 0; i < fft_size; i++) + /* skip cycle prefix */ + in[i] = intime[(cur_cycle_prefix + i) * 2] + I * intime[(cur_cycle_prefix + i) * 2 + 1]; + } else { + short intime[(ofdm_duration + cur_cycle_prefix) * 2]; + if (fread(intime, (ofdm_duration + cur_cycle_prefix) * 4, 1, stdin) != 1) break; + + for (int i = 0; i < fft_size; i++) + /* skip cycle prefix */ + in[i] = intime[(cur_cycle_prefix + i) * 2] / 2048. + I * intime[(cur_cycle_prefix + i) * 2 + 1] / 2048.; + } + + /* go to freq domain */ + fftwf_execute(p_fft); + + goto after_fft; + +without_fft: + /* input data is symbols, each containing full FFT data */ + if (input_is_float) { + float infreq[fft_size * 2]; + if (fread(infreq, fft_size * 2 * 4, 1, stdin) != 1) break; + + for (int i = 0; i < fft_size; i++) + out[i] = infreq[i * 2] + I * infreq[i * 2 + 1]; + } else { + short infreq[ofdm_duration * 2]; + if (fread(infreq, fft_size * 2 * 2, 1, stdin) != 1) break; + + for (int i = 0; i < fft_size; i++) + out[i] = infreq[i * 2] / 2048. + I * infreq[i * 2 + 1] / 2048.; + } + +after_fft: + lock(); + + /* extract data of this ofdm symbol */ + unsigned char *p = g.img_data + g.img_pos * 4; + int front_pos = (g.img_pos + 8) % width; + unsigned char *pfront = g.img_data + front_pos * 4; + for (int i = prb * 12 - 1; i >= 0; i -= vzoom) { + /* take the max */ + float out_val = 0; + for (int j = 0; j < vzoom; j++) { + int start = - prb * 12 / 2; + int pos = start + i + j; + /* neg freq are at the end of fft data array */ + int pos_in_fft = pos >= 0 + ? pos + : pos + fft_size; + float v = cabs(out[pos_in_fft]); + if (v > out_val) out_val = v; + } + int color = re_to_pixel(out_val); + /* grey level image mixed with some background changing every slot */ + unsigned char back_col[2][3] = { + { 100, 90, 90 }, + { 100, 60, 60, } + }; + if (hpixel == 0) { + p[0] = p[1] = p[2] = color; + } else { + p[0] = mix(p[0], color); + p[1] = mix(p[1], color); + p[2] = mix(p[2], color); + } + if (hpixel == hzoom - 1) { + p[0] = mix(p[0], back_col[back_color][0]); + p[1] = mix(p[1], back_col[back_color][1]); + p[2] = mix(p[2], back_col[back_color][2]); + } + p[3] = 255; + p += width * 4; + pfront[0] = 255; + pfront[1] = 128; + pfront[2] = 128; + pfront[3] = 255; + pfront += width * 4; + } + + unlock(); + + hpixel++; + if (hpixel == hzoom) { + hpixel = 0; + g.img_pos++; + if (g.img_pos == width) g.img_pos = 0; + } + + symbol++; + if (symbol == symbols_per_subframe) symbol = 0; + + /* switch back color every slot */ + if (symbol % 14 == 0) + back_color = 1 - back_color; + + if (delay) usleep(delay); + } + + return 0; +}