Fixed "test_cells -vlog"
[yosys.git] / passes / tests / test_cell.cc
1 /*
2 * yosys -- Yosys Open SYnthesis Suite
3 *
4 * Copyright (C) 2014 Clifford Wolf <clifford@clifford.at>
5 * Copyright (C) 2014 Johann Glaser <Johann.Glaser@gmx.at>
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 */
20
21 #include "kernel/yosys.h"
22 #include "kernel/satgen.h"
23 #include "kernel/consteval.h"
24 #include <algorithm>
25
26 static uint32_t xorshift32_state = 123456789;
27
28 static uint32_t xorshift32(uint32_t limit) {
29 xorshift32_state ^= xorshift32_state << 13;
30 xorshift32_state ^= xorshift32_state >> 17;
31 xorshift32_state ^= xorshift32_state << 5;
32 return xorshift32_state % limit;
33 }
34
35 static void create_gold_module(RTLIL::Design *design, RTLIL::IdString cell_type, std::string cell_type_flags)
36 {
37 RTLIL::Module *module = design->addModule("\\gold");
38 RTLIL::Cell *cell = module->addCell("\\UUT", cell_type);
39 RTLIL::Wire *wire;
40
41 if (cell_type == "$lut")
42 {
43 int width = 1 + xorshift32(6);
44
45 wire = module->addWire("\\A");
46 wire->width = width;
47 wire->port_input = true;
48 cell->setPort("\\A", wire);
49
50 wire = module->addWire("\\Y");
51 wire->port_output = true;
52 cell->setPort("\\Y", wire);
53
54 RTLIL::SigSpec config;
55 for (int i = 0; i < (1 << width); i++)
56 config.append(xorshift32(2) ? RTLIL::S1 : RTLIL::S0);
57
58 cell->setParam("\\LUT", config.as_const());
59 }
60
61 if (cell_type_flags.find('A') != std::string::npos) {
62 wire = module->addWire("\\A");
63 wire->width = 1 + xorshift32(8);
64 wire->port_input = true;
65 cell->setPort("\\A", wire);
66 }
67
68 if (cell_type_flags.find('B') != std::string::npos) {
69 wire = module->addWire("\\B");
70 if (cell_type_flags.find('h') != std::string::npos)
71 wire->width = 1 + xorshift32(6);
72 else
73 wire->width = 1 + xorshift32(8);
74 wire->port_input = true;
75 cell->setPort("\\B", wire);
76 }
77
78 if (cell_type_flags.find('S') != std::string::npos && xorshift32(2)) {
79 if (cell_type_flags.find('A') != std::string::npos)
80 cell->parameters["\\A_SIGNED"] = true;
81 if (cell_type_flags.find('B') != std::string::npos)
82 cell->parameters["\\B_SIGNED"] = true;
83 }
84
85 if (cell_type_flags.find('s') != std::string::npos) {
86 if (cell_type_flags.find('A') != std::string::npos && xorshift32(2))
87 cell->parameters["\\A_SIGNED"] = true;
88 if (cell_type_flags.find('B') != std::string::npos && xorshift32(2))
89 cell->parameters["\\B_SIGNED"] = true;
90 }
91
92 if (cell_type_flags.find('Y') != std::string::npos) {
93 wire = module->addWire("\\Y");
94 wire->width = 1 + xorshift32(8);
95 wire->port_output = true;
96 cell->setPort("\\Y", wire);
97 }
98
99 if (cell_type == "$alu")
100 {
101 wire = module->addWire("\\CI");
102 wire->port_input = true;
103 cell->setPort("\\CI", wire);
104
105 wire = module->addWire("\\BI");
106 wire->port_input = true;
107 cell->setPort("\\BI", wire);
108
109 wire = module->addWire("\\X");
110 wire->width = SIZE(cell->getPort("\\Y"));
111 wire->port_output = true;
112 cell->setPort("\\X", wire);
113
114 wire = module->addWire("\\CO");
115 wire->width = SIZE(cell->getPort("\\Y"));
116 wire->port_output = true;
117 cell->setPort("\\CO", wire);
118 }
119
120 module->fixup_ports();
121 cell->fixup_parameters();
122 cell->check();
123 }
124
125 static void run_eval_test(RTLIL::Design *design, bool verbose, std::string uut_name, std::ofstream &vlog_file)
126 {
127 log("Eval testing:%c", verbose ? '\n' : ' ');
128
129 RTLIL::Module *gold_mod = design->module("\\gold");
130 RTLIL::Module *gate_mod = design->module("\\gate");
131 ConstEval gold_ce(gold_mod), gate_ce(gate_mod);
132
133 ezDefaultSAT ez1, ez2;
134 SigMap sigmap(gold_mod);
135 SatGen satgen1(&ez1, &sigmap);
136 SatGen satgen2(&ez2, &sigmap);
137 satgen2.model_undef = true;
138
139 for (auto cell : gold_mod->cells()) {
140 satgen1.importCell(cell);
141 satgen2.importCell(cell);
142 }
143
144 if (vlog_file.is_open())
145 {
146 vlog_file << stringf("\nmodule %s;\n", uut_name.c_str());
147
148 for (auto port : gold_mod->ports) {
149 RTLIL::Wire *wire = gold_mod->wire(port);
150 if (wire->port_input)
151 vlog_file << stringf(" reg [%d:0] %s;\n", SIZE(wire)-1, log_id(wire));
152 else
153 vlog_file << stringf(" wire [%d:0] %s_expr, %s_noexpr;\n", SIZE(wire)-1, log_id(wire), log_id(wire));
154 }
155
156 vlog_file << stringf(" %s_expr uut_expr(", uut_name.c_str());
157 for (int i = 0; i < SIZE(gold_mod->ports); i++)
158 vlog_file << stringf("%s.%s(%s%s)", i ? ", " : "", log_id(gold_mod->ports[i]), log_id(gold_mod->ports[i]),
159 gold_mod->wire(gold_mod->ports[i])->port_input ? "" : "_expr");
160 vlog_file << stringf(");\n");
161
162 vlog_file << stringf(" %s_expr uut_noexpr(", uut_name.c_str());
163 for (int i = 0; i < SIZE(gold_mod->ports); i++)
164 vlog_file << stringf("%s.%s(%s%s)", i ? ", " : "", log_id(gold_mod->ports[i]), log_id(gold_mod->ports[i]),
165 gold_mod->wire(gold_mod->ports[i])->port_input ? "" : "_noexpr");
166 vlog_file << stringf(");\n");
167
168 vlog_file << stringf(" task run;\n");
169 vlog_file << stringf(" begin\n");
170 vlog_file << stringf(" $display(\"%s\");\n", uut_name.c_str());
171 }
172
173 for (int i = 0; i < 64; i++)
174 {
175 log(verbose ? "\n" : ".");
176 gold_ce.clear();
177 gate_ce.clear();
178
179 RTLIL::SigSpec in_sig, in_val;
180 RTLIL::SigSpec out_sig, out_val;
181 std::string vlog_pattern_info;
182
183 for (auto port : gold_mod->ports)
184 {
185 RTLIL::Wire *gold_wire = gold_mod->wire(port);
186 RTLIL::Wire *gate_wire = gate_mod->wire(port);
187
188 log_assert(gold_wire != nullptr);
189 log_assert(gate_wire != nullptr);
190 log_assert(gold_wire->port_input == gate_wire->port_input);
191 log_assert(SIZE(gold_wire) == SIZE(gate_wire));
192
193 if (!gold_wire->port_input)
194 continue;
195
196 RTLIL::Const in_value;
197 for (int i = 0; i < SIZE(gold_wire); i++)
198 in_value.bits.push_back(xorshift32(2) ? RTLIL::S1 : RTLIL::S0);
199
200 if (xorshift32(4) == 0) {
201 int inv_chance = 1 + xorshift32(8);
202 for (int i = 0; i < SIZE(gold_wire); i++)
203 if (xorshift32(inv_chance) == 0)
204 in_value.bits[i] = RTLIL::Sx;
205 }
206
207 if (verbose)
208 log("%s: %s\n", log_id(gold_wire), log_signal(in_value));
209
210 in_sig.append(gold_wire);
211 in_val.append(in_value);
212
213 gold_ce.set(gold_wire, in_value);
214 gate_ce.set(gate_wire, in_value);
215
216 if (vlog_file.is_open()) {
217 vlog_file << stringf(" %s = 'b%s;\n", log_id(gold_wire), in_value.as_string().c_str());
218 if (!vlog_pattern_info.empty())
219 vlog_pattern_info += " ";
220 vlog_pattern_info += stringf("%s=%s", log_id(gold_wire), log_signal(in_value));
221 }
222 }
223
224 if (vlog_file.is_open())
225 vlog_file << stringf(" #1;\n");
226
227 for (auto port : gold_mod->ports)
228 {
229 RTLIL::Wire *gold_wire = gold_mod->wire(port);
230 RTLIL::Wire *gate_wire = gate_mod->wire(port);
231
232 log_assert(gold_wire != nullptr);
233 log_assert(gate_wire != nullptr);
234 log_assert(gold_wire->port_output == gate_wire->port_output);
235 log_assert(SIZE(gold_wire) == SIZE(gate_wire));
236
237 if (!gold_wire->port_output)
238 continue;
239
240 RTLIL::SigSpec gold_outval(gold_wire);
241 RTLIL::SigSpec gate_outval(gate_wire);
242
243 if (!gold_ce.eval(gold_outval))
244 log_error("Failed to eval %s in gold module.\n", log_id(gold_wire));
245
246 if (!gate_ce.eval(gate_outval))
247 log_error("Failed to eval %s in gate module.\n", log_id(gate_wire));
248
249 bool gold_gate_mismatch = false;
250 for (int i = 0; i < SIZE(gold_wire); i++) {
251 if (gold_outval[i] == RTLIL::Sx)
252 continue;
253 if (gold_outval[i] == gate_outval[i])
254 continue;
255 gold_gate_mismatch = true;
256 break;
257 }
258
259 if (gold_gate_mismatch)
260 log_error("Mismatch in output %s: gold:%s != gate:%s\n", log_id(gate_wire), log_signal(gold_outval), log_signal(gate_outval));
261
262 if (verbose)
263 log("%s: %s\n", log_id(gold_wire), log_signal(gold_outval));
264
265 out_sig.append(gold_wire);
266 out_val.append(gold_outval);
267
268 if (vlog_file.is_open()) {
269 vlog_file << stringf(" $display(\"[%s] %s expected: %%b, expr: %%b, noexpr: %%b\", %d'b%s, %s_expr, %s_noexpr);\n",
270 vlog_pattern_info.c_str(), log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str(), log_id(gold_wire), log_id(gold_wire));
271 vlog_file << stringf(" if (%s_expr !== %d'b%s) begin $display(\"ERROR\"); $finish; end\n", log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str());
272 vlog_file << stringf(" if (%s_noexpr !== %d'b%s) begin $display(\"ERROR\"); $finish; end\n", log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str());
273 }
274 }
275
276 if (verbose)
277 log("EVAL: %s\n", out_val.as_string().c_str());
278
279 std::vector<int> sat1_in_sig = satgen1.importSigSpec(in_sig);
280 std::vector<int> sat1_in_val = satgen1.importSigSpec(in_val);
281
282 std::vector<int> sat1_model = satgen1.importSigSpec(out_sig);
283 std::vector<bool> sat1_model_value;
284
285 if (!ez1.solve(sat1_model, sat1_model_value, ez1.vec_eq(sat1_in_sig, sat1_in_val)))
286 log_error("Evaluating sat model 1 (no undef modeling) failed!\n");
287
288 if (verbose) {
289 log("SAT 1: ");
290 for (int i = SIZE(out_sig)-1; i >= 0; i--)
291 log("%c", sat1_model_value.at(i) ? '1' : '0');
292 log("\n");
293 }
294
295 for (int i = 0; i < SIZE(out_sig); i++) {
296 if (out_val[i] != RTLIL::S0 && out_val[i] != RTLIL::S1)
297 continue;
298 if (out_val[i] == RTLIL::S0 && sat1_model_value.at(i) == false)
299 continue;
300 if (out_val[i] == RTLIL::S1 && sat1_model_value.at(i) == true)
301 continue;
302 log_error("Mismatch in sat model 1 (no undef modeling) output!\n");
303 }
304
305 std::vector<int> sat2_in_def_sig = satgen2.importDefSigSpec(in_sig);
306 std::vector<int> sat2_in_def_val = satgen2.importDefSigSpec(in_val);
307
308 std::vector<int> sat2_in_undef_sig = satgen2.importUndefSigSpec(in_sig);
309 std::vector<int> sat2_in_undef_val = satgen2.importUndefSigSpec(in_val);
310
311 std::vector<int> sat2_model_def_sig = satgen2.importDefSigSpec(out_sig);
312 std::vector<int> sat2_model_undef_sig = satgen2.importUndefSigSpec(out_sig);
313
314 std::vector<int> sat2_model;
315 sat2_model.insert(sat2_model.end(), sat2_model_def_sig.begin(), sat2_model_def_sig.end());
316 sat2_model.insert(sat2_model.end(), sat2_model_undef_sig.begin(), sat2_model_undef_sig.end());
317
318 std::vector<bool> sat2_model_value;
319
320 if (!ez2.solve(sat2_model, sat2_model_value, ez2.vec_eq(sat2_in_def_sig, sat2_in_def_val), ez2.vec_eq(sat2_in_undef_sig, sat2_in_undef_val)))
321 log_error("Evaluating sat model 2 (undef modeling) failed!\n");
322
323 if (verbose) {
324 log("SAT 2: ");
325 for (int i = SIZE(out_sig)-1; i >= 0; i--)
326 log("%c", sat2_model_value.at(SIZE(out_sig) + i) ? 'x' : sat2_model_value.at(i) ? '1' : '0');
327 log("\n");
328 }
329
330 for (int i = 0; i < SIZE(out_sig); i++) {
331 if (sat2_model_value.at(SIZE(out_sig) + i)) {
332 if (out_val[i] != RTLIL::S0 && out_val[i] != RTLIL::S1)
333 continue;
334 } else {
335 if (out_val[i] == RTLIL::S0 && sat2_model_value.at(i) == false)
336 continue;
337 if (out_val[i] == RTLIL::S1 && sat2_model_value.at(i) == true)
338 continue;
339 }
340 log_error("Mismatch in sat model 2 (undef modeling) output!\n");
341 }
342 }
343
344 if (vlog_file.is_open()) {
345 vlog_file << stringf(" end\n");
346 vlog_file << stringf(" endtask\n");
347 vlog_file << stringf("endmodule\n");
348 }
349
350 if (!verbose)
351 log(" ok.\n");
352 }
353
354 struct TestCellPass : public Pass {
355 TestCellPass() : Pass("test_cell", "automatically test the implementation of a cell type") { }
356 virtual void help()
357 {
358 // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
359 log("\n");
360 log(" test_cell [options] {cell-types}\n");
361 log("\n");
362 log("Tests the internal implementation of the given cell type (for example '$mux')\n");
363 log("by comparing SAT solver, EVAL and TECHMAP implementations of the cell types..\n");
364 log("\n");
365 log("Run with 'all' instead of a cell type to run the test on all supported\n");
366 log("cell types.\n");
367 log("\n");
368 log(" -n {integer}\n");
369 log(" create this number of cell instances and test them (default = 100).\n");
370 log("\n");
371 log(" -s {positive_integer}\n");
372 log(" use this value as rng seed value (default = unix time).\n");
373 log("\n");
374 log(" -f {ilang_file}\n");
375 log(" don't generate circuits. instead load the specified ilang file.\n");
376 log("\n");
377 log(" -map {filename}\n");
378 log(" pass this option to techmap.\n");
379 log("\n");
380 log(" -simplib\n");
381 log(" use \"techmap -map +/simlib.v -max_iter 2 -autoproc\"\n");
382 log("\n");
383 log(" -v\n");
384 log(" print additional debug information to the console\n");
385 log("\n");
386 log(" -vlog {filename}\n");
387 log(" create a verilog test bench to test simlib and write_verilog\n");
388 log("\n");
389 }
390 virtual void execute(std::vector<std::string> args, RTLIL::Design*)
391 {
392 int num_iter = 100;
393 std::string techmap_cmd = "techmap -assert";
394 std::string ilang_file;
395 xorshift32_state = 0;
396 std::ofstream vlog_file;
397 bool verbose = false;
398
399 int argidx;
400 for (argidx = 1; argidx < SIZE(args); argidx++)
401 {
402 if (args[argidx] == "-n" && argidx+1 < SIZE(args)) {
403 num_iter = atoi(args[++argidx].c_str());
404 continue;
405 }
406 if (args[argidx] == "-s" && argidx+1 < SIZE(args)) {
407 xorshift32_state = atoi(args[++argidx].c_str());
408 continue;
409 }
410 if (args[argidx] == "-map" && argidx+1 < SIZE(args)) {
411 techmap_cmd += " -map " + args[++argidx];
412 continue;
413 }
414 if (args[argidx] == "-f" && argidx+1 < SIZE(args)) {
415 ilang_file = args[++argidx];
416 num_iter = 1;
417 continue;
418 }
419 if (args[argidx] == "-simlib") {
420 techmap_cmd = "techmap -map +/simlib.v -max_iter 2 -autoproc";
421 continue;
422 }
423 if (args[argidx] == "-v") {
424 verbose = true;
425 continue;
426 }
427 if (args[argidx] == "-vlog" && argidx+1 < SIZE(args)) {
428 vlog_file.open(args[++argidx], std::ios_base::trunc);
429 if (!vlog_file.is_open())
430 log_cmd_error("Failed to open output file `%s'.\n", args[argidx].c_str());
431 continue;
432 }
433 break;
434 }
435
436 if (xorshift32_state == 0)
437 xorshift32_state = time(NULL);
438
439 std::map<std::string, std::string> cell_types;
440 std::vector<std::string> selected_cell_types;
441
442 cell_types["$not"] = "ASY";
443 cell_types["$pos"] = "ASY";
444 cell_types["$bu0"] = "ASY";
445 cell_types["$neg"] = "ASY";
446
447 cell_types["$and"] = "ABSY";
448 cell_types["$or"] = "ABSY";
449 cell_types["$xor"] = "ABSY";
450 cell_types["$xnor"] = "ABSY";
451
452 cell_types["$reduce_and"] = "ASY";
453 cell_types["$reduce_or"] = "ASY";
454 cell_types["$reduce_xor"] = "ASY";
455 cell_types["$reduce_xnor"] = "ASY";
456 cell_types["$reduce_bool"] = "ASY";
457
458 cell_types["$shl"] = "ABshY";
459 cell_types["$shr"] = "ABshY";
460 cell_types["$sshl"] = "ABshY";
461 cell_types["$sshr"] = "ABshY";
462 cell_types["$shift"] = "ABshY";
463 cell_types["$shiftx"] = "ABshY";
464
465 cell_types["$lt"] = "ABSY";
466 cell_types["$le"] = "ABSY";
467 cell_types["$eq"] = "ABSY";
468 cell_types["$ne"] = "ABSY";
469 // cell_types["$eqx"] = "ABSY";
470 // cell_types["$nex"] = "ABSY";
471 cell_types["$ge"] = "ABSY";
472 cell_types["$gt"] = "ABSY";
473
474 cell_types["$add"] = "ABSY";
475 cell_types["$sub"] = "ABSY";
476 cell_types["$mul"] = "ABSY";
477 cell_types["$div"] = "ABSY";
478 cell_types["$mod"] = "ABSY";
479 // cell_types["$pow"] = "ABsY";
480
481 cell_types["$logic_not"] = "ASY";
482 cell_types["$logic_and"] = "ABSY";
483 cell_types["$logic_or"] = "ABSY";
484
485 // cell_types["$mux"] = "A";
486 // cell_types["$pmux"] = "A";
487 // cell_types["$slice"] = "A";
488 // cell_types["$concat"] = "A";
489 // cell_types["$assert"] = "A";
490
491 cell_types["$lut"] = "*";
492 cell_types["$alu"] = "ABSY";
493
494 for (; argidx < SIZE(args); argidx++)
495 {
496 if (args[argidx].rfind("-", 0) == 0)
497 log_cmd_error("Unexpected option: %s\n", args[argidx].c_str());
498
499 if (args[argidx] == "all") {
500 for (auto &it : cell_types)
501 if (std::count(selected_cell_types.begin(), selected_cell_types.end(), it.first) == 0)
502 selected_cell_types.push_back(it.first);
503 continue;
504 }
505
506 if (cell_types.count(args[argidx]) == 0) {
507 std::string cell_type_list;
508 int charcount = 100;
509 for (auto &it : cell_types) {
510 if (charcount > 60) {
511 cell_type_list += "\n" + it.first;
512 charcount = 0;
513 } else
514 cell_type_list += " " + it.first;
515 charcount += SIZE(it.first);
516 }
517 log_cmd_error("The cell type `%s' is currently not supported. Try one of these:%s\n",
518 args[argidx].c_str(), cell_type_list.c_str());
519 }
520
521 if (std::count(selected_cell_types.begin(), selected_cell_types.end(), args[argidx]) == 0)
522 selected_cell_types.push_back(args[argidx]);
523 }
524
525 if (!ilang_file.empty()) {
526 if (!selected_cell_types.empty())
527 log_cmd_error("Do not specify any cell types when using -f.\n");
528 selected_cell_types.push_back("ilang");
529 }
530
531 if (selected_cell_types.empty())
532 log_cmd_error("No cell type to test specified.\n");
533
534 std::vector<std::string> uut_names;
535
536 for (auto cell_type : selected_cell_types)
537 for (int i = 0; i < num_iter; i++)
538 {
539 RTLIL::Design *design = new RTLIL::Design;
540 if (cell_type == "ilang")
541 Frontend::frontend_call(design, NULL, std::string(), "ilang " + ilang_file);
542 else
543 create_gold_module(design, cell_type, cell_types.at(cell_type));
544 Pass::call(design, stringf("copy gold gate; %s gate; opt gate", techmap_cmd.c_str()));
545 Pass::call(design, "miter -equiv -flatten -make_outputs -ignore_gold_x gold gate miter");
546 if (verbose)
547 Pass::call(design, "dump gate");
548 Pass::call(design, "dump gold");
549 Pass::call(design, "sat -verify -enable_undef -prove trigger 0 -show-inputs -show-outputs miter");
550 std::string uut_name = stringf("uut_%s_%d", cell_type.substr(1).c_str(), i);
551 if (vlog_file.is_open()) {
552 Pass::call(design, stringf("copy gold %s_expr; select %s_expr", uut_name.c_str(), uut_name.c_str()));
553 Backend::backend_call(design, &vlog_file, "<test_cell -vlog>", "verilog -selected");
554 Pass::call(design, stringf("copy gold %s_noexpr; select %s_noexpr", uut_name.c_str(), uut_name.c_str()));
555 Backend::backend_call(design, &vlog_file, "<test_cell -vlog>", "verilog -selected -noexpr");
556 uut_names.push_back(uut_name);
557 }
558 run_eval_test(design, verbose, uut_name, vlog_file);
559 delete design;
560 }
561
562 if (vlog_file.is_open()) {
563 vlog_file << "\nmodule testbench;\n";
564 for (auto &uut : uut_names)
565 vlog_file << stringf(" %s %s ();\n", uut.c_str(), uut.c_str());
566 vlog_file << " initial begin\n";
567 for (auto &uut : uut_names)
568 vlog_file << " " << uut << ".run;\n";
569 vlog_file << " end\n";
570 vlog_file << "endmodule\n";
571 }
572 }
573 } TestCellPass;
574