#!/usr/bin/env python3
import hashlib,json
from pathlib import Path
ROOT=Path(__file__).resolve().parent; LOADS=(30,100,300); VDD=1.8
def read(p):
 lines=p.read_text(encoding='ascii').splitlines()[1:]; return [[float(x) for x in s.split()] for s in lines if s.strip()]
def cross(t,y,level,start):
 for i in range(1,len(t)):
  if t[i]>=start and y[i-1]<level<=y[i]:
   f=(level-y[i-1])/(y[i]-y[i-1]); return t[i-1]+f*(t[i]-t[i-1])
 raise RuntimeError('crossing not found')
cases=[]; hashes={}
for load in LOADS:
 stem='sky130-sram-read' if load==30 else f'sky130-sram-read-{load}ff'; rr=read(ROOT/f'{stem}.csv')
 t=[r[0] for r in rr]; pre,wl,bl,blb,q,qb,ivdd=([r[i] for r in rr] for i in range(2,9))
 wh=cross(t,wl,.9,2.2e-9); delta=[b-a for a,b in zip(bl,blb)]; sense=cross(t,delta,.1,wh)
 active=[i for i,x in enumerate(t) if wh<=x<=6e-9]; qp=max(q[i] for i in active); qm=min(qb[i] for i in active)
 energy=sum(.5*(max(-ivdd[i-1]*VDD,0)+max(-ivdd[i]*VDD,0))*(t[i]-t[i-1]) for i in range(1,len(t)) if 2e-9<=t[i]<=6.2e-9)
 step=max(1,len(rr)//360); ids=list(range(0,len(rr),step))
 for target in (wh,sense,t[-1]):
  i=min(range(len(t)),key=lambda n:abs(t[n]-target)); ids.append(i) if i not in ids else None
 wave=[{'time_ns':round(t[i]*1e9,5),'pre_v':round(pre[i],5),'wl_v':round(wl[i],5),'bl_v':round(bl[i],5),'blb_v':round(blb[i],5),'q_v':round(q[i],5),'qb_v':round(qb[i],5)} for i in sorted(ids)]
 net=ROOT/f'{stem}.spice'; hashes[net.name]=hashlib.sha256(net.read_bytes()).hexdigest()
 cases.append({'id':f'sky130_6t_read_{load}ff','bitline_load_ff':load,'sense_delta_target_mv':100,'wordline_half_ns':round(wh*1e9,5),'sense_100mv_ns':round(sense*1e9,5),'sense_delay_ps':round((sense-wh)*1e12,3),'stored_zero_peak_v':round(qp,6),'stored_one_min_v':round(qm,6),'read_window_energy_fj':round(energy*1e15,5),'state_retained':qp<.9 and qm>.9,'raw_points':len(rr),'waveform':wave})
delays=[c['sense_delay_ps'] for c in cases]
assert all(c['state_retained'] for c in cases) and delays==sorted(delays) and all(c['raw_points']>=4000 for c in cases)
payload={'schema_version':'1.0','experiment_id':'SKY130_SRAM_READ_001','title':'SKY130 6T SRAM precharge and read transient load sweep','model_fidelity':'transistor-level PDK compact models','execution':'precomputed','conditions':{'vdd_v':VDD,'temperature_c':27,'corner':'tt','stored_state':'Q=0, QB=1','bitline_load_ff':list(LOADS),'precharge_release_ns':2.0,'wordline_rise_ns':[2.2,2.4],'wordline_fall_ns':[6.0,6.2],'dimensions_um':{'length':.15,'pull_up_width':.42,'pull_down_width':.65,'access_width':.42,'precharge_width':.84}},'simulator':{'name':'ngspice','version':'42','analysis':'transient 2 ps / 8 ns, UIC'},'verification':{'case_count':len(cases),'all_cases_converged':True,'all_states_retained':True,'sense_metric':'time from WL=VDD/2 to BLB-BL=100 mV','load_delay_monotonic':True,'minimum_raw_points_per_case':min(c['raw_points'] for c in cases)},'cases':cases,'provenance':{'pdk':'SKY130A','open_pdks_ciel_commit':'f6eeac7dad085ffcc829ccfd721f7b4ce39edcf7','model_library':'sky130.lib.spice tt','netlist_sha256':hashes},'limitations':['Pre-layout schematic simulation; extracted interconnect, package, and sense-amplifier loading are excluded.','One TT/27 C operating point and one stored polarity are shown; this is not a PVT or mismatch characterization.','UIC supplies a deterministic stored state and precharged bitlines before the timed read sequence.','The 100 mV differential is an educational observation threshold, not a characterized sense-amplifier specification.']}
base=json.dumps(payload,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode(); payload['provenance']['payload_sha256_before_hash_field']=hashlib.sha256(base).hexdigest()
out=ROOT/'sky130-sram-read-web-v1.json'; out.write_text(json.dumps(payload,indent=2,ensure_ascii=False)+'\
',encoding='utf-8')
print(json.dumps({'dataset':str(out),'sense_delay_ps':delays,'stored_zero_peak_v':[c['stored_zero_peak_v'] for c in cases],'read_window_energy_fj':[c['read_window_energy_fj'] for c in cases],'sha256':hashlib.sha256(out.read_bytes()).hexdigest()},indent=2))
