From e2f0f8ba081cc2aa2e7163db4c5ff85743902c03 Mon Sep 17 00:00:00 2001 From: Andreas Olofsson Date: Thu, 26 Mar 2026 16:09:45 -0400 Subject: [PATCH 1/4] Making clockmux safer - Adding reset synchronizers to avoid non-deterministic deadlock - Making clk0 the main clockpin that must always toggle, default during reset - Adding comments --- lambdalib/auxlib/la_clkmux2/la_clkmux2.py | 4 +- lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v | 73 ++++++++++++++------ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/lambdalib/auxlib/la_clkmux2/la_clkmux2.py b/lambdalib/auxlib/la_clkmux2/la_clkmux2.py index a8ae0eae..ab44b4be 100644 --- a/lambdalib/auxlib/la_clkmux2/la_clkmux2.py +++ b/lambdalib/auxlib/la_clkmux2/la_clkmux2.py @@ -3,7 +3,7 @@ from lambdalib.stdlib import Inv from lambdalib.stdlib import And2 from lambdalib.stdlib import Clkor2 -from lambdalib.auxlib import Dsync +from lambdalib.auxlib import Drsync from lambdalib.auxlib import Clkicgand @@ -13,7 +13,7 @@ def __init__(self): super().__init__(name, __file__) # dependencies - deps = [Inv(), And2(), Clkor2(), Dsync(), Clkicgand()] + deps = [Inv(), And2(), Clkor2(), Drsync(), Clkicgand()] with self.active_fileset('rtl'): for item in deps: self.add_depfileset(item) diff --git a/lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v b/lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v index 6a33945f..d610c5d2 100644 --- a/lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v +++ b/lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v @@ -4,27 +4,47 @@ //# License: MIT (see LICENSE file in Lambda repository) # //############################################################################# +/* Glitch-free 2-input clock mux with feedback handshake. The handshake + * ensures one clock is fully gated before the other is enabled, preventing + * output glitches during switchover. + * + * clk0 is the default clock and must always be running. It propagates + * through the mux during reset. An absence of clk0 typically requires a + * full system reset to recover. clk1 does not need to be running at all + * times, but the handshake requires both clocks to be toggling for a + * sel transition to complete. + * + * WARNING: If clk1 is generated by logic clocked from 'out', switching + * to clk1 (sel=1) will stop 'out' when clk1 stops, making the control + * registers unreachable. Always switch back to clk0 (sel=0) before + * modifying logic that produces clk1. + * + */ + module la_clkmux2 #(parameter PROP = "DEFAULT" // cell property ) ( - input clk0, + input clk0, // default clock (enabled during reset) input clk1, - input sel, // 1=clk1, 0=clk0 + input nreset, // async active low reset + input sel, // 1=clk1, 0=clk0 output out ); `ifdef VERILATOR - // Dual-edge register breaks combinational clk to out dependency. - // In real silicon, the glitch-free clock mux uses ICG standard cells - // with no combinational clock-to-output path in static timing analysis. - // This is a reduced order model, so we give a warning at startp + // Reduced-order Verilator-safe model. In real silicon, the glitch-free + // clock mux uses ICG cells with no combinational clock-to-output path. + // This dual-edge register approximation breaks that dependency. initial - $display("WARNING: Reduced order verilator safe model used for la_clkmux2."); + $display("WARNING: Reduced order verilator model used for la_clkmux2."); reg out_reg; - always @(posedge clk0 or negedge clk0 or posedge clk1 or negedge clk1) - out_reg <= sel ? clk1 : clk0; + always @(posedge clk0 or negedge clk0 or posedge clk1 or negedge clk1 or negedge nreset) + if (!nreset) + out_reg <= clk0; + else + out_reg <= sel ? clk1 : clk0; assign out = out_reg; `else // local wires @@ -33,8 +53,10 @@ module la_clkmux2 #(parameter PROP = "DEFAULT" // cell property wire [1:0] ensync; wire [1:0] maskb; wire [1:0] clkg; + wire en0b; + wire ensync0b; - // non glitch clock enable trick + // Handshake logic: each path is enabled only after the other is gated off la_inv isel (.a(sel), .z(selb)); @@ -45,23 +67,32 @@ module la_clkmux2 #(parameter PROP = "DEFAULT" // cell property .z(maskb[1])); la_and2 ien0 (.a(selb), - .b(maskb[1]), // en once clk1 is safely zero + .b(maskb[1]), // enable once clk1 path is gated off .z(en[0])); la_and2 ien1 (.a(sel), - .b(maskb[0]), // en once clk0 is safely zero + .b(maskb[0]), // enable once clk0 path is gated off .z(en[1])); - // syncing logic to each clock domain - la_dsync isync0 (.clk (clk0), - .in (en[0]), - .out (ensync[0])); + // Invert en[0] before and after the sync so that the clk0 path resets + // to enabled (la_drsync resets to 0; inverted, that becomes 1). + la_inv ien0b (.a(en[0]), + .z(en0b)); + + la_drsync isync0 (.clk (clk0), + .nreset (nreset), + .in (en0b), + .out (ensync0b)); + + la_inv iensync0 (.a(ensync0b), + .z(ensync[0])); - la_dsync isync1 (.clk (clk1), - .in (en[1]), - .out (ensync[1])); + la_drsync isync1 (.clk (clk1), + .nreset (nreset), + .in (en[1]), + .out (ensync[1])); - // glitch free clock gating + // Glitch-free clock gating la_clkicgand igate0 (.clk (clk0), .te (1'b0), .en (ensync[0]), @@ -72,7 +103,7 @@ module la_clkmux2 #(parameter PROP = "DEFAULT" // cell property .en (ensync[1]), .eclk (clkg[1])); - // or-ing one hot clocks + // OR the mutually exclusive gated clocks la_clkor2 iorclk (.a(clkg[0]), .b(clkg[1]), .z(out)); From ef343baa5651294c0988b909b8e112eb6ae881f6 Mon Sep 17 00:00:00 2001 From: Andreas Olofsson Date: Thu, 26 Mar 2026 16:10:58 -0400 Subject: [PATCH 2/4] Expanding testbench to show all combos of sel and clk1 toggling --- .../la_clkmux2/testbench/tb_la_clkmux2.v | 118 +++++++++++++++--- 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v b/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v index 270e2335..05bdc532 100644 --- a/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v +++ b/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v @@ -1,33 +1,123 @@ module tb_la_clkmux2(); - reg clk0, clk1, sel; + reg clk0, clk1, sel, nreset; + reg clk1_en; wire out; // Clock definitions - initial clk0 = 0; always #5 clk0 = ~clk0; // 100MHz - initial clk1 = 0; always #2 clk1 = ~clk1; // 250MHz + initial clk0 = 0; always #20 clk0 = ~clk0; // 25MHz + initial begin clk1 = 0; clk1_en = 0; end + always #2 if (clk1_en) clk1 = ~clk1; - la_clkmux2 dut (.clk0(clk0), .clk1(clk1), .sel(sel), .out(out)); + la_clkmux2 dut (.clk0(clk0), .clk1(clk1), .nreset(nreset), .sel(sel), .out(out)); - // Watchdog Timer: Stop the simulation if it hangs + // Watchdog Timer initial begin - #1000; - $display("Simulation Timeout! Check if clocks are toggling."); + #10000; + $display("FAIL: Simulation Timeout!"); $finish; end + // Edge counter for output + integer out_edges; + initial out_edges = 0; + always @(posedge out) out_edges <= out_edges + 1; + + // Helper task: check output is toggling + task check_toggling; + input [80*8-1:0] label; + begin + out_edges = 0; + #200; + if (out_edges == 0) begin + $display("FAIL: %0s", label); + $finish; + end + $display("PASS: %0s (%0d edges)", label, out_edges); + end + endtask + initial begin - // Setup VCD dumping $dumpfile("waveform.vcd"); $dumpvars(0, tb_la_clkmux2); - // startup + // ---------------------------------------------------------- + // 1. Reset: clk0 propagates, clk1 off + // ---------------------------------------------------------- + sel = 0; + nreset = 0; + check_toggling("clk0 during reset"); + + // ---------------------------------------------------------- + // 2. Release reset, start clk1 + // ---------------------------------------------------------- + @(negedge clk0); + nreset = 1; + clk1_en = 1; + check_toggling("clk0 after reset, clk1 running"); + + // ---------------------------------------------------------- + // sel transitions with clk1 running + // ---------------------------------------------------------- + + // 3. sel 0->1, clk1 running + sel = 1; + #100; + check_toggling("sel 0->1, clk1 running"); + + // 4. sel 1->0, clk1 running sel = 0; - // Wait for synchronization (usually 2-3 cycles of clk0) - #107; - sel = 1; // Switch to clk1 - #237; - sel = 0; // Switch back to clk0 #100; + check_toggling("sel 1->0, clk1 running"); + + // ---------------------------------------------------------- + // sel transitions with clk1 stopped + // ---------------------------------------------------------- + + // 5. sel 0->1 with clk1 stopped (output should stop) + clk1_en = 0; + clk1 = 0; + #50; + sel = 1; + #100; + out_edges = 0; + #200; + $display("INFO: sel 0->1, clk1 stopped (%0d edges, expect 0)", out_edges); + + // 6. sel 1->0 with clk1 stopped, then restart clk1 + sel = 0; + #100; + clk1_en = 1; + check_toggling("sel 1->0, clk1 stopped then restarted"); + + // ---------------------------------------------------------- + // sel already at target with clk1 stop/start + // ---------------------------------------------------------- + + // 7. sel=0 throughout, stop and restart clk1 + clk1_en = 0; + clk1 = 0; + #100; + check_toggling("sel=0, clk1 stopped (clk0 unaffected)"); + clk1_en = 1; + #50; + + // 8. Switch to clk1, stop it, restart it + sel = 1; + #100; + check_toggling("sel=1, clk1 running"); + clk1_en = 0; + clk1 = 0; + #100; + clk1_en = 1; + check_toggling("sel=1, clk1 stopped then restarted"); + + // ---------------------------------------------------------- + // Final: back to clk0 + // ---------------------------------------------------------- + sel = 0; + #100; + check_toggling("final sel=0, both clocks running"); + $display("Test Finished Successfully"); $finish; end From 30e86eb23d9d8f183d32523118804de6762a6b5a Mon Sep 17 00:00:00 2001 From: Andreas Olofsson Date: Thu, 26 Mar 2026 16:14:03 -0400 Subject: [PATCH 3/4] Bumping version --- lambdalib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdalib/__init__.py b/lambdalib/__init__.py index d71b229e..16baaa61 100644 --- a/lambdalib/__init__.py +++ b/lambdalib/__init__.py @@ -13,7 +13,7 @@ from lambdalib import ramlib from lambdalib import veclib -__version__ = "0.11.3" +__version__ = "0.11.4" class LambalibTechLibrary(Design): From d6a22ed7b8afa6dc9c8e7b774641a07df66415e0 Mon Sep 17 00:00:00 2001 From: Andreas Olofsson Date: Thu, 26 Mar 2026 16:20:15 -0400 Subject: [PATCH 4/4] Lint fix --- lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v b/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v index 05bdc532..20f4df9a 100644 --- a/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v +++ b/lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v @@ -23,7 +23,7 @@ module tb_la_clkmux2(); always @(posedge out) out_edges <= out_edges + 1; // Helper task: check output is toggling - task check_toggling; + task automatic check_toggling; input [80*8-1:0] label; begin out_edges = 0;