mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-10-31 11:42:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			115 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			115 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| from pathlib import Path
 | |
| import re
 | |
| import subprocess
 | |
| from typing import Dict, List, Tuple
 | |
| from random import Random
 | |
| 
 | |
| vcd_version = "Yosys/tests/functional/rkt_vcd.py"
 | |
| StepList = List[Tuple[int, str]]
 | |
| SignalStepMap = Dict[str, StepList]
 | |
| SignalWidthMap = Dict[str, int]
 | |
| 
 | |
| def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='today'):
 | |
|     with open(filename, 'w') as f:
 | |
|         # Write the header
 | |
|         f.write(f"$date\n    {date}\n$end\n")
 | |
|         f.write(f"$timescale {timescale} $end\n")
 | |
|         
 | |
|         # Declare signals
 | |
|         f.write("$scope module gold $end\n")
 | |
|         for signal_name, changes in signals.items():
 | |
|             signal_size = len(changes[0][1])
 | |
|             f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n")
 | |
|         f.write("$upscope $end\n")
 | |
|         f.write("$enddefinitions $end\n")
 | |
|         
 | |
|         # Collect all unique timestamps
 | |
|         timestamps = sorted(set(time for changes in signals.values() for time, _ in changes))
 | |
|         
 | |
|         # Write initial values
 | |
|         f.write("#0\n")
 | |
|         for signal_name, changes in signals.items():
 | |
|             for time, value in changes:
 | |
|                 if time == 0:
 | |
|                     f.write(f"{value} {signal_name}\n")
 | |
|         
 | |
|         # Write value changes
 | |
|         for time in timestamps:
 | |
|             if time != 0:
 | |
|                 f.write(f"#{time}\n")
 | |
|                 for signal_name, changes in signals.items():
 | |
|                     for change_time, value in changes:
 | |
|                         if change_time == time:
 | |
|                             f.write(f"{value} {signal_name}\n")
 | |
| 
 | |
| def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random):
 | |
|     signals: dict[str, list[str]] = {}
 | |
|     inputs: SignalWidthMap = {}
 | |
|     outputs: SignalWidthMap = {}
 | |
| 
 | |
|     current_struct_name: str = ""
 | |
|     with open(rkt_file_path, 'r') as rkt_file:
 | |
|         for line in rkt_file:
 | |
|             m = re.search(r'gold_(Inputs|Outputs|State)', line)
 | |
|             if m:
 | |
|                 current_struct_name = m.group(1)
 | |
|                 if current_struct_name == "State": break
 | |
|             elif not current_struct_name: continue # skip lines before structs
 | |
|             m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line)
 | |
|             if not m: continue # skip non matching lines (probably closing the struct)
 | |
|             signal = m.group(1)
 | |
|             width = int(m.group(2))
 | |
|             if current_struct_name == "Inputs":
 | |
|                 inputs[signal] = width
 | |
|             elif current_struct_name == "Outputs":
 | |
|                 outputs[signal] = width
 | |
| 
 | |
|     for signal, width in inputs.items():
 | |
|         step_list: list[int] = []
 | |
|         for step in range(num_steps):
 | |
|             value = rnd.getrandbits(width)
 | |
|             binary_string = format(value, '0{}b'.format(width))
 | |
|             step_list.append(binary_string)
 | |
|         signals[signal] = step_list
 | |
| 
 | |
|     test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt')
 | |
|     with open(test_rkt_file_path, 'w') as test_rkt_file:
 | |
|         test_rkt_file.writelines([
 | |
|             '#lang rosette\n',
 | |
|             f'(require "{rkt_file_path.name}")\n',
 | |
|         ])
 | |
| 
 | |
|         for step in range(num_steps):
 | |
|             this_step = f"step_{step}"
 | |
|             value_list: list[str] = []
 | |
|             for signal, width in inputs.items():
 | |
|                 value = signals[signal][step]
 | |
|                 value_list.append(f"(bv #b{value} {width})")
 | |
|             gold_Inputs = f"(gold_Inputs {' '.join(value_list)})"
 | |
|             gold_State = f"(cdr step_{step-1})" if step else "gold_initial"
 | |
|             test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n")
 | |
| 
 | |
|     cmd = ["racket", test_rkt_file_path]
 | |
|     status = subprocess.run(cmd, capture_output=True)
 | |
|     assert status.returncode == 0, f"{cmd[0]} failed"
 | |
| 
 | |
|     for signal in outputs.keys():
 | |
|         signals[signal] = []
 | |
| 
 | |
|     for line in status.stdout.decode().splitlines():
 | |
|         m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line)
 | |
|         assert m, f"Incomplete output definition {line!r}"
 | |
|         for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)):
 | |
|             assert isinstance(value, str), f"Bad value {value!r}"
 | |
|             assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}"
 | |
|             assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})"
 | |
|             int_value = int(value[2:], 16 if value.startswith('#x') else 2)
 | |
|             binary_string = format(int_value, '0{}b'.format(width))
 | |
|             signals[output].append(binary_string)
 | |
| 
 | |
|     vcd_signals: SignalStepMap = {}
 | |
|     for signal, steps in signals.items():
 | |
|         vcd_signals[signal] = [(time, f"b{value}") for time, value in enumerate(steps)]
 | |
| 
 | |
|     write_vcd(vcd_path, vcd_signals)
 |