#!/usr/bin/ruby -w # Generates Ruby-VPI tests from Verilog 2001 module declarations. # * The standard input stream is read if no input files are specified. # * The first input signal in a module's declaration is assumed to be the clocking signal. # # = Progress indicators # module:: A Verilog module has been identified. # backup:: A backup copy of a file is being made. # create:: A file is being created because it does not exist. # skip:: A file is being skipped because it is already up to date. # update:: A file will be updated because it is out of date. A backup copy will be made before the file is updated. Use a text merging tool (see MERGER) or manually transfer any necessary information from the backup copy to the updated file. # # = Environment variables # MERGER:: A command that invokes a text merging tool with two arguments: (1) old file, (2) new file. The tool's output should be written to the new file. =begin Copyright 2006 Suraj N. Kurapati This file is part of Ruby-VPI. Ruby-VPI is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Ruby-VPI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ruby-VPI; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =end require 'ruby-vpi/verilog_parser' require 'fileutils' require 'digest/md5' # Notify the user about some action being performed. def notify *args printf "%8s %s\n", *args end # Writes the given contents to the file at the given path. If the given path already exists, then a backup is created before invoking the merging tool. def write_file aPath, aContent if File.exist? aPath oldDigest = Digest::MD5.digest(File.read(aPath)) newDigest = Digest::MD5.digest(aContent) if oldDigest == newDigest notify :skip, aPath else old, new = "#{aPath}.old", aPath notify :backup, old FileUtils.cp aPath, old, :preserve => true notify :update, aPath File.open(new, 'w') {|f| f << aContent} if m = ENV['MERGER'] system "#{m} #{old.inspect} #{new.inspect}" end end else notify :create, aPath File.open(aPath, 'w') {|f| f << aContent} end end require 'ruby-vpi/erb' # Template used for generating output. class Template < ERB TEMPLATE_PATH = __FILE__.sub %r{\.rb$}, '_tpl' def initialize aName super File.read(File.join(TEMPLATE_PATH, aName)) end end # Holds information about the output destinations of a parsed Verilog module. class OutputInfo RUBY_EXT = '.rb' VERILOG_EXT = '.v' RUNNER_EXT = '.rake' SPEC_FORMATS = [:rSpec, :xUnit, :generic] attr_reader :verilogBenchName, :verilogBenchPath, :rubyBenchName, :rubyBenchPath, :designName, :designClassName, :designPath, :specName, :specClassName, :specFormat, :specPath, :rubyVpiPath, :runnerName, :runnerPath, :protoName, :protoPath attr_reader :testName, :suffix, :benchSuffix, :designSuffix, :specSuffix, :runnerSuffix, :protoSuffix def initialize aModuleName, aSpecFormat, aTestName, aRubyVpiPath raise ArgumentError unless SPEC_FORMATS.include? aSpecFormat @specFormat = aSpecFormat @testName = aTestName @suffix = '_' + @testName @benchSuffix = @suffix + '_bench' @designSuffix = @suffix + '_design' @specSuffix = @suffix + '_spec' @runnerSuffix = @suffix + '_runner' @protoSuffix = @suffix + '_proto' @rubyVpiPath = aRubyVpiPath @verilogBenchName = aModuleName + @benchSuffix @verilogBenchPath = @verilogBenchName + VERILOG_EXT @rubyBenchName = aModuleName + @benchSuffix @rubyBenchPath = @rubyBenchName + RUBY_EXT @designName = aModuleName + @designSuffix @designPath = @designName + RUBY_EXT @protoName = aModuleName + @protoSuffix @protoPath = @protoName + RUBY_EXT @specName = aModuleName + @specSuffix @specPath = @specName + RUBY_EXT @designClassName = aModuleName.to_ruby_const_name @specClassName = @specName.to_ruby_const_name @runnerName = aModuleName + @runnerSuffix @runnerPath = @runnerName + RUNNER_EXT end end if File.basename($0) == File.basename(__FILE__) # obtain templates for output generation VERILOG_BENCH_TEMPLATE = Template.new('bench.v') RUBY_BENCH_TEMPLATE = Template.new('bench.rb') DESIGN_TEMPLATE = Template.new('design.rb') PROTO_TEMPLATE = Template.new('proto.rb') SPEC_TEMPLATE = Template.new('spec.rb') RUNNER_TEMPLATE = Template.new('runner.rake') # parse command-line options require 'optparse' optSpecFmt = :generic optTestName = 'test' opts = OptionParser.new opts.banner = "Usage: #{File.basename __FILE__} [options] [files]" opts.on '-h', '--help', 'show this help message' do require 'ruby-vpi/rdoc' RDoc.usage_from_file __FILE__ puts opts exit end opts.on '--xunit', 'use xUnit specification format' do |val| optSpecFmt = :xUnit if val end opts.on '--rspec', 'use rSpec specification format' do |val| optSpecFmt = :rSpec if val end opts.on '-n', '--name NAME', 'insert NAME into the names of generated files' do |val| optTestName = val end opts.parse! ARGV v = VerilogParser.new(ARGF.read) v.modules.each do |m| puts notify :module, m.name o = OutputInfo.new(m.name, optSpecFmt, optTestName, File.dirname(File.dirname(__FILE__))) # generate output aParseInfo, aModuleInfo, aOutputInfo = v.freeze, m.freeze, o.freeze write_file o.runnerPath, RUNNER_TEMPLATE.result(binding) write_file o.verilogBenchPath, VERILOG_BENCH_TEMPLATE.result(binding) write_file o.rubyBenchPath, RUBY_BENCH_TEMPLATE.result(binding) write_file o.designPath, DESIGN_TEMPLATE.result(binding) write_file o.protoPath, PROTO_TEMPLATE.result(binding) write_file o.specPath, SPEC_TEMPLATE.result(binding) end end