1//
2// Copyright 2018 Ettus Research, A National Instruments Company
3//
4// SPDX-License-Identifier: LGPL-3.0-or-later
5//
6// Module: variable_delay_line
7// Description:
8//   This module implements a variable length delay line. It can be used
9//   in filter implementation where the delay is either variable and/or
10//   longer than a few flip-flops
11//
12// Parameters:
13//   - WIDTH: Width of data_in and data_out
14//   - DYNAMIC_DELAY: Is the delay variable (configurable at runtime)
15//   - DEPTH: The depth of the delay line. Must be greater than 2.
16//            The output delay can be between 0 and DEPTH-1.
17//            If DYNAMIC_DELAY==0, then this is the static delay
18//   - DEFAULT_DATA: Data to output if time post-delay is negative
19//   - OUT_REG: Add an output register. This adds a cycle of latency
20//   - DEVICE: FPGA device family
21// Signals:
22//   - data_in  : Input sample value
23//   - stb_in   : Is input sample valid?
24//   - delay    : Delay value for output (Must be between 0 and DEPTH-1)
25//   - data_out : Output sample value. data_out is updated 1 clock
26//                cycle (2 if OUT_REG == 1) after assertion of delay
27//
28
29module variable_delay_line #(
30  parameter             WIDTH         = 18,
31  parameter             DEPTH         = 256,
32  parameter             DYNAMIC_DELAY = 0,
33  parameter [WIDTH-1:0] DEFAULT_DATA  = 0,
34  parameter             OUT_REG       = 0,
35  parameter             DEVICE        = "7SERIES"
36) (
37  input  wire                     clk,
38  input  wire                     clk_en,
39  input  wire                     reset,
40  input  wire [WIDTH-1:0]         data_in,
41  input  wire                     stb_in,
42  input  wire [$clog2(DEPTH)-1:0] delay,
43  output wire [WIDTH-1:0]         data_out
44);
45  localparam ADDR_W = $clog2(DEPTH+1);
46  localparam DATA_W = WIDTH;
47
48  //-----------------------------------------------------------
49  // RAM State Machine: FIFO write, random access read
50  //-----------------------------------------------------------
51  wire              w_en;
52  wire [DATA_W-1:0] r_data, w_data;
53  wire [ADDR_W-1:0] r_addr;
54  reg  [ADDR_W-1:0] w_addr = {ADDR_W{1'b0}}, occupied = {ADDR_W{1'b0}};
55  reg  [1:0]        use_default = 2'b11;
56
57  // FIFO write, random access read
58  always @(posedge clk) begin
59    if (reset) begin
60      w_addr <= {ADDR_W{1'b0}};
61      occupied <= {ADDR_W{1'b0}};
62    end else if (w_en) begin
63      w_addr <= w_addr + 1'b1;
64      if (occupied != DEPTH) begin
65        occupied <= occupied + 1'b1;
66      end
67    end
68  end
69
70  // Logic to handle negative delays
71  always @(posedge clk) begin
72    if (reset) begin
73      use_default <= 2'b11;
74    end else if (clk_en && (occupied != 0)) begin
75      use_default <= {use_default[0], (r_addr >= occupied ? 1'b1 : 1'b0)};
76    end
77  end
78
79  assign w_en     = stb_in & clk_en;
80  assign w_data   = data_in;
81  assign r_addr   = (DYNAMIC_DELAY == 0) ? DEPTH : delay;
82  assign data_out = use_default[OUT_REG] ? DEFAULT_DATA : r_data;
83
84  //-----------------------------------------------------------
85  // Delay Line RAM Implementation
86  //-----------------------------------------------------------
87  // Use a delay line implementation based on the depth.
88  // The DEVICE parameter is passed in but SPARTAN6,
89  // 7Series, Ultrascale and Ultrascale+ have the same
90  // MACROs for SRLs so we don't use the param quite yet.
91
92  genvar i;
93  generate
94    if (ADDR_W == 4 || ADDR_W == 5) begin
95      // SRLs don't have an output register to instantiate
96      // that plus the pipeline register manually
97      wire [DATA_W-1:0] r_data_srl;
98      reg  [DATA_W-1:0] r_data_shreg[0:1];
99      always @(posedge clk) begin
100        if (clk_en)
101          {r_data_shreg[1], r_data_shreg[0]} <= {r_data_shreg[0], r_data_srl};
102      end
103      assign r_data = r_data_shreg[OUT_REG];
104
105      for (i = 0; i < DATA_W; i = i + 1) begin: bits
106        // Pick SRL based on address width
107        if (ADDR_W == 4) begin
108          SRL16E #(
109            .INIT(16'h0000), .IS_CLK_INVERTED(1'b0)
110          ) srl16e_i (
111            .CLK(clk), .CE(w_en),
112            .D(w_data[i]),
113            .A0(r_addr[0]),.A1(r_addr[1]),.A2(r_addr[2]),.A3(r_addr[3]),
114            .Q(r_data_srl[i])
115          );
116        end else begin
117          SRLC32E #(
118            .INIT(32'h00000000), .IS_CLK_INVERTED(1'b0)
119          ) srlc32e_i (
120            .CLK(clk), .CE(w_en),
121            .D(w_data[i]),
122            .A(r_addr),
123            .Q(r_data_srl[i]), .Q31()
124         );
125        end
126      end
127    end else begin
128      // For ADDR_W < 4, the RAM should ideally get
129      // synthesized down to flip-flops.
130      ram_2port #(
131        .DWIDTH (DATA_W), .AWIDTH(ADDR_W),
132        .RW_MODE("NO-CHANGE"), .OUT_REG(OUT_REG)
133      ) ram_i (
134        .clka (clk), .ena(clk_en), .wea(w_en),
135        .addra(w_addr), .dia(w_data), .doa(),
136        .clkb (clk), .enb(clk_en), .web(1'b0),
137        .addrb(w_addr - r_addr - 1), .dib(), .dob(r_data)
138      );
139    end
140  endgenerate
141
142endmodule // delay_line