diff --git a/lib/PinSync.bsv b/lib/PinSync.bsv new file mode 100644 index 0000000..a7db442 --- /dev/null +++ b/lib/PinSync.bsv @@ -0,0 +1,45 @@ +package PinSync; + +// mkPinSync builds a synchronizer for use with asynchronous inputs. +// +// You should only use this to capture asynchronous inputs coming from +// outside your design. For clock domain crossing within your design, +// use the dual-clocked synchronizers found in Bluespec's standard +// library. +// +// As the name suggests, mkPinSync is intended to be used to +// synchronize data coming into your design from an external pin, such +// as the RX line of a UART. Such signals do not run according to a +// known clock, so the regular stdlib synchronizers cannot be used as +// there's no "source" clock we can provide them. +// +// You can think of mkPinSync as the output end of a standard +// synchronizer, without the initial register that's clocked by the +// source domain. Conceptually, we assume that register exists outside +// our design and is driving the input of mkPinSync, so we just need +// the metastability mitigation within our own domain. +module mkPinSync(val init_value, Reg#(val) ifc) + provisos(Bits#(val, _)); + + Reg#(val) r1 <- mkReg(init_value); + Reg#(val) r2 <- mkReg(init_value); + + // To break write+read conflicts. Without this, a rule that + // atomically reads the sync while also writing it fails to + // schedule vs. the 'every' rule below. This shouldn't really + // happen in real designs, but it's a convenient idiom in + // testing. The wire is free in terms of logic, so might as well + // make atomic read+write work. + Wire#(val) out <- mkBypassWire(); + + (* no_implicit_conditions, fire_when_enabled *) + rule every; + out <= r2; + r2 <= r1; + endrule + + method _read = out._read; + method _write = r1._write; +endmodule + +endpackage diff --git a/lib/PinSync_Test.bsv b/lib/PinSync_Test.bsv new file mode 100644 index 0000000..37fec83 --- /dev/null +++ b/lib/PinSync_Test.bsv @@ -0,0 +1,50 @@ +package PinSync_Test; + +import Assert::*; +import StmtFSM::*; + +import PinSync::*; +import Testing::*; + +module mkTB(); + let testflags <- mkTestFlags(); + let cycles <- mkCycleCounter(); + + Reg#(UInt#(2)) dut <- mkPinSync(0); + + function Action check_dut_val(UInt#(2) want_val); + return action + if (testflags.verbose) + $display("%0d: PinSync = %0d, want %0d", cycles.all, dut, want_val); + dynamicAssert(dut == want_val, "wrong value"); + endaction; + endfunction + + function Stmt check_sync(UInt#(2) starting_val, UInt#(2) want_val); + return seq + action + check_dut_val(starting_val); + dut <= want_val; + cycles.reset(); + if (testflags.verbose) + $display("%0d: write(%0d)", cycles.all, want_val); + endaction + + check_dut_val(starting_val); + + action + check_dut_val(want_val); + dynamicAssert(cycles == 2, "synchronizer didn't sync at the right time"); + endaction + endseq; + endfunction + + runTest(100, + mkTest("PinSync", seq + check_sync(0, 2); + check_sync(2, 3); + check_sync(3, 1); + endseq)); +endmodule + +endpackage