Pulses, timeouts and edges

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:

@quartz struct Strobe
  @in  go::Bool = false
  @out fire::Pulse
end

@on Strobe posedge(clk) begin
  go && (fire  true)
end

fires = Bool[]
let m = Strobe()
  for i in 1:5
    m = step(m; go=i == 2)
    push!(fires, m.fire)
  end
end
fires
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.

@quartz struct Divider
  @in  period::Bits{4} = 3
  @in  arm::Bool = false
  @out tick::Pulse
  div::Timeout{4}
  hold::Timeout{4}
end

@on Divider posedge(clk) begin
  if expired(div)
    div  period           # reload on expiry: a divider
    tick  true
  end
  arm && (hold  5)        # a one-shot: runs down and stays expired
end

let m = Divider()
  for i in 1:10
    m = step(m; arm=i == 5)
    @info "edge $i: div = $(Int(m.div)), tick = $(m.tick), hold = $(Int(m.hold))"
  end
end
[ 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:

@quartz struct Detect
  @in  x::Bool = false
  @out rises::Bits{8} = 0
  @out falls::Bits{8} = 0
  xe::Edge
end

@on Detect posedge(clk) begin
  xe  x
  rose(xe) && (rises  rises + 1)
  fell(xe) && (falls  falls + 1)
end

let m = Detect()
  for i in 1:12
    m = step(m; x=4 <= i <= 8)
  end
  m.rises, m.falls
end
(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.