This document is a high-level overview over the L1 threading mechanism. ```mermaid flowchart TB ru_thread --> RFin[block rx_rf] --> UL{is UL slot?} UL -- yes --> wait_free_rx_tti --> fep_rx --> rx_nr_prach_ru --> msg:L1_tx_out UL -- no --> msg:L1_tx_out msg:L1_tx_out -- asnyc launch --> L1_tx_thread msg:L1_tx_out --> RFin ``` The main thread is `ru_thread()`. It blocks on reception of radio samples (either time domain or frequency domain). In the case of an UL slot, it waits that no more than N UL jobs are scheduled (via `wait_free_rx_tti()`, which waits on queue `L1_rx_out`, cf. the RX L1 processing further below). Then: - if the radio is time domain-based, it performs RX front-end processing (RX FEP -> `fep_rx()`, i.e. DFT) to reach a frequency domain representation of the RX signal, as well as does DFT for PRACH. - if the radio is frequency domain-based, nothing is done. Afterwards, it triggers TX processing by pushing a message into the FIFO queue `L1_tx_out`, which asynchronously starts a TX job in `L1_tx_thread()` (see below). After that, it blocks again on reception on the radio. ```mermaid flowchart TB L1_tx_thread --> TX_in[block L1_tx_out] --> NR_slot_indication subgraph tx_func NR_slot_indication --> msg:resp_L1 --> phy_procedures_gNB_tx --> ru_tx_func NR_slot_indication["run the scheduler: - monolithic: run_scheduler_monolithic() - nFAPI: send indication via pnf_send_slot_ind()"] msg:resp_L1 -- async launch --> L1_RX_thread end ru_tx_func --> TX_in ``` The `L1_tx_thread()` processes individual TX jobs sequentially, by waiting for new messages on queue `L1_tx_out`, signalling individual TX jobs. For each message, it calls `tx_func()` which does in order: - run the scheduler through `NR_slot_indication`, which corresponds to a "Slot.indication" in FAPI parlance. This runs the scheduler, and schedules a given slot (either downlink, uplink, or both). - trigger RX processing by pushing a message into the FIFO queue `resp_L1`, asynchronously starting an RX job in `L1_rx_thread()` (see below). - process the current L1 TX job through `phy_procedures_gNB_tx()` - write to the radio board via `ru_tx_func()`. After these steps, `tx_func()` return to `L1_tx_thread()`, which will wait for the next TX job. ```mermaid flowchart TB L1_rx_thread --> RX_in[block resp_L1] --> L1_nr_prach_proc subgraph rx_func L1_nr_prach_proc --> phase_comp{apply phase comp.?} phase_comp -- yes --> apply_nr_rotation --> phy_procedures_gNB_uespec_RX phase_comp -- no --> phy_procedures_gNB_uespec_RX phy_procedures_gNB_uespec_RX --> NR_ul_indication --> msg:L1_rx_out NR_ul_indication["run the scheduler: NR_UL_indication()"] msg:L1_rx_out -- async signal free --> ru_thread end msg:L1_rx_out --> RX_in ``` The `L1_rx_thread()` processes individual RX jobs sequentially. It waits for a new RX job through the queue `resp_L1`, and then calls `rx_func()`, which does in order: - run PRACH processing via `L1_nr_prach_proc()` - optionally apply rotation to the RX signal if phase compensation is to be applied - run the current L1 RX job through (`phy_procedures_gNB_uespec_RX()`), which notably includes PUCCH, PUSCH, SRS processing - call the scheduler through `NR_ul_indication()`, which corresponds to FAPI uplink messages (e.g., `RX_data.indication`, `CRC.indication`, `UCI.indication` etc.) - signal completion via FIFO queue `L1_rx_out()`, which tells `ru_thread()` that RX processing finished. The signalling of scheduler data is done through a variable `UL_INFO`, which is filled by `L1_nr_prach_proc()` (for PRACH) and `phy_procedures_gNB_uespec_RX()` (for PUCCH, PUSCH, SRS). After these steps, `rx_func()` returns to `L1_rx_thread()`, which will wait the next RX job. Note that while individual TX (RX) jobs are run sequentially through `L1_tx_thread()` (`L1_rx_thread()`), both TX and RX processing run in parallel.