Source file async_fifo.ml

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
332
333
334
335
336
337
338
339
340
open Base
open Signal

let gray_inc_mux_arg bits =
  List.init (1 lsl bits) ~f:(fun i ->
    let i = Bits.of_int ~width:bits i in
    Bits.(binary_to_gray i, binary_to_gray (i +:. 1)))
;;

let%expect_test "binary_to_gray" =
  let to_str x = Bits.to_string x in
  List.iter (gray_inc_mux_arg 4) ~f:(fun (i, b) ->
    Stdio.printf "%s -> %s\n" (to_str i) (to_str b));
  [%expect
    {|
    0000 -> 0001
    0001 -> 0011
    0011 -> 0010
    0010 -> 0110
    0110 -> 0111
    0111 -> 0101
    0101 -> 0100
    0100 -> 1100
    1100 -> 1101
    1101 -> 1111
    1111 -> 1110
    1110 -> 1010
    1010 -> 1011
    1011 -> 1001
    1001 -> 1000
    1000 -> 0000
    |}]
;;

let gray_inc bits =
  let gray_inc_mux_arg =
    gray_inc_mux_arg bits
    |> List.sort ~compare:(fun (a, _) (b, _) -> Bits.compare a b)
    |> List.map ~f:snd
    |> List.map ~f:(fun x -> Signal.of_constant (Bits.to_constant x))
  in
  fun x -> mux x gray_inc_mux_arg
;;

module type S = sig
  val width : int
  val log2_depth : int
end

module Make (M : sig
  val width : int
  val log2_depth : int
end) =
struct
  let address_width = M.log2_depth
  let fifo_capacity = 1 lsl address_width
  let gray_inc = gray_inc address_width

  module I = struct
    type 'a t =
      { clock_write : 'a
      ; clock_read : 'a
      ; reset_write : 'a
      ; reset_read : 'a
      ; data_in : 'a [@bits M.width]
      ; write_enable : 'a
      ; read_enable : 'a
      }
    [@@deriving hardcaml]
  end

  module O = struct
    type 'a t =
      { full : 'a
      ; data_out : 'a [@bits M.width]
      ; valid : 'a
      ; almost_empty : 'a
      }
    [@@deriving hardcaml]
  end

  module Async_distributed_ram = struct
    type t =
      { clock_write : Signal.t
      ; multiport_mem : Signal.t
      ; mutable is_written : bool
      ; mutable is_read : bool
      ; write_enable : Always.Variable.t
      ; write_address : Signal.t
      ; write_data : Signal.t
      ; read_address : Signal.t
      }

    let create ~name ~clock_write =
      let write_data = Signal.wire M.width in
      let write_address = Signal.wire address_width in
      let read_address = Signal.wire address_width in
      let write_enable = Always.Variable.wire ~default:Signal.gnd in
      let multiport_mem =
        Signal.multiport_memory
          ~name
          ~attributes:[ Rtl_attribute.Vivado.Ram_style.distributed ]
          ~write_ports:
            [| { write_clock = clock_write
               ; write_data
               ; write_enable = write_enable.value
               ; write_address
               }
            |]
          ~read_addresses:[| read_address |]
          fifo_capacity
      in
      let multiport_mem = multiport_mem.(0) in
      { clock_write
      ; write_address
      ; write_enable
      ; write_data
      ; read_address
      ; multiport_mem
      ; is_written = false
      ; is_read = false
      }
    ;;

    let read t ~address =
      assert (width address = address_width);
      if t.is_read then raise_s [%message "Async_ram has already previously been read"];
      t.is_read <- true;
      t.read_address <== address;
      t.multiport_mem
    ;;

    let write t ~address ~data =
      assert (width address = address_width);
      assert (width data = M.width);
      if t.is_written
      then raise_s [%message "Async_ram has already previously been written"];
      t.is_written <- true;
      t.write_address <== address;
      t.write_data <== data;
      Always.(t.write_enable <--. 1)
    ;;
  end

  let create ?(use_negedge_sync_chain = false) ?(sync_stages = 2) ?scope (i : _ I.t) =
    if sync_stages < 2
    then raise_s [%message "[sync_stages] must be >= 2!" (sync_stages : int)];
    if use_negedge_sync_chain && sync_stages % 2 = 1
    then
      raise_s
        [%message "[sync_stages] must be even when using negedge!" (sync_stages : int)];
    let ( -- ) =
      match scope with
      | Some scope -> Scope.naming scope
      | None -> ( -- )
    in
    let async_reg_var ?clock_edge () ~name ~clock ~reset ~width =
      let spec = Reg_spec.create ~clock ~reset () |> Reg_spec.override ?clock_edge in
      let var = Always.Variable.reg spec ~enable:vdd ~width in
      ignore
        (Signal.add_attribute var.value (Rtl_attribute.Vivado.async_reg true) -- name
          : Signal.t);
      var
    in
    let reg_var ~name ~clock ~reset ~width =
      let var =
        Always.Variable.reg (Reg_spec.create ~clock ~reset ()) ~enable:vdd ~width
      in
      ignore (var.value -- name : Signal.t);
      var
    in
    let waddr_rd =
      async_reg_var
        ()
        ~clock:i.clock_read
        ~reset:i.reset_read
        ~width:address_width
        ~name:"waddr_rd"
    in
    let clock_edge_of_sync_chain idx =
      if use_negedge_sync_chain
      then if idx % 2 = 0 then Edge.Falling else Rising
      else Rising
    in
    let waddr_rd_ffs =
      Array.init (sync_stages - 1) ~f:(fun idx ->
        async_reg_var
          ~clock_edge:(clock_edge_of_sync_chain idx)
          ()
          ~name:[%string "waddr_rd_ff_%{idx#Int}"]
          ~clock:i.clock_read
          ~reset:i.reset_read
          ~width:address_width)
    in
    let raddr_rd =
      reg_var
        ~clock:i.clock_read
        ~reset:i.reset_read
        ~width:address_width
        ~name:"raddr_rd"
    in
    let data_out =
      async_reg_var
        ()
        ~clock:i.clock_read
        ~reset:i.reset_read
        ~width:M.width
        ~name:"data_out"
    in
    let waddr_wd =
      reg_var
        ~clock:i.clock_write
        ~reset:i.reset_write
        ~width:address_width
        ~name:"waddr_wd"
    in
    let raddr_wd_ffs =
      Array.init (sync_stages - 1) ~f:(fun idx ->
        async_reg_var
          ~clock_edge:(clock_edge_of_sync_chain idx)
          ()
          ~name:[%string "raddr_wd_ff_%{idx#Int}"]
          ~clock:i.clock_write
          ~reset:i.reset_write
          ~width:address_width)
    in
    let raddr_wd =
      async_reg_var
        ()
        ~clock:i.clock_write
        ~reset:i.reset_write
        ~width:address_width
        ~name:"raddr_wd"
    in
    let full = gray_inc waddr_wd.value ==: raddr_wd.value in
    let vld = waddr_rd.value <>: raddr_rd.value in
    let almost_empty =
      waddr_rd.value
      ==: raddr_rd.value
      |: (waddr_rd.value ==: gray_inc raddr_rd.value)
      |: (waddr_rd.value ==: gray_inc (gray_inc raddr_rd.value))
    in
    let ram =
      Async_distributed_ram.create
        ~name:(Option.value_map scope ~default:"ram" ~f:(fun s -> Scope.name s "ram"))
        ~clock_write:i.clock_write
    in
    let raddr_rd_next =
      mux2 (i.read_enable &: vld) (gray_inc raddr_rd.value) raddr_rd.value
    in
    Always.(
      compile
        [ (* @(posedge clk_read) *)
          Array.init (Array.length waddr_rd_ffs) ~f:(fun idx ->
            if idx = 0
            then waddr_rd_ffs.(idx) <-- waddr_wd.value
            else waddr_rd_ffs.(idx) <-- waddr_rd_ffs.(idx - 1).value)
          |> Array.to_list
          |> proc
        ; waddr_rd <-- (Array.last waddr_rd_ffs).value
        ; (* @(posedge clk_write) *)
          Array.init (Array.length raddr_wd_ffs) ~f:(fun idx ->
            if idx = 0
            then raddr_wd_ffs.(idx) <-- raddr_rd.value
            else raddr_wd_ffs.(idx) <-- raddr_wd_ffs.(idx - 1).value)
          |> Array.to_list
          |> proc
        ; raddr_wd <-- (Array.last raddr_wd_ffs).value
        ; (* @(posedge clk_write) *)
          when_
            (i.write_enable &: ~:full)
            [ Async_distributed_ram.write ram ~address:waddr_wd.value ~data:i.data_in
            ; waddr_wd <-- gray_inc waddr_wd.value
            ]
        ; (* @(posedge clk_read) *)
          data_out <-- Async_distributed_ram.read ram ~address:raddr_rd_next
        ; (* @(posedge clk_read) *)
          raddr_rd <-- raddr_rd_next
        ]);
    { O.full; data_out = data_out.value; valid = vld; almost_empty }
  ;;

  let create_with_delay ?(delay = 0) scope (i : _ I.t) =
    let ( -- ) = Scope.naming scope in
    let async_fifo_has_valid_value = wire 1 in
    (* If delay is 0 we don't count, otherwise start a counter each time we see valid low
       on the FIFO output, and only set it back to 0 when the FIFO valid goes low again *)
    let delay_val =
      if delay = 0
      then vdd
      else (
        let min_bits = Bits.num_bits_to_represent delay in
        let spec = Reg_spec.create () ~clock:i.clock_read ~reset:i.reset_read in
        let delay_cnt_wire = wire min_bits in
        let delay_cnt =
          reg_fb spec ~enable:vdd ~width:min_bits ~f:(fun d ->
            mux2
              async_fifo_has_valid_value
              (mux2 (delay_cnt_wire ==:. delay) d (d +:. 1))
              (zero min_bits))
        in
        delay_cnt_wire <== delay_cnt;
        delay_cnt ==:. delay)
    in
    let async_fifo =
      create
        ~scope
        { i with
          read_enable = (i.read_enable &: delay_val) -- "read_en"
        ; write_enable = i.write_enable -- "write_en"
        ; data_in = i.data_in -- "data_in"
        }
    in
    async_fifo_has_valid_value <== async_fifo.valid;
    { O.full = async_fifo.full -- "full"
    ; data_out = async_fifo.data_out
    ; valid = async_fifo.valid &: delay_val
    ; almost_empty = async_fifo.almost_empty
    }
  ;;

  (* The tcl scripts that constrain the name of this module depend on the module name
     being [hardcaml_async_fifo*]. *)
  let base_name = "hardcaml_async_fifo"

  let hierarchical ?(name = base_name) ?use_negedge_sync_chain ?sync_stages scope i =
    let module H = Hierarchy.In_scope (I) (O) in
    H.hierarchical
      ~name
      ~scope
      (fun scope -> create ?use_negedge_sync_chain ?sync_stages ~scope)
      i
  ;;

  let hierarchical_with_delay ?(name = [%string "%{base_name}_with_delay"]) ?delay scope i
    =
    let module H = Hierarchy.In_scope (I) (O) in
    H.hierarchical ~name ~scope (create_with_delay ?delay) i
  ;;
end