module OrigenTesters module PatternCompilers class V93KPatternCompiler < BasePatternCompiler require_relative 'v93k/multiport' include MultiportAPI require_relative 'v93k/digcap' include DigCapAPI attr_reader :avc_files, :max_avcfilename_size, :vec_per_frame TEMPLATE = "#{Origen.root!}/lib/origen_testers/pattern_compilers/templates/template.aiv.erb" # Linux compiler executable path def self.linux_compiler Origen.site_config.origen_testers[:v93k_linux_pattern_compiler] end # Windows compiler executable path - not available for V93K def self.windows_compiler nil end # Resolves to correct compiler (linux is only available) def self.compiler linux_compiler end def self.compiler_cmd Pathname.new(compiler).absolute? ? compiler : eval('"' + compiler + '"') end def self.compiler_options "#{compiler_cmd} -h" end def self.compiler_version "#{compiler_cmd} -V" end def initialize(id, options = {}) super @user_options = { config_dir: nil, # Common directory where all configs can be stored pinconfig_dir: nil, # Can override common config_dir if pinconfig stored elsewhere pinconfig: nil, # Can specify just file name (to use config_dir/pinconfig_dir), or give full path tmf_dir: nil, # Can override common config_dir if tmf stored elsewhere tmf: nil, # Can specify just file name (to use config_dir/tmf_dir), or give full path vbc_dir: nil, # Can override common config_dir if vbc stored elsewhere vbc: nil, # Can specify just file name (to use config_dir/vbc_dir), or give full path incl_dir: nil, includes: [], # Array of files that will be copied into tmp job workspace before compilation tmp_dir: nil, avc_dir: nil, binl_dir: nil, multiport: nil, # Optional hash for multiport settings: port_bursts, port_in_focus, prefix, postfix digcap: nil, # Optional hash for digcap settings: pins, vps, nrf, char }.merge(@user_options) @job_options = { tester: :v93k, compiler: self.class.compiler, # required }.merge(@job_options) @compiler_options = { }.merge(@compiler_options) @compiler_options_with_args = { aiv2b_opts: nil }.merge(@compiler_options_with_args) @avc_files = [] @vec_per_frame = {} update_common_options(options) # Update common options with default (see BasePatternCompiler) verify_pinconfig_is_specified # Verify pinconfig specified correctly - Smartest specific verify_tmf_is_specified # Verify tmf specified correctly - Smartest specific clean_and_verify_options # Standard cleaning and verifying (see BasePatternCompiler) end def pinconfig_file @pinconfig_file ||= build_file(:pinconfig) end def tmf_file @tmf_file ||= build_file(:tmf) end def vbc_file @vbc_file ||= build_file(:vbc) end def verify_pinconfig_is_specified fail 'Pinconfig file is not defined! Pass as an option.' if pinconfig_file.nil? fail 'Pinconfig is not a file!' unless pinconfig_file.file? end def verify_tmf_is_specified fail 'Timing Map File (tmf) is not defined! Pass as an option.' if tmf_file.nil? fail 'Timing Map File (tmf) is not a file!' unless tmf_file.file? end # Executes the compiler for each job in the queue def run(aiv = nil, options = {}) aiv, options = nil, aiv if aiv.is_a? Hash fail "Error: the tester #{Origen.tester} is not an V93K tester, exiting..." unless is_v93k? msg = "Error: application #{Origen.app.name} is running on Windows, " msg += 'to run the pattern compiler you must be on a Linux machine' fail msg if Origen.running_on_windows? # Check if there was a pattern list passed as an argument # If so, then compile the patterns inside it. # Otherwise compile the jobs in the queue if aiv.nil? if empty? empty_msg return end @jobs.each do |job| unless options[:ignore_ready] fail "Error: compiler #{job.id} not ready for pattern #{job.name}" unless job.ready? end if job.location == :lsf # puts "#{self.class.aiv_setup} ; #{job.cmd}" # Origen.app.lsf.submit(self.class.aiv_setup + '; ' + job.cmd) Origen.app.lsf.submit(job.cmd) else Origen.profile "Linux pattern compiler compiles pattern #{job.pattern}" do Dir.chdir(job.output_directory) do # puts "#{job.cmd}" system job.cmd end end end end if @job_options[:location] == :local if @job_options[:clean] == true puts 'Log file :clean option set to true, deleting log files' clean_output end end # Clear @jobs clear else # Assumes .aiv file and all workspace collateral has been built up aiv = convert_to_pathname(aiv) fail 'File does not exist! Please specify existing aiv file.' unless aiv.file? current_job_options = @job_options.merge(@compiler_options_with_args) current_job_options = current_job_options.merge(extract_job_options_from_aiv(aiv)) current_job_options = current_job_options.merge(options) current_job_options[:output_directory] = aiv.dirname @jobs << Job.new(Pathname.new(aiv), current_job_options, @compiler_options) inspect_jobs run(options) end end # Output the compiler jobs in the queue to the console def inspect_jobs(index = nil) return empty_msg if empty? desc = [] puts "\n" @jobs.each_with_index do |j, i| unless index.nil? next unless i == index end desc << '| Job: ' + " #{i + 1} ".rjust(8) + '|' + 'Pattern/AIV:'.rjust(18) + " #{j.pattern.basename}".ljust(125) + '|' desc << '| |' + 'Compiler ID:'.rjust(18) + " #{j.id} ".ljust(125) + '|' desc << '| |' + 'AVC Files:'.rjust(18) + " #{j.count} ".ljust(125) + '|' desc << '| |' + 'Output Directory:'.rjust(18) + " #{j.output_directory} ".ljust(125) + '|' desc << '| |' + '.avc directory:'.rjust(18) + " #{j.avc_dir} ".ljust(125) + '|' desc << '| |' + '.binl directory:'.rjust(18) + " #{j.binl_dir} ".ljust(125) + '|' desc << '| |' + 'LSF:'.rjust(18) + " #{j.location == :lsf ? true : false} ".ljust(125) + '|' desc << '| |' + 'Delete log files:'.rjust(18) + " #{j.clean} ".ljust(125) + '|' desc << '| |' + 'Verbose:'.rjust(18) + " #{j.verbose} ".ljust(125) + '|' if j.aiv2b_opts && j.aiv2b_opts.is_a?(String) aiv2b_opts = j.aiv2b_opts.gsub('AI_V2B_OPTIONS ', '') else aiv2b_opts = render_aiv2b_options_line.gsub('AI_V2B_OPTIONS', '') end desc << '| |' + 'AI_V2B Options:'.rjust(18) + " #{aiv2b_opts} ".ljust(125) + '|' desc << '-' * desc.first.size end puts desc.flatten.join("\n") end # Finds the patterns and creates a compiler job for each one found. # Handles singles files (.atp, .atp.gz, or .list) and directories (recursively or flat) def find_jobs(p = @path) # First-level verification: file/directory was given and exists msg = 'Pass in a valid file (.avc, .avc.gz, .list) or a valid directory' fail "Pattern path is set to nil! #{msg}" if p.nil? path = Pathname.new(p) fail "Pattern path does not exist! #{msg}" unless path.exist? path = path.expand_path # Set the reference directory for pattern sub-dir mirroring set_reference_directory # Collect file, list, or directory (recursively) Origen.profile 'Linux pattern compiler finds patterns' do if path.directory? # Get all of the patterns inside this dir or inside this directory recursively process_directory(path, @files, @user_options[:recursive]) else # Found a file so no searching is necessary, process as single pattern or list process_file(path, @files) end end fail "Did not fild a valid file to compile! #{msg}" if @files.empty? Origen.profile 'Linux pattern compiler creates job' do # For V93K, only single AIV file is really sent to the compiler, but need list of all # avc files that will be compiled using that aiv file, so keep a separate array. @max_avcfilename_size = 0 @files.each do |f| @avc_files << Pathname.new(f).basename.sub_ext('').to_s @max_avcfilename_size = @avc_files[-1].size > @max_avcfilename_size ? @avc_files[-1].size : @max_avcfilename_size end rel_dir = Pathname.new("#{path.dirname.to_s[@user_options[:reference_directory].to_s.size..-1]}") # Resolve output directory if @job_options[:output_directory].nil? # job output dir not specified, create a unique (hash) based on path/compiler_name s = Digest::MD5.new s << @user_options[:reference_directory].to_s s << @id.to_s out = "#{@user_options[:reference_directory]}/job_#{@id}_#{s.to_s[0..6].upcase}#{rel_dir}" job_output_dir = Pathname.new(out) else job_output_dir = Pathname.new("#{@job_options[:output_directory]}#{rel_dir}") end # Create any necessary output directories before trying to compile unless job_output_dir.directory? puts "Output directory #{job_output_dir} does not exist, creating it..." FileUtils.mkdir_p(job_output_dir) end job_avc_dir = avc_dir.absolute? ? avc_dir : Pathname.new("#{job_output_dir}/#{avc_dir}").cleanpath unless job_avc_dir.directory? puts "AVC Output directory #{job_avc_dir} does not exist, creating it..." FileUtils.mkdir_p(job_avc_dir) end job_binl_dir = binl_dir.absolute? ? binl_dir : Pathname.new("#{job_output_dir}/#{binl_dir}").cleanpath unless job_binl_dir.directory? puts "BINL Output directory #{job_binl_dir} does not exist, creating it..." FileUtils.mkdir_p(job_binl_dir) end # Move AVC files into job space (through pre-processor) @files.each do |file| contents = File.open(file, 'rb') { |f| f.read } new_contents = preprocess_avc(contents) new_avc_file = Pathname.new("#{job_avc_dir}/#{Pathname.new(file).basename}").cleanpath File.open(new_avc_file, 'w') { |f| f.write(new_contents) } avc_key = Pathname.new(file).basename.sub_ext('').to_s.to_sym @vec_per_frame[avc_key] = digcap? ? avc_digcap_vpf(new_contents) : 0 end # Generate the AIV file using the template with all the pattern compiler parameters aiv_file = "#{job_output_dir}/#{path.basename.to_s.split('.')[0]}.aiv" Origen.log.info "Creating... #{aiv_file}" contents = Origen.compile(self.class::TEMPLATE, scope: self, preserve_target: true) File.open(aiv_file, 'w') { |f| f.write(contents) } # Copy Timing Map File to local AIV workspace dest = Pathname.new("#{job_output_dir}/#{tmf_file.basename}").cleanpath FileUtils.cp tmf_file, dest # Copy VBC file to local AIV workspace (if specified) if vbc_file dest = Pathname.new("#{job_output_dir}/#{vbc_file.basename}").cleanpath FileUtils.cp vbc_file, dest end # Copy any extra files needed (includes) @user_options[:includes].each do |incl| src = build_file(:incl, incl) dest = Pathname.new("#{job_output_dir}/#{src.basename}").cleanpath FileUtils.cp src, dest end # Gather up job options current_job_options = @job_options.merge(@compiler_options_with_args) current_job_options[:output_directory] = job_output_dir current_job_options[:pinconfig] = pinconfig_file current_job_options[:tmf] = tmf_file current_job_options[:count] = @avc_files.count current_job_options[:avc_dir] = avc_dir current_job_options[:binl_dir] = binl_dir # Create new job @jobs << Job.new(Pathname.new(aiv_file), current_job_options, @compiler_options) current_job_options = {} end # Clear files and avc_files now that job has successfully been queued @files = [] @avc_files = [] @vec_per_frame = {} inspect_jobs end # Given the file contents, parse and calculate number of capture vectors def avc_digcap_vpf(contents) capture_vectors = 0 contents.each_line do |line| capture_vectors += 1 if /#{digcap.capture_string}/.match(line) end capture_vectors end def avc_dir @avc_dir ||= begin if @user_options[:avc_dir] clean_path(@user_options[:avc_dir].to_s) else Pathname.new('./AVC') # default value end end end def binl_dir @binl_dir ||= begin if @user_options[:binl_dir] clean_path(@user_options[:binl_dir].to_s) else Pathname.new('./BINL') # default value end end end def tmp_dir @tmp_dir ||= begin if @user_options[:tmp_dir] clean_path(@user_options[:tmp_dir].to_s) else Pathname.new('./tmp') # default value end end end # Given path string, return Pathname object with cleaned up path def clean_path(path_str) path = Pathname.new(path_str).cleanpath if path.absolute? return path else return Pathname.new("./#{path}") end end # Placeholder - TopLevel can monkey patch this method to do more # sophisticated AVC modification prior to compilation def preprocess_avc(contents) new_contents = contents # no manipulation done here new_contents end def render_aiv2b_options_line line = 'AI_V2B_OPTIONS' if @compiler_options_with_args[:aiv2b_opts] if @compiler_options_with_args[:aiv2b_opts].is_a? Array @compiler_options_with_args[:aiv2b_opts].each do |opt| line += " #{opt}" end elsif @compiler_options_with_args[:aiv2b_opts].is_a? String line += " #{@compiler_options[:aiv2b]}" else fail 'aiv2b options must be an array or string' end end line += " -c #{vbc_file.basename}" if vbc_file line end def render_aiv_patterns_header line = 'PATTERNS ' line += 'name'.ljust(max_avcfilename_size + 2) line += 'port'.ljust(multiport.port_in_focus.size + 2) if multiport? line += 'tmf_file' line end def render_aiv_patterns_entry(pattern) line = ' ' line += "#{pattern}".ljust(max_avcfilename_size + 2) line += "#{multiport.port_in_focus}".ljust(multiport.port_in_focus.size + 2) if multiport? line += "#{tmf_file.basename}" line end private def extract_job_options_from_aiv(file) options = {} contents = File.open(file, 'rb') { |f| f.read } count = 0 counting = false contents.each_line do |line| if match = line.match(/^avc_dir\s*(\S*)/) options[:avc_dir] = match.captures[0] end if match = line.match(/^pinconfig_file\s*(\S*)/) options[:pinconfig] = Pathname.new(match.captures[0]) end if match = line.match(/^single_binary_pattern_dir\s*(\S*)/) options[:binl_dir] = match.captures[0] end if match = line.match(/^AI_V2B_OPTIONS/) options[:aiv2b_opts] = match.captures[0] end if counting if line.match(/\w+/) count += 1 else counting = false end end if match = line.match(/^PATTERNS/) counting = true end end options[:count] = count options end def build_file(type, fstr = nil) type_dir = "#{type}_dir".to_sym fstr ||= @user_options[type] if fstr.nil? nil else if Pathname.new(fstr).absolute? Pathname.new(fstr) elsif @user_options[type_dir] Pathname.new("#{@user_options[type_dir]}/#{fstr}").cleanpath elsif @user_options[:config_dir] Pathname.new("#{@user_options[:config_dir]}/#{fstr}").cleanpath else Pathname.new("#{Origen.root!}/#{fstr}").cleanpath end end end end end end