From eea7589160505e6045da1bc3e486ae64da4fdaa0 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 25 Jul 2024 13:09:29 -0700 Subject: [PATCH] Split ArchDef into config dependend/independent parts --- .gitignore | 1 + arch/ext/B.yaml | 10 + backends/arch_gen/tasks.rake | 57 +++- backends/ext_pdf_doc/tasks.rake | 62 +++- .../ext_pdf_doc/templates/ext_pdf.adoc.erb | 10 +- lib/arch_def.rb | 269 ++++++++++-------- lib/idl.rb | 18 +- 7 files changed, 279 insertions(+), 148 deletions(-) diff --git a/.gitignore b/.gitignore index dd6bc47bf..2f97339f7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ .venv gen node_modules +_site diff --git a/arch/ext/B.yaml b/arch/ext/B.yaml index 5ba27e214..d79f75414 100644 --- a/arch/ext/B.yaml +++ b/arch/ext/B.yaml @@ -3,10 +3,20 @@ B: type: unprivileged long_name: Bitmanipulation instructions + company: + - name: RISC-V International + url: https://riscv.org + doc_license: + name: Creative Commons Attribution 4.0 International License + url: https://creativecommons.org/licenses/by/4.0/ versions: - version: 1.0 state: ratified ratification_date: 2024-04 + contributors: + - name: Ved Shanbhogue + email: ved@rivosinc.com + company: Rivos, Inc. url: https://drive.google.com/file/d/1SgLoasaBjs5WboQMaU3wpHkjUwV71UZn/view implies: - [Zba, 1.0] diff --git a/backends/arch_gen/tasks.rake b/backends/arch_gen/tasks.rake index 6ec977264..483b3fcec 100644 --- a/backends/arch_gen/tasks.rake +++ b/backends/arch_gen/tasks.rake @@ -7,10 +7,60 @@ require_relative "lib/arch_gen" ARCH_GEN_DIR = Pathname.new(__FILE__).dirname def arch_def_for(config_name) + config_name = "_" if config_name.nil? @arch_defs ||= {} return @arch_defs[config_name] if @arch_defs.key?(config_name) - @arch_defs[config_name] = ArchDef.new(config_name) + if config_name == "_" + @arch_defs[config_name] = ArchDef.new + else + @arch_defs[config_name] = ImplArchDef.new(config_name) + end +end + +file "#{$root}/.stamps/arch-gen.stamp" => ( + [ + "#{$root}/.stamps", + "#{ARCH_GEN_DIR}/lib/arch_gen.rb", + "#{$root}/lib/idl/ast.rb", + "#{ARCH_GEN_DIR}/tasks.rake", + __FILE__ + ] + Dir.glob($root / "arch" / "**" / "*.yaml") +) do |t| + csr_hash = Dir.glob($root / "arch" / "csr" / "**" / "*.yaml").map do |f| + csr_obj = YAML.load_file(f) + csr_name = csr_obj.keys[0] + csr_obj[csr_name]["name"] = csr_name + csr_obj[csr_name]["fields"].map do |k, v| + v["name"] = k + [k, v] + end + [csr_name, csr_obj[csr_name]] + end.to_h + inst_hash = Dir.glob($root / "arch" / "inst" / "**" / "*.yaml").map do |f| + inst_obj = YAML.load_file(f) + inst_name = inst_obj.keys[0] + inst_obj[inst_name]["name"] = inst_name + [inst_name, inst_obj[inst_name]] + end.to_h + ext_hash = Dir.glob($root / "arch" / "ext" / "**" / "*.yaml").map do |f| + ext_obj = YAML.load_file(f) + ext_name = ext_obj.keys[0] + ext_obj[ext_name]["name"] = ext_name + [ext_name, ext_obj[ext_name]] + end.to_h + + arch_def = { + "instructions" => inst_hash, + "extensions" => ext_hash, + "csrs" => csr_hash, + } + + dest = "#{$root}/gen/_/arch/arch_def.yaml" + FileUtils.mkdir_p File.dirname(dest) + File.write(dest, YAML.dump(arch_def)) + + FileUtils.touch(t.name) end # stamp to indicate completion of Arch Gen for a given config @@ -48,4 +98,9 @@ namespace :gen do Rake::Task["#{$root}/.stamps/arch-gen-#{args[:config_name]}.stamp"].invoke(args[:config_name]) end + + desc "Generate a unified architecture file (configuration independent)" + task :arch do + Rake::Task["#{$root}/.stamps/arch-gen.stamp"].invoke + end end diff --git a/backends/ext_pdf_doc/tasks.rake b/backends/ext_pdf_doc/tasks.rake index effd2633a..f89b2b6ae 100644 --- a/backends/ext_pdf_doc/tasks.rake +++ b/backends/ext_pdf_doc/tasks.rake @@ -7,6 +7,8 @@ require "asciidoctor-diagram" require_relative "#{$lib}/idl/passes/gen_adoc" +EXT_PDF_DOC_DIR = Pathname.new "#{$root}/backends/ext_pdf_doc" + # Utilities for generating an Antora site out of an architecture def module AsciidocUtils class << self @@ -47,50 +49,84 @@ module AsciidocUtils end rule %r{#{$root}/gen/ext_pdf_doc/.*/pdf/.*_extension\.pdf} => proc { |tname| - config_name = Pathname.new(tname).relative_path_from($root / "gen" / "ext_pdf_doc").to_s.split("/")[0] + config_name = Pathname.new(tname).relative_path_from("#{$root}/gen/ext_pdf_doc").to_s.split("/")[0] ext_name = Pathname.new(tname).basename(".pdf").to_s.split("_")[0..-2].join("_") [ "#{$root}/gen/ext_pdf_doc/#{config_name}/adoc/#{ext_name}_extension.adoc" ] } do |t| - config_name = Pathname.new(t.name).relative_path_from($root / "gen" / "ext_pdf_doc").to_s.split("/")[0] ext_name = Pathname.new(t.name).basename(".pdf").to_s.split("_")[0..-2].join("_") + config_name = Pathname.new(t.name).relative_path_from("#{$root}/gen/ext_pdf_doc").to_s.split("/")[0] adoc_file = "#{$root}/gen/ext_pdf_doc/#{config_name}/adoc/#{ext_name}_extension.adoc" FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, Asciidoctor.convert_file(adoc_file, backend: "pdf", safe: :safe) + Asciidoctor.convert_file(adoc_file, backend: "pdf", safe: :safe, to_file: t.name) + + puts + puts "Success!! File written to #{t.name}" end rule %r{#{$root}/gen/ext_pdf_doc/.*/adoc/.*_extension\.adoc} => proc { |tname| - config_name = Pathname.new(tname).relative_path_from($root / "gen" / "ext_pdf_doc").to_s.split("/")[0] + config_name = Pathname.new(tname).relative_path_from("#{$root}/gen/ext_pdf_doc").to_s.split("/")[0] + ext_name = Pathname.new(tname).basename(".adoc").to_s.split("_")[0..-2].join("_") + arch_yaml_paths = + if File.exist?("#{$root}/arch/ext/#{ext_name}.yaml") + ["#{$root}/arch/ext/#{ext_name}.yaml"] + Dir.glob("#{$root}/cfgs/*/arch_overlay/ext/#{ext_name}.yaml") + else + Dir.glob("#{$root}/cfgs/*/arch_overlay/ext/#{ext_name}.yaml") + end + raise "Can't find extension '#{ext_name}'" if arch_yaml_paths.empty? + + stamp = config_name == "_" ? "#{$root}/.stamps/arch-gen.stamp" : "#{$root}/.stamps/arch-gen-#{config_name}.stamp" + [ - "#{$root}/.stamps/arch-gen-#{config_name}.stamp", + stamp, (EXT_PDF_DOC_DIR / "templates" / "ext_pdf.adoc.erb").to_s, + arch_yaml_paths, __FILE__ - ] + ].flatten } do |t| config_name = Pathname.new(t.name).relative_path_from("#{$root}/gen/ext_pdf_doc").to_s.split("/")[0] - ext_name = Pathname.new(t.name).basename(".pdf").to_s.split("_")[0..-2].join("_") + + arch_def = + if config_name == "_" + arch_def_for(nil) + else + arch_def_for(config_name) + end + + ext_name = Pathname.new(t.name).basename(".adoc").to_s.split("_")[0..-2].join("_") template_path = EXT_PDF_DOC_DIR / "templates" / "ext_pdf.adoc.erb" erb = ERB.new(template_path.read, trim_mode: "-") erb.filename = template_path.to_s - arch_def = arch_def_for(config_name) ext = arch_def.extension(ext_name) FileUtils.mkdir_p File.dirname(t.name) File.write t.name, AsciidocUtils.resolve_links(arch_def.find_replace_links(erb.result(binding))) end - namespace :gen do desc <<~DESC - Generate PDF documentation for :extension using configuration :config_name + Generate PDF documentation for :extension + + If the extension is custom (from an arch_overlay), also give the config name + DESC + task :ext_pdf, [:extension] do |_t, args| + extension = args[:extension] + + Rake::Task[$root / "gen" / "ext_pdf_doc" / "_" / "pdf" / "#{extension}_extension.pdf"].invoke + end + + desc <<~DESC + Generate PDF documentation for :extension that is defined or overlayed in :cfg DESC - task :ext_pdf, [:extension, :config_name] do |_t, args| - config = args[:config_name] + task :cfg_ext_pdf, [:extension, :cfg] do |_t, args| + raise ArgumentError, "Missing required argument :extension" if args[:extension].nil? + raise ArgumentError, "Missing required argument :cfg" if args[:cfg].nil? + extension = args[:extension] - Rake::Task[$root / "gen" / "ext_pdf_doc" / config.to_s / "pdf" / "#{extension}_extension.pdf"].invoke + Rake::Task[$root / "gen" / "ext_pdf_doc" / args[:cfg] / "pdf" / "#{extension}_extension.pdf"].invoke(args) end end diff --git a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb index 7a4686773..d20d8c03b 100644 --- a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb +++ b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb @@ -121,7 +121,7 @@ Mnemoic:: ---- Encoding:: -<%- if arch_def.multi_xlen? && i.multi_encoding? -%> +<%- if i.multi_encoding? -%> [NOTE] This instruction has different encodings in RV32 and RV64 @@ -139,7 +139,7 @@ RV64:: <%- else -%> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump i.wavedrom_desc(arch_def.config_params["XLEN"]) %> +<%= JSON.dump i.wavedrom_desc(i.respond_to?(:base) ? i.base : 64) %> .... <%- end -%> @@ -148,7 +148,7 @@ Description:: Decode Variables:: -<%- if i.arch_def.multi_xlen? && i.multi_encoding? -%> +<%- if i.multi_encoding? -%> RV32:: + ---- @@ -166,7 +166,7 @@ RV64:: ---- <%- else -%> ---- -<%- i.decode_variables(i.arch_def.config_params["XLEN"]).each do |d| -%> +<%- i.decode_variables(i.respond_to?(:base) ? i.base : 64).each do |d| -%> <%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; <%- end -%> ---- @@ -175,7 +175,7 @@ RV64:: Operation:: [souce,idl] ---- -<%= i.operation_ast.gen_adoc %> +<%= i.operation_ast(arch_def.idl_compiler).gen_adoc %> ---- Included in:: diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 6cff3bf58..58bdf857e 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -1558,8 +1558,151 @@ def satisfied_by?(*args) end end -# Object model for a configured architecture definition class ArchDef + # @return [Idl::Compiler] The IDL compiler + attr_reader :idl_compiler + + # @return [Idl::AstNode] Abstract syntax tree of global scope + attr_reader :global_ast + + # Initialize a new configured architecture defintiion + # + # @param config_name [#to_s] The name of a configuration, which must correspond + # to a folder under $root/cfgs + def initialize(from_child: false) + @idl_compiler = Idl::Compiler.new(self) + + unless from_child + arch_def_file = $root / "gen" / "_" / "arch" / "arch_def.yaml" + + @arch_def = YAML.load_file(arch_def_file) + + # load the globals into the symbol table + @global_ast = @idl_compiler.compile_file( + $root / "arch" / "isa" / "globals.isa", + type_check: false + ) + end + end + + def inspect = "ArchDef" + + # @return [Array] List of all extensions, even those that are't implemented + def extensions + return @extensions unless @extensions.nil? + + @extensions = [] + @arch_def["extensions"].each_value do |ext_data| + @extensions << Extension.new(ext_data, self) + end + @extensions + end + + # @returns [Hash] Hash of all extensions, even those that aren't implemented, indexed by extension name + def extension_hash + return @extension_hash unless @extension_hash.nil? + + @extension_hash = {} + extensions.each do |ext| + @extension_hash[ext.name] = ext + end + @extension_hash + end + + # @param name [#to_s] Extension name + # @return [Extension] Extension named `name` + # @return [nil] if no extension `name` exists + def extension(name) + extension_hash[name.to_s] + end + + # @return [Array] List of all CSRs defined by RISC-V, whether or not they are implemented + def csrs + return @csrs unless @csrs.nil? + + @csrs = @arch_def["csrs"].map do |_csr_name, csr_data| + Csr.new(csr_data) + end + end + + # @return [Array] List of all known CSRs, even those not implemented by + # this config + def all_known_csr_names + @arch_def["csrs"].map { |csr| csr[0] } + end + + # @return [Hash] All csrs, even unimplemented ones, indexed by CSR name + def csr_hash + return @csr_hash unless @csr_hash.nil? + + @csr_hash = {} + csrs.each do |csr| + @csr_hash[csr.name] = csr + end + @csr_hash + end + + # @param csr_name [#to_s] CSR name + # @return [Csr,nil] a specific csr, or nil if it doesn't exist + def csr(csr_name) + csr_hash[csr_name] + end + + # @return [Array] List of all instructions, whether or not they are implemented + def instructions + return @instructions unless @instructions.nil? + + @instructions = @arch_def["instructions"].map do |_inst_name, inst_data| + Instruction.new(inst_data) + end + + @instructions + end + + # @return [Hash] All instructions, indexed by name + def instruction_hash + return @instruction_hash unless @instruction_hash.nil? + + @instruction_hash = {} + instructions.each do |inst| + @instruction_hash[inst.name] = inst + end + @instruction_hash + end + + # @param inst_name [#to_s] Instruction name + # @return [Instruction,nil] An instruction named 'inst_name', or nil if it doesn't exist + def inst(inst_name) + instruction_hash[inst_name.to_s] + end + + # given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` + # and replace them with links to the relevant object page + # + # @param adoc [String] Asciidoc source + # @return [String] Asciidoc source, with link placeholders + def find_replace_links(adoc) + adoc.gsub(/`([\w.]+)`/) do |match| + name = Regexp.last_match(1) + csr_name, field_name = name.split(".") + csr = csr(csr_name) + if !field_name.nil? && !csr.nil? && csr.field?(field_name) + "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" + elsif !csr.nil? + "%%LINK%csr;#{csr_name};#{csr_name}%%" + elsif inst(name.downcase) + "%%LINK%inst;#{name};#{name}%%" + elsif extension(name) + "%%LINK%ext;#{name};#{name}%%" + else + match + end + end + end +end + +# Object model for a configured architecture definition +class ImplArchDef < ArchDef # @return [String] Name of the architecture configuration attr_reader :name @@ -1569,12 +1712,6 @@ class ArchDef # @return [Hash] The configuration parameters attr_reader :config_params - # @return [Idl::Compiler] The IDL compiler - attr_reader :idl_compiler - - # @return [Idl::AstNode] Abstract syntax tree of global scope - attr_reader :global_ast - # @return [Integer] 32 or 64, the XLEN in m-mode attr_reader :mxlen @@ -1586,6 +1723,8 @@ def hash = @name.hash # @param config_name [#to_s] The name of a configuration, which must correspond # to a folder under $root/cfgs def initialize(config_name) + super(from_child: true) + @name = config_name.to_s arch_def_file = $root / "gen" / @name / "arch" / "arch_def.yaml" @@ -1603,12 +1742,11 @@ def initialize(config_name) @mxlen = @arch_def["params"]["XLEN"] @sym_table = Idl::SymbolTable.new(self) - @idl_compiler = Idl::Compiler.new(self) # load the globals into the symbol table @global_ast = @idl_compiler.compile_file( $root / "arch" / "isa" / "globals.isa", - @sym_table + symtab: @sym_table ) @sym_table.deep_freeze @@ -1653,38 +1791,9 @@ def implemented_extensions @implemented_extensions = [] @arch_def["implemented_extensions"].each do |e| @implemented_extensions << ExtensionVersion.new(e["name"], e["version"]) -2 end - - @implemented_extensions - end - - # @return [Array] List of all extensions, even those that are't implemented - def extensions - return @extensions unless @extensions.nil? - - @extensions = [] - @arch_def["extensions"].each_value do |ext_data| - @extensions << Extension.new(ext_data, self) - end - @extensions - end - - # @returns [Hash] Hash of all extensions, even those that aren't implemented, indexed by extension name - def extension_hash - return @extension_hash unless @extension_hash.nil? - - @extension_hash = {} - extensions.each do |ext| - @extension_hash[ext.name] = ext end - @extension_hash - end - # @param name [#to_s] Extension name - # @return [Extension] Extension named `name` - # @return [nil] if no extension `name` exists - def extension(name) - extension_hash[name.to_s] + @implemented_extensions end # @overload ext?(ext_name) @@ -1724,21 +1833,6 @@ def implemented_csrs @implemented_csrs = csrs.select { |c| @arch_def["implemented_csrs"].include?(c.name) } end - # @return [Array] List of all CSRs defined by RISC-V, whether or not they are implemented - def csrs - return @csrs unless @csrs.nil? - - @csrs = @arch_def["csrs"].map do |_csr_name, csr_data| - Csr.new(csr_data) - end - end - - # @return [Array] List of all known CSRs, even those not implemented by - # this config - def all_known_csr_names - @arch_def["csrs"].map { |csr| csr[0] } - end - # @return [Hash] Implemented csrs, indexed by CSR name def implemented_csr_hash return @implemented_csr_hash unless @implemented_csr_hash.nil? @@ -1750,16 +1844,7 @@ def implemented_csr_hash @implemented_csr_hash end - # @return [Hash] All csrs, even unimplemented ones, indexed by CSR name - def csr_hash - return @csr_hash unless @csr_hash.nil? - @csr_hash = {} - csrs.each do |csr| - @csr_hash[csr.name] = csr - end - @csr_hash - end # @param csr_name [#to_s] CSR name # @return [Csr,nil] a specific csr, or nil if it doesn't exist or isn't implemented @@ -1767,34 +1852,6 @@ def implemented_csr(csr_name) implemented_csr_hash[csr_name] end - # @param csr_name [#to_s] CSR name - # @return [Csr,nil] a specific csr, or nil if it doesn't exist - def csr(csr_name) - csr_hash[csr_name] - end - - # @return [Array] List of all instructions, whether or not they are implemented - def instructions - return @instructions unless @instructions.nil? - - @instructions = @arch_def["instructions"].map do |_inst_name, inst_data| - Instruction.new(inst_data) - end - - @instructions - end - - # @return [Hash] All instructions, indexed by name - def instruction_hash - return @instruction_hash unless @instruction_hash.nil? - - @instruction_hash = {} - instructions.each do |inst| - @instruction_hash[inst.name] = inst - end - @instruction_hash - end - # @return [Array] List of all implemented instructions def implemented_instructions return @implemented_instructions unless @implemented_instructions.nil? @@ -1806,12 +1863,6 @@ def implemented_instructions @implemented_instructions end - # @param inst_name [#to_s] Instruction name - # @return [Instruction,nil] An instruction named 'inst_name', or nil if it doesn't exist - def inst(inst_name) - instruction_hash[inst_name.to_s] - end - # @return [Array] List of all reachable IDL functions for the config def implemented_functions return @implemented_functions unless @implemented_functions.nil? @@ -1838,28 +1889,4 @@ def implemented_functions @implemented_functions end - - # given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` - # and replace them with links to the relevant object page - # - # @param adoc [String] Asciidoc source - # @return [String] Asciidoc source, with link placeholders - def find_replace_links(adoc) - adoc.gsub(/`([\w.]+)`/) do |match| - name = Regexp.last_match(1) - csr_name, field_name = name.split(".") - csr = csr(csr_name) - if !field_name.nil? && !csr.nil? && csr.field?(field_name) - "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" - elsif !csr.nil? - "%%LINK%csr;#{csr_name};#{csr_name}%%" - elsif inst(name.downcase) - "%%LINK%inst;#{name};#{name}%%" - elsif extension(name) - "%%LINK%ext;#{name};#{name}%%" - else - match - end - end - end end diff --git a/lib/idl.rb b/lib/idl.rb index e17dddd1d..6fa4db093 100644 --- a/lib/idl.rb +++ b/lib/idl.rb @@ -41,7 +41,7 @@ def initialize(arch_def) @arch_def = arch_def end - def compile_file(path, symtab) + def compile_file(path, symtab: nil, type_check: true) @parser.set_input_file(path.to_s) m = @parser.parse path.read @@ -59,14 +59,16 @@ def compile_file(path, symtab) ast = m.to_ast ast.set_input_file(path.to_s) - begin - ast.type_check(symtab) - rescue AstNode::TypeError, AstNode::InternalError => e - warn e.what - warn e.bt - exit 1 + if type_check + begin + ast.type_check(symtab) + rescue AstNode::TypeError, AstNode::InternalError => e + warn e.what + warn e.bt + exit 1 + end + ast.freeze_tree end - ast.freeze_tree ast end