PLL Blink
Five LEDs blink at different rates, each driven by an independent clock domain. The 27 MHz board oscillator feeds the first counter directly and drives a PLL that generates four additional clocks. The visible blink differences confirm that all PLL outputs are active and running at the expected frequencies.
Clock Tree
text
27 MHz crystal (SCLK)
└─ PLL (IDIV=2, FBDIV=24, ODIV=4)
├─ OUT BASE CLK_A = 225 MHz
├─ OUT PHASE CLK_B = 225 MHz (phase-shifted)
├─ OUT DIV CLK_C = 56.25 MHz (CLKOUTD_DIV=4)
└─ OUT DIV3 CLK_D = 75 MHzPLL math: VCO = 27 × (24+1) × 4 / (2+1) = 900 MHz. Base output = 900/4 = 225 MHz. Divided outputs: 225/4 = 56.25 MHz and 225/3 = 75 MHz.
Each LED blinks at clock_freq / 2^27 — roughly 0.2 Hz for the 27 MHz counter, up to 1.7 Hz for the 225 MHz counters.
Modules
blinkers
Five clock inputs (clk1 through clk5), a POR input, a reset button, and 5 LED outputs.
The ASYNCHRONOUS block combines por & rst_n into an active-low reset wire and drives each LED from the inverted MSB of its corresponding counter (leds[N] <= ~counter_X[26]).
Five SYNCHRONOUS blocks each declare their own clock (CLK=clk1 through CLK=clk5) with RESET_TYPE=Clocked for proper synchronous reset deassertion within each domain. Each block increments its 27-bit counter by 1.
Project Files
Both Tang Nano 20K and 9K project files use identical PLL configuration. The CLOCK_GEN block declares the PLL with all four outputs (BASE, PHASE, DIV, DIV3) and configuration parameters. The @top instance maps all five clocks and the reset.
jz
@project(CHIP="GW2AR-18-QN88-C8-I7") BLINKER
@import "blinkers.jz"
CLOCKS {
SCLK = { period=37.04 }; // 27MHz clock
CLK_A;
CLK_B;
CLK_C;
CLK_D;
}
IN_PINS {
SCLK = { standard=LVCMOS33 };
DONE = { standard=LVCMOS33 };
KEY[2] = { standard=LVCMOS33 };
}
OUT_PINS {
LED[6] = { standard=LVCMOS33, drive=8 };
}
MAP {
// System Clock
// 27MHz
SCLK = 4;
// 2 Buttons
// High = Closed
// Low = Open
KEY[0] = 87;
KEY[1] = 88;
// 6 LEDs
// High = OFF
// Low = ON
LED[0] = 15;
LED[1] = 16;
LED[2] = 17;
LED[3] = 18;
LED[4] = 19;
LED[5] = 20;
// DONE can be used as a POR signal
// Low = programming ongoing
// High = programming successfully
DONE = IOR32B;
}
CLOCK_GEN {
PLL {
IN REF_CLK SCLK; // 27 MHz input
OUT BASE CLK_A; // 225 MHz
OUT PHASE CLK_B; // 225 MHz phase shifted
OUT DIV CLK_C; // 56.25 MHz
OUT DIV3 CLK_D; // 75 MHz
CONFIG {
IDIV = 2;
FBDIV = 24;
ODIV = 4;
PHASESEL = 1;
CLKOUTD_DIV = 4;
};
};
}
@top blinkers {
IN [1] clk1 = SCLK;
IN [1] clk2 = CLK_A;
IN [1] clk3 = CLK_B;
IN [1] clk4 = CLK_C;
IN [1] clk5 = CLK_D;
IN [1] por = DONE;
IN [1] rst_n = ~KEY[0];
OUT [5] leds = LED[4:0];
}
@endprojjz
@module blinkers
PORT {
IN [1] clk1;
IN [1] clk2;
IN [1] clk3;
IN [1] clk4;
IN [1] clk5;
IN [1] por;
IN [1] rst_n;
OUT [5] leds;
}
WIRE {
reset [1];
}
REGISTER {
counter_a [27] = 27'b1;
counter_b [27] = 27'b1;
counter_c [27] = 27'b1;
counter_d [27] = 27'b1;
counter_e [27] = 27'b1;
}
ASYNCHRONOUS {
reset <= por & rst_n;
leds[0] <= ~counter_a[26];
leds[1] <= ~counter_b[26];
leds[2] <= ~counter_c[26];
leds[3] <= ~counter_d[26];
leds[4] <= ~counter_e[26];
}
SYNCHRONOUS(CLK=clk1 RESET=reset RESET_ACTIVE=Low RESET_TYPE=Clocked) {
counter_a <= counter_a + 27'b1;
}
SYNCHRONOUS(CLK=clk2 RESET=reset RESET_ACTIVE=Low RESET_TYPE=Clocked) {
counter_b <= counter_b + 27'b1;
}
SYNCHRONOUS(CLK=clk3 RESET=reset RESET_ACTIVE=Low RESET_TYPE=Clocked) {
counter_c <= counter_c + 27'b1;
}
SYNCHRONOUS(CLK=clk4 RESET=reset RESET_ACTIVE=Low RESET_TYPE=Clocked) {
counter_d <= counter_d + 27'b1;
}
SYNCHRONOUS(CLK=clk5 RESET=reset RESET_ACTIVE=Low RESET_TYPE=Clocked) {
counter_e <= counter_e + 27'b1;
}
@endmodJZ-HDL Language Features
PLL in the source. The CLOCK_GEN block declares the PLL's input, outputs, and divider parameters directly in the project file. Clock outputs use OUT (e.g., OUT BASE CLK_A), while non-clock outputs like the lock indicator use WIRE (e.g., WIRE LOCK pll_lock). The compiler validates VCO range and divider ratios against the target chip's constraints. Traditional FPGA flows configure PLLs through vendor GUI tools that generate opaque wrapper modules disconnected from the HDL.
Multi-clock enforcement. Each SYNCHRONOUS block names its clock explicitly. The compiler tracks which registers belong to which domain. Reading a register from clk1 inside a clk2 synchronous block would be a compile-time error, catching cross-domain violations before they become metastability bugs on hardware. Verilog has no such check — nothing prevents reading a clk1 register inside an always @(posedge clk2) block.