Dallas/Maxim 1-Wire chip-ID slave for ChipFoundry OpenFrame projects.
Publishes the padframe mask_rev as a DS2401-compatible 64-bit ROM ID over a single open-drain GPIO. No external clock connection is required — the IP carries its own ring oscillator — which makes it the right ID-readout choice for OpenFrame designs that don't have a clock to spare. Read it with any stock 1-Wire master: a host MCU, an FTDI MPSSE 1-Wire library, an oscilloscope's protocol decoder, or just the Linux w1 driver with a USB 1-Wire adapter.
Two ways to use it inside openframe_project_wrapper:
CF_owire_id_pad— give the IP one dedicated open-drain GPIO.CF_owire_id_shared— share the GPIO (e.g.GPIO[0]) with your own logic; both the IP and the user can pull the wire low, read the line, and release.
Comes with a self-checking iverilog testbench acting as a real 1-Wire master, a host-side Python decoder, and a worked reference design in openframe_example.v.
Apache-2.0.
- What it does
- ROM layout
- Repository layout
- Integration
- Parameters & port reference
- Internal oscillator and synthesis
- Host-side decoding
- Bench testing the IP
- Common pitfalls
- Reference design
- License
OpenFrame chips carry a 32-bit hardcoded mask_rev value, programmed via the user_id_programming block inside chip_io_openframe. In Caravel that value is read back through the housekeeping SPI; OpenFrame has no housekeeping SoC, so the user project has to publish it. CF_owire_id does that with the minimum requirements possible on the chip side:
- one open-drain GPIO for both directions of the 1-Wire bus,
- no other connections (no clock pin, no reset choreography, no host firmware), and
- a standard, off-the-shelf host protocol so existing Linux/Arduino/FTDI tooling decodes the chip ID without custom software.
Behaviour at the package pin:
- Idle: chip releases the line (open-drain weak-high; an external pull-up — or the OpenFrame pad's weak-1 driver in
dm=010mode — holds it high). - Host's 1-Wire master pulls the line low for ≥
T_RESET_MIN_US(480 µs standard): chip detects a reset pulse. T_PRES_DELAY_USµs (15 µs default) after the master releases, the chip drives the line low forT_PRES_LOW_USµs (120 µs default) — the presence pulse.- The host writes 8 bits of command, LSB-first, using standard 1-Wire write slots:
0x33(Read ROM) → chip transmits the 64-bit ROM, LSB-first, one bit per master-initiated read slot.- Anything else → chip returns to idle.
This is exactly the protocol a DS2401 silicon serial number speaks. Linux's w1 kernel module, the OWFS userspace stack, and most MCU 1-Wire libraries already know how to read it.
64 bits, transmitted LSB-first per Dallas 1-Wire convention. The bytes appear in the order they come off the wire below:
| Bits | Field | Source |
|---|---|---|
7:0 |
Family code | FAMILY_CODE parameter (default 0xCF) |
39:8 |
Serial low 32 bits | mask_rev (from the padframe user_id_programming) |
55:40 |
Serial high 16 bits | SERIAL_HIGH parameter (default 0xCF1D) |
63:56 |
CRC-8 | Dallas polynomial 0x8C, over bits 0..55 |
Example: with the defaults and mask_rev = 0x12345678, the 8 ROM bytes in transmit order (LSB-first byte order) are:
CF 78 56 34 12 1D CF 8F
Which a Dallas-style host display would print MSB-first as 8F-CF-1D-12-34-56-78-CF.
CF_owire_id/
CF_owire_id.yaml # IP manifest
LICENSE # Apache 2.0
README.md # this file
filelist.f # include all RTL with one -f flag
rtl/
CF_owire_id.v # 1-Wire slave core + behavioural / synth osc
CF_owire_id_pad.v # OpenFrame pad wrapper -- dedicated GPIO
CF_owire_id_shared.v # OpenFrame pad wrapper -- shared GPIO (open-drain co-use)
sim/
tb_CF_owire_id.v # self-checking iverilog testbench (acts as 1-Wire master)
Makefile # `make sim` / `make vcd` / `make clean`
host/
decode_owire_id.py # Linux w1 / hex / Dallas-address decoder
Your OpenFrame synthesis flow only needs the files listed in filelist.f. The sim/ and host/ directories are for bring-up and bench testing.
CF_owire_id_pad and CF_owire_id_shared are the two ways you'd ever instantiate this IP in a real OpenFrame design. Pick one based on whether the pin you want the ID on is also a pin you need for your own logic.
| Wrapper | Pad mode | Use it when |
|---|---|---|
CF_owire_id_pad |
OpenFrame open-drain (dm=010) |
You can spare one GPIO exclusively for the ID. |
CF_owire_id_shared |
OpenFrame open-drain (dm=010) + ownership mux |
The ID pin (e.g. GPIO[0]) is also a user-facing pin. |
Both wrappers consume only one OpenFrame GPIO. Nothing else on the chip is touched — every other GPIO bit is yours to configure.
The simplest integration. Wire it to one GPIO and you're done. Pad mode is locked to dm=010 open-drain with oeb=0 and inp_dis=0.
CF_owire_id_pad #(
.FAMILY_CODE (8'hCF),
.SERIAL_HIGH (16'hCF1D)
// Leave the timing parameters at their defaults for real silicon
// (480 us / 15 us / 120 us / 30 us / 30 us). See the "Bench testing"
// section for how to shrink them for simulation.
) u_owire (
.rst_n (resetb_l),
.mask_rev (mask_rev),
// The IP owns this one pin (here GPIO[42]):
.gpio_in_pin (gpio_in[42]),
.gpio_out_pin (gpio_out[42]),
.gpio_oeb_pin (gpio_oeb[42]),
.gpio_inp_dis_pin (gpio_inp_dis[42]),
.gpio_dm2_pin (gpio_dm2[42]),
.gpio_dm1_pin (gpio_dm1[42]),
.gpio_dm0_pin (gpio_dm0[42]),
.gpio_analog_en_pin (gpio_analog_en[42]),
.gpio_analog_sel_pin (gpio_analog_sel[42]),
.gpio_analog_pol_pin (gpio_analog_pol[42]),
.gpio_ib_mode_sel_pin (gpio_ib_mode_sel[42]),
.gpio_vtrip_sel_pin (gpio_vtrip_sel[42]),
.gpio_slow_sel_pin (gpio_slow_sel[42]),
.gpio_holdover_pin (gpio_holdover[42])
);The OpenFrame open-drain pad mode (dm=010) drives strong-0 / weak-1, so you don't need an external pull-up resistor on the test board if the on-chip weak-1 driver is enough for the bus capacitance. For long cables or heavy parasitics, add a 4.7 kΩ external pull-up to VCCIO.
Use this on GPIO[0] (or any other pin you want the ID on AND want user logic on). The pad is permanently in open-drain mode and either side — the IP or the user — can pull the wire low. The line floats high (via the pad's weak-1 driver) when neither side is asserting.
wire ip_busy;
wire gpio0_in;
wire my_user_pull_low = my_logic_value; // 1 = pull the line low, 0 = release
CF_owire_id_shared #(
.FAMILY_CODE (8'hCF),
.SERIAL_HIGH (16'hCF1D)
) u_owire_shared (
.rst_n (resetb_l),
.mask_rev (mask_rev),
// User-side open-drain port for the shared pin:
.user_pull_low (my_user_pull_low),
.user_in (gpio0_in),
.ip_busy (ip_busy),
// OpenFrame pad signals for the shared GPIO (here GPIO[0]):
.gpio_in_pin (gpio_in[0]),
.gpio_out_pin (gpio_out[0]),
.gpio_oeb_pin (gpio_oeb[0]),
.gpio_inp_dis_pin (gpio_inp_dis[0]),
.gpio_dm2_pin (gpio_dm2[0]),
.gpio_dm1_pin (gpio_dm1[0]),
.gpio_dm0_pin (gpio_dm0[0]),
.gpio_analog_en_pin (gpio_analog_en[0]),
.gpio_analog_sel_pin (gpio_analog_sel[0]),
.gpio_analog_pol_pin (gpio_analog_pol[0]),
.gpio_ib_mode_sel_pin (gpio_ib_mode_sel[0]),
.gpio_vtrip_sel_pin (gpio_vtrip_sel[0]),
.gpio_slow_sel_pin (gpio_slow_sel[0]),
.gpio_holdover_pin (gpio_holdover[0])
);Sharing rules:
- Whoever pulls low wins. The pad drives strong-0 if
user_pull_low=1OR the IP is asserting low. If neither side asserts, the pad releases and the line floats high. - The user can read the line at any time via
user_in. - The user can't drive high. Open-drain only releases. If you need to assert a high level, use a different GPIO — or know that the line floats high once you stop pulling low.
ip_busy = 1while the IP is in the middle of a 1-Wire transaction (presence pulse, command receive, or ROM readout). User FSMs that don't want their pulls interfering with a host master can gate on!ip_busy.- Constraint: the user MUST NOT pull the line low continuously for ≥
T_RESET_MIN_US(480 µs by default). A sustained low looks identical to a 1-Wire master reset pulse and will wake the slave's FSM. This is irrelevant for typical user uses (LEDs, status bits, button reads, short pulses); be careful if you implement another protocol on the same wire.
CF_owire_id is the 1-Wire slave underneath both pad wrappers. It's logic-plus-ring-oscillator with no pad config in it. You'd only instantiate it directly if you've built your own pad-config block and want to plug the slave into it.
wire owire_oeb;
wire owire_busy;
CF_owire_id #(
.FAMILY_CODE (8'hCF),
.SERIAL_HIGH (16'hCF1D)
) u_owire (
.rst_n (resetb_l),
.id_in (mask_rev),
.owire_in (line_value), // pad input
.owire_oeb (owire_oeb), // 0 = drive low, 1 = release
.busy (owire_busy)
);You're then on the hook for routing owire_oeb and the wire's input through your own open-drain pad config. For 99% of OpenFrame projects, just use CF_owire_id_pad or CF_owire_id_shared and skip this section.
| Name | Default | Description |
|---|---|---|
FAMILY_CODE |
8'hCF |
Dallas family-code byte (low 8 bits of ROM). |
SERIAL_HIGH |
16'hCF1D |
High 16 bits of the 48-bit serial. |
OSC_CYCLES_PER_US |
100 |
Internal oscillator cycles per µs. Scale all timings by changing this. |
T_RESET_MIN_US |
480 |
Master reset pulse threshold (1-Wire standard). |
T_PRES_DELAY_US |
15 |
Delay before presence after master release. |
T_PRES_LOW_US |
120 |
Presence pulse low duration. |
T_SAMPLE_US |
30 |
Slave sample point inside a master-write slot. |
T_READ_LOW_US |
30 |
Slave-write-0 hold during a read slot. |
| Signal | Dir | Width | Description |
|---|---|---|---|
rst_n |
in | 1 | Active-low chip reset. Holds the slave in IDLE. |
id_in |
in | 32 | Low 32 bits of the serial. Connect to mask_rev. |
owire_in |
in | 1 | Bidirectional pad input (line level). |
owire_oeb |
out | 1 | Bidirectional pad output-enable bar. 0 = drive low, 1 = release. |
busy |
out | 1 | High while the FSM is engaged (anything other than IDLE). |
Both wrappers add a mask_rev[31:0] payload input and the 14 OpenFrame GPIO control signals: gpio_in_pin, gpio_out_pin, gpio_oeb_pin, gpio_inp_dis_pin, gpio_dm[2:0]_pin, gpio_analog_{en,sel,pol}_pin, gpio_ib_mode_sel_pin, gpio_vtrip_sel_pin, gpio_slow_sel_pin, gpio_holdover_pin.
CF_owire_id_shared additionally exposes:
| Signal | Dir | Description |
|---|---|---|
user_pull_low |
in | 1 = user is pulling the wire low (wired-OR with the IP). |
user_in |
out | Pad input value. Always valid. |
ip_busy |
out | 1 while the IP is engaged in a 1-Wire transaction. |
CF_owire_id carries its own clock. The source RTL has three osc implementations behind \ifdef` guards:
| Define | Osc implementation | When to use |
|---|---|---|
COCOTB_SIM |
Behavioural always #5 sim_osc = ~sim_osc; |
cocotb tests |
IVERILOG_SIM |
Same behavioural osc | this repo's sim/ |
SYNTHESIS |
Three-inverter ring placeholder | synthesis flow |
| (none) | Behavioural osc (fallback) | quick experiments |
For real silicon you MUST replace the \ifdef SYNTHESIS` placeholder with PDK-keep-attribute cells (so the synthesizer doesn't optimize the ring away). Example for sky130:
(* keep = "true" *) sky130_fd_sc_hd__inv_2 i0 (.A(n2), .Y(n0));
(* keep = "true" *) sky130_fd_sc_hd__inv_2 i1 (.A(n0), .Y(n1));
(* keep = "true" *) sky130_fd_sc_hd__inv_2 i2 (.A(n1), .Y(n2));Tune the chain length, drive strength, and any added cap loads to land near OSC_CYCLES_PER_US ≈ 100. The 1-Wire spec is forgiving: anything within roughly 0.5x–2x of the nominal frequency still works with a stock host master.
You need a real 1-Wire master on the host side to talk to the chip. Three common options, easiest first:
Plug a DS9097-style adapter (or any kernel-supported 1-Wire master) into a Linux box. Connect the adapter's data line to the chip's GPIO[42] (or wherever you wired the IP). The kernel auto-discovers the device:
$ ls /sys/bus/w1/devices/
cf-0000cf1d12345678 w1_bus_master1
$ ./host/decode_owire_id.py --w1
=== cf-0000cf1d12345678 ===
raw bytes (LSB-first) : CF 78 56 34 12 1D CF 8F
Dallas address (MSB) : 8F-CF-1D-12-34-56-78-CF
64-bit ROM : 0x8FCF1D12345678CF
family code : 0xCF OK
serial low (mask_rev) : 0x12345678
serial high (magic) : 0xCF1D OK
CRC-8 (RX) : 0x8F
CRC-8 (recomputed) : 0x8F OK
Most logic analyzers (Saleae, DSLogic, etc.) have a 1-Wire protocol decoder built in. Capture a Read-ROM transaction, grab the 8 byte payload, then:
$ ./host/decode_owire_id.py --hex-lsb "CF 78 56 34 12 1D CF 8F"
Or paste in a Dallas-format address (MSB-first):
$ ./host/decode_owire_id.py --addr "8F-CF-1D-12-34-56-78-CF"
Any MCU 1-Wire library can issue the standard reset + presence + Read-ROM (0x33) + 64-bit shift sequence. Once you have the 8 bytes, feed them to the same decode_owire_id.py --hex-lsb mode for verification.
Exit status is 0 if the CRC matches, non-zero otherwise — easy to wire into bring-up scripts.
Two layers of tests:
1. Block-level (iverilog, no OpenFrame infrastructure) — sim/tb_CF_owire_id.v instantiates the bare CF_owire_id core, builds an open-drain wire model, and runs a full Verilog 1-Wire master. It pulses reset, waits for presence, sends 0x33, clocks 64 read slots, validates the ROM against the expected mask_rev. Two back-to-back frames with different id_in values prove re-arm across resets.
The DUT's 1-Wire timing parameters are scaled down by ~60x in the testbench (T_RESET_MIN_US=5 etc.) so the run finishes in ~hundreds of µs of simulated time. The protocol itself is identical to canonical 480/15/120/30/30 µs timings; real silicon uses the defaults.
$ cd sim
$ make sim
iverilog -g2012 -Wall -DIVERILOG_SIM -o tb_CF_owire_id.vvp ../rtl/CF_owire_id.v tb_CF_owire_id.v
vvp tb_CF_owire_id.vvp
---- CAFEBABE ----
raw ROM bytes (LSB-first) : cf be ba fe ca 1d cf 47
64-bit ROM : 0x47cf1dcafebabecf
family code : 0xcf (expected 0xcf)
serial[31:0] = mask_rev : 0xcafebabe (expected 0xcafebabe)
serial[47:32] = magic : 0xcf1d (expected 0xcf1d)
CRC-8 (Dallas 0x8C) : 0x47 (recomputed 0x47)
PASS
---- 12345678 ----
raw ROM bytes (LSB-first) : cf 78 56 34 12 1d cf 8f
PASS
RESULT: PASS
make vcd opens the waveform in gtkwave.
2. Top-level (cocotb, OpenFrame integration) — verilog/dv/cocotb/owire_id_test/owire_id_test.py runs the IP through the full openframe_project_wrapper, drives the shared GPIO[0] as a 1-Wire master from outside the chip, decodes the 64-bit ROM, and verifies the shared-pin handoff to user logic when the IP is idle. Run with:
$ cf verify owire_id_test --sim rtl
- No pull-up on a long-cable test board. The OpenFrame pad in
dm=010mode drives strong-0 / weak-1 — good enough for a few cm of board trace, marginal for long cables with significant capacitance. Add a 4.7 kΩ external pull-up to VCCIO if you see slow rising edges that confuse the host master. - Forgot to replace the
SYNTHESISplaceholder ring osc. The default\ifdef SYNTHESISbranch is a placeholder with hardcoded#1` delays. Synthesis tools strip those out, leaving you with a combinational loop the optimizer will collapse. Replace with PDK-keep-attribute inverter cells before tape-out (see the "Internal oscillator" section). OSC_CYCLES_PER_USway off from 100. All slot timings derive from this. If your ring osc lands at 50 MHz instead of 100, setOSC_CYCLES_PER_US=50to keep the µs-denominated thresholds correct.- Shared-pin user logic pulling low for > 480 µs. That looks identical to a master reset pulse and wakes the slave. Keep user-driven lows well below
T_RESET_MIN_USunless you actually want to issue a reset pulse. - Two 1-Wire devices on the same bus. This IP implements the Read-ROM (
0x33) subset only — no Search-ROM, no Match-ROM. It works as the sole slave on its bus. For multi-slave buses (or when you want the chip to share a wire with a real DS24xx), implement the Search-ROM algorithm on top of the current FSM.
The IP is wired up end-to-end in openframe_user_project/verilog/rtl/openframe_example.v (this repo). It demonstrates the shared-pin pattern: CF_owire_id_shared lives on GPIO[0] and the demo's user logic also pulls the same wire low (and reads it back) without breaking 1-Wire transactions.
The cocotb test at openframe_user_project/verilog/dv/cocotb/owire_id_test/owire_id_test.py verifies:
- a full Reset + Presence + Read-ROM + 64-bit shift sequence completes on
GPIO[0], - the 64-bit ROM decodes to family
0xCF, serial high0xCF1D, the on-chipmask_rev, and a matching CRC-8, - the shared-pin co-use with user logic works (user can pull low and read while the IP is idle).
That same example is the recommended starting point for any new OpenFrame project that wants the ID on GPIO[0] while still using GPIO[0] for user logic.
Apache 2.0 — see LICENSE.