Partager des instruments entre slots parallèles
Le test parallèle suppose que chaque position a ses propres instruments. Les postes réels le font rarement. Un multimètre derrière un multiplexeur sert huit positions, une enceinte thermique contient tout le plateau, une alimentation nourrit un rack entier.
Les instruments partagés sont l'endroit où les postes de test parallèles perdent le débit pour lequel ils ont été construits, et où ils produisent des mesures fausses d'une manière qui passe inaperçue. Ce guide couvre le fonctionnement du partage, comment en dimensionner le bénéfice, et les modes de défaillance contre lesquels il faut concevoir.
Il suppose un banc multi-positions ; pour savoir ce qu'est une position et comment la déclarer, voir Que sont les test sockets.
Pourquoi le partage pose problème
Un instrument est un matériel unique doté d'un état. Configurez un multimètre sur un calibre 10 V continu, et ce sera son calibre jusqu'à ce que quelque chose le change.
Deux slots mesurant en même temps créent deux défaillances qui ne se ressemblent pas :
Commandes entrelacées. Le slot 1 règle le calibre sur 10 V, le slot 2 le règle sur 100 mV, puis le slot 1 déclenche une lecture. Le slot 1 mesure son unité sur le calibre du slot 2. L'instrument ne signale aucune erreur, la mesure est un nombre plausible, et l'unité soit échoue sans raison, soit passe alors qu'elle ne devrait pas.
Sérialisation. Deux slots ne peuvent pas physiquement lire un multimètre simultanément. Les appels sont mis en file, que vous l'ayez conçu ainsi ou non.
La première défaillance est la dangereuse, parce qu'elle produit de mauvaises données plutôt qu'une erreur.
La portée décide du nombre d'instances
Dans le TofuPilot Framework, la portée du plug contrôle le nombre d'instances et leur durée de vie :
| Portée | Instance | À utiliser pour |
|---|---|---|
slot (défaut) | Une par slot | Voie d'alimentation par position, port série par position |
execution | Une partagée par tous les slots | Un multimètre derrière un multiplexeur, une enceinte, une alimentation de rack |
station | Une par poste, conservée entre exécutions | Sessions VISA ou TCP lentes à ouvrir |
execution: slots: 8plugs: - name: dut_serial python: plugs.serial_port:DutSerial scope: slot - name: dmm python: plugs.dmm:Keysight34461A scope: execution config: address: "192.168.1.50" - name: chamber python: plugs.chamber:ThermalChamber scope: station config: address: "192.168.1.60"Déclarer un instrument partagé en execution ou station est ce qui le rend sûr. Une instance de plug exécute un appel de méthode à la fois. Quand plusieurs slots appellent le même plug partagé, leurs appels sont mis en file et exécutés un par un, comme des commandes sur une socket SCPI. La défaillance d'entrelacement décrite plus haut ne peut pas se produire via un plug partagé, puisqu'il y a une instance et un appel en cours.
L'erreur correspondante est de déclarer un instrument partagé en portée slot. Huit slots ouvrent alors huit sessions vers un seul instrument physique, sans aucune sérialisation, et vous obtenez exactement le problème d'entrelacement — de façon intermittente, sous charge, en production.
Garder la section critique dans un seul appel
La sérialisation est par appel de méthode, pas par phase. Une mesure partagée doit donc être un seul appel qui configure, déclenche et lit :
import pyvisaclass Keysight34461A: def __init__(self, address: str): self._rm = pyvisa.ResourceManager() self._inst = self._rm.open_resource(f"TCPIP::{address}::INSTR") self._inst.timeout = 5000 def measure_dc_volts(self, channel: int, expected_range: float) -> float: """Sélectionner la voie, configurer et lire en un appel indivisible.""" self._inst.write(f"ROUT:CLOS (@{channel})") self._inst.write(f"CONF:VOLT:DC {expected_range}") return float(self._inst.query("READ?")) def __del__(self): self._inst.close()Découpé en trois appels, l'appel d'un autre slot peut s'intercaler entre la configuration et la lecture :
class DmmWrong: def select_channel(self, channel: int): self._inst.write(f"ROUT:CLOS (@{channel})") def configure(self, expected_range: float): self._inst.write(f"CONF:VOLT:DC {expected_range}") def read(self) -> float: # Un autre slot a peut-être déjà reconfiguré le multiplexeur. return float(self._inst.query("READ?"))La phase passe alors sa propre voie, et l'instance partagée conserve chaque séquence intacte. Une phase lit sa position depuis run.slot_id, la clé déclarée dans execution.slots :
CHANNELS = {"s1": 101, "s2": 102, "s3": 103, "s4": 104}def measure_rail(measurements, run, dmm): measurements.rail_voltage = dmm.measure_dc_volts( channel=CHANNELS[run.slot_id], expected_range=10.0, )Associer explicitement les clés de slot aux voies, plutôt que de dériver une voie d'un numéro de slot, garde le câblage lisible et survit à une renumérotation du banc.
Garder les appels partagés courts
La mise en file a un coût direct sur le débit, et c'est la raison pour laquelle les postes parallèles n'atteignent pas leurs spécifications.
Une méthode de plug partagé qui dort retient les appels de tous les autres slots vers ce plug aussi longtemps qu'elle s'exécute. Le temps de stabilisation en est la cause habituelle :
import timeclass DmmSlow: def measure_after_settle(self, channel: int) -> float: self._inst.write(f"ROUT:CLOS (@{channel})") time.sleep(2.0) # bloque les huit slots return float(self._inst.query("READ?"))Avec huit slots, cette stabilisation de deux secondes coûte seize secondes de temps de poste par cycle, et sept slots la passent inactifs. Mettez l'attente dans la phase, où les slots attendent simultanément :
import timeCHANNELS = {"s1": 101, "s2": 102, "s3": 103, "s4": 104}def measure_rail(measurements, run, psu, dmm): channel = CHANNELS[run.slot_id] psu.enable(channel) time.sleep(2.0) # chaque slot attend en parallèle measurements.rail_voltage = dmm.measure_dc_volts(channel, 10.0)La règle : les méthodes de plug partagé ne devraient contenir que des E/S d'instrument, rien d'autre. L'attente, le calcul et les boucles de reprise appartiennent aux phases.
Dimensionner le bénéfice avant de construire
Savoir si le partage est acceptable relève de l'arithmétique, à faire avant la construction du banc.
Soit N le nombre de slots, S le temps d'instrument partagé par unité, et P le temps par slot qui s'exécute en parallèle. Le temps de cycle vaut approximativement :
cycle ≈ max(P, N × S)Huit slots, 25 s de travail parallèle, 1 s de mesure partagée :
max(25, 8 × 1) = 25s l'instrument partagé est gratuitLe même poste avec une mesure partagée de 4 secondes :
max(25, 8 × 4) = 32s le multimètre est maintenant le goulotAu-delà de N × S > P, ajouter des slots n'apporte rien : le neuvième slot attend le multimètre. C'est le moment soit d'acheter un second instrument, soit de raccourcir la mesure partagée, soit d'arrêter d'ajouter des positions.
Utiliser phase_first avec les instruments partagés
Avec des slots configurés, la stratégie d'exécution influence le regroupement du travail partagé :
execution: strategy: phase_first slots: 8phase_first, la valeur par défaut, exécute la même phase sur tous les slots avant de passer à la suivante. La mesure au multimètre de chaque slot se produit dans la même fenêtre, donc l'instrument reste dans une seule configuration pour le groupe.
slot_first termine un slot avant de démarrer le suivant, ce qui reconfigure l'instrument partagé à chaque changement. Pour un poste dont le partage est coûteux à reconfigurer, phase_first est le meilleur défaut.
Setup et teardown partagés
Certains matériels partagés ne sont pas mesurés par unité du tout : une enceinte monte en température une fois, une alimentation de rack s'allume une fois. Ce sont des phases de portée execution :
setup: - name: Ramp Chamber to 85C python: phases.ramp_chamber scope: executionmain: - name: Measure Leakage python: phases.measure_leakageteardown: - name: Discharge This Position python: phases.discharge - name: Return Chamber to Ambient python: phases.cool_chamber scope: executionLe setup de portée execution s'exécute une fois avant le démarrage de tout slot ; le teardown de portée execution s'exécute une fois après la fin de tous les slots.
Cette portée est une exigence de correction, pas une commodité. Un teardown de portée slot peut s'exécuter pendant que ses voisins testent encore. Return Chamber to Ambient en portée slot commencerait à refroidir dès la fin de la première unité, et toutes les unités restantes seraient mesurées sur une rampe de température descendante. Ces unités échouent de façon intermittente, les échecs ne se reproduisent pas au banc, et rien dans les données ne désigne l'enceinte.
La règle en découle directement : si un teardown touche quelque chose qu'un autre slot peut encore utiliser, il doit être en scope: execution.
Comportement en cas d'échec
Un plug partagé qui échoue à s'initialiser arrête tous les slots, tout comme une phase de setup de portée execution en échec. C'est correct — huit unités testées sans enceinte à température produiraient huit réussites dénuées de sens — mais cela fait du setup partagé le code aux plus lourdes conséquences du poste.
Donnez à ces phases des reprises et des messages d'erreur qui nomment l'instrument :
setup: - name: Ramp Chamber to 85C python: phases.ramp_chamber scope: execution timeout: 15m retry: limit: 2 delay: 30sSi la connexion à l'instrument tombe en cours de test, gérez la reconnexion dans le plug. Le moteur garantit un processus de plug durable, pas une session d'instrument vivante ; il vérifie la santé d'un plug station conservé avant chaque réutilisation et le relance si le processus est mort, mais une session TCP morte dans un processus vivant est à vous de détecter.
Pièges courants
Déclarer un instrument partagé en portée slot. N sessions vers un instrument, aucune sérialisation, commandes entrelacées. C'est la défaillance qui produit de mauvais nombres au lieu d'erreurs.
Découper une configuration-lecture en plusieurs méthodes de plug. Un autre slot peut s'intercaler dans l'intervalle.
Dormir dans une méthode de plug partagé. Tous les autres slots attendent. Déplacez l'attente dans la phase.
Nettoyer du matériel partagé dans un teardown de slot. Le premier slot qui termine perturbe les autres.
Dimensionner le banc sans le terme d'instrument partagé. Un banc à 16 slots avec une mesure partagée de 3 secondes est un cycle de 48 secondes, quelle que soit la rapidité des unités.
Points clés
- Les instruments partagés échouent de deux façons : commandes entrelacées produisant de mauvaises données, et mise en file détruisant le débit.
scope: executionouscope: stationdonne une instance dont les appels se sérialisent, ce qui empêche l'entrelacement par construction.- Une méthode de plug partagé doit être toute la section critique : configurer, déclencher et lire en un appel.
- Gardez les attentes hors des méthodes partagées ; une méthode qui dort bloque tous les slots.
- Le temps de cycle vaut approximativement
max(P, N × S)— au-delà de ce point de bascule, ajouter des slots n'apporte rien. - Tout ce qu'un slot qui termine pourrait perturber appartient à un teardown de portée execution.