aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/dsp/variable_delay_line.v
blob: ccef6172f478d99d80e511dc0659f643a8750d31 (plain)
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
//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: variable_delay_line
// Description: 
//   This module implements a variable length delay line. It can be used
//   in filter implementation where the delay is either variable and/or
//   longer than a few flip-flops
//
// Parameters:
//   - WIDTH: Width of data_in and data_out
//   - DYNAMIC_DELAY: Is the delay variable (configurable at runtime)
//   - DEPTH: The depth of the delay line. Must be greater than 2.
//            The output delay can be between 0 and DEPTH-1.
//            If DYNAMIC_DELAY==0, then this is the static delay
//   - DEFAULT_DATA: Data to output if time post-delay is negative 
//   - OUT_REG: Add an output register. This adds a cycle of latency
//   - DEVICE: FPGA device family
// Signals:
//   - data_in  : Input sample value
//   - stb_in   : Is input sample valid?
//   - delay    : Delay value for output (Must be between 0 and DEPTH-1)
//   - data_out : Output sample value. data_out is updated 1 clock
//                cycle (2 if OUT_REG == 1) after assertion of delay
//

module variable_delay_line #(
  parameter             WIDTH         = 18,
  parameter             DEPTH         = 256,
  parameter             DYNAMIC_DELAY = 0,
  parameter [WIDTH-1:0] DEFAULT_DATA  = 0,
  parameter             OUT_REG       = 0,
  parameter             DEVICE        = "7SERIES"
) (
  input  wire                     clk,
  input  wire                     clk_en,
  input  wire                     reset,
  input  wire [WIDTH-1:0]         data_in,
  input  wire                     stb_in,
  input  wire [$clog2(DEPTH)-1:0] delay,
  output wire [WIDTH-1:0]         data_out
);
   //FIXME: Change to localparam when Vivado doesn't freak out
   //       about the use of clog2.
  parameter  ADDR_W = $clog2(DEPTH+1);
  localparam DATA_W = WIDTH;

  //-----------------------------------------------------------
  // RAM State Machine: FIFO write, random access read
  //-----------------------------------------------------------
  wire              w_en;
  wire [DATA_W-1:0] r_data, w_data;
  wire [ADDR_W-1:0] r_addr;
  reg  [ADDR_W-1:0] w_addr = {ADDR_W{1'b0}}, occupied = {ADDR_W{1'b0}};
  reg  [1:0]        use_default = 2'b11;

  // FIFO write, random access read
  always @(posedge clk) begin
    if (reset) begin
      w_addr <= {ADDR_W{1'b0}};
      occupied <= {ADDR_W{1'b0}};
    end else if (w_en) begin
      w_addr <= w_addr + 1'b1;
      if (occupied != DEPTH) begin
        occupied <= occupied + 1'b1;
      end
    end
  end

  // Logic to handle negative delays
  always @(posedge clk) begin
    if (reset) begin
      use_default <= 2'b11;
    end else if (clk_en && (occupied != 0)) begin
      use_default <= {use_default[0], (r_addr >= occupied ? 1'b1 : 1'b0)};
    end
  end

  assign w_en     = stb_in & clk_en;
  assign w_data   = data_in;
  assign r_addr   = (DYNAMIC_DELAY == 0) ? DEPTH : delay;
  assign data_out = use_default[OUT_REG] ? DEFAULT_DATA : r_data;

  //-----------------------------------------------------------
  // Delay Line RAM Implementation
  //-----------------------------------------------------------
  // Use a delay line implementation based on the depth. 
  // The DEVICE parameter is passed in but SPARTAN6,
  // 7Series, Ultrascale and Ultrascale+ have the same 
  // MACROs for SRLs so we don't use the param quite yet.

  genvar i;
  generate
    if (ADDR_W == 4 || ADDR_W == 5) begin
      // SRLs don't have an output register to instantiate
      // that plus the pipeline register manually
      wire [DATA_W-1:0] r_data_srl;
      reg  [DATA_W-1:0] r_data_shreg[0:1];
      always @(posedge clk) begin
        if (clk_en)
          {r_data_shreg[1], r_data_shreg[0]} <= {r_data_shreg[0], r_data_srl};
      end
      assign r_data = r_data_shreg[OUT_REG];

      for (i = 0; i < DATA_W; i = i + 1) begin: bits
        // Pick SRL based on address width
        if (ADDR_W == 4) begin
          SRL16E #(
            .INIT(16'h0000), .IS_CLK_INVERTED(1'b0)
          ) srl16e_i (
            .CLK(clk), .CE(w_en),
            .D(w_data[i]),
            .A0(r_addr[0]),.A1(r_addr[1]),.A2(r_addr[2]),.A3(r_addr[3]),
            .Q(r_data_srl[i])
          );
        end else begin
          SRLC32E #(
            .INIT(32'h00000000), .IS_CLK_INVERTED(1'b0)
          ) srlc32e_i (
            .CLK(clk), .CE(w_en),
            .D(w_data[i]),
            .A(r_addr),
            .Q(r_data_srl[i]), .Q31()
         );
        end
      end
    end else begin
      // For ADDR_W < 4, the RAM should ideally get 
      // synthesized down to flip-flops.
      ram_2port #(
        .DWIDTH (DATA_W), .AWIDTH(ADDR_W),
        .RW_MODE("NO-CHANGE"), .OUT_REG(OUT_REG)
      ) ram_i (
        .clka (clk), .ena(clk_en), .wea(w_en),
        .addra(w_addr), .dia(w_data), .doa(),
        .clkb (clk), .enb(clk_en), .web(1'b0),
        .addrb(w_addr - r_addr - 1), .dib(), .dob(r_data)
      );
    end
  endgenerate

endmodule // delay_line