"""Execute the linked Thumb du_ok with frozen MMIO snapshots, count instructions.
Not a Cortex-M4 cycle simulator. Not real ADC/TIM1/SPI timing evidence.
Requires external unicorn==2.1.4. Address-bounded hooks avoid IT-state sync bug. Synthetic ticks are not measured hardware cycles.
"""
from pathlib import Path
import subprocess,re,json,hashlib
from unicorn import Uc,UC_ARCH_ARM,UC_MODE_THUMB,UC_MODE_MCLASS,UC_HOOK_CODE
from unicorn.arm_const import UC_ARM_REG_SP,UC_ARM_REG_LR,UC_ARM_REG_R0,UC_CPU_ARM_CORTEX_M4
import unicorn
p=Path(__file__).resolve().parent.parent;t=p/'firmware/固件/drill_target/build'
elf=t/'drill_control_g431cb.elf';binary=t/'drill_control_g431cb.bin'
syms={}
for line in subprocess.check_output(['arm-none-eabi-nm',str(elf)],text=True).splitlines():
 m=re.match(r'([0-9a-fA-F]+)\s+\w\s+(\S+)$',line)
 if m:syms[m[2]]=int(m[1],16)
u=Uc(UC_ARCH_ARM,UC_MODE_THUMB|UC_MODE_MCLASS);u.ctl_set_cpu_model(UC_CPU_ARM_CORTEX_M4)
for address,size in [(0x08000000,0x20000),(0x20000000,0x10000),(0x40000000,0x30000),(0x48000000,0x2000),(0x50000000,0x1000),(0xe0000000,0x100000)]:u.mem_map(address,size)
u.mem_write(0x08000000,binary.read_bytes())
for name,address in [('DMA',0x40020000),('DMA_CHANNEL',0x40020008),('MUX',0x40020800),('MUX_STATUS',0x40020880),('SYSTICK',0xe000e010),('FLASH',0x40022000),('PWR',0x40007000),('RCC',0x40021000),('GPIOA',0x48000000),('GPIOB',0x48000400),('GPIOF',0x48001400),('TIM1',0x40012c00),('TIM2',0x40000000),('SPI2',0x40003800),('ADC1',0x50000000),('ADC2',0x50000100),('ADC_COMMON',0x50000300)]:u.mem_write(address,(p/'performance_model'/ (name+'.bin')).read_bytes())
for name,value in [('owned',1),('sampling_owned',1),('state',1),('drill_config',5),('drill_dual_state',9),('drill_adc_state',0)]:u.mem_write(syms[name],bytes([value]))
u.mem_write(syms['SystemCoreClock'],(128000000).to_bytes(4,'little'))

from unicorn import UC_HOOK_MEM_WRITE,UC_HOOK_MEM_READ
from unicorn.arm_const import UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3,UC_ARM_REG_APSR,UC_ARM_REG_XPSR,UC_ARM_REG_PC
for name,val in [('ds_owned',1),('timebase_owned',1),('ds_pending',0)]:u.mem_write(syms[name],bytes([val]))
for name,val in [('du_last',60),('du_phase',60),('ds_last',60),('ds_progress',60),('ds_remaining',2),('ds_sequence',0)]:u.mem_write(syms[name],val.to_bytes(4,'little'))
u.mem_write(0x40020010,(0x5000030c).to_bytes(4,'little'))
u.mem_write(0x40020014,syms['ds_ring'].to_bytes(4,'little'))
u.mem_write(0x40000024,(80).to_bytes(4,'little'))
# Model one fully completed slot before the foreground services it.
u.mem_write(0x4002000c,(1).to_bytes(4,'little'))
u.mem_write(0x40020000,(4).to_bytes(4,'little'))
u.mem_write(syms['ds_ring'],(0x04560123).to_bytes(4,'little'))
hook_events=[]
def hook_trace(uc,address):
 xpsr=uc.reg_read(UC_ARM_REG_XPSR);itstate=((xpsr>>25)&3)<<6|((xpsr>>10)&63)
 hook_events.append({'address':hex(address),'pc':hex(uc.reg_read(UC_ARM_REG_PC)),'ITSTATE':itstate})
 assert itstate==0, 'This Unicorn version cannot accept a memory callback in an IT block'
def w1c(uc,access,address,size,value,data):
 hook_trace(uc,address)
 if address==0x40020004:
  flags=int.from_bytes(uc.mem_read(0x40020000,4),'little');uc.mem_write(0x40020000,(flags&~value).to_bytes(4,'little'))
u.hook_add(UC_HOOK_MEM_WRITE,w1c,begin=0x40020004,end=0x40020004)
def gpio_write(uc,access,address,size,value,data):
 hook_trace(uc,address)
 if address in (0x48000018,0x48000418,0x48001418):
  odr=address-4;old=int.from_bytes(uc.mem_read(odr,4),'little');new=(old|(value&0xffff))&~(value>>16);uc.mem_write(odr,new.to_bytes(4,'little'))
u.hook_add(UC_HOOK_MEM_WRITE,gpio_write,begin=0x48000018,end=0x48001418)

import struct,argparse
parser=argparse.ArgumentParser();parser.add_argument('--ticks-per-instruction',type=int,choices=[1,2,3],default=1);args=parser.parse_args()
for name,val in [('timing_owned',1),('timing_failed',0),('timing_open',0),('previous_busy',0),('have_previous',0)]:u.mem_write(syms[name],bytes([val]))
u.mem_write(0xe0001000,(1).to_bytes(4,'little'));u.mem_write(0xe0001004,(0).to_bytes(4,'little'));u.mem_write(0xe000edfc,(1<<24).to_bytes(4,'little'))
u.mem_write(syms['drill_debug_timing'],struct.pack('<16I',0x544d3830,1,0,0,0,0,0,0,0,0,0,0,128000000,3840,30,1))
u.mem_write(syms['ds_last'],(65).to_bytes(4,'little'))
trace=[];synthetic_ticks=0
# Advance two model counters from executed instructions. These are synthetic
# ticks, not observed MCU cycles; no Flash/bus/IRQ timing is simulated.
def count(uc,address,size,data):
 global synthetic_ticks
 trace.append(address);synthetic_ticks+=args.ticks_per_instruction
timer_bytes=bytearray((p/'performance_model/TIM2.bin').read_bytes())
def timer_read(uc,offset,size,data):
 if offset==0x24:return (80+synthetic_ticks//128)&0xffffffff
 return int.from_bytes(timer_bytes[offset:offset+size],'little')
def timer_write(uc,offset,size,value,data):timer_bytes[offset:offset+size]=value.to_bytes(size,'little')
def dwt_read(uc,offset,size,data):return (synthetic_ticks&0xffffffff) if offset==4 else (1 if offset==0 else 0)
u.mem_unmap(0x40000000,0x1000);u.mmio_map(0x40000000,0x1000,timer_read,None,timer_write,None)
u.mem_unmap(0xe0001000,0x1000);u.mmio_map(0xe0001000,0x1000,dwt_read,None,None,None)
u.hook_add(UC_HOOK_CODE,count)
u.reg_write(UC_ARM_REG_SP,0x20005800);u.reg_write(UC_ARM_REG_LR,0x0801fff1);u.reg_write(UC_ARM_REG_R0,0x20004000)
u.emu_start(syms['drill_timed_foreground']|1,0x0801fff0,count=50000)
record=struct.unpack('<16I',u.mem_read(syms['drill_debug_timing'],64));result=u.reg_read(UC_ARM_REG_R0)

received=int.from_bytes(u.mem_read(syms['drill_debug_stream_received'],4),'little')
dual=int.from_bytes(u.mem_read(syms['drill_dual_state'],1),'little')
dual_error=int.from_bytes(u.mem_read(syms['drill_dual_error'],4),'little')
assert record[2]%2==0
if args.ticks_per_instruction==1:
 assert result==1 and record[11]==0 and record[15]==3 and received==1 and dual==9
 case='COMPLETED_PAIR_UNDER_SYNTHETIC_WINDOW'
elif args.ticks_per_instruction==2:
 assert result==1 and record[11]==0 and record[15]==3 and received==0 and dual==10 and dual_error==7
 case='ADC_CONSUMER_FRESHNESS_REJECTED_MONITOR_WINDOW_WITHIN_LIMIT'
else:
 assert result==0 and record[11]==3 and record[15]&2==0 and dual==10
 case='TIMING_LIMIT_HARD_OFF'
if dual==10:
 assert int.from_bytes(u.mem_read(0x40012c00,4),'little')&1==0
 assert int.from_bytes(u.mem_read(0x48001414,4),'little')&2==0
 assert int.from_bytes(u.mem_read(0x48000014,4),'little')&(1<<12)
r={'binary_sha256':hashlib.sha256(binary.read_bytes()).hexdigest(),'emulator':unicorn.__version__,'entry':'drill_timed_foreground','status':'ADDRESS_BOUNDED_DYNAMIC_MODEL_PASS','case':case,'executed_instruction_count':len(trace),'synthetic_ticks_per_instruction':args.ticks_per_instruction,'return':result,'synthetic_record':list(record),'synthetic_last_cycles':record[5],'synthetic_last_us':record[7],'synthetic_timing_fault':record[11],'sample_received':received,'dual_state':dual,'dual_error':dual_error,'memory_hook_events':hook_events,'counter_time_advanced_from_instructions':True,'cycle_accurate':False,'physical_validation':False,'actual_hardware_capture':False,'30us_service_deadline_qualified':False,'scope':'Actual linked wrapper with frozen peripherals except synthetic DWT/TIM2, DMA IFCR W1C, GPIO BSRR latches. Not actual CPU performance.'}
print(json.dumps(r,indent=2));(p/('performance_model/dynamic_timing_model_A81_'+str(args.ticks_per_instruction)+'.json')).write_text(json.dumps(r,indent=2)+'\n')
