Pulse, Timeout and Edge: registers that clear, count and remember on their own
Three patterns come up in most designs: a flag that is true for exactly one cycle, a countdown, and a check for whether a signal just changed. Each has a register type that updates itself. All three are stored as plain registers; the block that writes one advances it before its own statements run, so the block’s own write always wins the cycle.
Pulse: true for one cycle
A Pulse is a Bool that clears itself. Write true and it is true for one cycle, then false again until the next write:
@quartzstruct Strobe@in go::Bool =false@out fire::Pulseend@on Strobe posedge(clk) begin go && (fire ←true)endfires =Bool[]let m =Strobe()for i in1:5 m =step(m; go=i ==2)push!(fires, m.fire)endendfires
5-element Vector{Bool}:
0
1
0
0
0
It replaces the fire && (fire ← false) line at the top of the block.
Timeout{N}: a countdown
A Timeout{N} is a Bits{N} that counts down to zero and holds there. Write a value to start it; expired(t) is true from the moment it reaches zero. Read bare, it is the count.
@quartzstruct Divider@in period::Bits{4} =3@in arm::Bool =false@out tick::Pulse div::Timeout{4} hold::Timeout{4}end@on Divider posedge(clk) beginifexpired(div) div ← period # reload on expiry: a divider tick ←trueend arm && (hold ←5) # a one-shot: runs down and stays expiredendlet m =Divider()for i in1:10 m =step(m; arm=i ==5)@info"edge $i: div = $(Int(m.div)), tick = $(m.tick), hold = $(Int(m.hold))"endend
[ Info: edge 1: div = 3, tick = true, hold = 0
[ Info: edge 2: div = 2, tick = false, hold = 0
[ Info: edge 3: div = 1, tick = false, hold = 0
[ Info: edge 4: div = 0, tick = false, hold = 0
[ Info: edge 5: div = 3, tick = true, hold = 5
[ Info: edge 6: div = 2, tick = false, hold = 4
[ Info: edge 7: div = 1, tick = false, hold = 3
[ Info: edge 8: div = 0, tick = false, hold = 2
[ Info: edge 9: div = 3, tick = true, hold = 1
[ Info: edge 10: div = 2, tick = false, hold = 0
Written 3, the timeout expires four edges later, so a divider written with period has a period of period + 1 clocks. A timeout that has run down stays at zero: expired keeps answering true until it is written again.
TipIf you know Verilog
Pulse and Timeout are ordinary registers in the emitted Verilog — a reg with a clear or a decrement at the top of the always block, followed by the block’s writes. write(stdout, Divider, Verilog()) shows it.
Edge: did it just change?
An Edge is a Bool register that also remembers the value it held before, so its transitions can be queried. Write it like any register and read it bare as the level:
@quartzstruct Detect@in x::Bool =false@out rises::Bits{8} =0@out falls::Bits{8} =0 xe::Edgeend@on Detect posedge(clk) begin xe ← xrose(xe) && (rises ← rises +1)fell(xe) && (falls ← falls +1)endlet m =Detect()for i in1:12 m =step(m; x=4<= i <=8)end m.rises, m.fallsend
(Bits{8}(0x01), Bits{8}(0x01))
rose(e) and fell(e) are the transition the last clock edge registered — exactly x_q && !x_qq — glitch-free, one cycle after the sample that made it, and true for exactly one cycle. On a cycle that leaves the edge unwritten the history settles by itself, so a gated feed (en && (e ← x)) cannot latch an event.
isrising(e, x) and isfalling(e, x) compare an incoming sample with the level instead — x && !e — and see the transition as it happens, with no cycle of delay. Keep them to signals already on this clock, and take an asynchronous pin through a MetaGuard first.
The default of an Edge is the level seen so far, so power-up and reset manufacture no event.
Restrictions
An input cannot be a Pulse, a Timeout or an Edge, since an input has no storage. A @wire block cannot write one, since they advance on a clock. And an Edge is written whole — e[0] ← x is an error.
Next
Clock domains: MetaGuard, and modules with more than one clock.