File: ofdm_plateau_detector.v

package info (click to toggle)
uhd 3.13.1.0-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 207,120 kB
  • sloc: cpp: 167,245; ansic: 86,841; vhdl: 53,420; python: 40,839; xml: 13,167; tcl: 5,688; makefile: 2,167; sh: 1,719; pascal: 230; csh: 94; asm: 20; perl: 11
file content (331 lines) | stat: -rw-r--r-- 15,557 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
//
// Copyright 2015 Ettus Research
//
// Specifically designed to work with 802.11 short preamble & long preamble.
//
// Algorithm: 1) Correlate plateaus from both short & long preamble and wait until threshold is exceeded
//               - Correlating the plateaus creates one peak. This helps avoid false detects due to noise 
//                 spikes and prevents a false detect on one of the two plateaus
//            2) Once the threshold is exceeded, find max of averaged D metric and then use 87.5% of max value
//               to indicate that the peak has passed
//            3) Align sample data to peak (with max metric value's index) and release.
//               - Output is aligned to the start of the short preamble
//               - tlast used as the trigger to indicate the NEXT sample is the detected start
//                 of the short preamble
//            4) Apply gain correction to sample data based on magnitude at max metric value
//            5) Begin looking for another peak

module ofdm_plateau_detector
#(
  parameter WIDTH_D          = 16,
  parameter WIDTH_PHASE      = 32,
  parameter WIDTH_MAG        = 16,  // Not so useful due to fixed divider width
  parameter WIDTH_SAMPLE     = 16,  // sc16
  parameter PREAMBLE_LEN     = 160, // Short length
  parameter PLATEAU_WIDTH    = 32,  // Expected plateau width
  parameter AGC_REF_LEVEL    = 16'd4000,
  parameter SR_THRESHOLD     = 5,
  parameter SR_AGC_REF_LEVEL = 6)
(
  input clk, input reset,
  input set_stb, input [7:0] set_addr, input [31:0] set_data,
  input [WIDTH_D-1:0] d_metric_tdata, input d_metric_tvalid, output d_metric_tready,
  input [WIDTH_PHASE-1:0] phase_in_tdata, input phase_in_tvalid, output phase_in_tready,
  input [WIDTH_MAG-1:0] magnitude_in_tdata, input magnitude_in_tvalid, output magnitude_in_tready,
  input [2*WIDTH_SAMPLE-1:0] sample_in_tdata, input sample_in_tvalid, output sample_in_tready,
  output [2*WIDTH_SAMPLE-1:0] sample_out_tdata, output sample_out_tlast, output sample_out_tvalid, input sample_out_tready,
  output [WIDTH_PHASE-1:0] phase_out_tdata, output phase_out_tlast, output phase_out_tvalid, input phase_out_tready
);

  /****************************************************************************
  ** Settings registers
  ****************************************************************************/
  wire [15:0] threshold; // Q1.14 (Signed bit, 1 integer bit, 14 fractional)
  setting_reg #(.my_addr(SR_THRESHOLD), .width(16)) sr_threshold (
    .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
    .out(threshold), .changed());

  wire [15:0] agc_reference_level;
  setting_reg #(.my_addr(SR_AGC_REF_LEVEL), .width(16), .at_reset(AGC_REF_LEVEL)) sr_agc_ref_level (
    .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
    .out(agc_reference_level), .changed());

  /****************************************************************************
  ** Average phase and magnitude across plateau
  ****************************************************************************/
  wire [WIDTH_PHASE-1:0] phase_in_fifo_tdata;
  wire phase_in_fifo_tvalid, phase_in_fifo_tready;
  axi_fifo #(.WIDTH(WIDTH_PHASE), .SIZE(8)) axi_fifo_short_phase_in (
    .clk(clk), .reset(reset), .clear(),
    .i_tdata(phase_in_tdata), .i_tvalid(phase_in_tvalid), .i_tready(phase_in_tready),
    .o_tdata(phase_in_fifo_tdata), .o_tvalid(phase_in_fifo_tvalid), .o_tready(phase_in_fifo_tready),
    .occupied(), .space());

  // Moving average of phase
  wire [$clog2(PLATEAU_WIDTH)+WIDTH_PHASE-1:0] phase_in_sum_tdata;
  wire phase_in_sum_tvalid, phase_in_sum_tready;
  moving_sum #(.MAX_LEN(PLATEAU_WIDTH), .WIDTH(WIDTH_PHASE)) moving_sum_phase (
    .clk(clk), .reset(reset), .clear(clear),
    .len(PLATEAU_WIDTH),
    .i_tdata(phase_in_fifo_tdata), .i_tlast(1'b0), .i_tvalid(phase_in_fifo_tvalid), .i_tready(phase_in_fifo_tready),
    .o_tdata(phase_in_sum_tdata), .o_tlast(), .o_tvalid(phase_in_sum_tvalid), .o_tready(phase_in_sum_tready));

  wire [WIDTH_PHASE-1:0] phase_in_round_tdata;
  wire phase_in_round_tvalid, phase_in_round_tready;
  axi_round #(
    .WIDTH_IN($clog2(PLATEAU_WIDTH)+WIDTH_PHASE),
    .WIDTH_OUT(WIDTH_PHASE))
  round_phase (
    .clk(clk), .reset(reset),
    .i_tdata(phase_in_sum_tdata), .i_tlast(1'b0), .i_tvalid(phase_in_sum_tvalid), .i_tready(phase_in_sum_tready),
    .o_tdata(phase_in_round_tdata), .o_tlast(), .o_tvalid(phase_in_round_tvalid), .o_tready(phase_in_round_tready));

  wire [WIDTH_MAG-1:0] magnitude_in_fifo_tdata;
  wire magnitude_in_fifo_tvalid, magnitude_in_fifo_tready;
  axi_fifo #(.WIDTH(WIDTH_MAG), .SIZE(8)) axi_fifo_short_magnitude_in (
    .clk(clk), .reset(reset), .clear(),
    .i_tdata(magnitude_in_tdata), .i_tvalid(magnitude_in_tvalid), .i_tready(magnitude_in_tready),
    .o_tdata(magnitude_in_fifo_tdata), .o_tvalid(magnitude_in_fifo_tvalid), .o_tready(magnitude_in_fifo_tready),
    .occupied(), .space());

  wire [15:0] gain_div_out, gain_frac_div_out;
  wire [30:0] gain_tdata = {gain_div_out,gain_frac_div_out[14:0]};
  wire gain_tvalid, gain_tready;
  divide_int16 divide_gain (
    .aclk(clk), .aresetn(~reset),
    .s_axis_divisor_tdata(magnitude_in_fifo_tdata), .s_axis_divisor_tlast(1'b0), .s_axis_divisor_tvalid(magnitude_in_fifo_tvalid), .s_axis_divisor_tready(magnitude_in_fifo_tready),
    .s_axis_dividend_tdata(agc_reference_level), .s_axis_dividend_tlast(1'b0), .s_axis_dividend_tvalid(1'b1), .s_axis_dividend_tready(),
    .m_axis_dout_tdata({gain_div_out, gain_frac_div_out}), .m_axis_dout_tlast(), .m_axis_dout_tvalid(gain_tvalid), .m_axis_dout_tready(gain_tready),
    .m_axis_dout_tuser());

  // Moving average of gain
  wire [$clog2(PLATEAU_WIDTH)+30:0] gain_sum_tdata;
  wire gain_sum_tvalid, gain_sum_tready;
  moving_sum #(.MAX_LEN(PLATEAU_WIDTH), .WIDTH(31)) moving_sum_gain (
    .clk(clk), .reset(reset), .clear(clear),
    .len(PLATEAU_WIDTH),
    .i_tdata(gain_tdata), .i_tlast(1'b0), .i_tvalid(gain_tvalid), .i_tready(gain_tready),
    .o_tdata(gain_sum_tdata), .o_tlast(), .o_tvalid(gain_sum_tvalid), .o_tready(gain_sum_tready));

  localparam GAIN_NUM_INTEGER_BITS = 8;
  wire [15:0] gain_round_tdata;
  wire        gain_round_tvalid, gain_round_tready;
  axi_round_and_clip #(
    .WIDTH_IN($clog2(PLATEAU_WIDTH)+31),
    .WIDTH_OUT(16),
    .CLIP_BITS(GAIN_NUM_INTEGER_BITS))
  round_gain (
    .clk(clk), .reset(reset),
    .i_tdata(gain_sum_tdata), .i_tlast(1'b0), .i_tvalid(gain_sum_tvalid), .i_tready(gain_sum_tready),
    .o_tdata(gain_round_tdata), .o_tlast(), .o_tvalid(gain_round_tvalid), .o_tready(gain_round_tready));

  /****************************************************************************
  ** Delay & gate sample data
  ****************************************************************************/
  localparam SAMPLE_DELAY = 511;
  wire consume;
  reg trigger;

  wire [2*WIDTH_SAMPLE-1:0] sample_dly_tdata;
  wire sample_dly_tvalid, sample_dly_tready;
  delay_fifo #(.MAX_LEN(SAMPLE_DELAY), .WIDTH(2*WIDTH_SAMPLE)) delay_samples (
    .clk(clk), .reset(reset), .clear(),
    .len(SAMPLE_DELAY),
    .i_tdata(sample_in_tdata), .i_tlast(1'b0), .i_tvalid(sample_in_tvalid), .i_tready(sample_in_tready),
    .o_tdata(sample_dly_tdata), .o_tlast(), .o_tvalid(sample_dly_tvalid), .o_tready(consume));

  wire [2*WIDTH_SAMPLE-1:0] sample_gate_tdata;
  wire sample_gate_tlast, sample_gate_tvalid, sample_gate_tready;
  axi_fifo #(.WIDTH(2*WIDTH_SAMPLE+1), .SIZE(8)) axi_fifo_short_sample_gate (
    .clk(clk), .reset(reset), .clear(),
    .i_tdata({trigger,sample_dly_tdata}), .i_tvalid(consume), .i_tready(sample_dly_tready),
    .o_tdata({sample_gate_tlast, sample_gate_tdata}), .o_tvalid(sample_gate_tvalid), .o_tready(sample_gate_tready),
    .occupied(), .space());

  /****************************************************************************
  ** AGC
  ****************************************************************************/
  reg  [15:0] max_gain, max_gain_hold;
  wire [2*WIDTH_SAMPLE-1:0] sample_agc_tdata;
  wire sample_agc_tlast, sample_agc_tvalid, sample_agc_tready;
  multiply #(
    .WIDTH_A(WIDTH_SAMPLE),
    .WIDTH_B(WIDTH_SAMPLE),
    .WIDTH_P(WIDTH_SAMPLE),
    .DROP_TOP_P(GAIN_NUM_INTEGER_BITS),
    .LATENCY(2),
    .EN_SATURATE(1),
    .EN_ROUND(1))
  multiply_agc_i (
    .clk(clk), .reset(reset),
    .a_tdata(sample_gate_tdata[2*WIDTH_SAMPLE-1:WIDTH_SAMPLE]), .a_tlast(sample_gate_tlast), .a_tvalid(sample_gate_tvalid), .a_tready(sample_gate_tready),
    .b_tdata(max_gain_hold), .b_tlast(1'b0), .b_tvalid(1'b1), .b_tready(),
    .p_tdata(sample_agc_tdata[2*WIDTH_SAMPLE-1:WIDTH_SAMPLE]), .p_tlast(sample_agc_tlast), .p_tvalid(sample_agc_tvalid), .p_tready(sample_agc_tready));

  multiply #(
    .WIDTH_A(WIDTH_SAMPLE),
    .WIDTH_B(WIDTH_SAMPLE),
    .WIDTH_P(WIDTH_SAMPLE),
    .DROP_TOP_P(GAIN_NUM_INTEGER_BITS),
    .LATENCY(2),
    .EN_SATURATE(1),
    .EN_ROUND(1))
  multiply_agc_q (
    .clk(clk), .reset(reset),
    .a_tdata(sample_gate_tdata[WIDTH_SAMPLE-1:0]), .a_tlast(sample_gate_tlast), .a_tvalid(sample_gate_tvalid), .a_tready(),
    .b_tdata(max_gain_hold), .b_tlast(1'b0), .b_tvalid(1'b1), .b_tready(),
    .p_tdata(sample_agc_tdata[WIDTH_SAMPLE-1:0]), .p_tlast(), .p_tvalid(), .p_tready(sample_agc_tready));

  /****************************************************************************
  ** Use sample out stream to pace phase output stream
  ****************************************************************************/
  split_stream_fifo #(.WIDTH(32), .ACTIVE_MASK(4'b0011), .FIFO_SIZE(8)) split_stream_fifo (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata(sample_agc_tdata), .i_tlast(sample_agc_tlast), .i_tvalid(sample_agc_tvalid), .i_tready(sample_agc_tready),
    .o0_tdata(sample_out_tdata), .o0_tlast(sample_out_tlast), .o0_tvalid(sample_out_tvalid), .o0_tready(sample_out_tready),
    .o1_tdata(), .o1_tlast(phase_out_tlast), .o1_tvalid(phase_out_tvalid), .o1_tready(phase_out_tready),
    .o2_tdata(), .o2_tlast(), .o2_tvalid(), .o2_tready(),
    .o3_tdata(), .o3_tlast(), .o3_tvalid(), .o3_tready());

  reg [WIDTH_PHASE-1:0] max_phase, max_phase_hold;
  assign phase_out_tdata = ~max_phase_hold + 1;

  /****************************************************************************
  ** Average Plateaus
  ****************************************************************************/
  wire [WIDTH_D-1:0] d_metric_fifo_tdata;
  wire d_metric_fifo_tvalid, d_metric_fifo_tready;
  axi_fifo #(.WIDTH(WIDTH_D), .SIZE(8)) axi_fifo_short_d_metric (
    .clk(clk), .reset(reset), .clear(),
    .i_tdata(d_metric_tdata), .i_tvalid(d_metric_tvalid), .i_tready(d_metric_tready),
    .o_tdata(d_metric_fifo_tdata), .o_tvalid(d_metric_fifo_tvalid), .o_tready(d_metric_fifo_tready),
    .occupied(), .space());

  // d metric moving average
  wire [WIDTH_D+$clog2(PLATEAU_WIDTH)-1:0] d_metric_sum;
  wire [WIDTH_D-1:0] d_metric_avg_tdata = d_metric_sum[20:$clog2(PLATEAU_WIDTH)]; // Truncate to average
  wire d_metric_avg_tvalid, d_metric_avg_tready;
  moving_sum #(.MAX_LEN(PLATEAU_WIDTH), .WIDTH(16)) moving_sum_d_metric (
    .clk(clk), .reset(reset), .clear(),
    .len(PLATEAU_WIDTH),
    .i_tdata(d_metric_fifo_tdata), .i_tlast(1'b0), .i_tvalid(d_metric_fifo_tvalid), .i_tready(d_metric_fifo_tready),
    .o_tdata(d_metric_sum), .o_tlast(), .o_tvalid(d_metric_avg_tvalid), .o_tready(d_metric_avg_tready));

  wire threshold_exceeded = d_metric_avg_tdata > threshold;

  /****************************************************************************
  ** Algorithm State Machine
  ****************************************************************************/
  reg [2:0] state;
  localparam S_WAIT_EXCEED_THRESH   = 3'd0;
  localparam S_FIND_FIRST_PEAK      = 3'd1;
  localparam S_WAIT_EXCEED_THRESH2  = 3'd2;
  localparam S_FIND_SECOND_PEAK     = 3'd3;
  localparam S_ALIGN_OUTPUT         = 3'd4;
  localparam S_WAIT_BELOW_THRESH    = 3'd5;

  reg  [$clog2(SAMPLE_DELAY)-1:0] peak_offset, trigger_cnt;

  reg  [WIDTH_D-1:0] max_d_metric;
  wire [WIDTH_D-1:0] max_d_metric_87_5 = max_d_metric - max_d_metric[WIDTH_D-1:3]; // 87.5%

  assign consume = sample_dly_tready & sample_dly_tvalid & d_metric_avg_tvalid & phase_in_round_tvalid & gain_round_tvalid;
  assign d_metric_avg_tready      = consume;
  assign phase_in_round_tready    = consume;
  assign gain_round_tready        = consume;

  always @(posedge clk) begin
    if (reset) begin
      trigger_cnt   <= 0;
      peak_offset   <= 0;
      max_d_metric  <= 'd0;
      max_phase     <= 'd0;
      max_gain      <= 'd0;
      max_phase_hold <= 'd0;
      max_gain_hold  <= 'd0;
      trigger       <= 1'b0;
      state         <= S_WAIT_EXCEED_THRESH;
    end else begin
      if (consume) begin
        case(state)
          // Wait for threshold to be exceeded
          S_WAIT_EXCEED_THRESH : begin
            trigger_cnt   <= 0;
            peak_offset   <= 0;
            max_d_metric  <= 'd0;
            trigger       <= 1'b0;
            if (threshold_exceeded) begin
              max_d_metric <= d_metric_avg_tdata;
              max_phase    <= phase_in_round_tdata >> 6;
              max_gain     <= gain_round_tdata;
              state        <= S_FIND_FIRST_PEAK;
            end
          end

          S_FIND_FIRST_PEAK : begin
            if (d_metric_avg_tdata > max_d_metric) begin
              max_d_metric  <= d_metric_avg_tdata;
              max_phase     <= phase_in_round_tdata >> 6;
              max_gain      <= gain_round_tdata;
              peak_offset   <= 0;
            end else begin
              peak_offset   <= peak_offset + 1;
            end
            if (d_metric_avg_tdata < max_d_metric_87_5) begin
              max_d_metric   <= 'd0;
              state          <= S_WAIT_EXCEED_THRESH2;
            // Avoid triggering on tones
            end else if (peak_offset > PREAMBLE_LEN/2) begin
              state         <= S_WAIT_BELOW_THRESH;
            end
          end

          S_WAIT_EXCEED_THRESH2 : begin
            peak_offset   <= peak_offset + 1;
            if (threshold_exceeded) begin
              max_d_metric <= d_metric_avg_tdata;
              state        <= S_FIND_SECOND_PEAK;
            end
            if (peak_offset > PREAMBLE_LEN) begin
              state         <= S_WAIT_BELOW_THRESH;
            end
          end

          S_FIND_SECOND_PEAK : begin
            if (d_metric_avg_tdata > max_d_metric) begin
              max_d_metric  <= d_metric_avg_tdata;
            end
            peak_offset   <= peak_offset + 1;
            if (d_metric_avg_tdata < max_d_metric_87_5) begin
              state         <= S_ALIGN_OUTPUT;
            // Avoid triggering on tones
            end else if (peak_offset > 2*PREAMBLE_LEN) begin
              state         <= S_WAIT_BELOW_THRESH;
            end
          end

          S_ALIGN_OUTPUT : begin
            trigger_cnt  <= trigger_cnt + 1;
            // Extra -3 to account for off by one for trigger_cnt, peak_offset, etc
            if (trigger_cnt > SAMPLE_DELAY-PREAMBLE_LEN-peak_offset-4) begin
              max_phase_hold <= max_phase;
              max_gain_hold  <= max_gain;
              trigger    <= 1'b1;
              state      <= S_WAIT_BELOW_THRESH;
            end
          end

          S_WAIT_BELOW_THRESH : begin
            trigger <= 1'b0;
            if (~threshold_exceeded) begin
              state <= S_WAIT_EXCEED_THRESH;
            end
          end

          default : state <= S_WAIT_EXCEED_THRESH;
        endcase
      end
    end
  end

endmodule