Search code examples
yosys

Does yosys preserve port ordering?


Does yosys preserve the ordering of input/output ports for modules/cells? Is the ordering in the RTL guaranteed to match the ordering of the verilog when it is read/written? Will the ordering ever be changed "unexpectedly" by yosys?


Solution

  • Yosys does store the order in which module ports are declared in RTLIL::Wire::port_id. Positional parameters on instances are renamed to $1, $2, $3, .... The hierarchy command then uses the port_id property to correctly rename the ports on cell instances during design elaboration.

    When writing a module as verilog file the wire port_id property is used to determine the order in which ports are declared in the module header.

    So yes: Yosys does preserve ordering of input/output ports for modules and resolves the names of positional parameters in cell instances.

    When hierarchy is not run or the declaration of a cell type is not available, then the positional parameters in those cell instances are not resolved and they are again dumped as positional parameters when the design is written as verilog file.

    Edit: In response to the comment: The following example plugin (portsigdemo.cc) demonstrates how to create a SigSpec containing all module ports / cell ports in the order of the port declarations in the corresponding module:

    #include "kernel/yosys.h"
    #include "kernel/consteval.h"
    
    USING_YOSYS_NAMESPACE
    PRIVATE_NAMESPACE_BEGIN
    
    SigSpec get_portsig(Module *module, string direction, Cell *cell = nullptr)
    {
        if (cell)
            log_assert(cell->type == module->name);
    
        SigSpec sig;
        dict<int, SigSpec> sigs;
    
        for (auto wire : module->wires())
        {
            bool selected = false;
    
            if (direction == "in")
                selected = wire->port_input;
            else if (direction == "out")
                selected = wire->port_output;
            else
                log_abort();
    
            if (selected) {
                if (cell) {
                    if (cell->hasPort(wire->name))
                        sigs[wire->port_id] = cell->getPort(wire->name);
                    else if (cell->hasPort(stringf("$%d", wire->port_id)))
                        sigs[wire->port_id] = cell->getPort(stringf("$%d", wire->port_id));
                    else
                        log_abort();
                } else {
                    sigs[wire->port_id] = wire;
                }
            }
        }
    
        sigs.sort();
        for (auto &it : sigs)
            sig.append(it.second);
    
        return sig;
    }
    
    struct PortsigDemoPass : public Pass
    {
        PortsigDemoPass():Pass("portsigdemo") { }
    
        virtual void execute(vector < string >, Design * design)
        {
            for (auto module : design->modules())
            {
                log("Module %s:\n", log_id(module));
                log("  Inputs: %s\n", log_signal(get_portsig(module, "in")));
                log("  Outputs: %s\n", log_signal(get_portsig(module, "out")));
    
                for (auto cell : module->cells())
                {
                    auto cell_type_mod = cell->module->design->module(cell->type);
                    if (cell_type_mod) {
                        log("  Cell %s (%s):\n", log_id(cell), log_id(cell->type));
                        log("    Inputs: %s\n", log_signal(get_portsig(cell_type_mod, "in", cell)));
                        log("    Outputs: %s\n", log_signal(get_portsig(cell_type_mod, "out", cell)));
                    }
                }
            }
        }
    } PortsigDemoPass;
    
    PRIVATE_NAMESPACE_END
    

    Example usage:

    $ cat > portsigdemo.v << EOT
    module mod1 (input [3:0] A, B, output [3:0] Y);
      mod2 mod2_inst0 (.P(A[0]), .Q(B[0]), .X(Y[0]));
      mod2 mod2_inst1 (.P(B[1]), .Q(A[1]), .X(Y[1]));
      mod2 mod2_inst2 (A[2], B[2], Y[2]);
      mod2 mod2_inst3 (B[3], A[3], Y[3]);
    endmodule
    
    module mod2 (input P, Q, output X);
      assign X = P ^ Q;
    endmodule
    EOT
    
    $ yosys-config --build portsigdemo.so portsigdemo.cc
    $ yosys -m portsigdemo.so -p portsigdemo portsigdemo.v
    ...
    -- Running command `portsigdemo' --
    Module mod2:
      Inputs: { \Q \P }
      Outputs: \X
    Module mod1:
      Inputs: { \B \A }
      Outputs: \Y
      Cell mod2_inst3 (mod2):
        Inputs: { \A [3] \B [3] }
        Outputs: \Y [3]
      Cell mod2_inst2 (mod2):
        Inputs: { \B [2] \A [2] }
        Outputs: \Y [2]
      Cell mod2_inst1 (mod2):
        Inputs: { \A [1] \B [1] }
        Outputs: \Y [1]
      Cell mod2_inst0 (mod2):
        Inputs: { \B [0] \A [0] }
        Outputs: \Y [0]
    

    PS: In portsigdemo.v, the cells (module instances) mod1.mod2_inst0 and mod1.mod2_inst1 are using named parameters (the name of the module port is given) and the cells mod1.mod2_inst2 and mod1.mod2_inst3 are using positional parameters (the module port is determined by the order of parameters).