PandA-2024.02
TestbenchAXIMModuleGenerator.cpp
Go to the documentation of this file.
1 /*
2  *
3  * _/_/_/ _/_/ _/ _/ _/_/_/ _/_/
4  * _/ _/ _/ _/ _/_/ _/ _/ _/ _/ _/
5  * _/_/_/ _/_/_/_/ _/ _/_/ _/ _/ _/_/_/_/
6  * _/ _/ _/ _/ _/ _/ _/ _/ _/
7  * _/ _/ _/ _/ _/ _/_/_/ _/ _/
8  *
9  * ***********************************************
10  * PandA Project
11  * URL: http://panda.dei.polimi.it
12  * Politecnico di Milano - DEIB
13  * System Architectures Group
14  * ***********************************************
15  * Copyright (C) 2023-2024 Politecnico di Milano
16  *
17  * This file is part of the PandA framework.
18  *
19  * The PandA framework is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <http://www.gnu.org/licenses/>.
31  *
32  */
45 
46 #include "Parameter.hpp"
47 #include "behavioral_helper.hpp"
48 #include "function_behavior.hpp"
49 #include "hls_manager.hpp"
50 #include "language_writer.hpp"
51 #include "math_function.hpp"
52 #include "structural_manager.hpp"
53 #include "tree_helper.hpp"
54 
56 {
57 }
58 
60  unsigned int /* function_id */, vertex /* op_v */,
61  const HDLWriter_Language language,
62  const std::vector<ModuleGenerator::parameter>& /* _p */,
63  const std::vector<ModuleGenerator::parameter>& /* _ports_in */,
64  const std::vector<ModuleGenerator::parameter>& /* _ports_out */,
65  const std::vector<ModuleGenerator::parameter>& /* _ports_inout */)
66 {
67  if(language != HDLWriter_Language::VERILOG)
68  {
69  THROW_UNREACHABLE("Unsupported output language");
70  return;
71  }
72 
73  const auto port_prefix = mod_cir->get_id().substr(sizeof("if_") - 1U, std::string::npos);
74  std::string np_library = mod_cir->get_id() + " index";
75  std::string internal_port_assign;
76  const auto add_port_parametric_wire = [&](const std::string& name, port_o::port_direction dir, unsigned port_size) {
77  const auto port_name = port_prefix + "_" + name;
78  const auto port_obj = structural_manager::add_port(
79  port_name, dir, mod_cir, structural_type_descriptorRef(new structural_type_descriptor("bool", port_size)));
80  GetPointerS<port_o>(port_obj)->set_is_memory(true);
81  if(port_size)
82  {
83  np_library += " " + port_name;
84  }
85  out << "wire ";
86  if(port_size)
87  {
88  out << "[BITSIZE_" << port_name << "-1:0] ";
89  }
90  out << name << ";\n";
91  if(dir == port_o::IN)
92  {
93  internal_port_assign += "assign " + name + "=" + port_name + ";\n";
94  }
95  else
96  {
97  internal_port_assign += "assign " + port_name + "=" + name + ";\n";
98  }
99  };
100 
101  const auto add_port_parametric_reg = [&](const std::string& name, port_o::port_direction dir, unsigned port_size) {
102  const auto port_name = port_prefix + "_" + name;
103  const auto port_obj = structural_manager::add_port(
104  port_name, dir, mod_cir, structural_type_descriptorRef(new structural_type_descriptor("bool", port_size)));
105  GetPointerS<port_o>(port_obj)->set_is_memory(true);
106  if(port_size)
107  {
108  np_library += " " + port_name;
109  }
110  out << "reg ";
111  if(port_size)
112  {
113  out << "[BITSIZE_" << port_name << "-1:0] ";
114  }
115  out << name << ";\n";
116  if(dir == port_o::IN)
117  {
118  internal_port_assign += "assign " + name + "=" + port_name + ";\n";
119  }
120  else
121  {
122  internal_port_assign += "assign " + port_name + "=" + name + ";\n";
123  }
124  };
125 
126  add_port_parametric_reg("awready", port_o::OUT, 0U);
127  add_port_parametric_reg("wready", port_o::OUT, 0U);
128  add_port_parametric_reg("bid", port_o::OUT, 1U);
129  add_port_parametric_reg("bresp", port_o::OUT, 2U);
130  add_port_parametric_reg("buser", port_o::OUT, 1U);
131  add_port_parametric_reg("bvalid", port_o::OUT, 0U);
132  add_port_parametric_reg("arready", port_o::OUT, 0U);
133  add_port_parametric_reg("rid", port_o::OUT, 1U);
134  add_port_parametric_reg("rdata", port_o::OUT, 1U);
135  add_port_parametric_reg("rresp", port_o::OUT, 2U);
136  add_port_parametric_reg("rlast", port_o::OUT, 0U);
137  add_port_parametric_reg("ruser", port_o::OUT, 1U);
138  add_port_parametric_reg("rvalid", port_o::OUT, 0U);
139 
140  add_port_parametric_reg("awid", port_o::IN, 1U);
141  add_port_parametric_reg("awaddr", port_o::IN, 1U);
142  add_port_parametric_reg("awlen", port_o::IN, 1U);
143  add_port_parametric_reg("awsize", port_o::IN, 1U);
144  add_port_parametric_reg("awburst", port_o::IN, 2U);
145  add_port_parametric_reg("awlock", port_o::IN, 1U);
146  add_port_parametric_reg("awcache", port_o::IN, 1U);
147  add_port_parametric_reg("awprot", port_o::IN, 1U);
148  add_port_parametric_reg("awqos", port_o::IN, 1U);
149  add_port_parametric_reg("awregion", port_o::IN, 1U);
150  add_port_parametric_reg("awuser", port_o::IN, 1U);
151  add_port_parametric_reg("awvalid", port_o::IN, 0U);
152 
153  add_port_parametric_reg("wdata", port_o::IN, 1U);
154  add_port_parametric_reg("wstrb", port_o::IN, 1U);
155  add_port_parametric_reg("wlast", port_o::IN, 0U);
156  add_port_parametric_reg("wuser", port_o::IN, 1U);
157  add_port_parametric_reg("wvalid", port_o::IN, 0U);
158 
159  add_port_parametric_wire("bready", port_o::IN, 0U);
160 
161  add_port_parametric_reg("arid", port_o::IN, 1U);
162  add_port_parametric_reg("araddr", port_o::IN, 1U);
163  add_port_parametric_reg("arlen", port_o::IN, 1U);
164  add_port_parametric_reg("arsize", port_o::IN, 1U);
165  add_port_parametric_reg("arburst", port_o::IN, 2U);
166  add_port_parametric_reg("arlock", port_o::IN, 1U);
167  add_port_parametric_reg("arcache", port_o::IN, 1U);
168  add_port_parametric_reg("arprot", port_o::IN, 1U);
169  add_port_parametric_reg("arqos", port_o::IN, 1U);
170  add_port_parametric_reg("arregion", port_o::IN, 1U);
171  add_port_parametric_reg("aruser", port_o::IN, 1U);
172  add_port_parametric_reg("arvalid", port_o::IN, 0U);
173  add_port_parametric_wire("rready", port_o::IN, 0U);
174 
176 
177  if(HLSMgr->get_parameter()->getOption<unsigned int>(OPT_mem_delay_write) == 1)
178  {
180  "Warning: AXI does not support mem-delay-write==1, as it requires at least a cycle of latency -> "
181  "mem-delay-write changed to 2.\n");
182  }
183  out << internal_port_assign << "\n"
184  << "localparam WRITE_DELAY="
185  << std::max(1U, (HLSMgr->get_parameter()->getOption<unsigned int>(OPT_mem_delay_write) - 1)) << ",\n"
186  << " READ_DELAY=" << (HLSMgr->get_parameter()->getOption<unsigned int>(OPT_mem_delay_read) - 1) << ",\n"
187  << " QUEUE_SIZE=" << HLSMgr->get_parameter()->getOption<unsigned int>(OPT_tb_queue_size) << ",\n"
188  << " BITSIZE_data=BITSIZE_" << port_prefix << "_rdata,\n"
189  << " BITSIZE_counter=32,\n"
190  << " BITSIZE_burst=BITSIZE_" << port_prefix << "_arburst,\n"
191  << " BITSIZE_len=BITSIZE_" << port_prefix << "_arlen,\n"
192  << " BITSIZE_delay=32,\n"
193  << " BITSIZE_size=BITSIZE_" << port_prefix << "_arsize,\n"
194  << " BITSIZE_addr=BITSIZE_" << port_prefix << "_araddr,\n"
195  << " BITSIZE_wstrb=BITSIZE_" << port_prefix << "_wstrb,\n"
196  << " BITSIZE_id=BITSIZE_" << port_prefix << "_arid,\n"
197  << " OFFSET_delay=0,\n"
198  << " OFFSET_counter=OFFSET_delay+BITSIZE_delay,\n"
199  << " OFFSET_burst=OFFSET_counter+BITSIZE_counter,\n"
200  << " OFFSET_len=OFFSET_burst+BITSIZE_burst,\n"
201  << " OFFSET_size=OFFSET_len+BITSIZE_len,\n"
202  << " OFFSET_addr=OFFSET_size+BITSIZE_size,\n"
203  << " OFFSET_id=OFFSET_addr+BITSIZE_addr,\n"
204  << " OFFSET_data=OFFSET_id+BITSIZE_id,\n"
205  << " OFFSET_wstrb=OFFSET_data+BITSIZE_data,\n"
206  << " "
207  "BITSIZE_aritem=BITSIZE_id+BITSIZE_addr+BITSIZE_size+BITSIZE_len+BITSIZE_burst+BITSIZE_counter+BITSIZE_delay,"
208  "\n"
209  << " "
210  "BITSIZE_awitem=BITSIZE_wstrb+BITSIZE_data+BITSIZE_id+BITSIZE_addr+BITSIZE_size+BITSIZE_len+"
211  "BITSIZE_burst+BITSIZE_counter+BITSIZE_delay;"
212  "\n"
213  << R"(
214 reg [QUEUE_SIZE*BITSIZE_aritem-1:0] arqueue;
215 reg [QUEUE_SIZE*BITSIZE_aritem-1:0] next_arqueue;
216 reg [QUEUE_SIZE*BITSIZE_awitem-1:0] awqueue;
217 reg [QUEUE_SIZE*BITSIZE_awitem-1:0] next_awqueue;
218 reg [31:0] test_addr;
219 reg [31:0] test_wstrb;
220 reg [31:0] test_data;
221 reg [31:0] test_addr_read;
222 integer arqueue_size, next_arqueue_size;
223 integer awqueue_size, next_awqueue_size;
224 
225 reg [BITSIZE_id-1:0] awid_reg;
226 reg [BITSIZE_addr-1:0] awaddr_reg;
227 reg [BITSIZE_size-1:0] awsize_reg;
228 reg [BITSIZE_len-1:0] awlen_reg;
229 reg [BITSIZE_burst-1:0] awburst_reg;
230 reg [BITSIZE_counter-1:0] counter_reg, counter_next;
231 reg write_done;
232 reg wlast_reg, wlast_next;
233 
234 if_utils #(index, BITSIZE_data) m_utils();
235 
236 initial
237 begin
238  arqueue = 0;
239  arqueue_size = 0;
240  next_arqueue = 0;
241  next_arqueue_size = 0;
242  awqueue = 0;
243  awqueue_size = 0;
244  next_awqueue = 0;
245  next_awqueue_size = 0;
246  awready = 0;
247  wready = 0;
248  bid = 0;
249  bresp = 0;
250  buser = 0;
251  bvalid = 0;
252  arready = 0;
253  rid = 0;
254  rdata = 0;
255  rresp = 0;
256  rlast = 0;
257  ruser = 0;
258  rvalid = 0;
259  awid_reg = 0;
260  awaddr_reg = 0;
261  awsize_reg = 0;
262  awlen_reg = 0;
263  awburst_reg = 0;
264 end
265 
266 // Combinatorial logic for read transactions
267 always@(*)
268 begin: read_comb
269  automatic integer unsigned i;
270  next_arqueue = arqueue;
271  next_arqueue_size = arqueue_size;
272  if(arvalid && arready) // Valid and ready -> accept new transaction
273  begin
274  next_arqueue[arqueue_size*BITSIZE_aritem +:BITSIZE_aritem] = {arid, araddr, arsize, arlen, arburst, {BITSIZE_counter{1'b0}}, ({BITSIZE_delay{1'b0}} + READ_DELAY)}; // size of parameter is implementation dependent
275  next_arqueue_size = arqueue_size + 1;
276  end
277  for(i = 0; i < QUEUE_SIZE; i = i + 1)
278  begin
279  if(arqueue[i*BITSIZE_aritem+OFFSET_delay+:BITSIZE_delay] > 1)
280  begin
281  next_arqueue[i*BITSIZE_aritem+OFFSET_delay+:BITSIZE_delay] = arqueue[i*BITSIZE_aritem+OFFSET_delay+:BITSIZE_delay] - 1;
282  end
283  end
284  if(next_arqueue_size > 0 && next_arqueue[OFFSET_counter+:BITSIZE_counter] >= (next_arqueue[OFFSET_len+:BITSIZE_len] + 1) && rready && rvalid)
285  begin
286  for(i = 1; i < QUEUE_SIZE; i = i + 1)
287  begin
288  next_arqueue[(i-1)*BITSIZE_aritem+:BITSIZE_aritem] = next_arqueue[i*BITSIZE_aritem+:BITSIZE_aritem];
289  end
290  next_arqueue_size = next_arqueue_size - 1;
291  end
292  if(next_arqueue_size > 0 && (next_arqueue[OFFSET_delay+:BITSIZE_delay] == 1) && (next_arqueue[OFFSET_counter+:BITSIZE_counter] < (next_arqueue[OFFSET_len+:BITSIZE_len] + 1)))
293  begin
294  next_arqueue[OFFSET_counter+:BITSIZE_counter] = next_arqueue[OFFSET_counter+:BITSIZE_counter] + 1;
295  end
296 end
297 
298 // Combinatorial logic for write transactions
299 always@(*)
300 begin: write_comb
301  automatic integer i;
302  automatic reg [BITSIZE_counter-1:0] counter;
303  next_awqueue = awqueue;
304  next_awqueue_size = awqueue_size;
305  wlast_next = wlast_reg;
306  if(awvalid && awready && wready && wvalid) // Valid and ready -> accept new transaction and save data
307  begin
308  next_awqueue[awqueue_size*BITSIZE_awitem+:BITSIZE_awitem] = {wstrb, wdata, awid, awaddr, awsize, awlen, awburst, {BITSIZE_counter{1'b0}}, ({BITSIZE_delay{1'b0}} + WRITE_DELAY)};
309  next_awqueue_size = awqueue_size + 1;
310  end
311  else if(awvalid && awready && wlast_reg)
312  begin
313  awid_reg = awid;
314  awaddr_reg = awaddr;
315  awsize_reg = awsize;
316  awlen_reg = awlen;
317  awburst_reg = awburst;
318  counter_next = 0;
319  wlast_next = 0;
320  end
321  else if(wready && wvalid)
322  begin
323  next_awqueue[awqueue_size*BITSIZE_awitem+:BITSIZE_awitem] = {wstrb, wdata, awid_reg, awaddr_reg, awsize_reg, awlen_reg, awburst_reg, counter_reg, ({BITSIZE_delay{1'b0}} + WRITE_DELAY)};
324  next_awqueue_size = awqueue_size + 1;
325  counter_next = counter_reg + 1;
326  end
327  for(i = 0; i < QUEUE_SIZE; i = i + 1)
328  begin
329  if(awqueue[i*BITSIZE_awitem+OFFSET_delay+:BITSIZE_delay] > 1)
330  begin
331  next_awqueue[i*BITSIZE_awitem+OFFSET_delay+:BITSIZE_delay] = awqueue[i*BITSIZE_awitem+OFFSET_delay+:BITSIZE_delay] - 1;
332  end
333  end
334  if(write_done && next_awqueue_size > 0 && ((next_awqueue[OFFSET_counter+:BITSIZE_counter] == next_awqueue[OFFSET_len+:BITSIZE_len] && bready) || next_awqueue[OFFSET_counter+:BITSIZE_counter] < next_awqueue[OFFSET_len+:BITSIZE_len]))
335  begin
336  for(i = 1; i < QUEUE_SIZE; i = i + 1)
337  begin
338  next_awqueue[(i-1)*BITSIZE_awitem+:BITSIZE_awitem] = next_awqueue[i*BITSIZE_awitem+:BITSIZE_awitem];
339  end
340  next_awqueue_size = next_awqueue_size - 1;
341  end
342 end
343 
344 always@(posedge clock)
345 begin
346  if(1RESET_VALUE)
347  begin
348  arready <= 0;
349  awready <= 0;
350  wready <= 0;
351  wlast_reg <= 1;
352  counter_reg <= 0;
353  end
354  else
355  begin
356  arready <= (next_arqueue_size - (next_arqueue_size > 0 && next_arqueue[OFFSET_counter+:BITSIZE_counter] >= next_arqueue[OFFSET_len+:BITSIZE_len]) < QUEUE_SIZE); //Ready if next_queue_size - rlast < QUEUE_SIZE
357  awready <= (wlast_next || wlast) && (next_awqueue_size - (next_awqueue_size > 0 && next_awqueue[OFFSET_delay+:BITSIZE_delay] == 1 && next_awqueue[OFFSET_counter+:BITSIZE_counter] == next_awqueue[OFFSET_len+:BITSIZE_len]) < QUEUE_SIZE); // ready if next_queue_size - bvalid < QUEUE_SIZE
358  wready <= (next_awqueue_size - (next_awqueue_size > 0 && next_awqueue[OFFSET_delay+:BITSIZE_delay] == 1 && next_awqueue[OFFSET_counter+:BITSIZE_counter] == next_awqueue[OFFSET_len+:BITSIZE_len]) < QUEUE_SIZE);
359  wlast_reg <= wlast_next || wlast;
360  counter_reg <= counter_next;
361  end
362 end
363 
364 always@(posedge clock)
365 begin
366  if(1RESET_VALUE)
367  begin
368  rvalid <= 0;
369  bvalid <= 0;
370  end
371  else
372  begin
373  rvalid <= (next_arqueue_size > 0 && next_arqueue[OFFSET_delay+:BITSIZE_delay] == 1); // if at posedge_clock delay is 1 I have to perfrom the operation in this cycle
374  bvalid <= (next_awqueue_size > 0 && next_awqueue[OFFSET_delay+:BITSIZE_delay] == 1 && next_awqueue[OFFSET_counter+:BITSIZE_counter] == (next_awqueue[OFFSET_len+:BITSIZE_len])); // if at posedge_clock delay is 1 I have to perfrom the last operation in this cycle
375  end
376 end
377 
378 always@(posedge clock)
379 begin
380  if(1RESET_VALUE)
381  begin
382  arqueue <= 0;
383  arqueue_size <= 0;
384  awqueue <= 0;
385  awqueue_size <= 0;
386  end
387  else
388  begin
389  arqueue <= next_arqueue;
390  arqueue_size <= next_arqueue_size;
391  awqueue <= next_awqueue;
392  awqueue_size <= next_awqueue_size;
393  end
394 end
395 
396 // Sequential logic for read transactions
397 always@(posedge clock)
398 begin : read_seq
399  automatic ptr_t currAddr;
400  automatic ptr_t endAddr;
401  rlast <= 0;
402  test_addr_read <= 0;
403  if(next_arqueue_size > 0 && next_arqueue[OFFSET_delay+:BITSIZE_delay] == 1)
404  begin
405  if(next_arqueue[OFFSET_burst+:BITSIZE_burst] == 2'b00)
406  begin
407  currAddr = next_arqueue[OFFSET_addr+:BITSIZE_addr];
408  end
409  else if(next_arqueue[OFFSET_burst+:BITSIZE_burst] == 2'b01)
410  begin
411  currAddr = next_arqueue[OFFSET_addr+:BITSIZE_addr] + (next_arqueue[OFFSET_counter+:BITSIZE_counter] - 1) * (1 << next_arqueue[OFFSET_size+:BITSIZE_size]);
412  end
413  else
414  begin
415  $display("Unsupported burst type: %0d", next_arqueue[OFFSET_burst+:BITSIZE_burst]);
416  $finish;
417  end
418  test_addr_read <= currAddr;
419  rid <= next_arqueue[OFFSET_id+:BITSIZE_id];
420  rdata <= m_utils.read_a(currAddr);
421  if(next_arqueue[OFFSET_counter+:BITSIZE_counter] >= (next_arqueue[OFFSET_len+:BITSIZE_len] + 1))
422  begin
423  rlast <= 1;
424  end
425  end
426 end
427 
428 // Sequential logic for write transactions
429 always@(posedge clock)
430 begin: write_seq
431  automatic ptr_t currAddr;
432  automatic ptr_t endAddr;
433  test_wstrb <=0;
434  test_addr <= 0;
435  test_data <= 0;
436  write_done <= 0;
437  if(next_awqueue_size > 0 && next_awqueue[OFFSET_delay+:BITSIZE_delay] == 1) // Performs the first write of the queue
438  begin
439  if(next_awqueue[OFFSET_burst+:BITSIZE_burst] == 2'b00)
440  begin
441  currAddr = next_awqueue[OFFSET_addr+:BITSIZE_addr];
442  end
443  else if(next_awqueue[OFFSET_burst+:BITSIZE_burst] == 2'b01)
444  begin
445  currAddr = next_awqueue[OFFSET_addr+:BITSIZE_addr] + next_awqueue[OFFSET_counter+:BITSIZE_counter] * (1 << next_awqueue[OFFSET_size+:BITSIZE_size]);
446  end
447  else
448  begin
449  $display("Unsupported burst type: %0d", next_arqueue[OFFSET_burst+:BITSIZE_burst]);
450  $finish;
451  end
452  test_wstrb <= next_awqueue[OFFSET_wstrb+:BITSIZE_wstrb];
453  test_addr <= currAddr;
454  test_data <= next_awqueue[OFFSET_data+:BITSIZE_data];
455  bid <= next_awqueue[OFFSET_id+:BITSIZE_id];
456  write_done <= 1;
457  m_utils.write_strobe(next_awqueue[OFFSET_data+:BITSIZE_data], next_awqueue[OFFSET_wstrb+:BITSIZE_wstrb], currAddr);
458  end
459 end
460 )";
461 }
Data structure representing the entire HLS information.
refcount< structural_type_descriptor > structural_type_descriptorRef
RefCount type definition of the structural_type_descriptor class structure.
#define OUTPUT_LEVEL_NONE
no output print is performed.
Structure representing the most relevant information about the type of a structural object...
const std::string & get_id() const
Return the identifier associated with the structural_object.
mathematical utility function not provided by standard libraries
TestbenchAXIMModuleGenerator(const HLS_managerRef &HLSMgr)
#define max
Definition: backprop.h:17
HDLWriter_Language
port_direction
Enumerative type describing the direction of a port.
#define THROW_UNREACHABLE(str_expr)
helper function used to specify that some points should never be reached
Definition: exceptions.hpp:292
static void add_NP_functionality(structural_objectRef cir, NP_functionality::NP_functionaly_type dt, std::string functionality_description)
Add a not-parsed functionality.
This class writes different HDL based descriptions (VHDL, Verilog, SystemC) starting from a structura...
static structural_objectRef add_port(const std::string &id, port_o::port_direction pdir, structural_objectRef owner, structural_type_descriptorRef type_descr, unsigned int treenode=0)
Create a new port.
boost::graph_traits< graph >::vertex_descriptor vertex
vertex definition.
Definition: graph.hpp:1303
This file collects some utility functions.
void InternalExec(std::ostream &out, structural_objectRef mod, unsigned int function_id, vertex op_v, const HDLWriter_Language language, const std::vector< ModuleGenerator::parameter > &_p, const std::vector< ModuleGenerator::parameter > &_ports_in, const std::vector< ModuleGenerator::parameter > &_ports_out, const std::vector< ModuleGenerator::parameter > &_ports_inout) final
Template borrowed from the ANTLR library by Terence Parr (http://www.jGuru.com - Software rights: htt...
Definition: refcount.hpp:94
this class is used to manage the command-line or XML options.
Class implementation of the structural_manager.
#define PRINT_OUT_MEX(profLevel, curprofLevel, mex)
A brief description of the C++ Header File.

Generated on Mon Feb 12 2024 13:02:54 for PandA-2024.02 by doxygen 1.8.13