GCC Code Coverage Report
Directory: generated/vunit_out/preprocessed/ Exec Total Coverage
File: generated/vunit_out/preprocessed/common/width_conversion.vhd Lines: 109 109 100.0 %
Date: 2021-06-12 04:12:08 Branches: 249 372 66.9 %

Line Branch Exec Source
1
84
-- -------------------------------------------------------------------------------------------------
2
-- Copyright (c) Lukas Vik. All rights reserved.
3
--
4
-- This file is part of the tsfpga project.
5
-- https://tsfpga.com
6
-- https://gitlab.com/tsfpga/tsfpga
7
-- -------------------------------------------------------------------------------------------------
8
-- Width conversion of a data bus. Can handle downconversion (wide to thin) or upconversion (thin
9
-- to wide). The data widths must be a power-of-two multiple of each other. E.g. 4->16 is
10
-- supported while 8->24 is not.
11
--
12
-- There is a generic to enable strobing of data. The data and strobe will be passed on from
13
-- 'input' to 'output' side as is. This means that there might be output words where all strobe
14
-- lanes are zero.
15
--
16
-- We have done some experimentation with removing words that are strobed out, so that they never
17
-- reach the 'output' side. See comments in code. It increases the resource utilization by a lot,
18
-- and it is not super clear if it is correct behavior for the common use case.
19
--
20
-- When upconverting, the 'input' side burst length must align with the 'output' side data width.
21
-- If it is not, then the 'input' stream must be padded.
22
-- Consider the example when converting 32->64, and 'last' is asserted in the third 'input' word.
23
-- Unless the 'support_unaligned_burst_length' generic is set there will still only be one word sent
24
-- to the 'output'. If the generic is set, however, the 'input' stream will be padded so that a
25
-- whole 'output' word is filled. The padded lanes will have their 'strobe' set to zero.
26
-- -------------------------------------------------------------------------------------------------
27
28
library ieee;
29
use ieee.std_logic_1164.all;
30
use ieee.numeric_std.all;
31
32
use work.types_pkg.all;
33
34
35



1242
entity width_conversion is
36
  generic (
37
    input_width : positive;
38
    output_width : positive;
39
    -- Enable usage of the 'input_strobe' and 'output_strobe' ports.
40
    -- Will increase the logic footprint.
41
    enable_strobe : boolean := false;
42
    -- In the typical use case where we want a "byte strobe", this would be set to 8.
43
    -- In other cases, for example when the data is packed, we migh use a higher value.
44
    -- Must assign a positive value if 'enable_strobe' is true.
45
    strobe_unit_width : integer := -1;
46
    -- Enable if 'input' burst lengths are not a multiple of the 'output' width.
47
    -- Will increase the logic footprint.
48
    support_unaligned_burst_length : boolean := false
49
  );
50
  port (
51
    clk : in std_logic;
52
    --
53
    input_ready : out std_logic := '1';
54
    input_valid : in std_logic;
55
    input_last : in std_logic;
56
    input_data : in std_logic_vector(input_width - 1 downto 0);
57
    -- Optional word strobe. Must set 'enable_strobe' generic in order to use this.
58
    input_strobe : in std_logic_vector(input_width / strobe_unit_width - 1 downto 0) :=
59
      (others => '1');
60
    --
61
    output_ready : in std_logic;
62
    output_valid : out std_logic := '0';
63
    output_last : out std_logic;
64
    output_data : out std_logic_vector(output_width - 1 downto 0);
65
    -- Optional word strobe. Must set 'enable_strobe' generic in order to use this.
66
    output_strobe : out std_logic_vector(output_width / strobe_unit_width - 1 downto 0) :=
67
      (others => '1')
68
  );
69
end entity;
70
71
architecture a of width_conversion is
72
73
  function get_atom_width return positive is
74
  begin
75
14
    if enable_strobe then
76
6
      assert strobe_unit_width > 0
77
        report "Must set a valid strobe width when strobing is enabled."
78
        severity failure;
79
6
      return strobe_unit_width;
80
    end if;
81
82
    -- When converting e.g. 16->32 the data atom that is handled internally will be of width 16.
83
    -- This gives lower resource utilization than if it was e.g. always 8.
84
95949
    return minimum(input_width, output_width);
85
  end function;
86
14
  constant atom_width : positive := get_atom_width;
87
  subtype atom_range is natural range atom_width - 1 downto 0;
88
89
14
  constant num_atoms_per_input : positive := input_width / atom_width;
90
14
  constant num_atoms_per_output : positive := output_width / atom_width;
91
92
  -- +1 for last
93
14
  constant packed_atom_width : positive := atom_width + 1 + to_int(enable_strobe);
94
14
  constant stored_atom_count_max : positive := num_atoms_per_input + num_atoms_per_output;
95
96
14
  constant shift_reg_length : positive := stored_atom_count_max * packed_atom_width;
97
1174
  signal shift_reg : std_logic_vector(shift_reg_length - 1 downto 0) := (others => '0');
98
99
14
  signal num_atoms_stored : natural range 0 to stored_atom_count_max := 0;
100
101
  impure function pack(
102
    atom_data : std_logic_vector(atom_range);
103
    atom_strobe : std_logic;
104
    atom_last : std_logic
105
  ) return std_logic_vector is
106

1061760
    variable result : std_logic_vector(packed_atom_width - 1 downto 0) := (others => '0');
107
  begin
108


95941
    result(atom_data'range) := atom_data;
109
110
95941
    if enable_strobe then
111

51126
      result(result'high - 1) := atom_strobe;
112
    end if;
113
114
95941
    result(result'high) := atom_last;
115
116
95941
    return result;
117
  end function;
118
119
278928
  procedure unpack(
120
    packed : in std_logic_vector(packed_atom_width - 1 downto 0);
121
    atom_data : out std_logic_vector(atom_range);
122
    atom_strobe : out std_logic;
123
    atom_last : out std_logic
124
  ) is
125
  begin
126


139464
    atom_data := packed(atom_data'range);
127
128
139464
    if enable_strobe then
129

73994
      atom_strobe := packed(packed'high - 1);
130
    end if;
131
132
278928
    atom_last := packed(packed'high);
133
  end procedure;
134
135
14
  signal padded_input_ready, padded_input_valid, padded_input_last : std_logic := '0';
136
510
  signal padded_input_data : std_logic_vector(input_data'range) := (others => '0');
137
76
  signal padded_input_strobe : std_logic_vector(input_strobe'range) := (others => '0');
138
139
begin
140
141
  ------------------------------------------------------------------------------
142
28
  assert input_width /= output_width
143
    report "Do not use this module with equal widths" severity failure;
144
145

28
  assert input_width mod output_width = 0 or output_width mod input_width = 0
146
    report "Larger width has to be multiple of smaller." severity failure;
147
148

28
  assert (output_width / input_width) mod 2 = 0 and (input_width / output_width) mod 2 = 0
149
    report "Larger width has to be power of two multiple of smaller." severity failure;
150
151

28
  assert strobe_unit_width > 0 or not enable_strobe
152
    report "Must set a valid strobe width when strobing is enabled." severity failure;
153
154

28
  assert input_width < output_width or not support_unaligned_burst_length
155
    report "Unaligned burst length only makes sense when upconverting." severity failure;
156
157

28
  assert enable_strobe or not support_unaligned_burst_length
158
    report "Must enable strobing when doing unaligned bursts." severity failure;
159
160
161
  ------------------------------------------------------------------------------
162
  pad_input_data_generate : if support_unaligned_burst_length generate
163
164
    type state_t is (let_data_pass, send_padding);
165
3
    signal state : state_t := let_data_pass;
166
167
3
    constant input_beats_per_output_beat : natural := output_width / input_width;
168
14
    signal output_words_filled : natural range 0 to input_beats_per_output_beat := 0;
169
170
  begin
171
172
    ------------------------------------------------------------------------------
173
75
    assign : process(all)
174
    begin
175


276785
      padded_input_data <= input_data;
176

25089
      padded_input_last <= input_last;
177
178
25089
      if state = let_data_pass then
179
180
        -- Passthrough.
181

25040
        input_ready <= padded_input_ready;
182

25040
        padded_input_valid <= input_valid;
183


56404
        padded_input_strobe <= input_strobe;
184
185
      else -- send_padding
186
187

49
        input_ready <= '0';
188

49
        padded_input_valid <= '1';
189



107729
        padded_input_strobe <= (others => '0');
190
191
      end if;
192
    end process;
193
194
195
    ------------------------------------------------------------------------------
196
3
    pad_input_data : process
197
    begin
198

107484
      wait until rising_edge(clk) and support_unaligned_burst_length;
199
200
26871
      if padded_input_ready and padded_input_valid then
201
17168
        if output_words_filled = input_beats_per_output_beat then
202

6982
          output_words_filled <= 1;
203
        else
204

10186
          output_words_filled <= output_words_filled + 1;
205
        end if;
206
      end if;
207
208
26871
      case state is
209
        when let_data_pass =>
210


26823
          if (
211
            padded_input_ready = '1'
212
            and padded_input_valid = '1'
213
            and padded_input_last = '1'
214
            and output_words_filled /= input_beats_per_output_beat - 1
215
          ) then
216

26823
            state <= send_padding;
217
          end if;
218
219
        when send_padding =>
220
48
          if padded_input_ready and padded_input_valid then
221
47
            if output_words_filled = input_beats_per_output_beat - 1 then
222
              -- This transaction fills the last words out the output.
223

53742
              state <= let_data_pass;
224
            end if;
225
          end if;
226
227
      end case;
228
    end process;
229
230
  else generate
231
232
    -- Passthrough.
233

38585
    input_ready <= padded_input_ready;
234

28942
    padded_input_valid <= input_valid;
235

2222
    padded_input_last <= input_last;
236


688197
    padded_input_data <= input_data;
237



537094
    padded_input_strobe <= input_strobe;
238
239
  end generate;
240
241
242
  ------------------------------------------------------------------------------
243
14
  main : process
244
14
    variable num_atoms_next : natural range 0 to stored_atom_count_max;
245
246
14
    variable atom_strobe, atom_last : std_logic := '0';
247
14
    variable num_atoms_strobed : natural range 0 to num_atoms_per_input := num_atoms_per_input;
248
249
162
    variable packed_data_to_shift_in : std_logic_vector(packed_atom_width - 1 downto 0) :=
250
      (others => '0');
251
1174
    variable shift_reg_next : std_logic_vector(shift_reg'range) := (others => '0');
252
  begin
253

511012
    wait until rising_edge(clk);
254
255
127753
    num_atoms_next := num_atoms_stored;
256
257
127753
    shift_reg_next := shift_reg;
258
127753
    if padded_input_ready and padded_input_valid then
259
58734
      if enable_strobe then
260
26725
        num_atoms_strobed := count_ones(padded_input_strobe);
261
      end if;
262
263
      -- In order to remove words that are strobed out, num_atoms_strobed could be used instead
264
      -- of num_atoms_per_input below. This increases the LUT usage by a factor of four.
265
58734
      num_atoms_next := num_atoms_next + num_atoms_per_input;
266
267
58734
      for input_atom_idx in 0 to num_atoms_per_input - 1 loop
268
95941
        if enable_strobe then
269
          -- When strobing, the atom size is always one strobe unit, so this indexing works.
270
51126
          atom_strobe := padded_input_strobe(input_atom_idx);
271
        end if;
272
273
        -- Set 'last' only on the last strobed atom of the input word.
274
95941
        if input_atom_idx = num_atoms_strobed - 1 then
275
58687
          atom_last := padded_input_last;
276
        else
277
37254
          atom_last := '0';
278
        end if;
279
280


95941
        packed_data_to_shift_in := pack(
281
          atom_data=>padded_input_data(
282
            (input_atom_idx + 1) * atom_width - 1 downto input_atom_idx * atom_width
283
          ),
284
          atom_strobe=>atom_strobe,
285
          atom_last=>atom_last
286
        );
287




260901
        shift_reg_next :=
288
          packed_data_to_shift_in
289
          & shift_reg_next(shift_reg_next'left downto packed_data_to_shift_in'length);
290
      end loop;
291
    end if;
292


5348876
    shift_reg <= shift_reg_next;
293
294
127753
    if output_ready and output_valid then
295
63332
      num_atoms_next := num_atoms_next - num_atoms_per_output;
296
    end if;
297
298

255506
    num_atoms_stored <= num_atoms_next;
299
  end process;
300
301

99770
  padded_input_ready <= to_sl(num_atoms_stored <= stored_atom_count_max - num_atoms_per_input);
302
303
304
  ------------------------------------------------------------------------------
305
14
  slice_output : process(all)
306
14
    variable output_atoms_base : natural range 0 to stored_atom_count_max := 0;
307
308
162
    variable packed_atom : std_logic_vector(packed_atom_width - 1 downto 0) := (others => '0');
309
310
142
    variable atom_data : std_logic_vector(atom_width - 1 downto 0) := (others => '0');
311
1177
    variable atom_strobe, atom_last : std_logic_vector(num_atoms_per_output - 1 downto 0) :=
312
      (others => '0');
313
  begin
314

99756
    output_valid <= to_sl(num_atoms_stored >= num_atoms_per_output);
315
316
99756
    output_atoms_base := stored_atom_count_max - num_atoms_stored;
317
318
99756
    for output_atom_idx in 0 to num_atoms_per_output - 1 loop
319
191057
      if output_atom_idx < num_atoms_stored then
320


139464
        packed_atom := shift_reg(
321
          (output_atoms_base + output_atom_idx + 1) * packed_atom_width - 1
322
          downto (output_atoms_base + output_atom_idx) * packed_atom_width
323
        );
324
325


139464
        unpack(
326
          packed=>packed_atom,
327
          atom_data=>atom_data,
328
          atom_strobe=>atom_strobe(output_atom_idx),
329
          atom_last=>atom_last(output_atom_idx)
330
        );
331
332




1325424
        output_data((output_atom_idx + 1) * atom_width - 1 downto output_atom_idx * atom_width) <=
333
          atom_data;
334
      else
335
336
        -- This is just so that the indexing does not go out of range. When the condition for
337
        -- output_valid is met, we will not end up here for any atom.
338



925993
        output_data((output_atom_idx + 1) * atom_width - 1 downto output_atom_idx * atom_width) <=
339
          (others => '-');
340
51593
        atom_strobe(output_atom_idx) := '-';
341

191057
        atom_last(output_atom_idx) := '-';
342
343
      end if;
344
    end loop;
345
346
99756
    if enable_strobe then
347
      -- The top atome might be strobed out and not have 'last' set. Instead it is found in one of
348
      -- the lower atoms.
349

49090
      output_last <= or atom_last;
350


149927
      output_strobe <= atom_strobe;
351
    else
352

50680
      output_last <= atom_last(atom_last'high);
353
    end if;
354
  end process;
355
356
end architecture;