mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 09:11:11 -07:00
FIRE drop gate PASSED both paths - all pre-live-fire scope gates closed
Scope on GPIO2_IO30 (the SoC FIRE drive): two 2.000 s FIRE windows, the second terminated only by the SDMA end-of-data backstop. Measured 2.0000 s exactly with clean edges on BOTH termination paths - normal completion and true underrun (streaming=1, kernel underrun state reached and acked; fire_test.py gains the U mode). The backstop drops FIRE within one tick regardless of how the stream dies. With the waveform, stream-path, latch, topology, and interlock- semantics results from earlier today, every standing hardware verification gate for live fire is now passed; what remains is the laser-milestone software and a chain-armed first-light procedure.
This commit is contained in:
+22
-11
@@ -198,17 +198,28 @@ hardware I/O — host testing).
|
|||||||
(13→7 during the unlocked FIRE window): b0 = SoC-side LASER_ON
|
(13→7 during the unlocked FIRE window): b0 = SoC-side LASER_ON
|
||||||
monitor, active LOW (1 = not lasing); b1 = FIRE, active high;
|
monitor, active LOW (1 = not lasing); b1 = FIRE, active high;
|
||||||
b3 = latch, 1 = locked/0 = unlocked.
|
b3 = latch, 1 = locked/0 = unlocked.
|
||||||
- **≤1-tick FIRE drop at underrun/end-of-data: OPEN (deferred).**
|
- **≤1-tick FIRE drop at underrun/end-of-data: PASSED 2026-08-02**
|
||||||
The SoC FIRE net is not probe-accessible on the bench, and the
|
(scope on GPIO2_IO30, the SoC FIRE drive feeding the safing
|
||||||
chain-gated PSU pin cannot show FIRE without arming the full
|
logic; `fire_test.py` B and U, operator-executed, duty 0, chain
|
||||||
chain (HV powered + pgood + HV_WDOG retriggered). Close it later
|
unarmed). Stream: two 2.000 s FIRE windows, the second ending
|
||||||
either by finding a probe point on the FIRE net (safing-logic
|
exactly at end-of-data so its falling edge IS the SDMA backstop.
|
||||||
input) or as the first measurement of the eventual chain-armed
|
Measured: **both pulses 2.0000 s exactly, clean edges, on BOTH
|
||||||
laser-milestone session, with the duty-0 discipline from this one.
|
termination paths** — normal completion (streaming=0) and true
|
||||||
Signal naming (per the OpenGlow LASER SAFING sheet, now confirmed
|
underrun (streaming=1, kernel `underrun` state reached and
|
||||||
to match the factory board): FIRE = per-tick request (kernel
|
acked). The backstop drops FIRE within one tick (≤100 µs at
|
||||||
`laser_enable`); OK_2_FIRE = chain verdict; LASER_ON =
|
10 kHz) regardless of how the stream dies.
|
||||||
FIRE∧OK_2_FIRE to the PSU; HV_EN = HV enable, safing-driven only.
|
Signal naming (per the OpenGlow LASER SAFING sheet, confirmed to
|
||||||
|
match the factory board): FIRE = per-tick request (kernel
|
||||||
|
`laser_enable`, GPIO2_IO30); OK_2_FIRE = chain verdict; LASER_ON
|
||||||
|
= FIRE∧OK_2_FIRE to the PSU; HV_EN = HV enable, safing-driven
|
||||||
|
only.
|
||||||
|
- **ALL STANDING SCOPE GATES ARE NOW PASSED.** Live fire remains
|
||||||
|
gated on the laser-milestone software itself (power-byte + FIRE
|
||||||
|
emission in the stream engine with power-before-fire ordering,
|
||||||
|
HV_WDOG retriggering only while genuinely cutting, M3/M4/$32
|
||||||
|
mapping) plus a chain-armed first-light procedure; the hardware
|
||||||
|
verification prerequisites are complete. Interlock-trip recovery
|
||||||
|
behavior remains to be exercised (non-scope check).
|
||||||
- **Interlock readback semantics cross-check: OPEN** (see
|
- **Interlock readback semantics cross-check: OPEN** (see
|
||||||
factory-laser-safety-readbacks notes).
|
factory-laser-safety-readbacks notes).
|
||||||
3. **Homing**: X/Y home switch GPIOs exist in the cnc pin map (unused so
|
3. **Homing**: X/Y home switch GPIOs exist in the cnc pin map (unused so
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""FIRE-line drop-timing test - runs ON the board. Usage: fire_test.py A|B
|
"""FIRE-line drop-timing test - runs ON the board. Usage: fire_test.py A|B|U
|
||||||
|
|
||||||
Stream: power(0) first byte (duty forced to ZERO before any FIRE bit -
|
Stream: power(0) first byte (duty forced to ZERO before any FIRE bit -
|
||||||
the run-start reset would otherwise leave it at 100%), then
|
the run-start reset would otherwise leave it at 100%), then
|
||||||
1 s pads / 2.000 s FIRE bits / 1 s pads / 2.000 s FIRE bits ending
|
1 s pads / 2.000 s FIRE bits / 1 s pads / 2.000 s FIRE bits ending
|
||||||
exactly at end-of-data (the backstop edge the gate wants timed).
|
exactly at end-of-data (the backstop edge the gate wants timed).
|
||||||
No step bytes; motor_lock=15; streaming=0 (normal completion).
|
No step bytes; motor_lock=15.
|
||||||
|
|
||||||
Phase A: laser latch LOCKED - expects nothing on the LASER_ON pin.
|
Phase A: laser latch LOCKED - expects nothing on the FIRE/LASER_ON nets.
|
||||||
Phase B: latch UNLOCKED for the run (re-locked in finally). Refuses to
|
Phase B: latch UNLOCKED for the run (re-locked in finally), streaming=0
|
||||||
run if laser_pgood reports the HV supply good. Duty is zero throughout.
|
(end-of-data = normal completion). Refuses to run if
|
||||||
|
laser_pgood reports the HV supply good. Duty is zero throughout.
|
||||||
|
Phase U: like B but with streaming=1 declared, so the terminal
|
||||||
|
end-of-data is a TRUE UNDERRUN: the kernel lands in the
|
||||||
|
`underrun` fault state (expected - the script acks it via
|
||||||
|
stop). Same SDMA backstop, exercised through the fault path;
|
||||||
|
the scope measurement on the FIRE net is identical.
|
||||||
"""
|
"""
|
||||||
import fcntl, os, struct, sys, time
|
import fcntl, os, struct, sys, time
|
||||||
|
|
||||||
@@ -41,6 +47,8 @@ def snap(tag):
|
|||||||
|
|
||||||
|
|
||||||
mode = sys.argv[1].upper() if len(sys.argv) > 1 else 'A'
|
mode = sys.argv[1].upper() if len(sys.argv) > 1 else 'A'
|
||||||
|
unlock = mode in ('B', 'U')
|
||||||
|
underrun_mode = mode == 'U'
|
||||||
|
|
||||||
stream = (
|
stream = (
|
||||||
bytes([0x80]) + # power = 0: duty zero before any FIRE bit
|
bytes([0x80]) + # power = 0: duty zero before any FIRE bit
|
||||||
@@ -52,10 +60,10 @@ stream = (
|
|||||||
|
|
||||||
print('phase %s: stream %d bytes = %.3f s' % (mode, len(stream), len(stream) / TICK_HZ))
|
print('phase %s: stream %d bytes = %.3f s' % (mode, len(stream), len(stream) / TICK_HZ))
|
||||||
|
|
||||||
if mode == 'B':
|
if unlock:
|
||||||
pgood = rd('cnc/laser_pgood')
|
pgood = rd('cnc/laser_pgood')
|
||||||
if pgood != '0':
|
if pgood != '0':
|
||||||
print('ABORT: laser_pgood=%s (HV supply reports good) - refusing phase B' % pgood)
|
print('ABORT: laser_pgood=%s (HV supply reports good) - refusing latch unlock' % pgood)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
snap('pre ')
|
snap('pre ')
|
||||||
@@ -72,9 +80,13 @@ try:
|
|||||||
os.write(fd, stream)
|
os.write(fd, stream)
|
||||||
pos_before = rd_pos()
|
pos_before = rd_pos()
|
||||||
|
|
||||||
if mode == 'B':
|
if underrun_mode:
|
||||||
|
wr('cnc/streaming', 1) # end-of-data mid-run = true underrun
|
||||||
|
print('streaming=1: terminal end-of-data will be a TRUE UNDERRUN (expected)')
|
||||||
|
|
||||||
|
if unlock:
|
||||||
wr('cnc/laser_latch', 0) # UNLOCK for this run only
|
wr('cnc/laser_latch', 0) # UNLOCK for this run only
|
||||||
print('latch UNLOCKED for phase B run')
|
print('latch UNLOCKED for phase %s run' % mode)
|
||||||
|
|
||||||
wr('cnc/run', 1)
|
wr('cnc/run', 1)
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -89,6 +101,14 @@ try:
|
|||||||
break
|
break
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
print('done: state=%s after %.1f s' % (state, time.time() - t0))
|
print('done: state=%s after %.1f s' % (state, time.time() - t0))
|
||||||
|
if underrun_mode:
|
||||||
|
if state == 'underrun':
|
||||||
|
print('underrun state reached as EXPECTED; acking via stop')
|
||||||
|
else:
|
||||||
|
print('WARNING: expected underrun state, got %s' % state)
|
||||||
|
wr('cnc/stop', 1) # ack the underrun
|
||||||
|
wr('cnc/streaming', 0)
|
||||||
|
print('acked: state=%s' % rd('cnc/state'))
|
||||||
finally:
|
finally:
|
||||||
wr('cnc/laser_latch', 1) # re-lock unconditionally
|
wr('cnc/laser_latch', 1) # re-lock unconditionally
|
||||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||||
|
|||||||
Reference in New Issue
Block a user