├── .github └── workflows │ └── verify.yml ├── .gitmodules ├── .pylintrc ├── LICENSE ├── README.md ├── doc ├── channel_estimator.jpg ├── channel_estimator.pptx ├── frame_structure.jpg ├── frame_structure.pptx ├── iio_dma.txt ├── overview.jpg ├── overview.pptx ├── receiver_test_constellation_diagram.png ├── ressource_usage_7020.jpg └── ressource_usage_Ant5G.jpg ├── hdl ├── AXIS_FIFO.sv ├── AXI_lite_interface.sv ├── BWP_extractor.sv ├── CFO_calc.sv ├── CIC ├── DDS ├── Decimator_Correlator_PeakDetector.sv ├── Decimator_Correlator_PeakDetector_FFT.sv ├── Decimator_to_SSS_detector.sv ├── FFT ├── FFT_demod.sv ├── LFSR ├── PSS_correlator.sv ├── PSS_correlator_mr.sv ├── PSS_correlator_mr_old.sv ├── PSS_correlator_with_peak_detector.sv ├── PSS_detector.sv ├── PSS_detector_regmap.sv ├── Peak_detector.sv ├── SSS_detector.sv ├── atan.sv ├── atan2.sv ├── axi_ring_writer.sv ├── axil_interconnect_wrap_1x4.v ├── axis_axil_fifo.sv ├── axis_fifo_asym.sv ├── channel_estimator.sv ├── complex_multiplier ├── demap.sv ├── div.sv ├── dot_product.sv ├── frame_sync.sv ├── frame_sync_regmap.sv ├── receiver.sv ├── receiver_regmap.sv ├── ressource_grid_framer.sv ├── test_CFO_correction.sv └── verilog-axi ├── model ├── PSS_correlator.py └── peak_detector.py ├── requirements.txt ├── tests ├── 1876954_7680KSPS_srsRAN_Project_gnb_short_2.sigmf-data ├── 1876954_7680KSPS_srsRAN_Project_gnb_short_2.sigmf-meta ├── 30720KSPS_dl_signal.sigmf-data ├── 30720KSPS_dl_signal.sigmf-meta ├── 3627MHz_30720KSPS_short.sigmf-data ├── 3627MHz_30720KSPS_short.sigmf-meta ├── 763450KHz_7680KSPS_low_gain.sigmf-data ├── 763450KHz_7680KSPS_low_gain.sigmf-meta ├── 772850KHz_3840KSPS_low_gain.sigmf-data ├── 772850KHz_3840KSPS_low_gain.sigmf-meta ├── FFT1_in.txt ├── FFT2_in.txt ├── correlate.ipynb ├── demodulate.ipynb ├── gen_testsignals.ipynb ├── sine_lut_16_16.hex ├── test_CFO_calc.py ├── test_CFO_correction.py ├── test_Decimator_Correlator_PeakDetector.py ├── test_Decimator_Correlator_PeakDetector_FFT.py ├── test_Decimator_to_SSS_detector.py ├── test_PSS_correlator.py ├── test_PSS_correlator_mr.py ├── test_PSS_correlator_with_peak_detector.py ├── test_PSS_detector.py ├── test_SSS_detector.py ├── test_atan2.py ├── test_channel_estimator.py ├── test_div.py ├── test_dot_product.py ├── test_frame_sync.py └── test_receiver.py └── tools ├── generate_FFT_demod_tap_file.py └── generate_PSS_tap_file.py /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**/README.md' 7 | - 'doc/**' 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | - name: Set up Python "3.10" 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: "3.10" 21 | 22 | - name: Install dependencies 23 | run: | 24 | pip3 install -r requirements.txt 25 | #pip3 uninstall cocotb 26 | git clone https://github.com/catkira/cocotb.git 27 | cd cocotb 28 | pip3 install -e . 29 | sudo apt install -y --no-install-recommends iverilog 30 | #git clone https://github.com/steveicarus/iverilog.git 31 | #cd iverilog 32 | #git reset --hard 3612577b52d1a1e1980610b218c8f2487a7bb0fe 33 | #./configure 34 | #make -j20 install 35 | 36 | - name: Compile Verilator 37 | run: | 38 | sudo apt install libfl-dev help2man 39 | git clone https://github.com/verilator/verilator.git 40 | cd verilator 41 | git reset --hard v5.006 42 | autoconf 43 | ./configure 44 | sudo make -j20 45 | sudo make install 46 | 47 | - name: Verify with Verilator 48 | run: | 49 | SIM=verilator pytest -v tests/test_receiver.py 50 | SIM=verilator pytest -v tests/test_Decimator_to_SSS_detector.py 51 | 52 | - name: Verify with iVerilog 53 | run: | 54 | pytest -v --workers=10 tests/test_PSS_correlator.py 55 | pytest -v --workers=10 tests/test_PSS_correlator_mr.py 56 | pytest -v --workers=10 tests/test_PSS_correlator_with_peak_detector.py 57 | pytest -v --workers=10 tests/test_PSS_detector.py 58 | pytest -v --workers=10 tests/test_dot_product.py 59 | pytest -v tests/test_SSS_detector.py 60 | pytest -v --workers=10 tests/test_Decimator_Correlator_PeakDetector.py 61 | pytest -v tests/test_Decimator_Correlator_PeakDetector_FFT.py 62 | pytest -v --workers=10 tests/test_CFO_correction.py 63 | pytest -v --workers=9 tests/test_CFO_calc.py 64 | pytest -v --workers=10 tests/test_channel_estimator.py 65 | pytest -v tests/test_frame_sync.py 66 | pytest -v tests/test_div.py 67 | pytest -v tests/test_atan2.py 68 | 69 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/CIC"] 2 | path = submodules/CIC 3 | url = https://github.com/catkira/CIC 4 | [submodule "submodules/LFSR"] 5 | path = submodules/LFSR 6 | url = https://github.com/catkira/LFSR 7 | [submodule "submodules/FFT"] 8 | path = submodules/FFT 9 | url = https://github.com/catkira/FFT.git 10 | [submodule "submodules/DDS"] 11 | path = submodules/DDS 12 | url = https://github.com/catkira/DDS.git 13 | [submodule "submodules/complex_multiplier"] 14 | path = submodules/complex_multiplier 15 | url = https://github.com/catkira/complex_multiplier.git 16 | [submodule "submodules/verilator-unisims"] 17 | path = submodules/verilator-unisims 18 | url = https://github.com/catkira/verilator-unisims.git 19 | [submodule "submodules/verilog-axi"] 20 | path = submodules/verilog-axi 21 | url = https://github.com/alexforencich/verilog-axi.git 22 | [submodule "submodules/XilinxUnisimLibrary"] 23 | path = submodules/XilinxUnisimLibrary 24 | url = https://github.com/Xilinx/XilinxUnisimLibrary.git 25 | -------------------------------------------------------------------------------- /doc/channel_estimator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/channel_estimator.jpg -------------------------------------------------------------------------------- /doc/channel_estimator.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/channel_estimator.pptx -------------------------------------------------------------------------------- /doc/frame_structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/frame_structure.jpg -------------------------------------------------------------------------------- /doc/frame_structure.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/frame_structure.pptx -------------------------------------------------------------------------------- /doc/iio_dma.txt: -------------------------------------------------------------------------------- 1 | read dma 2 | 3 | -- libiio -- 4 | 5 | buffer.c 6 | iio_device_create_buffer(dev, ...) 7 | iio_device_open(dev, ...) -> [local.c]:local_open(dev, ...) 8 | get_buffer(...) -> [local.c]:local_get_buffer(...) 9 | 10 | iio_buffer_refill(buffer) 11 | get_buffer(...) -> [local.c]:local_get_buffer(...) 12 | 13 | iio_buffer_push(buffer) 14 | get_buffer(...) -> [local.c]:local_get_buffer(...) 15 | 16 | block.c 17 | iio_buffer_create_block(buf, size) 18 | [local-mmap.c]:local_create_mmap_block(...) 19 | ioctl_nointr(pdata->fd, BLOCK_ALLOC_IOCTL, &req) 20 | 21 | iio_block_dequeue(block, nonblock) 22 | [local-mmap.c]:local_dequeue_mmap_block(pdata, nonblock) 23 | ioctl_nointr(fd, BLOCK_DEQUEUE_IOCTL, &block) 24 | 25 | iio_block_enqueue(block, bytes_used, cyclic) 26 | [local-mmap.c]:local_enqueue_mmap_block(pdata, bytes_used, cyclic) 27 | ioctl_nointr(fd, BLOCK_ENQUEUE_IOCTL, &priv->block) 28 | 29 | 30 | usage: 31 | to start transfers 32 | 1. app creates buffer with iio_device_create_buffer() 33 | 2. app creates blocks with iio_buffer_create_block() 34 | 3. app enqueues/dequeues blocks with iio_block_enqueue()/iio_block_dequeue() 35 | 36 | to abort pending transfers: 37 | [buffer.c]: iio_buffer_set_enabled(buf, enabled) 38 | [local.c]: local_enable_buffer(...) 39 | local_do_enable_buffer(...) 40 | local_write_dev_attr(pdata->dev, pdata->idx, "enable", enable ? "1" : "0", 2, IIO_ATTR_TYPE_BUFFER); 41 | [industrialio-bufffer.c]: iio_buffer_disable(buffer, indio_dev) 42 | [industrial-buffer-dma.c]: iio_dma_buffer_disable(buffer, indio_dev) 43 | queue->ops->abort(queue) -> [industrialio-buffer-dmaengine.c]: iio_dmaengine_buffer_abort(queue) 44 | [industrialio-buffer-dmaengine.c]: dmaengine_terminate_sync(dmaengine_buffer->chan) 45 | -> calls dma_dev->device_terminate_all() in dma driver (ie axi_dmac_terminate_all()) 46 | [industrialio-buffer-dmaengine.c]: iio_dma_buffer_block_list_abort(queue, &dmaengine_buffer->active) 47 | 48 | -- iio -- 49 | undustrialio-buffer.c 50 | iio_buffer_ioctl(dev, ...) 51 | case(cmd) 52 | IIO_BLOCK_ALLOC_IOCTL: 53 | iio_buffer_alloc_blocks(buffer, ...) 54 | IIO_BLOCK_FREE_IOCTL: 55 | iio_buffer_free_blocks(buffer) 56 | IIO_BLOCK_QUERY_IOCTL: 57 | iio_buffer_query_block(buffer, ...) 58 | IIO_BLOCK_ENQUEUE_IOCTL: 59 | iio_buffer_enqueue_block(buffer, ...) 60 | [industrialio-buffer-dma.c]: iio_dma_buffer_dequeue_block() 61 | IIO_BLOCK_DEQUEUE_IOCTL: 62 | iio_buffer_dequeue_block(indio_dev, ...) 63 | [industrialio-buffer-dma.c]: iio_dma_buffer_enqueue_block() 64 | 65 | iio_disable_all_buffers(indio_dev) // called from [undustrialio-core.c]: iio_device_unregister(indio_dev) 66 | iio_disable_buffers(indio_dev) 67 | iio_bufffer_disable(buffer, indio_dev) 68 | [industrial-buffer-dma.c]: iio_dma_buffer_disable(buffer, indio_dev) 69 | 70 | 71 | industrialio-buffer-dma.c 72 | 73 | iio_dma_buffer_disable(buffer, indio_dev) 74 | queue->ops->abort(queue) 75 | 76 | iio_dma_buffer_alloc_blocks(bufffer, req) 77 | iio_dma_buffer_alloc_block() 78 | 79 | iio_dma_buffer_enable(buffer, indio_dev) 80 | 81 | iio_dma_buffer_enqueue_block(buffer, block) 82 | iio_dma_buffer_enqueue() 83 | iio_dma_buffer_submit_block(queue, block) 84 | 85 | iio_dma_buffer_query_block(buffer, block) 86 | 87 | iio_dma_buffer_dequeue_block(buffer, block) 88 | iio_dma_buffer_dequeue(queue) 89 | 90 | iio_dma_buffer_mmap(buffer, vma) 91 | dma_mmap_coherent(...) -------------------------------------------------------------------------------- /doc/overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/overview.jpg -------------------------------------------------------------------------------- /doc/overview.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/overview.pptx -------------------------------------------------------------------------------- /doc/receiver_test_constellation_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/receiver_test_constellation_diagram.png -------------------------------------------------------------------------------- /doc/ressource_usage_7020.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/ressource_usage_7020.jpg -------------------------------------------------------------------------------- /doc/ressource_usage_Ant5G.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/doc/ressource_usage_Ant5G.jpg -------------------------------------------------------------------------------- /hdl/AXIS_FIFO.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This is a very simple FIFO. 3 | // Sync and async clock mode is supported. Operation mode is cut-through. 4 | // Async clock mode currently assumes that out_clk is faster than clk_i 5 | // Copyright (C) 2023 Benjamin Menkuec 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | 20 | module AXIS_FIFO #( 21 | parameter DATA_WIDTH = 16, 22 | parameter FIFO_LEN = 8, // has to be power of 2 ! 23 | parameter USER_WIDTH = 1, 24 | parameter ASYNC = 1 25 | ) 26 | ( 27 | input clk_i, 28 | input s_reset_ni, 29 | 30 | input [DATA_WIDTH - 1 : 0] s_axis_in_tdata, 31 | input [USER_WIDTH - 1 : 0] s_axis_in_tuser, 32 | input s_axis_in_tlast, 33 | input s_axis_in_tvalid, 34 | output s_axis_in_tfull, 35 | 36 | input out_clk_i, 37 | input m_reset_ni, 38 | input m_axis_out_tready, 39 | output reg [DATA_WIDTH - 1 : 0] m_axis_out_tdata, 40 | output reg [USER_WIDTH - 1 : 0] m_axis_out_tuser, 41 | output reg m_axis_out_tlast, 42 | output reg m_axis_out_tvalid, 43 | output [$clog2(FIFO_LEN) - 1 : 0] m_axis_out_tlevel, 44 | output m_axis_out_tempty 45 | ); 46 | 47 | localparam PTR_WIDTH = $clog2(FIFO_LEN); 48 | 49 | function [PTR_WIDTH : 0] g2b; 50 | input [PTR_WIDTH : 0] g; 51 | reg [PTR_WIDTH : 0] b; 52 | integer i; 53 | begin 54 | b[PTR_WIDTH] = g[PTR_WIDTH]; 55 | for (i = PTR_WIDTH - 1; i >= 0; i = i - 1) 56 | b[i] = b[i + 1] ^ g[i]; 57 | g2b = b; 58 | end 59 | endfunction 60 | 61 | function [PTR_WIDTH : 0] b2g; 62 | input [PTR_WIDTH : 0] b; 63 | reg [PTR_WIDTH : 0] g; 64 | integer i; 65 | begin 66 | g[PTR_WIDTH] = b[PTR_WIDTH]; 67 | for (i = PTR_WIDTH - 1; i >= 0; i = i -1) 68 | g[i] = b[i + 1] ^ b[i]; 69 | b2g = g; 70 | end 71 | endfunction 72 | 73 | if (ASYNC) begin : GEN_ASYNC 74 | (* ram_style = "block" *) 75 | reg [DATA_WIDTH + USER_WIDTH - 1 : 0] mem[0 : FIFO_LEN - 1]; 76 | 77 | reg [PTR_WIDTH : 0] rd_ptr; 78 | reg [PTR_WIDTH : 0] wr_ptr_grey; 79 | wire [PTR_WIDTH : 0] wr_ptr = g2b(wr_ptr_grey); 80 | wire [PTR_WIDTH - 1: 0] wr_ptr_addr = wr_ptr[PTR_WIDTH - 1 : 0]; 81 | wire [PTR_WIDTH - 1: 0] rd_ptr_addr = rd_ptr[PTR_WIDTH - 1 : 0]; 82 | reg [PTR_WIDTH : 0] wr_ptr_f, wr_ptr_ff; 83 | wire [PTR_WIDTH : 0] wr_ptr_master = g2b(wr_ptr_ff); 84 | wire empty = wr_ptr_master == rd_ptr; 85 | 86 | always @(posedge clk_i) begin 87 | if (!s_reset_ni) wr_ptr_grey <= '0; 88 | else if (s_axis_in_tvalid) wr_ptr_grey <= b2g(g2b(wr_ptr_grey) + 1); 89 | end 90 | 91 | always @(posedge clk_i) begin 92 | if (USER_WIDTH == 0) mem[wr_ptr_addr] <= s_axis_in_tdata; 93 | else mem[wr_ptr_addr] <= {s_axis_in_tuser, s_axis_in_tdata}; 94 | end 95 | 96 | always @(posedge out_clk_i) begin 97 | if (!m_reset_ni) begin 98 | wr_ptr_f <= '0; 99 | wr_ptr_ff <= '0; 100 | end else begin 101 | wr_ptr_f <= wr_ptr_grey; 102 | wr_ptr_ff <= wr_ptr_f; 103 | end 104 | end 105 | 106 | wire data_in_pipeline = m_axis_out_tvalid && (!m_axis_out_tready); 107 | wire [DATA_WIDTH + USER_WIDTH - 1 : 0] rd_data = mem[rd_ptr_addr]; 108 | always @(posedge out_clk_i) begin 109 | if (!m_reset_ni) begin 110 | m_axis_out_tdata <= '0; 111 | rd_ptr <= '0; 112 | m_axis_out_tvalid <= '0; 113 | end else begin 114 | m_axis_out_tvalid <= !empty || data_in_pipeline; 115 | // read new data if fifo is not empty and if there is space in the output pipeline 116 | if (!empty && ((!m_axis_out_tvalid) || m_axis_out_tready)) begin 117 | if (USER_WIDTH == 0) begin 118 | m_axis_out_tdata <= rd_data; 119 | end else begin 120 | m_axis_out_tdata <= rd_data[DATA_WIDTH - 1 : 0]; 121 | m_axis_out_tuser <= rd_data[DATA_WIDTH + USER_WIDTH - 1 : DATA_WIDTH]; 122 | end 123 | rd_ptr <= rd_ptr + 1; 124 | end 125 | end 126 | end 127 | 128 | // TODO: tfull, tlast, level are not support for ASYNC = 1 129 | assign m_axis_out_tlevel = '0; 130 | assign m_axis_out_tempty = empty; 131 | assign s_axis_in_tfull = '0; 132 | always @(posedge out_clk_i) begin 133 | m_axis_out_tlast <= '0; 134 | end 135 | end 136 | // ----------------------------------------------------------------------------------------------------- 137 | // SYNC CLOCK 138 | else begin : GEN_SYNC 139 | reg [DATA_WIDTH - 1 : 0] mem[0 : FIFO_LEN - 1]; 140 | reg [USER_WIDTH - 1 : 0] mem_user[0 : FIFO_LEN - 1]; 141 | reg [FIFO_LEN - 1 : 0] mem_last; 142 | 143 | reg [PTR_WIDTH : 0] wr_ptr; 144 | reg [PTR_WIDTH : 0] rd_ptr; 145 | wire ptr_equal = wr_ptr[PTR_WIDTH - 1 : 0] == rd_ptr[PTR_WIDTH - 1 : 0]; 146 | wire ptr_msb_equal = wr_ptr[PTR_WIDTH] == rd_ptr[PTR_WIDTH]; 147 | wire [PTR_WIDTH - 1: 0] wr_ptr_addr = wr_ptr[PTR_WIDTH - 1 : 0]; 148 | wire [PTR_WIDTH - 1: 0] rd_ptr_addr = rd_ptr[PTR_WIDTH - 1 : 0]; 149 | wire overflow = s_axis_in_tfull && s_axis_in_tvalid; 150 | wire empty = wr_ptr == rd_ptr; 151 | 152 | always @(posedge clk_i) begin 153 | if (!s_reset_ni) wr_ptr <= '0; 154 | else if (s_axis_in_tvalid && !s_axis_in_tfull) wr_ptr <= wr_ptr + 1'b1; 155 | end 156 | 157 | always @(posedge clk_i) begin 158 | if (s_axis_in_tvalid && !s_axis_in_tfull) begin 159 | mem[wr_ptr_addr] <= s_axis_in_tdata; 160 | mem_last[wr_ptr_addr] <= s_axis_in_tlast; 161 | if (USER_WIDTH > 0) mem_user[wr_ptr_addr] <= s_axis_in_tuser; 162 | end 163 | end 164 | 165 | wire data_in_pipeline = m_axis_out_tvalid && (!m_axis_out_tready); 166 | always @(posedge clk_i) begin 167 | if (!m_reset_ni) begin 168 | m_axis_out_tdata <= '0; 169 | m_axis_out_tlast <= '0; 170 | m_axis_out_tuser <= '0; 171 | rd_ptr <= '0; 172 | m_axis_out_tvalid <= '0; 173 | end else begin 174 | m_axis_out_tvalid <= !empty || data_in_pipeline; 175 | if (!empty && ((!m_axis_out_tvalid) || m_axis_out_tready)) begin 176 | m_axis_out_tdata <= mem[rd_ptr_addr]; 177 | m_axis_out_tlast <= mem_last[rd_ptr_addr]; 178 | if (USER_WIDTH > 0) m_axis_out_tuser <= mem_user[rd_ptr_addr]; 179 | rd_ptr <= rd_ptr + 1; 180 | end 181 | end 182 | end 183 | 184 | assign m_axis_out_tlevel = wr_ptr - rd_ptr + data_in_pipeline; 185 | assign m_axis_out_tempty = empty && (!data_in_pipeline); 186 | assign s_axis_in_tfull = (m_axis_out_tlevel == FIFO_LEN - 1); 187 | end 188 | 189 | endmodule -------------------------------------------------------------------------------- /hdl/AXI_lite_interface.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core encapsulates a AXI lite slave interface and provides a simple core interface 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | 19 | // This core encapsulates a AXI lite slave interface and provides a simple core interface 20 | // 21 | // The simple client core interface can be used to write or write to specific addresses. 22 | // The size of each read or write is fixed 4 byte. 23 | // This core will wait for 16 clks to receive a rack or wack from the client core, 24 | // if it does not receive one, it will ack anyway. 25 | 26 | module AXI_lite_interface #( 27 | parameter ADDRESS_WIDTH = 16 28 | ) 29 | ( 30 | // reset and clocks 31 | input reset_ni, 32 | input clk_i, 33 | 34 | // AXI lite interface 35 | // write address channel 36 | input [ADDRESS_WIDTH - 1 : 0] s_axi_awaddr, 37 | input s_axi_awvalid, 38 | output reg s_axi_awready, 39 | 40 | // write data channel 41 | input [31 : 0] s_axi_wdata, 42 | input [ 3 : 0] s_axi_wstrb, // not used 43 | input s_axi_wvalid, 44 | output reg s_axi_wready, 45 | 46 | // write response channel 47 | output [ 1 : 0] s_axi_bresp, 48 | output reg s_axi_bvalid, 49 | input s_axi_bready, 50 | 51 | // read address channel 52 | input [ADDRESS_WIDTH - 1 : 0] s_axi_araddr, 53 | input s_axi_arvalid, 54 | output reg s_axi_arready, 55 | 56 | // read data channel 57 | output reg [31 : 0] s_axi_rdata, 58 | output [ 1 : 0] s_axi_rresp, 59 | output reg s_axi_rvalid, 60 | input s_axi_rready, 61 | 62 | // core interface 63 | output reg wreq_o, 64 | output reg [ADDRESS_WIDTH - 3 : 0] waddr_o, 65 | output reg [31 : 0] wdata_o, 66 | input wack, 67 | output reg rreq_o, 68 | output reg [ADDRESS_WIDTH - 3 : 0] raddr_o, 69 | input [31 : 0] rdata, 70 | input rack 71 | ); 72 | 73 | // internal registers 74 | reg wack_f; 75 | reg wsel; 76 | reg [ 4 : 0] wcount; 77 | reg rack_f; 78 | reg [31 : 0] rdata_f; 79 | reg rsel; 80 | reg [ 4 : 0] rcount; 81 | 82 | // internal signals 83 | wire wack_w; 84 | wire rack_w; 85 | wire [31 : 0] rdata_s; 86 | 87 | // write channel interface 88 | assign s_axi_bresp = 2'd0; 89 | 90 | always @(posedge clk_i) begin 91 | if (!reset_ni) begin 92 | s_axi_awready <= '0; 93 | s_axi_wready <= '0; 94 | s_axi_bvalid <= '0; 95 | end else begin 96 | if (s_axi_awready) s_axi_awready <= 1'b0; // keep axi_awready only high for 1 cycle 97 | else if (wack_w) s_axi_awready <= 1'b1; // ready to receive write addr 98 | 99 | if (s_axi_wready) s_axi_wready <= 1'b0; // keep axi_wready only high for 1 cycle 100 | else if (wack_w) s_axi_wready <= 1'b1; // ready to receive data 101 | 102 | if (s_axi_bready && s_axi_bvalid) s_axi_bvalid <= 1'b0; 103 | else if (wack_f) s_axi_bvalid <= 1'b1; // write response 104 | end 105 | end 106 | 107 | // wait 16 clks to receive wack from client core, otherwise this core will ack it anyway 108 | assign wack_w = (wcount == 5'h1f) ? 1'b1 : (wcount[4] & wack); 109 | 110 | always @(posedge clk_i) begin 111 | if (!reset_ni) begin 112 | wack_f <= '0; 113 | wsel <= '0; 114 | wreq_o <= '0; 115 | waddr_o <= '0; 116 | wdata_o <= '0; 117 | wcount <= '0; 118 | end else begin 119 | wack_f <= wack_w; 120 | if (wsel) begin 121 | if (s_axi_bready && s_axi_bvalid) wsel <= 1'b0; // clear wsel if response handshake happened 122 | wreq_o <= 1'b0; 123 | end else begin 124 | wsel <= s_axi_awvalid & s_axi_wvalid; // set wsel if write address and write handshake happened 125 | wreq_o <= s_axi_awvalid & s_axi_wvalid; // signal write request to core 126 | waddr_o <= s_axi_awaddr[(ADDRESS_WIDTH-1):2]; // signal address of write request to core 127 | wdata_o <= s_axi_wdata; // signal data of write request to core 128 | end 129 | 130 | if (wack_w) wcount <= 5'h00; // reset wcount if ready to receive write request 131 | else if (wcount[4]) wcount <= wcount + 1'b1; // inc wcount if write request is active 132 | else if (wreq_o) wcount <= 5'h10; // set wcount = 0x10 if write request just started 133 | end 134 | end 135 | 136 | // read channel interface 137 | assign s_axi_rresp = 2'd0; 138 | 139 | always @(posedge clk_i) begin 140 | if (!reset_ni) begin 141 | s_axi_arready <= '0; 142 | s_axi_rvalid <= '0; 143 | s_axi_rdata <= '0; 144 | end else begin 145 | if (s_axi_arready) s_axi_arready <= 1'b0; 146 | else if (rack_w) s_axi_arready <= 1'b1; 147 | 148 | if (s_axi_rready && s_axi_rvalid) begin 149 | s_axi_rvalid <= 1'b0; 150 | s_axi_rdata <= 32'd0; 151 | end else if (rack_f) begin 152 | s_axi_rvalid <= 1'b1; 153 | s_axi_rdata <= rdata_f; 154 | end 155 | end 156 | end 157 | 158 | // if rack does not arrive within 16 clks from client core, this core will rack anyway 159 | assign rack_w = (rcount == 5'h1f) ? 1'b1 : (rcount[4] & rack); 160 | // if rack did not come from client core, received data will be 'dead' 161 | assign rdata_s = (rcount == 5'h1f) ? {2{16'hdead}} : rdata; 162 | 163 | always @(posedge clk_i) begin 164 | if (!reset_ni) begin 165 | rack_f <= '0; 166 | rdata_f <= '0; 167 | rsel <= '0; 168 | rreq_o <= '0; 169 | raddr_o <= '0; 170 | rcount <= '0; 171 | end else begin 172 | rack_f <= rack_w; 173 | rdata_f <= rdata_s; 174 | 175 | if (rsel) begin 176 | if (s_axi_rready && s_axi_rvalid) rsel <= 1'b0; 177 | rreq_o <= 1'b0; 178 | end else begin 179 | rsel <= s_axi_arvalid; 180 | rreq_o <= s_axi_arvalid; 181 | raddr_o <= s_axi_araddr[ADDRESS_WIDTH - 1 : 2]; 182 | end 183 | 184 | if (rack_w) rcount <= 5'h00; 185 | else if (rcount[4]) rcount <= rcount + 1'b1; 186 | else if (rreq_o) rcount <= 5'h10; 187 | end 188 | end 189 | 190 | endmodule -------------------------------------------------------------------------------- /hdl/BWP_extractor.sv: -------------------------------------------------------------------------------- 1 | module BWP_extractor #( 2 | parameter IN_DW = 16, // input data width 3 | parameter NFFT = 8, 4 | parameter BLK_EXP_LEN = 8, 5 | 6 | localparam SFN_MAX = 1023, 7 | localparam SUBFRAMES_PER_FRAME = 20, 8 | localparam SYM_PER_SF = 14, 9 | localparam SFN_WIDTH = $clog2(SFN_MAX), 10 | localparam SUBFRAME_NUMBER_WIDTH = $clog2(SUBFRAMES_PER_FRAME - 1), 11 | localparam SYMBOL_NUMBER_WIDTH = $clog2(SYM_PER_SF - 1), 12 | localparam USER_WIDTH_IN = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN, 13 | localparam USER_WIDTH_OUT = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN + 1 14 | ) 15 | ( 16 | input clk_i, 17 | input reset_ni, 18 | input wire [IN_DW - 1 : 0] s_axis_in_tdata, 19 | input wire [USER_WIDTH_IN - 1 : 0] s_axis_in_tuser, 20 | input s_axis_in_tlast, 21 | input s_axis_in_tvalid, 22 | 23 | output reg [IN_DW - 1 : 0] m_axis_out_tdata, 24 | output reg [USER_WIDTH_OUT - 1 : 0] m_axis_out_tuser, 25 | output reg m_axis_out_tlast, 26 | output reg m_axis_out_tvalid, 27 | output reg PBCH_valid_o, 28 | output reg SSS_valid_o 29 | ); 30 | 31 | localparam SYMBOLS_PER_PRB = 12; 32 | wire [SYMBOL_NUMBER_WIDTH - 1 : 0] sym = s_axis_in_tuser[SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN - 1 -: SYMBOL_NUMBER_WIDTH]; 33 | wire [SUBFRAME_NUMBER_WIDTH - 1 : 0] subframe = s_axis_in_tuser[SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN - 1 -: SUBFRAME_NUMBER_WIDTH]; 34 | localparam FFT_LEN = 2 ** NFFT; 35 | reg [$clog2(FFT_LEN) - 1 : 0] sc_cnt; 36 | wire is_PBCH_symbol = (sym == 3 || sym == 4 || sym == 5) && (subframe == 0); 37 | wire is_SSS_symbol = (sym == 4) && (subframe == 0); 38 | localparam SSS_LEN = 127; 39 | localparam SSS_START = FFT_LEN / 2 - (SSS_LEN + 1) / 2; 40 | wire valid_SSS_SC = (sc_cnt >= SSS_START) && (sc_cnt <= SSS_START + SSS_LEN - 1); 41 | localparam PBCH_LEN = 20 * SYMBOLS_PER_PRB; 42 | localparam PBCH_START = FFT_LEN / 2 - PBCH_LEN / 2; 43 | wire valid_PBCH_SC = (sc_cnt >= PBCH_START) && (sc_cnt <= PBCH_START + PBCH_LEN - 1); 44 | 45 | function integer calc_num_prb; 46 | input integer NFFT; 47 | begin 48 | case (NFFT) 49 | 8 : calc_num_prb = 20; 50 | 9 : calc_num_prb = 25; 51 | 10 : calc_num_prb = 52; 52 | 11 : calc_num_prb = 106; 53 | default: $display("NFFT = %d is not supported!", NFFT); 54 | endcase 55 | end 56 | endfunction 57 | 58 | localparam BWP_LEN = calc_num_prb(NFFT) * SYMBOLS_PER_PRB; 59 | 60 | localparam SC_START = FFT_LEN / 2 - BWP_LEN / 2; 61 | localparam SC_END = SC_START + BWP_LEN; 62 | wire valid_SC = (sc_cnt >= SC_START) && (sc_cnt <= SC_END - 1); 63 | 64 | always @(posedge clk_i) begin 65 | if (!reset_ni) begin 66 | m_axis_out_tvalid <= '0; 67 | m_axis_out_tdata <= '0; 68 | m_axis_out_tuser <= '0; 69 | m_axis_out_tlast <= '0; 70 | sc_cnt <= '0; 71 | PBCH_valid_o <= '0; 72 | SSS_valid_o <= '0; 73 | end else begin 74 | SSS_valid_o <= valid_SSS_SC && is_SSS_symbol; 75 | PBCH_valid_o <= valid_PBCH_SC && is_PBCH_symbol; 76 | m_axis_out_tvalid <= valid_SC && s_axis_in_tvalid; 77 | m_axis_out_tdata <= s_axis_in_tdata; 78 | m_axis_out_tuser <= {s_axis_in_tuser, is_PBCH_symbol}; 79 | m_axis_out_tlast <= s_axis_in_tvalid && (sc_cnt == (FFT_LEN - 1 - SC_START)); 80 | 81 | if (s_axis_in_tvalid) begin 82 | if (sc_cnt == FFT_LEN - 1) sc_cnt <= '0; 83 | else sc_cnt <= sc_cnt + 1; 84 | end 85 | end 86 | end 87 | 88 | endmodule -------------------------------------------------------------------------------- /hdl/CFO_calc.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module CFO_calc 4 | #( 5 | parameter C_DW = 32, 6 | parameter CFO_DW = 20, 7 | parameter DDS_DW = 20, 8 | parameter ATAN_IN_DW = 8, 9 | localparam SAMPLE_RATE = 3840000 10 | ) 11 | ( 12 | input clk_i, 13 | input reset_ni, 14 | input [C_DW - 1 : 0] C0_i, 15 | input [C_DW - 1 : 0] C1_i, 16 | input valid_i, 17 | 18 | output reg signed [CFO_DW - 1 : 0] CFO_angle_o, 19 | output reg signed [DDS_DW - 1 : 0] CFO_DDS_inc_o, 20 | output reg valid_o 21 | ); 22 | 23 | reg [C_DW - 1 : 0] C0, C1; 24 | wire [C_DW - 1 : 0] C1_conj = {-C1[C_DW-1 : C_DW/2], C1[C_DW/2-1 : 0]}; 25 | reg mult_valid_out; 26 | 27 | reg [3 : 0] state; 28 | localparam WAIT_FOR_INPUT = 4'b0000; 29 | localparam INPUT_SCALING = 4'b0001; 30 | localparam WAIT_FOR_MULT = 4'b0010; 31 | localparam CALC_ATAN = 4'b0101; 32 | localparam OUTPUT = 4'b0110; 33 | 34 | reg [2*ATAN_IN_DW : 0] C0_times_conjC1; 35 | 36 | reg atan_valid_in; 37 | reg atan2_valid_out; 38 | reg signed [CFO_DW - 1 : 0] atan2_out; 39 | reg signed [CFO_DW - 1 : 0] atan; 40 | reg signed [ATAN_IN_DW - 1 : 0] prod_im, prod_re; 41 | atan2 #( 42 | .INPUT_WIDTH(ATAN_IN_DW), 43 | .LUT_DW(ATAN_IN_DW), 44 | .OUTPUT_WIDTH(CFO_DW) 45 | ) 46 | atan2_i( 47 | .clk_i(clk_i), 48 | .reset_ni(reset_ni), 49 | 50 | .numerator_i(prod_im), 51 | .denominator_i(prod_re), 52 | .valid_i(atan_valid_in), 53 | 54 | .angle_o(atan2_out), 55 | .valid_o(atan2_valid_out) 56 | ); 57 | 58 | 59 | // TODO: this multiplier can run on a slower clock, ie 3.84 MHz 60 | // so that it can be synthesized easily without any DSP48 units 61 | reg mult_valid_in; 62 | complex_multiplier #( 63 | .OPERAND_WIDTH_A(C_DW/2), // TODO: input width can be reduced, because of dynamic scaling 64 | .OPERAND_WIDTH_B(C_DW/2), 65 | .OPERAND_WIDTH_OUT(ATAN_IN_DW), 66 | .BLOCKING(0), 67 | .BYTE_ALIGNED(0) 68 | ) 69 | complex_multiplier_i( 70 | .aclk(clk_i), 71 | .aresetn(reset_ni), 72 | .s_axis_a_tdata(C0), 73 | .s_axis_a_tvalid(mult_valid_in), 74 | .s_axis_b_tdata(C1_conj), 75 | .s_axis_b_tvalid(mult_valid_in), 76 | .m_axis_dout_tdata(C0_times_conjC1), 77 | .m_axis_dout_tvalid(mult_valid_out) 78 | ); 79 | 80 | function is_bit_used; 81 | input [C_DW / 2 - 1 : 0] data; 82 | input [$clog2(C_DW/2) - 1 : 0] MSB_pos; 83 | begin 84 | if (data[C_DW / 2 - 1]) begin // neg. number 85 | is_bit_used = !data[MSB_pos]; 86 | end else begin // pos. number 87 | is_bit_used = data[MSB_pos]; 88 | end 89 | end 90 | endfunction 91 | 92 | reg [$clog2(C_DW/2) - 1 : 0] input_max_used_MSB; 93 | wire signed [CFO_DW - 1 : 0] atan_rshift7 = atan >>> 7; 94 | always @(posedge clk_i) begin 95 | if (!reset_ni) begin 96 | valid_o <= '0; 97 | // CFO_angle_o <= '0; 98 | CFO_DDS_inc_o <= '0; 99 | state <= WAIT_FOR_INPUT; 100 | input_max_used_MSB <= C_DW / 2 - 2; // dont need to test the MSB 101 | mult_valid_in <= '0; 102 | atan_valid_in <= '0; 103 | end else begin 104 | case (state) 105 | WAIT_FOR_INPUT : begin 106 | valid_o <= 0; 107 | if (valid_i) begin 108 | state <= INPUT_SCALING; 109 | // mult_valid_in <= 1; 110 | // state <= WAIT_FOR_MULT; 111 | // $display("go to state INPUT_SCALING"); 112 | C0 <= C0_i; 113 | C1 <= C1_i; 114 | input_max_used_MSB <= C_DW / 2 - 2; // dont need to test the MSB 115 | end 116 | end 117 | INPUT_SCALING : begin 118 | // $display("is used %x bit %d = %d",C0[C_DW - 1 : C_DW / 2], input_max_used_MSB, is_bit_used(C0[C_DW - 1 : C_DW / 2], input_max_used_MSB)); 119 | // $display("is used %x bit %d = %d",C0[C_DW / 2 - 1 : 0], input_max_used_MSB, is_bit_used(C0[C_DW / 2 - 1 : 0], input_max_used_MSB)); 120 | // $display("is used %x bit %d = %d",C1[C_DW - 1 : C_DW / 2], input_max_used_MSB, is_bit_used(C1[C_DW - 1 : C_DW / 2], input_max_used_MSB)); 121 | // $display("is used %x bit %d = %d",C0[C_DW / 2 - 1 : 0], input_max_used_MSB, is_bit_used(C0[C_DW / 2 - 1 : 0], input_max_used_MSB)); 122 | if ( (!is_bit_used(C0[C_DW - 1 : C_DW / 2], input_max_used_MSB)) 123 | && (!is_bit_used(C0[C_DW / 2 - 1 : 0], input_max_used_MSB)) 124 | && (!is_bit_used(C1[C_DW - 1 : C_DW / 2], input_max_used_MSB)) 125 | && (!is_bit_used(C1[C_DW / 2 - 1 : 0], input_max_used_MSB)) 126 | && (input_max_used_MSB > 0)) begin 127 | input_max_used_MSB <= input_max_used_MSB - 1; 128 | end else begin 129 | C0[C_DW - 1 : C_DW / 2] <= C0[C_DW - 1 : C_DW / 2] << (C_DW / 2 - 1 - input_max_used_MSB - 1); 130 | C0[C_DW / 2 - 1 : 0] <= C0[C_DW / 2 - 1 : 0] << (C_DW / 2 - 1 - input_max_used_MSB - 1); 131 | C1[C_DW - 1 : C_DW / 2] <= C1[C_DW - 1 : C_DW / 2] << (C_DW / 2 - 1 - input_max_used_MSB - 1); 132 | C1[C_DW / 2 - 1 : 0] <= C1[C_DW / 2 - 1 : 0] << (C_DW / 2 - 1 - input_max_used_MSB - 1); 133 | mult_valid_in <= 1; 134 | state <= WAIT_FOR_MULT; 135 | // $display("go to state WAIT_FOR_MULT"); 136 | end 137 | end 138 | WAIT_FOR_MULT : begin 139 | mult_valid_in <= '0; 140 | if (mult_valid_out) begin 141 | prod_im <= C0_times_conjC1[2*ATAN_IN_DW - 1 : ATAN_IN_DW]; 142 | prod_re <= C0_times_conjC1[ATAN_IN_DW - 1 : 0]; 143 | atan_valid_in <= 1; 144 | state <= CALC_ATAN; 145 | end 146 | end 147 | CALC_ATAN: begin 148 | atan_valid_in <= '0; 149 | if (atan2_valid_out) begin 150 | atan <= atan2_out; 151 | state <= OUTPUT; 152 | end 153 | end 154 | OUTPUT : begin 155 | if (CFO_DW >= DDS_DW) begin 156 | // take upper MSBs 157 | CFO_DDS_inc_o <= atan_rshift7[CFO_DW - 1 -: DDS_DW]; // >>> 7 for divide / 64 / 2 158 | end else begin 159 | // sign extend 160 | CFO_DDS_inc_o <= {{(DDS_DW - CFO_DW){atan[CFO_DW - 1]}}, atan_rshift7}; 161 | end 162 | CFO_angle_o <= atan; 163 | valid_o <= 1; 164 | state <= WAIT_FOR_INPUT; 165 | end 166 | default : begin end 167 | endcase 168 | end 169 | end 170 | 171 | endmodule -------------------------------------------------------------------------------- /hdl/CIC: -------------------------------------------------------------------------------- 1 | ../submodules/CIC/hdl/ -------------------------------------------------------------------------------- /hdl/DDS: -------------------------------------------------------------------------------- 1 | ../submodules/DDS/hdl/ -------------------------------------------------------------------------------- /hdl/Decimator_Correlator_PeakDetector.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module Decimator_Correlator_PeakDetector 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter OUT_DW = 32, // correlator output data width 7 | parameter TAP_DW = 32, 8 | parameter PSS_LEN = 128, 9 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL = {(PSS_LEN * TAP_DW){1'b0}}, 10 | parameter ALGO = 1, 11 | parameter WINDOW_LEN = 8 12 | ) 13 | ( 14 | input clk_i, 15 | input reset_ni, 16 | input wire [IN_DW-1:0] s_axis_in_tdata, 17 | input s_axis_in_tvalid, 18 | output reg peak_detected_o, 19 | 20 | // debug outputs 21 | output wire [IN_DW-1:0] m_axis_cic_debug_tdata, 22 | output wire m_axis_cic_debug_tvalid, 23 | output wire [OUT_DW - 1 : 0] m_axis_correlator_debug_tdata, 24 | output wire m_axis_correlator_debug_tvalid 25 | ); 26 | 27 | wire [IN_DW - 1 : 0] m_axis_cic_tdata; 28 | wire m_axis_cic_tvalid; 29 | assign m_axis_cic_debug_tdata = m_axis_cic_tdata; 30 | assign m_axis_cic_debug_tvalid = m_axis_cic_tvalid; 31 | 32 | cic_d #( 33 | .INP_DW(IN_DW/2), 34 | .OUT_DW(IN_DW/2), 35 | .CIC_R(2), 36 | .CIC_N(3), 37 | .VAR_RATE(0) 38 | ) 39 | cic_real( 40 | .clk(clk_i), 41 | .reset_n(reset_ni), 42 | .s_axis_in_tdata(s_axis_in_tdata[IN_DW / 2 - 1 -: IN_DW / 2]), 43 | .s_axis_in_tvalid(s_axis_in_tvalid), 44 | .m_axis_out_tdata(m_axis_cic_tdata[IN_DW / 2 - 1 -: IN_DW / 2]), 45 | .m_axis_out_tvalid(m_axis_cic_tvalid) 46 | ); 47 | 48 | cic_d #( 49 | .INP_DW(IN_DW / 2), 50 | .OUT_DW(IN_DW / 2), 51 | .CIC_R(2), 52 | .CIC_N(3), 53 | .VAR_RATE(0) 54 | ) 55 | cic_imag( 56 | .clk(clk_i), 57 | .reset_n(reset_ni), 58 | .s_axis_in_tdata(s_axis_in_tdata[IN_DW - 1 -: IN_DW / 2]), 59 | .s_axis_in_tvalid(s_axis_in_tvalid), 60 | .m_axis_out_tdata(m_axis_cic_tdata[IN_DW - 1 -: IN_DW / 2]) 61 | ); 62 | 63 | 64 | wire [OUT_DW - 1 : 0] correlator_tdata; 65 | wire correlator_tvalid; 66 | assign m_axis_correlator_debug_tdata = correlator_tdata; 67 | assign m_axis_correlator_debug_tvalid = correlator_tvalid; 68 | 69 | PSS_correlator #( 70 | .IN_DW(IN_DW), 71 | .OUT_DW(OUT_DW), 72 | .TAP_DW(TAP_DW), 73 | .PSS_LEN(PSS_LEN), 74 | .PSS_LOCAL(PSS_LOCAL), 75 | .ALGO(ALGO), 76 | .USE_TAP_FILE(1), 77 | .N_ID_2(2) 78 | ) 79 | correlator( 80 | .clk_i(clk_i), 81 | .reset_ni(reset_ni), 82 | .s_axis_in_tdata(m_axis_cic_tdata), 83 | .s_axis_in_tvalid(m_axis_cic_tvalid), 84 | .enable_i(1'b1), 85 | .m_axis_out_tdata(correlator_tdata), 86 | .m_axis_out_tvalid(correlator_tvalid) 87 | ); 88 | 89 | Peak_detector #( 90 | .IN_DW(OUT_DW), 91 | .WINDOW_LEN(WINDOW_LEN) 92 | ) 93 | peak_detector( 94 | .clk_i(clk_i), 95 | .reset_ni(reset_ni), 96 | .s_axis_in_tdata(correlator_tdata), 97 | .s_axis_in_tvalid(correlator_tvalid), 98 | .noise_limit_i(0), 99 | .detection_shift_i(4), 100 | .enable_i(1'b1), 101 | .peak_detected_o(peak_detected_o) 102 | ); 103 | 104 | endmodule -------------------------------------------------------------------------------- /hdl/Decimator_Correlator_PeakDetector_FFT.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module Decimator_Correlator_PeakDetector_FFT 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter OUT_DW = 32, // correlator output data width 7 | parameter TAP_DW = 32, 8 | parameter PSS_LEN = 128, 9 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_0 = {(PSS_LEN * TAP_DW){1'b0}}, 10 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_1 = {(PSS_LEN * TAP_DW){1'b0}}, 11 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_2 = {(PSS_LEN * TAP_DW){1'b0}}, 12 | parameter ALGO = 1, 13 | parameter WINDOW_LEN = 8, 14 | parameter HALF_CP_ADVANCE = 1, 15 | parameter NFFT = 8, 16 | parameter USE_TAP_FILE = 1, 17 | parameter TAP_FILE = "", 18 | parameter MULT_REUSE = 0, 19 | parameter MULT_REUSE_FFT = 1, 20 | parameter INITIAL_DETECTION_SHIFT = 4, 21 | 22 | localparam FFT_OUT_DW = 32, 23 | localparam FFT_LEN = 2 ** NFFT, 24 | localparam CIC_RATE = FFT_LEN / 128, 25 | localparam MAX_CP_LEN = 20 * FFT_LEN / 256, 26 | localparam SFN_MAX = 1023, 27 | localparam SUBFRAMES_PER_FRAME = 20, 28 | localparam SYM_PER_SF = 14, 29 | localparam SFN_WIDTH = $clog2(SFN_MAX), 30 | localparam SUBFRAME_NUMBER_WIDTH = $clog2(SUBFRAMES_PER_FRAME - 1), 31 | localparam SYMBOL_NUMBER_WIDTH = $clog2(SYM_PER_SF - 1), 32 | localparam BLK_EXP_LEN = 8, 33 | localparam OUT_USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN + 1 34 | ) 35 | ( 36 | input clk_i, 37 | input reset_ni, 38 | input wire [IN_DW-1:0] s_axis_in_tdata, 39 | input s_axis_in_tvalid, 40 | 41 | output PBCH_valid_o, 42 | output SSS_valid_o, 43 | output [FFT_OUT_DW-1:0] m_axis_out_tdata, 44 | output m_axis_out_tvalid, 45 | output m_axis_out_tlast, 46 | output [OUT_USER_WIDTH-1:0] m_axis_out_tuser, 47 | 48 | // debug outputs 49 | output reg peak_detected_debug_o, 50 | output wire [FFT_OUT_DW-1:0] fft_result_debug_o, 51 | output wire fft_sync_debug_o, 52 | output wire [15:0] sync_wait_counter_debug_o, 53 | output reg fft_demod_PBCH_start_o, 54 | output reg fft_demod_SSS_start_o, 55 | output reg fft_demod_tlast_o, 56 | output [IN_DW-1:0] m_axis_PSS_out_tdata, 57 | output m_axis_PSS_out_tvalid 58 | ); 59 | 60 | wire [IN_DW - 1 : 0] in_data; 61 | wire in_valid; 62 | PSS_detector #( 63 | .IN_DW(IN_DW), 64 | .OUT_DW(OUT_DW), 65 | .TAP_DW(TAP_DW), 66 | .PSS_LEN(PSS_LEN), 67 | .PSS_LOCAL_0(PSS_LOCAL_0), 68 | .PSS_LOCAL_1(PSS_LOCAL_1), 69 | .PSS_LOCAL_2(PSS_LOCAL_2), 70 | .ALGO(ALGO), 71 | .USE_TAP_FILE(USE_TAP_FILE), 72 | .MULT_REUSE(MULT_REUSE), 73 | .MULT_REUSE_FFT(MULT_REUSE_FFT), 74 | .INITIAL_DETECTION_SHIFT(INITIAL_DETECTION_SHIFT), 75 | .CIC_RATE(CIC_RATE) 76 | ) 77 | PSS_detector_i( 78 | .clk_i(clk_i), 79 | .reset_ni(reset_ni), 80 | .clear_ni(1'b1), 81 | .s_axis_in_tdata(s_axis_in_tdata), 82 | .s_axis_in_tvalid(s_axis_in_tvalid), 83 | 84 | .m_axis_out_tdata(in_data), 85 | .m_axis_out_tvalid(in_valid), 86 | .N_id_2_valid_o(peak_detected) 87 | ); 88 | 89 | wire peak_detected; 90 | assign peak_detected_debug_o = peak_detected; 91 | assign m_axis_PSS_out_tdata = in_data; 92 | assign m_axis_PSS_out_tvalid = in_valid; 93 | 94 | wire [FFT_OUT_DW - 1 : 0] fft_result, fft_result_demod; 95 | wire [FFT_OUT_DW / 2 - 1 : 0] fft_result_re, fft_result_im; 96 | wire fft_result_demod_valid; 97 | wire fft_sync; 98 | 99 | assign fft_result_debug_o = fft_result; 100 | assign fft_sync_debug_o = fft_sync; 101 | 102 | localparam USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + $clog2(MAX_CP_LEN); 103 | 104 | reg [IN_DW - 1 : 0] fs_out_tdata; 105 | reg [USER_WIDTH - 1 : 0] fs_out_tuser; 106 | reg fs_out_tvalid; 107 | reg fs_out_SSB_start; 108 | wire fs_out_tlast; 109 | 110 | frame_sync #( 111 | .IN_DW(IN_DW), 112 | .NFFT(NFFT) 113 | ) 114 | frame_sync_i 115 | ( 116 | .clk_i(clk_i), 117 | .reset_ni(reset_ni), 118 | .N_id_2_i(), 119 | .N_id_2_valid_i(peak_detected), 120 | .ibar_SSB_i(), 121 | .ibar_SSB_valid_i(), 122 | .s_axis_in_tdata(in_data), 123 | .s_axis_in_tvalid(in_valid), 124 | 125 | .PSS_detector_mode_o(), 126 | .requested_N_id_2_o(), 127 | 128 | .m_axis_out_tdata(fs_out_tdata), 129 | .m_axis_out_tuser(fs_out_tuser), 130 | .m_axis_out_tlast(fs_out_tlast), 131 | .m_axis_out_tvalid(fs_out_tvalid), 132 | .SSB_start_o(fs_out_SSB_start) 133 | ); 134 | 135 | localparam FFT_DEMOD_OUT_USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN; 136 | wire [FFT_OUT_DW - 1 : 0] fft_demod_out_tdata; 137 | wire [FFT_DEMOD_OUT_USER_WIDTH - 1 : 0] fft_demod_out_tuser; 138 | wire fft_demod_out_tvalid; 139 | wire fft_demod_out_tlast; 140 | FFT_demod #( 141 | .IN_DW(IN_DW), 142 | .OUT_DW(FFT_OUT_DW), 143 | .HALF_CP_ADVANCE(HALF_CP_ADVANCE), 144 | .NFFT(NFFT), 145 | .USE_TAP_FILE(USE_TAP_FILE), 146 | .TAP_FILE(TAP_FILE) 147 | ) 148 | FFT_demod_i( 149 | .clk_i(clk_i), 150 | .reset_ni(reset_ni), 151 | .SSB_start_i(fs_out_SSB_start), 152 | .s_axis_in_tdata(fs_out_tdata), 153 | .s_axis_in_tlast(fs_out_tlast), 154 | .s_axis_in_tuser(fs_out_tuser), 155 | .s_axis_in_tvalid(fs_out_tvalid), 156 | .m_axis_out_tdata(fft_demod_out_tdata), 157 | .m_axis_out_tuser(fft_demod_out_tuser), 158 | .m_axis_out_tlast(fft_demod_out_tlast), 159 | .m_axis_out_tvalid(fft_demod_out_tvalid) 160 | ); 161 | 162 | BWP_extractor #( 163 | .IN_DW(FFT_OUT_DW), 164 | .NFFT(NFFT), 165 | .BLK_EXP_LEN(BLK_EXP_LEN) 166 | ) 167 | BWP_extractor_i( 168 | .clk_i(clk_i), 169 | .reset_ni(reset_ni), 170 | 171 | .s_axis_in_tdata(fft_demod_out_tdata), 172 | .s_axis_in_tuser(fft_demod_out_tuser), 173 | .s_axis_in_tvalid(fft_demod_out_tvalid), 174 | .s_axis_in_tlast(fft_demod_out_tlast), 175 | 176 | .m_axis_out_tdata(m_axis_out_tdata), 177 | .m_axis_out_tuser(m_axis_out_tuser), 178 | .m_axis_out_tvalid(m_axis_out_tvalid), 179 | .m_axis_out_tlast(m_axis_out_tlast), 180 | .PBCH_valid_o(PBCH_valid_o), 181 | .SSS_valid_o(SSS_valid_o) 182 | ); 183 | 184 | endmodule 185 | 186 | -------------------------------------------------------------------------------- /hdl/Decimator_to_SSS_detector.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module Decimator_to_SSS_detector 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter OUT_DW = 32, // correlator output data width 7 | parameter TAP_DW = 32, 8 | parameter PSS_LEN = 128, 9 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_0 = {(PSS_LEN * TAP_DW){1'b0}}, 10 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_1 = {(PSS_LEN * TAP_DW){1'b0}}, 11 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL_2 = {(PSS_LEN * TAP_DW){1'b0}}, 12 | parameter ALGO = 1, 13 | parameter WINDOW_LEN = 8, 14 | parameter HALF_CP_ADVANCE = 1, 15 | parameter USE_TAP_FILE = 0, 16 | parameter TAP_FILE_0 = "", 17 | parameter TAP_FILE_1 = "", 18 | parameter TAP_FILE_2 = "", 19 | parameter NFFT = 8, 20 | parameter MULT_REUSE = 0, 21 | parameter MULT_REUSE_FFT = 1, 22 | parameter INITIAL_DETECTION_SHIFT = 4, 23 | 24 | localparam FFT_LEN = 2 ** NFFT, 25 | localparam CIC_RATE = FFT_LEN / 128, 26 | localparam MAX_CP_LEN = 20 * FFT_LEN / 256, 27 | localparam FFT_OUT_DW = 32, 28 | localparam N_id_1_MAX = 335, 29 | localparam N_id_MAX = 1007 30 | ) 31 | ( 32 | input clk_i, 33 | input reset_ni, 34 | input wire [IN_DW - 1 : 0] s_axis_in_tdata, 35 | input s_axis_in_tvalid, 36 | 37 | output PBCH_valid_o, 38 | output SSS_valid_o, 39 | output [FFT_OUT_DW - 1 : 0] m_axis_out_tdata, 40 | output m_axis_out_tvalid, 41 | output [$clog2(N_id_1_MAX) - 1 : 0] m_axis_SSS_tdata, 42 | output m_axis_SSS_tvalid, 43 | output [$clog2(N_id_MAX) - 1 : 0] N_id_o, 44 | 45 | // debug outputs 46 | output peak_detected_debug_o, 47 | output wire [15:0] sync_wait_counter_debug_o, 48 | output reg fft_demod_PBCH_start_o, 49 | output reg fft_demod_SSS_start_o 50 | ); 51 | 52 | wire peak_detected; 53 | assign peak_detected_debug_o = peak_detected; 54 | 55 | reg N_id_2_valid; 56 | wire [1 : 0] N_id_2; 57 | 58 | wire [IN_DW - 1 : 0] in_data; 59 | wire in_valid; 60 | PSS_detector #( 61 | .IN_DW(IN_DW), 62 | .OUT_DW(OUT_DW), 63 | .TAP_DW(TAP_DW), 64 | .PSS_LEN(PSS_LEN), 65 | .PSS_LOCAL_0(PSS_LOCAL_0), 66 | .PSS_LOCAL_1(PSS_LOCAL_1), 67 | .PSS_LOCAL_2(PSS_LOCAL_2), 68 | .ALGO(ALGO), 69 | .USE_TAP_FILE(USE_TAP_FILE), 70 | .MULT_REUSE(MULT_REUSE), 71 | .MULT_REUSE_FFT(MULT_REUSE_FFT), 72 | .INITIAL_DETECTION_SHIFT(INITIAL_DETECTION_SHIFT), 73 | .CIC_RATE(CIC_RATE) 74 | ) 75 | PSS_detector_i( 76 | .clk_i(clk_i), 77 | .reset_ni(reset_ni), 78 | .clear_ni(1'b1), 79 | .s_axis_in_tdata(s_axis_in_tdata), 80 | .s_axis_in_tvalid(s_axis_in_tvalid), 81 | 82 | .m_axis_out_tdata(in_data), 83 | .m_axis_out_tvalid(in_valid), 84 | .N_id_2_valid_o(peak_detected), 85 | .N_id_2_o(N_id_2) 86 | ); 87 | 88 | localparam SFN_MAX = 1023; 89 | localparam SUBFRAMES_PER_FRAME = 20; 90 | localparam SYM_PER_SF = 14; 91 | localparam SFN_WIDTH = $clog2(SFN_MAX); 92 | localparam SUBFRAME_NUMBER_WIDTH = $clog2(SUBFRAMES_PER_FRAME - 1); 93 | localparam SYMBOL_NUMBER_WIDTH = $clog2(SYM_PER_SF - 1); 94 | localparam USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + $clog2(MAX_CP_LEN); 95 | 96 | reg [IN_DW - 1 : 0] fs_out_tdata; 97 | reg [USER_WIDTH - 1 : 0] fs_out_tuser; 98 | reg fs_out_tvalid; 99 | reg fs_out_SSB_start; 100 | wire fs_out_tlast; 101 | 102 | frame_sync #( 103 | .IN_DW(IN_DW), 104 | .NFFT(NFFT) 105 | ) 106 | frame_sync_i 107 | ( 108 | .clk_i(clk_i), 109 | .reset_ni(reset_ni), 110 | .N_id_2_i(N_id_2), 111 | .N_id_2_valid_i(peak_detected), 112 | .ibar_SSB_i(), 113 | .ibar_SSB_valid_i(), 114 | .s_axis_in_tdata(in_data), 115 | .s_axis_in_tvalid(in_valid), 116 | 117 | .PSS_detector_mode_o(), 118 | .requested_N_id_2_o(), 119 | 120 | .m_axis_out_tdata(fs_out_tdata), 121 | .m_axis_out_tuser(fs_out_tuser), 122 | .m_axis_out_tlast(fs_out_tlast), 123 | .m_axis_out_tvalid(fs_out_tvalid), 124 | .SSB_start_o(fs_out_SSB_start) 125 | ); 126 | 127 | localparam BLK_EXP_LEN = 8; 128 | localparam FFT_DEMOD_OUT_USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN; 129 | wire [FFT_OUT_DW - 1 : 0] fft_demod_out_tdata; 130 | wire [FFT_DEMOD_OUT_USER_WIDTH - 1 : 0] fft_demod_out_tuser; 131 | wire fft_demod_out_tvalid; 132 | wire fft_demod_out_tlast; 133 | FFT_demod #( 134 | .IN_DW(IN_DW), 135 | .OUT_DW(FFT_OUT_DW), 136 | .HALF_CP_ADVANCE(HALF_CP_ADVANCE), 137 | .NFFT(NFFT), 138 | .USE_TAP_FILE(USE_TAP_FILE) 139 | ) 140 | FFT_demod_i( 141 | .clk_i(clk_i), 142 | .reset_ni(reset_ni), 143 | .SSB_start_i(fs_out_SSB_start), 144 | .s_axis_in_tdata(fs_out_tdata), 145 | .s_axis_in_tlast(fs_out_tlast), 146 | .s_axis_in_tuser(fs_out_tuser), 147 | .s_axis_in_tvalid(fs_out_tvalid), 148 | .m_axis_out_tdata(fft_demod_out_tdata), 149 | .m_axis_out_tuser(fft_demod_out_tuser), 150 | .m_axis_out_tlast(fft_demod_out_tlast), 151 | .m_axis_out_tvalid(fft_demod_out_tvalid) 152 | ); 153 | 154 | BWP_extractor #( 155 | .IN_DW(FFT_OUT_DW), 156 | .NFFT(NFFT), 157 | .BLK_EXP_LEN(BLK_EXP_LEN) 158 | ) 159 | BWP_extractor_i( 160 | .clk_i(clk_i), 161 | .reset_ni(reset_ni), 162 | 163 | .s_axis_in_tdata(fft_demod_out_tdata), 164 | .s_axis_in_tuser(fft_demod_out_tuser), 165 | .s_axis_in_tvalid(fft_demod_out_tvalid), 166 | .s_axis_in_tlast(fft_demod_out_tlast), 167 | 168 | .m_axis_out_tdata(m_axis_out_tdata), 169 | .m_axis_out_tuser(), 170 | .m_axis_out_tvalid(m_axis_out_tvalid), 171 | .m_axis_out_tlast(), 172 | .PBCH_valid_o(PBCH_valid_o), 173 | .SSS_valid_o(SSS_valid_o) 174 | ); 175 | 176 | 177 | SSS_detector #( 178 | .IN_DW(FFT_OUT_DW) 179 | ) 180 | SSS_detector_i( 181 | .clk_i(clk_i), 182 | .reset_ni(reset_ni), 183 | .N_id_2_i(N_id_2), 184 | .N_id_2_valid_i(peak_detected), 185 | .s_axis_in_tdata(m_axis_out_tdata), 186 | .s_axis_in_tvalid(SSS_valid_o), 187 | .m_axis_out_tdata(m_axis_SSS_tdata), 188 | .m_axis_out_tvalid(m_axis_SSS_tvalid), 189 | .N_id_o(N_id_o) 190 | ); 191 | 192 | endmodule -------------------------------------------------------------------------------- /hdl/FFT: -------------------------------------------------------------------------------- 1 | ../submodules/FFT/src/verilog/ -------------------------------------------------------------------------------- /hdl/LFSR: -------------------------------------------------------------------------------- 1 | ../submodules/LFSR/hdl/ -------------------------------------------------------------------------------- /hdl/PSS_correlator.sv: -------------------------------------------------------------------------------- 1 | // It's best to not use truncation inside this module, because 2 | // truncation is only implemented rudimentally without rounding 3 | 4 | `timescale 1ns / 1ns 5 | 6 | module PSS_correlator 7 | #( 8 | parameter IN_DW = 32, // input data width 9 | parameter OUT_DW = 24, // output data width 10 | parameter TAP_DW = 32, 11 | parameter PSS_LEN = 128, 12 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL = {(PSS_LEN * TAP_DW){1'b0}}, 13 | parameter ALGO = 1, 14 | parameter USE_TAP_FILE = 0, 15 | parameter TAP_FILE = "", 16 | parameter TAP_FILE_PATH = "", 17 | parameter N_ID_2 = 0, 18 | localparam C_DW = IN_DW + TAP_DW + 2 + 2 * $clog2(PSS_LEN) 19 | ) 20 | ( 21 | input clk_i, 22 | input reset_ni, 23 | input wire [IN_DW-1:0] s_axis_in_tdata, 24 | input s_axis_in_tvalid, 25 | input enable_i, 26 | output reg [OUT_DW-1:0] m_axis_out_tdata, 27 | output reg m_axis_out_tvalid, 28 | output reg [C_DW - 1 : 0] C0_o, 29 | output reg [C_DW - 1 : 0] C1_o, 30 | 31 | // debug outputs 32 | output [TAP_DW - 1 : 0] taps_o [0 : PSS_LEN - 1] 33 | ); 34 | 35 | localparam IN_OP_DW = IN_DW / 2; 36 | localparam TAP_OP_DW = TAP_DW / 2; 37 | localparam REQUIRED_OUT_DW = IN_OP_DW + TAP_OP_DW + 1 + $clog2(PSS_LEN); 38 | 39 | reg signed [REQUIRED_OUT_DW - 1 : 0] C0_im, C0_re; // partial sums, used for CFO estimation 40 | 41 | wire signed [IN_OP_DW - 1 : 0] axis_in_re, axis_in_im; 42 | assign axis_in_re = s_axis_in_tdata[IN_DW / 2 - 1 -: IN_OP_DW]; 43 | assign axis_in_im = s_axis_in_tdata[IN_DW - 1 -: IN_OP_DW]; 44 | 45 | reg signed [TAP_OP_DW - 1 : 0] tap_re, tap_im; 46 | 47 | reg signed [IN_OP_DW - 1 : 0] in_re [0 : PSS_LEN - 1]; 48 | reg signed [IN_OP_DW - 1 : 0] in_im [0 : PSS_LEN - 1]; 49 | reg valid; 50 | reg signed [REQUIRED_OUT_DW - 1 : 0] sum_im, sum_re; 51 | 52 | reg [TAP_DW - 1 : 0] taps [0 : PSS_LEN - 1]; 53 | assign taps_o = taps; 54 | initial begin 55 | if (USE_TAP_FILE) begin 56 | if (TAP_FILE == "") begin 57 | if (TAP_FILE_PATH == "") begin 58 | $display("load PSS_correlator taps from %s", $sformatf("PSS_taps_%0d.hex", N_ID_2)); 59 | $readmemh($sformatf("PSS_taps_%0d.hex", N_ID_2), taps); 60 | end else begin 61 | $display("load PSS_correlator taps from %s", $sformatf("%s/PSS_taps_%0d.hex", TAP_FILE_PATH, N_ID_2)); 62 | $readmemh($sformatf("%s/PSS_taps_%0d.hex", TAP_FILE_PATH, N_ID_2), taps); 63 | end 64 | end else begin 65 | $display("load PSS_correlator taps from %s", TAP_FILE); 66 | $readmemh(TAP_FILE, taps); 67 | end 68 | end else begin 69 | $display("loading PSS_correlator taps from PSS_LOCAL parameter"); 70 | end 71 | // for (integer i = 0; i < PSS_LEN; i = i + 1) begin 72 | // if (N_ID_2 == 2) begin 73 | // tap_im = get_tap_im(i); 74 | // tap_re = get_tap_re(i); 75 | // $display("PSS_LOCAL[%d] = %d + j%d", i, tap_re, tap_im); 76 | // end 77 | // // tap_re = PSS_LOCAL[i * TAP_DW + TAP_DW / 2 - 1 -: TAP_OP_DW]; 78 | // // tap_im = PSS_LOCAL[i * TAP_DW + TAP_DW - 1 -: TAP_OP_DW]; 79 | // // $display("PSS_LOCAL[%d] = %d + j%d", i, tap_re, tap_im); 80 | // // tap_re = PSS_LOCAL[(PSS_LEN-i-1)*TAP_DW+TAP_DW/2-1-:TAP_OP_DW]; 81 | // // tap_im = PSS_LOCAL[(PSS_LEN-i-1)*TAP_DW+TAP_DW-1-:TAP_OP_DW]; 82 | // // $display("PSS_LOCAL[%d] = %d + j%d", PSS_LEN-i-1, tap_re, tap_im); 83 | // end 84 | end 85 | 86 | function [TAP_OP_DW - 1 : 0] get_tap_im; 87 | input integer arg; 88 | begin 89 | if (USE_TAP_FILE) get_tap_im = taps[arg] >> TAP_OP_DW; 90 | else get_tap_im = PSS_LOCAL[arg * TAP_DW + TAP_DW - 1 -: TAP_OP_DW]; 91 | end 92 | endfunction 93 | 94 | function [TAP_OP_DW - 1 : 0] get_tap_re; 95 | input integer arg; 96 | begin 97 | if (USE_TAP_FILE) get_tap_re = taps[arg][TAP_OP_DW - 1 : 0]; 98 | else get_tap_re = PSS_LOCAL[arg * TAP_DW + TAP_DW / 2 - 1 -: TAP_OP_DW]; 99 | end 100 | endfunction 101 | 102 | 103 | reg [REQUIRED_OUT_DW - 1: 0] filter_result; 104 | // assign filter_result = sum_im * sum_im + sum_re * sum_re; 105 | 106 | function [REQUIRED_OUT_DW - 1 : 0] abs; 107 | input signed [REQUIRED_OUT_DW - 1 : 0] arg; 108 | begin 109 | abs = arg[REQUIRED_OUT_DW - 1] ? ~arg + 1 : arg; 110 | end 111 | endfunction 112 | 113 | localparam OUTPUT_PAD_BITS = REQUIRED_OUT_DW >= OUT_DW ? 0 : OUT_DW - REQUIRED_OUT_DW; 114 | 115 | genvar ii; 116 | for (ii = 0; ii < PSS_LEN; ii++) begin 117 | always @(posedge clk_i) begin 118 | if (!reset_ni) begin 119 | in_re[ii] <= '0; 120 | in_im[ii] <= '0; 121 | end else if (s_axis_in_tvalid) begin 122 | if (ii == 0) begin 123 | in_re[0] <= axis_in_re; 124 | in_im[0] <= axis_in_im; 125 | end 126 | if (ii < PSS_LEN - 1) begin 127 | in_re[ii + 1] <= in_re[ii]; 128 | in_im[ii + 1] <= in_im[ii]; 129 | end 130 | end 131 | end 132 | end 133 | 134 | 135 | always @(posedge clk_i) begin // cannot use $display inside always_ff with iverilog 136 | if (!reset_ni) begin 137 | m_axis_out_tdata <= '0; 138 | m_axis_out_tvalid <= '0; 139 | valid <= '0; 140 | C0_im = '0; 141 | C0_re = '0; 142 | C0_o <= '0; 143 | C1_o <= '0; 144 | end 145 | else begin 146 | valid <= s_axis_in_tvalid; 147 | if (valid) begin 148 | sum_im = '0; 149 | sum_re = '0; 150 | if (ALGO == 0) begin 151 | // 4*PSS_LEN multiplications 152 | for (integer i = 0; i < PSS_LEN; i++) begin 153 | tap_re = get_tap_re(i); 154 | tap_im = get_tap_im(i); 155 | sum_re = sum_re + in_re[i] * tap_re - in_im[i] * tap_im; 156 | sum_im = sum_im + in_re[i] * tap_im + in_im[i] * tap_re; 157 | if (i == PSS_LEN / 2 - 1) begin 158 | C0_im = sum_im; 159 | C0_re = sum_re; 160 | end 161 | end 162 | C0_o <= {C0_im, C0_re}; 163 | C1_o <= {sum_im - C0_im, sum_re - C0_re}; 164 | end else begin 165 | // 2*PSS_LEN multiplications 166 | // simplification by taking into account that PSS is 167 | // complex conjugate centrally symetric in time-domain 168 | 169 | static integer i = 0; 170 | if (0) begin 171 | // tap[0] and tap[64] symmetric pair, so it has to be calculated as before 172 | // another source for error is that the taps are not perfectly symmetric, 173 | // when truncation is used, because rounding is not implemented in that case 174 | // these 2 taps can also be discarded for simplicity 175 | tap_re = get_tap_re(i); 176 | tap_im = get_tap_im(i); 177 | sum_re = sum_re + in_re[i] * tap_re - in_im[i] * tap_im; 178 | sum_im = sum_im + in_re[i] * tap_im + in_im[i] * tap_re; 179 | i = 64; 180 | tap_re = get_tap_re(i); 181 | tap_im = get_tap_im(i); 182 | sum_re = sum_re + in_re[i] * tap_re - in_im[i] * tap_im; 183 | sum_im = sum_im + in_re[i] * tap_im + in_im[i] * tap_re; 184 | end 185 | 186 | for (i = 1; i < PSS_LEN / 2; i++) begin 187 | tap_re = get_tap_re(i); 188 | tap_im = get_tap_im(i); 189 | sum_re = sum_re + (in_re[PSS_LEN - i] + in_re[i]) * tap_re 190 | + (in_im[PSS_LEN - i] - in_im[i]) * tap_im; 191 | sum_im = sum_im + (in_im[PSS_LEN - i] + in_im[i]) * tap_re 192 | - (in_re[PSS_LEN - i] - in_re[i]) * tap_im; 193 | if (i == PSS_LEN / 4 - 1) begin 194 | C0_im = sum_im; 195 | C0_re = sum_re; 196 | end 197 | end 198 | C0_o <= {C0_im, C0_re}; 199 | C1_o <= {sum_im - C0_im, sum_re - C0_re}; 200 | end 201 | 202 | // https://openofdm.readthedocs.io/en/latest/verilog.html 203 | if (abs(sum_im) > abs(sum_re)) filter_result = abs(sum_im) + (abs(sum_re) >> 2); 204 | else filter_result = abs(sum_re) + (abs(sum_im) >> 2); 205 | 206 | if (REQUIRED_OUT_DW >= OUT_DW) begin 207 | m_axis_out_tdata <= enable_i ? filter_result[REQUIRED_OUT_DW - 1 -: OUT_DW] : '0; 208 | end else begin 209 | // do zero padding 210 | // m_axis_out_tdata <= {{(OUT_DW - REQUIRED_OUT_DW){1'b0}}, filter_result}; 211 | m_axis_out_tdata <= enable_i ? {{(OUTPUT_PAD_BITS){1'b0}}, filter_result} : '0; 212 | end 213 | m_axis_out_tvalid <= '1; 214 | end else begin 215 | m_axis_out_tdata <= '0; 216 | m_axis_out_tvalid <= '0; 217 | end 218 | end 219 | end 220 | 221 | endmodule -------------------------------------------------------------------------------- /hdl/PSS_correlator_with_peak_detector.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module PSS_correlator_with_peak_detector 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter OUT_DW = 32, // correlator output data width 7 | parameter TAP_DW = 32, 8 | parameter PSS_LEN = 128, 9 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL = {(PSS_LEN * TAP_DW){1'b0}}, 10 | parameter ALGO = 1, 11 | parameter WINDOW_LEN = 8, 12 | parameter DETECTION_SHIFT = 4 13 | ) 14 | ( 15 | input clk_i, 16 | input reset_ni, 17 | input wire [IN_DW-1:0] s_axis_in_tdata, 18 | input s_axis_in_tvalid, 19 | output reg peak_detected_o 20 | ); 21 | 22 | wire [OUT_DW - 1 : 0] correlator_tdata; 23 | wire correlator_tvalid; 24 | 25 | PSS_correlator #( 26 | .IN_DW(IN_DW), 27 | .OUT_DW(OUT_DW), 28 | .TAP_DW(TAP_DW), 29 | .PSS_LEN(PSS_LEN), 30 | .PSS_LOCAL(PSS_LOCAL), 31 | .ALGO(ALGO) 32 | ) 33 | correlator( 34 | .clk_i(clk_i), 35 | .reset_ni(reset_ni), 36 | .s_axis_in_tdata(s_axis_in_tdata), 37 | .s_axis_in_tvalid(s_axis_in_tvalid), 38 | .enable_i(1'b1), 39 | .m_axis_out_tdata(correlator_tdata), 40 | .m_axis_out_tvalid(correlator_tvalid) 41 | ); 42 | 43 | Peak_detector #( 44 | .IN_DW(OUT_DW), 45 | .WINDOW_LEN(WINDOW_LEN) 46 | ) 47 | peak_detector( 48 | .clk_i(clk_i), 49 | .reset_ni(reset_ni), 50 | .s_axis_in_tdata(correlator_tdata), 51 | .s_axis_in_tvalid(correlator_tvalid), 52 | .noise_limit_i('0), 53 | .detection_shift_i(DETECTION_SHIFT), 54 | .enable_i(1'b1), 55 | .peak_detected_o(peak_detected_o) 56 | ); 57 | 58 | endmodule -------------------------------------------------------------------------------- /hdl/PSS_detector_regmap.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core connects AXI lite mapped registers from the PSS detector 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | module PSS_detector_regmap #( 19 | parameter ID = 0, 20 | parameter ADDRESS_WIDTH = 11, 21 | parameter CORR_DW = 32, 22 | parameter VARIABLE_NOISE_LIMIT = 0, 23 | parameter VARIABLE_DETECTION_FACTOR = 0, 24 | parameter INITIAL_DETECTION_SHIFT = 3, 25 | parameter INITIAL_CFO_MODE = 0, 26 | 27 | localparam INITIAL_NOISE_LIMIT = 2**(CORR_DW/2) 28 | ) 29 | ( 30 | input clk_i, 31 | input reset_ni, 32 | 33 | // AXI lite interface 34 | // write address channel 35 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_awaddr, 36 | input s_axi_if_awvalid, 37 | output reg s_axi_if_awready, 38 | 39 | // write data channel 40 | input [31 : 0] s_axi_if_wdata, 41 | input [ 3 : 0] s_axi_if_wstrb, // not used 42 | input s_axi_if_wvalid, 43 | output reg s_axi_if_wready, 44 | 45 | // write response channel 46 | output [ 1 : 0] s_axi_if_bresp, 47 | output reg s_axi_if_bvalid, 48 | input s_axi_if_bready, 49 | 50 | // read address channel 51 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_araddr, 52 | input s_axi_if_arvalid, 53 | output reg s_axi_if_arready, 54 | 55 | // read data channel 56 | output reg [31 : 0] s_axi_if_rdata, 57 | output [ 1 : 0] s_axi_if_rresp, 58 | output reg s_axi_if_rvalid, 59 | input s_axi_if_rready, 60 | 61 | // mapped registers 62 | input [1 : 0] mode_i, 63 | input signed [31 : 0] CFO_angle_i, 64 | output reg cfo_mode_o, 65 | input [31 : 0] peak_counter_0_i, 66 | input [31 : 0] peak_counter_1_i, 67 | input [31 : 0] peak_counter_2_i, 68 | input [31 : 0] peak_fifo_level_i, 69 | input [31 : 0] data_fifo_level_i, 70 | output reg [CORR_DW - 1 : 0] noise_limit_o, 71 | output reg [7 : 0] detection_shift_o 72 | ); 73 | 74 | localparam PCORE_VERSION = 'h00010061; 75 | initial $display("INITIAL_DETECTION_SHIFT = %d", INITIAL_DETECTION_SHIFT); 76 | 77 | wire rreq; 78 | wire [8:0] raddr; 79 | reg [31:0] rdata; 80 | reg rack; 81 | 82 | always @(posedge clk_i) begin 83 | if (!reset_ni) rack <= '0; 84 | else rack <= rreq; // ack immediately after req 85 | end 86 | 87 | always @(posedge clk_i) begin 88 | if (!reset_ni) rdata <= '0; 89 | else begin 90 | if (rreq == 1'b1) begin 91 | case (raddr) 92 | 9'h000: rdata <= PCORE_VERSION; 93 | 9'h001: rdata <= ID; 94 | 9'h002: rdata <= '0; 95 | 9'h003: rdata <= 32'h50535344; // "PSSD" 96 | 9'h004: rdata <= 32'h69696969; 97 | 9'h005: rdata <= mode_i; 98 | 9'h006: rdata <= CFO_angle_i; 99 | 9'h007: rdata <= cfo_mode_o; 100 | 9'h008: rdata <= peak_counter_0_i; 101 | 9'h009: rdata <= peak_counter_1_i; 102 | 9'h00A: rdata <= peak_counter_2_i; 103 | 9'h00B: rdata <= noise_limit_o; 104 | 9'h00C: rdata <= detection_shift_o; 105 | 9'h00D: rdata <= peak_fifo_level_i; 106 | 9'h00E: rdata <= data_fifo_level_i; 107 | default: rdata <= '0; 108 | endcase 109 | end 110 | end 111 | end 112 | 113 | wire wreq; 114 | wire [8:0] waddr; 115 | reg [31:0] wdata; 116 | reg wack; 117 | 118 | always @(posedge clk_i) begin 119 | if (!reset_ni) wack <= '0; 120 | else wack <= wreq; // ack immediately after req 121 | end 122 | 123 | 124 | if (VARIABLE_NOISE_LIMIT) begin 125 | always @(posedge clk_i) begin 126 | if (!reset_ni) noise_limit_o <= INITIAL_NOISE_LIMIT; 127 | else if (wreq && (waddr == 9'h00B)) noise_limit_o <= wdata; 128 | end 129 | end else begin 130 | assign noise_limit_o = INITIAL_NOISE_LIMIT; 131 | end 132 | 133 | if (VARIABLE_DETECTION_FACTOR) begin 134 | always @(posedge clk_i) begin 135 | if (!reset_ni) detection_shift_o <= INITIAL_DETECTION_SHIFT; 136 | else if (wreq && (waddr == 9'h00C)) detection_shift_o <= wdata; 137 | end 138 | end else begin 139 | assign detection_shift_o = INITIAL_DETECTION_SHIFT; 140 | end 141 | 142 | always @(posedge clk_i) begin 143 | if (!reset_ni) begin 144 | cfo_mode_o <= INITIAL_CFO_MODE; 145 | end else begin 146 | if (wreq) begin 147 | case (waddr) 148 | 9'h007: cfo_mode_o <= wdata; 149 | default: begin end 150 | endcase 151 | end 152 | end 153 | end 154 | 155 | AXI_lite_interface #( 156 | .ADDRESS_WIDTH(ADDRESS_WIDTH) 157 | ) 158 | AXI_lite_interface_i( 159 | .clk_i(clk_i), 160 | .reset_ni(reset_ni), 161 | 162 | .s_axi_awaddr(s_axi_if_awaddr), 163 | .s_axi_awvalid(s_axi_if_awvalid), 164 | .s_axi_awready(s_axi_if_awready), 165 | .s_axi_wdata(s_axi_if_wdata), 166 | .s_axi_wstrb(s_axi_if_wstrb), 167 | .s_axi_wvalid(s_axi_if_wvalid), 168 | .s_axi_wready(s_axi_if_wready), 169 | .s_axi_bresp(s_axi_if_bresp), 170 | .s_axi_bvalid(s_axi_if_bvalid), 171 | .s_axi_bready(s_axi_if_bready), 172 | .s_axi_araddr(s_axi_if_araddr), 173 | .s_axi_arvalid(s_axi_if_arvalid), 174 | .s_axi_arready(s_axi_if_arready), 175 | .s_axi_rdata(s_axi_if_rdata), 176 | .s_axi_rresp(s_axi_if_rresp), 177 | .s_axi_rvalid(s_axi_if_rvalid), 178 | .s_axi_rready(s_axi_if_rready), 179 | 180 | .wreq_o(wreq), 181 | .waddr_o(waddr), 182 | .wdata_o(wdata), 183 | .wack(wack), 184 | .rreq_o(rreq), 185 | .raddr_o(raddr), 186 | .rdata(rdata), 187 | .rack(rack) 188 | ); 189 | 190 | endmodule 191 | -------------------------------------------------------------------------------- /hdl/Peak_detector.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module Peak_detector 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter WINDOW_LEN = 8 // length of average window, should be power of 2 7 | ) 8 | ( 9 | input clk_i, 10 | input reset_ni, 11 | input [IN_DW - 1 : 0] s_axis_in_tdata, 12 | input s_axis_in_tvalid, 13 | input [IN_DW - 1 : 0] noise_limit_i, 14 | input [7 : 0] detection_shift_i, 15 | input enable_i, 16 | output reg peak_detected_o, 17 | output reg peak_valid_o, 18 | output reg [IN_DW - 1 : 0] score_o 19 | ); 20 | 21 | reg [IN_DW - 1 : 0] in_buffer [0 : WINDOW_LEN - 1]; 22 | reg [IN_DW - 1 + $clog2(WINDOW_LEN) : 0] average; 23 | reg [$clog2(WINDOW_LEN) : 0] init_counter = '0; 24 | wire signed [7 : 0] total_shift = $clog2(WINDOW_LEN) - detection_shift_i; 25 | 26 | genvar ii; 27 | for (ii = 0; ii < WINDOW_LEN; ii++) begin 28 | always @(posedge clk_i) begin 29 | if (!reset_ni) in_buffer[ii] <= '0; 30 | else if (s_axis_in_tvalid) begin 31 | if (ii == 0) in_buffer[ii] <= s_axis_in_tdata; 32 | else in_buffer[ii] <= in_buffer[ii - 1]; 33 | end 34 | end 35 | end 36 | 37 | always @(posedge clk_i) begin 38 | if (!reset_ni) peak_valid_o <= '0; 39 | else peak_valid_o <= s_axis_in_tvalid && (init_counter == WINDOW_LEN); 40 | end 41 | 42 | reg in_valid_f; 43 | reg [IN_DW - 1 : 0] in_data_f; 44 | always @(posedge clk_i) begin 45 | if (!reset_ni) begin 46 | peak_detected_o <= '0; 47 | init_counter <= '0; 48 | score_o <= '0; 49 | average <= '0; 50 | in_valid_f <= '0; 51 | in_data_f <= '0; 52 | end else begin 53 | in_valid_f <= s_axis_in_tvalid; 54 | 55 | if (s_axis_in_tvalid) begin 56 | if (init_counter < WINDOW_LEN) init_counter <= init_counter + 1; 57 | 58 | average <= average + in_buffer[0] - in_buffer[WINDOW_LEN - 1]; 59 | 60 | if (init_counter == WINDOW_LEN) begin 61 | if ((total_shift > 0 ? (s_axis_in_tdata > (average >> total_shift)) && (s_axis_in_tdata > noise_limit_i) 62 | : (s_axis_in_tdata > (average << -total_shift))) && (s_axis_in_tdata > noise_limit_i)) begin 63 | peak_detected_o <= 1 && enable_i; 64 | score_o <= total_shift > 0 ? s_axis_in_tdata - (average >> total_shift) 65 | : s_axis_in_tdata - (average << -total_shift); 66 | end else begin 67 | peak_detected_o <= '0; 68 | score_o <= '0; 69 | end 70 | end else begin 71 | peak_detected_o <= '0; 72 | score_o <= '0; 73 | end 74 | end else begin 75 | peak_detected_o <= '0; 76 | end 77 | end 78 | end 79 | endmodule -------------------------------------------------------------------------------- /hdl/SSS_detector.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module SSS_detector 4 | #( 5 | parameter IN_DW = 16, 6 | 7 | localparam N_id_1_MAX = 335, 8 | localparam N_id_MAX = 1007 9 | ) 10 | ( 11 | input clk_i, 12 | input reset_ni, 13 | input wire [IN_DW - 1 : 0] s_axis_in_tdata, 14 | input s_axis_in_tvalid, 15 | input wire [1 : 0] N_id_2_i, 16 | input wire N_id_2_valid_i, 17 | output reg [$clog2(N_id_1_MAX) - 1 : 0] m_axis_out_tdata, 18 | output reg m_axis_out_tvalid, 19 | output reg [$clog2(N_id_MAX) - 1 : 0] N_id_o, 20 | output reg N_id_valid_o 21 | ); 22 | 23 | localparam SSS_LEN = 127; 24 | 25 | //localparam [$clog2(10) - 1 : 0]times_5 [0 : 2] = '{10, 5, 0}; 26 | // stupid iverilog does not support multidimensional localparams, 27 | // there have to use a reg that is assigned a constant value at reset 28 | reg [$clog2(10) - 1 : 0] times_5 [0 : 2]; 29 | reg [$clog2(35) - 1 : 0] times_15 [0 : 2]; 30 | 31 | reg [SSS_LEN - 1 : 0] sss_in_I, sss_in_Q; 32 | reg [1 : 0] N_id_2; 33 | 34 | localparam NUM_STATES = 2; 35 | localparam STATE_READ_SSS = 0; 36 | localparam STATE_DETECT_SSS = 1; 37 | reg [$clog2(NUM_STATES) - 1 : 0] state; 38 | reg [$clog2(SSS_LEN - 1) - 1 : 0] copy_counter, copy_counter_m_seq; 39 | reg [$clog2(SSS_LEN - 1) - 1 : 0] compare_counter; 40 | reg [$clog2(SSS_LEN - 1) - 1 : 0] acc_max; 41 | reg signed [$clog2(SSS_LEN - 1) : 0] acc_I, acc_Q; 42 | wire signed [$clog2(SSS_LEN - 1) : 0] abs_acc_I = acc_I[$clog2(SSS_LEN - 1)] ? ~acc_I + 1 : acc_I; 43 | wire signed [$clog2(SSS_LEN - 1) : 0] abs_acc_Q = acc_Q[$clog2(SSS_LEN - 1)] ? ~acc_Q + 1 : acc_Q; 44 | localparam SHIFT_MAX = 112; 45 | reg [$clog2(SHIFT_MAX) - 1 : 0] shift_cur; 46 | 47 | reg [$clog2(N_id_1_MAX) - 1 : 0] N_id_1, N_id_1_det; 48 | 49 | wire lfsr_out_0, lfsr_out_1, lfsr_valid; 50 | localparam LFSR_N = 7; 51 | LFSR #( 52 | .N(LFSR_N), 53 | .TAPS('h11), 54 | .START_VALUE(1), 55 | .VARIABLE_CONFIG(0) 56 | ) 57 | lfsr_0 58 | ( 59 | .clk_i(clk_i), 60 | .reset_ni(reset_ni), 61 | .data_o(lfsr_out_0), 62 | .valid_o(lfsr_valid) 63 | ); 64 | LFSR #( 65 | .N(LFSR_N), 66 | .TAPS('h03), 67 | .START_VALUE(1), 68 | .VARIABLE_CONFIG(0) 69 | ) 70 | lfsr_1 71 | ( 72 | .clk_i(clk_i), 73 | .reset_ni(reset_ni), 74 | .data_o(lfsr_out_1) 75 | ); 76 | reg [SSS_LEN - 1 : 0] m_seq_0, m_seq_1; 77 | wire [$clog2(SSS_LEN - 1) - 1 : 0] m_seq_0_pos, m_seq_1_pos; 78 | // if SSS_LEN would be 128, roll operation would be very easy by just using overflows 79 | // however since SSS_LEN is 127 it is a bit more complicated 80 | reg [$clog2(SSS_LEN - 1) - 1 : 0] m_0, m_1, m_0_start; 81 | reg [3 : 0] div_112; // is (N_id_1 / 112) 82 | reg m_seq_0_wrap, m_seq_1_wrap; 83 | assign m_seq_0_pos = m_0 + compare_counter + m_seq_0_wrap; 84 | assign m_seq_1_pos = m_1 + compare_counter + m_seq_1_wrap; 85 | reg N_id_2_set; 86 | reg [1 : 0] N_id_2_f; 87 | 88 | always @(posedge clk_i) begin 89 | if (!reset_ni) begin 90 | N_id_2_set <= '0; 91 | N_id_2_f <= '0; 92 | end else if (N_id_2_valid_i) begin 93 | N_id_2_f <= N_id_2_i; 94 | N_id_2_set <= 1; 95 | end 96 | end 97 | 98 | always @(posedge clk_i) begin 99 | if (!reset_ni) begin 100 | times_5[0] = 0; 101 | times_5[1] = 5; 102 | times_5[2] = 10; 103 | times_15[0] = 0; 104 | times_15[1] = 15; 105 | times_15[2] = 30; 106 | m_axis_out_tdata <= '0; 107 | m_axis_out_tvalid <= '0; 108 | N_id_valid_o <= '0; 109 | N_id_o <= '0; 110 | copy_counter <= '0; 111 | copy_counter_m_seq <= '0; 112 | state <= STATE_READ_SSS; 113 | compare_counter <= '0; 114 | shift_cur <= '0; 115 | m_0 <= '0; 116 | m_1 <= '0; 117 | div_112 <= '0; 118 | N_id_1 <= '0; 119 | N_id_1_det <= '0; 120 | N_id_2 <= '0; 121 | m_0_start <= '0; 122 | acc_max <= '0; 123 | acc_I <= '0; 124 | acc_Q <= '0; 125 | m_seq_0_wrap <= '0; 126 | m_seq_1_wrap <= '0; 127 | m_seq_0 <= '0; 128 | m_seq_1 <= '0; 129 | // $display("reset"); 130 | end else begin 131 | case (state) 132 | STATE_READ_SSS : begin // and prepare mseq 133 | m_axis_out_tvalid <= '0; 134 | N_id_valid_o <= '0; 135 | acc_max <= '0; 136 | // copy SSS into internal buffer and create m_seq_0 and m_seq_1 137 | // m_0 = 15 * int((N_id_1 / 112)) + 5 * N_id_2 138 | // m_1 = N_id_1 % 112 139 | // d_SSS = (1 - 2 * np.roll(mseq_0, -m_0)) * (1 - 2 * np.roll(mseq_1, -m_1)) 140 | 141 | if (lfsr_valid && (copy_counter_m_seq < SSS_LEN)) begin 142 | // $display("store %d %d", lfsr_out_0, lfsr_out_1); 143 | m_seq_0[copy_counter_m_seq] <= lfsr_out_0; 144 | m_seq_1[copy_counter_m_seq] <= lfsr_out_1; 145 | copy_counter_m_seq <= copy_counter_m_seq + 1; 146 | end 147 | if (s_axis_in_tvalid) begin 148 | sss_in_I[copy_counter] <= ~s_axis_in_tdata[IN_DW / 2 - 1]; // bpsk demod: take MSB of real part 149 | sss_in_Q[copy_counter] <= ~s_axis_in_tdata[IN_DW - 1]; // bpsk demod: take MSB of imag part 150 | // $display("ss_in[%d] = %d", copy_counter, s_axis_in_tdata); 151 | if (copy_counter == SSS_LEN - 1) begin 152 | // state <= 1; 153 | // $display("enter state 1"); 154 | // copy_counter <= '0; 155 | end 156 | else copy_counter <= copy_counter + 1; 157 | end 158 | if ((copy_counter == SSS_LEN - 1) && N_id_2_set) begin 159 | copy_counter <= '0; 160 | state <= STATE_DETECT_SSS; 161 | N_id_2 <= N_id_2_f; 162 | m_0_start <= times_5[N_id_2_f]; // optimized to not use multiplication 163 | m_0 <= times_5[N_id_2_f]; 164 | m_1 <= 0; 165 | end 166 | end 167 | STATE_DETECT_SSS : begin // compare input to single SSS sequence 168 | if (compare_counter == 0) begin 169 | // $display("N_id_1 = %d shift_cur = %d", N_id_1, shift_cur); 170 | // $display("m_0 = %d m_1 = %d mod = %d", m_seq_0_pos, m_seq_1_pos, div_112); 171 | end 172 | 173 | if (m_seq_0_pos == SSS_LEN - 1) begin 174 | m_seq_0_wrap <= 1; 175 | end 176 | if (m_seq_1_pos == SSS_LEN - 1) begin 177 | m_seq_1_wrap <= 1; 178 | end 179 | 180 | if (compare_counter == SSS_LEN - 1) begin 181 | // $display("correlation = %d", acc); 182 | if ((abs_acc_I > acc_max) || (abs_acc_Q > acc_max)) begin 183 | acc_max <= abs_acc_I > abs_acc_Q ? abs_acc_I : abs_acc_Q; 184 | m_axis_out_tdata <= N_id_1; 185 | N_id_o <= N_id_1 + N_id_1 + N_id_1 + N_id_2; 186 | // $display("best N_id_1 so far is %d", N_id_1); 187 | end 188 | compare_counter <= '0; 189 | m_seq_0_wrap <= '0; 190 | m_seq_1_wrap <= '0; 191 | acc_I <= '0; 192 | acc_Q <= '0; 193 | 194 | if (N_id_1 == N_id_1_MAX) begin 195 | m_axis_out_tvalid <= 1; 196 | N_id_valid_o <= 1; 197 | shift_cur <= '0; 198 | div_112 <= '0; 199 | N_id_1 <= '0; 200 | $display("SSS_detector: detected N_id = %d (0x%x)", N_id_o, N_id_o); 201 | state <= STATE_READ_SSS; // back to init state 202 | end else begin 203 | if (shift_cur == SHIFT_MAX - 1) begin 204 | // m_0 <= m_0_start + 15 * (div_112 + 1); 205 | m_0 <= m_0_start + times_15[div_112 + 1]; // optimized to not use multiplication 206 | m_1 <= 0; 207 | div_112 <= div_112 + 1; 208 | shift_cur <= '0; 209 | end else begin 210 | m_1 <= m_1 + 1; 211 | shift_cur <= shift_cur + 1; 212 | end 213 | N_id_1 <= N_id_1 + 1; 214 | // $display("test next: N_id_1 = %d N_id_2 = %d", N_id_1 + 1, m_0_start / 5); 215 | end 216 | end else begin 217 | // $display("pos0 = %d pos1 = %d seq0 = %d seq1 = %d wrap0 = %d wrap1 = %d acc = %d", m_seq_0_pos, m_seq_1_pos, m_seq_0[m_seq_0_pos], m_seq_1[m_seq_1_pos], m_seq_0_wrap, m_seq_1_wrap, acc); 218 | // $display("cnt = %d %d <-> %d", compare_counter, sss_in[compare_counter], m_seq_0[m_seq_0_pos] ^ m_seq_1[m_seq_1_pos]); 219 | 220 | if (sss_in_I[compare_counter] == ~(m_seq_0[m_seq_0_pos] ^ m_seq_1[m_seq_1_pos])) acc_I <= acc_I + 1; 221 | else if (sss_in_I[compare_counter] == (m_seq_0[m_seq_0_pos] ^ m_seq_1[m_seq_1_pos])) acc_I <= acc_I - 1; 222 | if (sss_in_Q[compare_counter] == ~(m_seq_0[m_seq_0_pos] ^ m_seq_1[m_seq_1_pos])) acc_Q <= acc_Q + 1; 223 | else if (sss_in_Q[compare_counter] == (m_seq_0[m_seq_0_pos] ^ m_seq_1[m_seq_1_pos])) acc_Q <= acc_Q - 1; 224 | compare_counter <= compare_counter + 1; 225 | end 226 | end 227 | default: begin 228 | $display("ERROR: undefined state %d", state); 229 | end 230 | endcase 231 | end 232 | end 233 | 234 | endmodule -------------------------------------------------------------------------------- /hdl/atan.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core calculates the arctan by using a LUT, 3 | // input range is from 0 to 1 only ! 4 | // Copyright (C) 2023 Benjamin Menkuec 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | 20 | module atan #( 21 | parameter INPUT_WIDTH = 16, 22 | parameter OUTPUT_WIDTH = 16 23 | ) 24 | ( 25 | input clk_i, 26 | input reset_ni, 27 | 28 | input [INPUT_WIDTH - 1 : 0] arg_i, 29 | 30 | output [OUTPUT_WIDTH - 1 : 0] angle_o 31 | ); 32 | 33 | localparam MAX_LUT_IN_VAL = (2**INPUT_WIDTH - 1); 34 | localparam MAX_LUT_OUT_VAL = (2**OUTPUT_WIDTH - 1); 35 | reg [OUTPUT_WIDTH - 1 : 0] atan_lut[0 : MAX_LUT_IN_VAL]; 36 | 37 | initial begin 38 | $display("tan lut has %d entries", MAX_LUT_IN_VAL+1); 39 | for (integer i = 0; i <= MAX_LUT_IN_VAL; i = i + 1) begin 40 | atan_lut[i] = $atan($itor(i)/MAX_LUT_IN_VAL) / (3.14159 / 4) * MAX_LUT_OUT_VAL; 41 | // $display("atan %d = %d", i, atan_lut[i]); 42 | end 43 | end 44 | 45 | assign angle_o = atan_lut[arg_i]; 46 | 47 | 48 | // always @(posedge clk_i) begin 49 | // if (!reset_ni) begin 50 | // angle_o <= '0; 51 | // end else begin 52 | // angle_o <= atan_lut[arg_i]; 53 | // end 54 | // end 55 | 56 | endmodule -------------------------------------------------------------------------------- /hdl/atan2.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core calculates the arctan by using the atan core 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | 19 | module atan2 #( 20 | parameter INPUT_WIDTH = 16, 21 | parameter LUT_DW = 16, 22 | parameter OUTPUT_WIDTH = 16 23 | ) 24 | ( 25 | input clk_i, 26 | input reset_ni, 27 | 28 | input signed [INPUT_WIDTH - 1 : 0] numerator_i, 29 | input signed [INPUT_WIDTH - 1 : 0] denominator_i, 30 | input valid_i, 31 | 32 | output reg signed [OUTPUT_WIDTH - 1 : 0] angle_o, 33 | output reg valid_o 34 | ); 35 | 36 | function [INPUT_WIDTH - 1 : 0] abs; 37 | input signed [INPUT_WIDTH - 1 : 0] arg; 38 | begin 39 | abs = arg[INPUT_WIDTH-1] ? -arg : arg; 40 | end 41 | endfunction 42 | 43 | function sign; 44 | input [INPUT_WIDTH - 1 : 0] arg; 45 | begin 46 | sign = !arg[INPUT_WIDTH - 1]; 47 | end 48 | endfunction 49 | 50 | localparam ATAN_OUT_DW = OUTPUT_WIDTH - 3; 51 | 52 | reg div_valid_in; 53 | reg div_valid_out; 54 | reg [LUT_DW - 1 : 0] div_result; 55 | reg [2 : 0] div_user_out; 56 | reg [2 : 0] div_user_in; 57 | reg [LUT_DW + INPUT_WIDTH - 1 : 0] numerator_wide, denominator_wide; 58 | div #( 59 | .INPUT_WIDTH(INPUT_WIDTH + LUT_DW), 60 | .RESULT_WIDTH(LUT_DW), 61 | .PIPELINED(1), 62 | .USER_WIDTH(3) 63 | ) 64 | div_i( 65 | .clk_i(clk_i), 66 | .reset_ni(reset_ni), 67 | 68 | .numerator_i(numerator_wide), 69 | .denominator_i(denominator_wide), 70 | .user_i(div_user_in), 71 | .valid_i(div_valid_in), 72 | 73 | .result_o(div_result), 74 | .user_o(div_user_out), 75 | .valid_o(div_valid_out) 76 | ); 77 | 78 | reg [LUT_DW - 1 : 0] atan_arg; 79 | reg [ATAN_OUT_DW - 1 : 0] atan_angle; 80 | reg atan_valid; 81 | atan #( 82 | .INPUT_WIDTH(LUT_DW), 83 | .OUTPUT_WIDTH(ATAN_OUT_DW) 84 | ) 85 | atan_i( 86 | .clk_i(clk_i), 87 | .reset_ni(reset_ni), 88 | 89 | .arg_i(atan_arg), 90 | .angle_o(atan_angle) 91 | ); 92 | wire [OUTPUT_WIDTH - 1 : 0] atan_angle_ext = {{(3){1'b0}}, atan_angle}; 93 | 94 | reg [1 : 0] state; 95 | reg signed [INPUT_WIDTH - 1 : 0] numerator; 96 | reg signed [INPUT_WIDTH - 1 : 0] denominator; 97 | reg inv_div_result; 98 | reg [2 : 0] user_out_N; 99 | reg signed [OUTPUT_WIDTH - 1 : 0] atan2_out, atan2_out_buf; 100 | reg atan2_valid; 101 | localparam signed [OUTPUT_WIDTH - 1 : 0] PI_HALF = 2 ** (OUTPUT_WIDTH - 1) - 1; 102 | localparam signed [OUTPUT_WIDTH - 1 : 0] PI_QUARTER = 2 ** (OUTPUT_WIDTH - 2) - 1; 103 | always @(posedge clk_i) begin 104 | if (!reset_ni) begin 105 | angle_o <= '0; 106 | state <= '0; 107 | atan2_out = '0; 108 | atan2_out_buf <= '0; 109 | atan_valid <= '0; 110 | atan2_valid <= '0; 111 | div_user_in <= '0; 112 | inv_div_result = 0; 113 | end else begin 114 | // stage 0 115 | if (abs(denominator_i) > abs(numerator_i)) begin 116 | numerator = abs(numerator_i); 117 | denominator = abs(denominator_i); 118 | inv_div_result = 0; 119 | end else begin 120 | // $display("reverse"); 121 | inv_div_result = 1; 122 | numerator = abs(denominator_i); 123 | denominator = abs(numerator_i); 124 | end 125 | 126 | div_valid_in <= valid_i; 127 | div_user_in <= {inv_div_result, sign(numerator_i), sign(denominator_i)}; 128 | numerator_wide <= numerator != 0 ? (numerator << LUT_DW) - 1 : 0; // don't do -1 when numerator is 0 !!! 129 | denominator_wide <= {{(LUT_DW){1'b0}}, denominator}; // explicit zero padding is actually not needed 130 | 131 | // stage n 132 | atan_arg <= div_result; 133 | user_out_N <= div_user_out; 134 | atan_valid <= div_valid_out; 135 | 136 | // stage n + 1 137 | atan2_valid <= atan_valid; 138 | if (user_out_N[2]) atan2_out = PI_QUARTER - atan_angle_ext; 139 | else atan2_out = atan_angle_ext; 140 | // 1. quadrant 141 | if (user_out_N[1] && user_out_N[0]) begin end 142 | // do nothing 143 | // 2. quadrant 144 | else if (user_out_N[1] && (!user_out_N[0])) atan2_out = -atan2_out + PI_HALF; 145 | // 3. quadrant 146 | else if ((!user_out_N[1]) && (!user_out_N[0])) atan2_out = atan2_out - PI_HALF; 147 | // 4. quadrant 148 | else if ((!user_out_N[1]) && (user_out_N[0])) atan2_out = -atan2_out; 149 | atan2_out_buf <= atan2_out; 150 | 151 | // stage n + 2 152 | angle_o <= atan2_out_buf; 153 | valid_o <= atan2_valid; 154 | end 155 | end 156 | 157 | endmodule -------------------------------------------------------------------------------- /hdl/axi_ring_writer.sv: -------------------------------------------------------------------------------- 1 | // This core reads data from an AXI stream and writes it into a segmented 2 | // ring buffer. It is assumed that the stream sources delivers data immediately 3 | // after tready goes high, if not this DMA core will stop and signal an underflow. 4 | // Copyright (C) 2023 Benjamin Menkuec 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | module axi_ring_writer #( 20 | parameter NUM_SEGMENTS = 10, 21 | parameter SEGMENT_SIZE = 240, 22 | parameter IQ_WIDTH = 16, 23 | 24 | localparam BRAM_SIZE = NUM_SEGMENTS * SEGMENT_SIZE 25 | ) 26 | ( 27 | input clk_i, 28 | input reset_ni, 29 | 30 | // interface to ressource grid subscriber 31 | output reg s_axis_fifo_tready, 32 | input s_axis_fifo_tvalid, 33 | input [IQ_WIDTH - 1 : 0] s_axis_fifo_tdata, 34 | 35 | output reg [$clog2(NUM_SEGMENTS) - 1 : 0] last_segment_o, 36 | output reg busy_o, 37 | output reg underflow_o, 38 | 39 | // interface to CPU DMA 40 | ); 41 | 42 | endmodule -------------------------------------------------------------------------------- /hdl/axis_axil_fifo.sv: -------------------------------------------------------------------------------- 1 | module axis_axil_fifo #( 2 | parameter ID = 0, 3 | parameter DATA_WIDTH = 16, 4 | parameter FIFO_LEN = 8, // has to be power of 2 ! 5 | parameter USER_WIDTH = 1, 6 | parameter ADDRESS_WIDTH = 16 7 | ) 8 | ( 9 | input clk_i, 10 | input reset_ni, 11 | input clear_i, 12 | 13 | input [DATA_WIDTH - 1 : 0] s_axis_in_tdata, 14 | input [USER_WIDTH - 1 : 0] s_axis_in_tuser, 15 | input s_axis_in_tlast, 16 | input s_axis_in_tvalid, 17 | output reg s_axis_in_tfull, 18 | 19 | // AXI lite interface 20 | // write address channel 21 | input [ADDRESS_WIDTH - 1 : 0] s_axi_awaddr, 22 | input s_axi_awvalid, 23 | output reg s_axi_awready, 24 | 25 | // write data channel 26 | input [31 : 0] s_axi_wdata, 27 | input [ 3 : 0] s_axi_wstrb, // not used 28 | input s_axi_wvalid, 29 | output reg s_axi_wready, 30 | 31 | // write response channel 32 | output [ 1 : 0] s_axi_bresp, 33 | output reg s_axi_bvalid, 34 | input s_axi_bready, 35 | 36 | // read address channel 37 | input [ADDRESS_WIDTH - 1 : 0] s_axi_araddr, 38 | input s_axi_arvalid, 39 | output reg s_axi_arready, 40 | 41 | // read data channel 42 | output reg [31 : 0] s_axi_rdata, 43 | output [ 1 : 0] s_axi_rresp, 44 | output reg s_axi_rvalid, 45 | input s_axi_rready 46 | ); 47 | 48 | localparam PCORE_VERSION = 'h00010069; 49 | 50 | reg [DATA_WIDTH - 1 : 0] fifo_data; 51 | reg [USER_WIDTH - 1 : 0] fifo_user; 52 | reg [31 : 0] fifo_level; 53 | reg fifo_ready; 54 | reg fifo_valid; 55 | reg fifo_empty; 56 | reg fifo_last; 57 | 58 | wire reset_fifo_n = reset_ni && (~clear_i); 59 | 60 | AXIS_FIFO #( 61 | .DATA_WIDTH(DATA_WIDTH), 62 | .FIFO_LEN(FIFO_LEN), 63 | .USER_WIDTH(USER_WIDTH), 64 | .ASYNC(1'b0) 65 | ) 66 | AXIS_FIFO_i( 67 | .clk_i(clk_i), 68 | .s_reset_ni(reset_fifo_n), 69 | 70 | .s_axis_in_tdata(s_axis_in_tdata), 71 | .s_axis_in_tuser(s_axis_in_tuser), 72 | .s_axis_in_tlast(s_axis_in_tlast), 73 | .s_axis_in_tvalid(s_axis_in_tvalid), 74 | .s_axis_in_tfull(s_axis_in_tfull), 75 | 76 | .out_clk_i(clk_i), 77 | .m_reset_ni(reset_ni), 78 | .m_axis_out_tready(fifo_ready), 79 | .m_axis_out_tdata(fifo_data), 80 | .m_axis_out_tuser(fifo_user), 81 | .m_axis_out_tlast(fifo_last), 82 | .m_axis_out_tvalid(fifo_valid), 83 | .m_axis_out_tlevel(fifo_level), 84 | .m_axis_out_tempty(fifo_empty) 85 | ); 86 | 87 | wire rreq; 88 | wire [ADDRESS_WIDTH - 3 : 0] raddr; 89 | reg [31:0] rdata; 90 | reg rack; 91 | 92 | reg [1 : 0] state; 93 | always @(posedge clk_i) begin 94 | if (!reset_ni) begin 95 | rack <= '0; 96 | state <= '0; 97 | end else if (raddr == 7) begin 98 | case (state) 99 | 0 : begin 100 | rack <= '0; 101 | if ((!fifo_empty) && rreq) begin // this will block forever if fifo is empty ! 102 | fifo_ready <= 1; 103 | state <= 1; 104 | end 105 | end 106 | 1 : begin 107 | fifo_ready <= '0; 108 | if (fifo_valid) begin 109 | state <= 2; 110 | rack <= 1; 111 | end 112 | end 113 | 2 : begin 114 | rack <= '0; 115 | state <= '0; 116 | end 117 | endcase 118 | end else begin 119 | rack <= rreq; // ack immediately after req 120 | end 121 | end 122 | 123 | always @(posedge clk_i) begin 124 | if (!reset_ni) rdata <= '0; 125 | else begin 126 | if (rreq == 1'b1) begin 127 | case (raddr) 128 | 9'h000: rdata <= PCORE_VERSION; 129 | 9'h001: rdata <= ID; 130 | 9'h002: rdata <= '0; 131 | 9'h003: rdata <= 32'h4649464F; // "FIFO" 132 | 9'h004: rdata <= 32'h69696969; 133 | 134 | 9'h005: rdata <= fifo_level; 135 | 9'h006: rdata <= fifo_empty; 136 | 9'h007: rdata <= fifo_data; 137 | 9'h008: rdata <= fifo_user; 138 | default: rdata <= '0; 139 | endcase 140 | end 141 | end 142 | end 143 | 144 | AXI_lite_interface #( 145 | .ADDRESS_WIDTH(ADDRESS_WIDTH) 146 | ) 147 | AXI_lite_interface_i( 148 | .clk_i(clk_i), 149 | .reset_ni(reset_ni), 150 | 151 | .s_axi_awaddr(s_axi_awaddr), 152 | .s_axi_awvalid(s_axi_awvalid), 153 | .s_axi_awready(s_axi_awready), 154 | .s_axi_wdata(s_axi_wdata), 155 | .s_axi_wstrb(s_axi_wstrb), 156 | .s_axi_wvalid(s_axi_wvalid), 157 | .s_axi_wready(s_axi_wready), 158 | .s_axi_bresp(s_axi_bresp), 159 | .s_axi_bvalid(s_axi_bvalid), 160 | .s_axi_bready(s_axi_bready), 161 | .s_axi_araddr(s_axi_araddr), 162 | .s_axi_arvalid(s_axi_arvalid), 163 | .s_axi_arready(s_axi_arready), 164 | .s_axi_rdata(s_axi_rdata), 165 | .s_axi_rresp(s_axi_rresp), 166 | .s_axi_rvalid(s_axi_rvalid), 167 | .s_axi_rready(s_axi_rready), 168 | 169 | .wreq_o(), 170 | .waddr_o(), 171 | .wdata_o(), 172 | .wack(), 173 | .rreq_o(rreq), 174 | .raddr_o(raddr), 175 | .rdata(rdata), 176 | .rack(rack) 177 | ); 178 | 179 | endmodule -------------------------------------------------------------------------------- /hdl/axis_fifo_asym.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This is a FIFO with asymetric input / output widths. 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | module axis_fifo_asym #( 19 | parameter DATA_WIDTH_IN = 16, 20 | parameter DATA_WIDTH_OUT = 8, 21 | parameter ADDRESS_WIDTH_IN = 4, 22 | parameter USER_WIDTH_IN = 2, 23 | parameter ASYNC = 1, 24 | 25 | localparam RATIO_TYPE = DATA_WIDTH_IN >= DATA_WIDTH_OUT, 26 | localparam RATIO = RATIO_TYPE ? DATA_WIDTH_IN / DATA_WIDTH_OUT : DATA_WIDTH_OUT / DATA_WIDTH_IN, 27 | localparam USER_WIDTH_OUT = USER_WIDTH_IN == 0 ? 0 : ((RATIO_TYPE) ? USER_WIDTH_IN / RATIO : USER_WIDTH_IN * RATIO) 28 | ) 29 | ( 30 | input clk_i, 31 | input s_reset_ni, 32 | 33 | input [DATA_WIDTH_IN - 1 : 0] s_axis_in_tdata, 34 | input [USER_WIDTH_IN - 1 : 0] s_axis_in_tuser, 35 | input s_axis_in_tlast, 36 | input s_axis_in_tvalid, 37 | output reg s_axis_in_tfull, 38 | 39 | input out_clk_i, 40 | input m_reset_ni, 41 | input m_axis_out_tready, 42 | output reg [DATA_WIDTH_OUT - 1 : 0] m_axis_out_tdata, 43 | output reg [USER_WIDTH_OUT - 1 : 0] m_axis_out_tuser, 44 | output reg m_axis_out_tlast, 45 | output reg m_axis_out_tvalid, 46 | output reg [ADDRESS_WIDTH_IN - 1 : 0] m_axis_out_tlevel, 47 | output reg m_axis_out_tempty 48 | ); 49 | 50 | localparam A_WIDTH = (RATIO_TYPE) ? DATA_WIDTH_OUT : DATA_WIDTH_IN; 51 | localparam A_ADDRESS = (RATIO_TYPE) ? ADDRESS_WIDTH_IN : (ADDRESS_WIDTH_IN - $clog2(RATIO)); 52 | localparam A_USER_WIDTH = (RATIO_TYPE) ? USER_WIDTH_OUT : USER_WIDTH_IN; 53 | 54 | wire [RATIO * A_WIDTH - 1 : 0] s_axis_data_int; 55 | wire [RATIO-1:0] s_axis_valid_int; 56 | wire [RATIO * A_USER_WIDTH - 1 : 0] s_axis_user_int; 57 | wire [RATIO-1:0] s_axis_tlast_int; 58 | wire [RATIO-1:0] s_axis_tready_int; 59 | 60 | wire [RATIO * A_WIDTH - 1 : 0] m_axis_data_int; 61 | wire [RATIO-1:0] m_axis_valid_int; 62 | wire [RATIO * A_USER_WIDTH - 1 : 0] m_axis_user_int; 63 | wire [RATIO-1:0] m_axis_ready_int; 64 | 65 | wire user_out_ready; 66 | 67 | reg [$clog2(RATIO) - 1 : 0] s_axis_counter; 68 | reg [$clog2(RATIO) - 1 : 0] m_axis_counter; 69 | 70 | genvar ii; 71 | for (ii = 0; ii < RATIO; ii = ii + 1) begin 72 | AXIS_FIFO #( 73 | .DATA_WIDTH(A_WIDTH), 74 | .FIFO_LEN(2 ** A_ADDRESS), 75 | .USER_WIDTH(A_USER_WIDTH), 76 | .ASYNC(ASYNC) 77 | ) 78 | axis_fifo_i( 79 | .clk_i(clk_i), 80 | .s_reset_ni(s_reset_ni), 81 | .s_axis_in_tdata(s_axis_data_int[A_WIDTH * ii +: A_WIDTH]), 82 | .s_axis_in_tvalid(s_axis_valid_int[ii]), 83 | .s_axis_in_tuser(s_axis_user_int[A_USER_WIDTH * ii +: A_USER_WIDTH]), 84 | 85 | .out_clk_i(out_clk_i), 86 | .m_reset_ni(m_reset_ni), 87 | .m_axis_out_tdata(m_axis_data_int[A_WIDTH * ii +: A_WIDTH]), 88 | .m_axis_out_tvalid(m_axis_valid_int[ii]), 89 | .m_axis_out_tuser(m_axis_user_int[A_USER_WIDTH * ii +: A_USER_WIDTH]), 90 | .m_axis_out_tready(m_axis_ready_int[ii]) 91 | ); 92 | end 93 | 94 | // write logic 95 | if (RATIO_TYPE) begin : big_slave 96 | for (ii = 0; ii < RATIO; ii = ii + 1) begin 97 | assign s_axis_valid_int[ii] = s_axis_in_tvalid; 98 | assign s_axis_tlast_int[ii] = s_axis_in_tlast; 99 | end 100 | assign s_axis_data_int = s_axis_in_tdata; 101 | assign s_axis_user_int = s_axis_in_tuser; 102 | end else begin : small_slave 103 | initial $display("Error: small slave is not yet implemented!"); 104 | initial $finish(); 105 | end 106 | 107 | // read logic 108 | if (RATIO_TYPE) begin : small_master 109 | for (ii = 0; ii < RATIO; ii = ii + 1) begin 110 | assign m_axis_ready_int[ii] = (m_axis_counter == ii) ? m_axis_out_tready : 1'b0; 111 | end 112 | 113 | // user data is the same for all atomic outputs 114 | assign user_out_ready = (m_axis_counter == 0) ? m_axis_out_tready : 1'b0; 115 | 116 | assign m_axis_out_tdata = m_axis_data_int >> (m_axis_counter * A_WIDTH); 117 | assign m_axis_out_tvalid = m_axis_valid_int >> m_axis_counter; 118 | assign m_axis_out_tuser = m_axis_user_int >> (m_axis_counter * A_USER_WIDTH); 119 | end else begin : big_master 120 | initial $display("Error: big master is not yet implemented!"); 121 | initial $finish(); 122 | end 123 | 124 | // sequencer 125 | if (RATIO == 1) begin 126 | initial m_axis_counter = 1'b0; 127 | end else begin 128 | always @(posedge out_clk_i) begin 129 | if (!m_reset_ni) begin 130 | m_axis_counter <= 0; 131 | end else begin 132 | if (m_axis_out_tready && m_axis_out_tvalid) begin 133 | m_axis_counter <= m_axis_counter + 1'b1; 134 | end 135 | end 136 | end 137 | end 138 | 139 | endmodule -------------------------------------------------------------------------------- /hdl/complex_multiplier: -------------------------------------------------------------------------------- 1 | ../submodules/complex_multiplier/hdl/ -------------------------------------------------------------------------------- /hdl/demap.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This is a demodulator with AXI stream interface. 3 | // It currently supports QPSK only. 4 | // Copyright (C) 2023 Benjamin Menkuec 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | module demap #( 20 | parameter IQ_DW = 16, 21 | parameter LLR_DW = 8 22 | ) 23 | ( 24 | input clk_i, 25 | input reset_ni, 26 | input [IQ_DW * 2 - 1 : 0] s_axis_in_tdata, 27 | input [1 : 0] s_axis_in_tuser, 28 | input s_axis_in_tlast, 29 | input s_axis_in_tvalid, 30 | 31 | output [LLR_DW - 1 : 0] m_axis_out_tdata, 32 | output [1 : 0] m_axis_out_tuser, 33 | output m_axis_out_tlast, 34 | output m_axis_out_tvalid 35 | ); 36 | 37 | initial begin 38 | if (LLR_DW > IQ_DW / 2) begin 39 | $display("LLR_DW > IQ_DW / 2, this does not make sense!"); 40 | end 41 | end 42 | 43 | reg out_fifo_valid_in; 44 | reg [2 * 2 - 1 : 0] out_fifo_user_in; 45 | reg out_fifo_last_in; 46 | reg [LLR_DW * 2 - 1 : 0] out_fifo_data_in; 47 | wire [LLR_DW - 1 : 0] llr_I; 48 | wire [LLR_DW - 1 : 0] llr_Q; 49 | if (LLR_DW > IQ_DW) begin 50 | assign llr_I = s_axis_in_tdata[IQ_DW - 1 -: IQ_DW] << (LLR_DW - IQ_DW); 51 | assign llr_Q = s_axis_in_tdata[IQ_DW / 2 - 1 -: IQ_DW] << (LLR_DW - IQ_DW); 52 | end else begin 53 | assign llr_I = s_axis_in_tdata[IQ_DW - 1 -: LLR_DW]; 54 | assign llr_Q = s_axis_in_tdata[IQ_DW * 2 - 1 -: LLR_DW]; 55 | end 56 | always @(posedge clk_i) begin 57 | if (!reset_ni) begin 58 | out_fifo_valid_in <= '0; 59 | out_fifo_data_in <= '0; 60 | end else if (s_axis_in_tvalid && (s_axis_in_tuser == 1)) begin // only demap PBCH symbols 61 | out_fifo_data_in <= {llr_Q, llr_I}; 62 | out_fifo_valid_in <= 1; 63 | out_fifo_user_in <= {s_axis_in_tuser, s_axis_in_tuser}; 64 | out_fifo_last_in <= s_axis_in_tlast; 65 | end else begin 66 | out_fifo_valid_in <= '0; 67 | end 68 | end 69 | 70 | axis_fifo_asym #( 71 | .DATA_WIDTH_IN(2 * LLR_DW), 72 | .DATA_WIDTH_OUT(LLR_DW), 73 | .ADDRESS_WIDTH_IN(10), 74 | .USER_WIDTH_IN(2 * 2), 75 | .ASYNC(0) 76 | ) 77 | out_fifo_i( 78 | .clk_i(clk_i), 79 | .s_reset_ni(reset_ni), 80 | 81 | .s_axis_in_tdata(out_fifo_data_in), 82 | .s_axis_in_tuser(out_fifo_user_in), 83 | .s_axis_in_tlast(out_fifo_last_in), 84 | .s_axis_in_tvalid(out_fifo_valid_in), 85 | .s_axis_in_tfull(), 86 | 87 | .out_clk_i(clk_i), 88 | .m_reset_ni(reset_ni), 89 | .m_axis_out_tready(1'b1), 90 | .m_axis_out_tdata(m_axis_out_tdata), 91 | .m_axis_out_tuser(m_axis_out_tuser), 92 | .m_axis_out_tlast(m_axis_out_tlast), 93 | .m_axis_out_tvalid(m_axis_out_tvalid), 94 | .m_axis_out_tlevel(), 95 | .m_axis_out_tempty() 96 | ); 97 | 98 | // AXIS_FIFO #( 99 | // .DATA_WIDTH(LLR_DW), 100 | // .FIFO_LEN(1024), 101 | // .USER_WIDTH(2), 102 | // .ASYNC(0), 103 | // .IN_MUX(2) 104 | // ) 105 | // out_fifo_i( 106 | // .clk_i(clk_i), 107 | // .reset_ni(reset_ni), 108 | 109 | // .s_axis_in_tdata(out_fifo_data_in), 110 | // .s_axis_in_tuser(out_fifo_user_in), 111 | // .s_axis_in_tlast(out_fifo_last_in), 112 | // .s_axis_in_tvalid(out_fifo_valid_in), 113 | 114 | // .m_axis_out_tready(1'b1), 115 | // .m_axis_out_tdata(m_axis_out_tdata), 116 | // .m_axis_out_tuser(m_axis_out_tuser), 117 | // .m_axis_out_tlast(m_axis_out_tlast), 118 | // .m_axis_out_tvalid(m_axis_out_tvalid) 119 | // ); 120 | 121 | endmodule -------------------------------------------------------------------------------- /hdl/div.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This is a divider for positive integer numbers 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | module div #( 19 | parameter INPUT_WIDTH = 16, 20 | parameter RESULT_WIDTH = 16, 21 | parameter PIPELINED = 0, 22 | parameter USER_WIDTH = 1 23 | ) 24 | ( 25 | input clk_i, 26 | input reset_ni, 27 | 28 | input [INPUT_WIDTH - 1 : 0] numerator_i, 29 | input [INPUT_WIDTH - 1 : 0] denominator_i, 30 | input [USER_WIDTH - 1 : 0] user_i, 31 | input valid_i, 32 | 33 | output reg [RESULT_WIDTH - 1 : 0] result_o, 34 | output reg [USER_WIDTH - 1 : 0] user_o, 35 | output reg valid_o 36 | ); 37 | 38 | if (PIPELINED) begin 39 | localparam STAGES = RESULT_WIDTH + 1; 40 | reg [RESULT_WIDTH - 1 : 0] result_int [0 : STAGES - 1]; 41 | reg [STAGES - 1 : 0] valid_int; 42 | reg [INPUT_WIDTH + RESULT_WIDTH - 1 : 0] denominator [0 : STAGES - 1]; 43 | reg [INPUT_WIDTH - 1 : 0] numerator [0 : STAGES - 1]; 44 | reg [USER_WIDTH - 1 : 0] user_int [0 : STAGES - 1]; 45 | assign result_o = result_int[STAGES - 1]; // assigning to registers makes them to wires, is this coding style bad? 46 | assign user_o = user_int[STAGES - 1]; 47 | assign valid_o = valid_int[STAGES - 1]; 48 | always @(posedge clk_i) begin 49 | if (!reset_ni) begin 50 | for (integer i = 0; i < STAGES; i = i + 1) begin 51 | result_int[i] <= '0; 52 | denominator[i] <= '0; 53 | numerator[i] <= '0; 54 | valid_int[i] <= '0; 55 | user_int[i] <= '0; 56 | // result_o <= '0; 57 | // valid_o <= '0; 58 | end 59 | end else begin 60 | // result_o <= result_int[STAGES - 1]; 61 | // valid_o <= valid_int[STAGES - 1]; 62 | for (integer ii = 0; ii < STAGES; ii = ii + 1) begin 63 | if (ii == 0) begin 64 | valid_int[ii] <= valid_i; 65 | numerator[ii] <= numerator_i; 66 | denominator[ii] <= denominator_i; 67 | user_int[ii] <= user_i; 68 | end else begin 69 | if (numerator[ii - 1] >= (denominator[ii - 1] << (RESULT_WIDTH - ii))) begin 70 | numerator[ii] <= numerator[ii - 1] - (denominator[ii - 1] << (RESULT_WIDTH - ii)); 71 | result_int[ii] <= result_int[ii - 1] + 2 ** (RESULT_WIDTH - ii); 72 | end else begin 73 | numerator[ii] <= numerator[ii - 1]; 74 | result_int[ii] <= result_int[ii - 1]; 75 | end 76 | denominator[ii] <= denominator[ii - 1]; 77 | valid_int[ii] <= valid_int[ii - 1]; 78 | user_int[ii] <= user_int[ii - 1]; 79 | end 80 | end 81 | end 82 | end 83 | 84 | end else begin 85 | reg [1 : 0] state; 86 | reg [INPUT_WIDTH - 1 : 0] numerator; 87 | reg [INPUT_WIDTH + RESULT_WIDTH - 1 : 0] denominator; 88 | reg [$clog2(RESULT_WIDTH) : 0] div_pos; 89 | 90 | always @(posedge clk_i) begin 91 | if (!reset_ni) begin 92 | result_o <= '0; 93 | valid_o <= '0; 94 | state <= '0; 95 | div_pos <= '0; 96 | end else begin 97 | case(state) 98 | 0 : begin 99 | if (valid_i) begin 100 | if ((numerator_i == 0) || (denominator_i == 0)) begin 101 | result_o <= '0; 102 | valid_o <= 1; 103 | end else begin 104 | numerator <= numerator_i; 105 | denominator <= denominator_i; 106 | result_o <= '0; 107 | div_pos <= RESULT_WIDTH - 1; 108 | state <= 1; 109 | valid_o <= '0; 110 | // $display("div: calculate %d / %d", numerator_i, denominator_i); 111 | end 112 | end else begin 113 | valid_o <= '0; 114 | end 115 | end 116 | 1 : begin 117 | if (numerator >= (denominator << div_pos)) begin 118 | numerator <= numerator - (denominator << div_pos); 119 | result_o <= result_o + 2**div_pos; 120 | end 121 | div_pos <= div_pos - 1; 122 | if (div_pos == 0) begin 123 | state <= 0; 124 | valid_o <= '1; 125 | end 126 | end 127 | endcase 128 | end 129 | end 130 | end 131 | 132 | endmodule -------------------------------------------------------------------------------- /hdl/dot_product.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module dot_product 4 | #( 5 | parameter A_DW = 1, // input a data width 6 | parameter B_DW = 32, // input b data width 7 | parameter OUT_DW = 8, // output data width 8 | parameter LEN = 128, // len 9 | parameter A_COMPLEX = 0, // is input a complex? 10 | parameter B_COMPLEX = 1 // is input b complex? 11 | ) 12 | ( 13 | input clk_i, 14 | input reset_ni, 15 | input start_i, 16 | input wire [A_DW-1:0] s_axis_a_tdata, 17 | input s_axis_a_tvalid, 18 | output reg s_axis_a_tready, 19 | input wire [B_DW-1:0] s_axis_b_tdata, 20 | input s_axis_b_tvalid, 21 | output reg [OUT_DW-1:0] result_o, 22 | output reg valid_o 23 | ); 24 | 25 | localparam REAL_COMPLEX = (A_COMPLEX != B_COMPLEX); 26 | localparam COMPLEX_DW = A_COMPLEX ? A_DW : B_DW; 27 | localparam REAL_DW = A_COMPLEX ? B_DW : A_DW; 28 | wire [COMPLEX_DW - 1 : 0] complex_in; 29 | assign complex_in = A_COMPLEX ? s_axis_a_tdata : s_axis_b_tdata; 30 | wire signed [COMPLEX_DW / 2 - 1 : 0] complex_in_re, complex_in_im; 31 | assign complex_in_re = complex_in[COMPLEX_DW / 2 - 1 : 0]; 32 | assign complex_in_im = complex_in[COMPLEX_DW - 1 : COMPLEX_DW / 2]; 33 | wire [REAL_DW - 1 : 0] real_in; 34 | assign real_in = B_COMPLEX ? s_axis_a_tdata : s_axis_b_tdata; 35 | 36 | initial begin 37 | $display("REAL_COMPLEX = %d", REAL_COMPLEX); 38 | $display("A_DW = %d B_DW = %d", A_DW, B_DW); 39 | end 40 | 41 | reg signed [OUT_DW : 0] acc; // user needs to make sure no overflow happens ! 42 | reg signed [OUT_DW / 2 - 1 : 0] acc_re, acc_im; 43 | 44 | reg [10 : 0] state; 45 | reg [$clog2(LEN) : 0] cnt; 46 | always @(posedge clk_i) begin 47 | if (!reset_ni) begin 48 | $display("RESET"); 49 | acc <= '0; 50 | acc_re <= '0; 51 | acc_im <= '0; 52 | result_o <= '0; 53 | valid_o <= '0; 54 | state <= 0; 55 | s_axis_a_tready <= '0; 56 | end else begin 57 | if (state == 0) begin // perform multiply adds 58 | if (cnt == LEN - 1) begin 59 | state <= 1; 60 | valid_o <= 1'b1; 61 | if (REAL_COMPLEX) begin 62 | result_o <= {acc_im, acc_re}; 63 | end else begin 64 | // TODO: implement 65 | end 66 | end else if (s_axis_a_tvalid) begin 67 | state <= 0; 68 | valid_o <= '0; 69 | // perform multiply adds 70 | // $display("cnt = %d complex_in_re = %d acc_re = %d acc_im = %d", cnt, complex_in_re, acc_re, acc_im); 71 | if (start_i) begin 72 | cnt <= '0; 73 | if(REAL_COMPLEX) begin 74 | if(REAL_DW == 1) begin // one input is signed boolean 75 | if (real_in == 1) begin 76 | acc_re <= complex_in_re; 77 | acc_im <= complex_in_im; 78 | end else begin 79 | acc_re <= complex_in_re; 80 | acc_im <= complex_in_im; 81 | end 82 | end else begin 83 | // TODO: implement 84 | end 85 | end else begin 86 | // TODO: implement 87 | end 88 | end else begin 89 | cnt <= cnt + 1; 90 | if(REAL_COMPLEX) begin 91 | if(REAL_DW == 1) begin // one input is signed boolean 92 | if (real_in == 1) begin 93 | acc_re <= acc_re + complex_in_re; 94 | acc_im <= acc_im + complex_in_im; 95 | end else begin 96 | acc_re <= acc_re - complex_in_re; 97 | acc_im <= acc_im - complex_in_im; 98 | end 99 | end else begin 100 | // TODO: implement 101 | end 102 | end else begin 103 | // TODO: implement 104 | end 105 | end 106 | end 107 | end else if (state == 1) begin // output result 108 | state <= '0; 109 | s_axis_a_tready <= '1; 110 | valid_o <= '0; 111 | cnt <= '0; 112 | end 113 | else begin // should never happen 114 | // $display("ERROR state = %d", state); 115 | end 116 | end 117 | end 118 | endmodule -------------------------------------------------------------------------------- /hdl/frame_sync_regmap.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core connects AXI lite mapped registers from the Open5G receiver 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | module frame_sync_regmap #( 19 | parameter ID = 0, 20 | parameter ADDRESS_WIDTH = 11 21 | ) 22 | ( 23 | input clk_i, 24 | input reset_ni, 25 | 26 | // AXI lite interface 27 | // write address channel 28 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_awaddr, 29 | input s_axi_if_awvalid, 30 | output reg s_axi_if_awready, 31 | 32 | // write data channel 33 | input [31 : 0] s_axi_if_wdata, 34 | input [ 3 : 0] s_axi_if_wstrb, // not used 35 | input s_axi_if_wvalid, 36 | output reg s_axi_if_wready, 37 | 38 | // write response channel 39 | output [ 1 : 0] s_axi_if_bresp, 40 | output reg s_axi_if_bvalid, 41 | input s_axi_if_bready, 42 | 43 | // read address channel 44 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_araddr, 45 | input s_axi_if_arvalid, 46 | output reg s_axi_if_arready, 47 | 48 | // read data channel 49 | output reg [31 : 0] s_axi_if_rdata, 50 | output [ 1 : 0] s_axi_if_rresp, 51 | output reg s_axi_if_rvalid, 52 | input s_axi_if_rready, 53 | 54 | // mapped registers 55 | input [1 : 0] fs_state_i, 56 | input signed [7 : 0] sample_cnt_mismatch_i, 57 | input [15 : 0] missed_SSBs_i, 58 | input [2 : 0] ibar_SSB_i, 59 | input [31 : 0] clks_btwn_SSBs_i, 60 | input [31 : 0] num_disconnects_i, 61 | input [1 : 0] reconnect_mode_i, 62 | input rgf_overflow_i, 63 | input [31 : 0] timing_advance_i, 64 | input timing_advance_queued_i, 65 | 66 | output reconnect_mode_write_o, 67 | output [1 : 0] reconnect_mode_o, 68 | output timing_advance_write_o, 69 | output [31 : 0] timing_advance_o, 70 | output reg timing_advance_mode_o, 71 | output reg [7 : 0] sym_cnt_offset_o 72 | ); 73 | 74 | localparam PCORE_VERSION = 'h00010061; // 1.0.a 75 | 76 | wire rreq; 77 | wire [8:0] raddr; 78 | reg [31:0] rdata; 79 | reg rack; 80 | 81 | always @(posedge clk_i) begin 82 | if (!reset_ni) rack <= '0; 83 | else rack <= rreq; // ack immediately after req 84 | end 85 | 86 | always @(posedge clk_i) begin 87 | if (!reset_ni) rdata <= '0; 88 | else begin 89 | if (rreq == 1'b1) begin 90 | case (raddr) 91 | 9'h000: rdata <= PCORE_VERSION; 92 | 9'h001: rdata <= ID; 93 | 9'h002: rdata <= '0; 94 | 9'h003: rdata <= 32'h46537E7E; // "FS~~" 95 | 9'h004: rdata <= 32'h69696969; 96 | 9'h005: rdata <= {30'd0, fs_state_i}; 97 | 9'h006: rdata <= {30'd0, reconnect_mode_i}; 98 | 9'h007: rdata <= {31'd0, rgf_overflow_i}; 99 | 9'h008: rdata <= '0; 100 | 9'h009: rdata <= {24'd0, sample_cnt_mismatch_i}; 101 | 9'h00A: rdata <= {16'd0, missed_SSBs_i}; 102 | 9'h00B: rdata <= {29'd0, ibar_SSB_i}; 103 | 9'h00C: rdata <= clks_btwn_SSBs_i; 104 | 9'h00D: rdata <= num_disconnects_i; 105 | 9'h00E: rdata <= '0; 106 | 9'h010: rdata <= timing_advance_i; 107 | 9'h011: rdata <= {31'd0, timing_advance_mode_o}; 108 | 9'h012: rdata <= timing_advance_queued_i; 109 | 9'h013: rdata <= sym_cnt_offset_o; 110 | default: rdata <= '0; 111 | endcase 112 | end 113 | end 114 | end 115 | 116 | wire wreq; 117 | wire [8:0] waddr; 118 | reg [31:0] wdata; 119 | reg wack; 120 | 121 | always @(posedge clk_i) begin 122 | if (!reset_ni) wack <= '0; 123 | else wack <= wreq; // ack immediately after req 124 | end 125 | 126 | assign reconnect_mode_write_o = wreq && (waddr == 9'h006); 127 | assign reconnect_mode_o = wdata[1 : 0]; 128 | assign timing_advance_write_o = wreq && (waddr == 9'h010); 129 | assign timing_advance_o = wdata; 130 | always @(posedge clk_i) begin 131 | if (!reset_ni) begin 132 | timing_advance_mode_o <= '0; 133 | sym_cnt_offset_o <= '0; 134 | end else begin 135 | if (wreq) begin 136 | case (waddr) 137 | 9'h011: timing_advance_mode_o <= wdata[0]; 138 | 9'h013: sym_cnt_offset_o <= wdata; 139 | default: begin end 140 | endcase 141 | end 142 | end 143 | end 144 | 145 | AXI_lite_interface #( 146 | .ADDRESS_WIDTH(ADDRESS_WIDTH) 147 | ) 148 | AXI_lite_interface_i( 149 | .clk_i(clk_i), 150 | .reset_ni(reset_ni), 151 | 152 | .s_axi_awaddr(s_axi_if_awaddr), 153 | .s_axi_awvalid(s_axi_if_awvalid), 154 | .s_axi_awready(s_axi_if_awready), 155 | .s_axi_wdata(s_axi_if_wdata), 156 | .s_axi_wstrb(s_axi_if_wstrb), 157 | .s_axi_wvalid(s_axi_if_wvalid), 158 | .s_axi_wready(s_axi_if_wready), 159 | .s_axi_bresp(s_axi_if_bresp), 160 | .s_axi_bvalid(s_axi_if_bvalid), 161 | .s_axi_bready(s_axi_if_bready), 162 | .s_axi_araddr(s_axi_if_araddr), 163 | .s_axi_arvalid(s_axi_if_arvalid), 164 | .s_axi_arready(s_axi_if_arready), 165 | .s_axi_rdata(s_axi_if_rdata), 166 | .s_axi_rresp(s_axi_if_rresp), 167 | .s_axi_rvalid(s_axi_if_rvalid), 168 | .s_axi_rready(s_axi_if_rready), 169 | 170 | .wreq_o(wreq), 171 | .waddr_o(waddr), 172 | .wdata_o(wdata), 173 | .wack(wack), 174 | .rreq_o(rreq), 175 | .raddr_o(raddr), 176 | .rdata(rdata), 177 | .rack(rack) 178 | ); 179 | 180 | endmodule -------------------------------------------------------------------------------- /hdl/receiver_regmap.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | // This core connects AXI lite mapped registers from the Open5G receiver 3 | // Copyright (C) 2023 Benjamin Menkuec 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | module receiver_regmap #( 19 | parameter ID = 0, 20 | parameter ADDRESS_WIDTH = 11, 21 | 22 | localparam N_id_MAX = 1007 23 | ) 24 | ( 25 | input clk_i, 26 | input reset_ni, 27 | 28 | // AXI lite interface 29 | // write address channel 30 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_awaddr, 31 | input s_axi_if_awvalid, 32 | output reg s_axi_if_awready, 33 | 34 | // write data channel 35 | input [31 : 0] s_axi_if_wdata, 36 | input [ 3 : 0] s_axi_if_wstrb, // not used 37 | input s_axi_if_wvalid, 38 | output reg s_axi_if_wready, 39 | 40 | // write response channel 41 | output [ 1 : 0] s_axi_if_bresp, 42 | output reg s_axi_if_bvalid, 43 | input s_axi_if_bready, 44 | 45 | // read address channel 46 | input [ADDRESS_WIDTH - 1 : 0] s_axi_if_araddr, 47 | input s_axi_if_arvalid, 48 | output reg s_axi_if_arready, 49 | 50 | // read data channel 51 | output reg [31 : 0] s_axi_if_rdata, 52 | output [ 1 : 0] s_axi_if_rresp, 53 | output reg s_axi_if_rvalid, 54 | input s_axi_if_rready, 55 | 56 | // mapped registers 57 | input [31 : 0] rx_signal_i, 58 | input [1 : 0] N_id_2_i, 59 | input [$clog2(N_id_MAX) - 1 : 0] N_id_i, 60 | input [31 : 0] N_id_used_i 61 | ); 62 | 63 | localparam PCORE_VERSION = 'h00010061; // 1.0.a 64 | 65 | wire rreq; 66 | wire [8:0] raddr; 67 | reg [31:0] rdata; 68 | reg rack; 69 | 70 | always @(posedge clk_i) begin 71 | if (!reset_ni) rack <= '0; 72 | else rack <= rreq; // ack immediately after req 73 | end 74 | 75 | always @(posedge clk_i) begin 76 | if (!reset_ni) rdata <= '0; 77 | else begin 78 | if (rreq == 1'b1) begin 79 | case (raddr) 80 | 9'h000: rdata <= PCORE_VERSION; 81 | 9'h001: rdata <= ID; 82 | 9'h002: rdata <= '0; 83 | 9'h003: rdata <= 32'h52587E7E; // "RX~~" 84 | 9'h004: rdata <= 32'h69696969; 85 | 9'h005: rdata <= '0; 86 | 9'h006: rdata <= rx_signal_i; 87 | 9'h007: rdata <= {30'd0, N_id_2_i}; 88 | 9'h008: rdata <= {22'd0, N_id_i}; 89 | 9'h009: rdata <= '0; 90 | 9'h00A: rdata <= '0; 91 | 9'h00B: rdata <= '0; 92 | 9'h00C: rdata <= '0; 93 | 9'h00D: rdata <= '0; 94 | 9'h00E: rdata <= N_id_used_i; 95 | default: rdata <= '0; 96 | endcase 97 | end 98 | end 99 | end 100 | 101 | wire wreq; 102 | wire [8:0] waddr; 103 | reg [31:0] wdata; 104 | reg wack; 105 | 106 | always @(posedge clk_i) begin 107 | if (!reset_ni) wack <= '0; 108 | else wack <= wreq; // ack immediately after req 109 | end 110 | 111 | always @(posedge clk_i) begin 112 | if (!reset_ni) begin 113 | end else begin 114 | if (wreq) begin 115 | case (waddr) 116 | default: begin end 117 | endcase 118 | end 119 | end 120 | end 121 | 122 | AXI_lite_interface #( 123 | .ADDRESS_WIDTH(ADDRESS_WIDTH) 124 | ) 125 | AXI_lite_interface_i( 126 | .clk_i(clk_i), 127 | .reset_ni(reset_ni), 128 | 129 | .s_axi_awaddr(s_axi_if_awaddr), 130 | .s_axi_awvalid(s_axi_if_awvalid), 131 | .s_axi_awready(s_axi_if_awready), 132 | .s_axi_wdata(s_axi_if_wdata), 133 | .s_axi_wstrb(s_axi_if_wstrb), 134 | .s_axi_wvalid(s_axi_if_wvalid), 135 | .s_axi_wready(s_axi_if_wready), 136 | .s_axi_bresp(s_axi_if_bresp), 137 | .s_axi_bvalid(s_axi_if_bvalid), 138 | .s_axi_bready(s_axi_if_bready), 139 | .s_axi_araddr(s_axi_if_araddr), 140 | .s_axi_arvalid(s_axi_if_arvalid), 141 | .s_axi_arready(s_axi_if_arready), 142 | .s_axi_rdata(s_axi_if_rdata), 143 | .s_axi_rresp(s_axi_if_rresp), 144 | .s_axi_rvalid(s_axi_if_rvalid), 145 | .s_axi_rready(s_axi_if_rready), 146 | 147 | .wreq_o(wreq), 148 | .waddr_o(waddr), 149 | .wdata_o(wdata), 150 | .wack(wack), 151 | .rreq_o(rreq), 152 | .raddr_o(raddr), 153 | .rdata(rdata), 154 | .rack(rack) 155 | ); 156 | 157 | endmodule -------------------------------------------------------------------------------- /hdl/ressource_grid_framer.sv: -------------------------------------------------------------------------------- 1 | // This core receives ressource grid data and provides it to the 2 | // AXI-DMAC via AXI stream interface. The block exponent is inserted 3 | // at the beginning of each symbol. 4 | // Copyright (C) 2023 Benjamin Menkuec 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | module ressource_grid_framer #( 20 | parameter IQ_WIDTH = 16, 21 | parameter BLK_EXP_LEN = 8, 22 | 23 | localparam SFN_MAX = 1023, 24 | localparam SUBFRAMES_PER_FRAME = 20, 25 | localparam SYM_PER_SF = 14, 26 | localparam SFN_WIDTH = $clog2(SFN_MAX), 27 | localparam SUBFRAME_NUMBER_WIDTH = $clog2(SUBFRAMES_PER_FRAME - 1), 28 | localparam SYMBOL_NUMBER_WIDTH = $clog2(SYM_PER_SF - 1), 29 | localparam SAMPLE_ID_WIDTH = 64, 30 | localparam USER_WIDTH = SFN_WIDTH + SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN + 1 31 | ) 32 | ( 33 | input clk_i, 34 | input reset_ni, 35 | 36 | // interface to FFT_demod 37 | input [IQ_WIDTH - 1 : 0] s_axis_iq_tdata, 38 | input s_axis_iq_tvalid, 39 | input [USER_WIDTH - 1 : 0] s_axis_iq_tuser, 40 | input s_axis_iq_tlast, 41 | 42 | // interface to sample_id FIFO 43 | input [SAMPLE_ID_WIDTH - 1 : 0] sample_id_data, 44 | input sample_id_valid, 45 | output reg sample_id_ready, 46 | 47 | // interface to frame_sync 48 | output reg overflow_o, 49 | 50 | // interface to AXI-DMAC 51 | input m_axis_fifo_tready, 52 | output reg m_axis_fifo_tvalid, 53 | output reg [IQ_WIDTH - 1 : 0] m_axis_fifo_tdata, 54 | output reg m_axis_fifo_tlast, 55 | 56 | // interface to CPU interrupt controller 57 | output reg int_o 58 | ); 59 | 60 | reg out_fifo_ready; 61 | wire [IQ_WIDTH - 1 : 0] out_fifo_data; 62 | wire out_fifo_last; 63 | wire out_fifo_valid; 64 | wire [BLK_EXP_LEN - 1 : 0] blk_exp; 65 | AXIS_FIFO #( 66 | .DATA_WIDTH(IQ_WIDTH), 67 | .FIFO_LEN(1024), 68 | .USER_WIDTH(BLK_EXP_LEN), 69 | .ASYNC(0) 70 | ) 71 | output_fifo_i( 72 | .clk_i(clk_i), 73 | .s_reset_ni(reset_ni), 74 | 75 | .s_axis_in_tdata(s_axis_iq_tdata), 76 | .s_axis_in_tvalid(s_axis_iq_tvalid), 77 | .s_axis_in_tlast(s_axis_iq_tlast), 78 | .s_axis_in_tuser(s_axis_iq_tuser[BLK_EXP_LEN -: BLK_EXP_LEN]), 79 | 80 | .out_clk_i(clk_i), 81 | .m_reset_ni(reset_ni), 82 | .m_axis_out_tready(out_fifo_ready), 83 | .m_axis_out_tdata(out_fifo_data), 84 | .m_axis_out_tlast(out_fifo_last), 85 | .m_axis_out_tuser(blk_exp), 86 | .m_axis_out_tvalid(out_fifo_valid) 87 | ); 88 | 89 | always @(posedge clk_i) begin 90 | if (!reset_ni) overflow_o <= '0; 91 | else if (!overflow_o) overflow_o <= !m_axis_fifo_tready && m_axis_fifo_tvalid; 92 | end 93 | 94 | reg [2 : 0] state; 95 | wire [SYMBOL_NUMBER_WIDTH - 1 : 0] symbol_id = s_axis_iq_tuser[SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN + 1 - 1 -: SYMBOL_NUMBER_WIDTH]; 96 | wire [SUBFRAME_NUMBER_WIDTH - 1 : 0] subframe_id = s_axis_iq_tuser[SUBFRAME_NUMBER_WIDTH + SYMBOL_NUMBER_WIDTH + BLK_EXP_LEN + 1 - 1 -: SUBFRAME_NUMBER_WIDTH]; 97 | wire [SFN_WIDTH - 1 : 0] sfn = s_axis_iq_tuser[USER_WIDTH - 1 -: SFN_WIDTH]; 98 | reg [IQ_WIDTH - 1 : 0] sample_buffer; 99 | reg [SAMPLE_ID_WIDTH - 1 : 0] sample_id_buffer; 100 | localparam NUM_TIMESTAMP_SAMPLES = 64 / IQ_WIDTH; 101 | reg [$clog2(NUM_TIMESTAMP_SAMPLES) - 1: 0] timestamp_cnt; 102 | 103 | always @(posedge clk_i) begin 104 | if (!reset_ni) begin 105 | state <= '0; 106 | m_axis_fifo_tdata <= '0; 107 | m_axis_fifo_tvalid <= '0; 108 | m_axis_fifo_tlast <= '0; 109 | sample_buffer <= '0; 110 | sample_id_ready <= '0; 111 | timestamp_cnt <= '0; 112 | out_fifo_ready <= '0; 113 | end else begin 114 | case (state) 115 | 0 : begin 116 | m_axis_fifo_tlast <= '0; 117 | if (out_fifo_valid) begin 118 | // start outputting data when out_fifo has data 119 | // output blk_exp first 120 | m_axis_fifo_tdata <= blk_exp; 121 | m_axis_fifo_tvalid <= 1; 122 | state <= 1; 123 | end else begin 124 | m_axis_fifo_tvalid <= '0; 125 | end 126 | end 127 | 1 : begin // output timestamp 128 | if (sample_id_valid) begin 129 | m_axis_fifo_tdata <= sample_id_data; 130 | sample_id_buffer <= sample_id_data >> IQ_WIDTH; 131 | timestamp_cnt <= 0; 132 | m_axis_fifo_tvalid <= 1; 133 | sample_id_ready <= 1; 134 | state <= 2; 135 | end else begin 136 | m_axis_fifo_tvalid <= '0; 137 | end 138 | end 139 | 2: begin // output remaining samples with timestamp 140 | sample_id_ready <= '0; 141 | timestamp_cnt <= timestamp_cnt + 1; 142 | m_axis_fifo_tdata <= sample_id_buffer; 143 | sample_id_buffer <= sample_id_buffer >> IQ_WIDTH; 144 | if (timestamp_cnt == NUM_TIMESTAMP_SAMPLES - 1) begin 145 | out_fifo_ready <= 1; 146 | state <= 3; 147 | m_axis_fifo_tvalid <= '0; 148 | end else begin 149 | m_axis_fifo_tvalid <= 1; 150 | end 151 | end 152 | 3 : begin // output IQ samples (forward from FIFO) 153 | m_axis_fifo_tdata <= out_fifo_data; 154 | m_axis_fifo_tvalid <= out_fifo_valid; 155 | if (out_fifo_last) begin 156 | state <= 0; 157 | out_fifo_ready <= '0; 158 | m_axis_fifo_tlast <= 1; 159 | end 160 | end 161 | endcase 162 | end 163 | end 164 | 165 | endmodule -------------------------------------------------------------------------------- /hdl/test_CFO_correction.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ns 2 | 3 | module test_CFO_correction 4 | #( 5 | parameter IN_DW = 32, // input data width 6 | parameter OUT_DW = 32, // correlator output data width 7 | parameter TAP_DW = 32, 8 | parameter PSS_LEN = 128, 9 | parameter [TAP_DW * PSS_LEN - 1 : 0] PSS_LOCAL = {(PSS_LEN * TAP_DW){1'b0}}, 10 | parameter ALGO = 0, 11 | parameter WINDOW_LEN = 8, 12 | parameter MULT_REUSE = 16, 13 | parameter DDS_OUT_DW = 32, 14 | parameter DDS_PHASE_DW = 20, 15 | parameter COMPL_MULT_OUT_DW = 32, // has to be multiple of 16 16 | parameter CIC_OUT_DW = 34, 17 | parameter PSS_CORRELATOR_MR = 1 18 | ) 19 | ( 20 | input clk_i, 21 | input reset_ni, 22 | input wire [IN_DW-1:0] s_axis_in_tdata, 23 | input s_axis_in_tvalid, 24 | input CFO_norm_valid_in, 25 | input wire [DDS_PHASE_DW -1 : 0] CFO_norm_in, // CFO_norm = CFO_hz / fs * (2**DDS_PHASE_DW - 1) 26 | 27 | output wire [CIC_OUT_DW + TAP_DW + 2 + 2 * $clog2(PSS_LEN) - 1 : 0] C0, 28 | output wire [CIC_OUT_DW + TAP_DW + 2 + 2 * $clog2(PSS_LEN) - 1: 0] C1, 29 | 30 | output reg peak_detected_o, 31 | 32 | // debug outputs 33 | output wire [CIC_OUT_DW-1:0] m_axis_cic_debug_tdata, 34 | output wire m_axis_cic_debug_tvalid, 35 | output wire [OUT_DW - 1 : 0] m_axis_correlator_debug_tdata, 36 | output wire m_axis_correlator_debug_tvalid 37 | ); 38 | 39 | 40 | 41 | wire [CIC_OUT_DW - 1 : 0] m_axis_cic_tdata; 42 | wire m_axis_cic_tvalid; 43 | assign m_axis_cic_debug_tdata = m_axis_cic_tdata; 44 | assign m_axis_cic_debug_tvalid = m_axis_cic_tvalid; 45 | 46 | wire [DDS_OUT_DW - 1 : 0] DDS_out; 47 | wire DDS_out_valid; 48 | 49 | reg [DDS_PHASE_DW - 1 : 0] DDS_phase; 50 | reg DDS_phase_valid; 51 | reg [COMPL_MULT_OUT_DW - 1 : 0] mult_out_tdata; 52 | reg mult_out_tvalid; 53 | 54 | reg [DDS_PHASE_DW -1 : 0] CFO_norm; 55 | 56 | 57 | always @(posedge clk_i) begin 58 | if (!reset_ni) begin 59 | DDS_phase <= '0; 60 | DDS_phase_valid <= '0; 61 | CFO_norm <= '0; 62 | end 63 | else begin 64 | if (CFO_norm_valid_in) begin 65 | CFO_norm <= CFO_norm_in; 66 | end 67 | if(s_axis_in_tvalid) begin 68 | DDS_phase <= DDS_phase + CFO_norm_in; 69 | DDS_phase_valid <= 1; 70 | end 71 | end 72 | end 73 | 74 | dds #( 75 | .PHASE_DW(DDS_PHASE_DW), 76 | .OUT_DW(DDS_OUT_DW/2), 77 | .USE_TAYLOR(1), 78 | .LUT_DW(16), 79 | .SIN_COS(1), 80 | .NEGATIVE_SINE(0), 81 | .NEGATIVE_COSINE(0), 82 | .USE_LUT_FILE(0) 83 | ) 84 | dds_i( 85 | .clk(clk_i), 86 | .reset_n(reset_ni), 87 | .s_axis_phase_tdata(DDS_phase), 88 | .s_axis_phase_tvalid(DDS_phase_valid), 89 | 90 | .m_axis_out_tdata(DDS_out), 91 | .m_axis_out_tvalid(DDS_out_valid) 92 | ); 93 | 94 | complex_multiplier #( 95 | .OPERAND_WIDTH_A(DDS_OUT_DW/2), 96 | .OPERAND_WIDTH_B(IN_DW/2), 97 | .OPERAND_WIDTH_OUT(COMPL_MULT_OUT_DW/2), 98 | .BLOCKING(0), 99 | .GROWTH_BITS(-2) // input is rotating vector with length 2^(IN_DW/2 - 1), therefore bit growth is 2 bits less than worst case 100 | ) 101 | complex_multiplier_i( 102 | .aclk(clk_i), 103 | .aresetn(reset_ni), 104 | .s_axis_a_tdata(DDS_out), 105 | .s_axis_a_tvalid(DDS_out_valid), 106 | .s_axis_b_tdata(s_axis_in_tdata), 107 | .s_axis_b_tvalid(s_axis_in_tvalid), 108 | 109 | .m_axis_dout_tdata(mult_out_tdata), 110 | .m_axis_dout_tvalid(mult_out_tvalid) 111 | ); 112 | 113 | cic_d #( 114 | .INP_DW(COMPL_MULT_OUT_DW/2), 115 | .OUT_DW(CIC_OUT_DW/2), 116 | .CIC_R(2), 117 | .CIC_N(3), 118 | .VAR_RATE(0) 119 | ) 120 | cic_real( 121 | .clk(clk_i), 122 | .reset_n(reset_ni), 123 | .s_axis_in_tdata(mult_out_tdata[COMPL_MULT_OUT_DW / 2 - 1 -: COMPL_MULT_OUT_DW / 2]), 124 | .s_axis_in_tvalid(mult_out_tvalid), 125 | 126 | .m_axis_out_tdata(m_axis_cic_tdata[CIC_OUT_DW / 2 - 1 -: CIC_OUT_DW / 2]), 127 | .m_axis_out_tvalid(m_axis_cic_tvalid) 128 | ); 129 | 130 | cic_d #( 131 | .INP_DW(COMPL_MULT_OUT_DW / 2), 132 | .OUT_DW(CIC_OUT_DW / 2), 133 | .CIC_R(2), 134 | .CIC_N(3), 135 | .VAR_RATE(0) 136 | ) 137 | cic_imag( 138 | .clk(clk_i), 139 | .reset_n(reset_ni), 140 | .s_axis_in_tdata(mult_out_tdata[COMPL_MULT_OUT_DW - 1 -: COMPL_MULT_OUT_DW / 2]), 141 | .s_axis_in_tvalid(mult_out_tvalid), 142 | 143 | .m_axis_out_tdata(m_axis_cic_tdata[CIC_OUT_DW - 1 -: CIC_OUT_DW / 2]), 144 | .m_axis_out_tvalid() // not used 145 | ); 146 | 147 | 148 | wire [OUT_DW - 1 : 0] correlator_tdata; 149 | wire correlator_tvalid; 150 | assign m_axis_correlator_debug_tdata = correlator_tdata; 151 | assign m_axis_correlator_debug_tvalid = correlator_tvalid; 152 | 153 | if (PSS_CORRELATOR_MR) begin 154 | PSS_correlator_mr #( 155 | .IN_DW(CIC_OUT_DW), 156 | .OUT_DW(OUT_DW), 157 | .TAP_DW(TAP_DW), 158 | .PSS_LEN(PSS_LEN), 159 | .PSS_LOCAL(PSS_LOCAL), 160 | .MULT_REUSE(MULT_REUSE) 161 | ) 162 | correlator( 163 | .clk_i(clk_i), 164 | .reset_ni(reset_ni), 165 | .s_axis_in_tdata(m_axis_cic_tdata), 166 | .s_axis_in_tvalid(m_axis_cic_tvalid), 167 | .enable_i(1'b1), 168 | .m_axis_out_tdata(correlator_tdata), 169 | .m_axis_out_tvalid(correlator_tvalid), 170 | .C0_o(C0), 171 | .C1_o(C1) 172 | ); 173 | end else begin 174 | PSS_correlator #( 175 | .IN_DW(CIC_OUT_DW), 176 | .OUT_DW(OUT_DW), 177 | .TAP_DW(TAP_DW), 178 | .PSS_LEN(PSS_LEN), 179 | .PSS_LOCAL(PSS_LOCAL), 180 | .ALGO(ALGO) 181 | ) 182 | correlator( 183 | .clk_i(clk_i), 184 | .reset_ni(reset_ni), 185 | .s_axis_in_tdata(m_axis_cic_tdata), 186 | .s_axis_in_tvalid(m_axis_cic_tvalid), 187 | .enable_i(1'b1), 188 | .m_axis_out_tdata(correlator_tdata), 189 | .m_axis_out_tvalid(correlator_tvalid), 190 | .C0_o(C0), 191 | .C1_o(C1) 192 | ); 193 | end 194 | 195 | Peak_detector #( 196 | .IN_DW(OUT_DW), 197 | .WINDOW_LEN(WINDOW_LEN) 198 | ) 199 | peak_detector( 200 | .clk_i(clk_i), 201 | .reset_ni(reset_ni), 202 | .s_axis_in_tdata(correlator_tdata), 203 | .s_axis_in_tvalid(correlator_tvalid), 204 | .peak_detected_o(peak_detected_o) 205 | ); 206 | 207 | `ifdef COCOTB_SIM 208 | initial begin 209 | $dumpfile ("debug.vcd"); 210 | $dumpvars (0, test_CFO_correction); 211 | end 212 | `endif 213 | 214 | endmodule -------------------------------------------------------------------------------- /hdl/verilog-axi: -------------------------------------------------------------------------------- 1 | ../submodules/verilog-axi/rtl/ -------------------------------------------------------------------------------- /model/PSS_correlator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def _twos_comp(val, bits): 4 | """compute the 2's complement of int value val""" 5 | if (val & (1 << (bits - 1))) != 0: 6 | val = val - (1 << bits) 7 | return int(val) 8 | 9 | class Model: 10 | def __init__(self, IN_DW, OUT_DW, TAP_DW, PSS_LEN, PSS_LOCAL, ALGO, USE_TAP_FILE = 0, TAP_FILE = ''): 11 | self.PSS_LEN = int(PSS_LEN) 12 | self.OUT_DW = int(OUT_DW) 13 | self.TAP_DW = int(TAP_DW) 14 | self.IN_DW = int(IN_DW) 15 | self.POSSIBLE_IN_DW = int(self.OUT_DW - (np.ceil(np.log2(self.PSS_LEN) + 1)) * 2 - 3) 16 | self.IN_OP_DW = self.POSSIBLE_IN_DW // 2 17 | self.TAP_OP_DW = self.POSSIBLE_IN_DW // 2 + (self.POSSIBLE_IN_DW % 2) 18 | 19 | # print(f'model IN_DW = {IN_DW}') 20 | # print(f'model OUT_DW = {OUT_DW}') 21 | # print(f'model PSS_LEN = {PSS_LEN}') 22 | # print(f'model PSS_LOCAL = {PSS_LOCAL}') 23 | self.taps = np.empty(PSS_LEN, 'complex') 24 | 25 | if False: 26 | # don't do rounding for truncations, because its also not implemented in HDL 27 | if self.trunc_taps > 1: 28 | round_bits = 2 ** (self.trunc_taps - 2) - 1 29 | else: 30 | round_bits = 0 31 | else: 32 | round_bits = 0 33 | 34 | if USE_TAP_FILE: 35 | print(f'using tap file {TAP_FILE}') 36 | temp_taps = np.loadtxt(TAP_FILE, delimiter = ' ', dtype = int, converters={0:lambda s: int(s, 16)}) 37 | 38 | for i in range(PSS_LEN): 39 | if USE_TAP_FILE: 40 | self.taps[i] = _twos_comp(temp_taps[i] & (2 ** (self.TAP_DW // 2) - 1), self.TAP_DW // 2) \ 41 | + 1j * _twos_comp((temp_taps[i] >> (self.TAP_DW // 2)) & (2 ** (self.TAP_DW // 2) - 1), self.TAP_DW // 2) 42 | else: 43 | self.taps[i] = _twos_comp(((PSS_LOCAL >> (self.TAP_DW * i)) & (2 ** (self.TAP_DW // 2) - 1)), 44 | self.TAP_DW // 2) \ 45 | + 1j*_twos_comp(((PSS_LOCAL >> (self.TAP_DW * i + self.TAP_DW // 2)) & (2 ** (self.TAP_DW // 2) - 1)), 46 | self.TAP_DW // 2) 47 | 48 | # for i in range(PSS_LEN): 49 | # print(f'taps[{i}] = {self.taps[i]}') 50 | 51 | self.reset() 52 | 53 | def tick(self): 54 | self.valid[1:] = self.valid[:-1] 55 | self.result[1:] = self.result[:-1] 56 | self.valid[0] = False 57 | 58 | if self.in_buffer is not None: 59 | self.in_pipeline[1:] = self.in_pipeline[:-1] 60 | self.in_pipeline[0] = self.in_buffer 61 | self.valid[0] = True 62 | self.in_buffer = None 63 | 64 | result_re = 0 65 | result_im = 0 66 | for i in range(self.PSS_LEN): 67 | # bit growth inside this loop is ceil(log2(PSS_LEN)) + IN_DW/2 for result_re and result_im 68 | result_re += ( int(self.taps[i].real) * int(self.in_pipeline[i].real) \ 69 | - int(self.taps[i].imag) * int(self.in_pipeline[i].imag)) 70 | result_im += ( int(self.taps[i].real) * int(self.in_pipeline[i].imag) \ 71 | + int(self.taps[i].imag) * int(self.in_pipeline[i].real)) 72 | # result_abs = result_re ** 2 + result_im ** 2 73 | if np.abs(result_re) > np.abs(result_im): 74 | result_abs = np.abs(result_re) + (int(np.abs(result_im))>>2) 75 | else: 76 | result_abs = np.abs(result_im) + (int(np.abs(result_re))>>2) 77 | truncate = int(np.ceil(np.log2(self.PSS_LEN)) + self.IN_DW//2 + self.TAP_DW//2 + 1 - self.OUT_DW) 78 | if truncate < 0: 79 | truncate = 0 80 | self.result[0] = (result_abs >> truncate) & (2 ** self.OUT_DW - 1) 81 | 82 | def set_data(self, data_in): 83 | self.in_buffer = _twos_comp((data_in & (2 ** (self.IN_DW // 2) - 1)), self.IN_DW // 2) \ 84 | + 1j*_twos_comp(((data_in >> (self.IN_DW // 2)) & (2 ** (self.IN_DW // 2) - 1)), self.IN_DW // 2) 85 | 86 | def reset(self): 87 | self.in_pipeline = np.zeros(self.PSS_LEN, 'complex') 88 | self.in_buffer = None 89 | pipeline_stages = 3 90 | self.valid = np.zeros(pipeline_stages, bool) 91 | self.result = np.zeros(pipeline_stages) 92 | 93 | def data_valid(self): 94 | return self.valid[-1] 95 | 96 | def get_data(self): 97 | return self.result[-1] 98 | -------------------------------------------------------------------------------- /model/peak_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Model: 5 | def __init__(self, IN_DW, WINDOW_LEN): 6 | self.IN_DW = int(IN_DW) 7 | self.WINDOW_LEN = int(WINDOW_LEN) 8 | 9 | def tick(self): 10 | pass 11 | 12 | def reset(self): 13 | pass -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | matplotlib 4 | py 5 | cocotb 6 | cocotb-test 7 | cocotb-bus>=0.2.1 8 | cocotbext-axi>=0.1.20 9 | pytest 10 | pytest-parallel 11 | py3gpp==0.1.7 12 | sigmf 13 | -------------------------------------------------------------------------------- /tests/1876954_7680KSPS_srsRAN_Project_gnb_short_2.sigmf-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/tests/1876954_7680KSPS_srsRAN_Project_gnb_short_2.sigmf-data -------------------------------------------------------------------------------- /tests/1876954_7680KSPS_srsRAN_Project_gnb_short_2.sigmf-meta: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [], 3 | "captures": [ 4 | { 5 | "core:datetime": "2023-07-25T10:18:40.803Z", 6 | "core:frequency": 1876954000.0, 7 | "core:length": 81920, 8 | "sdrangel:sample_rate": 7680000, 9 | "sdrangel:tsms": 1690280320803 10 | } 11 | ], 12 | "global": { 13 | "core:author": "SDRangel", 14 | "core:datatype": "ci32_le", 15 | "core:description": "SDRangel SigMF I/Q recording file", 16 | "core:hw": "PlutoSDR", 17 | "core:recorder": "SDRangel", 18 | "core:sample_rate": 7680000.0, 19 | "core:version": "0.0.2", 20 | "sdrangel:arch": "x86_64", 21 | "sdrangel:os": "Ubuntu 22.04.2 LTS", 22 | "sdrangel:qt_version": "5.15.3", 23 | "sdrangel:rx_bits": 24, 24 | "sdrangel:version": "7.13.0-19-g91f484445" 25 | } 26 | } -------------------------------------------------------------------------------- /tests/30720KSPS_dl_signal.sigmf-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/tests/30720KSPS_dl_signal.sigmf-data -------------------------------------------------------------------------------- /tests/30720KSPS_dl_signal.sigmf-meta: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [], 3 | "captures": [ 4 | { 5 | "core:datetime": "2022-10-21T11:40:51.727Z", 6 | "core:frequency": 763000000.0, 7 | "core:length": 43171840, 8 | "sdrangel:sample_rate": 30720000 9 | } 10 | ], 11 | "global": { 12 | "core:author": "SDRangel", 13 | "core:datatype": "ci32_le", 14 | "core:description": "SDRangel SigMF I/Q recording file", 15 | "core:hw": "BladeRF2", 16 | "core:recorder": "SDRangel", 17 | "core:sample_rate": 30720000.0, 18 | "core:version": "0.0.2" 19 | } 20 | } -------------------------------------------------------------------------------- /tests/3627MHz_30720KSPS_short.sigmf-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/tests/3627MHz_30720KSPS_short.sigmf-data -------------------------------------------------------------------------------- /tests/3627MHz_30720KSPS_short.sigmf-meta: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [], 3 | "captures": [ 4 | { 5 | "core:datetime": "2022-10-21T11:53:44.729Z", 6 | "core:frequency": 3627000000.0, 7 | "core:length": 30702000, 8 | "sdrangel:sample_rate": 30720000, 9 | "sdrangel:tsms": 1666353224729 10 | } 11 | ], 12 | "global": { 13 | "core:author": "SDRangel", 14 | "core:datatype": "ci32_le", 15 | "core:description": "SDRangel SigMF I/Q recording file", 16 | "core:hw": "BladeRF2", 17 | "core:recorder": "SDRangel", 18 | "core:sample_rate": 30720000.0, 19 | "core:version": "0.0.2", 20 | "sdrangel:arch": "x86_64", 21 | "sdrangel:os": "Ubuntu 22.04.1 LTS", 22 | "sdrangel:qt_version": "5.15.3", 23 | "sdrangel:rx_bits": 24, 24 | "sdrangel:version": "7.8.0-46-g5b27a50cf" 25 | } 26 | } -------------------------------------------------------------------------------- /tests/763450KHz_7680KSPS_low_gain.sigmf-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/tests/763450KHz_7680KSPS_low_gain.sigmf-data -------------------------------------------------------------------------------- /tests/763450KHz_7680KSPS_low_gain.sigmf-meta: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [], 3 | "captures": [ 4 | { 5 | "core:datetime": "2023-05-26T10:33:14.534Z", 6 | "core:frequency": 763450000.0, 7 | "core:length": 2834432, 8 | "sdrangel:sample_rate": 7680000, 9 | "sdrangel:tsms": 1685097194534 10 | } 11 | ], 12 | "global": { 13 | "core:author": "SDRangel", 14 | "core:datatype": "ci32_le", 15 | "core:description": "SDRangel SigMF I/Q recording file", 16 | "core:hw": "PlutoSDR", 17 | "core:recorder": "SDRangel", 18 | "core:sample_rate": 7680000.0, 19 | "core:version": "0.0.2", 20 | "sdrangel:arch": "x86_64", 21 | "sdrangel:os": "Ubuntu 22.04.2 LTS", 22 | "sdrangel:qt_version": "5.15.3", 23 | "sdrangel:rx_bits": 24, 24 | "sdrangel:version": "7.13.0-19-g91f484445" 25 | } 26 | } -------------------------------------------------------------------------------- /tests/772850KHz_3840KSPS_low_gain.sigmf-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catkira/open5G_phy/18290b41fa0c57ec8da2c8a2c5ca35e890b14a59/tests/772850KHz_3840KSPS_low_gain.sigmf-data -------------------------------------------------------------------------------- /tests/772850KHz_3840KSPS_low_gain.sigmf-meta: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [], 3 | "captures": [ 4 | { 5 | "core:datetime": "2023-04-26T15:04:15.209Z", 6 | "core:frequency": 772855000.0, 7 | "core:length": 717146, 8 | "sdrangel:sample_rate": 3840000, 9 | "sdrangel:tsms": 1682521455209 10 | } 11 | ], 12 | "global": { 13 | "core:author": "SDRangel", 14 | "core:datatype": "ci32_le", 15 | "core:description": "SDRangel SigMF I/Q recording file", 16 | "core:hw": "PlutoSDR", 17 | "core:recorder": "SDRangel", 18 | "core:sample_rate": 3840000.0, 19 | "core:version": "0.0.2", 20 | "sdrangel:arch": "x86_64", 21 | "sdrangel:os": "Ubuntu 22.04.2 LTS", 22 | "sdrangel:qt_version": "5.15.3", 23 | "sdrangel:rx_bits": 24, 24 | "sdrangel:version": "7.13.0-19-g91f484445" 25 | } 26 | } -------------------------------------------------------------------------------- /tests/FFT1_in.txt: -------------------------------------------------------------------------------- 1 | 9812,56183 2 | 4838,64059 3 | 64011,7835 4 | 65128,55885 5 | 60265,3370 6 | 64901,1694 7 | 707,62451 8 | 2964,57868 9 | 7608,22569 10 | 9136,14800 11 | 5908,56357 12 | 62206,7821 13 | 890,6818 14 | 4305,12053 15 | 4379,65053 16 | 7711,57604 17 | 10291,60385 18 | 62974,52034 19 | 8111,65467 20 | 59061,64653 21 | 52635,12468 22 | 1262,128 23 | 643,2127 24 | 5636,5727 25 | 52434,52633 26 | 49642,1456 27 | 3170,56823 28 | 2202,8190 29 | 8632,13952 30 | 5295,1734 31 | 54668,7117 32 | 4680,3338 33 | 52025,53604 34 | 58225,57910 35 | 3108,9621 36 | 62544,7568 37 | 61521,10959 38 | 64812,2098 39 | 63236,65120 40 | 65145,64438 41 | 14024,5517 42 | 577,6580 43 | 62292,3559 44 | 64257,64696 45 | 10179,61330 46 | 4511,1139 47 | 7927,1443 48 | 59450,2581 49 | 59925,56053 50 | 99,4720 51 | 61127,8630 52 | 63595,7349 53 | 6368,62342 54 | 3528,1469 55 | 57698,2032 56 | 12020,51726 57 | 63757,62400 58 | 3367,1151 59 | 2440,4895 60 | 62923,64541 61 | 56804,10022 62 | 1931,55441 63 | 64115,153 64 | 62534,3729 65 | 1829,59166 66 | 61240,4469 67 | 6042,52266 68 | 51025,56975 69 | 13984,2597 70 | 14552,3757 71 | 50744,56372 72 | 49959,12952 73 | 63471,9466 74 | 865,62587 75 | 8432,2384 76 | 6027,63738 77 | 44196,63351 78 | 63722,61559 79 | 61230,1884 80 | 43362,55348 81 | 57944,60976 82 | 65001,11162 83 | 63403,3814 84 | 53321,64589 85 | 49880,3151 86 | 10077,9502 87 | 5196,57021 88 | 82,55009 89 | 21354,5287 90 | 7402,53934 91 | 61864,5031 92 | 2482,58989 93 | 3014,59022 94 | 12973,8707 95 | 7954,48747 96 | 6343,1481 97 | 443,8071 98 | 63696,64681 99 | 9072,1323 100 | 2231,53016 101 | 6649,57246 102 | 63341,5983 103 | 65045,7452 104 | 918,13580 105 | 63829,777 106 | 6568,128 107 | 2931,4103 108 | 469,65141 109 | 12900,37 110 | 23238,11244 111 | 1713,10469 112 | 9673,56230 113 | 10329,61560 114 | 58577,7068 115 | 1394,5047 116 | 7026,493 117 | 13057,45156 118 | 58635,53645 119 | 50072,11502 120 | 10536,61593 121 | 54832,14847 122 | 43099,64295 123 | 11158,56372 124 | 9565,80 125 | 3564,103 126 | 4644,6406 127 | 8039,57840 128 | 2723,59042 129 | 58098,58371 130 | 60574,65299 131 | 7419,48189 132 | 14025,59792 133 | 62923,1379 134 | 6475,65282 135 | 2859,1816 136 | 7483,61548 137 | 2289,16313 138 | 55817,8499 139 | 8111,59577 140 | 63215,781 141 | 62927,2282 142 | 53456,12408 143 | 55636,9001 144 | 63919,38473 145 | 65429,59598 146 | 65528,620 147 | 54921,58136 148 | 1921,3517 149 | 3707,54795 150 | 5701,49231 151 | 5229,50985 152 | 2908,30 153 | 36,63759 154 | 55046,65150 155 | 62112,65270 156 | 2931,45749 157 | 58000,55991 158 | 912,61624 159 | 2160,51234 160 | 55504,56491 161 | 2804,64353 162 | 61854,13596 163 | 53041,7671 164 | 321,61838 165 | 13282,48591 166 | 65153,53335 167 | 5482,6034 168 | 58628,56411 169 | 48317,505 170 | 4044,62467 171 | 62361,14707 172 | 4437,10470 173 | 60945,59431 174 | 17193,5965 175 | 11981,50551 176 | 1477,3961 177 | 4803,500 178 | 60396,2893 179 | 62464,2893 180 | 54681,64364 181 | 3786,7844 182 | 63806,57570 183 | 571,55866 184 | 63014,61154 185 | 5119,3378 186 | 62265,3572 187 | 60070,62324 188 | 65275,50996 189 | 63963,7859 190 | 57850,18984 191 | 65125,13900 192 | 16534,6476 193 | 61751,10889 194 | 5165,2945 195 | 58743,60527 196 | 60299,5343 197 | 58748,50181 198 | 9293,57543 199 | 9421,2409 200 | 58848,59211 201 | 9558,56209 202 | 60377,54725 203 | 65128,53793 204 | 2139,73 205 | 63055,62698 206 | 11237,1033 207 | 9402,55178 208 | 63899,53821 209 | 5687,58878 210 | 8525,63496 211 | 1308,12111 212 | 62310,50188 213 | 63820,8239 214 | 51191,571 215 | 59850,55382 216 | 61582,19504 217 | 62507,55323 218 | 5318,47613 219 | 63695,3534 220 | 63338,60935 221 | 10160,2658 222 | 12400,5530 223 | 56436,536 224 | 57310,5833 225 | 58893,9516 226 | 65303,22081 227 | 1,57843 228 | 64922,51446 229 | 60743,6635 230 | 13910,54012 231 | 5747,63926 232 | 57699,63317 233 | 4000,54891 234 | 7771,1787 235 | 15237,64741 236 | 2685,5126 237 | 63473,61589 238 | 10468,8386 239 | 14531,15642 240 | 45315,7705 241 | 7344,130 242 | 5248,58169 243 | 54298,48706 244 | 1633,41 245 | 1513,9040 246 | 14444,50080 247 | 60405,14354 248 | 1981,60865 249 | 59779,60341 250 | 55182,56327 251 | 62386,2496 252 | 58701,12836 253 | 9959,49866 254 | 10497,64451 255 | 275,64188 256 | 7179,7011 -------------------------------------------------------------------------------- /tests/FFT2_in.txt: -------------------------------------------------------------------------------- 1 | -10736,1125 2 | -11134,7896 3 | -2845,4610 4 | -1361,-5827 5 | -8695,-1155 6 | -11014,-14284 7 | 16222,-5565 8 | 6398,-4819 9 | 8112,-17177 10 | -982,2591 11 | 2248,-6811 12 | 7621,-9821 13 | -3769,-4793 14 | 13697,4335 15 | -540,6506 16 | -1509,-8435 17 | 9399,-2076 18 | -2436,-10787 19 | -622,-17314 20 | 183,9650 21 | 8720,9309 22 | -3163,-1887 23 | -894,8848 24 | 6145,-1734 25 | 1271,-6349 26 | 9270,520 27 | -6317,-1563 28 | 3102,-5114 29 | -8771,-2517 30 | 1278,1457 31 | 12019,7732 32 | -9694,9446 33 | -4486,9412 34 | -4149,8886 35 | 9186,-1023 36 | -6666,-10923 37 | 70,6279 38 | 4238,-2173 39 | -7574,-11754 40 | 12700,4081 41 | -8612,-384 42 | -9227,9587 43 | -7453,1085 44 | -2159,-3925 45 | 7981,-472 46 | 3544,13730 47 | 10505,17235 48 | 2208,5884 49 | 345,-6605 50 | -5676,6356 51 | -5178,10832 52 | -11727,6059 53 | 7815,10060 54 | 2426,-3508 55 | 1048,-2108 56 | -365,-18439 57 | -10254,5816 58 | 7709,5594 59 | -7593,6769 60 | 7492,4662 61 | -4698,-6176 62 | 874,-6610 63 | 3642,4699 64 | -3781,13162 65 | -6841,-14304 66 | 1053,11521 67 | 18697,510 68 | -7858,6354 69 | 6607,6754 70 | 4278,-15525 71 | -10952,3716 72 | 1130,-1258 73 | -1754,-776 74 | -12557,10304 75 | 14529,-10675 76 | 827,1716 77 | -20633,3553 78 | -69,-11403 79 | -506,5672 80 | 8447,-10573 81 | 5740,-5109 82 | -1485,-2311 83 | -5716,-13404 84 | -10996,-2712 85 | 4308,2740 86 | -6213,282 87 | -14620,1743 88 | 1187,-2411 89 | -14664,2125 90 | 10015,3397 91 | 15609,-756 92 | 3384,842 93 | -3770,972 94 | -19836,-5013 95 | 8217,-4973 96 | 3573,-12722 97 | -151,-5454 98 | -4453,10940 99 | -9444,-1916 100 | -4050,9455 101 | -8842,-9547 102 | -16370,-1988 103 | -9877,-9306 104 | 3174,-15522 105 | -3137,-2239 106 | 3278,-10179 107 | 8615,21948 108 | 1097,3786 109 | 4869,-2998 110 | 12307,9154 111 | -13853,70 112 | -1155,4970 113 | 3712,9162 114 | -4747,2247 115 | 12691,1519 116 | -6797,-14456 117 | 7173,-16685 118 | -3078,-7850 119 | -8157,-22665 120 | 19718,-2692 121 | -7641,1246 122 | -27887,-11645 123 | -10847,6215 124 | 4717,9762 125 | -6680,-10211 126 | 2945,-5357 127 | -1116,1177 128 | -14161,-7406 129 | -261,1121 130 | -5974,3444 131 | -2898,3924 132 | -9435,4770 133 | 8644,4083 134 | 2334,-2944 135 | -21516,-7016 136 | -18415,8494 137 | -6826,-2115 138 | 14540,9962 139 | -8381,15680 140 | 2856,8188 141 | 1488,20890 142 | -2065,11453 143 | 4075,569 144 | 7084,-8813 145 | -1403,1890 146 | -10583,-11365 147 | 4641,-6449 148 | -1364,6042 149 | 6313,-8098 150 | 6506,-5277 151 | 6502,-10759 152 | -2489,-2460 153 | 7778,11306 154 | -7263,11118 155 | -4887,7845 156 | -13555,10601 157 | -16987,-3649 158 | 2699,1804 159 | -10178,-2666 160 | -5968,-11881 161 | -5345,7838 162 | 18743,10525 163 | -9415,8199 164 | -10033,581 165 | -3783,2503 166 | 49,-1981 167 | 17482,1029 168 | 6932,-4432 169 | -5896,-1787 170 | -11285,5447 171 | -6009,-7209 172 | -6325,2623 173 | -862,86 174 | -7590,-2372 175 | -3593,13782 176 | -7383,8789 177 | 11515,-2037 178 | 4532,9895 179 | 3250,4383 180 | -6122,-473 181 | -13651,815 182 | -334,3663 183 | 3288,1508 184 | 8323,-4628 185 | -18453,725 186 | 427,-2063 187 | 4990,1478 188 | -9289,7709 189 | 2140,2351 190 | 11968,-10400 191 | 958,-7921 192 | 3680,5270 193 | 1882,-501 194 | -10650,-10785 195 | 2005,4186 196 | 5467,-2190 197 | -3035,5558 198 | -3178,3629 199 | 7313,-13641 200 | -4804,-5951 201 | -4110,-364 202 | 446,12809 203 | -4612,6697 204 | 9263,-1927 205 | 2727,-4188 206 | -8650,-8045 207 | -7612,-16041 208 | -3862,3320 209 | 851,-526 210 | -851,-5706 211 | 12831,-12597 212 | 6008,-17922 213 | 444,2118 214 | 6260,2408 215 | -11568,2329 216 | -12291,-12251 217 | 1300,-2395 218 | -521,4175 219 | 2810,4428 220 | 2012,2165 221 | -5169,412 222 | -1026,3817 223 | 8891,4549 224 | -5845,-10688 225 | -10211,-7047 226 | 6827,-9450 227 | -6445,-14620 228 | 9223,10077 229 | -3496,-4674 230 | -6905,2871 231 | 323,7267 232 | 4807,-130 233 | 9425,-257 234 | -6191,4868 235 | 9554,-2635 236 | -7033,-6904 237 | 7292,-8463 238 | 4226,-2944 239 | -8094,14205 240 | 9384,3496 241 | -1743,13112 242 | 618,4508 243 | 8080,-10520 244 | 2574,-1084 245 | 960,7169 246 | 8517,7370 247 | 3477,2373 248 | -7134,6359 249 | 12297,12632 250 | 11566,1302 251 | 3856,11501 252 | -342,12768 253 | -13092,-1946 254 | 494,8854 255 | -6340,-4715 256 | -8872,-9903 -------------------------------------------------------------------------------- /tests/test_CFO_calc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import pytest 4 | import logging 5 | import matplotlib.pyplot as plt 6 | import os 7 | 8 | import cocotb 9 | import cocotb_test.simulator 10 | from cocotb.clock import Clock 11 | from cocotb.triggers import RisingEdge 12 | 13 | 14 | CLK_PERIOD_NS = 8 15 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 16 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 17 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 18 | 19 | def _twos_comp(val, bits): 20 | """compute the 2's complement of int value val""" 21 | if (val & (1 << (bits - 1))) != 0: 22 | val = val - (1 << bits) 23 | return int(val) 24 | 25 | class TB(object): 26 | def __init__(self, dut): 27 | self.dut = dut 28 | self.C_DW = int(dut.C_DW.value) 29 | self.CFO_DW = int(dut.CFO_DW.value) 30 | self.DDS_DW = int(dut.DDS_DW.value) 31 | 32 | self.log = logging.getLogger('cocotb.tb') 33 | self.log.setLevel(logging.DEBUG) 34 | 35 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 36 | 37 | async def cycle_reset(self): 38 | self.dut.reset_ni.setimmediatevalue(1) 39 | await RisingEdge(self.dut.clk_i) 40 | self.dut.reset_ni.value = 0 41 | await RisingEdge(self.dut.clk_i) 42 | self.dut.reset_ni.value = 1 43 | await RisingEdge(self.dut.clk_i) 44 | 45 | @cocotb.test() 46 | async def simple_test(dut): 47 | tb = TB(dut) 48 | 49 | dut.valid_i.value = 0 50 | await tb.cycle_reset() 51 | angle = int(os.environ['ANGLE']) 52 | C0 = 1 53 | C1 = C0 * np.exp(1j * angle / 180 * np.pi) 54 | MAX_VAL = int(2 ** (tb.C_DW // 2 - 1) - 1) 55 | dut.C0_i.value = ((int(C0.imag*MAX_VAL) & int(2**(tb.C_DW//2)-1)) << (tb.C_DW//2)) + (int(C0.real*MAX_VAL) & int(2**(tb.C_DW//2)-1)) 56 | dut.C1_i.value = ((int(C1.imag*MAX_VAL) & int(2**(tb.C_DW//2)-1)) << (tb.C_DW//2)) + (int(C1.real*MAX_VAL) & int(2**(tb.C_DW//2)-1)) 57 | dut.valid_i.value = 1 58 | 59 | await RisingEdge(dut.clk_i) 60 | dut.valid_i.value = 0 61 | received_angle = 0 62 | 63 | clk_cnt = 0 64 | max_clk_cnt = 1000 65 | while clk_cnt < max_clk_cnt: 66 | await RisingEdge(dut.clk_i) 67 | clk_cnt += 1 68 | if (dut.valid_o.value == 1): 69 | received_angle = _twos_comp(dut.CFO_angle_o.value.integer, tb.CFO_DW) / (2**(tb.CFO_DW-1) - 1) * 180 70 | print(f'received CFO {received_angle} deg') 71 | print(f'expected CFO {angle} deg') 72 | DDS_inc = _twos_comp(dut.CFO_DDS_inc_o.value.integer, tb.DDS_DW) 73 | print(f'received DDS inc {DDS_inc}') 74 | break 75 | 76 | assert np.abs(received_angle + angle) < 1 77 | 78 | @pytest.mark.parametrize("C_DW", [30, 32]) 79 | @pytest.mark.parametrize("CFO_DW", [20, 32]) 80 | @pytest.mark.parametrize("DDS_DW", [20]) 81 | @pytest.mark.parametrize("ANGLE", [20, 60, 100, 150, 170, -20, -60, -100, -150, -170]) 82 | def test(C_DW, CFO_DW, DDS_DW, ANGLE): 83 | dut = 'CFO_calc' 84 | module = os.path.splitext(os.path.basename(__file__))[0] 85 | toplevel = dut 86 | 87 | verilog_sources = [ 88 | os.path.join(rtl_dir, f'{dut}.sv'), 89 | os.path.join(rtl_dir, 'atan.sv'), 90 | os.path.join(rtl_dir, 'atan2.sv'), 91 | os.path.join(rtl_dir, 'div.sv'), 92 | os.path.join(rtl_dir, 'complex_multiplier', 'complex_multiplier.sv') 93 | ] 94 | includes = [] 95 | 96 | parameters = {} 97 | parameters['C_DW'] = C_DW 98 | parameters['CFO_DW'] = CFO_DW 99 | parameters['DDS_DW'] = DDS_DW 100 | parameters['ATAN_IN_DW'] = 16 101 | os.environ['ANGLE'] = str(ANGLE) 102 | 103 | parameters_dir = parameters.copy() 104 | parameters_dir['ANGLE'] = ANGLE 105 | 106 | sim_build='sim_build/_CFO_calc_' + '_'.join(('{}={}'.format(*i) for i in parameters_dir.items())) 107 | cocotb_test.simulator.run( 108 | python_search=[tests_dir], 109 | verilog_sources=verilog_sources, 110 | includes=includes, 111 | toplevel=toplevel, 112 | module=module, 113 | parameters=parameters, 114 | sim_build=sim_build, 115 | testcase='simple_test', 116 | force_compile=True, 117 | waves=True 118 | ) 119 | 120 | if __name__ == '__main__': 121 | test(C_DW = 30, CFO_DW = 32, DDS_DW = 20, ANGLE=80) 122 | -------------------------------------------------------------------------------- /tests/test_Decimator_Correlator_PeakDetector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import os 4 | import pytest 5 | import logging 6 | import importlib 7 | import matplotlib.pyplot as plt 8 | import os 9 | 10 | import cocotb 11 | import cocotb_test.simulator 12 | from cocotb.clock import Clock 13 | from cocotb.triggers import RisingEdge 14 | 15 | import py3gpp 16 | import sigmf 17 | 18 | CLK_PERIOD_NS = 8 19 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 20 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 21 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 22 | 23 | def _twos_comp(val, bits): 24 | """compute the 2's complement of int value val""" 25 | if (val & (1 << (bits - 1))) != 0: 26 | val = val - (1 << bits) 27 | return int(val) 28 | 29 | class TB(object): 30 | def __init__(self, dut): 31 | self.dut = dut 32 | self.IN_DW = int(dut.IN_DW.value) 33 | self.OUT_DW = int(dut.OUT_DW.value) 34 | self.TAP_DW = int(dut.TAP_DW.value) 35 | self.PSS_LEN = int(dut.PSS_LEN.value) 36 | self.PSS_LOCAL = int(dut.PSS_LOCAL.value) 37 | self.ALGO = int(dut.ALGO.value) 38 | self.WINDOW_LEN = int(dut.WINDOW_LEN.value) 39 | 40 | self.log = logging.getLogger('cocotb.tb') 41 | self.log.setLevel(logging.DEBUG) 42 | 43 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 44 | model_file = os.path.abspath(os.path.join(tests_dir, '../model/PSS_correlator.py')) 45 | 46 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 47 | 48 | async def generate_input(self): 49 | pass 50 | 51 | async def cycle_reset(self): 52 | self.dut.s_axis_in_tvalid.value = 0 53 | self.dut.reset_ni.setimmediatevalue(1) 54 | await RisingEdge(self.dut.clk_i) 55 | self.dut.reset_ni.value = 0 56 | await RisingEdge(self.dut.clk_i) 57 | self.dut.reset_ni.value = 1 58 | await RisingEdge(self.dut.clk_i) 59 | 60 | @cocotb.test() 61 | async def simple_test(dut): 62 | tb = TB(dut) 63 | handle = sigmf.sigmffile.fromfile('../../tests/30720KSPS_dl_signal.sigmf-data') 64 | waveform = handle.read_samples() 65 | waveform /= max(waveform.real.max(), waveform.imag.max()) 66 | waveform = scipy.signal.decimate(waveform, 16//2, ftype='fir') 67 | waveform /= max(waveform.real.max(), waveform.imag.max()) 68 | waveform *= 2 ** (tb.IN_DW // 2 - 1) 69 | waveform = waveform.real.astype(int) + 1j*waveform.imag.astype(int) 70 | 71 | await tb.cycle_reset() 72 | 73 | num_items = 2000 74 | rx_counter = 0 75 | in_counter = 0 76 | received = np.empty(num_items, int) 77 | received_correlator = [] 78 | received_data = [] 79 | while rx_counter < num_items: 80 | await RisingEdge(dut.clk_i) 81 | data = (((int(waveform[in_counter].imag) & ((2 ** (tb.IN_DW // 2)) - 1)) << (tb.IN_DW // 2)) \ 82 | + ((int(waveform[in_counter].real)) & ((2 ** (tb.IN_DW // 2)) - 1))) & ((2 ** tb.IN_DW) - 1) 83 | dut.s_axis_in_tdata.value = data 84 | dut.s_axis_in_tvalid.value = 1 85 | in_counter += 1 86 | 87 | # print(f'{dut.m_axis_cic_tvalid.value.integer} + {dut.m_axis_cic_tdata.value.integer}') 88 | 89 | if dut.m_axis_correlator_debug_tvalid == 1: 90 | received_correlator.append(dut.m_axis_correlator_debug_tdata.value.integer) 91 | 92 | if dut.m_axis_cic_debug_tvalid.value.binstr == '1': 93 | received_data.append(1j*_twos_comp(dut.m_axis_cic_debug_tdata.value.integer & (2**(tb.OUT_DW//2) - 1), tb.OUT_DW//2) 94 | + _twos_comp((dut.m_axis_cic_debug_tdata.value.integer>>(tb.OUT_DW//2)) & (2**(tb.OUT_DW//2) - 1), tb.OUT_DW//2)) 95 | 96 | received[rx_counter] = dut.peak_detected_o.value.integer 97 | rx_counter += 1 98 | 99 | peak_pos = np.argmax(received) 100 | if 'PLOTS' in os.environ and os.environ['PLOTS'] == '1': 101 | _, (ax1, ax2) = plt.subplots(2,1) 102 | ax1.plot(received_correlator) 103 | ax1.set_title('correlation') 104 | ax2.plot(received) 105 | ax2.set_title('peak detected') 106 | plt.show() 107 | print(f'highest peak at {peak_pos}') 108 | assert peak_pos == 840 109 | 110 | @pytest.mark.parametrize("ALGO", [0, 1]) 111 | @pytest.mark.parametrize("IN_DW", [32]) 112 | @pytest.mark.parametrize("OUT_DW", [32]) 113 | @pytest.mark.parametrize("TAP_DW", [32]) 114 | @pytest.mark.parametrize("WINDOW_LEN", [8]) 115 | def test(IN_DW, OUT_DW, TAP_DW, ALGO, WINDOW_LEN): 116 | dut = 'Decimator_Correlator_PeakDetector' 117 | module = os.path.splitext(os.path.basename(__file__))[0] 118 | toplevel = dut 119 | 120 | verilog_sources = [ 121 | os.path.join(rtl_dir, f'{dut}.sv'), 122 | os.path.join(rtl_dir, 'div.sv'), 123 | os.path.join(rtl_dir, 'atan.sv'), 124 | os.path.join(rtl_dir, 'atan2.sv'), 125 | os.path.join(rtl_dir, 'Peak_detector.sv'), 126 | os.path.join(rtl_dir, 'PSS_correlator.sv'), 127 | os.path.join(rtl_dir, 'CIC/cic_d.sv'), 128 | os.path.join(rtl_dir, 'CIC/comb.sv'), 129 | os.path.join(rtl_dir, 'CIC/downsampler.sv'), 130 | os.path.join(rtl_dir, 'CIC/integrator.sv') 131 | ] 132 | includes = [ 133 | os.path.join(rtl_dir, 'CIC') 134 | ] 135 | 136 | PSS_LEN = 128 137 | parameters = {} 138 | parameters['IN_DW'] = IN_DW 139 | parameters['OUT_DW'] = OUT_DW 140 | parameters['TAP_DW'] = TAP_DW 141 | parameters['PSS_LEN'] = PSS_LEN 142 | parameters['ALGO'] = ALGO 143 | parameters['WINDOW_LEN'] = WINDOW_LEN 144 | 145 | extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} 146 | parameters_no_taps = parameters.copy() 147 | sim_build='sim_build/Decimator_to_PeakDetector_' + '_'.join(('{}={}'.format(*i) for i in parameters_no_taps.items())) 148 | 149 | N_id_2 = 2 150 | # parameters['TAP_FILE'] = f'\"../../{sim_build}/PSS_taps_{N_id_2}.hex\"' 151 | os.environ['TAP_FILE'] = f'{rtl_dir}/../{sim_build}/PSS_taps_{N_id_2}.hex' 152 | 153 | os.makedirs(sim_build, exist_ok=True) 154 | file_path = os.path.abspath(os.path.join(tests_dir, '../tools/generate_PSS_tap_file.py')) 155 | spec = importlib.util.spec_from_file_location("generate_PSS_tap_file", file_path) 156 | generate_PSS_tap_file = importlib.util.module_from_spec(spec) 157 | spec.loader.exec_module(generate_PSS_tap_file) 158 | generate_PSS_tap_file.main(['--PSS_LEN', str(PSS_LEN),'--TAP_DW', str(TAP_DW), '--N_id_2', str(N_id_2), '--path', sim_build]) 159 | 160 | cocotb_test.simulator.run( 161 | python_search=[tests_dir], 162 | verilog_sources=verilog_sources, 163 | includes=includes, 164 | toplevel=toplevel, 165 | module=module, 166 | parameters=parameters, 167 | sim_build=sim_build, 168 | extra_env=extra_env, 169 | testcase='simple_test', 170 | force_compile=True 171 | ) 172 | 173 | if __name__ == '__main__': 174 | os.environ['PLOTS'] = "1" 175 | test(IN_DW=32, OUT_DW=32, TAP_DW=32, ALGO=0, WINDOW_LEN=8) 176 | -------------------------------------------------------------------------------- /tests/test_PSS_correlator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import os 4 | import pytest 5 | import logging 6 | import importlib 7 | import matplotlib.pyplot as plt 8 | import os 9 | import importlib.util 10 | 11 | import cocotb 12 | import cocotb_test.simulator 13 | from cocotb.clock import Clock 14 | from cocotb.triggers import Timer 15 | from cocotb.triggers import RisingEdge 16 | 17 | import py3gpp 18 | import sigmf 19 | 20 | CLK_PERIOD_NS = 8 21 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 22 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 23 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 24 | 25 | class TB(object): 26 | def __init__(self, dut): 27 | self.dut = dut 28 | self.IN_DW = int(dut.IN_DW.value) 29 | self.OUT_DW = int(dut.OUT_DW.value) 30 | self.TAP_DW = int(dut.TAP_DW.value) 31 | self.PSS_LEN = int(dut.PSS_LEN.value) 32 | self.ALGO = int(dut.ALGO.value) 33 | self.USE_TAP_FILE = int(dut.USE_TAP_FILE.value) 34 | self.TAP_FILE = dut.TAP_FILE.value 35 | 36 | self.log = logging.getLogger('cocotb.tb') 37 | self.log.setLevel(logging.DEBUG) 38 | 39 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 40 | model_dir = os.path.abspath(os.path.join(tests_dir, '../model/PSS_correlator.py')) 41 | spec = importlib.util.spec_from_file_location('PSS_correlator', model_dir) 42 | foo = importlib.util.module_from_spec(spec) 43 | spec.loader.exec_module(foo) 44 | 45 | if self.USE_TAP_FILE: 46 | self.TAP_FILE = os.environ['TAP_FILE'] 47 | self.PSS_LOCAL = 0 48 | else: 49 | self.TAP_FILE = "" 50 | self.PSS_LOCAL = int(dut.PSS_LOCAL.value) 51 | self.model = foo.Model(self.IN_DW, self.OUT_DW, self.TAP_DW, self.PSS_LEN, self.PSS_LOCAL, self.ALGO, self.USE_TAP_FILE, self.TAP_FILE) 52 | 53 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 54 | cocotb.start_soon(self.model_clk(CLK_PERIOD_NS, 'ns')) 55 | 56 | async def model_clk(self, period, period_units): 57 | timer = Timer(period, period_units) 58 | while True: 59 | self.model.tick() 60 | await timer 61 | 62 | async def cycle_reset(self): 63 | self.dut.s_axis_in_tvalid.value = 0 64 | self.dut.reset_ni.setimmediatevalue(1) 65 | await RisingEdge(self.dut.clk_i) 66 | self.dut.reset_ni.value = 0 67 | await RisingEdge(self.dut.clk_i) 68 | self.dut.reset_ni.value = 1 69 | await RisingEdge(self.dut.clk_i) 70 | self.model.reset() 71 | 72 | @cocotb.test() 73 | async def simple_test(dut): 74 | tb = TB(dut) 75 | handle = sigmf.sigmffile.fromfile('../../tests/30720KSPS_dl_signal.sigmf-data') 76 | waveform = handle.read_samples() 77 | fs = 30720000 78 | CFO = int(os.getenv('CFO')) 79 | print(f'CFO = {CFO} Hz') 80 | waveform *= np.exp(np.arange(len(waveform))*1j*2*np.pi*CFO/fs) 81 | waveform /= max(waveform.real.max(), waveform.imag.max()) 82 | waveform = scipy.signal.decimate(waveform, 16, ftype='fir') 83 | waveform /= max(waveform.real.max(), waveform.imag.max()) 84 | waveform *= 2 ** (tb.IN_DW // 2 - 1) - 1 85 | waveform = waveform.real.astype(int) + 1j*waveform.imag.astype(int) 86 | await tb.cycle_reset() 87 | 88 | num_items = 500 89 | rx_counter = 0 90 | rx_counter_model = 0 91 | in_counter = 0 92 | received = np.empty(num_items, int) 93 | received_model = np.empty(num_items, int) 94 | dut.enable_i.value = 1 95 | while rx_counter < num_items: 96 | await RisingEdge(dut.clk_i) 97 | data = (((int(waveform[in_counter].imag) & (2 ** (tb.IN_DW // 2) - 1)) << (tb.IN_DW // 2)) \ 98 | + ((int(waveform[in_counter].real)) & (2 ** (tb.IN_DW // 2) - 1))) & (2 ** tb.IN_DW - 1) 99 | dut.s_axis_in_tdata.value = data 100 | dut.s_axis_in_tvalid.value = 1 101 | tb.model.set_data(data) 102 | in_counter += 1 103 | 104 | if dut.m_axis_out_tvalid == 1: 105 | received[rx_counter] = dut.m_axis_out_tdata.value.integer 106 | # print(f'{rx_counter}: rx hdl {received[rx_counter]}') 107 | rx_counter += 1 108 | 109 | if tb.model.data_valid() and rx_counter_model < num_items: 110 | received_model[rx_counter_model] = tb.model.get_data() 111 | # print(f'{rx_counter_model}: rx mod {received_model[rx_counter_model]}') 112 | rx_counter_model += 1 113 | 114 | ssb_start = np.argmax(received) 115 | print(f'max model {max(received_model)} max hdl {max(received)}') 116 | if 'PLOTS' in os.environ and os.environ['PLOTS'] == '1': 117 | _, (ax, ax2) = plt.subplots(2, 1) 118 | print(f'{type(received.dtype)} {type(received_model.dtype)}') 119 | ax.plot(np.sqrt(received)) 120 | ax.set_title('hdl') 121 | ax2.plot(np.sqrt(received_model), 'r-') 122 | ax.set_title('model') 123 | ax.axvline(x = ssb_start, color = 'y', linestyle = '--', label = 'axvline - full height') 124 | plt.show() 125 | print(f'max correlation is {received[ssb_start]} at {ssb_start}') 126 | 127 | print(f'max model-hdl difference is {max(np.abs(received - received_model))}') 128 | if tb.ALGO == 0: 129 | #ok_limit = 0.0001 130 | #for i in range(len(received)): 131 | # assert np.abs((received[i] - received_model[i]) / received[i]) < ok_limit 132 | for i in range(len(received)): 133 | assert received[i] == received_model[i] 134 | else: 135 | # there is not yet a model for ALGO=1 136 | pass 137 | 138 | assert ssb_start == 412 139 | assert len(received) == num_items 140 | 141 | # bit growth inside PSS_correlator is a lot, be careful to not make OUT_DW too small ! 142 | @pytest.mark.parametrize("ALGO", [0, 1]) 143 | @pytest.mark.parametrize("IN_DW", [14, 32]) 144 | @pytest.mark.parametrize("OUT_DW", [24, 48]) 145 | @pytest.mark.parametrize("TAP_DW", [18, 32]) 146 | @pytest.mark.parametrize("CFO", [0, 7500]) 147 | @pytest.mark.parametrize("USE_TAP_FILE", [0, 1]) 148 | def test(IN_DW, OUT_DW, TAP_DW, ALGO, CFO, USE_TAP_FILE): 149 | dut = 'PSS_correlator' 150 | module = os.path.splitext(os.path.basename(__file__))[0] 151 | toplevel = dut 152 | 153 | verilog_sources = [ 154 | os.path.join(rtl_dir, f'{dut}.sv') 155 | ] 156 | includes = [] 157 | 158 | PSS_LEN = 128 159 | parameters = {} 160 | parameters['IN_DW'] = IN_DW 161 | parameters['OUT_DW'] = OUT_DW 162 | parameters['TAP_DW'] = TAP_DW 163 | parameters['PSS_LEN'] = PSS_LEN 164 | parameters['ALGO'] = ALGO 165 | parameters['USE_TAP_FILE'] = USE_TAP_FILE 166 | 167 | extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} 168 | os.environ['CFO'] = str(CFO) 169 | parameters_no_taps = parameters.copy() 170 | folder = '_'.join(('{}={}'.format(*i) for i in parameters_no_taps.items())) 171 | sim_build='sim_build/' + folder 172 | N_id_2 = 2 173 | 174 | if not USE_TAP_FILE: 175 | PSS = np.zeros(PSS_LEN, 'complex') 176 | PSS[0:-1] = py3gpp.nrPSS(N_id_2) 177 | taps = np.fft.ifft(np.fft.fftshift(PSS)) 178 | taps /= max(taps.real.max(), taps.imag.max()) 179 | taps *= 2 ** (TAP_DW // 2 - 1) - 1 180 | parameters['PSS_LOCAL'] = 0 181 | for i in range(len(taps)): 182 | parameters['PSS_LOCAL'] += ((int(np.imag(taps[i])) & (2 ** (TAP_DW // 2) - 1)) << (TAP_DW * i + TAP_DW // 2)) \ 183 | + ((int(np.real(taps[i])) & (2 ** (TAP_DW // 2) - 1)) << (TAP_DW * i)) 184 | else: 185 | # every parameter combination needs to have its own TAP_FILE to allow parallel tests! 186 | parameters['TAP_FILE'] = f'\"../{folder}/PSS_taps_{N_id_2}.hex\"' 187 | os.environ['TAP_FILE'] = f'{rtl_dir}/../{sim_build}/PSS_taps_{N_id_2}.hex' 188 | 189 | os.makedirs(sim_build, exist_ok=True) 190 | file_path = os.path.abspath(os.path.join(tests_dir, '../tools/generate_PSS_tap_file.py')) 191 | spec = importlib.util.spec_from_file_location("generate_PSS_tap_file", file_path) 192 | generate_PSS_tap_file = importlib.util.module_from_spec(spec) 193 | spec.loader.exec_module(generate_PSS_tap_file) 194 | generate_PSS_tap_file.main(['--PSS_LEN', str(PSS_LEN),'--TAP_DW', str(TAP_DW), '--N_id_2', str(N_id_2), '--path', sim_build]) 195 | 196 | cocotb_test.simulator.run( 197 | python_search=[tests_dir], 198 | verilog_sources=verilog_sources, 199 | includes=includes, 200 | toplevel=toplevel, 201 | module=module, 202 | parameters=parameters, 203 | sim_build=sim_build, 204 | extra_env=extra_env, 205 | testcase='simple_test', 206 | force_compile=True 207 | ) 208 | 209 | if __name__ == '__main__': 210 | os.environ['PLOTS'] = "1" 211 | test(IN_DW = 32, OUT_DW = 24, TAP_DW = 18, ALGO = 0, CFO = 10000, USE_TAP_FILE = 0) 212 | -------------------------------------------------------------------------------- /tests/test_PSS_correlator_with_peak_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import os 4 | import pytest 5 | import logging 6 | import importlib 7 | import matplotlib.pyplot as plt 8 | import os 9 | 10 | import cocotb 11 | import cocotb_test.simulator 12 | from cocotb.clock import Clock 13 | from cocotb.triggers import Timer 14 | from cocotb.triggers import RisingEdge 15 | 16 | import py3gpp 17 | import sigmf 18 | 19 | CLK_PERIOD_NS = 8 20 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 21 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 22 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 23 | 24 | 25 | class TB(object): 26 | def __init__(self, dut): 27 | self.dut = dut 28 | self.IN_DW = int(dut.IN_DW.value) 29 | self.OUT_DW = int(dut.OUT_DW.value) 30 | self.TAP_DW = int(dut.TAP_DW.value) 31 | self.PSS_LEN = int(dut.PSS_LEN.value) 32 | self.PSS_LOCAL = int(dut.PSS_LOCAL.value) 33 | self.ALGO = int(dut.ALGO.value) 34 | self.WINDOW_LEN = int(dut.WINDOW_LEN.value) 35 | 36 | self.log = logging.getLogger('cocotb.tb') 37 | self.log.setLevel(logging.DEBUG) 38 | 39 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 40 | model_file = os.path.abspath(os.path.join(tests_dir, '../model/PSS_correlator.py')) 41 | spec = importlib.util.spec_from_file_location('PSS_correlator', model_file) 42 | foo = importlib.util.module_from_spec(spec) 43 | spec.loader.exec_module(foo) 44 | self.PSS_correlator_model = foo.Model(self.IN_DW, self.OUT_DW, self.TAP_DW, self.PSS_LEN, self.PSS_LOCAL, self.ALGO) 45 | 46 | model_file = os.path.abspath(os.path.join(tests_dir, '../model/peak_detector.py')) 47 | spec = importlib.util.spec_from_file_location('peak_detector', model_file) 48 | foo = importlib.util.module_from_spec(spec) 49 | spec.loader.exec_module(foo) 50 | self.peak_detector_model = foo.Model(self.OUT_DW, self.WINDOW_LEN) 51 | 52 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 53 | cocotb.start_soon(self.model_clk(CLK_PERIOD_NS, 'ns')) 54 | 55 | async def model_clk(self, period, period_units): 56 | timer = Timer(period, period_units) 57 | while True: 58 | self.PSS_correlator_model.tick() 59 | self.peak_detector_model.tick() 60 | await timer 61 | 62 | async def generate_input(self): 63 | pass 64 | 65 | async def cycle_reset(self): 66 | self.dut.s_axis_in_tvalid.value = 0 67 | self.dut.reset_ni.setimmediatevalue(1) 68 | await RisingEdge(self.dut.clk_i) 69 | self.dut.reset_ni.value = 0 70 | await RisingEdge(self.dut.clk_i) 71 | self.dut.reset_ni.value = 1 72 | await RisingEdge(self.dut.clk_i) 73 | self.PSS_correlator_model.reset() 74 | self.peak_detector_model.reset() 75 | 76 | @cocotb.test() 77 | async def simple_test(dut): 78 | tb = TB(dut) 79 | FILE = '../../tests/' + os.environ['TEST_FILE'] + '.sigmf-data' 80 | handle = sigmf.sigmffile.fromfile(FILE) 81 | fs = handle.get_global_field(sigmf.SigMFFile.SAMPLE_RATE_KEY) 82 | waveform = handle.read_samples() 83 | waveform /= max(waveform.real.max(), waveform.imag.max()) 84 | dec_factor = int(fs / 1920000) 85 | print(f'test_file = {FILE}') 86 | print(f'sample_rate = {fs}, decimation_factor = {dec_factor}') 87 | waveform = scipy.signal.decimate(waveform, dec_factor, ftype='fir') 88 | waveform /= max(waveform.real.max(), waveform.imag.max()) 89 | waveform *= 2 ** (tb.IN_DW // 2 - 1) 90 | waveform = waveform.real.astype(int) + 1j*waveform.imag.astype(int) 91 | 92 | await tb.cycle_reset() 93 | 94 | if os.environ['TEST_FILE'] == '30720KSPS_dl_signal': 95 | num_items = 500 96 | expected_peak_pos = 416 97 | else: 98 | num_items = 1500 99 | expected_peak_pos = 1197 100 | rx_counter = 0 101 | in_counter = 0 102 | received = np.empty(num_items, int) 103 | while rx_counter < num_items: 104 | await RisingEdge(dut.clk_i) 105 | data = (((int(waveform[in_counter].imag) & ((2 ** (tb.IN_DW // 2)) - 1)) << (tb.IN_DW // 2)) \ 106 | + ((int(waveform[in_counter].real)) & ((2 ** (tb.IN_DW // 2)) - 1))) & ((2 ** tb.IN_DW) - 1) 107 | dut.s_axis_in_tdata.value = data 108 | dut.s_axis_in_tvalid.value = 1 109 | tb.PSS_correlator_model.set_data(data) 110 | in_counter += 1 111 | 112 | #if dut.m_axis_out_tvalid == 1: 113 | # print(dut.m_axis_out_tdata.value.integer) 114 | # received[rx_counter] = dut.m_axis_out_tdata.value.integer 115 | # print(f'rx hdl {received[rx_counter]}') 116 | # rx_counter += 1 117 | received[rx_counter] = dut.peak_detected_o.value.integer 118 | rx_counter += 1 119 | # print(dut.peak_detected_o.value.integer) 120 | 121 | 122 | peak_pos = np.argmax(received) 123 | if 'PLOTS' in os.environ and os.environ['PLOTS'] == '1': 124 | _, ax = plt.subplots() 125 | ax.plot(np.sqrt(received)) 126 | # ax2=ax.twinx() 127 | # ax2.plot(np.sqrt(received_model), 'r-') 128 | # ax.axvline(x = ssb_start, color = 'y', linestyle = '--', label = 'axvline - full height') 129 | plt.show() 130 | print(f'highest peak at {peak_pos}') 131 | assert peak_pos == expected_peak_pos 132 | 133 | # bit growth inside PSS_correlator is a lot, be careful to not make OUT_DW too small ! 134 | @pytest.mark.parametrize("ALGO", [0, 1]) 135 | @pytest.mark.parametrize("IN_DW", [32]) 136 | @pytest.mark.parametrize("OUT_DW", [32]) 137 | @pytest.mark.parametrize("TAP_DW", [32]) 138 | @pytest.mark.parametrize("WINDOW_LEN", [8]) 139 | @pytest.mark.parametrize("DETECTION_SHIFT", [4]) 140 | def test(IN_DW, OUT_DW, TAP_DW, ALGO, DETECTION_SHIFT, WINDOW_LEN, FILE = '30720KSPS_dl_signal'): 141 | dut = 'PSS_correlator_with_peak_detector' 142 | module = os.path.splitext(os.path.basename(__file__))[0] 143 | toplevel = dut 144 | 145 | verilog_sources = [ 146 | os.path.join(rtl_dir, f'{dut}.sv'), 147 | os.path.join(rtl_dir, 'Peak_detector.sv'), 148 | os.path.join(rtl_dir, 'PSS_correlator.sv') 149 | ] 150 | includes = [] 151 | 152 | PSS_LEN = 128 153 | parameters = {} 154 | parameters['IN_DW'] = IN_DW 155 | parameters['OUT_DW'] = OUT_DW 156 | parameters['TAP_DW'] = TAP_DW 157 | parameters['PSS_LEN'] = PSS_LEN 158 | parameters['ALGO'] = ALGO 159 | parameters['WINDOW_LEN'] = WINDOW_LEN 160 | parameters['DETECTION_SHIFT'] = DETECTION_SHIFT 161 | os.environ['TEST_FILE'] = FILE 162 | 163 | if FILE == '30720KSPS_dl_signal': 164 | N_id_2 = 2 165 | else: 166 | N_id_2 = 0 167 | 168 | # imaginary part is in upper 16 Bit 169 | PSS = np.zeros(PSS_LEN, 'complex') 170 | PSS[0:-1] = py3gpp.nrPSS(N_id_2) 171 | taps = np.fft.ifft(np.fft.fftshift(PSS)) 172 | taps /= max(taps.real.max(), taps.imag.max()) 173 | taps *= 2 ** (TAP_DW // 2 - 1) 174 | parameters['PSS_LOCAL'] = 0 175 | for i in range(len(taps)): 176 | parameters['PSS_LOCAL'] += ((int(np.round(np.imag(taps[i]))) & (2 ** (TAP_DW // 2) - 1)) << (TAP_DW * i + TAP_DW // 2)) \ 177 | + ((int(np.round(np.real(taps[i]))) & (2 ** (TAP_DW // 2) - 1)) << (TAP_DW * i)) 178 | extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} 179 | parameters_no_taps = parameters.copy() 180 | del parameters_no_taps['PSS_LOCAL'] 181 | sim_build='sim_build/' + '_'.join(('{}={}'.format(*i) for i in parameters_no_taps.items())) 182 | cocotb_test.simulator.run( 183 | python_search=[tests_dir], 184 | verilog_sources=verilog_sources, 185 | includes=includes, 186 | toplevel=toplevel, 187 | module=module, 188 | parameters=parameters, 189 | sim_build=sim_build, 190 | extra_env=extra_env, 191 | waves=True, 192 | testcase='simple_test', 193 | force_compile=True 194 | ) 195 | 196 | @pytest.mark.parametrize("FILE", ["772850KHz_3840KSPS_low_gain"]) 197 | def test_recording(FILE): 198 | test(IN_DW = 32, OUT_DW = 32, TAP_DW = 32, WINDOW_LEN = 8, ALGO = 0, DETECTION_SHIFT = 3, FILE = FILE) 199 | 200 | if __name__ == '__main__': 201 | os.environ['PLOTS'] = '1' 202 | test_recording("772850KHz_3840KSPS_low_gain") 203 | # test(IN_DW = 32, OUT_DW = 32, TAP_DW = 32, WINDOW_LEN = 8, DETECTION_SHIFT = 4, ALGO = 0) 204 | -------------------------------------------------------------------------------- /tests/test_SSS_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import pytest 4 | import logging 5 | import os 6 | 7 | import cocotb 8 | import cocotb_test.simulator 9 | from cocotb.clock import Clock 10 | from cocotb.triggers import RisingEdge 11 | 12 | import py3gpp 13 | 14 | CLK_PERIOD_NS = 8 15 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 16 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 17 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 18 | 19 | class TB(object): 20 | def __init__(self, dut): 21 | self.dut = dut 22 | 23 | self.log = logging.getLogger('cocotb.tb') 24 | self.log.setLevel(logging.DEBUG) 25 | 26 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 27 | 28 | async def cycle_reset(self): 29 | self.dut.s_axis_in_tvalid.value = 0 30 | self.dut.reset_ni.value = 0 31 | await RisingEdge(self.dut.clk_i) 32 | self.dut.reset_ni.value = 1 33 | await RisingEdge(self.dut.clk_i) 34 | 35 | @cocotb.test() 36 | async def simple_test(dut): 37 | tb = TB(dut) 38 | await tb.cycle_reset() 39 | 40 | SSS_len = 127 41 | N_id_1 = int(os.environ['N_ID_1']) 42 | N_id_2 = int(os.environ['N_ID_2']) 43 | print(f'test N_id_1 = {N_id_1} N_id_2 = {N_id_2}') 44 | SSS_seq = (py3gpp.nrSSS(3*N_id_1 + N_id_2) + 1) / 2 45 | # SSS_seq = np.append(SSS_seq, 0) 46 | 47 | await RisingEdge(dut.clk_i) 48 | dut.N_id_2_i.value = N_id_2 49 | dut.N_id_2_valid_i.value = 1 50 | await RisingEdge(dut.clk_i) 51 | dut.N_id_2_valid_i.value = 0 52 | 53 | for i in range(SSS_len): 54 | dut.s_axis_in_tvalid.value = 1 55 | dut.s_axis_in_tdata.value = int(SSS_seq[i]) * 2 - 1 # simple BPSK modulation 56 | await RisingEdge(dut.clk_i) 57 | dut.s_axis_in_tvalid.value = 0 58 | await RisingEdge(dut.clk_i) 59 | 60 | max_wait_cycles = 335 * SSS_len + 1000 61 | cycle_counter = 0 62 | while cycle_counter < max_wait_cycles: 63 | await RisingEdge(dut.clk_i) 64 | if dut.m_axis_out_tvalid.value.integer == 1: 65 | detected_N_id_1 = dut.m_axis_out_tdata.value.integer 66 | detected_N_id = dut.N_id_o.value.integer 67 | print(f'detected_N_id_1 = {detected_N_id_1}') 68 | break 69 | cycle_counter += 1 70 | 71 | assert detected_N_id_1 == N_id_1 72 | assert detected_N_id == N_id_1 * 3 + N_id_2 73 | # assert dut.m_axis_out_tdata.value == N_id_1 74 | 75 | @pytest.mark.parametrize("N_ID_1", [0, 335]) 76 | @pytest.mark.parametrize("N_ID_2", [0, 1, 2]) 77 | def test(N_ID_1, N_ID_2): 78 | dut = 'SSS_detector' 79 | module = os.path.splitext(os.path.basename(__file__))[0] 80 | toplevel = dut 81 | 82 | verilog_sources = [ 83 | os.path.join(rtl_dir, f'{dut}.sv'), 84 | os.path.join(rtl_dir, 'LFSR/LFSR.sv') 85 | ] 86 | includes = [] 87 | 88 | os.environ['N_ID_1'] = str(N_ID_1) 89 | os.environ['N_ID_2'] = str(N_ID_2) 90 | parameters = {} 91 | parameters['IN_DW'] = 32 92 | 93 | sim_build='sim_build/' + '_'.join(('{}={}'.format(*i) for i in parameters.items())) 94 | cocotb_test.simulator.run( 95 | python_search=[tests_dir], 96 | verilog_sources=verilog_sources, 97 | includes=includes, 98 | toplevel=toplevel, 99 | module=module, 100 | parameters=parameters, 101 | sim_build=sim_build, 102 | testcase='simple_test', 103 | force_compile=True 104 | ) 105 | 106 | if __name__ == '__main__': 107 | test(N_ID_1 = 335, N_ID_2 = 1) 108 | -------------------------------------------------------------------------------- /tests/test_atan2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import pytest 4 | import logging 5 | import matplotlib.pyplot as plt 6 | import os 7 | 8 | import cocotb 9 | import cocotb_test.simulator 10 | from cocotb.clock import Clock 11 | from cocotb.triggers import RisingEdge 12 | 13 | 14 | CLK_PERIOD_NS = 8 15 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 16 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 17 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 18 | 19 | def _twos_comp(val, bits): 20 | """compute the 2's complement of int value val""" 21 | if (val & (1 << (bits - 1))) != 0: 22 | val = val - (1 << bits) 23 | return int(val) 24 | 25 | class TB(object): 26 | def __init__(self, dut): 27 | self.dut = dut 28 | self.INPUT_WIDTH = int(dut.INPUT_WIDTH.value) 29 | self.OUTPUT_WIDTH = int(dut.OUTPUT_WIDTH.value) 30 | 31 | self.log = logging.getLogger('cocotb.tb') 32 | self.log.setLevel(logging.DEBUG) 33 | 34 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 35 | 36 | async def cycle_reset(self): 37 | self.dut.reset_ni.setimmediatevalue(1) 38 | await RisingEdge(self.dut.clk_i) 39 | self.dut.reset_ni.value = 0 40 | await RisingEdge(self.dut.clk_i) 41 | self.dut.reset_ni.value = 1 42 | await RisingEdge(self.dut.clk_i) 43 | 44 | @cocotb.test() 45 | async def simple_test(dut): 46 | tb = TB(dut) 47 | 48 | dut.valid_i.value = 0 49 | await tb.cycle_reset() 50 | 51 | max_rx_cnt = 500 52 | MAX_VAL = 2**(tb.INPUT_WIDTH - 1) - 1 53 | np.random.seed(1) 54 | numerator = np.random.randint(-MAX_VAL, MAX_VAL, max_rx_cnt) 55 | denominator = np.random.randint(-MAX_VAL, MAX_VAL, max_rx_cnt) 56 | 57 | # manually add interesting test cases 58 | # these corner cases caused a nasty bug because they were not covered in tests ! 59 | numerator[0] = 0 60 | denominator[0] = MAX_VAL 61 | numerator[1] = 0 62 | denominator[1] = -MAX_VAL 63 | 64 | tx_cnt = 0 65 | expected_results = [] 66 | dut.numerator_i.value = int(numerator[tx_cnt]) 67 | dut.denominator_i.value = int(denominator[tx_cnt]) 68 | dut.valid_i.value = 1 69 | 70 | await RisingEdge(dut.clk_i) 71 | dut.valid_i.value = 0 72 | tx_cnt += 1 73 | expected_results.append(np.arctan2(numerator[tx_cnt], denominator[tx_cnt])) 74 | 75 | clk_cnt = 0 76 | max_clk_cnt = 100000 77 | rx_cnt = 0 78 | PI = 2 ** (tb.OUTPUT_WIDTH - 1) - 1 79 | 80 | if os.environ['PIPELINED'] == '0': 81 | while (clk_cnt < max_clk_cnt) and (rx_cnt < max_rx_cnt): 82 | await RisingEdge(dut.clk_i) 83 | clk_cnt += 1 84 | 85 | if (dut.valid_o.value == 1): 86 | result = _twos_comp(dut.angle_o.value.integer, tb.OUTPUT_WIDTH) / PI * 180 87 | print(f'atan2({numerator[rx_cnt]} / {denominator[rx_cnt]}) = {result:.3f} expected {np.arctan2(numerator[rx_cnt], denominator[rx_cnt]) / np.pi * 180:.3f}') 88 | assert np.abs(np.abs(np.arctan2(numerator[rx_cnt], denominator[rx_cnt]) / np.pi * 180) - np.abs(result)) < 0.1 89 | rx_cnt += 1 90 | 91 | if rx_cnt < max_rx_cnt: 92 | dut.numerator_i.value = int(numerator[tx_cnt]) 93 | dut.denominator_i.value = int(denominator[tx_cnt]) 94 | dut.valid_i.value = 1 95 | tx_cnt += 1 96 | else: 97 | dut.valid_i.value = 0 98 | else: 99 | while (clk_cnt < max_clk_cnt) and (rx_cnt < max_rx_cnt): 100 | await RisingEdge(dut.clk_i) 101 | clk_cnt += 1 102 | 103 | if tx_cnt < max_rx_cnt: 104 | dut.numerator_i.value = int(numerator[tx_cnt]) 105 | dut.denominator_i.value = int(denominator[tx_cnt]) 106 | dut.valid_i.value = 1 107 | expected_results.append(np.arctan2(numerator[tx_cnt], denominator[tx_cnt])) 108 | # print(f'tx atan2({numerator[tx_cnt]} / {denominator[tx_cnt]}) expecting {expected_results[tx_cnt] / np.pi * 180:.3f}') 109 | tx_cnt += 1 110 | else: 111 | dut.valid_i.value = 0 112 | 113 | if (dut.valid_o.value == 1): 114 | result = _twos_comp(dut.angle_o.value.integer, tb.OUTPUT_WIDTH) / PI * 180 115 | print(f'atan2({numerator[rx_cnt]} / {denominator[rx_cnt]}) = {result:.3f} expected {expected_results[rx_cnt] / np.pi * 180:.3f}') 116 | # assert np.abs(np.abs(result) - np.abs(expected_results[rx_cnt] / np.pi * 180)) < 0.1 117 | rx_cnt += 1 118 | 119 | if clk_cnt == max_clk_cnt: 120 | print("no result received!") 121 | 122 | 123 | @pytest.mark.parametrize("INPUT_WIDTH", [16, 32]) 124 | @pytest.mark.parametrize("OUTPUT_WIDTH", [16, 32]) 125 | @pytest.mark.parametrize("PIPELINED", [0, 1]) 126 | def test(INPUT_WIDTH, OUTPUT_WIDTH, PIPELINED): 127 | dut = 'atan2' 128 | module = os.path.splitext(os.path.basename(__file__))[0] 129 | toplevel = dut 130 | 131 | verilog_sources = [ 132 | os.path.join(rtl_dir, f'{dut}.sv'), 133 | os.path.join(rtl_dir, 'atan.sv'), 134 | os.path.join(rtl_dir, 'div.sv') 135 | ] 136 | includes = [] 137 | 138 | parameters = {} 139 | parameters['INPUT_WIDTH'] = INPUT_WIDTH 140 | parameters['OUTPUT_WIDTH'] = OUTPUT_WIDTH 141 | os.environ['PIPELINED'] = str(PIPELINED) 142 | 143 | parameters_dir = parameters.copy() 144 | sim_build='sim_build/_atan2_' + '_'.join(('{}={}'.format(*i) for i in parameters_dir.items())) 145 | cocotb_test.simulator.run( 146 | python_search=[tests_dir], 147 | verilog_sources=verilog_sources, 148 | includes=includes, 149 | toplevel=toplevel, 150 | module=module, 151 | parameters=parameters, 152 | sim_build=sim_build, 153 | testcase='simple_test', 154 | force_compile=True, 155 | waves=True 156 | ) 157 | 158 | if __name__ == '__main__': 159 | test(INPUT_WIDTH = 18, OUTPUT_WIDTH = 16, PIPELINED = 0) 160 | -------------------------------------------------------------------------------- /tests/test_div.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import pytest 4 | import logging 5 | import matplotlib.pyplot as plt 6 | import os 7 | 8 | import cocotb 9 | import cocotb_test.simulator 10 | from cocotb.clock import Clock 11 | from cocotb.triggers import RisingEdge 12 | 13 | 14 | CLK_PERIOD_NS = 8 15 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 16 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 17 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 18 | 19 | def _twos_comp(val, bits): 20 | """compute the 2's complement of int value val""" 21 | if (val & (1 << (bits - 1))) != 0: 22 | val = val - (1 << bits) 23 | return int(val) 24 | 25 | class TB(object): 26 | def __init__(self, dut): 27 | self.dut = dut 28 | self.INPUT_WIDTH = int(dut.INPUT_WIDTH.value) 29 | self.RESULT_WIDTH = int(dut.RESULT_WIDTH.value) 30 | self.PIPELINED = int(dut.PIPELINED.value) 31 | 32 | self.log = logging.getLogger('cocotb.tb') 33 | self.log.setLevel(logging.DEBUG) 34 | 35 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 36 | 37 | async def cycle_reset(self): 38 | self.dut.reset_ni.setimmediatevalue(1) 39 | await RisingEdge(self.dut.clk_i) 40 | self.dut.reset_ni.value = 0 41 | await RisingEdge(self.dut.clk_i) 42 | self.dut.reset_ni.value = 1 43 | await RisingEdge(self.dut.clk_i) 44 | 45 | @cocotb.test() 46 | async def simple_test(dut): 47 | tb = TB(dut) 48 | 49 | dut.valid_i.value = 0 50 | await tb.cycle_reset() 51 | 52 | numerator = np.random.randint(0, 2**(tb.INPUT_WIDTH) - 1) 53 | denominator = np.random.randint(1, 2**(tb.INPUT_WIDTH) - 1) 54 | # numerator = 2775 55 | # denominator = 48908 56 | dut.numerator_i.value = numerator 57 | dut.denominator_i.value = denominator 58 | dut.valid_i.value = 1 59 | 60 | await RisingEdge(dut.clk_i) 61 | dut.valid_i.value = 0 62 | 63 | clk_cnt = 0 64 | max_clk_cnt = 1000 65 | rx_cnt = 0 66 | max_rx_cnt = 500 67 | while (clk_cnt < max_clk_cnt) and (rx_cnt < max_rx_cnt): 68 | await RisingEdge(dut.clk_i) 69 | clk_cnt += 1 70 | 71 | if (dut.valid_o.value == 1): 72 | result = dut.result_o.value.integer 73 | # print(f'{numerator} / {denominator} = {result}') 74 | assert np.floor(numerator / denominator) == result 75 | rx_cnt += 1 76 | 77 | numerator = np.random.randint(0, 2**(tb.INPUT_WIDTH) - 1) 78 | denominator = np.random.randint(1, 2**(tb.INPUT_WIDTH) - 1) 79 | dut.numerator_i.value = numerator 80 | dut.denominator_i.value = denominator 81 | dut.valid_i.value = 1 82 | else: 83 | dut.valid_i.value = 0 84 | 85 | if clk_cnt == max_clk_cnt: 86 | print("no result received!") 87 | 88 | 89 | @pytest.mark.parametrize("INPUT_WIDTH", [16, 32]) 90 | @pytest.mark.parametrize("RESULT_WIDTH", [16, 32]) 91 | @pytest.mark.parametrize("PIPELINED", [0, 1]) 92 | def test(INPUT_WIDTH, RESULT_WIDTH, PIPELINED): 93 | dut = 'div' 94 | module = os.path.splitext(os.path.basename(__file__))[0] 95 | toplevel = dut 96 | 97 | verilog_sources = [ 98 | os.path.join(rtl_dir, f'{dut}.sv') 99 | ] 100 | includes = [] 101 | 102 | parameters = {} 103 | parameters['INPUT_WIDTH'] = INPUT_WIDTH 104 | parameters['RESULT_WIDTH'] = RESULT_WIDTH 105 | parameters['PIPELINED'] = PIPELINED 106 | 107 | parameters_dir = parameters.copy() 108 | sim_build='sim_build/' + '_'.join(('{}={}'.format(*i) for i in parameters_dir.items())) 109 | cocotb_test.simulator.run( 110 | python_search=[tests_dir], 111 | verilog_sources=verilog_sources, 112 | includes=includes, 113 | toplevel=toplevel, 114 | module=module, 115 | parameters=parameters, 116 | sim_build=sim_build, 117 | testcase='simple_test', 118 | force_compile=True, 119 | waves=True 120 | ) 121 | 122 | if __name__ == '__main__': 123 | test(INPUT_WIDTH = 16, RESULT_WIDTH = 16, PIPELINED = 1) 124 | -------------------------------------------------------------------------------- /tests/test_dot_product.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import os 4 | import pytest 5 | import logging 6 | import importlib 7 | import matplotlib.pyplot as plt 8 | import os 9 | 10 | import cocotb 11 | import cocotb_test.simulator 12 | from cocotb.clock import Clock 13 | from cocotb.triggers import Timer 14 | from cocotb.triggers import RisingEdge 15 | 16 | import py3gpp 17 | import sigmf 18 | 19 | CLK_PERIOD_NS = 8 20 | CLK_PERIOD_S = CLK_PERIOD_NS * 0.000000001 21 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 22 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 23 | 24 | def _twos_comp(val, bits): 25 | """compute the 2's complement of int value val""" 26 | if (val & (1 << (bits - 1))) != 0: 27 | val = val - (1 << bits) 28 | return int(val) 29 | 30 | class TB(object): 31 | def __init__(self, dut): 32 | self.dut = dut 33 | self.A_DW = int(dut.A_DW.value) 34 | self.B_DW = int(dut.B_DW.value) 35 | self.OUT_DW = int(dut.OUT_DW.value) 36 | self.LEN_DW = int(dut.LEN.value) 37 | 38 | self.log = logging.getLogger('cocotb.tb') 39 | self.log.setLevel(logging.DEBUG) 40 | 41 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 42 | #model_dir = os.path.abspath(os.path.join(tests_dir, '../model/PSS_correlator.py')) 43 | #spec = importlib.util.spec_from_file_location('PSS_correlator', model_dir) 44 | #foo = importlib.util.module_from_spec(spec) 45 | #spec.loader.exec_module(foo) 46 | #self.model = foo.Model(self.IN_DW, self.OUT_DW, self.TAP_DW, self.PSS_LEN, self.PSS_LOCAL, self.ALGO) 47 | 48 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 49 | cocotb.start_soon(self.model_clk(CLK_PERIOD_NS, 'ns')) 50 | 51 | async def model_clk(self, period, period_units): 52 | timer = Timer(period, period_units) 53 | while True: 54 | #self.model.tick() 55 | await timer 56 | 57 | async def cycle_reset(self): 58 | self.dut.reset_ni.setimmediatevalue(1) 59 | await RisingEdge(self.dut.clk_i) 60 | self.dut.reset_ni.value = 0 61 | await RisingEdge(self.dut.clk_i) 62 | self.dut.reset_ni.value = 1 63 | await RisingEdge(self.dut.clk_i) 64 | # self.model.reset() 65 | 66 | async def send_vector(self, vec_a, vec_b): 67 | for i in range(len(vec_a)): 68 | self.dut.start_i.value = i == 0 69 | self.dut.s_axis_a_tdata.value = int(vec_a[i]) 70 | self.dut.s_axis_b_tdata.value = int(vec_b[i]) 71 | self.dut.s_axis_a_tvalid.value = 1 72 | self.dut.s_axis_b_tvalid.value = 1 73 | await RisingEdge(self.dut.clk_i) 74 | self.dut.s_axis_a_tvalid.value = 0 75 | self.dut.s_axis_b_tvalid.value = 0 76 | self.dut.start_i.value = 0 77 | await RisingEdge(self.dut.clk_i) 78 | for i in range(10): 79 | await RisingEdge(self.dut.clk_i) 80 | if self.dut.valid_o == 1: 81 | return _twos_comp(self.dut.result_o.value.integer & (2 ** (self.OUT_DW // 2) - 1), self.OUT_DW // 2) \ 82 | + 1j * _twos_comp((self.dut.result_o.value.integer >> (self.OUT_DW // 2)) & (2 ** (self.OUT_DW // 2) - 1), self.OUT_DW // 2) 83 | assert False, "no answer received" 84 | 85 | 86 | 87 | @cocotb.test() 88 | async def simple_test(dut): 89 | tb = TB(dut) 90 | await tb.cycle_reset() 91 | 92 | for _ in range(10): 93 | await RisingEdge(dut.clk_i) 94 | 95 | vec_a = np.ones(128) 96 | vec_b = np.ones(128) 97 | res = await tb.send_vector(vec_a, vec_b) 98 | print(f'result = {res}') 99 | assert res == np.dot(vec_a, vec_b) 100 | 101 | vec_a = np.ones(128) 102 | vec_b = np.arange(128) 103 | res = await tb.send_vector(vec_a, vec_b) 104 | print(f'result = {res}') 105 | assert res == np.dot(vec_a, vec_b) 106 | 107 | vec_a = np.ones(128) 108 | vec_b = -np.arange(128) 109 | vec_b_twos = np.empty(128) 110 | for i in range(128): 111 | vec_b_twos[i] = vec_b[i] & (2 ** (tb.B_DW // 2) - 1) 112 | res = await tb.send_vector(vec_a, vec_b_twos) 113 | print(f'result = {res}') 114 | assert res == np.dot(vec_a, vec_b) 115 | 116 | vec_a = np.ones(128) 117 | vec_b = -np.arange(128) 118 | vec_b_twos = np.empty(128) 119 | for i in range(128): 120 | vec_b_twos[i] = (vec_b[i] & (2 ** (tb.B_DW // 2) - 1)) << (tb.B_DW // 2) 121 | res = await tb.send_vector(vec_a, vec_b_twos) 122 | print(f'result = {res}') 123 | assert res == np.dot(vec_a, 1j * vec_b) 124 | 125 | 126 | # bit growth inside PSS_correlator is a lot, be careful to not make OUT_DW too small ! 127 | @pytest.mark.parametrize("A_DW", [1]) 128 | @pytest.mark.parametrize("B_DW", [32]) 129 | @pytest.mark.parametrize("LEN", [128]) 130 | @pytest.mark.parametrize("OUT_DW", [32]) 131 | @pytest.mark.parametrize("A_COMPLEX", [0]) 132 | @pytest.mark.parametrize("B_COMPLEX", [1]) 133 | def test(A_DW, B_DW, LEN, OUT_DW, A_COMPLEX, B_COMPLEX): 134 | dut = 'dot_product' 135 | module = os.path.splitext(os.path.basename(__file__))[0] 136 | toplevel = dut 137 | 138 | verilog_sources = [ 139 | os.path.join(rtl_dir, f'{dut}.sv') 140 | ] 141 | includes = [] 142 | 143 | parameters = {} 144 | parameters['A_DW'] = A_DW 145 | parameters['B_DW'] = B_DW 146 | parameters['LEN'] = LEN 147 | parameters['OUT_DW'] = OUT_DW 148 | parameters['A_COMPLEX'] = A_COMPLEX 149 | parameters['B_COMPLEX'] = B_COMPLEX 150 | 151 | sim_build='sim_build/' + '_'.join(('{}={}'.format(*i) for i in parameters.items())) 152 | cocotb_test.simulator.run( 153 | python_search=[tests_dir], 154 | verilog_sources=verilog_sources, 155 | includes=includes, 156 | toplevel=toplevel, 157 | module=module, 158 | parameters=parameters, 159 | sim_build=sim_build, 160 | testcase='simple_test', 161 | force_compile=True 162 | ) 163 | 164 | if __name__ == '__main__': 165 | test(A_DW = 1, B_DW = 32, LEN = 128, OUT_DW = 32, A_COMPLEX = 0, B_COMPLEX = 1) 166 | -------------------------------------------------------------------------------- /tests/test_frame_sync.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import pytest 4 | import logging 5 | import os 6 | import scipy 7 | import matplotlib.pyplot as plt 8 | 9 | import cocotb 10 | import cocotb_test.simulator 11 | from cocotb.clock import Clock 12 | from cocotb.triggers import RisingEdge 13 | 14 | import py3gpp 15 | import sigmf 16 | 17 | CLK_PERIOD_NS = 260416 18 | CLK_PERIOD_S = CLK_PERIOD_NS * 1e-12 19 | tests_dir = os.path.abspath(os.path.dirname(__file__)) 20 | rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', 'hdl')) 21 | 22 | class TB(object): 23 | def __init__(self, dut): 24 | self.dut = dut 25 | 26 | self.IN_DW = int(dut.IN_DW.value) 27 | 28 | self.log = logging.getLogger('cocotb.tb') 29 | self.log.setLevel(logging.DEBUG) 30 | 31 | cocotb.start_soon(Clock(self.dut.clk_i, CLK_PERIOD_NS, units='ns').start()) 32 | 33 | async def cycle_reset(self): 34 | self.dut.s_axis_in_tvalid.value = 0 35 | self.dut.reset_ni.setimmediatevalue(1) 36 | await RisingEdge(self.dut.clk_i) 37 | self.dut.reset_ni.value = 0 38 | await RisingEdge(self.dut.clk_i) 39 | self.dut.reset_ni.value = 1 40 | await RisingEdge(self.dut.clk_i) 41 | 42 | @cocotb.test() 43 | async def stream_tb(dut): 44 | tb = TB(dut) 45 | await tb.cycle_reset() 46 | 47 | handle = sigmf.sigmffile.fromfile(tests_dir + '/30720KSPS_dl_signal.sigmf-data') 48 | waveform = handle.read_samples() 49 | waveform /= max(waveform.real.max(), waveform.imag.max()) 50 | waveform = scipy.signal.decimate(waveform, 16//2, ftype='fir') 51 | waveform /= max(waveform.real.max(), waveform.imag.max()) 52 | waveform *= 2 ** (tb.IN_DW // 2 - 1) 53 | waveform = waveform.real.astype(int) + 1j*waveform.imag.astype(int) 54 | 55 | CP1_LEN = 20 56 | CP2_LEN = 18 57 | FFT_LEN = 256 58 | L_MAX = 4 # 4 SSBs 59 | SSB_pattern = [2, 8, 16, 22] # case A 60 | 61 | START_POS = 842 #+ int(3.84e6 * 0.001 * int(ibar_SSB / 2)) + 1646 * (ibar_SSB % 2) # this hack works for ibar_SSB = 0 .. 3 62 | SSB_POS = [842, 842 + int(3.84e6 * 0.02)] 63 | START_SYMBOL = 2 64 | num_symbols = 50 65 | symbol = np.empty((num_symbols,256), 'complex') 66 | 67 | pos = START_POS 68 | _, axs = plt.subplots(8, 7, sharex=True, sharey=True) 69 | axs = np.ravel(axs) 70 | for i in range(num_symbols): 71 | new_symbol = np.fft.fftshift(np.fft.fft(waveform[pos:][:FFT_LEN])) 72 | if (i+4)%7 == 0: 73 | pos += CP1_LEN + FFT_LEN 74 | else: 75 | pos += CP2_LEN + FFT_LEN 76 | axs[i].plot(new_symbol.real, new_symbol.imag, '.r') 77 | symbol[i] = new_symbol 78 | # plt.show() 79 | 80 | symbol /= max(symbol.real.max(), symbol.imag.max()) 81 | symbol *= (2 ** (tb.IN_DW // 2 - 1) - 1) 82 | symbol = symbol.real.astype(int) + 1j*symbol.imag.astype(int) 83 | 84 | max_clk_cnt = int(3.84e6 * 0.025) # 25ms 85 | clk_cnt = 0 86 | symbol_id = 0 87 | SC_cnt = 0 88 | pos = 0 89 | current_CP_len = CP2_LEN 90 | ibar_SSB_DEALAY = 1000 91 | while clk_cnt < max_clk_cnt: 92 | await RisingEdge(dut.clk_i) 93 | 94 | if pos in SSB_POS: 95 | dut.N_id_2_valid_i.value = 1 96 | # assume SSB arrives at symbol 3 97 | current_CP_len = CP2_LEN 98 | SC_cnt = 0 99 | symbol_id = START_SYMBOL + 1 # +1 because PSS is not included 100 | print(f'sending SSB at pos = {pos}') 101 | else: 102 | dut.N_id_2_valid_i.value = 0 103 | 104 | if pos == ibar_SSB_DEALAY: 105 | dut.ibar_SSB_i.value = 0 106 | dut.ibar_SSB_valid_i.value = 1 107 | else: 108 | dut.ibar_SSB_valid_i.value = 0 109 | 110 | data = (((int(waveform[pos].imag) & ((2 ** (tb.IN_DW // 2)) - 1)) << (tb.IN_DW // 2)) \ 111 | + ((int(waveform[pos].real)) & ((2 ** (tb.IN_DW // 2)) - 1))) 112 | dut.s_axis_in_tdata.value = data 113 | dut.s_axis_in_tvalid.value = 1 114 | 115 | if SC_cnt == (256 + current_CP_len): 116 | symbol_id += 1 117 | symbol_id %= 14 118 | SC_cnt = 0 119 | if symbol_id in [0, 7]: 120 | current_CP_len = CP1_LEN 121 | else: 122 | current_CP_len = CP2_LEN 123 | print(f'send symbol {symbol_id}') 124 | else: 125 | SC_cnt += 1 126 | 127 | if pos == SSB_POS[1] + 2: 128 | assert dut.SSB_start_o.value == 1 129 | 130 | if dut.SSB_start_o.value == 1: 131 | print(f'SSB_start at pos = {pos}') 132 | clk_cnt += 1 133 | pos += 1 134 | print(f'finished after {clk_cnt} clk cycles') 135 | 136 | 137 | @pytest.mark.parametrize("IN_DW", [32]) 138 | def test_stream(IN_DW): 139 | dut = 'frame_sync' 140 | module = os.path.splitext(os.path.basename(__file__))[0] 141 | toplevel = dut 142 | 143 | verilog_sources = [ 144 | os.path.join(rtl_dir, f'{dut}.sv'), 145 | os.path.join(rtl_dir, f'frame_sync_regmap.sv'), 146 | os.path.join(rtl_dir, f'AXI_lite_interface.sv') 147 | ] 148 | includes = [] 149 | parameters = {} 150 | parameters['IN_DW'] = IN_DW 151 | parameters_dirname = parameters.copy() 152 | 153 | sim_build='sim_build/test_stream' + '_'.join(('{}={}'.format(*i) for i in parameters_dirname.items())) 154 | cocotb_test.simulator.run( 155 | python_search=[tests_dir], 156 | verilog_sources=verilog_sources, 157 | includes=includes, 158 | toplevel=toplevel, 159 | module=module, 160 | parameters=parameters, 161 | sim_build=sim_build, 162 | testcase='stream_tb', 163 | force_compile=True 164 | ) 165 | 166 | if __name__ == '__main__': 167 | test_stream(IN_DW = 32) 168 | -------------------------------------------------------------------------------- /tools/generate_FFT_demod_tap_file.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import argparse 3 | import sys 4 | import os 5 | 6 | def create_lut_file(NFFT, CP_LEN, CP_ADVANCE, OUT_DW, path): 7 | FFT_demod_taps = np.empty(2 ** NFFT, int) 8 | angle_step = 2 * np.pi * (CP_LEN - CP_ADVANCE) / (2 ** NFFT) 9 | const_angle = np.pi * (CP_LEN - CP_ADVANCE) 10 | for i in range(2 ** NFFT): 11 | FFT_demod_taps[i] = int((np.cos(angle_step * i + const_angle) * (2 ** (OUT_DW // 2 - 1) - 1))) & (2 ** (OUT_DW // 2) - 1) 12 | tmp = int((np.sin(angle_step * i + const_angle) * (2 ** (OUT_DW // 2 - 1) - 1))) & (2 ** (OUT_DW // 2) - 1) 13 | # print(f'{FFT_demod_taps[i]} = {np.cos(angle_step * i + np.pi * (CP_LEN - CP_ADVANCE))}') 14 | FFT_demod_taps[i] |= tmp << (OUT_DW // 2) 15 | filename = f'FFT_demod_taps_{int(NFFT)}_{int(CP_LEN)}_{int(CP_ADVANCE)}_{int(OUT_DW)}.hex' 16 | if not path == '': 17 | os.makedirs(path, exist_ok=True) 18 | np.savetxt(os.path.join(path, filename), FFT_demod_taps.T, fmt = '%x', delimiter = ' ') 19 | 20 | def main(args): 21 | print(sys.argv) 22 | 23 | parser = argparse.ArgumentParser(description='Creates lut table for DDS core') 24 | parser.add_argument('--path', metavar='path', required=False, default = '', help='path for lut file') 25 | parser.add_argument('--NFFT', metavar='NFFT', required=False, default = 8, help='NFFT (FFT length = 2 ** NFFT)') 26 | parser.add_argument('--CP_LEN', metavar='CP_LEN', required=False, default = 8, help='CP length in number of samples') 27 | parser.add_argument('--CP_ADVANCE', metavar='CP_ADVANCE', required=False, default = 8, help='CP advance in number of samples') 28 | parser.add_argument('--OUT_DW', metavar='OUT_DW', required=False, default = 8, help='Data width of output') 29 | args = parser.parse_args(args) 30 | 31 | create_lut_file(int(args.NFFT), int(args.CP_LEN), int(args.CP_ADVANCE), int(args.OUT_DW), args.path) 32 | 33 | if __name__ == "__main__": 34 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /tools/generate_PSS_tap_file.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import argparse 3 | import sys 4 | import os 5 | import py3gpp 6 | 7 | def create_tap_file(PSS_LEN, TAP_DW, N_id_2, path): 8 | PSS = np.zeros(PSS_LEN, 'complex') 9 | PSS[0:-1] = py3gpp.nrPSS(N_id_2) 10 | taps = np.fft.ifft(np.fft.fftshift(PSS)) 11 | taps /= max(taps.real.max(), taps.imag.max()) 12 | taps *= 2 ** (TAP_DW // 2 - 1) - 1 13 | 14 | PSS_taps = np.empty(PSS_LEN, int) 15 | for i in range(len(taps)): 16 | PSS_taps[i] = ((int(np.imag(taps[i])) & (2 ** (TAP_DW // 2) - 1)) << (TAP_DW // 2)) \ 17 | + (int(np.real(taps[i])) & (2 ** (TAP_DW // 2) - 1)) 18 | filename = f'PSS_taps_{int(N_id_2)}.hex' 19 | np.savetxt(os.path.join(path, filename), PSS_taps.T, fmt = '%x', delimiter = ' ') 20 | 21 | def main(args): 22 | print(sys.argv) 23 | 24 | parser = argparse.ArgumentParser(description='Creates lut table for DDS core') 25 | parser.add_argument('--path', metavar='path', required=False, default = '', help='path for lut file') 26 | parser.add_argument('--PSS_LEN', metavar='PSS_LEN', required=False, default = 8, help='Length of PSS in samples') 27 | parser.add_argument('--TAP_DW', metavar='TAP_DW', required=False, default = 8, help='TAP_DW') 28 | parser.add_argument('--N_id_2', metavar='N_id_2', required=False, default = 8, help='N_id_2') 29 | args = parser.parse_args(args) 30 | 31 | create_tap_file(int(args.PSS_LEN), int(args.TAP_DW), int(args.N_id_2), args.path) 32 | 33 | if __name__ == '__main__': 34 | main(sys.argv[1:]) --------------------------------------------------------------------------------