From d8117e3cadaa4f4db606b64a465b7638b05dac68 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Thu, 6 Nov 2025 18:17:05 +1000 Subject: [PATCH] Gowin. Implement ADC. (#1597) ADC support for GW5A-25 chips has been added. The inputs of this primitive are fixed and do not require routing, although they can be switched dynamically. The .CST file also specifies the pins used as signal sources for the bus0 and bus1 ADC buses. Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 3 ++ himbaechel/uarch/gowin/cst.cc | 51 +++++++++++++++++++++--- himbaechel/uarch/gowin/gowin.h | 6 +++ himbaechel/uarch/gowin/gowin_arch_gen.py | 16 +++++++- himbaechel/uarch/gowin/pack.cc | 31 ++++++++++++++ 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 53ca6a23..5307eaee 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1038,6 +1038,9 @@ X(RPLLA) X(PLLVR) X(PLLA) +// ADC +X(ADC) + // primitive attributes X(INIT) X(FF_USED) diff --git a/himbaechel/uarch/gowin/cst.cc b/himbaechel/uarch/gowin/cst.cc index b02fab9f..e502750f 100644 --- a/himbaechel/uarch/gowin/cst.cc +++ b/himbaechel/uarch/gowin/cst.cc @@ -80,6 +80,7 @@ struct GowinCstReader constrained_cells.insert(std::make_pair(cellId, belId)); } }; + pool adc_ios; // bus#/X#Y# log_info("Reading constraints...\n"); try { @@ -95,6 +96,7 @@ struct GowinCstReader std::regex hclkre = std::regex("INS_LOC +\"([^\"]+)\" +(TOP|RIGHT|BOTTOM|LEFT)SIDE\\[([0,1])\\] *;*[\\s\\S]*"); std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*"); + std::regex adcre = std::regex("USE_ADC_SRC +bus([0-9]) +IO([TRBL])([0-9]+) *;.*[\\s\\S]*"); std::smatch match, match_attr, match_pinloc; std::string line, pinlines[2]; std::vector constrained_clkdivs; @@ -104,7 +106,8 @@ struct GowinCstReader ioport, insloc, clock, - hclk + hclk, + adc } cst_type; while (!in.eof()) { @@ -123,10 +126,14 @@ struct GowinCstReader if (std::regex_match(line, match, hclkre)) { cst_type = hclk; } else { - if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { - log_warning("Invalid constraint: %s\n", line.c_str()); + if (std::regex_match(line, match, adcre)) { + cst_type = adc; + } else { + if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { + log_warning("Invalid constraint: %s\n", line.c_str()); + } + continue; } - continue; } } } @@ -135,11 +142,26 @@ struct GowinCstReader IdString net = ctx->id(match[1]); auto it = ctx->cells.find(net); - if (cst_type != clock && it == ctx->cells.end()) { + if (cst_type != clock && cst_type != adc && it == ctx->cells.end()) { log_info("Cell %s not found\n", net.c_str(ctx)); continue; } switch (cst_type) { + case adc: { // USE_ADC_SRC bus# IOLOC + int col = std::stoi(match[3]); + int row = 1; // Top + std::string side = match[2].str(); + if (side == "R") { + row = col; + col = ctx->getGridDimX(); + } else if (side == "B") { + row = ctx->getGridDimY(); + } else if (side == "L") { + row = col; + col = 1; + } + adc_ios.insert(ctx->idf("%d/X%dY%d", std::stoi(match[1]), row - 1, col - 1)); + } break; case clock: { // CLOCK name BUFG|S=# std::string which_clock = match[2]; std::string lw = match[4]; @@ -258,6 +280,25 @@ struct GowinCstReader for (auto &cell : constrained_cells) { log_info("Cell %s is constrained to %s\n", cell.first.c_str(ctx), cell.second.str(ctx).c_str()); } + if (!adc_ios.empty()) { + log_info("ADC iobufs:\n"); + for (auto &bus_io : adc_ios) { + log_info(" bus %s\n", bus_io.c_str(ctx)); + } + } + } + if (!adc_ios.empty()) { + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + + if (is_adc(&ci)) { + int idx = 0; + for (auto &bus_io : adc_ios) { + ci.setAttr(ctx->idf("ADC_IO_%d", idx), bus_io.str(ctx)); + ++idx; + } + } + } } return true; } catch (log_execution_error_exception) { diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index aaeb8d42..22b62dec 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -100,6 +100,10 @@ inline bool is_userflash(const CellInfo *cell) { return type_is_userflash(cell-> inline bool type_is_pll(IdString cell_type) { return cell_type.in(id_rPLL, id_PLLVR); } inline bool is_pll(const CellInfo *cell) { return type_is_pll(cell->type); } +// Return true if a cell is a ADC +inline bool type_is_adc(IdString cell_type) { return cell_type.in(id_ADC); } +inline bool is_adc(const CellInfo *cell) { return type_is_adc(cell->type); } + // Return true if a cell is a EMCU inline bool type_is_emcu(IdString cell_type) { return cell_type == id_EMCU; } inline bool is_emcu(const CellInfo *cell) { return type_is_emcu(cell->type); } @@ -254,6 +258,8 @@ enum PINCFG_Z = 400, + ADC_Z = 401, + // The two least significant bits encode Z for 9-bit adders and // multipliers, if they are equal to 0, then we get Z of their common // 18-bit equivalent. diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 5058005c..8e211829 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -75,7 +75,8 @@ MIPIIBUF_Z = 302 DLLDLY_Z = 303 # : 305 reserve for 2 DLLDLYs -PINCFG_Z = 400 # +PINCFG_Z = 400 +ADC_Z = 401 DSP_Z = 509 @@ -463,7 +464,7 @@ def create_nodes(chip: Chip, db: chipdb): for i in range(5): nodes.append([NodeWire(x, y, f'COUT{i}'), NodeWire(x, y, f'CIN{i + 1}')]); - # gobal carry chain + # global carry chain if x > 1 and chip.tile_type_at(x - 1, y).extra_data.tile_class == chip.strs.id('LOGIC'): nodes.append([NodeWire(x, y, f'CIN0'), NodeWire(x - 1, y, f'COUT5')]) @@ -788,6 +789,17 @@ def create_extra_funcs(tt: TileType, db: chipdb, x: int, y: int): for pin, wire in desc['inputs'].items(): tt.create_wire(wire, "PLL_I") tt.add_bel_pin(pll, pin, wire, PinType.INPUT) + elif func == 'adc': + pll = tt.create_bel("ADC", "ADC", z = ADC_Z) + for pin, wire in desc['outputs'].items(): + tt.create_wire(wire, "ADC_O") + tt.add_bel_pin(pll, pin, wire, PinType.OUTPUT) + for pin, wire in desc['inputs'].items(): + if pin == 'CLK' or pin == 'MDRP_CLK': + tt.create_wire(wire, "TILE_CLK") + else: + tt.create_wire(wire, "ADC_I") + tt.add_bel_pin(pll, pin, wire, PinType.INPUT) elif func == 'gnd_source': # GND is the logic low level generator tt.create_wire('VSS', 'GND', const_value = 'VSS') diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index ed3433ec..5c09f7a5 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -4073,6 +4073,34 @@ struct GowinPacker } } + // =================================== + // ADC + // =================================== + void pack_adc(void) + { + log_info("Pack ADC...\n"); + + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + + if (is_adc(&ci)) { + for (int i = 0; i < 14; ++i) { + if (i < 2) { + ci.renamePort(ctx->idf("MDRP_OPCODE[%d]", i), ctx->idf("MDRP_OPCODE%d", i)); + } + if (i < 3) { + ci.renamePort(ctx->idf("VSENCTL[%d]", i), ctx->idf("VSENCTL%d", i)); + } + if (i < 8) { + ci.renamePort(ctx->idf("MDRP_WDATA[%d]", i), ctx->idf("MDRP_WDATA%d", i)); + ci.renamePort(ctx->idf("MDRP_RDATA[%d]", i), ctx->idf("MDRP_RDATA%d", i)); + } + ci.renamePort(ctx->idf("ADCVALUE[%d]", i), ctx->idf("ADCVALUE%d", i)); + } + } + } + } + // =================================== // HCLK -- CLKDIV and CLKDIV2 for now // =================================== @@ -4557,6 +4585,9 @@ struct GowinPacker pack_pll(); ctx->check(); + pack_adc(); + ctx->check(); + pack_bsram(); ctx->check();