The global buffer
You will learn
What a global buffer is, why it sits on its own network, and what one real build of the whole flow looked like.
A global buffer, BUFG on a 7-series chip, drives the clock on a dedicated low-skew network so the tree of the last lesson has a clean root. The widget shows the flow a BUFG sits inside: one real XC7A200T build, 116.9 s with openXC7 and 83.5 s through the t27 L3+L4 path, byte-identical bitstreams, and place and route taking 60 percent of it. The buffer is one layer of that picture, and the spec frame opens cts.t27 again, where bufg() is built and bufg_has_higher_fanout() is tested.
Try it
In the widget, find the two build times and what percentage place and route took; then in the spec frame find bufg() and the test that gives it the higher fanout.

6 of 6 self-tests hold: anomalies, blog_drift, lessons, site_gate, tick, vault.
specs/fpga/cts.t27
// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/cts.t27
// T27 Clock Tree Synthesis Specification
// PLL configuration, clock buffer trees, skew estimation
// Artix-7: BUFH=0.05ns, BUFG=0.1ns, PLL jitter=50ps, max skew=100ps
// Uses flat arrays + count fields (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY
module CTS {
pub struct PllConfig {
name : &str,
input_mhz : u32,
output_mhz : u32,
multiply : u32,
divide : u32,
jitter_ps : u32,
}
fn pll_config(name: &str, input_mhz: u32, output_mhz: u32) -> PllConfig {
var m : u32 = 1;
var d : u32 = 1;
if input_mhz > 0 {
d = input_mhz;
m = output_mhz;
}
return PllConfig{
.name = name,
.input_mhz = input_mhz,
.output_mhz = output_mhz,
.multiply = m,
.divide = d,
.jitter_ps = 50,
};
}
fn pll_period_ps(pll: PllConfig) -> u32 {
if pll.output_mhz == 0 {
return 0;
}
return 1000000000 / pll.output_mhz;
}
pub struct ClockBuffer {
name : &str,
delay_ps : u32,
fanout : u32,
}
fn bufg(name: &str) -> ClockBuffer {
return ClockBuffer{ .name = name, .delay_ps = 100, .fanout = 32 };
}
fn bufh(name: &str) -> ClockBuffer {
return ClockBuffer{ .name = name, .delay_ps = 50, .fanout = 16 };
}
fn bufg_has_higher_fanout(b: ClockBuffer) -> bool {
return b.fanout >= 32;
}
pub struct ClockTree {
root : &str,
num_levels : u32,
total_buffers : u32,
max_skew_ps : u32,
}
fn clock_tree(root: &str, levels: u32, bufs: u32) -> ClockTree {
return ClockTree{
.root = root,
.num_levels = levels,
.total_buffers = bufs,
.max_skew_ps = 100,
};
}
fn tree_delay_ps(tree: ClockTree, buf_delay: u32) -> u32 {
return tree.num_levels * buf_delay;
}
fn skew_ok(tree: ClockTree, max_allowed_ps: u32) -> bool {
return tree.max_skew_ps <= max_allowed_ps;
}
pub struct CtsReport {
num_clocks : u32,
num_plls : u32,
total_buffers : u32,
worst_skew_ps : u32,
worst_latency_ps : u32,
has_violations : bool,
}
fn cts_ok(clocks: u32, plls: u32, bufs: u32, skew: u32, latency: u32) -> CtsReport {
return CtsReport{
.num_clocks = clocks,
.num_plls = plls,
.total_buffers = bufs,
.worst_skew_ps = skew,
.worst_latency_ps = latency,
.has_violations = false,
};
}
fn passed(r: CtsReport) -> bool {
return r.has_violations == false;
}
// === Auto tree estimation ===
fn est_buffers_needed(num_sinks: u32) -> u32 {
if num_sinks <= 16 {
return 1;
}
return num_sinks / 16 + 1;
}
fn est_tree_levels(num_sinks: u32) -> u32 {
if num_sinks <= 16 {
return 1;
}
if num_sinks <= 256 {
return 2;
}
return 3;
}
// === Validation ===
fn validate_pll(pll: PllConfig) -> u32 {
var errors : u32 = 0;
if pll.name == "" { errors = errors + 1; }
if pll.output_mhz == 0 { errors = errors + 1; }
return errors;
}
// === Tests ===
test pll_config_creation
given p = pll_config("sys_pll", 100, 200)
then p.input_mhz == 100
and p.output_mhz == 200
and pll_period_ps(p) == 5000000
test bufg_creation
given b = bufg("clk_buf")
then b.delay_ps == 100
and b.fanout == 32
and bufg_has_higher_fanout(b) == true
test bufh_creation
given b = bufh("clk_h")
then b.delay_ps == 50
and b.fanout == 16
and bufg_has_higher_fanout(b) == false
test clock_tree_creation
given t = clock_tree("clk", 2, 5)
then t.root == "clk"
and t.num_levels == 2
and t.total_buffers == 5
and t.max_skew_ps == 100
test tree_delay
given t = clock_tree("clk", 3, 8)
then tree_delay_ps(t, 100) == 300
test skew_ok_yes
given t = clock_tree("clk", 2, 5)
then skew_ok(t, 200) == true
test skew_ok_no
given t = clock_tree("clk", 2, 5)
then skew_ok(t, 50) == false
test cts_report_ok
given r = cts_ok(2, 1, 10, 80, 300)
then passed(r) == true
and r.has_violations == false
test est_buffers_one
then est_buffers_needed(10) == 1
test est_buffers_many
then est_buffers_needed(100) == 7
test est_tree_levels_one
then est_tree_levels(10) == 1
test est_tree_levels_two
then est_tree_levels(100) == 2
test est_tree_levels_three
then est_tree_levels(500) == 3
test validate_pll_ok
given p = pll_config("ok", 100, 200)
then validate_pll(p) == 0
test validate_pll_empty
given p = PllConfig{.name = "", .input_mhz = 100, .output_mhz = 0, .multiply = 1, .divide = 1, .jitter_ps = 50}
then validate_pll(p) > 0
// === Invariants ===
invariant bufg_delay_positive
given b = bufg("inv")
assert b.delay_ps > 0
invariant skew_non_negative
given t = clock_tree("inv", 2, 5)
assert t.max_skew_ps >= 0
bench buffer_estimation_latency
measure: nanoseconds to est_buffers_needed(50)
target: < 50ns
bench tree_level_estimation_latency
measure: nanoseconds to est_tree_levels(200)
target: < 50ns
}
// phi^2 + 1/phi^2 = 3 | TRINITY
All lessons
Module 1 · What a clock is
One edge, one world: what shares a clock edge shares a world, the period and the jitter of a real edge, and where the clock enters a board.
Module 2 · Clock trees
Skew and insertion delay, the global buffer network, and the trap of gating a clock with logic.
Module 3 · PLL and MMCM
Multiply and divide one clock into another, move its phase in steps of the VCO, and which clocks the analyzer treats as related.
Module 4 · Resets
Assert asynchronously, release synchronously: the three reset kinds, the release pipe, and the tree a reset grows.
Module 5 · Metastability
The setup-hold window, the mean time between failures in integer arithmetic, and the two flops that fix it.
Module 6 · Crossing many bits
Why a binary bus tears, why Gray code does not, and the handshake that moves a pulse between worlds.
Module 7 · The asynchronous FIFO
Pointers, flags and depth: the buffer that moves a stream between two clocks.
Module 8 · Constraints
The lines that tell the analyzer what a clock is, which paths not to check, and what the pins must meet.
Module 9 · On the board
A CDC report, one crossing captured at the flip-flops, and the bitstream diff that closes the course.