Hardware Connector HDL Generator v0.1

STEP: 1574 / Date: 2026-08-30 / Test: 69/69 PASS / Type: STEP 1570 spec を 実 artifact 生成器に格上げ (Verilog + .cst + testbench skeleton)

ConnectorSpec → Verilog + Gowin .cst + testbench の 三点セット skeleton emitter。 STEP 1570 で定義した spec 5 軸 (lines / encoding / clock / power / physical) + 5 layer roles (receiver / decoder / oscillator / arbiter / output) を Verilog port list + 層別 sub-module stub に mapping。

Deterministic — 同じ (spec, board profile) は 常に 同じ text を返す (diff 可能、 CI 化 前提)。 Tang Nano 9K の 27 MHz clk を default 想定、 board profile で pin 番号を substitute。

Skeleton であって完成品ではない — 各 layer に TODO marker + synthesizable pass-through default で「そのまま合成通る空箱」。 実 protocol (JJY pulse 幅解析 / Manchester transition 検出) は 生成器が意図的に refuse、 手書き or Phase C spike で埋める。

Chat-Claude arc の位置づけ

「藤本さんが目指しているのは、ファイルを投げると コネクタ/マシンに落ちる生成ツールのはずです。」
「ツールになるのは、他の選択肢も同じ形式で並べられて、入力条件から選択・生成できるようになった時点です。」

STEP 1570 = 「入力条件を定式化する枠」、 本 STEP 1574 = 「入力から生成物を吐く一段目」。 生成物は skeleton だが、 これで「schema 保持者」 から 「生成器の入口」に一歩進む。

API surface

import {
  RADIO_CLOCK_SPEC,
  generateArtifacts,
  type BoardProfile,
} from 'src/fpga/connector-generator';

const board: BoardProfile = {
  name: 'tang-nano-9k',
  ioType: 'LVCMOS33',
  clkFrequencyMHz: 27,
  pinAssignments: { clk: '52', rst_n: '4', rx_line_0: '25', output_pulse: '10' },
};

const bundle = generateArtifacts(RADIO_CLOCK_SPEC, board);
// bundle.verilog        — top module + 5 layer stubs
// bundle.cst            — .cst with real pins where mapped, __PIN_TBD__ elsewhere
// bundle.testbench      — smoke testbench (clk + rst + minimal stimulus)
// bundle.files          — { 'connector_radio_clock_jjy.v': ..., '...cst': ..., '..._tb.v': ... }
// bundle.generatorNotes — honest scope notes (4 lines)

Port derivation table

RolePorts 追加備考
(常時)clk, rst_n27 MHz + active-low reset
receiver (single N 本)rx_line_0..rx_line_{N-1}lines.count に応じて 1..4 本
receiver (differential-pair M pair)rx_diff_p_0..M-1, rx_diff_n_0..M-1LVDS-like 想定
decoderframe_valid, parity_ok, decoded_frame[63:0]frame 幅 64 bit fixed (v0.1)
arbitersync_locked, using_local_only受信成功 / 自走 fallback
outputoutput_pulse1 line pulse driver

Radio-clock-jjy 生成例

1. Verilog (connector_radio_clock_jjy.v)

// ═══════════════════════════════════════════════════════════════
// connector_radio_clock_jjy.v — auto-generated skeleton (STEP 1574 HDL generator)
// Spec: 電波時計型 (JJY pulse-width, self+sync) (id=radio-clock-jjy)
// Roles: receiver, decoder, oscillator, arbiter, output
// Encoding=pulse-width / Clock=self-plus-sync / Power=none
// ───────────────────────────────────────────────────────────────
// This is a STRUCTURAL SKELETON, not a complete implementation.
// Every layer sub-module contains a TODO marker; the defaults are
// synthesizable pass-throughs so the top compiles as-is.
// Actual protocol logic must be filled in by hand or Phase C spike.
// ═══════════════════════════════════════════════════════════════

`timescale 1ns / 1ps

module connector_radio_clock_jjy (
  input  wire        clk,
  input  wire        rst_n,
  input  wire        rx_line_0,
  output wire        frame_valid,
  output wire        parity_ok,
  output wire [63:0] decoded_frame,
  output wire        sync_locked,
  output wire        using_local_only,
  output wire        output_pulse
);
// ── receiver layer ─────────────────────────────────────────────
// TODO(receiver): implement physical signal capture per spec.encoding=pulse-width.
// For pulse-width (JJY): sample the receive line at >=10x symbol rate, output width in ticks.
// This stub wires raw_bit to a default source for downstream wiring only.
wire raw_bit;
wire raw_valid;
assign raw_bit   = rx_line_0;
assign raw_valid = 1'b1;

// ── decoder layer ──────────────────────────────────────────────
// TODO(decoder): implement pulse-width → frame parser.
// Frame: 60 秒 (1 bit/sec × 60)、 minute marker で 同期
// Sync marker: true
// Parity: even
// This stub emits zero frame + parity_ok=1 as pass-through default.
reg [63:0] decoded_frame_r = 64'h0;
reg        frame_valid_r   = 1'b0;
reg        parity_ok_r     = 1'b1;
assign decoded_frame = decoded_frame_r;
assign frame_valid   = frame_valid_r;
assign parity_ok     = parity_ok_r;

// ── oscillator layer ───────────────────────────────────────────
// TODO(oscillator): implement NCO / prescaler for self-running clock.
// Clock domains declared: 2
// For 27 MHz → 1 Hz on Tang Nano 9K: divide by 27_000_000 or use NCO.
// This stub uses a simple counter to produce local_tick at ~1 Hz assuming 27 MHz clk.
reg [24:0] local_counter = 25'd0;
reg        local_tick    = 1'b0;
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    local_counter <= 25'd0;
    local_tick    <= 1'b0;
  end else if (local_counter == 25'd27_000_000 - 1) begin
    local_counter <= 25'd0;
    local_tick    <= ~local_tick;
  end else begin
    local_counter <= local_counter + 25'd1;
  end
end

// ── arbiter layer ──────────────────────────────────────────────
// TODO(arbiter): implement "load from decoder if frame_valid & parity_ok, else
// continue on oscillator". Timeout: fall back to local after N missed frames.
// Clock strategy: self-plus-sync
// This stub asserts sync_locked=parity_ok when a frame arrives; using_local_only
// is the complement — real design should latch these across seconds.
assign sync_locked      = frame_valid & parity_ok;
assign using_local_only = ~sync_locked;

// ── output layer ───────────────────────────────────────────────
// TODO(output): implement downstream driver. For step-motor: 1 pulse per second.
// This stub drives output_pulse from local_tick as a placeholder.
assign output_pulse = local_tick;

endmodule

2. Gowin .cst (connector_radio_clock_jjy.cst) — Tang Nano 9K profile

// ═══════════════════════════════════════════════════════════════
// Gowin .cst pin constraint skeleton (STEP 1574 HDL generator)
// Spec: 電波時計型 (JJY pulse-width, self+sync) (id=radio-clock-jjy)
// Board profile: tang-nano-9k
// Default IO_TYPE: LVCMOS33
// Assumed clk: 27 MHz (create_clock -period 37.037 ns)
// ───────────────────────────────────────────────────────────────
// PLACEHOLDERS (__PIN_TBD__) MUST be replaced with real pin numbers
// from the target board pinout before synthesis. Using wrong pins
// can drive contention on physical nets.
// ═══════════════════════════════════════════════════════════════
IO_LOC "clk" 52;
IO_PORT "clk" IO_TYPE=LVCMOS33;
IO_LOC "rst_n" 4;
IO_PORT "rst_n" IO_TYPE=LVCMOS33;
IO_LOC "rx_line_0" 25;
IO_PORT "rx_line_0" IO_TYPE=LVCMOS33;
IO_LOC "frame_valid" __PIN_TBD__;
IO_PORT "frame_valid" IO_TYPE=LVCMOS33;
IO_LOC "parity_ok" __PIN_TBD__;
IO_PORT "parity_ok" IO_TYPE=LVCMOS33;
IO_LOC "decoded_frame" __PIN_TBD__;
IO_PORT "decoded_frame" IO_TYPE=LVCMOS33;
IO_LOC "sync_locked" __PIN_TBD__;
IO_PORT "sync_locked" IO_TYPE=LVCMOS33;
IO_LOC "using_local_only" __PIN_TBD__;
IO_PORT "using_local_only" IO_TYPE=LVCMOS33;
IO_LOC "output_pulse" 10;
IO_PORT "output_pulse" IO_TYPE=LVCMOS33;

3. Testbench (connector_radio_clock_jjy_tb.v)

// ═══════════════════════════════════════════════════════════════
// connector_radio_clock_jjy_tb.v — auto-generated testbench skeleton (STEP 1574)
// ───────────────────────────────────────────────────────────────
// Verifies: clock generation, reset propagation, module instantiation.
// Does NOT verify protocol correctness — that is Phase C spike work.
// ═══════════════════════════════════════════════════════════════

`timescale 1ns / 1ps

module connector_radio_clock_jjy_tb;
  reg clk   = 1'b0;
  reg rst_n = 1'b0;
  reg rx_line_0 = 1'b0;
  wire        frame_valid;
  wire        parity_ok;
  wire [63:0] decoded_frame;
  wire        sync_locked;
  wire        using_local_only;
  wire        output_pulse;

  // Clock: 27 MHz, period 37.037 ns
  always #(18.519) clk = ~clk;

  // DUT instantiation
  connector_radio_clock_jjy dut (
    .clk(clk), .rst_n(rst_n),
    .rx_line_0(rx_line_0),
    .frame_valid(frame_valid), .parity_ok(parity_ok), .decoded_frame(decoded_frame),
    .sync_locked(sync_locked), .using_local_only(using_local_only),
    .output_pulse(output_pulse)
  );

  initial begin
    // Reset pulse (10 clock cycles low)
    #(370.370) rst_n = 1'b1;
    // Drive rx_line_0 with a placeholder pulse pattern (0 → 1 → 0)
    #(3703.704) rx_line_0 = 1'b1;
    #(3703.704) rx_line_0 = 1'b0;
    // Run for a short simulated time then finish
    #(37037.037) $finish;
  end

  initial begin
    $dumpfile("connector_radio_clock_jjy_tb.vcd");
    $dumpvars(0, connector_radio_clock_jjy_tb);
  end
endmodule

Honest scope

V0.2+ candidate (defer)

Files