KUNST
SCHÖNHEIT & WELLNESS
HANDWERK
KULTUR & GESCHICHTE
UNTERHALTUNG
UMFELD
ESSEN & GETRÄNKE
REVERSE ENGINEERING
WISSENSCHAFTEN
SPORT
TECHNOLOGIE
WEARABLES
Your chip_core as Standard Cells: 42 GF180MCU Gates, Proven Equal to the Verilog
Youblob (drawn from the netlist) · CC0
Ed

Erstellt von

Ed

29. September 2026FI
4
0
0
0
0

Your chip_core as Standard Cells: 42 GF180MCU Gates, Proven Equal to the Verilog

A foundry does not make Verilog. It makes transistors, arranged as standard cells: small, fixed layouts such as an inverter, an AND gate, a full adder or a flip-flop, each drawn once by the library's authors and placed thousands of times. Between your Verilog and the wafer, a synthesis tool rewrites every line of your design as a list of those cells, a netlist. This rung does that by hand for the previous rung's PWM dimmer, with the cells of the GF180MCU library that wafer.space's template uses by default. The dimmer becomes 42 cells: a counter of half adders and flip-flops, and a comparator that is really a subtractor. Then the netlist is simulated on the PDK's own Verilog models of those cells, the same models the template's gate-level simulation uses, and compared with the original Verilog on every clock. Everything here was run for this blueprint with Icarus Verilog 12.0 and cocotb 2.1.0: the gates and the Verilog agreed on all 96,800 clocks compared, and a deliberately broken netlist was caught at once.
Erfahren
An evening

Anweisungen

1

The comparator is a subtractor

Jupyter-Notebook wird geladen …
2

The Verilog this rung turns into gates

The embedded blueprint writes the dimmer in Verilog and tests it. This rung rewrites the same logic as cells.
3

What a standard cell is

The template's default library is gf180mcu_fd_sc_mcu7t5v0: GlobalFoundries' 180 nm cells, 7 tracks high, for 5 V. Every cell sits on the library's placement site, 0.56 by 3.92 micrometres, and comes as a set of views: a layout for the foundry, a timing file for the tools, and a Verilog model for simulation. Six cells are enough for the dimmer. addh_1 is a half adder (S = A xor B, CO = A and B); addf_1 a full adder; and2_1 and and3_1 AND gates; inv_1 an inverter; dffq_1 a D flip-flop that copies D to Q on each rising clock edge. That flip-flop has no reset pin, so the reset goes in front of it: an AND gate lets the next count through only while rst_n is high, which clears the counter on the first clock edge in reset. That is a synchronous reset, exactly what the Verilog asked for. In the real flow, LibreLane runs the synthesis, choosing cells and sizes itself; its netlist also carries cells added for the layout, such as buffers. Writing 42 of them by hand once shows what the tool is doing.
4

The netlist

pwm_gates.v: the dimmer's logic as 42 GF180MCU cells. Each line is one cell and the names of its pins are the library's. The two generate loops write the eight identical bit slices; the comparator's bit 0 is a half adder because nothing carries into it. The MUTANT block is the deliberate mistake used in step 7. chip_core_gates.sv is the previous rung's chip_core with its two always_ff blocks replaced by this module.
pwm_gates.vverilog
// SPDX-License-Identifier: Apache-2.0
// Youblob silicon rung 4: the PWM dimmer's logic written by hand as GF180MCU standard cells
// (gf180mcu_fd_sc_mcu7t5v0), the way a synthesis tool writes it. 42 cells.
//
//   count  <= rst_n ? count + enable : 0          8 half adders, 8 AND2, 8 flip-flops
//   pwm    <= rst_n & enable & (count < duty)     8 inverters, 1 half adder + 7 full adders, 1 AND3, 1 flip-flop
//
// The comparator is a subtractor whose answer is thrown away: duty + ~count carries out of
// bit 7 exactly when duty > count, which is count < duty.
// The library's dffq has no reset pin, so the reset goes in front of D: an AND gate forces 0
// on the next clock edge while rst_n is low. That is a synchronous reset, as in the RTL.

`default_nettype none

module pwm_gates (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       enable,
    input  wire [7:0] duty,
    output wire       pwm
);
    wire [7:0] count;      // flip-flop outputs
    wire [8:0] c;          // incrementer carry chain
    wire [7:0] sum;        // count + enable
    wire [7:0] d_next;     // sum, forced to 0 in reset
    wire [7:0] ncount;     // ~count
    wire [8:0] k;          // comparator carry chain; k[8] = (count < duty)
    wire       pwm_d;

    assign c[0] = enable;  // a connection, not a gate

    genvar i;
    generate
        for (i = 0; i < 8; i = i + 1) begin : bit_
            // counter bit i
            gf180mcu_fd_sc_mcu7t5v0__addh_1 inc (.A(count[i]), .B(c[i]), .S(sum[i]), .CO(c[i+1]));
            gf180mcu_fd_sc_mcu7t5v0__and2_1 rst (.A1(sum[i]), .A2(rst_n), .Z(d_next[i]));
            gf180mcu_fd_sc_mcu7t5v0__dffq_1 ff  (.CLK(clk), .D(d_next[i]), .Q(count[i]));
            // comparator bit i
            gf180mcu_fd_sc_mcu7t5v0__inv_1  inv (.I(count[i]), .ZN(ncount[i]));
        end
    endgenerate

    // bit 0 has no carry in, so a half adder does; bits 1..7 are full adders. The sums are not used.
`ifdef MUTANT
    // one missing inversion, the kind of slip this test exists to catch
    gf180mcu_fd_sc_mcu7t5v0__addh_1 cmp0 (.A(duty[0]), .B(count[0]), .S(), .CO(k[1]));
`else
    gf180mcu_fd_sc_mcu7t5v0__addh_1 cmp0 (.A(duty[0]), .B(ncount[0]), .S(), .CO(k[1]));
`endif
    generate
        for (i = 1; i < 8; i = i + 1) begin : cmp_
            gf180mcu_fd_sc_mcu7t5v0__addf_1 fa (.A(duty[i]), .B(ncount[i]), .CI(k[i]), .S(), .CO(k[i+1]));
        end
    endgenerate
    assign k[0] = 1'b0;    // unused; bit 0 is a half adder

    gf180mcu_fd_sc_mcu7t5v0__and3_1 pwm_and (.A1(enable), .A2(k[8]), .A3(rst_n), .Z(pwm_d));
    gf180mcu_fd_sc_mcu7t5v0__dffq_1 pwm_ff  (.CLK(clk), .D(pwm_d), .Q(pwm));
endmodule

`default_nettype wire

Benötigte Werkzeuge:

Desktop-ComputerDesktop-Computer
5

Both versions side by side

tb_equiv.sv puts the Verilog core and the gate core next to each other on the same inputs, so a test can compare their outputs directly. Nothing in it is logic: it is only wiring.
tb_equiv.svsystemverilog
// SPDX-License-Identifier: Apache-2.0
// Both versions of the core side by side, fed the same inputs: the RTL (chip_core) and the
// standard-cell netlist (chip_core_gates). The cocotb test compares their outputs every clock.
`default_nettype none
module tb_equiv #(parameter NUM_INPUT_PADS = 4, NUM_BIDIR_PADS = 38, NUM_ANALOG_PADS = 4) (
    input  wire clk,
    input  wire rst_n,
    input  wire [NUM_INPUT_PADS-1:0] input_in,
    input  wire [NUM_BIDIR_PADS-1:0] bidir_in,
    output wire [NUM_BIDIR_PADS-1:0] out_rtl,
    output wire [NUM_BIDIR_PADS-1:0] out_gates
);
    wire [NUM_INPUT_PADS-1:0] ipu_r, ipd_r, ipu_g, ipd_g;
    wire [NUM_BIDIR_PADS-1:0] oe_r, cs_r, sl_r, ie_r, pu_r, pd_r, oe_g, cs_g, sl_g, ie_g, pu_g, pd_g;
    wire [NUM_ANALOG_PADS-1:0] analog;

    chip_core #(NUM_INPUT_PADS, NUM_BIDIR_PADS, NUM_ANALOG_PADS) rtl (
        .clk(clk), .rst_n(rst_n), .input_in(input_in), .input_pu(ipu_r), .input_pd(ipd_r),
        .bidir_in(bidir_in), .bidir_out(out_rtl), .bidir_oe(oe_r), .bidir_cs(cs_r), .bidir_sl(sl_r),
        .bidir_ie(ie_r), .bidir_pu(pu_r), .bidir_pd(pd_r), .analog(analog));

    chip_core_gates #(NUM_INPUT_PADS, NUM_BIDIR_PADS, NUM_ANALOG_PADS) gates (
        .clk(clk), .rst_n(rst_n), .input_in(input_in), .input_pu(ipu_g), .input_pd(ipd_g),
        .bidir_in(bidir_in), .bidir_out(out_gates), .bidir_oe(oe_g), .bidir_cs(cs_g), .bidir_sl(sl_g),
        .bidir_ie(ie_g), .bidir_pu(pu_g), .bidir_pd(pd_g), .analog(analog));
endmodule
`default_nettype wire

Benötigte Werkzeuge:

Desktop-ComputerDesktop-Computer
6

The test that proves them equal

test_equiv.py builds the design against the PDK's own cell models, primitives.v and gf180mcu_fd_sc_mcu7t5v0.v from the gf180mcuD build the template pins, with FUNCTIONAL defined: the cells' logic without their timing checks, as the template's gate-level simulation does. Three tests: the flip-flops start unknown and one clock in reset clears them; every duty value from 0 to 255 for 300 clocks each; and 20,000 clocks of random duty changes, enable toggles and resets. Download the PDK with the template's make clone-pdk, or unpack the gf180mcu_fd_sc_mcu7t5v0 archive from the ciel releases, and point PDK_ROOT at it.
test_equiv.pypython
# SPDX-License-Identifier: Apache-2.0
# Gate-level check for the PWM dimmer: the hand-written GF180MCU cell netlist against the RTL,
# simulated with the PDK's own functional cell models (gf180mcuD, the commit the template pins).
import os
import random
from pathlib import Path

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles, FallingEdge, RisingEdge
from cocotb_tools.runner import get_runner

PWM_PAD = 8
PDK = Path(os.getenv("PDK_ROOT", "pdk")) / "gf180mcuD" / "libs.ref" / "gf180mcu_fd_sc_mcu7t5v0" / "verilog"


async def start(dut):
    cocotb.start_soon(Clock(dut.clk, 20, unit="ns").start())
    dut.input_in.value = 0
    dut.bidir_in.value = 0
    dut.rst_n.value = 0
    await ClockCycles(dut.clk, 2)
    dut.rst_n.value = 1


@cocotb.test()
async def test_reset_clears_x(dut):
    """Before the first clock the flip-flops hold X; one clock with reset low makes them 0."""
    cocotb.start_soon(Clock(dut.clk, 20, unit="ns").start())
    dut.input_in.value = 1
    dut.bidir_in.value = 100
    dut.rst_n.value = 0
    await FallingEdge(dut.clk)                              # before any rising edge
    assert not dut.out_gates.value.is_resolvable, "flip-flops should start unknown"
    await RisingEdge(dut.clk)
    await FallingEdge(dut.clk)
    assert dut.out_gates.value.is_resolvable and int(dut.out_gates.value) == 0


@cocotb.test()
async def test_every_duty_lockstep(dut):
    """All 256 duty values, 300 clocks each: the gates and the RTL agree on every clock."""
    await start(dut)
    dut.input_in.value = 1
    checked = 0
    for duty in range(256):
        dut.bidir_in.value = duty
        for _ in range(300):
            await FallingEdge(dut.clk)
            assert dut.out_gates.value == dut.out_rtl.value, f"duty {duty}: gates {dut.out_gates.value} rtl {dut.out_rtl.value}"
            checked += 1
    dut._log.info(f"{checked} clocks compared, 0 differences")


@cocotb.test()
async def test_random_lockstep(dut):
    """Random duty changes, enable toggling and resets mid-run: still identical on every clock."""
    await start(dut)
    rng = random.Random(2026)
    for _ in range(20000):
        if rng.random() < 0.02:
            dut.bidir_in.value = rng.randrange(256)
        if rng.random() < 0.005:
            dut.input_in.value = rng.randrange(2)
        dut.rst_n.value = 0 if rng.random() < 0.001 else 1
        await FallingEdge(dut.clk)
        assert dut.out_gates.value == dut.out_rtl.value
    dut._log.info("20000 random clocks compared, 0 differences")


def test_runner():
    here = Path(__file__).resolve().parent
    defines = {"FUNCTIONAL": True}                          # the cells' logic only, no timing checks
    if os.getenv("MUTANT"):
        defines["MUTANT"] = True
    runner = get_runner("icarus")
    runner.build(
        sources=[PDK / "primitives.v", PDK / "gf180mcu_fd_sc_mcu7t5v0.v",
                 here / "pwm_gates.v", here / "chip_core.sv", here / "chip_core_gates.sv", here / "tb_equiv.sv"],
        hdl_toplevel="tb_equiv",
        defines=defines,
        build_args=["-g2012"],
        timescale=("1ns", "1ps"),
        always=True,
    )
    runner.test(hdl_toplevel="tb_equiv", test_module="test_equiv")


if __name__ == "__main__":
    test_runner()

Benötigte Werkzeuge:

Desktop-ComputerDesktop-Computer
7

What the simulation printed

Run for this blueprint with Icarus Verilog 12.0 and cocotb 2.1.0: test_equiv.test_reset_clears_x PASS test_equiv.test_every_duty_lockstep PASS (76800 clocks compared, 0 differences) test_equiv.test_random_lockstep PASS (20000 random clocks compared, 0 differences) TESTS=3 PASS=3 FAIL=0 SKIP=0 To prove the test can fail, the netlist was built with MUTANT defined, which feeds count[0] into the comparator's first adder instead of its inverse: one missing inverter. Both comparison tests failed at once, the first at duty 1, where the gates held the output low and the Verilog drove it high. The first test shows something a Verilog simulation hides. In the gate model, a flip-flop has no value until a clock edge gives it one, so every output is X, unknown, until reset has been clocked in. A real chip powers up the same way, with each flip-flop in whichever state it happens to fall into. That is why the reset must reach every flip-flop that matters.
8

The same check in the template

In the template, the flow does the synthesis. make librelane runs it and saves the results to final/; make sim-gl then runs the template's cocotb testbench on final/pnl/chip_top.pnl.v, the netlist after placement and routing, with the same library models and the FUNCTIONAL and USE_POWER_PINS defines. Its test has to be rewritten for your design first, as the previous rung describes. Neither gate-level simulation checks timing: FUNCTIONAL leaves the delays out. Timing is checked by the flow's static timing analysis against the clock in the template's configuration, a 40 ns period (25 MHz). The comparator here is a carry chain eight adders long, and a chain like that is what such an analysis looks at first.
9

A gate-level simulation that will not build or will not match

Gate-level simulation troubleshooting.

Flow

Loading...
10

Sources and honest limits

**Sources**, read 29 September 2026: the GF180MCU PDK at the commit the template pins (gf180mcuD, f6eeac7dad085ffcc829ccfd721f7b4ce39edcf7, from the fossi-foundation ciel releases; Apache-2.0): the cell models gf180mcu_fd_sc_mcu7t5v0.v and primitives.v, the timing file gf180mcu_fd_sc_mcu7t5v0__tt_025C_5v00.lib for the areas, and the technology LEF for the site size. The wafer-space/gf180mcu-project-template repository (Makefile, cocotb/chip_top_tb.py, librelane/config.yaml, librelane/slots/slot_0p5x0p5.yaml; Apache-2.0). **Honest limits.** The netlist is written by hand, not produced by synthesis; a synthesis tool would choose its own cells and sizes. The simulation uses the cells' functional models only, with no delays, so it proves the logic, not the timing. Nothing here has been placed, routed or made.

Benötigte Werkzeuge

1

CC0 Gemeinfrei

Dieser Blueprint ist unter CC0 veröffentlicht. Sie dürfen dieses Werk für jeden Zweck frei kopieren, ändern, verbreiten und verwenden, ohne um Erlaubnis zu fragen.

Unterstützen Sie den Maker, indem Sie Produkte über seinen Blueprint kaufen, wo er eine Maker-Provision von Anbietern festgelegt, verdient. Oder erstellen Sie eine neue Iteration dieses Blueprints und verbinden Sie ihn in Ihrem eigenen Blueprint, um Einnahmen zu teilen.

Diskussion

(0)

Anmelden um an der Diskussion teilzunehmen

Kommentare werden geladen...