├── .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:])
--------------------------------------------------------------------------------