Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lambdalib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from lambdalib import ramlib
from lambdalib import veclib

__version__ = "0.11.3"
__version__ = "0.11.4"


class LambalibTechLibrary(Design):
Expand Down
4 changes: 2 additions & 2 deletions lambdalib/auxlib/la_clkmux2/la_clkmux2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)
Expand Down
73 changes: 52 additions & 21 deletions lambdalib/auxlib/la_clkmux2/rtl/la_clkmux2.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));

Expand All @@ -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]),
Expand All @@ -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));
Expand Down
118 changes: 104 additions & 14 deletions lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v
Original file line number Diff line number Diff line change
@@ -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 automatic 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);
Comment on lines +76 to +84
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing assertion for stopped clock scenario.

This test expects out_edges == 0 when sel=1 and clk1 is stopped, but only displays INFO without asserting the condition. A bug causing unexpected output toggling would go undetected.

🧪 Proposed fix to add assertion
       out_edges = 0;
       `#200`;
+      if (out_edges != 0) begin
+         $display("FAIL: sel 0->1, clk1 stopped (expected 0 edges, got %0d)", out_edges);
+         $finish;
+      end
       $display("INFO: sel 0->1, clk1 stopped (%0d edges, expect 0)", out_edges);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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);
// 5. sel 0->1 with clk1 stopped (output should stop)
clk1_en = 0;
clk1 = 0;
`#50`;
sel = 1;
`#100`;
out_edges = 0;
`#200`;
if (out_edges != 0) begin
$display("FAIL: sel 0->1, clk1 stopped (expected 0 edges, got %0d)", out_edges);
$finish;
end
$display("INFO: sel 0->1, clk1 stopped (%0d edges, expect 0)", out_edges);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lambdalib/auxlib/la_clkmux2/testbench/tb_la_clkmux2.v` around lines 76 - 84,
The INFO-only check for the stopped-clock scenario must be turned into a real
assertion so failures fail the test; after the block that sets clk1_en=0,
clk1=0, waits, sets sel=1 and measures out_edges (the code around the variables
clk1_en, clk1, sel, out_edges), add a conditional that checks out_edges !== 0
and invokes a simulator-fatal/error (e.g., $fatal or $error + $finish) with a
descriptive message so the test fails when edges are observed instead of only
printing INFO; place this assertion immediately after the existing $display for
the sel 0->1, clk1 stopped case.


// 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
Expand Down