aboutsummaryrefslogtreecommitdiffstats
path: root/it8212.c
blob: e47f6f8ee89ba5cdb0aa99dab91152172bd77c6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
 * This file is part of the flashrom project.
 *
 * Copyright (C) 2011 Carl-Daniel Hailfinger
 * Copyright (C) 2012 Kyösti Mälkki <kyosti.malkki@gmail.com>
 *
 * 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; version 2 of the License.
 *
 * 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.
 */

#include <stdlib.h>
#include "flash.h"
#include "programmer.h"
#include "hwaccess.h"

static uint8_t *it8212_bar = NULL;

#define PCI_VENDOR_ID_ITE 0x1283

static const struct dev_entry devs_it8212[] = {
	{PCI_VENDOR_ID_ITE, 0x8212, NT, "ITE", "8212F PATA RAID"},

	{0},
};

#define IT8212_MEMMAP_SIZE (128 * 1024)
#define IT8212_MEMMAP_MASK (IT8212_MEMMAP_SIZE - 1)

static void it8212_chip_writeb(const struct flashctx *flash, uint8_t val, chipaddr addr)
{
	pci_mmio_writeb(val, it8212_bar + (addr & IT8212_MEMMAP_MASK));
}

static uint8_t it8212_chip_readb(const struct flashctx *flash, const chipaddr addr)
{
	return pci_mmio_readb(it8212_bar + (addr & IT8212_MEMMAP_MASK));
}

static const struct par_master par_master_it8212 = {
		.chip_readb		= it8212_chip_readb,
		.chip_readw		= fallback_chip_readw,
		.chip_readl		= fallback_chip_readl,
		.chip_readn		= fallback_chip_readn,
		.chip_writeb		= it8212_chip_writeb,
		.chip_writew		= fallback_chip_writew,
		.chip_writel		= fallback_chip_writel,
		.chip_writen		= fallback_chip_writen,
};

static int it8212_init(void)
{
	if (rget_io_perms())
		return 1;

	struct pci_dev *dev = pcidev_init(devs_it8212, PCI_ROM_ADDRESS);
	if (!dev)
		return 1;

	/* Bit 0 is address decode enable, 17-31 the base address, everything else reserved/zero. */
	uint32_t io_base_addr = pcidev_readbar(dev, PCI_ROM_ADDRESS) & 0xFFFFFFFE;
	if (!io_base_addr)
		return 1;

	it8212_bar = rphysmap("IT8212F flash", io_base_addr, IT8212_MEMMAP_SIZE);
	if (it8212_bar == ERROR_PTR)
		return 1;

	/* Restore ROM BAR decode state automatically at shutdown. */
	rpci_write_long(dev, PCI_ROM_ADDRESS, io_base_addr | 0x01);

	max_rom_decode.parallel = IT8212_MEMMAP_SIZE;
	return register_par_master(&par_master_it8212, BUS_PARALLEL, NULL);
}
const struct programmer_entry programmer_it8212 = {
	.name			= "it8212",
	.type			= PCI,
	.devs.dev		= devs_it8212,
	.init			= it8212_init,
	.map_flash_region	= fallback_map,
	.unmap_flash_region	= fallback_unmap,
	.delay			= internal_delay,
};
ht .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 *  nextpnr -- Next Generation Place and Route
 *
 *  Copyright (C) 2018  David Shah <david@symbioticeda.com>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include "cells.h"
#include <algorithm>
#include "design_utils.h"
#include "log.h"
#include "util.h"

NEXTPNR_NAMESPACE_BEGIN

void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
{
    IdString id = ctx->id(name);
    cell->ports[id] = PortInfo{id, nullptr, dir};
}

std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::string name)
{
    static int auto_idx = 0;
    std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
    if (name.empty()) {
        new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
    } else {
        new_cell->name = ctx->id(name);
    }
    new_cell->type = type;

    auto copy_bel_ports = [&]() {
        // First find a Bel of the target type
        BelId tgt;
        for (auto bel : ctx->getBels()) {
            if (ctx->getBelType(bel) == type) {
                tgt = bel;
                break;
            }
        }
        NPNR_ASSERT(tgt != BelId());
        for (auto port : ctx->getBelPins(tgt)) {
            add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port));
        }
    };

    if (type == ctx->id("TRELLIS_SLICE")) {
        new_cell->params[ctx->id("MODE")] = std::string("LOGIC");
        new_cell->params[ctx->id("GSR")] = std::string("DISABLED");
        new_cell->params[ctx->id("SRMODE")] = std::string("LSR_OVER_CE");
        new_cell->params[ctx->id("CEMUX")] = std::string("1");
        new_cell->params[ctx->id("CLKMUX")] = std::string("CLK");
        new_cell->params[ctx->id("LSRMUX")] = std::string("LSR");
        new_cell->params[ctx->id("LUT0_INITVAL")] = Property(0, 16);
        new_cell->params[ctx->id("LUT1_INITVAL")] = Property(0, 16);
        new_cell->params[ctx->id("REG0_SD")] = std::string("0");
        new_cell->params[ctx->id("REG1_SD")] = std::string("0");
        new_cell->params[ctx->id("REG0_REGSET")] = std::string("RESET");
        new_cell->params[ctx->id("REG1_REGSET")] = std::string("RESET");
        new_cell->params[ctx->id("CCU2_INJECT1_0")] = std::string("NO");
        new_cell->params[ctx->id("CCU2_INJECT1_1")] = std::string("NO");
        new_cell->params[ctx->id("WREMUX")] = std::string("WRE");

        add_port(ctx, new_cell.get(), "A0", PORT_IN);
        add_port(ctx, new_cell.get(), "B0", PORT_IN);
        add_port(ctx, new_cell.get(), "C0", PORT_IN);
        add_port(ctx, new_cell.get(), "D0", PORT_IN);

        add_port(ctx, new_cell.get(), "A1", PORT_IN);
        add_port(ctx, new_cell.get(), "B1", PORT_IN);
        add_port(ctx, new_cell.get(), "C1", PORT_IN);
        add_port(ctx, new_cell.get(), "D1", PORT_IN);

        add_port(ctx, new_cell.get(), "M0", PORT_IN);
        add_port(ctx, new_cell.get(), "M1", PORT_IN);

        add_port(ctx, new_cell.get(), "FCI", PORT_IN);
        add_port(ctx, new_cell.get(), "FXA", PORT_IN);
        add_port(ctx, new_cell.get(), "FXB", PORT_IN);

        add_port(ctx, new_cell.get(), "CLK", PORT_IN);
        add_port(ctx, new_cell.get(), "LSR", PORT_IN);
        add_port(ctx, new_cell.get(), "CE", PORT_IN);

        add_port(ctx, new_cell.get(), "DI0", PORT_IN);
        add_port(ctx, new_cell.get(), "DI1", PORT_IN);

        add_port(ctx, new_cell.get(), "WD0", PORT_IN);
        add_port(ctx, new_cell.get(), "WD1", PORT_IN);
        add_port(ctx, new_cell.get(), "WAD0", PORT_IN);
        add_port(ctx, new_cell.get(), "WAD1", PORT_IN);
        add_port(ctx, new_cell.get(), "WAD2", PORT_IN);
        add_port(ctx, new_cell.get(), "WAD3", PORT_IN);
        add_port(ctx, new_cell.get(), "WRE", PORT_IN);
        add_port(ctx, new_cell.get(), "WCK", PORT_IN);

        add_port(ctx, new_cell.get(), "F0", PORT_OUT);
        add_port(ctx, new_cell.get(), "Q0", PORT_OUT);
        add_port(ctx, new_cell.get(), "F1", PORT_OUT);
        add_port(ctx, new_cell.get(), "Q1", PORT_OUT);

        add_port(ctx, new_cell.get(), "FCO", PORT_OUT);
        add_port(ctx, new_cell.get(), "OFX0", PORT_OUT);
        add_port(ctx, new_cell.get(), "OFX1", PORT_OUT);

        add_port(ctx, new_cell.get(), "WDO0", PORT_OUT);
        add_port(ctx, new_cell.get(), "WDO1", PORT_OUT);
        add_port(ctx, new_cell.get(), "WDO2", PORT_OUT);
        add_port(ctx, new_cell.get(), "WDO3", PORT_OUT);
        add_port(ctx, new_cell.get(), "WADO0", PORT_OUT);
        add_port(ctx, new_cell.get(), "WADO1", PORT_OUT);
        add_port(ctx, new_cell.get(), "WADO2", PORT_OUT);
        add_port(ctx, new_cell.get(), "WADO3", PORT_OUT);
    } else if (type == ctx->id("TRELLIS_IO")) {
        new_cell->params[ctx->id("DIR")] = std::string("INPUT");
        new_cell->attrs[ctx->id("IO_TYPE")] = std::string("LVCMOS33");
        new_cell->params[ctx->id("DATAMUX_ODDR")] = std::string("PADDO");
        new_cell->params[ctx->id("DATAMUX_MDDR")] = std::string("PADDO");

        add_port(ctx, new_cell.get(), "B", PORT_INOUT);
        add_port(ctx, new_cell.get(), "I", PORT_IN);
        add_port(ctx, new_cell.get(), "T", PORT_IN);
        add_port(ctx, new_cell.get(), "O", PORT_OUT);

        add_port(ctx, new_cell.get(), "IOLDO", PORT_IN);
        add_port(ctx, new_cell.get(), "IOLTO", PORT_IN);

    } else if (type == ctx->id("LUT4")) {
        new_cell->params[ctx->id("INIT")] = Property(0, 16);

        add_port(ctx, new_cell.get(), "A", PORT_IN);
        add_port(ctx, new_cell.get(), "B", PORT_IN);
        add_port(ctx, new_cell.get(), "C", PORT_IN);
        add_port(ctx, new_cell.get(), "D", PORT_IN);
        add_port(ctx, new_cell.get(), "Z", PORT_OUT);
    } else if (type == ctx->id("CCU2C")) {
        new_cell->params[ctx->id("INIT0")] = Property(0, 16);
        new_cell->params[ctx->id("INIT1")] = Property(0, 16);
        new_cell->params[ctx->id("INJECT1_0")] = std::string("YES");
        new_cell->params[ctx->id("INJECT1_1")] = std::string("YES");

        add_port(ctx, new_cell.get(), "CIN", PORT_IN);

        add_port(ctx, new_cell.get(), "A0", PORT_IN);
        add_port(ctx, new_cell.get(), "B0", PORT_IN);
        add_port(ctx, new_cell.get(), "C0", PORT_IN);
        add_port(ctx, new_cell.get(), "D0", PORT_IN);

        add_port(ctx, new_cell.get(), "A1", PORT_IN);
        add_port(ctx, new_cell.get(), "B1", PORT_IN);
        add_port(ctx, new_cell.get(), "C1", PORT_IN);
        add_port(ctx, new_cell.get(), "D1", PORT_IN);

        add_port(ctx, new_cell.get(), "S0", PORT_OUT);
        add_port(ctx, new_cell.get(), "S1", PORT_OUT);
        add_port(ctx, new_cell.get(), "COUT", PORT_OUT);

    } else if (type == ctx->id("DCCA")) {
        add_port(ctx, new_cell.get(), "CLKI", PORT_IN);
        add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
        add_port(ctx, new_cell.get(), "CE", PORT_IN);
    } else if (type == id_IOLOGIC || type == id_SIOLOGIC) {
        new_cell->params[ctx->id("MODE")] = std::string("NONE");
        new_cell->params[ctx->id("GSR")] = std::string("DISABLED");
        new_cell->params[ctx->id("CLKIMUX")] = std::string("CLK");
        new_cell->params[ctx->id("CLKOMUX")] = std::string("CLK");
        new_cell->params[ctx->id("LSRIMUX")] = std::string("0");
        new_cell->params[ctx->id("LSROMUX")] = std::string("0");
        new_cell->params[ctx->id("LSRMUX")] = std::string("LSR");

        new_cell->params[ctx->id("DELAY.OUTDEL")] = std::string("DISABLED");
        new_cell->params[ctx->id("DELAY.DEL_VALUE")] = Property(0, 7);
        new_cell->params[ctx->id("DELAY.WAIT_FOR_EDGE")] = std::string("DISABLED");

        if (type == id_IOLOGIC) {
            new_cell->params[ctx->id("IDDRXN.MODE")] = std::string("NONE");
            new_cell->params[ctx->id("ODDRXN.MODE")] = std::string("NONE");

            new_cell->params[ctx->id("MIDDRX.MODE")] = std::string("NONE");
            new_cell->params[ctx->id("MODDRX.MODE")] = std::string("NONE");
            new_cell->params[ctx->id("MTDDRX.MODE")] = std::string("NONE");

            new_cell->params[ctx->id("IOLTOMUX")] = std::string("NONE");
            new_cell->params[ctx->id("MTDDRX.DQSW_INVERT")] = std::string("DISABLED");
            new_cell->params[ctx->id("MTDDRX.REGSET")] = std::string("RESET");

            new_cell->params[ctx->id("MIDDRX_MODDRX.WRCLKMUX")] = std::string("NONE");
        }
        // Just copy ports from the Bel
        copy_bel_ports();
    } else if (type == id_TRELLIS_ECLKBUF) {
        add_port(ctx, new_cell.get(), "ECLKI", PORT_IN);
        add_port(ctx, new_cell.get(), "ECLKO", PORT_OUT);
    } else {
        log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
    }
    return new_cell;
}

static void set_param_safe(bool has_ff, CellInfo *lc, IdString name, const std::string &value)
{
    NPNR_ASSERT(!has_ff || lc->params.at(name) == value);
    lc->params[name] = value;
}

static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellInfo *lc, IdString lc_port)
{
    if (has_ff) {
        NPNR_ASSERT(lc->ports.at(lc_port).net == ff->ports.at(ff_port).net);
        NetInfo *ffnet = ff->ports.at(ff_port).net;
        if (ffnet != nullptr)
            ffnet->users.erase(
                    std::remove_if(ffnet->users.begin(), ffnet->users.end(),
                                   [ff, ff_port](PortRef port) { return port.cell == ff && port.port == ff_port; }),
                    ffnet->users.end());
    } else {
        replace_port(ff, ff_port, lc, lc_port);
    }
}

void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
{
    if (lc->hierpath == IdString())
        lc->hierpath = ff->hierpath;
    bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
    std::string reg = "REG" + std::to_string(index);
    set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
    set_param_safe(has_ff, lc, ctx->id("GSR"), str_or_default(ff->params, ctx->id("GSR"), "DISABLED"));
    set_param_safe(has_ff, lc, ctx->id("CEMUX"), str_or_default(ff->params, ctx->id("CEMUX"), "1"));
    set_param_safe(has_ff, lc, ctx->id("LSRMUX"), str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"));
    set_param_safe(has_ff, lc, ctx->id("CLKMUX"), str_or_default(ff->params, ctx->id("CLKMUX"), "CLK"));

    lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0");
    lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
    lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, ctx->id("LSRMODE"), "LSR");
    replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
    if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
        replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
    if (ff->ports.find(ctx->id("CE")) != ff->ports.end())
        replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));

    replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
    if (get_net_or_empty(ff, ctx->id("M")) != nullptr) {
        // PRLD FFs that use both M and DI
        NPNR_ASSERT(!driven_by_lut);
        // As M is used; must route DI through a new LUT
        lc->params[ctx->id(reg + "_SD")] = std::string("1");
        lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16);
        replace_port(ff, ctx->id("DI"), lc, ctx->id("D" + std::to_string(index)));
        replace_port(ff, ctx->id("M"), lc, ctx->id("M" + std::to_string(index)));
        connect_ports(ctx, lc, ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index)));
    } else {
        if (driven_by_lut) {
            replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
        } else {
            replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
        }
    }
}

void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
{
    if (lc->hierpath == IdString())
        lc->hierpath = lut->hierpath;
    lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] =
            get_or_default(lut->params, ctx->id("INIT"), Property(0, 16));
    replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
    replace_port(lut, ctx->id("B"), lc, ctx->id("B" + std::to_string(index)));
    replace_port(lut, ctx->id("C"), lc, ctx->id("C" + std::to_string(index)));
    replace_port(lut, ctx->id("D"), lc, ctx->id("D" + std::to_string(index)));
    replace_port(lut, ctx->id("Z"), lc, ctx->id("F" + std::to_string(index)));
}

void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
{
    if (lc->hierpath == IdString())
        lc->hierpath = ccu->hierpath;
    lc->params[ctx->id("MODE")] = std::string("CCU2");
    lc->params[ctx->id("LUT0_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT0"), Property(0, 16));
    lc->params[ctx->id("LUT1_INITVAL")] = get_or_default(ccu->params, ctx->id("INIT1"), Property(0, 16));

    lc->params[ctx->id("CCU2_INJECT1_0")] = str_or_default(ccu->params, ctx->id("INJECT1_0"), "YES");
    lc->params[ctx->id("CCU2_INJECT1_1")] = str_or_default(ccu->params, ctx->id("INJECT1_1"), "YES");

    replace_port(ccu, ctx->id("CIN"), lc, ctx->id("FCI"));

    replace_port(ccu, ctx->id("A0"), lc, ctx->id("A0"));
    replace_port(ccu, ctx->id("B0"), lc, ctx->id("B0"));
    replace_port(ccu, ctx->id("C0"), lc, ctx->id("C0"));
    replace_port(ccu, ctx->id("D0"), lc, ctx->id("D0"));

    replace_port(ccu, ctx->id("A1"), lc, ctx->id("A1"));
    replace_port(ccu, ctx->id("B1"), lc, ctx->id("B1"));
    replace_port(ccu, ctx->id("C1"), lc, ctx->id("C1"));
    replace_port(ccu, ctx->id("D1"), lc, ctx->id("D1"));

    replace_port(ccu, ctx->id("S0"), lc, ctx->id("F0"));
    replace_port(ccu, ctx->id("S1"), lc, ctx->id("F1"));

    replace_port(ccu, ctx->id("COUT"), lc, ctx->id("FCO"));
}

void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc)
{
    if (lc->hierpath == IdString())
        lc->hierpath = ram->hierpath;
    lc->params[ctx->id("MODE")] = std::string("RAMW");
    replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0"));
    replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0"));
    replace_port(ram, ctx->id("WAD[2]"), lc, ctx->id("C0"));
    replace_port(ram, ctx->id("WAD[3]"), lc, ctx->id("A0"));

    replace_port(ram, ctx->id("DI[0]"), lc, ctx->id("C1"));
    replace_port(ram, ctx->id("DI[1]"), lc, ctx->id("A1"));
    replace_port(ram, ctx->id("DI[2]"), lc, ctx->id("D1"));
    replace_port(ram, ctx->id("DI[3]"), lc, ctx->id("B1"));
}

static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit)
{
    auto init_prop = get_or_default(ram->params, ctx->id("INITVAL"), Property(0, 64));
    NPNR_ASSERT(!init_prop.is_string);
    const std::string &idata = init_prop.str;
    NPNR_ASSERT(idata.length() == 64);
    unsigned value = 0;
    for (int i = 0; i < 16; i++) {
        char c = idata.at(4 * i + bit);
        if (c == '1')
            value |= (1 << i);
        else
            NPNR_ASSERT(c == '0' || c == 'x');
    }
    return value;
}

void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index)
{
    if (lc->hierpath == IdString())
        lc->hierpath = ram->hierpath;
    lc->params[ctx->id("MODE")] = std::string("DPRAM");
    lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE");
    lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK");

    unsigned permuted_init0 = 0, permuted_init1 = 0;
    unsigned init0 = get_dram_init(ctx, ram, index * 2), init1 = get_dram_init(ctx, ram, index * 2 + 1);

    for (int i = 0; i < 16; i++) {
        int permuted_addr = 0;
        if (i & 1)
            permuted_addr |= 8;
        if (i & 2)
            permuted_addr |= 2;
        if (i & 4)
            permuted_addr |= 4;
        if (i & 8)
            permuted_addr |= 1;
        if (init0 & (1 << permuted_addr))
            permuted_init0 |= (1 << i);
        if (init1 & (1 << permuted_addr))
            permuted_init1 |= (1 << i);
    }

    lc->params[ctx->id("LUT0_INITVAL")] = Property(permuted_init0, 16);
    lc->params[ctx->id("LUT1_INITVAL")] = Property(permuted_init1, 16);

    if (ram->ports.count(ctx->id("RAD[0]"))) {
        connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D0"));
        connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D1"));
    }
    if (ram->ports.count(ctx->id("RAD[1]"))) {
        connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B0"));
        connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B1"));
    }
    if (ram->ports.count(ctx->id("RAD[2]"))) {
        connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C0"));
        connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C1"));
    }
    if (ram->ports.count(ctx->id("RAD[3]"))) {
        connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A0"));
        connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A1"));
    }

    if (ram->ports.count(ctx->id("WRE")))
        connect_port(ctx, ram->ports.at(ctx->id("WRE")).net, lc, ctx->id("WRE"));
    if (ram->ports.count(ctx->id("WCK")))
        connect_port(ctx, ram->ports.at(ctx->id("WCK")).net, lc, ctx->id("WCK"));

    connect_ports(ctx, ramw, id_WADO0, lc, id_WAD0);
    connect_ports(ctx, ramw, id_WADO1, lc, id_WAD1);
    connect_ports(ctx, ramw, id_WADO2, lc, id_WAD2);
    connect_ports(ctx, ramw, id_WADO3, lc, id_WAD3);

    if (index == 0) {
        connect_ports(ctx, ramw, id_WDO0, lc, id_WD0);
        connect_ports(ctx, ramw, id_WDO1, lc, id_WD1);

        replace_port(ram, ctx->id("DO[0]"), lc, id_F0);
        replace_port(ram, ctx->id("DO[1]"), lc, id_F1);

    } else if (index == 1) {
        connect_ports(ctx, ramw, id_WDO2, lc, id_WD0);
        connect_ports(ctx, ramw, id_WDO3, lc, id_WD1);

        replace_port(ram, ctx->id("DO[2]"), lc, id_F0);
        replace_port(ram, ctx->id("DO[3]"), lc, id_F1);
    } else {
        NPNR_ASSERT_FALSE("bad DPRAM index");
    }
}

void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells,
                std::unordered_set<IdString> &todelete_cells)
{
    if (nxio->type == ctx->id("$nextpnr_ibuf")) {
        trio->params[ctx->id("DIR")] = std::string("INPUT");
        replace_port(nxio, ctx->id("O"), trio, ctx->id("O"));
    } else if (nxio->type == ctx->id("$nextpnr_obuf")) {
        trio->params[ctx->id("DIR")] = std::string("OUTPUT");
        replace_port(nxio, ctx->id("I"), trio, ctx->id("I"));
    } else if (nxio->type == ctx->id("$nextpnr_iobuf")) {
        // N.B. tristate will be dealt with below
        NetInfo *i = get_net_or_empty(nxio, ctx->id("I"));
        if (i == nullptr || i->driver.cell == nullptr)
            trio->params[ctx->id("DIR")] = std::string("INPUT");
        else {
            log_info("%s: %s.%s\n", ctx->nameOf(i), ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port));
            trio->params[ctx->id("DIR")] = std::string("BIDIR");
        }
        replace_port(nxio, ctx->id("I"), trio, ctx->id("I"));
        replace_port(nxio, ctx->id("O"), trio, ctx->id("O"));
    } else {
        NPNR_ASSERT(false);
    }
    NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;

    // Rename I/O nets to avoid conflicts
    if (donet != nullptr && donet->name == nxio->name)
        rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
    if (dinet != nullptr && dinet->name == nxio->name)
        rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));

    if (ctx->nets.count(nxio->name)) {
        int i = 0;
        IdString new_name;
        do {
            new_name = ctx->id(nxio->name.str(ctx) + "$rename$" + std::to_string(i++));
        } while (ctx->nets.count(new_name));
        rename_net(ctx, ctx->nets.at(nxio->name).get(), new_name);
    }

    // Create a new top port net for accurate IO timing analysis and simulation netlists
    if (ctx->ports.count(nxio->name)) {
        IdString tn_netname = nxio->name;
        NPNR_ASSERT(!ctx->nets.count(tn_netname));
        std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
        toplevel_net->name = tn_netname;
        connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
        ctx->ports[nxio->name].net = toplevel_net.get();
        ctx->nets[tn_netname] = std::move(toplevel_net);
    }

    CellInfo *tbuf = net_driven_by(
            ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
            ctx->id("Y"));
    if (tbuf) {
        replace_port(tbuf, ctx->id("A"), trio, ctx->id("I"));
        // Need to invert E to form T
        std::unique_ptr<CellInfo> inv_lut = create_ecp5_cell(ctx, ctx->id("LUT4"), trio->name.str(ctx) + "$invert_T");
        replace_port(tbuf, ctx->id("E"), inv_lut.get(), ctx->id("A"));
        inv_lut->params[ctx->id("INIT")] = Property(21845, 16);
        connect_ports(ctx, inv_lut.get(), ctx->id("Z"), trio, ctx->id("T"));
        created_cells.push_back(std::move(inv_lut));

        if (donet->users.size() > 1) {
            for (auto user : donet->users)
                log_info("     remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));
            log_error("unsupported tristate IO pattern for IO buffer '%s', "
                      "instantiate SB_IO manually to ensure correct behaviour\n",
                      nxio->name.c_str(ctx));
        }
        ctx->nets.erase(donet->name);
        todelete_cells.insert(tbuf->name);
    }
}

NEXTPNR_NAMESPACE_END