diff --git a/src/base/sn/readme.md b/src/base/sn/readme.md index 06b598f49..824d666d2 100644 --- a/src/base/sn/readme.md +++ b/src/base/sn/readme.md @@ -63,14 +63,20 @@ set snslang /path/to/sn_slang `@slang` uses `sn_slang` from `PATH` unless the `snslang` setting overrides it. It accepts `-M` for the top module, repeatable `-D NAME` or `-D NAME=value` preprocessor definitions, `-F` for one additional source file, and any number of positional source files. For example, `-D WIDTH=8 -D SIGNED=1` defines two macros. `-T` is not used because ABC -conventionally reserves it for a time limit. `-v` prints the external command and frontend timing. Black-box patterns -and include-directory options remain unsupported. +conventionally reserves it for a time limit. `-v` prints the external command and frontend timing. A module declared +inside SystemVerilog `` `celldefine`` / `` `endcelldefine``, or marked by a nonzero `black_box` or `syn_black_box` +module attribute, is imported as an opaque technology primitive with its elaborated PI/PO interface; its simulation +body is not lowered. For example, both `` `celldefine`` around a module definition and +`(* syn_black_box = 1 *) module macro (...);` create an opaque leaf. An explicit zero or false attribute does not. +The declaration is still required: slang must know every port's name, direction, width, and signedness, so an +undefined-module inst remains an error. Undefined-module patterns and include-directory options remain unsupported. `@read` and `@write` provide binary persistence. `@write` selects SN or Verilog output from the `.sn`, `.v`, or `.sv` extension. `@read -M module` selects the top stored in a multi-top design; otherwise the last top is used. Before installing external binary data, `@read` validates the encoding and runs the same non-aborting structural and semantic checks as `@check`. A failed `@write` removes its incomplete output file. Every design installed in ABC is -topologically ordered. +topologically ordered. The current writer emits binary format version 6; the reader also accepts version 5 and treats +its modules as ordinary non-black-box modules because that format predates module flags. `@status` prints the current design and top names, SN revision, selected technology, hierarchy form, last extraction mode/module/revision, saved boundary hash, current GIA dimensions, and `@put` compatibility. A new `@read` or `@slang` @@ -94,12 +100,14 @@ instance/FAN ordering, hierarchy recursion, LUTs, gates, and mapped primitive in line per module. Memory, DSP, and carry mapping commands run the same checker transactionally before and after each transformation, so an invalid result is diagnosed and rejected without replacing the current design. -`@ps` prints compact statistics for the selected top module. `@ps -v` adds the hierarchy and statistics for every -module definition. Like `%ps -d`, `@ps -d` prints occurrences by object type and output/input width signature. Its -counts cover the elaborated hierarchy rooted at the selected top, including the multiplicity of repeated insts. The -hierarchical occurrence totals are accumulated over the module DAG rather than by recursively revisiting every inst, -so statistics remain practical for deeply repeated hierarchy. Memory is reported as used/allocated storage with -rounded K, M, or G suffixes. +`@ps` prints compact statistics for every module definition by default. `@ps -M module` prints the selected module +instead and uses it as the root for optional hierarchy and detailed reports. `@ps -v` adds the selected hierarchy and +keeps opaque definitions annotated with `[blackbox]`. Like `%ps -d`, `@ps -d` prints occurrences by object type and +output/input width signature. It also reports every reachable black-box type, its instance-occurrence multiplicity, +PI/PO port and bit counts, and totals for abstract AIG inputs and outputs. Counts cover the elaborated hierarchy rooted +at the selected module (or the current design top when `-M` is absent), including repeated insts. Hierarchical totals +are accumulated over the module DAG rather than by recursively revisiting every inst, so statistics remain practical +for deeply repeated hierarchy. Memory is reported as used/allocated storage with rounded K, M, or G suffixes. `@map_mem`, `@map_dsp`, and `@map_add` map into the initial AMD/Xilinx UltraScale+ technology description. Transformations are transactional and keep the original user-visible top-module name. `@map_add` replaces word-level @@ -108,6 +116,23 @@ operand inversion, extension, and final slicing remain ordinary SN logic for sub before carry mapping so future DSP preadder and postadder recognition is not hidden. `@collapse` flattens user hierarchy while retaining mapped hard-block leaf instances. +Opaque `SN_MODULE_BLACKBOX` insts are preserved by hierarchy collapse even when ordinary user hierarchy is flattened. +During `@blast`, each opaque output is an additional GIA input and each opaque input is an additional GIA output, in +natural port and LSB-first bit order. A black-box `SN_PO` has `SN_INVALID_ID` as its sole fanin, explicitly recording +that its value has no SN implementation; no zero-valued placeholder is created. `@write` emits the preserved interface +as a port-only `(* blackbox *)` module. Internally an opaque module contains only its declared `SN_PI` and `SN_PO` +objects; an `inout` is a same-named PI/PO pair. Its body and descendants are absent from SN. `@check` permits the +invalid PO fanin only for this boundary representation, and `@ps -v` / `@ps -d` expose the retained black boxes and +their reachable occurrence counts. + +`SN_CAST` is a one-fanin operator whose object width and signedness define the result type. It does not permute bits. +An equal-width cast only changes the signedness annotation; widening sign-extends a signed result and zero-extends an +unsigned result; narrowing discards high bits and retains the LSB-first low-order portion. `sn_slang` adds casts for +explicit and implicit slang conversions, `$signed` / `$unsigned`, dynamic selected-value normalization, packed-value +updates, and final normalization of `SN_MUX` data branches to the mux result width. Memory, DSP, and carry mapping may +also introduce casts while adapting word-level values to primitive interfaces. The Verilog writer uses `$signed` or +`$unsigned` on a result-width wire, and the bit-blaster implements the same extension or truncation directly. + `@opt_mux` restructures register mux cones by collecting root-to-terminal paths, grouping structurally identical LSB-first word values, and ORing the corresponding path conditions. A register-output terminal is converted into an explicit enable when the path controls are provably exclusive. The pass currently recognizes ordinary `SN_MUX` @@ -194,10 +219,12 @@ instances as well as ordinary SN logic. Mapped RAM/DSP/CARRY4 instances are reconstructed as technology leaf instances. SN loop-breaker pairs connect their output ports while the new flat module is built and are placed into a legal order by the final topological reorder. -Temporary loop pairs are pruned after reconnection unless an actual feedback dependency remains, so acyclic datapaths -do not gain artificial loop-breakers. Generic unmapped memory endpoints are recorded and abstracted by `@blast`, but -`@put` currently rejects them because the boundary does not yet retain enough per-memory-port ownership data. This -check prevents silent loss or misconnection of stateful memories. +Temporary primitive-output loop pairs are pruned after reconnection unless an actual feedback dependency remains, so +acyclic datapaths do not gain artificial loop-breakers. Explicit loop boundaries extracted from the original SN module +are reconstructed unchanged; they are not currently re-proved unnecessary after `&`-space optimization. Generic +unmapped memory endpoints are recorded and abstracted by `@blast`, but `@put` currently rejects them because the +boundary does not yet retain enough per-memory-port ownership data. This check prevents silent loss or misconnection +of stateful memories. ## Source files diff --git a/src/base/sn/sn.h b/src/base/sn/sn.h index 43150488c..415953433 100644 --- a/src/base/sn/sn.h +++ b/src/base/sn/sn.h @@ -82,6 +82,14 @@ ABC_NAMESPACE_HEADER_START // bidirectional lookup without storing another object ID. Constructors create // adjacent OUT/IN objects, but topologically reordered modules need not retain // that adjacency; the shared type ID is the authoritative pairing invariant. +// +// A module marked SN_MODULE_BLACKBOX retains only its declared PI/PO interface. +// Its implementation is intentionally opaque: hierarchy collapse preserves its +// insts, AIG construction abstracts their outputs as CIs and inputs as COs, and +// the Verilog writer emits a black-box module declaration without a body. Each +// black-box PO has SN_INVALID_ID as its sole fanin, denoting an unimplemented +// boundary value rather than an ordinary undriven net or a zero constant. An +// inout port is represented by same-named PI and PO objects. #define SN_INVALID_ID UINT32_MAX @@ -91,6 +99,14 @@ typedef uint32_t sn_name_id_t; typedef uint32_t sn_type_id_t; typedef uint16_t sn_fanin_count_t; +typedef enum sn_module_flag_t +{ + SN_MODULE_NO_FLAGS = 0, + SN_MODULE_BLACKBOX = 1u << 0 +} sn_module_flag_t; + +#define SN_MODULE_ALL_FLAGS ((uint32_t)SN_MODULE_BLACKBOX) + // A C-style generic vector. Cap and size are measured in elements. // Access macros take the element type explicitly, for example: // @@ -272,6 +288,10 @@ enum sn_obj_type_enum // least-significant result bits, and later fanins supply successively more- // significant bits. Repetition has one fanin and a type-indexed repeat count. // Slice has one fanin and type-indexed left, right, and direction data. + // Cast also has one fanin. Its object width and signedness define the result: + // widening sign-extends a signed result and zero-extends an unsigned result; + // narrowing retains the LSB-first low-order bits; equal-width conversion + // changes only the signedness annotation. SN_CONCAT, SN_REPLICATE, SN_SLICE, @@ -419,6 +439,7 @@ typedef struct sn_module_t sn_design_t* design; sn_module_id_t id; sn_name_id_t name; + uint32_t flags; // Core object attributes, all indexed by sn_obj_id_t. sn_vec_t obj_types; @@ -838,6 +859,7 @@ static inline void sn_module_init(sn_module_t* module, sn_design_t* design, sn_m module->design = design; module->id = id; module->name = name; + module->flags = SN_MODULE_NO_FLAGS; sn_vec_init(&module->obj_types); sn_vec_init(&module->width_signed); @@ -923,21 +945,27 @@ static inline void sn_design_destroy(sn_design_t* design) free(design); } +static inline sn_module_id_t sn_design_add_module_name_id(sn_design_t* design, sn_name_id_t name) +{ + assert(design); + assert(name < design->names.names.size); + assert(design->modules.size < SN_INVALID_ID); + sn_module_id_t id = (sn_module_id_t)design->modules.size; + sn_module_t* module = (sn_module_t*)calloc(1, sizeof(sn_module_t)); + assert(module); + sn_module_init(module, design, id, name); + *sn_vec_push(sn_module_t*, &design->modules) = module; + return id; +} + static inline sn_module_id_t sn_design_add_module(sn_design_t* design, const char* name) { assert(design); assert(name); - assert(design->modules.size < SN_INVALID_ID); sn_name_id_t name_id = sn_name_intern(&design->names, name); for (size_t i = 0; i < design->modules.size; i++) assert(sn_vec_at(sn_module_t*, &design->modules, i)->name != name_id); - - sn_module_id_t id = (sn_module_id_t)design->modules.size; - sn_module_t* module = (sn_module_t*)calloc(1, sizeof(sn_module_t)); - assert(module); - sn_module_init(module, design, id, name_id); - *sn_vec_push(sn_module_t*, &design->modules) = module; - return id; + return sn_design_add_module_name_id(design, name_id); } static inline sn_module_t* sn_design_get_module(sn_design_t* design, sn_module_id_t id) @@ -967,6 +995,21 @@ static inline sn_module_id_t sn_design_find_module(const sn_design_t* design, co return SN_INVALID_ID; } +static inline bool sn_module_is_blackbox(const sn_module_t* module) +{ + assert(module); + return (module->flags & SN_MODULE_BLACKBOX) != 0; +} + +static inline void sn_module_set_blackbox(sn_module_t* module, bool blackbox) +{ + assert(module); + if (blackbox) + module->flags |= SN_MODULE_BLACKBOX; + else + module->flags &= ~((uint32_t)SN_MODULE_BLACKBOX); +} + // Deep-copy the semantic design state directly in memory. Derived constant-interner tables are intentionally left // empty and rebuilt lazily, matching binary roundtrip behavior. Fanout caches and optional object-copy maps are // preserved because callers may intentionally retain them between transformations. @@ -986,6 +1029,7 @@ static inline sn_design_t* sn_design_dup(const sn_design_t* source) sn_module_id_t new_id = sn_design_add_module(target, sn_name_get(&source->names, old_module->name)); assert(new_id == module_id); sn_module_t* new_module = sn_design_get_module(target, new_id); + new_module->flags = old_module->flags; new_module->fanouts_valid = old_module->fanouts_valid; new_module->interface_locked = old_module->interface_locked; new_module->copy_module = old_module->copy_module; @@ -1224,36 +1268,46 @@ static inline void sn_obj_connect(sn_module_t* module, sn_obj_id_t object, uint3 sn_module_invalidate_fanouts(module); } -static inline void sn_obj_add_fanin(sn_module_t* module, sn_obj_id_t object, sn_obj_id_t fanin) +static inline void sn_obj_add_fanins(sn_module_t* module, sn_obj_id_t object, uint32_t added_count, + const sn_obj_id_t* added_fanins) { assert(module); assert(object < module->obj_types.size); - assert(fanin < module->obj_types.size); - assert(module->fanins.size < UINT32_MAX); + assert(added_count == 0 || added_fanins); + assert(module->fanins.size + added_count <= UINT32_MAX); + for (uint32_t i = 0; i < added_count; i++) + assert(added_fanins[i] < module->obj_types.size); + if (!added_count) + return; uint32_t offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); uint32_t count = sn_obj_fanin_count(module, object); - assert(count < UINT16_MAX); + assert(added_count <= UINT16_MAX - count); size_t insertion = (size_t)offset + count; assert(insertion <= module->fanins.size); size_t old_size = module->fanins.size; - sn_vec_resize(sn_obj_id_t, &module->fanins, old_size + 1); + sn_vec_resize(sn_obj_id_t, &module->fanins, old_size + added_count); sn_obj_id_t* fanins = sn_vec_data(sn_obj_id_t, &module->fanins); - memmove(fanins + insertion + 1, fanins + insertion, (old_size - insertion) * sizeof(*fanins)); - fanins[insertion] = fanin; - sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object) = (sn_fanin_count_t)(count + 1); + memmove(fanins + insertion + added_count, fanins + insertion, (old_size - insertion) * sizeof(*fanins)); + memcpy(fanins + insertion, added_fanins, (size_t)added_count * sizeof(*fanins)); + sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object) = (sn_fanin_count_t)(count + added_count); // Preserve fanin-span order for every object after the modified object. for (sn_obj_id_t other = object + 1; other < module->obj_types.size; other++) { uint32_t other_offset = sn_vec_at(uint32_t, &module->fanin_offsets, other); if (other_offset >= insertion) - sn_vec_at(uint32_t, &module->fanin_offsets, other) = other_offset + 1; + sn_vec_at(uint32_t, &module->fanin_offsets, other) = other_offset + added_count; } sn_module_invalidate_fanouts(module); } +static inline void sn_obj_add_fanin(sn_module_t* module, sn_obj_id_t object, sn_obj_id_t fanin) +{ + sn_obj_add_fanins(module, object, 1, &fanin); +} + static inline sn_obj_id_t sn_module_add_pi(sn_module_t* module, uint32_t width, bool is_signed, const char* name) { assert(module); @@ -1265,6 +1319,7 @@ static inline sn_obj_id_t sn_module_add_po(sn_module_t* module, uint32_t width, sn_obj_id_t driver) { assert(module); + assert(!sn_module_is_blackbox(module)); assert(!module->interface_locked); assert(driver < module->obj_types.size); assert(sn_obj_width(module, driver) == width); @@ -1273,6 +1328,36 @@ static inline sn_obj_id_t sn_module_add_po(sn_module_t* module, uint32_t width, return output; } +// Adds an output port to an opaque module. Unlike an ordinary SN_PO, whose +// only fanin is its RTL driver, a black-box output deliberately has no driver +// inside SN. Parent insts expose it through their normal SN_INST/SN_FAN +// boundary objects; collapse preserves that boundary and blasting abstracts +// the value as a new combinational input. +static inline sn_obj_id_t sn_module_add_blackbox_po(sn_module_t* module, uint32_t width, bool is_signed, + const char* name) +{ + assert(module); + assert(sn_module_is_blackbox(module)); + assert(!module->interface_locked); + return sn_module_add_named_obj(module, SN_PO, width, is_signed, 1, name); +} + +static inline bool sn_obj_fanin_may_be_invalid(const sn_module_t* module, sn_obj_type_t type, uint32_t index) +{ + assert(module); + if (type == SN_PO) + return sn_module_is_blackbox(module) && index == 0; + if (type == SN_REG_OUT) + return index != SN_REG_DATA; + if (type == SN_MEM_OUT) + return index == SN_MEM_INIT_DATA || index == SN_MEM_INIT_MASK; + if (type == SN_MEM_READ) + return index == SN_MEM_READ_CLOCK || index == SN_MEM_READ_ENABLE; + if (type == SN_MEM_WRITE) + return index == SN_MEM_WRITE_ENABLE; + return false; +} + static inline bool sn_obj_type_is_operator(sn_obj_type_t type) { return type == SN_BUF || (type >= SN_POS && type < SN_OBJ_TYPE_COUNT); @@ -1858,9 +1943,10 @@ static inline sn_obj_id_t sn_module_add_mem_read(sn_module_t* module, sn_obj_id_ return read; } -static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id_t mem_in, sn_obj_id_t clock, - sn_obj_id_t enable, sn_obj_id_t data, sn_obj_id_t address, - const char* name) +static inline sn_obj_id_t sn_module_add_mem_write_unlinked(sn_module_t* module, sn_obj_id_t mem_in, + sn_obj_id_t clock, sn_obj_id_t enable, + sn_obj_id_t data, sn_obj_id_t address, + const char* name) { assert(module); assert(sn_obj_type(module, mem_in) == SN_MEM_IN); @@ -1875,6 +1961,14 @@ static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id sn_obj_connect(module, write, SN_MEM_WRITE_ENABLE, enable); sn_obj_connect(module, write, SN_MEM_WRITE_DATA, data); sn_obj_connect(module, write, SN_MEM_WRITE_ADDRESS, address); + return write; +} + +static inline sn_obj_id_t sn_module_add_mem_write(sn_module_t* module, sn_obj_id_t mem_in, sn_obj_id_t clock, + sn_obj_id_t enable, sn_obj_id_t data, sn_obj_id_t address, + const char* name) +{ + sn_obj_id_t write = sn_module_add_mem_write_unlinked(module, mem_in, clock, enable, data, address, name); sn_obj_add_fanin(module, mem_in, write); return write; } @@ -2005,6 +2099,11 @@ static inline void sn_design_print_hierarchy_rec(FILE* out, const sn_design_t* d fputs(" [recursive]\n", out); return; } + if (sn_module_is_blackbox(module)) + { + fputs(" [blackbox]\n", out); + return; + } fputc('\n', out); active_modules[module_id] = true; @@ -2533,6 +2632,7 @@ static inline sn_module_id_t sn_design_dup_module_topo(sn_design_t* design, sn_m sn_vec_t order = sn_module_topo_order(source); sn_module_id_t target_module_id = sn_design_add_module(design, new_name); sn_module_t* target = sn_design_get_module(design, target_module_id); + target->flags = source->flags; sn_vec_resize(sn_obj_id_t, &source->copy_ids, source->obj_types.size); for (size_t i = 0; i < source->copy_ids.size; i++) @@ -3054,6 +3154,7 @@ static inline sn_module_id_t sn_design_dup_module_clean_topo(sn_design_t* design sn_module_id_t target_id = sn_design_add_module(design, new_name); sn_module_t* target = sn_design_get_module(design, target_id); + target->flags = source->flags; sn_vec_resize(sn_obj_id_t, &source->copy_ids, object_count); for (size_t i = 0; i < object_count; i++) sn_vec_at(sn_obj_id_t, &source->copy_ids, i) = SN_INVALID_ID; @@ -3071,9 +3172,19 @@ static inline sn_module_id_t sn_design_dup_module_clean_topo(sn_design_t* design continue; uint32_t bits = sn_obj_width(source, old_object); uint32_t* words = (uint32_t*)calloc(sn_const_word_count(bits), sizeof(uint32_t)); + sn_name_id_t source_name_id = sn_obj_name_id(source, old_object); + const char* source_name = source_name_id == SN_INVALID_ID ? NULL : + sn_name_get(&source->design->names, source_name_id); + char* name = source_name ? (char*)malloc(strlen(source_name) + 1) : NULL; assert(words); + if (source_name) + { + assert(name); + memcpy(name, source_name, strlen(source_name) + 1); + } sn_vec_at(sn_obj_id_t, &source->copy_ids, old_object) = - sn_module_add_const(target, bits, sn_obj_is_signed(source, old_object), words, NULL); + sn_module_add_const(target, bits, sn_obj_is_signed(source, old_object), words, name); + free(name); free(words); } for (size_t i = input_count; i < order.size; i++) @@ -3179,6 +3290,8 @@ typedef struct sn_collapse_context_t static inline bool sn_module_is_technology_primitive(const sn_module_t* module) { assert(module); + if (sn_module_is_blackbox(module)) + return true; const char* name = sn_name_get(&module->design->names, module->name); return strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0 || strncmp(name, "__sn_DSP", 8) == 0 || strncmp(name, "__sn_CARRY", 10) == 0; @@ -3187,15 +3300,15 @@ static inline bool sn_module_is_technology_primitive(const sn_module_t* module) static inline bool sn_collapse_preserves_object(const sn_collapse_context_t* context, const sn_module_t* source, sn_obj_id_t object) { - if (!context->preserve_technology_primitives) - return false; sn_obj_type_t type = sn_obj_type(source, object); sn_obj_id_t inst = type == SN_INST ? object : type == SN_FAN ? sn_fan_inst_id(source, object) : SN_INVALID_ID; if (inst == SN_INVALID_ID) return false; - return sn_module_is_technology_primitive( - sn_design_get_module_const(context->design, sn_inst_module_id(source, inst))); + const sn_module_t* child = sn_design_get_module_const(context->design, sn_inst_module_id(source, inst)); + if (sn_module_is_blackbox(child)) + return true; + return context->preserve_technology_primitives && sn_module_is_technology_primitive(child); } static inline bool sn_collapse_obj_is_copied(sn_obj_type_t type, bool is_top) @@ -3427,11 +3540,9 @@ static inline sn_module_id_t sn_design_collapse_module_internal(sn_design_t* des sn_module_rebuild_pair_type_ids(flat, SN_MEM_OUT, SN_MEM_IN, SN_MEM_STATE); sn_module_rebuild_pair_type_ids(flat, SN_LOOP_OUT, SN_LOOP_IN, 0); - if (!preserve_technology_primitives) - { - assert(flat->type_objects[SN_INST].size == 0); - assert(flat->type_objects[SN_FAN].size == 0); - } + for (size_t i = 0; i < flat->inst_modules.size; i++) + assert(sn_module_is_technology_primitive( + sn_design_get_module_const(design, sn_vec_at(sn_module_id_t, &flat->inst_modules, i)))); assert(sn_module_is_topo(flat)); return flat_module_id; } @@ -4270,6 +4381,8 @@ static inline void sn_module_write_verilog_as(FILE* out, const sn_module_t* modu write_memories[write_id] = memory; } } + if (sn_module_is_blackbox(module)) + fputs("(* blackbox *) ", out); fputs("module ", out); sn_write_verilog_identifier(out, emitted_name); fputs(" (", out); @@ -4314,6 +4427,13 @@ static inline void sn_module_write_verilog_as(FILE* out, const sn_module_t* modu fputc(10, out); } + if (sn_module_is_blackbox(module)) + { + fputs("endmodule\n\n", out); + free(write_memories); + return; + } + for (sn_obj_id_t object = 0; object < module->obj_types.size; object++) { sn_obj_type_t type = sn_obj_type(module, object); @@ -4527,7 +4647,8 @@ static inline void sn_design_write_verilog_file(const sn_design_t* design, const // data words are unsigned 32-bit values. A format change must increment the // version below. -#define SN_BINARY_FORMAT_VERSION 5u +#define SN_BINARY_FORMAT_VERSION 6u +#define SN_BINARY_MIN_READ_VERSION 5u // The format version covers field-layout changes. This signature additionally binds every serialized object type to // its numeric value, so reordering the enum cannot silently reinterpret an otherwise same-sized binary design. @@ -4839,6 +4960,7 @@ static inline void sn_module_assert_valid(const sn_module_t* module) assert(module->id < module->design->modules.size); assert(sn_design_get_module_const(module->design, module->id) == module); assert(module->name < module->design->names.names.size); + assert((module->flags & ~SN_MODULE_ALL_FLAGS) == 0); size_t object_count = module->obj_types.size; assert(object_count < SN_INVALID_ID); @@ -4865,12 +4987,23 @@ static inline void sn_module_assert_valid(const sn_module_t* module) for (uint32_t i = 0; i < fanin_count; i++) { sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, fanin_offset + i); - assert(fanin == SN_INVALID_ID || fanin < object_count); + assert(fanin < object_count || + (fanin == SN_INVALID_ID && sn_obj_fanin_may_be_invalid(module, type, i))); } expected_fanin_offset += fanin_count; } assert(expected_fanin_offset == module->fanins.size); + if (sn_module_is_blackbox(module)) + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_obj_type(module, object); + assert(type == SN_PI || type == SN_PO); + if (type == SN_PO) + assert(sn_obj_fanin_count(module, object) == 1 && + sn_obj_fanin(module, object, 0) == SN_INVALID_ID); + } + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) for (uint32_t type_id = 0; type_id < module->type_objects[type].size; type_id++) { @@ -5048,6 +5181,7 @@ static inline void sn_design_assert_valid(const sn_design_t* design) static inline void sn_binary_write_module(sn_binary_writer_t* writer, const sn_module_t* module) { sn_binary_write_u32(writer, module->name); + sn_binary_write_u32(writer, module->flags); sn_binary_write_u32(writer, module->fanouts_valid ? 1u : 0u); sn_binary_write_u32(writer, module->interface_locked ? 1u : 0u); sn_binary_write_u32(writer, module->copy_module); @@ -5075,23 +5209,27 @@ static inline void sn_binary_write_module(sn_binary_writer_t* writer, const sn_m sn_binary_write_u32_vec(writer, &module->copy_ids); } -static inline bool sn_binary_read_module(sn_binary_reader_t* reader, sn_design_t* design, sn_module_id_t expected_id) +static inline bool sn_binary_read_module(sn_binary_reader_t* reader, sn_design_t* design, sn_module_id_t expected_id, + uint32_t version, uint8_t* module_name_seen) { sn_name_id_t name = sn_binary_read_u32(reader); + uint32_t flags = version >= 6 ? sn_binary_read_u32(reader) : SN_MODULE_NO_FLAGS; uint32_t fanouts_valid = sn_binary_read_u32(reader); uint32_t interface_locked = sn_binary_read_u32(reader); sn_module_id_t copy_module = sn_binary_read_u32(reader); - if (!reader->valid || name >= design->names.names.size || fanouts_valid > 1 || interface_locked > 1 || - sn_design_find_module(design, sn_name_get(&design->names, name)) != SN_INVALID_ID) + if (!reader->valid || name >= design->names.names.size || module_name_seen[name] || + (flags & ~SN_MODULE_ALL_FLAGS) != 0 || fanouts_valid > 1 || interface_locked > 1) { reader->valid = false; return false; } - sn_module_id_t id = sn_design_add_module(design, sn_name_get(&design->names, name)); + module_name_seen[name] = 1; + sn_module_id_t id = sn_design_add_module_name_id(design, name); assert(id == expected_id); sn_module_t* module = sn_design_get_module(design, id); assert(module->name == name); + module->flags = flags; module->fanouts_valid = fanouts_valid != 0; module->interface_locked = interface_locked != 0; module->copy_module = copy_module; @@ -5194,7 +5332,7 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ status = SN_BINARY_READ_IO; else if (memcmp(magic, expected_magic, sizeof(magic)) != 0) status = SN_BINARY_READ_MAGIC; - else if (version != SN_BINARY_FORMAT_VERSION) + else if (version < SN_BINARY_MIN_READ_VERSION || version > SN_BINARY_FORMAT_VERSION) status = SN_BINARY_READ_VERSION; else if (layout_signature != sn_binary_layout_signature() || object_type_count != SN_OBJ_TYPE_COUNT || register_fanin_count != SN_REG_FANIN_COUNT || @@ -5220,7 +5358,11 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ break; } char* name = (char*)malloc(length + 1); - assert(name); + if (!name) + { + reader.valid = false; + break; + } sn_binary_read_bytes(&reader, name, length); name[length] = 0; if (memchr(name, 0, length) != NULL) @@ -5241,8 +5383,12 @@ static inline sn_design_t* sn_design_read_binary_raw_status(FILE* in, sn_binary_ size_t module_count = sn_binary_read_size(&reader); if (!reader.valid || module_count >= SN_INVALID_ID || module_count > reader.remaining / 16) reader.valid = false; + uint8_t* module_name_seen = name_count ? (uint8_t*)calloc(name_count, 1) : NULL; + if (reader.valid && module_count && !module_name_seen) + reader.valid = false; for (sn_module_id_t i = 0; reader.valid && i < module_count; i++) - sn_binary_read_module(&reader, design, i); + sn_binary_read_module(&reader, design, i, version, module_name_seen); + free(module_name_seen); if (!reader.valid) { sn_design_destroy(design); diff --git a/src/base/sn/snBlast.h b/src/base/sn/snBlast.h index d9e908a33..561defa2a 100644 --- a/src/base/sn/snBlast.h +++ b/src/base/sn/snBlast.h @@ -957,6 +957,7 @@ static inline int* sn_blast_shift(sn_blast_ctx_t* ctx, sn_obj_id_t object, bool int fill = arithmetic && !left && sn_obj_is_signed(m, value_id) ? current[work_width - 1] : Mini_AigLitConst0(); uint32_t useful_stages = 0; + // SN widths are capped below 2^31, so this unsigned shift never reaches 32. while ((UINT32_C(1) << useful_stages) < work_width) useful_stages++; uint32_t stage_count = amount_width < useful_stages ? amount_width : useful_stages; @@ -1186,6 +1187,7 @@ static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object) { uint32_t count = sn_obj_fanin_count(m, object); int inputs[6]; + assert(count <= sizeof(inputs) / sizeof(inputs[0])); for (uint32_t i = 0; i < count; i++) inputs[i] = sn_blast_eval(ctx, sn_obj_fanin(m, object, i))[0]; result = sn_blast_alloc_bits(1); @@ -1206,8 +1208,7 @@ static inline int* sn_blast_eval(sn_blast_ctx_t* ctx, sn_obj_id_t object) ctx->options.delay_comparators); result = sn_blast_alloc_bits(width); for (uint32_t i = 0; i < width; i++) - result[i] = i < work_width ? quotient_or_remainder[i] - : (signed_operands ? quotient_or_remainder[work_width - 1] : 0); + result[i] = quotient_or_remainder[i]; free(a); free(b); free(quotient_or_remainder); @@ -1475,6 +1476,8 @@ static inline bool sn_blast_hier_is_abstract_inst(const sn_blast_hier_t* hierarc { const sn_module_t* child = sn_design_get_module_const(hierarchy->design, sn_inst_module_id(module, inst)); + if (sn_module_is_blackbox(child)) + return true; const char* name = sn_name_get(&hierarchy->design->names, child->name); bool memory = strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0; bool multiplier = strncmp(name, "__sn_DSP", 8) == 0; diff --git a/src/base/sn/snBoundary.h b/src/base/sn/snBoundary.h index 1d878f20d..d955a7281 100644 --- a/src/base/sn/snBoundary.h +++ b/src/base/sn/snBoundary.h @@ -488,37 +488,99 @@ static inline sn_obj_id_t sn_boundary_co_word(sn_boundary_regs_t* regs, const sn return sn_boundary_pack_bits(regs->result, co_drivers + begin, width, "boundary_word"); } -static inline bool sn_boundary_depends_on(const sn_module_t* module, sn_obj_id_t root, sn_obj_id_t dependency) +typedef struct sn_boundary_dfs_frame_t { - uint8_t* visited = (uint8_t*)calloc(module->obj_types.size, sizeof(uint8_t)); - sn_vec_t stack; - assert(visited); + sn_obj_id_t object; + uint32_t next_fanout; +} sn_boundary_dfs_frame_t; + +// Marks tentative primitive-output substitutions that create combinational feedback. All temporary pair outputs +// have already been replaced by the corresponding primitive outputs. One iterative Kosaraju traversal identifies +// the resulting strongly connected components; a substituted edge whose endpoints share a component must retain +// its loop pair. This replaces one complete cone walk per primitive output by linear whole-module graph work. +static inline void sn_boundary_mark_feedback_pairs(sn_module_t* module, const sn_obj_id_t* actual_to_pair, + uint8_t* keep) +{ + size_t object_count = module->obj_types.size; + uint8_t* visited = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + uint32_t* components = object_count ? (uint32_t*)malloc(object_count * sizeof(uint32_t)) : NULL; + sn_vec_t order, stack; + assert(visited && (components || object_count == 0)); + sn_vec_init(&order); sn_vec_init(&stack); - *sn_vec_push(sn_obj_id_t, &stack) = root; - while (stack.size) + sn_vec_reserve(sn_obj_id_t, &order, object_count); + sn_module_build_fanouts(module); + + for (sn_obj_id_t start = 0; start < object_count; start++) { - sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &stack, --stack.size); - if (object == dependency) - { - sn_vec_destroy(&stack); - free(visited); - return true; - } - if (visited[object]) + if (visited[start]) continue; - visited[object] = 1; - if (sn_obj_type_is_pair_out(sn_obj_type(module, object))) - continue; - for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + visited[start] = 1; + sn_boundary_dfs_frame_t* first = sn_vec_push(sn_boundary_dfs_frame_t, &stack); + first->object = start; + first->next_fanout = 0; + while (stack.size) { - sn_obj_id_t fanin = sn_obj_fanin(module, object, i); - if (fanin != SN_INVALID_ID && !visited[fanin]) - *sn_vec_push(sn_obj_id_t, &stack) = fanin; + sn_boundary_dfs_frame_t* frame = + &sn_vec_at(sn_boundary_dfs_frame_t, &stack, stack.size - 1); + uint32_t count = sn_obj_fanout_count(module, frame->object); + if (frame->next_fanout < count) + { + sn_obj_id_t fanout = sn_obj_fanout(module, frame->object, frame->next_fanout++); + if (!visited[fanout]) + { + visited[fanout] = 1; + sn_boundary_dfs_frame_t* child = sn_vec_push(sn_boundary_dfs_frame_t, &stack); + child->object = fanout; + child->next_fanout = 0; + } + continue; + } + *sn_vec_push(sn_obj_id_t, &order) = frame->object; + stack.size--; } } + + for (sn_obj_id_t object = 0; object < object_count; object++) + components[object] = UINT32_MAX; + uint32_t component_count = 0; + for (size_t i = order.size; i-- > 0;) + { + sn_obj_id_t start = sn_vec_at(sn_obj_id_t, &order, i); + if (components[start] != UINT32_MAX) + continue; + components[start] = component_count; + *sn_vec_push(sn_obj_id_t, &stack) = start; + while (stack.size) + { + sn_obj_id_t object = sn_vec_at(sn_obj_id_t, &stack, --stack.size); + for (uint32_t k = 0; k < sn_obj_fanin_count(module, object); k++) + { + sn_obj_id_t fanin = sn_obj_fanin(module, object, k); + if (fanin != SN_INVALID_ID && components[fanin] == UINT32_MAX) + { + components[fanin] = component_count; + *sn_vec_push(sn_obj_id_t, &stack) = fanin; + } + } + } + component_count++; + } + + for (sn_obj_id_t object = 0; object < object_count; object++) + for (uint32_t i = 0; i < sn_obj_fanin_count(module, object); i++) + { + sn_obj_id_t actual = sn_obj_fanin(module, object, i); + sn_obj_id_t pair_out = actual == SN_INVALID_ID ? SN_INVALID_ID : actual_to_pair[actual]; + if (pair_out != SN_INVALID_ID && components[actual] == components[object]) + keep[pair_out] = 1; + } + + sn_module_invalidate_fanouts(module); + sn_vec_destroy(&order); sn_vec_destroy(&stack); + free(components); free(visited); - return false; } // Duplicates a module in topological order while omitting an explicitly unreferenced set of objects. This is used @@ -618,8 +680,13 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) sn_module_t* source = regs->result; size_t object_count = source->obj_types.size; uint8_t* remove = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + uint8_t* keep = (uint8_t*)calloc(object_count, sizeof(uint8_t)); + sn_obj_id_t* replacement = object_count ? (sn_obj_id_t*)malloc(object_count * sizeof(sn_obj_id_t)) : NULL; + sn_obj_id_t* actual_to_pair = object_count ? (sn_obj_id_t*)malloc(object_count * sizeof(sn_obj_id_t)) : NULL; size_t remove_count = 0; - assert(remove); + assert(remove && keep && (replacement || object_count == 0) && (actual_to_pair || object_count == 0)); + for (sn_obj_id_t object = 0; object < object_count; object++) + replacement[object] = actual_to_pair[object] = SN_INVALID_ID; for (size_t i = 0; i < regs->boundary->primitives.size; i++) { const sn_blast_primitive_t* entry = &sn_vec_at(sn_blast_primitive_t, ®s->boundary->primitives, i); @@ -628,16 +695,39 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) { sn_obj_pair_t pair = regs->primitive_pairs[regs->primitive_offsets[i] + output]; sn_obj_id_t actual = sn_obj_fanin(source, pair.in, 0); - sn_obj_id_t inst = sn_obj_type(source, actual) == SN_FAN ? sn_fan_inst_id(source, actual) : actual; - if (sn_boundary_depends_on(source, inst, pair.out)) - continue; - for (size_t k = 0; k < source->fanins.size; k++) - if (sn_vec_at(sn_obj_id_t, &source->fanins, k) == pair.out) - sn_vec_at(sn_obj_id_t, &source->fanins, k) = actual; - remove[pair.out] = remove[pair.in] = 1; - remove_count += 2; + assert(replacement[pair.out] == SN_INVALID_ID && actual_to_pair[actual] == SN_INVALID_ID); + replacement[pair.out] = actual; + actual_to_pair[actual] = pair.out; } } + for (size_t i = 0; i < source->fanins.size; i++) + { + sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &source->fanins, i); + if (fanin != SN_INVALID_ID && replacement[fanin] != SN_INVALID_ID) + sn_vec_at(sn_obj_id_t, &source->fanins, i) = replacement[fanin]; + } + sn_module_invalidate_fanouts(source); + sn_boundary_mark_feedback_pairs(source, actual_to_pair, keep); + for (sn_obj_id_t object = 0; object < object_count; object++) + for (uint32_t i = 0; i < sn_obj_fanin_count(source, object); i++) + { + sn_obj_id_t actual = sn_obj_fanin(source, object, i); + sn_obj_id_t pair_out = actual == SN_INVALID_ID ? SN_INVALID_ID : actual_to_pair[actual]; + if (pair_out != SN_INVALID_ID && keep[pair_out] && object != sn_obj_pair_in(source, pair_out)) + sn_obj_connect(source, object, i, pair_out); + } + for (sn_obj_id_t pair_out = 0; pair_out < object_count; pair_out++) + if (replacement[pair_out] != SN_INVALID_ID && !keep[pair_out]) + { + sn_obj_id_t actual = replacement[pair_out]; + sn_obj_id_t pair_in = sn_obj_pair_in(source, pair_out); + if (sn_obj_type(source, actual) == SN_FAN && sn_obj_name_id(source, actual) == SN_INVALID_ID && + sn_obj_name_id(source, pair_out) != SN_INVALID_ID) + sn_vec_at(sn_name_id_t, &source->name_ids, actual) = sn_obj_name_id(source, pair_out); + remove[pair_out] = remove[pair_in] = 1; + remove_count += 2; + } + sn_module_invalidate_fanouts(source); if (remove_count) { char name[96]; @@ -664,6 +754,9 @@ static inline void sn_boundary_prune_primitive_pairs(sn_boundary_regs_t* regs) sn_name_remove_last(®s->design->names, temporary_name); regs->result = filtered; } + free(actual_to_pair); + free(replacement); + free(keep); free(remove); } diff --git a/src/base/sn/snCheck.h b/src/base/sn/snCheck.h index bc80534a6..c559419de 100644 --- a/src/base/sn/snCheck.h +++ b/src/base/sn/snCheck.h @@ -80,6 +80,20 @@ static inline bool sn_check_const_type(sn_obj_type_t type) return type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST; } +static inline bool sn_check_name_is_emittable(const char* name) +{ + size_t i; + if (!name || !name[0]) + return false; + for (i = 0; name[i]; i++) + { + unsigned char c = (unsigned char)name[i]; + if (c <= 32 || c >= 127 || c == '\\') + return false; + } + return true; +} + static inline int sn_check_fixed_fanin_count(sn_obj_type_t type) { if (type == SN_PI || type == SN_CONST0 || type == SN_CONST1 || type == SN_CONST) @@ -108,19 +122,6 @@ static inline int sn_check_fixed_fanin_count(sn_obj_type_t type) return -1; } -static inline bool sn_check_optional_fanin(sn_obj_type_t type, uint32_t index) -{ - if (type == SN_REG_OUT) - return index != SN_REG_DATA; - if (type == SN_MEM_OUT) - return index == SN_MEM_INIT_DATA || index == SN_MEM_INIT_MASK; - if (type == SN_MEM_READ) - return index == SN_MEM_READ_CLOCK || index == SN_MEM_READ_ENABLE; - if (type == SN_MEM_WRITE) - return index == SN_MEM_WRITE_ENABLE; - return false; -} - static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* module) { const sn_design_t* design = module ? module->design : NULL; @@ -136,12 +137,14 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* "module table does not point back to this module"); SN_CHECK(ctx, module, SN_INVALID_ID, module->name < design->names.names.size, "module name ID %u is out of range", module->name); + SN_CHECK(ctx, module, SN_INVALID_ID, (module->flags & ~SN_MODULE_ALL_FLAGS) == 0, + "module flags 0x%x contain unsupported bits", module->flags); if (module->name < design->names.names.size) { - SN_CHECK(ctx, module, SN_INVALID_ID, sn_name_get(&design->names, module->name)[0] != '\0', - "module name is empty"); const char* module_name = sn_name_get(&design->names, module->name); - if (strncmp(module_name, "__sn_", 5) == 0) + SN_CHECK(ctx, module, SN_INVALID_ID, module_name != NULL && module_name[0] != '\0', + "module name is null or empty"); + if (module_name && strncmp(module_name, "__sn_", 5) == 0) SN_CHECK(ctx, module, SN_INVALID_ID, sn_module_is_technology_primitive(module), "module uses the reserved internal prefix __sn_"); } @@ -194,9 +197,14 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* SN_CHECK(ctx, module, object, name_id == SN_INVALID_ID || name_id < design->names.names.size, "name ID %u is out of range", name_id); if (type_valid && (type == SN_PI || type == SN_PO || type == SN_GATE)) + { + const char* object_name = name_id < design->names.names.size + ? sn_name_get(&design->names, name_id) + : NULL; SN_CHECK(ctx, module, object, - name_id < design->names.names.size && sn_name_get(&design->names, name_id)[0] != '\0', + object_name != NULL && object_name[0] != '\0', "type %u requires a nonempty Verilog name", (unsigned)type); + } if (type_valid) { int expected = sn_check_fixed_fanin_count(type); @@ -213,7 +221,8 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* { sn_obj_id_t fanin = sn_vec_at(sn_obj_id_t, &module->fanins, offset + i); SN_CHECK(ctx, module, object, fanin < object_count || - (fanin == SN_INVALID_ID && type_valid && sn_check_optional_fanin(type, i)), + (fanin == SN_INVALID_ID && type_valid && + sn_obj_fanin_may_be_invalid(module, type, i)), "fanin %u has invalid object ID %u", i, fanin); } offset += count; @@ -221,6 +230,23 @@ static inline bool sn_check_module_core(sn_check_ctx_t* ctx, const sn_module_t* SN_CHECK(ctx, module, SN_INVALID_ID, offset == module->fanins.size, "fanin spans use %zu entries but storage contains %zu", offset, module->fanins.size); + if (sn_module_is_blackbox(module)) + for (sn_obj_id_t object = 0; object < object_count; object++) + { + sn_obj_type_t type = sn_vec_at(sn_obj_type_t, &module->obj_types, object); + SN_CHECK(ctx, module, object, type == SN_PI || type == SN_PO, + "black-box module contains non-port object of type %u", (unsigned)type); + if (type == SN_PO) + { + uint32_t count = sn_vec_at(sn_fanin_count_t, &module->fanin_counts, object); + uint32_t po_offset = sn_vec_at(uint32_t, &module->fanin_offsets, object); + SN_CHECK(ctx, module, object, + count == 1 && po_offset < module->fanins.size && + sn_vec_at(sn_obj_id_t, &module->fanins, po_offset) == SN_INVALID_ID, + "black-box output must have one intentionally undriven fanin"); + } + } + for (uint32_t type = 0; type < SN_OBJ_TYPE_COUNT; type++) for (size_t type_id = 0; type_id < module->type_objects[type].size; type_id++) { @@ -940,47 +966,218 @@ static inline void sn_check_topology(sn_check_ctx_t* ctx, const sn_module_t* mod SN_CHECK(ctx, module, SN_INVALID_ID, valid, "objects are not in legal SN topological order"); } +static inline bool sn_check_parse_u32(const char** cursor, uint32_t* value, char delimiter) +{ + char* end; + unsigned long parsed; + if (!cursor || !*cursor || !value || **cursor < '0' || **cursor > '9') + return false; + parsed = strtoul(*cursor, &end, 10); + if (end == *cursor || parsed > UINT32_MAX || *end != delimiter) + return false; + *value = (uint32_t)parsed; + *cursor = delimiter ? end + 1 : end; + return true; +} + +static inline bool sn_check_slice_bit(const sn_module_t* module, sn_obj_id_t object, sn_obj_id_t source, + uint32_t bit) +{ + if (object == SN_INVALID_ID || object >= module->obj_types.size || sn_obj_type(module, object) != SN_SLICE || + sn_obj_fanin(module, object, 0) != source) + return false; + const sn_slice_info_t* info = sn_obj_slice_info(module, object); + return info->left_index == (int32_t)bit && info->right_index == (int32_t)bit; +} + +static inline void sn_check_carry_primitive(sn_check_ctx_t* ctx, const sn_module_t* module) +{ + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + const uint32_t pi_widths[] = {1, 1, 4, 4}; + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 4 && po_count == 2, + "carry primitive interface must have 4 inputs and 2 outputs"); + for (size_t i = 0; i < pi_count && i < 4; i++) + { + sn_obj_id_t pi = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); + SN_CHECK(ctx, module, pi, sn_obj_width(module, pi) == pi_widths[i], + "carry primitive input %zu has the wrong width", i); + } + for (size_t i = 0; i < po_count; i++) + { + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); + SN_CHECK(ctx, module, po, sn_obj_width(module, po) == 4, + "carry primitive output %zu has the wrong width", i); + } + if (pi_count != 4 || po_count != 2) + return; + sn_obj_id_t ci = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 0); + sn_obj_id_t cyinit = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 1); + sn_obj_id_t di = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 2); + sn_obj_id_t s = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 3); + sn_obj_id_t o = sn_obj_fanin(module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 0), 0); + sn_obj_id_t co = sn_obj_fanin(module, sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 1), 0); + bool packed = sn_obj_type(module, o) == SN_CONCAT && sn_obj_fanin_count(module, o) == 4 && + sn_obj_type(module, co) == SN_CONCAT && sn_obj_fanin_count(module, co) == 4; + SN_CHECK(ctx, module, SN_INVALID_ID, packed, + "carry primitive outputs must be four-bit concatenations"); + if (!packed) + return; + sn_obj_id_t first_o = sn_obj_fanin(module, o, 0); + sn_obj_id_t carry = sn_obj_type(module, first_o) == SN_BIT_XOR && sn_obj_fanin_count(module, first_o) == 2 + ? sn_obj_fanin(module, first_o, 1) + : SN_INVALID_ID; + bool initial = carry != SN_INVALID_ID && sn_obj_type(module, carry) == SN_BIT_OR && + sn_obj_fanin_count(module, carry) == 2 && + ((sn_obj_fanin(module, carry, 0) == ci && sn_obj_fanin(module, carry, 1) == cyinit) || + (sn_obj_fanin(module, carry, 0) == cyinit && sn_obj_fanin(module, carry, 1) == ci)); + SN_CHECK(ctx, module, carry, initial, "carry primitive has an invalid initial carry expression"); + for (uint32_t bit = 0; bit < 4; bit++) + { + sn_obj_id_t o_bit = sn_obj_fanin(module, o, bit); + sn_obj_id_t co_bit = sn_obj_fanin(module, co, bit); + bool o_valid = sn_obj_type(module, o_bit) == SN_BIT_XOR && sn_obj_fanin_count(module, o_bit) == 2; + bool co_valid = sn_obj_type(module, co_bit) == SN_MUX && sn_obj_fanin_count(module, co_bit) == 3; + sn_obj_id_t s_bit = o_valid ? sn_obj_fanin(module, o_bit, 0) : SN_INVALID_ID; + sn_obj_id_t di_bit = co_valid ? sn_obj_fanin(module, co_bit, SN_MUX_DEFAULT) : SN_INVALID_ID; + o_valid &= s_bit != SN_INVALID_ID && sn_obj_fanin(module, o_bit, 1) == carry && + sn_check_slice_bit(module, s_bit, s, bit); + co_valid &= s_bit != SN_INVALID_ID && di_bit != SN_INVALID_ID && + sn_obj_fanin(module, co_bit, SN_MUX_SELECT) == s_bit && + sn_obj_fanin(module, co_bit, SN_MUX_SELECTED) == carry && + sn_check_slice_bit(module, di_bit, di, bit); + SN_CHECK(ctx, module, o_bit, o_valid, "carry primitive O[%u] has invalid logic", bit); + SN_CHECK(ctx, module, co_bit, co_valid, "carry primitive CO[%u] has invalid logic", bit); + carry = co_bit; + } +} + +static inline void sn_check_dsp_primitive(sn_check_ctx_t* ctx, const sn_module_t* module, const char* name) +{ + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + size_t mul_count = module->type_objects[SN_MUL].size; + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 2 && po_count == 1, + "DSP primitive interface must have 2 inputs and 1 output"); + SN_CHECK(ctx, module, SN_INVALID_ID, mul_count == 1, + "DSP primitive behavioral wrapper must contain one multiplier"); + const char* shape = strstr(name, "_mul_"); + uint32_t a_width = 0, b_width = 0, y_width = 0; + bool parsed = shape != NULL; + const char* cursor = parsed ? shape + 5 : NULL; + parsed &= sn_check_parse_u32(&cursor, &a_width, '_'); + parsed &= sn_check_parse_u32(&cursor, &b_width, '_'); + parsed &= sn_check_parse_u32(&cursor, &y_width, '_'); + parsed &= cursor && cursor[0] == 's' && (cursor[1] == '0' || cursor[1] == '1') && + (cursor[2] == '0' || cursor[2] == '1') && cursor[3] == '\0'; + SN_CHECK(ctx, module, SN_INVALID_ID, parsed, "DSP primitive name does not encode a valid interface"); + if (pi_count != 2 || po_count != 1 || mul_count != 1 || !parsed) + return; + bool a_signed = cursor[1] == '1'; + bool b_signed = cursor[2] == '1'; + sn_obj_id_t a = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 0); + sn_obj_id_t b = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 1); + sn_obj_id_t mul = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MUL], 0); + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], 0); + bool valid = sn_obj_width(module, a) == a_width && sn_obj_is_signed(module, a) == a_signed && + sn_obj_width(module, b) == b_width && sn_obj_is_signed(module, b) == b_signed && + sn_obj_width(module, mul) == y_width && sn_obj_is_signed(module, mul) == (a_signed || b_signed) && + sn_obj_fanin_count(module, mul) == 2 && sn_obj_fanin(module, mul, 0) == a && + sn_obj_fanin(module, mul, 1) == b && sn_obj_width(module, po) == y_width && + sn_obj_is_signed(module, po) == (a_signed || b_signed) && sn_obj_fanin(module, po, 0) == mul; + SN_CHECK(ctx, module, SN_INVALID_ID, valid, + "DSP primitive behavior does not match its encoded interface"); +} + +static inline uint32_t sn_check_address_width(uint32_t depth) +{ + uint32_t width = 0; + for (uint32_t value = depth - 1; value; value >>= 1) + width++; + return width ? width : 1; +} + +static inline void sn_check_memory_primitive(sn_check_ctx_t* ctx, const sn_module_t* module, const char* name) +{ + const char* marker = strstr(name, "_tdp_tile_"); + bool tdp = marker != NULL; + bool legacy = false; + if (!marker) + marker = strstr(name, "_tile_"); + if (!marker) + { + marker = strstr(name, "_mem_"); + legacy = marker != NULL; + } + const char* cursor = marker ? marker + (tdp ? 10 : legacy ? 5 : 6) : NULL; + uint32_t width = 0, depth = 0; + bool parsed = marker && sn_check_parse_u32(&cursor, &width, '_') && + sn_check_parse_u32(&cursor, &depth, '\0') && cursor && *cursor == '\0' && width && depth; + SN_CHECK(ctx, module, SN_INVALID_ID, parsed, "memory primitive name does not encode valid dimensions"); + size_t pi_count = module->type_objects[SN_PI].size; + size_t po_count = module->type_objects[SN_PO].size; + size_t reads = module->type_objects[SN_MEM_READ].size; + size_t writes = module->type_objects[SN_MEM_WRITE].size; + SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MEM_OUT].size == 1, + "memory primitive wrapper must contain one memory"); + SN_CHECK(ctx, module, SN_INVALID_ID, reads == (tdp ? 2u : 1u) && writes == (tdp ? 2u : 1u), + "memory primitive wrapper has the wrong number of read or write ports"); + SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == (tdp ? 8u : 5u) && po_count == reads, + "memory primitive interface has the wrong number of ports"); + if (!parsed || module->type_objects[SN_MEM_OUT].size != 1 || reads != (tdp ? 2u : 1u) || + writes != (tdp ? 2u : 1u) || pi_count != (tdp ? 8u : 5u) || po_count != reads) + return; + sn_obj_id_t memory = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_OUT], 0); + uint32_t address_width = legacy ? 32 : sn_check_address_width(depth); + bool valid = sn_obj_width(module, memory) == width && sn_obj_mem_depth(module, memory) == depth; + for (uint32_t port = 0; port < reads; port++) + { + uint32_t base = tdp ? 4 * port : 0; + sn_obj_id_t clock = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base); + sn_obj_id_t enable = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 1); + sn_obj_id_t address = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 2); + sn_obj_id_t data = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], base + 3); + sn_obj_id_t read_address = tdp ? address : sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], 4); + sn_obj_id_t write = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_WRITE], port); + sn_obj_id_t read = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_MEM_READ], port); + sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], port); + valid &= sn_obj_width(module, clock) == 1 && sn_obj_width(module, enable) == 1 && + sn_obj_width(module, address) == address_width && sn_obj_width(module, data) == width && + sn_obj_fanin(module, write, SN_MEM_WRITE_CLOCK) == clock && + sn_obj_fanin(module, write, SN_MEM_WRITE_ENABLE) == enable && + sn_obj_fanin(module, write, SN_MEM_WRITE_DATA) == data && + sn_obj_fanin(module, write, SN_MEM_WRITE_ADDRESS) == address && + sn_obj_fanin(module, read, SN_MEM_READ_MEMORY) == memory && + sn_obj_fanin(module, read, SN_MEM_READ_CLOCK) == SN_INVALID_ID && + sn_obj_fanin(module, read, SN_MEM_READ_ENABLE) == SN_INVALID_ID && + sn_obj_width(module, read_address) == address_width && + sn_obj_fanin(module, read, SN_MEM_READ_ADDRESS) == read_address && + sn_obj_width(module, po) == width && sn_obj_fanin(module, po, 0) == read; + } + SN_CHECK(ctx, module, SN_INVALID_ID, valid, + "memory primitive behavior does not match its encoded interface"); +} + static inline void sn_check_primitive(sn_check_ctx_t* ctx, const sn_module_t* module) { const char* name = sn_check_module_name(module); - size_t pi_count = module->type_objects[SN_PI].size; - size_t po_count = module->type_objects[SN_PO].size; - if (strncmp(name, "__sn_CARRY", 10) == 0) + bool carry = strncmp(name, "__sn_CARRY", 10) == 0; + bool dsp = strncmp(name, "__sn_DSP", 8) == 0; + bool memory = strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0; + if (!carry && !dsp && !memory) + return; + if (sn_module_is_blackbox(module)) { - const uint32_t pi_widths[] = {1, 1, 4, 4}; - SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 4 && po_count == 2, - "carry primitive interface must have 4 inputs and 2 outputs"); - for (size_t i = 0; i < pi_count && i < 4; i++) - { - sn_obj_id_t pi = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PI], i); - SN_CHECK(ctx, module, pi, (sn_vec_at(uint32_t, &module->width_signed, pi) >> 1) == pi_widths[i], - "carry primitive input %zu has the wrong width", i); - } - for (size_t i = 0; i < po_count; i++) - { - sn_obj_id_t po = sn_vec_at(sn_obj_id_t, &module->type_objects[SN_PO], i); - SN_CHECK(ctx, module, po, (sn_vec_at(uint32_t, &module->width_signed, po) >> 1) == 4, - "carry primitive output %zu has the wrong width", i); - } - } - else if (strncmp(name, "__sn_DSP", 8) == 0) - { - SN_CHECK(ctx, module, SN_INVALID_ID, pi_count == 2 && po_count == 1, - "DSP primitive interface must have 2 inputs and 1 output"); - SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MUL].size == 1, - "DSP primitive behavioral wrapper must contain one multiplier"); - } - else if (strncmp(name, "__sn_RAM", 8) == 0 || strncmp(name, "__sn_URAM", 9) == 0) - { - size_t reads = module->type_objects[SN_MEM_READ].size; - size_t writes = module->type_objects[SN_MEM_WRITE].size; - SN_CHECK(ctx, module, SN_INVALID_ID, module->type_objects[SN_MEM_OUT].size == 1, - "memory primitive wrapper must contain one memory"); - SN_CHECK(ctx, module, SN_INVALID_ID, reads >= 1 && reads <= 2 && writes >= 1 && writes <= 2, - "memory primitive wrapper must contain one or two read and write ports"); - SN_CHECK(ctx, module, SN_INVALID_ID, po_count == reads, - "memory primitive output count %zu differs from read-port count %zu", po_count, reads); + SN_CHECK(ctx, module, SN_INVALID_ID, false, + "reserved __sn_ technology primitives must have a validated behavioral body"); + return; } + if (carry) + sn_check_carry_primitive(ctx, module); + else if (dsp) + sn_check_dsp_primitive(ctx, module, name); + else + sn_check_memory_primitive(ctx, module, name); } typedef struct sn_check_hierarchy_frame_t @@ -1058,6 +1255,9 @@ static inline bool sn_design_check(const sn_design_t* design, FILE* out, bool ve { const char* name = sn_vec_at(char*, &design->names.names, i); SN_CHECK(&ctx, NULL, SN_INVALID_ID, name != NULL, "name %zu has a null string", i); + if (name) + SN_CHECK(&ctx, NULL, SN_INVALID_ID, sn_check_name_is_emittable(name), + "name %zu cannot be emitted losslessly as a Verilog identifier", i); } uint8_t* name_seen = design->names.names.size ? (uint8_t*)calloc(design->names.names.size, 1) : NULL; SN_CHECK(&ctx, NULL, SN_INVALID_ID, design->names.names.size == 0 || name_seen != NULL, @@ -1169,8 +1369,8 @@ static inline sn_design_t* sn_design_read_binary_checked(FILE* in, FILE* errors) { FILE* out = errors ? errors : stderr; if (status == SN_BINARY_READ_VERSION) - fprintf(out, "Cannot read SN binary format version %u; this build requires version %u.\n", version, - SN_BINARY_FORMAT_VERSION); + fprintf(out, "Cannot read SN binary format version %u; this build supports versions %u through %u.\n", + version, SN_BINARY_MIN_READ_VERSION, SN_BINARY_FORMAT_VERSION); else if (status == SN_BINARY_READ_MAGIC) fprintf(out, "Input is not an SN binary file.\n"); else if (status == SN_BINARY_READ_LAYOUT) diff --git a/src/base/sn/snCom.c b/src/base/sn/snCom.c index 3af193e66..5e9ead0a7 100644 --- a/src/base/sn/snCom.c +++ b/src/base/sn/snCom.c @@ -291,6 +291,9 @@ static int Sn_RunProcess( char ** ppArgs ) if ( Child == 0 ) { execvp( ppArgs[0], ppArgs ); + // execvp() returns only on failure. Release the child copy so memory checkers do not report it as leaked; + // the parent's copy is unaffected and is freed by the caller. + ABC_FREE( ppArgs ); _exit( 127 ); } if ( waitpid(Child, &Status, 0) != Child ) @@ -404,8 +407,8 @@ static int Sn_DistribEntryCompare( const void * pLeft, const void * pRight ) return (int)pL->Signs - (int)pR->Signs; } -static void Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCap, - const sn_module_t * pModule, sn_obj_id_t Obj, uint64_t Mult ) +static int Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCap, + const sn_module_t * pModule, sn_obj_id_t Obj, uint64_t Mult ) { Sn_DistribEntry_t Entry; uint32_t nFanins = sn_obj_fanin_count( pModule, Obj ); @@ -431,34 +434,44 @@ static void Sn_DistribAdd( Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, s if ( pOld->OutWidth == Entry.OutWidth && pOld->In0Width == Entry.In0Width && pOld->In1Width == Entry.In1Width && pOld->FaninNum == Entry.FaninNum && pOld->Signs == Entry.Signs ) { - assert( UINT64_MAX - pOld->Occur >= Mult ); + if ( UINT64_MAX - pOld->Occur < Mult ) + return 0; pOld->Occur += Mult; - return; + return 1; } } if ( *pnEntries == *pnCap ) { + Sn_DistribEntry_t * pNew; + if ( *pnCap > SIZE_MAX / 2 / sizeof(Sn_DistribEntry_t) ) + return 0; *pnCap = *pnCap ? 2 * *pnCap : 8; - *ppEntries = ABC_REALLOC( Sn_DistribEntry_t, *ppEntries, *pnCap ); - assert( *ppEntries != NULL ); + pNew = ABC_REALLOC( Sn_DistribEntry_t, *ppEntries, *pnCap ); + if ( pNew == NULL ) + return 0; + *ppEntries = pNew; } Entry.Occur = Mult; (*ppEntries)[(*pnEntries)++] = Entry; + return 1; } -static void Sn_ModuleCollectDistrib( const sn_design_t * pDesign, sn_module_id_t ModuleId, uint64_t Mult, - Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCaps, - uint64_t * pTypeCounts ) +static int Sn_ModuleCollectDistrib( const sn_design_t * pDesign, sn_module_id_t ModuleId, uint64_t Mult, + Sn_DistribEntry_t ** ppEntries, size_t * pnEntries, size_t * pnCaps, + uint64_t * pTypeCounts ) { const sn_module_t * pModule = sn_design_get_module_const( pDesign, ModuleId ); sn_obj_id_t Obj; for ( Obj = 0; Obj < pModule->obj_types.size; Obj++ ) { sn_obj_type_t Type = sn_obj_type( pModule, Obj ); - assert( UINT64_MAX - pTypeCounts[Type] >= Mult ); + if ( UINT64_MAX - pTypeCounts[Type] < Mult ) + return 0; pTypeCounts[Type] += Mult; - Sn_DistribAdd( ppEntries + Type, pnEntries + Type, pnCaps + Type, pModule, Obj, Mult ); + if ( !Sn_DistribAdd(ppEntries + Type, pnEntries + Type, pnCaps + Type, pModule, Obj, Mult) ) + return 0; } + return 1; } typedef struct Sn_DistribFrame_t_ @@ -467,19 +480,25 @@ typedef struct Sn_DistribFrame_t_ size_t NextInst; } Sn_DistribFrame_t; -static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +// Returns the number of reachable occurrences of each module definition under Top. The hierarchy is a DAG, so a +// reverse-postorder propagation accounts for repeated insts without expanding every hierarchical occurrence. +static uint64_t * Sn_DesignCountModuleOccurrences( const sn_design_t * pDesign, sn_module_id_t Top ) { - Sn_DistribEntry_t * pEntries[SN_OBJ_TYPE_COUNT] = { NULL }; - size_t nEntries[SN_OBJ_TYPE_COUNT] = { 0 }; - size_t nCaps[SN_OBJ_TYPE_COUNT] = { 0 }; - uint64_t TypeCounts[SN_OBJ_TYPE_COUNT] = { 0 }; - unsigned char * pStates = ABC_CALLOC( unsigned char, pDesign->modules.size ); - uint64_t * pMults = ABC_CALLOC( uint64_t, pDesign->modules.size ); + unsigned char * pStates; + uint64_t * pMults; sn_vec_t Stack, Postorder; Sn_DistribFrame_t * pFrame; size_t i; - int Type; - assert( pStates != NULL && pMults != NULL ); + if ( pDesign == NULL || Top >= pDesign->modules.size ) + return NULL; + pStates = ABC_CALLOC( unsigned char, pDesign->modules.size ); + pMults = ABC_CALLOC( uint64_t, pDesign->modules.size ); + if ( pStates == NULL || pMults == NULL ) + { + ABC_FREE( pStates ); + ABC_FREE( pMults ); + return NULL; + } sn_vec_init( &Stack ); sn_vec_init( &Postorder ); pStates[Top] = 1; @@ -494,7 +513,8 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ if ( pFrame->NextInst < pModule->inst_modules.size ) { sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, pFrame->NextInst++ ); - assert( pStates[Child] != 1 ); + if ( Child >= pDesign->modules.size || pStates[Child] == 1 ) + goto fail; if ( pStates[Child] == 0 ) { pStates[Child] = 1; @@ -516,14 +536,117 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ uint64_t Mult = pMults[Module]; if ( Mult == 0 ) continue; - Sn_ModuleCollectDistrib( pDesign, Module, Mult, pEntries, nEntries, nCaps, TypeCounts ); for ( size_t k = 0; k < pModule->inst_modules.size; k++ ) { sn_module_id_t Child = sn_vec_at( sn_module_id_t, &pModule->inst_modules, k ); - assert( UINT64_MAX - pMults[Child] >= Mult ); + if ( Child >= pDesign->modules.size || UINT64_MAX - pMults[Child] < Mult ) + goto fail; pMults[Child] += Mult; } } + sn_vec_destroy( &Postorder ); + sn_vec_destroy( &Stack ); + ABC_FREE( pStates ); + return pMults; + +fail: + sn_vec_destroy( &Postorder ); + sn_vec_destroy( &Stack ); + ABC_FREE( pStates ); + ABC_FREE( pMults ); + return NULL; +} + +static int Sn_ModulePortBits( const sn_module_t * pModule, sn_obj_type_t Type, uint64_t * pBits ) +{ + uint64_t Bits = 0; + size_t i; + assert( Type == SN_PI || Type == SN_PO ); + for ( i = 0; i < pModule->type_objects[Type].size; i++ ) + { + uint32_t Width = sn_obj_width( pModule, sn_vec_at(sn_obj_id_t, &pModule->type_objects[Type], i) ); + if ( UINT64_MAX - Bits < Width ) + return 0; + Bits += Width; + } + *pBits = Bits; + return 1; +} + +static void Sn_DesignPrintBlackboxes( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +{ + const sn_module_t * pTop = sn_design_get_module_const( pDesign, Top ); + uint64_t * pMults = Sn_DesignCountModuleOccurrences( pDesign, Top ); + uint64_t nOccurrences = 0, nAigInputs = 0, nAigOutputs = 0; + size_t nTypes = 0, i; + if ( pMults == NULL ) + { + fprintf( pOut, "Cannot count black-box occurrences: hierarchy is invalid or the count overflows.\n" ); + return; + } + fprintf( pOut, "Black boxes reachable from \"%s\":\n", sn_name_get(&pDesign->names, pTop->name) ); + fprintf( pOut, "Module occurrences PI ports/bits PO ports/bits " + "AIG inputs AIG outputs\n" ); + for ( i = 0; i < pDesign->modules.size; i++ ) + { + const sn_module_t * pModule = sn_design_get_module_const( pDesign, (sn_module_id_t)i ); + uint64_t Mult = pMults[i], PiBits, PoBits, AigInputs, AigOutputs; + if ( Mult == 0 || !sn_module_is_blackbox(pModule) ) + continue; + if ( !Sn_ModulePortBits(pModule, SN_PI, &PiBits) || !Sn_ModulePortBits(pModule, SN_PO, &PoBits) || + (PoBits != 0 && Mult > UINT64_MAX / PoBits) || + (PiBits != 0 && Mult > UINT64_MAX / PiBits) ) + goto overflow; + AigInputs = Mult * PoBits; + AigOutputs = Mult * PiBits; + if ( UINT64_MAX - nOccurrences < Mult || UINT64_MAX - nAigInputs < AigInputs || + UINT64_MAX - nAigOutputs < AigOutputs ) + goto overflow; + nTypes++; + nOccurrences += Mult; + nAigInputs += AigInputs; + nAigOutputs += AigOutputs; + fprintf( pOut, "%-32s %10llu %6zu/%-6llu %6zu/%-6llu %10llu %11llu\n", + sn_name_get(&pDesign->names, pModule->name), (unsigned long long)Mult, + pModule->type_objects[SN_PI].size, (unsigned long long)PiBits, + pModule->type_objects[SN_PO].size, (unsigned long long)PoBits, + (unsigned long long)AigInputs, (unsigned long long)AigOutputs ); + } + fprintf( pOut, "Black-box totals: types = %zu occurrences = %llu AIG inputs = %llu AIG outputs = %llu\n", + nTypes, (unsigned long long)nOccurrences, (unsigned long long)nAigInputs, + (unsigned long long)nAigOutputs ); + ABC_FREE( pMults ); + return; + +overflow: + fprintf( pOut, "Cannot print black-box statistics: a bit or occurrence total overflows 64 bits.\n" ); + ABC_FREE( pMults ); +} + +static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_module_id_t Top ) +{ + Sn_DistribEntry_t * pEntries[SN_OBJ_TYPE_COUNT] = { NULL }; + size_t nEntries[SN_OBJ_TYPE_COUNT] = { 0 }; + size_t nCaps[SN_OBJ_TYPE_COUNT] = { 0 }; + uint64_t TypeCounts[SN_OBJ_TYPE_COUNT] = { 0 }; + uint64_t * pMults = Sn_DesignCountModuleOccurrences( pDesign, Top ); + size_t i; + int Type; + if ( pMults == NULL ) + { + fprintf( pOut, "Cannot print object distribution: hierarchy is invalid or the occurrence count overflows.\n" ); + return; + } + for ( i = 0; i < pDesign->modules.size; i++ ) + if ( pMults[i] && !Sn_ModuleCollectDistrib(pDesign, (sn_module_id_t)i, pMults[i], pEntries, + nEntries, nCaps, TypeCounts) ) + { + fprintf( pOut, "Cannot print object distribution: a count overflows or allocation failed.\n" ); + for ( Type = 0; Type < SN_OBJ_TYPE_COUNT; Type++ ) + ABC_FREE( pEntries[Type] ); + ABC_FREE( pMults ); + return; + } fprintf( pOut, "ID : name occurrence (occurrence)=. ...\n" ); for ( Type = 0; Type < SN_OBJ_TYPE_COUNT; Type++ ) { @@ -551,10 +674,7 @@ static void Sn_DesignPrintDistrib( FILE * pOut, const sn_design_t * pDesign, sn_ fprintf( pOut, "\n" ); ABC_FREE( pEntries[Type] ); } - sn_vec_destroy( &Postorder ); - sn_vec_destroy( &Stack ); ABC_FREE( pMults ); - ABC_FREE( pStates ); } void Sn_Init( Abc_Frame_t * pAbc ) @@ -997,7 +1117,7 @@ static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; pCurrent = Sn_AbcGetMan( pAbc ); - if ( !sn_design_check(pCurrent->pDesign, pAbc->Err, 0) ) + if ( !sn_design_check(pCurrent->pDesign, Abc_FrameReadErr(pAbc), 0) ) { Abc_Print( -1, "Cannot @opt_mux: the current SN design is inconsistent.\n" ); return 1; @@ -1009,7 +1129,7 @@ static int Sn_CommandOptMux( Abc_Frame_t * pAbc, int argc, char ** argv ) return 1; } Stats = sn_design_share( p->pDesign, Options ); - if ( !sn_design_check(p->pDesign, pAbc->Err, 0) ) + if ( !sn_design_check(p->pDesign, Abc_FrameReadErr(pAbc), 0) ) { Abc_Print( -1, "Cannot @opt_mux: the transformed SN design is inconsistent.\n" ); Sn_ManFree( p ); @@ -1393,8 +1513,9 @@ static int Sn_CommandPut( Abc_Frame_t * pAbc, int argc, char ** argv ) } pNtk = Abc_NtkFromCellMappedGia( pGia, 0 ); vMapping = Abc_NtkWriteMiniMapping( pNtk ); - Top = sn_design_add_gate_module( p->pDesign, p->BlastModule, Vec_IntArray(vMapping), &p->Boundary, - Sn_GateIdResolver, pLibrary, "__sn_gate_mapped" ); + Top = sn_design_add_gate_module( p->pDesign, p->BlastModule, Vec_IntArray(vMapping), + (size_t)Vec_IntSize(vMapping), &p->Boundary, Sn_GateIdResolver, + pLibrary, "__sn_gate_mapped" ); if ( Top == SN_INVALID_ID ) { Abc_Print( -1, "Cannot @put: the current genlib does not contain every gate used by the mapped GIA.\n" ); @@ -1826,7 +1947,6 @@ usage: static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) { Sn_Man_t * p; - const sn_module_t * pTop; char * pFileName; FILE * pFile; int c, Status = 0; @@ -1838,7 +1958,6 @@ static int Sn_CommandWrite( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; p = Sn_AbcGetMan( pAbc ); - pTop = sn_design_get_module_const( p->pDesign, p->Top ); pFileName = argv[globalUtilOptind]; if ( Sn_FileHasSuffix(pFileName, ".sn") ) { @@ -1887,17 +2006,27 @@ usage: static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) { Sn_Man_t * p; - const sn_module_t * pTop; + const sn_module_t * pReport; sn_design_mem_usage_t Mem; + char * pModuleName = NULL; + sn_module_id_t Report; size_t nObjects = 0; size_t i; int c, fDistrib = 0, fVerbose = 0, fMem, fLut, fGate; char UsedMemory[32], AllocatedMemory[32]; Extra_UtilGetoptReset(); - while ( (c = Extra_UtilGetopt(argc, argv, "dvh")) != EOF ) + while ( (c = Extra_UtilGetopt(argc, argv, "Mdvh")) != EOF ) { switch ( c ) { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by a module name.\n" ); + goto usage; + } + pModuleName = argv[globalUtilOptind++]; + break; case 'd': fDistrib ^= 1; break; @@ -1914,7 +2043,13 @@ static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) if ( !Sn_CommandCheckDesign(pAbc) ) return 1; p = Sn_AbcGetMan( pAbc ); - pTop = sn_design_get_module_const( p->pDesign, p->Top ); + Report = pModuleName ? sn_design_find_module( p->pDesign, pModuleName ) : p->Top; + if ( Report == SN_INVALID_ID ) + { + Abc_Print( -1, "Cannot find module \"%s\" in the current SN design.\n", pModuleName ); + return 1; + } + pReport = sn_design_get_module_const( p->pDesign, Report ); for ( i = 0; i < p->pDesign->modules.size; i++ ) nObjects += sn_design_get_module_const( p->pDesign, (sn_module_id_t)i )->obj_types.size; sn_design_get_mem_usage( p->pDesign, &Mem ); @@ -1925,27 +2060,35 @@ static int Sn_CommandPs( Abc_Frame_t * pAbc, int argc, char ** argv ) Sn_FormatMemory( Mem.total.allocated_bytes, AllocatedMemory, sizeof(AllocatedMemory) ); fprintf( pAbc->Out, "SN design: top = %s modules = %zu objects = %zu memory = %s/%s " "(used/allocated)\n", - sn_name_get(&p->pDesign->names, pTop->name), p->pDesign->modules.size, nObjects, + sn_name_get(&p->pDesign->names, pReport->name), p->pDesign->modules.size, nObjects, UsedMemory, AllocatedMemory ); - Sn_ModulePrintStats( pAbc->Out, pTop, fMem, fLut, fGate ); - if ( fVerbose ) + if ( pModuleName ) + Sn_ModulePrintStats( pAbc->Out, pReport, fMem, fLut, fGate ); + else { - fprintf( pAbc->Out, "Hierarchy:\n" ); - sn_design_print_hierarchy( pAbc->Out, p->pDesign, p->Top ); fprintf( pAbc->Out, "Modules:\n" ); for ( i = 0; i < p->pDesign->modules.size; i++ ) Sn_ModulePrintStats( pAbc->Out, sn_design_get_module_const(p->pDesign, (sn_module_id_t)i), fMem, fLut, fGate ); } + if ( fVerbose ) + { + fprintf( pAbc->Out, "Hierarchy:\n" ); + sn_design_print_hierarchy( pAbc->Out, p->pDesign, Report ); + } if ( fDistrib ) - Sn_DesignPrintDistrib( pAbc->Out, p->pDesign, p->Top ); + { + Sn_DesignPrintBlackboxes( pAbc->Out, p->pDesign, Report ); + Sn_DesignPrintDistrib( pAbc->Out, p->pDesign, Report ); + } return 0; usage: - Abc_Print( -2, "usage: @ps [-dvh]\n" ); + Abc_Print( -2, "usage: @ps [-M module] [-dvh]\n" ); Abc_Print( -2, "\t prints statistics for the current SN design\n" ); + Abc_Print( -2, "\t-M name : select one module and its hierarchy [default = print all module definitions]\n" ); Abc_Print( -2, "\t-d : print object-type and width distribution for the elaborated hierarchy\n" ); - Abc_Print( -2, "\t-v : print hierarchy and per-module statistics\n" ); + Abc_Print( -2, "\t-v : print the hierarchy rooted at the selected module\n" ); Abc_Print( -2, "\t-h : print the command usage\n" ); return 1; } diff --git a/src/base/sn/snMiniAig.h b/src/base/sn/snMiniAig.h index 2698743bc..ec7ab5d72 100644 --- a/src/base/sn/snMiniAig.h +++ b/src/base/sn/snMiniAig.h @@ -127,6 +127,7 @@ static inline sn_module_id_t sn_design_add_aig_module(sn_design_t* design, sn_mo } assert(co_index <= boundary->cos.size); sn_boundary_regs_finish(®s, drivers); + result = sn_design_get_module(design, result_id); free(drivers); free(objects); diff --git a/src/base/sn/snMiniGate.h b/src/base/sn/snMiniGate.h index c9763a7cf..f122958a0 100644 --- a/src/base/sn/snMiniGate.h +++ b/src/base/sn/snMiniGate.h @@ -31,38 +31,86 @@ typedef uint32_t (*sn_gate_id_resolver_t)(void* context, const char* gate_name); // afterward in topological order. Gate names stored at the end of the array are resolved into the current library's // stable gate IDs; the name is also retained as the SN object name for structural Verilog emission. static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_module_id_t source_top_id, - const int* mapping, const sn_blast_boundary_t* boundary, + const int* mapping, size_t mapping_count, + const sn_blast_boundary_t* boundary, sn_gate_id_resolver_t resolver, void* resolver_context, const char* module_name) { assert(design && source_top_id < design->modules.size && mapping && boundary && resolver && module_name); + if (mapping_count < 4 || mapping[0] < 0 || mapping[1] < 0 || mapping[2] < 0 || mapping[3] < 0) + return SN_INVALID_ID; uint32_t ci_count = (uint32_t)mapping[0]; uint32_t co_count = (uint32_t)mapping[1]; uint32_t node_count = (uint32_t)mapping[2]; uint32_t reg_count = (uint32_t)mapping[3]; - assert(reg_count == 0 && ci_count == boundary->cis.size && co_count == boundary->cos.size); + if (reg_count != 0 || ci_count != boundary->cis.size || co_count != boundary->cos.size || + node_count > UINT32_MAX - ci_count) + return SN_INVALID_ID; - // Resolve all gate names before mutating the design. A changed genlib can otherwise leave a partially constructed - // module behind or turn a user-level @put error into an assertion failure. - uint32_t position = 4; - for (uint32_t i = 0; i < node_count; i++) + // Validate the complete structural prefix and resolve all bounded gate-name strings before mutating the design. + // A changed genlib or malformed mini-mapping can otherwise leave a partially constructed module behind. + size_t position = 4; + uint32_t* fanin_counts = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; + const uint32_t** fanin_indices = + node_count ? (const uint32_t**)malloc(sizeof(uint32_t*) * node_count) : NULL; + uint32_t* gate_ids = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; + bool valid = true; + assert((fanin_counts && fanin_indices && gate_ids) || node_count == 0); + for (uint32_t i = 0; valid && i < node_count; i++) { + if (position >= mapping_count || mapping[position] < 0) + { + valid = false; + break; + } uint32_t count = (uint32_t)mapping[position++]; + if (count > mapping_count - position) + { + valid = false; + break; + } + fanin_counts[i] = count; + fanin_indices[i] = (const uint32_t*)(mapping + position); + for (uint32_t k = 0; k < count; k++) + if (mapping[position + k] < 0 || (uint32_t)mapping[position + k] >= ci_count + i) + valid = false; position += count; } - position += co_count; - const char* gate_name = (const char*)(mapping + position); - uint32_t* gate_ids = node_count ? (uint32_t*)malloc(sizeof(uint32_t) * node_count) : NULL; - assert(gate_ids || node_count == 0); - for (uint32_t i = 0; i < node_count; i++) + if (valid && co_count > mapping_count - position) + valid = false; + const uint32_t* output_indices = valid ? (const uint32_t*)(mapping + position) : NULL; + for (uint32_t i = 0; valid && i < co_count; i++) + if (mapping[position + i] < 0 || (uint32_t)mapping[position + i] >= ci_count + node_count) + valid = false; + if (valid) + position += co_count; + const char* gate_names = valid ? (const char*)(mapping + position) : NULL; + const char* gate_name = gate_names; + size_t name_bytes = valid ? (mapping_count - position) * sizeof(int) : 0; + for (uint32_t i = 0; valid && i < node_count; i++) { + const char* end = (const char*)memchr(gate_name, '\0', name_bytes); + if (!end || end == gate_name) + { + valid = false; + break; + } gate_ids[i] = resolver(resolver_context, gate_name); if (gate_ids[i] == SN_INVALID_ID) { - free(gate_ids); - return SN_INVALID_ID; + valid = false; + break; } - gate_name += strlen(gate_name) + 1; + size_t length = (size_t)(end - gate_name) + 1; + gate_name += length; + name_bytes -= length; + } + if (!valid) + { + free(gate_ids); + free(fanin_indices); + free(fanin_counts); + return SN_INVALID_ID; } const sn_module_t* source = sn_design_get_module_const(design, source_top_id); @@ -103,19 +151,7 @@ static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_m assert(false); } - position = 4; - uint32_t* fanin_counts = (uint32_t*)malloc(sizeof(uint32_t) * node_count); - const uint32_t** fanin_indices = (const uint32_t**)malloc(sizeof(uint32_t*) * node_count); - assert((fanin_counts && fanin_indices) || node_count == 0); - for (uint32_t i = 0; i < node_count; i++) - { - fanin_counts[i] = (uint32_t)mapping[position++]; - fanin_indices[i] = (const uint32_t*)(mapping + position); - position += fanin_counts[i]; - } - const uint32_t* output_indices = (const uint32_t*)(mapping + position); - position += co_count; - gate_name = (const char*)(mapping + position); + gate_name = gate_names; for (uint32_t i = 0; i < node_count; i++) { @@ -160,6 +196,7 @@ static inline sn_module_id_t sn_design_add_gate_module(sn_design_t* design, sn_m co_drivers[i] = objects[output_indices[i]]; } sn_boundary_regs_finish(®s, co_drivers); + result = sn_design_get_module(design, result_id); free(co_drivers); free(fanin_indices); diff --git a/src/base/sn/snMiniLut.h b/src/base/sn/snMiniLut.h index fb20a39a8..b26bcbc62 100644 --- a/src/base/sn/snMiniLut.h +++ b/src/base/sn/snMiniLut.h @@ -391,6 +391,7 @@ static inline sn_module_id_t sn_design_add_lut_module(sn_design_t* design, sn_mo sn_module_add_po(result, width, sn_obj_is_signed(source, old_po), sn_obj_name(source, old_po), driver); } sn_boundary_regs_finish(®s, co_drivers); + result = sn_design_get_module(design, result_id); free(co_drivers); free(mini_objects); diff --git a/src/base/sn/snMux.h b/src/base/sn/snMux.h index 781f7c8e3..03e235bd9 100644 --- a/src/base/sn/snMux.h +++ b/src/base/sn/snMux.h @@ -505,6 +505,8 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* sn_vec_t stack, steps, paths, terms, term_hashes, term_links, data_terms, controls; sn_obj_id_t hold = SN_INVALID_ID, data = SN_INVALID_ID, new_reg = SN_INVALID_ID; uint32_t* term_buckets = NULL; + uint32_t* group_heads = NULL; + uint32_t* path_links = NULL; uint32_t term_bucket_count = 0; size_t hold_index = 0; bool exclusive = true, overflow = false; @@ -553,6 +555,18 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* } if (paths.size <= terms.size || paths.size - terms.size < options.min_saved_paths || paths.size < 2 * terms.size) goto unchanged; + group_heads = (uint32_t*)malloc(terms.size * sizeof(uint32_t)); + path_links = (uint32_t*)malloc(paths.size * sizeof(uint32_t)); + assert(group_heads && path_links); + for (size_t k = 0; k < terms.size; k++) + group_heads[k] = SN_INVALID_ID; + for (size_t i = 0; i < paths.size; i++) + { + uint32_t group = sn_vec_at(sn_share_path_t, &paths, i).group; + assert(group < terms.size); + path_links[i] = group_heads[group]; + group_heads[group] = (uint32_t)i; + } hold = sn_share_strip_value(source, old_reg); hold_index = terms.size; @@ -566,11 +580,10 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* continue; sn_vec_t cubes; sn_vec_init(&cubes); - for (size_t i = 0; i < paths.size; i++) + for (uint32_t i = group_heads[k]; i != SN_INVALID_ID; i = path_links[i]) { sn_share_path_t* path = &sn_vec_at(sn_share_path_t, &paths, i); - if (path->group == k) - *sn_vec_push(sn_obj_id_t, &cubes) = sn_share_path_condition(target, source, path, &steps); + *sn_vec_push(sn_obj_id_t, &cubes) = sn_share_path_condition(target, source, path, &steps); } *sn_vec_push(sn_obj_id_t, &controls) = sn_share_or(target, sn_vec_data(sn_obj_id_t, &cubes), (uint32_t)cubes.size); @@ -615,6 +628,8 @@ static inline bool sn_share_reg_mux_tree(sn_module_t* target, const sn_module_t* sn_vec_destroy(&terms); sn_vec_destroy(&term_hashes); sn_vec_destroy(&term_links); + free(path_links); + free(group_heads); free(term_buckets); return true; @@ -625,6 +640,8 @@ unchanged: sn_vec_destroy(&terms); sn_vec_destroy(&term_hashes); sn_vec_destroy(&term_links); + free(path_links); + free(group_heads); free(term_buckets); sn_vec_destroy(&data_terms); sn_vec_destroy(&controls); @@ -672,6 +689,8 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou while (bucket_count < 2 * count) bucket_count <<= 1; uint32_t* buckets = (uint32_t*)malloc((size_t)bucket_count * sizeof(uint32_t)); + uint32_t* member_heads = NULL; + uint32_t* member_links = NULL; assert(buckets); memset(buckets, 0xff, (size_t)bucket_count * sizeof(uint32_t)); for (uint32_t i = 0; i < count; i++) @@ -712,6 +731,18 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou free(buckets); return false; } + member_heads = (uint32_t*)malloc(unique.size * sizeof(uint32_t)); + member_links = (uint32_t*)malloc(members.size * sizeof(uint32_t)); + assert(member_heads && member_links); + for (size_t k = 0; k < unique.size; k++) + member_heads[k] = SN_INVALID_ID; + for (size_t j = 0; j < members.size; j++) + { + uint32_t group = sn_vec_at(uint32_t, &members, j) >> 16; + assert(group < unique.size); + member_links[j] = member_heads[group]; + member_heads[group] = (uint32_t)j; + } sn_obj_id_t new_reg = sn_obj_dup(source, old_reg); sn_obj_id_t new_in = sn_obj_pair_in(target, new_reg); @@ -720,11 +751,10 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou { sn_vec_t bits; sn_vec_init(&bits); - for (size_t j = 0; j < members.size; j++) + for (uint32_t j = member_heads[k]; j != SN_INVALID_ID; j = member_links[j]) { uint32_t member = sn_vec_at(uint32_t, &members, j); - if ((member >> 16) == k) - *sn_vec_push(sn_obj_id_t, &bits) = sn_share_select_bit(target, new_select, member & UINT16_MAX); + *sn_vec_push(sn_obj_id_t, &bits) = sn_share_select_bit(target, new_select, member & UINT16_MAX); } *sn_vec_push(sn_obj_id_t, &conditions) = sn_share_or(target, sn_vec_data(sn_obj_id_t, &bits), (uint32_t)bits.size); @@ -776,6 +806,8 @@ static inline bool sn_share_reg_pmux(sn_module_t* target, const sn_module_t* sou sn_vec_destroy(&unique_links); sn_vec_destroy(&conditions); sn_vec_destroy(&members); + free(member_links); + free(member_heads); free(buckets); return true; } diff --git a/src/base/sn/snPth.h b/src/base/sn/snPth.h index a9f237fac..eee9c4771 100644 --- a/src/base/sn/snPth.h +++ b/src/base/sn/snPth.h @@ -103,10 +103,22 @@ static inline void sn_pth_process(void** jobs, size_t count, unsigned processes, pool.context = context; pool.function = function; int status = pthread_mutex_init(&pool.mutex, NULL); - assert(status == 0); - (void)status; + if (status != 0) + { + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); + return; + } pthread_t* workers = (pthread_t*)malloc(sizeof(pthread_t) * worker_count); - assert(workers); + if (!workers) + { + status = pthread_mutex_destroy(&pool.mutex); + assert(status == 0); + (void)status; + for (size_t i = 0; i < count; i++) + function(context, jobs[i]); + return; + } unsigned created = 0; for (; created < worker_count; created++) {