#!/usr/bin/env python3
import hashlib,json
from pathlib import Path
ROOT=Path(__file__).resolve().parent
faults={0:('none','No injected fault'),1:('sa0','stuck-at-0 at address 3, bit 2'),2:('sa1','stuck-at-1 at address 3, bit 2')}
elements=[
 {'id':0,'notation':'⇕(w0)','direction':'any','operations':['w0']},
 {'id':1,'notation':'⇑(r0,w1)','direction':'up','operations':['r0','w1']},
 {'id':2,'notation':'⇑(r1,w0)','direction':'up','operations':['r1','w0']},
 {'id':3,'notation':'⇓(r0,w1)','direction':'down','operations':['r0','w1']},
 {'id':4,'notation':'⇓(r1,w0)','direction':'down','operations':['r1','w0']},
 {'id':5,'notation':'⇕(r0)','direction':'any','operations':['r0']}]
scenarios={i:{'id':faults[i][0],'fault':faults[i][1],'trace':[]} for i in faults}
summaries={}
for raw in (ROOT/'iverilog.log').read_text().splitlines():
 parts=[p.strip() for p in raw.split(',')]
 if parts[0]=='TRACE':
  case,cycle,element,direction,address=map(int,parts[1:6])
  scenarios[case]['trace'].append({'cycle':cycle,'element':element,'direction':'up' if direction else 'down','address':address,'operation':parts[6].strip(),'expected_bit':int(parts[7]),'observed_hex':parts[8].upper(),'expected_hex':parts[9].upper(),'mismatch':parts[10]=='1'})
 elif parts[0]=='SUMMARY':
  case=int(parts[1]); summaries[case]={'accesses':int(parts[2]),'trace_mismatches':int(parts[3]),'fail':parts[4]=='1','rtl_mismatch_count':int(parts[5])}
for case,item in scenarios.items():
 item.update(summaries[case]); item['detected']=item['fail']; item['first_detection']=next((x for x in item['trace'] if x['mismatch']),None)
assert [len(scenarios[i]['trace']) for i in faults]==[80,80,80]
assert scenarios[0]['rtl_mismatch_count']==0 and not scenarios[0]['fail']
assert scenarios[1]['rtl_mismatch_count']==2 and scenarios[1]['fail']
assert scenarios[2]['rtl_mismatch_count']==3 and scenarios[2]['fail']
files=['march_cminus_bist.sv','march_cminus_bist_tb.sv','march_cminus_bist.vcd','iverilog.log','verilator-rtl.log']
payload={
 'schema_version':'1.0','experiment_id':'SRAM_BIST_MARCH_CMINUS_001','title':'March C- SRAM BIST with stuck-at fault injection',
 'algorithm':{'name':'March C-','notation':'{⇕(w0); ⇑(r0,w1); ⇑(r1,w0); ⇓(r0,w1); ⇓(r1,w0); ⇕(r0)}','elements':elements,'complexity':'10N operations'},
 'memory':{'depth_words':8,'data_width_bits':8,'address_width_bits':3,'fault_address':3,'fault_bit':2},
 'tools':{'iverilog':'12.0','verilator':'5.020','simulation':'Icarus execution + synthesizable RTL-only Verilator lint'},
 'verification':{'scenario_count':3,'all_scenarios_completed':True,'accesses_per_scenario':80,'total_trace_events':240,'fault_free_passed':True,'sa0_detected':True,'sa1_detected':True,'verilator_rtl_lint':'pass'},
 'scenarios':[scenarios[i] for i in faults],
 'provenance':{'sha256':{name:hashlib.sha256((ROOT/name).read_bytes()).hexdigest() for name in files}},
 'limitations':['The memory is an 8x8 behavioral model, not a compiled foundry SRAM macro.','Fault injection covers one single-cell stuck-at-0 and one stuck-at-1 site; coupling, transition, address-decoder, retention, and analog faults are outside this preset.','The browser replays the verified Icarus trace and does not execute arbitrary HDL.','March C- coverage claims depend on the fault model and are not equivalent to production test qualification.']}
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/'march-cminus-bist-web-v1.json'
out.write_text(json.dumps(payload,ensure_ascii=False,indent=2)+chr(10),encoding='utf-8')
print(json.dumps({'dataset':str(out),'events':240,'summaries':summaries,'sha256':hashlib.sha256(out.read_bytes()).hexdigest()},indent=2))
