1. The Problem: Single Pulses and Control Signals
From Days 1-3, we've covered:
- Day 1: Single-bit level synchronization (two-FF)
- Day 2: Multi-bit monotonic counters (Gray code)
But what about single-pulse control signals? Examples:
- Asynchronous reset button → needs to generate a synchronized reset pulse in the chip's clock domain
- Interrupt signal → must generate a single pulse in the target clock domain (not a level)
- Handshake request → send a 1-cycle pulse across domains to trigger an action
- Start/stop commands → single-pulse trigger, not a sustained level
The problem: A simple dual-FF on a level signal will synchronize the level, not generate a pulse.
2. The Pulse Synchronizer Architecture
Strategy 1: Stretch and Detect
The most common pulse synchronization uses level transmission with pulse detection:
- Sender (Clock A): Convert the pulse into a toggleing level
- Synchronizer: Use a dual-FF to synchronize the level
- Receiver (Clock B): Detect when the level toggles, and generate a pulse
3. RTL Implementation
Complete Pulse Synchronizer Module
module pulse_synchronizer (
input clk_a,
input rst_a,
input pulse_a, // Single-cycle pulse in Clock A domain
input clk_b,
input rst_b,
output pulse_b // Single-cycle pulse in Clock B domain
);
// ============ Clock A Domain: Toggle Latch ============
reg toggle_latch;
always @(posedge clk_a or negedge rst_a) begin
if (!rst_a) begin
toggle_latch <= 1'b0;
end else if (pulse_a) begin
toggle_latch <= ~toggle_latch; // Toggle on each input pulse
end
end
// ============ Synchronization: Dual-FF on Level ============
reg [1:0] level_sync; // Two-stage synchronizer
always @(posedge clk_b or negedge rst_b) begin
if (!rst_b) begin
level_sync <= 2'b00;
end else begin
level_sync <= {level_sync[0], toggle_latch}; // Shift register
end
end
// ============ Clock B Domain: Edge Detection ============
reg level_sync_prev;
always @(posedge clk_b or negedge rst_b) begin
if (!rst_b) begin
level_sync_prev <= 1'b0;
end else begin
level_sync_prev <= level_sync[1]; // Track previous state
end
end
// Generate output pulse when level changes
assign pulse_b = (level_sync[1] != level_sync_prev);
endmodule
Key Implementation Points
- Toggle latch in Clock A: Converts every input pulse into a single level toggle
- Dual-FF synchronizer in Clock B: Standard metastability protection on the toggling level
- Edge detector in Clock B: Compares synchronized level with previous cycle to detect toggle → generates pulse
- MTBF: Same as standard dual-FF (millions of years)
- Pulse duration in Clock B: Exactly 1 Clock B cycle (deterministic)
4. Detailed Timing Analysis
Pulse Synchronizer Timing @ 1 GHz & 500 MHz
Scenario: Clock A = 1 GHz (1ns period), Clock B = 500 MHz (2ns period) Timeline: T=0ns (Clock A): pulse_a goes high T=0ns: toggle_latch toggles (0 → 1) immediately T=1ns (Clock A): pulse_a goes low T=1ns: toggle_latch stays at 1 T=2ns (Clock B): FF1 samples toggle_latch = 1 T=2ns: FF1 output may be metastable T=4ns (Clock B): FF2 samples FF1 output T=4ns: level_sync[1] = 1 (metastability resolved) T=4ns: level_sync[1] != level_sync_prev (0 ≠ 1) T=4ns: pulse_b = 1 for exactly 1 Clock B cycle T=6ns (Clock B): level_sync_prev updates to 1 T=6ns: pulse_b = 0 (no more pulse)
5. Real-World Use Cases
Interrupt Across Domains
System-on-Chip with multiple clock domains: Peripheral (Clock A) signals interrupt to processor (Clock B).
- Peripheral: Asserts interrupt pulse for 1 Clock A cycle when event occurs
- Pulse Synchronizer: Synchronizes pulse across clock boundary
- Processor: Sees pulse_b asserted for 1 Clock B cycle → handles interrupt
Reset Distribution
Asynchronous system reset must be synchronized into all clock domains. A pulse synchronizer ensures each domain gets a single reset pulse.
Hand-Shake Protocols
Two subsystems in different clock domains need to coordinate:
- Sender (Clock A) issues request pulse
- Synchronizer converts to Clock B domain
- Receiver (Clock B) processes request, issues acknowledge pulse back through another synchronizer
6. Advanced Variant: Multi-Pulse Handling
What if multiple pulses arrive before the previous pulse is synchronized across?
The toggle latch naturally handles this: Each new pulse toggles the level again. The edge detector in Clock B will see another toggle and generate another pulse (when it arrives).
However, if pulses arrive faster than the synchronization latency (2-3 Clock B cycles), some toggling might not be detected.
7. Comparison: Three Synchronization Techniques
| Technique | Use Case | Input Type | Output Type | Design Complexity |
|---|---|---|---|---|
| Dual-FF | Single-bit level (req, ack, etc.) | Level (0 or 1) | Level (0 or 1) | Simple (2 FFs) |
| Gray Code | Multi-bit counters | Binary counter | Binary counter | Medium (encode/decode) |
| Pulse Sync | Single pulses, interrupts | Pulse (1 clock cycle) | Pulse (1 clock cycle) | Medium (toggle + edge detect) |
8. Common Mistakes
- ❌ Mistake: Using dual-FF directly on a pulse signal → Pulse gets stretched or lost
- ✓ Solution: Convert to toggle level first
- ❌ Mistake: Not accounting for synchronization latency → Pulses arrive too fast and get missed
- ✓ Solution: Limit pulse frequency to << Clock_B / 4
- ❌ Mistake: Assuming output pulse length matches input → Clock domains may have very different frequencies
- ✓ Solution: Output pulse is exactly 1 Clock B cycle (not dependent on input pulse length)
9. Summary & Checklist
- ✅ Pulse input requires special handling — not just a simple dual-FF
- ✅ Toggle latch converts pulse to level in source domain
- ✅ Dual-FF synchronizes the level across clock boundary
- ✅ Edge detector generates output pulse in destination domain
- ✅ Output pulse is exactly 1 destination clock cycle
- ✅ MTBF same as dual-FF (millions of years)
- ✅ Handles multiple pulses correctly (if frequency limit respected)
- ✅ Reset both toggle latch and edge detector to ensure known state
Next (Day 5): Dual-clock FIFO — integrating Gray code synchronization with FIFO control logic.