This file contains some very brief documentation on things like programming APIs. Also consult the Yosys manual and the section about programming in the presentation. (Both can be downloaded as PDF from the yosys webpage.) --snip-- only the lines below this mark are included in the yosys manual --snip-- Getting Started =============== Outline of a Yosys command -------------------------- Here is a the C++ code for a "hello_world" Yosys command (hello.cc): #include "kernel/yosys.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct HelloWorldPass : public Pass { HelloWorldPass() : Pass("hello_world") { } void execute(vector, Design*) override { log("Hello World!\n"); } } HelloWorldPass; PRIVATE_NAMESPACE_END This can be built into a Yosys module using the following command: yosys-config --exec --cxx --cxxflags --ldflags -o hello.so -shared hello.cc --ldlibs Or short: yosys-config --build hello.so hello.cc And then executed using the following command: yosys -m hello.so -p hello_world Yosys Data Structures --------------------- Here is a short list of data structures that you should make yourself familiar with before you write C++ code for Yosys. The following data structures are all defined when "kernel/yosys.h" is included and USING_YOSYS_NAMESPACE is used. 1. Yosys Container Classes Yosys uses dict and pool as main container classes. dict is essentially a replacement for std::unordered_map and pool is a replacement for std::unordered_set. The main characteristics are: - dict and pool are about 2x faster than the std containers - references to elements in a dict or pool are invalidated by insert and remove operations (similar to std::vector on push_back()). - some iterators are invalidated by erase(). specifically, iterators that have not passed the erased element yet are invalidated. (erase() itself returns valid iterator to the next element.) - no iterators are invalidated by insert(). elements are inserted at begin(). i.e. only a new iterator that starts at begin() will see the inserted elements. - the method .count(key, iterator) is like .count(key) but only considers elements that can be reached via the iterator. - iterators can be compared. it1 < it2 means that the position of t2 can be reached via t1 but not vice versa. - the method .sort() can be used to sort the elements in the container the container stays sorted until elements are added or removed. - dict and pool will have the same order of iteration across all compilers, standard libraries and architectures. In addition to dict and pool there is also an idict that creates a bijective map from K to the integers. For example: idict si; log("%d\n", si("hello")); // will print 42 log("%d\n", si("world")); // will print 43 log("%d\n", si.at("world")); // will print 43 log("%d\n", si.at("dummy")); // will throw exception log("%s\n", si[42].c_str())); // will print hello log("%s\n", si[43].c_str())); // will print world log("%s\n", si[44].c_str())); // will throw exception It is not possible to remove elements from an idict. Finally mfp implements a merge-find set data structure (aka. disjoint-set or union-find) over the type K ("mfp" = merge-find-promote). 2. Standard STL data types In Yosys we use std::vector and std::string whenever applicable. When dict and pool are not suitable then std::map and std::set are used instead. The types std::vector and std::string are also available as vector and string in the Yosys namespace. 3. RTLIL objects The current design (essentially a collection of modules, each defined by a netlist) is stored in memory using RTLIL object (declared in kernel/rtlil.h, automatically included by kernel/yosys.h). You should glance over at least the declarations for the following types in kernel/rtlil.h: RTLIL::IdString This is a handle for an identifier (e.g. cell or wire name). It feels a lot like a std::string, but is only a single int in size. (The actual string is stored in a global lookup table.) RTLIL::SigBit A single signal bit. I.e. either a constant state (0, 1, x, z) or a single bit from a wire. RTLIL::SigSpec Essentially a vector of SigBits. RTLIL::Wire RTLIL::Cell The building blocks of the netlist in a module. RTLIL::Module RTLIL::Design The module is a container with connected cells and wires in it. The design is a container with modules in it. All this types are also available without the RTLIL:: prefix in the Yosys namespace. 4. SigMap and other Helper Classes There are a couple of additional helper classes that are in wide use in Yosys. Most importantly there is SigMap (declared in kernel/sigtools.h). When a design has many wires in it that are connected to each other, then a single signal bit can have multiple valid names. The SigMap object can be used to map SigSpecs or SigBits to unique SigSpecs and SigBits that consistently only use one wire from such a group of connected wires. For example: SigBit a = module->addWire(NEW_ID); SigBit b = module->addWire(NEW_ID); module->connect(a, b); log("%d\n", a == b); // will print 0 SigMap sigmap(module); log("%d\n", sigmap(a) == sigmap(b)); // will print 1 Using the RTLIL Netlist Format ------------------------------ In the RTLIL netlist format the cell ports contain SigSpecs that point to the Wires. There are no references in the other direction. This has two direct consequences: (1) It is very easy to go from cells to wires but hard to go in the other way. (2) There is no danger in removing cells from the netlists, but removing wires can break the netlist format when there are still references to the wire somewhere in the netlist. The solution to (1) is easy: Create custom indexes that allow you to make fast lookups for the wire-to-cell direction. You can either use existing generic index structures to do that (such as the ModIndex class) or write your own index. For many application it is simplest to construct a custom index. For example: SigMap sigmap(module); dict sigbit_to_driver_index; for (auto cell : module->cells()) for (auto &conn : cell->connections()) if (cell->output(conn.first)) for (auto bit : sigmap(conn.second)) sigbit_to_driver_index[bit] = cell; Regarding (2): There is a general theme in Yosys that you don't remove wires from the design. You can rename them, unconnect them, but you do not actually remove the Wire object from the module. Instead you let the "clean" command take care of the dangling wires. On the other hand it is safe to remove cells (as long as you make sure this does not invalidate a custom index you are using in your code). Example Code ------------ The following yosys commands are a good starting point if you are looking for examples of how to use the Yosys API: manual/CHAPTER_Prog/stubnets.cc manual/PRESENTATION_Prog/my_cmd.cc Script Passes ------------- The ScriptPass base class can be used to implement passes that just call other passes, like a script. Examples for such passes are: techlibs/common/prep.cc techlibs/common/synth.cc In some cases it is easier to implement such a pass as regular pass, for example when ScriptPass doesn't provide the type of flow control desired. (But many of the script passes in Yosys that don't use ScriptPass simply predate the ScriptPass base class.) Examples for such passes are: passes/opt/opt.cc passes/proc/proc.cc Whether they use the ScriptPass base-class or not, a pass should always either call other passes without doing any non-trivial work itself, or should implement a non-trivial algorithm but not call any other passes. The reason for this is that this helps containing complexity in individual passes and simplifies debugging the entire system. Exceptions to this rule should be rare and limited to cases where calling other passes is optional and only happens when requested by the user (such as for example `techmap -autoproc`), or where it is about commands that are "top-level commands" in their own right, not components to be used in regular synthesis flows (such as the `bugpoint` command). A pass that would "naturally" call other passes and also do some work itself should be re-written in one of two ways: 1) It could be re-written as script pass with the parts that are not calls to other passes factored out into individual new passes. Usually in those cases the new sub passes share the same prefix as the top-level script pass. 2) It could be re-written so that it already expects the design in a certain state, expecting the calling script to set up this state before calling the pass in questions. Many back-ends are examples for the 2nd approach. For example, `write_aiger` does not convert the design into AIG representation, but expects the design to be already in this form, and prints an `Unsupported cell type` error message otherwise. Notes on the existing codebase ------------------------------ For historical reasons not all parts of Yosys adhere to the current coding style. When adding code to existing parts of the system, adhere to this guide for the new code instead of trying to mimic the style of the surrounding code. Coding Style ============ Formatting of code ------------------ - Yosys code is using tabs for indentation. Tabs are 8 characters. - A continuation of a statement in the following line is indented by two additional tabs. - Lines are as long as you want them to be. A good rule of thumb is to break lines at about column 150. - Opening braces can be put on the same or next line as the statement opening the block (if, switch, for, while, do). Put the opening brace on its own line for larger blocks, especially blocks that contains blank lines. - Otherwise stick to the Linux Kernel Coding Style: https://www.kernel.org/doc/Documentation/CodingStyle C++ Language ------------- Yosys is written in C++11. At the moment only constructs supported by gcc 4.8 are allowed in Yosys code. This will change in future releases. In general Yosys uses "int" instead of "size_t". To avoid compiler warnings for implicit type casts, always use "GetSize(foobar)" instead of "foobar.size()". (GetSize() is defined in kernel/yosys.h) Use range-based for loops whenever applicable. --snap-- only the lines above this mark are included in the yosys manual --snap-- Creating the Visual Studio Template Project =========================================== 1. Create an empty Visual C++ Win32 Console App project Microsoft Visual Studio Express 2013 for Windows Desktop Open New Project Wizard (File -> New Project..) Project Name: YosysVS Solution Name: YosysVS [X] Create directory for solution [ ] Add to source control [X] Console applications [X] Empty Project [ ] SDL checks 2. Open YosysVS Project Properties Select Configuration: All Configurations C/C++ -> General -> Additional Include Directories Add: ..\yosys C/C++ -> Preprocessor -> Preprocessor Definitions Add: _YOSYS_;_CRT_SECURE_NO_WARNINGS 3. Resulting file system tree: YosysVS/ YosysVS/YosysVS Yo
/* Copyright 2017 Jack Humbert
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "process_unicode_common.h"
#include "eeprom.h"
#include <ctype.h>
#include <string.h>

unicode_config_t unicode_config;
uint8_t          unicode_saved_mods;

#if UNICODE_SELECTED_MODES != -1
static uint8_t selected[]     = {UNICODE_SELECTED_MODES};
static uint8_t selected_count = sizeof selected / sizeof *selected;
static uint8_t selected_index;
#endif

void unicode_input_mode_init(void) {
    unicode_config.raw = eeprom_read_byte(EECONFIG_UNICODEMODE);
#if UNICODE_SELECTED_MODES != -1
#    if UNICODE_CYCLE_PERSIST
    // Find input_mode in selected modes
    uint8_t i;
    for (i = 0; i < selected_count; i++) {
        if (selected[i] == unicode_config.input_mode) {
            selected_index = i;
            break;
        }
    }
    if (i == selected_count) {
        // Not found: input_mode isn't selected, change to one that is
        unicode_config.input_mode = selected[selected_index = 0];
    }
#    else
    // Always change to the first selected input mode
    unicode_config.input_mode = selected[selected_index = 0];
#    endif
#endif
    dprintf("Unicode input mode init to: %u\n", unicode_config.input_mode);
}

uint8_t get_unicode_input_mode(void) { return unicode_config.input_mode; }

void set_unicode_input_mode(uint8_t mode) {
    unicode_config.input_mode = mode;
    persist_unicode_input_mode();
    dprintf("Unicode input mode set to: %u\n", unicode_config.input_mode);
}

void cycle_unicode_input_mode(uint8_t offset) {
#if UNICODE_SELECTED_MODES != -1
    selected_index            = (selected_index + offset) % selected_count;
    unicode_config.input_mode = selected[selected_index];
#    if UNICODE_CYCLE_PERSIST
    persist_unicode_input_mode();
#    endif
    dprintf("Unicode input mode cycle to: %u\n", unicode_config.input_mode);
#endif
}

void persist_unicode_input_mode(void) { eeprom_update_byte(EECONFIG_UNICODEMODE, unicode_config.input_mode); }

__attribute__((weak)) void unicode_input_start(void) {
    unicode_saved_mods = get_mods();  // Save current mods
    clear_mods();                     // Unregister mods to start from a clean state

    switch (unicode_config.input_mode) {
        case UC_OSX:
            register_code(UNICODE_KEY_OSX);
            break;
        case UC_LNX:
            tap_code16(UNICODE_KEY_LNX);
            break;
        case UC_WIN:
            register_code(KC_LALT);
            tap_code(KC_PPLS);
            break;
        case UC_WINC:
            tap_code(UNICODE_KEY_WINC);
            tap_code(KC_U);
            break;
    }

    wait_ms(UNICODE_TYPE_DELAY);
}

__attribute__((weak)) void unicode_input_finish(void) {
    switch (unicode_config.input_mode) {
        case UC_OSX:
            unregister_code(UNICODE_KEY_OSX);
            break;
        case UC_LNX:
            tap_code(KC_SPC);
            break;
        case UC_WIN:
            unregister_code(KC_LALT);
            break;
        case UC_WINC:
            tap_code(KC_ENTER);
            break;
    }

    set_mods(unicode_saved_mods);  // Reregister previously set mods
}

__attribute__((weak)) void unicode_input_cancel(void) {
    switch (unicode_config.input_mode) {
        case UC_OSX:
            unregister_code(UNICODE_KEY_OSX);
            break;
        case UC_LNX:
        case UC_WINC:
            tap_code(KC_ESC);
            break;
        case UC_WIN:
            unregister_code(KC_LALT);
            break;
    }

    set_mods(unicode_saved_mods);  // Reregister previously set mods
}

__attribute__((weak)) uint16_t hex_to_keycode(uint8_t hex) {
    if (hex == 0x0) {
        return KC_0;
    } else if (hex < 0xA) {
        return KC_1 + (hex - 0x1);
    } else {
        return KC_A + (hex - 0xA);
    }
}

void register_hex(uint16_t hex) {
    for (int i = 3; i >= 0; i--) {
        uint8_t digit = ((hex >> (i * 4)) & 0xF);
        tap_code(hex_to_keycode(digit));
    }
}

void send_unicode_hex_string(const char *str) {
    if (!str) {
        return;
    }

    while (*str) {
        // Find the next code point (token) in the string
        for (; *str == ' '; str++)
            ;
        size_t n = strcspn(str, " ");  // Length of the current token
        char   code_point[n + 1];
        strncpy(code_point, str, n);
        code_point[n] = '\0';  // Make sure it's null-terminated

        // Normalize the code point: make all hex digits lowercase
        for (char *p = code_point; *p; p++) {
            *p = tolower((unsigned char)*p);
        }

        // Send the code point as a Unicode input string
        unicode_input_start();
        send_string(code_point);
        unicode_input_finish();

        str += n;  // Move to the first ' ' (or '\0') after the current token
    }
}

bool process_unicode_common(uint16_t keycode, keyrecord_t *record) {
    if (record->event.pressed) {
        switch (keycode) {
            case UNICODE_MODE_FORWARD:
                cycle_unicode_input_mode(+1);
                break;
            case UNICODE_MODE_REVERSE:
                cycle_unicode_input_mode(-1);
                break;

            case UNICODE_MODE_OSX:
                set_unicode_input_mode(UC_OSX);
#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_OSX)
                static float song_osx[][2] = UNICODE_SONG_OSX;
                PLAY_SONG(song_osx);
#endif
                break;
            case UNICODE_MODE_LNX:
                set_unicode_input_mode(UC_LNX);
#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_LNX)
                static float song_lnx[][2] = UNICODE_SONG_LNX;
                PLAY_SONG(song_lnx);
#endif
                break;
            case UNICODE_MODE_WIN:
                set_unicode_input_mode(UC_WIN);
#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_WIN)
                static float song_win[][2] = UNICODE_SONG_WIN;
                PLAY_SONG(song_win);
#endif
                break;
            case UNICODE_MODE_BSD:
                set_unicode_input_mode(UC_BSD);
#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_BSD)
                static float song_bsd[][2] = UNICODE_SONG_BSD;
                PLAY_SONG(song_bsd);
#endif
                break;
            case UNICODE_MODE_WINC:
                set_unicode_input_mode(UC_WINC);
#if defined(AUDIO_ENABLE) && defined(UNICODE_SONG_WINC)
                static float song_winc[][2] = UNICODE_SONG_WINC;
                PLAY_SONG(song_winc);
#endif
                break;
        }
    }
#if defined(UNICODE_ENABLE)
    return process_unicode(keycode, record);
#elif defined(UNICODEMAP_ENABLE)
    return process_unicodemap(keycode, record);
#elif defined(UCIS_ENABLE)
    return process_ucis(keycode, record);
#else
    return true;
#endif
}