From 5d203ddb4a876af248ac12464a16757b2197ec3d Mon Sep 17 00:00:00 2001 From: James Ball Date: Thu, 14 Nov 2024 12:33:33 -0800 Subject: [PATCH 1/4] Created $copy to copy string values like $ref. Added unit-level test. Fixed mie to be defined by Sm not I extension. Description for mip and mie is now shared mimicing ISA manual. --- arch/csr/mhartid.yaml | 2 +- arch/csr/mie.yaml | 143 +---------------- arch/csr/mip.yaml | 141 ++++++++++++++++- .../templates/certificate.adoc.erb | 6 +- lib/test/test_yaml_loader.rb | 98 +++++++++++- lib/yaml_loader.rb | 145 ++++++++++-------- 6 files changed, 323 insertions(+), 212 deletions(-) diff --git a/arch/csr/mhartid.yaml b/arch/csr/mhartid.yaml index d67defcda..b3454d1de 100644 --- a/arch/csr/mhartid.yaml +++ b/arch/csr/mhartid.yaml @@ -6,7 +6,7 @@ mhartid: priv_mode: M length: MXLEN description: Reports the unique hart-specific ID in the system. - definedBy: I + definedBy: Sm fields: ID: location_rv32: 31-0 diff --git a/arch/csr/mie.yaml b/arch/csr/mie.yaml index 806214dd7..7445ab7e0 100644 --- a/arch/csr/mie.yaml +++ b/arch/csr/mie.yaml @@ -5,147 +5,8 @@ mie: address: 0x304 priv_mode: M length: MXLEN - definedBy: I - description: | - Per-type interrupt enables. - - For a detailed description of interrupt handling, see <%= link_to(:section, 'sec:interrupts') %>. - - The `mie` register is an - MXLEN-bit read/write register containing interrupt enable bits. - Interrupt cause number _i_ (as reported in CSR `mcause`) corresponds with - bit _i_ in - `mie`. Bits 15:0 are allocated to standard interrupt causes only, while - bits 16 and above are designated for platform use. - - NOTE: Interrupts designated for platform use may be designated for custom use - at the platform's discretion. - - An interrupt _i_ will trap to M-mode (causing the privilege mode to - change to M-mode) if all of the following are true: - - * either the current privilege mode is M and the MIE bit in the - `mstatus` register is set, or the current privilege mode has less - privilege than M-mode; - * bit _i_ is set in both `mip` and `mie`; - * if register `mideleg` exists, bit _i_ is not set in `mideleg`. - - These conditions for an interrupt trap to occur must be evaluated in a - bounded amount of time from when an interrupt becomes, or ceases to be, - pending in `mip`, and must also be evaluated immediately following the - execution of an __x__RET instruction or an explicit write to a CSR on - which these interrupt trap conditions expressly depend (including `mip`, - `mie`, `mstatus`, and `mideleg`). - - Interrupts to M-mode take priority over any interrupts to lower - privilege modes. - - A bit in `mie` must be writable if the corresponding interrupt can ever - become pending. Bits of `mie` that are not writable must be read-only - zero. - - [NOTE] - ==== - The machine-level interrupt registers handle a few root interrupt - sources which are assigned a fixed service priority for simplicity, - while separate external interrupt controllers can implement a more - complex prioritization scheme over a much larger set of interrupts that - are then muxed into the machine-level interrupt sources. - - ''' - - The non-maskable interrupt is not made visible via the `mip` register as - its presence is implicitly known when executing the NMI trap handler. - ==== - - If supervisor mode is implemented, bits `mip`.SEIP and `mie`.SEIE are - the interrupt-pending and interrupt-enable bits for supervisor-level - external interrupts. SEIP is writable in `mip`, and may be written by - M-mode software to indicate to S-mode that an external interrupt is - pending. Additionally, the platform-level interrupt controller may - generate supervisor-level external interrupts. Supervisor-level external - interrupts are made pending based on the logical-OR of the - software-writable SEIP bit and the signal from the external interrupt - controller. When `mip` is read with a CSR instruction, the value of the - SEIP bit returned in the `rd` destination register is the logical-OR of - the software-writable bit and the interrupt signal from the interrupt - controller, but the signal from the interrupt controller is not used to - calculate the value written to SEIP. Only the software-writable SEIP bit - participates in the read-modify-write sequence of a CSRRS or CSRRC - instruction. - - [NOTE] - ==== - For example, if we name the software-writable SEIP bit `B` and the - signal from the external interrupt controller `E`, then if - `csrrs t0, mip, t1` is executed, `t0[9]` is written with `B || E`, then - `B` is written with `B || t1[9]`. If `csrrw t0, mip, t1` is executed, - then `t0[9]` is written with `B || E`, and `B` is simply written with - `t1[9]`. In neither case does `B` depend upon `E`. - - The SEIP field behavior is designed to allow a higher privilege layer to - mimic external interrupts cleanly, without losing any real external - interrupts. The behavior of the CSR instructions is slightly modified - from regular CSR accesses as a result. - ==== - - If supervisor mode is implemented, bits `mip`.STIP and `mie`.STIE are - the interrupt-pending and interrupt-enable bits for supervisor-level - timer interrupts. STIP is writable in `mip`, and may be written by - M-mode software to deliver timer interrupts to S-mode. - - If supervisor mode is implemented, bits `mip`.SSIP and `mie`.SSIE are - the interrupt-pending and interrupt-enable bits for supervisor-level - software interrupts. SSIP is writable in `mip` and may also be set to 1 - by a platform-specific interrupt controller. - - <%- if ext?(:Sscofpmf) -%> - Bits `mip`.LCOFIP and `mie`.LCOFIE - are the interrupt-pending and interrupt-enable bits for local counter-overflow - interrupts. - LCOFIP is read-write in `mip` and reflects the occurrence of a local - counter-overflow overflow interrupt request resulting from any of the - `mhpmevent__n__`.OF bits being set. - If the Sscofpmf extension is not implemented, `mip`.LCOFIP and `mie`.LCOFIE are - read-only zeros. - <%- end -%> - - Multiple simultaneous interrupts destined for M-mode are handled in the - following decreasing priority order: MEI, MSI, MTI, SEI, SSI, STI, LCOFI. - - [NOTE] - ==== - The machine-level interrupt fixed-priority ordering rules were developed - with the following rationale. - - Interrupts for higher privilege modes must be serviced before interrupts - for lower privilege modes to support preemption. - - The platform-specific machine-level interrupt sources in bits 16 and - above have platform-specific priority, but are typically chosen to have - the highest service priority to support very fast local vectored - interrupts. - - External interrupts are handled before internal (timer/software) - interrupts as external interrupts are usually generated by devices that - might require low interrupt service times. - - Software interrupts are handled before internal timer interrupts, - because internal timer interrupts are usually intended for time slicing, - where time precision is less important, whereas software interrupts are - used for inter-processor messaging. Software interrupts can be avoided - when high-precision timing is required, or high-precision timer - interrupts can be routed via a different interrupt path. Software - interrupts are located in the lowest four bits of `mip` as these are - often written by software, and this position allows the use of a single - CSR instruction with a five-bit immediate. - ==== - - Restricted views of the `mip` and `mie` registers appear as the `sip` - and `sie` registers for supervisor level. If an interrupt is delegated - to S-mode by setting a bit in the `mideleg` register, it becomes visible - in the `sip` register and is maskable using the `sie` register. - Otherwise, the corresponding bits in `sip` and `sie` are read-only zero. + definedBy: Sm + description: "$copy: mip.yaml#/mip/description" fields: SSIE: location: 1 diff --git a/arch/csr/mip.yaml b/arch/csr/mip.yaml index 7d646bbe1..303635ba0 100644 --- a/arch/csr/mip.yaml +++ b/arch/csr/mip.yaml @@ -4,7 +4,146 @@ mip: long_name: Machine Interrupt Pending address: 0x344 priv_mode: M - description: Machine Interrupt Pending bits + description: | + The `mip` register is an MXLEN-bit read/write register containing + information on pending interrupts, while `mie` is the corresponding + MXLEN-bit read/write register containing interrupt enable bits. + Interrupt cause number _i_ (as reported in CSR `mcause`) + corresponds with bit _i_ in both `mip` and + `mie`. Bits 15:0 are allocated to standard interrupt causes only, while + bits 16 and above are designated for platform use. + + NOTE: Interrupts designated for platform use may be designated for custom use + at the platform's discretion. + + An interrupt _i_ will trap to M-mode (causing the privilege mode to + change to M-mode) if all of the following are true: + + * either the current privilege mode is M and the MIE bit in the `mstatus` register is + set, or the current privilege mode has less privilege than M-mode; + * bit _i_ is set in both `mip` and `mie` + * if register `mideleg` exists, bit _i_ is not set in `mideleg`. + + These conditions for an interrupt trap to occur must be evaluated in a + bounded amount of time from when an interrupt becomes, or ceases to be, + pending in `mip`, and must also be evaluated immediately following the + execution of an __x__RET instruction or an explicit write to a CSR on + which these interrupt trap conditions expressly depend (including `mip`, + `mie`, `mstatus`, and `mideleg`). + + Interrupts to M-mode take priority over any interrupts to lower + privilege modes. + + Each individual bit in register `mip` may be writable or may be + read-only. When bit _i_ in `mip` is writable, a pending interrupt _i_ + can be cleared by writing 0 to this bit. If interrupt _i_ can become + pending but bit _i_ in `mip` is read-only, the implementation must + provide some other mechanism for clearing the pending interrupt. + + A bit in `mie` must be writable if the corresponding interrupt can ever + become pending. Bits of `mie` that are not writable must be read-only + zero. + + [NOTE] + ==== + The machine-level interrupt registers handle a few root interrupt + sources which are assigned a fixed service priority for simplicity, + while separate external interrupt controllers can implement a more + complex prioritization scheme over a much larger set of interrupts that + are then muxed into the machine-level interrupt sources. + + ''' + + The non-maskable interrupt is not made visible via the `mip` register as + its presence is implicitly known when executing the NMI trap handler. + ==== + + If supervisor mode is implemented, bits `mip`.SEIP and `mie`.SEIE are + the interrupt-pending and interrupt-enable bits for supervisor-level + external interrupts. SEIP is writable in `mip`, and may be written by + M-mode software to indicate to S-mode that an external interrupt is + pending. Additionally, the platform-level interrupt controller may + generate supervisor-level external interrupts. Supervisor-level external + interrupts are made pending based on the logical-OR of the + software-writable SEIP bit and the signal from the external interrupt + controller. When `mip` is read with a CSR instruction, the value of the + SEIP bit returned in the `rd` destination register is the logical-OR of + the software-writable bit and the interrupt signal from the interrupt + controller, but the signal from the interrupt controller is not used to + calculate the value written to SEIP. Only the software-writable SEIP bit + participates in the read-modify-write sequence of a CSRRS or CSRRC + instruction. + + [NOTE] + ==== + For example, if we name the software-writable SEIP bit `B` and the + signal from the external interrupt controller `E`, then if + `csrrs t0, mip, t1` is executed, `t0[9]` is written with `B || E`, then + `B` is written with `B || t1[9]`. If `csrrw t0, mip, t1` is executed, + then `t0[9]` is written with `B || E`, and `B` is simply written with + `t1[9]`. In neither case does `B` depend upon `E`. + + The SEIP field behavior is designed to allow a higher privilege layer to + mimic external interrupts cleanly, without losing any real external + interrupts. The behavior of the CSR instructions is slightly modified + from regular CSR accesses as a result. + ==== + + If supervisor mode is implemented, bits `mip`.STIP and `mie`.STIE are + the interrupt-pending and interrupt-enable bits for supervisor-level + timer interrupts. STIP is writable in `mip`, and may be written by + M-mode software to deliver timer interrupts to S-mode. + + If supervisor mode is implemented, bits `mip`.SSIP and `mie`.SSIE are + the interrupt-pending and interrupt-enable bits for supervisor-level + software interrupts. SSIP is writable in `mip` and may also be set to 1 + by a platform-specific interrupt controller. + + <% if ext?(:Sscofpmf) -%> + bits `mip`.LCOFIP and `mie`.LCOFIE + are the interrupt-pending and interrupt-enable bits for local counter-overflow + interrupts. + LCOFIP is read-write in `mip` and reflects the occurrence of a local + counter-overflow overflow interrupt request resulting from any of the + `mhpmevent__n__`.OF bits being set. + <% end -%> + + Multiple simultaneous interrupts destined for M-mode are handled in the + following decreasing priority order: MEI, MSI, MTI, SEI, SSI, STI, LCOFI. + + [NOTE] + ==== + The machine-level interrupt fixed-priority ordering rules were developed + with the following rationale. + + Interrupts for higher privilege modes must be serviced before interrupts + for lower privilege modes to support preemption. + + The platform-specific machine-level interrupt sources in bits 16 and + above have platform-specific priority, but are typically chosen to have + the highest service priority to support very fast local vectored + interrupts. + + External interrupts are handled before internal (timer/software) + interrupts as external interrupts are usually generated by devices that + might require low interrupt service times. + + Software interrupts are handled before internal timer interrupts, + because internal timer interrupts are usually intended for time slicing, + where time precision is less important, whereas software interrupts are + used for inter-processor messaging. Software interrupts can be avoided + when high-precision timing is required, or high-precision timer + interrupts can be routed via a different interrupt path. Software + interrupts are located in the lowest four bits of `mip` as these are + often written by software, and this position allows the use of a single + CSR instruction with a five-bit immediate. + ==== + + Restricted views of the `mip` and `mie` registers appear as the `sip` + and `sie` registers for supervisor level. If an interrupt is delegated + to S-mode by setting a bit in the `mideleg` register, it becomes visible + in the `sip` register and is maskable using the `sie` register. + Otherwise, the corresponding bits in `sip` and `sie` are read-only zero. length: MXLEN definedBy: Sm fields: diff --git a/backends/certificate_doc/templates/certificate.adoc.erb b/backends/certificate_doc/templates/certificate.adoc.erb index 443133da4..cf1c1215c 100644 --- a/backends/certificate_doc/templates/certificate.adoc.erb +++ b/backends/certificate_doc/templates/certificate.adoc.erb @@ -559,7 +559,7 @@ h| Privilege Mode | <%= csr.priv_mode %> |=== -==== <%= cert_model.name %> Format +==== Format <% unless csr.dynamic_length?(arch_def) || csr.implemented_fields(arch_def).any? { |f| f.dynamic_location?(arch_def) } -%> <%# CSR has a known static length, so there is only one format to display -%> .<%= csr.name %> format @@ -587,7 +587,7 @@ This CSR format changes dynamically with XLEN. <% end # unless dynamic length -%> -==== <%= cert_model.name %> Field Summary +==== Field Summary // use @ as a separator since IDL code can contain | [%autowidth,separator=@,float="center",align="center",cols="^,<,<,<",options="header",role="stretch"] @@ -627,7 +627,7 @@ a@ <%- end -%> |=== -==== <%= cert_model.name %> Fields +==== Fields <%- if csr.implemented_fields(arch_def).empty? -%> This CSR has no fields. However, it must still exist (not cause an `Illegal Instruction` trap) and always return zero on a read. diff --git a/lib/test/test_yaml_loader.rb b/lib/test/test_yaml_loader.rb index 6471eea95..d502643f2 100644 --- a/lib/test/test_yaml_loader.rb +++ b/lib/test/test_yaml_loader.rb @@ -225,7 +225,6 @@ def test_refs_in_the_same_document obj3: target2: Should disappear $ref: "#/$defs/target2" - YAML f = Tempfile.new("yml") @@ -274,7 +273,7 @@ def test_refs_in_the_different_document assert_equal({ "a" => "hash" }, doc["obj3"]) end - def test_inheritss_in_the_same_document + def test_inherits_in_the_same_document yaml = <<~YAML $defs: target1: A string @@ -291,7 +290,6 @@ def test_inheritss_in_the_same_document obj3: a: Should take precedence $inherits: "#/$defs/target2" - YAML f = Tempfile.new("yml") @@ -304,7 +302,7 @@ def test_inheritss_in_the_same_document assert_equal({ "a" => "Should take precedence" }, doc["obj3"]) end - def test_inheritss_in_the_different_document + def test_inherits_in_the_different_document yaml1 = <<~YAML $defs: target1: A string @@ -340,7 +338,7 @@ def test_inheritss_in_the_different_document assert_equal({ "a" => "Should take precedence" }, doc["obj3"]) end - def test_multi_inheritss_in_the_same_document + def test_multi_inherits_in_the_same_document yaml = <<~YAML $defs: target1: @@ -363,7 +361,7 @@ def test_multi_inheritss_in_the_same_document assert_equal({ "a" => "hash", "b" => "nice" }, doc["obj1"]) end - def test_that_invalid_inheritss_raise + def test_that_invalid_inherits_raise yaml = <<~YAML $defs: target1: @@ -403,4 +401,92 @@ def test_that_invalid_refs_raise assert_raises(YamlLoader::DereferenceError) { YamlLoader.load(f.path) } end + def test_copy_in_the_same_document + yaml = <<~YAML + $defs: + target1: A string + target2: + a: hash + target3: Another string + + obj1: + target10: abc + target11: "$copy: #/$defs/target1" + target12: def + target13: "$copy: #/$defs/target3" + + YAML + + f = Tempfile.new("yml") + f.write(yaml) + f.flush + + doc = YamlLoader.load(f.path) + assert_equal({ + "target10" => "abc", + "target11" => "A string", + "target12" => "def", + "target13" => "Another string" + }, doc["obj1"]) + end + + def test_copy_in_the_different_document + yaml1 = <<~YAML + $defs: + target1: A string + target2: + a: hash + target3: Another string + YAML + + f1 = Tempfile.new("yml") + f1.write(yaml1) + f1.flush + f1_path = Pathname.new(f1.path) + + yaml2 = <<~YAML + obj1: + target10: abc + target11: "$copy: #{f1_path.basename}#/$defs/target1" + target12: def + target13: "$copy: #{f1_path.basename}#/$defs/target3" + YAML + + f2 = Tempfile.new("yml") + f2.write(yaml2) + f2.flush + + doc = YamlLoader.load(f2.path) + assert_equal({ + "target10" => "abc", + "target11" => "A string", + "target12" => "def", + "target13" => "Another string" + }, doc["obj1"]) + end + + def test_multi_inherits_in_the_same_document + yaml = <<~YAML + $defs: + target1: + b: nice + target2: + a: hash + + obj1: + $inherits: + - "#/$defs/target1" + - "#/$defs/target2" + + YAML + + f = Tempfile.new("yml") + f.write(yaml) + f.flush + + doc = YamlLoader.load(f.path) + assert_equal({ "a" => "hash", "b" => "nice" }, doc["obj1"]) + end + + end diff --git a/lib/yaml_loader.rb b/lib/yaml_loader.rb index f080234c4..ac3e67a0c 100644 --- a/lib/yaml_loader.rb +++ b/lib/yaml_loader.rb @@ -17,70 +17,21 @@ def self.expand(filename, obj, yaml_opts = {}) new_obj = if obj.keys.include?("$ref") # according JSON Reference, all keys except $ref are ignored - relative_path = obj["$ref"].split("#")[0] - if relative_path.empty? - # this is a reference in the same document - obj_doc = YAML.load_file(filename, **yaml_opts) - obj_path = obj["$ref"].split("#")[1].split("/")[1..] - target_obj = obj_doc.dig(*obj_path) - raise DereferenceError, "#{obj['$ref']} cannot be found" if target_obj.nil? - - ref = expand(filename, target_obj, yaml_opts) - if ref.nil? - raise DereferenceError, "JSON Path #{obj['$ref'].split('#')[1]} does not exist in #{filename}" - end - - ref - else - target_filename = File.realpath(File.join(filename.dirname, relative_path)) - - obj_doc = YamlLoader.load(target_filename, yaml_opts) - obj_path = obj["$ref"].split("#")[1].split("/")[1..] - target_obj = obj_doc.dig(*obj_path) - raise "#{obj['$ref']} cannot be found" if target_obj.nil? - - ref = expand(target_filename, target_obj, yaml_opts) - if ref.nil? - raise DereferenceError, "JSON Path #{obj['$ref'].split('#')[1]} does not exist in #{target_filename}" - end - - ref - end + ref_target = obj["$ref"] + self.get_ref_target_obj(filename, ref_target, yaml_opts) elsif obj.keys.include?("$inherits") # we handle the inherits key first so that any override will take priority inherits = obj["$inherits"] raise ArgumentError, "Missing reference after $inherits (did you forget to put a relative reference in quotes?)" if inherits.nil? + + # Create array of targets. If provided only one target as a string, convert it to an array of 1 element. inherits_targets = inherits.is_a?(String) ? [inherits] : inherits new_obj = {} inherits_targets.each do |inherits_target| - relative_path = inherits_target.split("#")[0] - target_obj = - if relative_path.empty? - YAML.load_file(filename, **yaml_opts) - else - target_filename = File.realpath(File.join(filename.dirname, relative_path)) - - unless File.exist?(target_filename) - raise DereferenceError, "While locating $inherits in #{filename}, #{target_filename} does not exist" - end + target_obj = get_inherits_target_obj(filename, inherits_target, yaml_opts) - YamlLoader.load(target_filename, yaml_opts) - end - - inherits_target_suffix = inherits_target.split("#/")[1] - inherits_target_path = inherits_target_suffix.split("/") - begin - target_obj = target_obj.dig(*inherits_target_path) - rescue TypeError => e - if e.message == "no implicit conversion of String into Integer" - warn "$inherits: \"#{inherits_target}\" found in file #{filename} references an Array but needs to reference a Hash" - end - raise e - end - - raise DereferenceError, "JSON Path #{inherits_target_suffix} in file #{filename} does not exist in #{relative_path}" if target_obj.nil? raise ArgumentError, "$inherits: \"#{inherits_target}\" in file #{filename} references a #{target_obj.class} but needs to reference a Hash" unless target_obj.is_a?(Hash) target_obj = expand(filename, target_obj, yaml_opts) @@ -116,13 +67,16 @@ def self.expand(filename, obj, yaml_opts = {}) end final_obj - else - obj_keys = obj.keys - obj_keys.each do |key| - value = obj[key] - - obj[key] = expand(filename, value, yaml_opts) + # Go through each hash entry. + obj.each do |key, value| + obj[key] = + if value.is_a?(String) && value.start_with?("$copy:") + copy_target = value.delete_prefix("$copy:").lstrip + self.get_ref_target_obj(filename, copy_target, yaml_opts) + else + expand(filename, value, yaml_opts) + end end obj end @@ -138,6 +92,77 @@ def self.expand(filename, obj, yaml_opts = {}) new_obj end + # @param filename [String,Pathname] path to the YAML file + # @param ref_target [String] + # @param yaml_opts [Hash] options to pass to YAML.load_file + # @return [Object] + def self.get_ref_target_obj(filename, ref_target, yaml_opts) + relative_path = ref_target.split("#")[0] + if relative_path.empty? + # this is a reference in the same document + obj_doc = YAML.load_file(filename, **yaml_opts) + obj_path = ref_target.split("#")[1].split("/")[1..] + target_obj = obj_doc.dig(*obj_path) + raise DereferenceError, "$ref: #{obj_path} cannot be found in file #{filename}" if target_obj.nil? + + ref = expand(filename, target_obj, yaml_opts) + if ref.nil? + raise DereferenceError, "JSON Path #{obj_path} does not exist in file #{filename}" + end + + ref + else + target_filename = File.realpath(File.join(filename.dirname, relative_path)) + + obj_doc = YamlLoader.load(target_filename, yaml_opts) + obj_path = ref_target.split("#")[1].split("/")[1..] + target_obj = obj_doc.dig(*obj_path) + raise "$ref: #{obj_path} cannot be found in file #{target_filename}" if target_obj.nil? + + ref = expand(target_filename, target_obj, yaml_opts) + if ref.nil? + raise DereferenceError, "JSON Path #{obj_path} does not exist in file #{target_filename}" + end + + ref + end + end + + # @param filename [String,Pathname] path to the YAML file + # @param inherits_target [String] + # @param yaml_opts [Hash] options to pass to YAML.load_file + # @return [Object] + def self.get_inherits_target_obj(filename, inherits_target, yaml_opts) + relative_path = inherits_target.split("#")[0] + target_obj = + if relative_path.empty? + YAML.load_file(filename, **yaml_opts) + else + target_filename = File.realpath(File.join(filename.dirname, relative_path)) + + unless File.exist?(target_filename) + raise DereferenceError, "While locating $inherits in #{filename}, #{target_filename} does not exist" + end + + YamlLoader.load(target_filename, yaml_opts) + end + + inherits_target_suffix = inherits_target.split("#/")[1] + inherits_target_path = inherits_target_suffix.split("/") + begin + target_obj = target_obj.dig(*inherits_target_path) + rescue TypeError => e + if e.message == "no implicit conversion of String into Integer" + warn "$inherits: \"#{inherits_target}\" found in file #{filename} references an Array but needs to reference a Hash" + end + raise e + end + + raise DereferenceError, "JSON Path #{inherits_target_suffix} in file #{filename} does not exist in #{relative_path}" if target_obj.nil? + + target_obj + end + # load a YAML file and expand any $ref/$inherits references # @param filename [String,Pathname] path to the YAML file # @param yaml_opts [Hash] options to pass to YAML.load_file From 7eb44b7d82a0af2e8719fb90526b8559b3ae3046 Mon Sep 17 00:00:00 2001 From: James Ball Date: Sat, 16 Nov 2024 16:40:49 -0800 Subject: [PATCH 2/4] Fixed bad merge of schema change from main into this branch. --- lib/test/test_yaml_loader.rb | 131 ++++++++++++++++++----------------- lib/yaml_loader.rb | 19 ++++- 2 files changed, 83 insertions(+), 67 deletions(-) diff --git a/lib/test/test_yaml_loader.rb b/lib/test/test_yaml_loader.rb index d502643f2..ee3be540b 100644 --- a/lib/test/test_yaml_loader.rb +++ b/lib/test/test_yaml_loader.rb @@ -208,70 +208,73 @@ def test_that_double_inherits_doesnt_delete_keys assert_equal({ "key1" => "value1", "key2" => "value2", "key3" => "value3_new", "key4" => "value4", "key5" => "value5", "key6" => "value6_new" }, doc["child"]) end - def test_refs_in_the_same_document - yaml = <<~YAML - $defs: - target1: A string - target2: - a: hash - - obj1: - $ref: "#/$defs/target2" - - obj2: - $ref: "#/$defs/target2" - target2: Should disappear - - obj3: - target2: Should disappear - $ref: "#/$defs/target2" - YAML - - f = Tempfile.new("yml") - f.write(yaml) - f.flush - - doc = YamlLoader.load(f.path) - assert_equal({ "a" => "hash" }, doc["obj1"]) - assert_equal({ "a" => "hash" }, doc["obj2"]) - assert_equal({ "a" => "hash" }, doc["obj3"]) - end - - def test_refs_in_the_different_document - yaml1 = <<~YAML - $defs: - target1: A string - target2: - a: hash - YAML - - f1 = Tempfile.new("yml") - f1.write(yaml1) - f1.flush - f1_path = Pathname.new(f1.path) - - yaml2 = <<~YAML - obj1: - $ref: "#{f1_path.basename}#/$defs/target2" - - obj2: - $ref: "#{f1_path.basename}#/$defs/target2" - target2: Should disappear - - obj3: - target2: Should disappear - $ref: "#{f1_path.basename}#/$defs/target2" - YAML - - f2 = Tempfile.new("yml") - f2.write(yaml2) - f2.flush - - doc = YamlLoader.load(f2.path) - assert_equal({ "a" => "hash" }, doc["obj1"]) - assert_equal({ "a" => "hash" }, doc["obj2"]) - assert_equal({ "a" => "hash" }, doc["obj3"]) - end +# Removed by @dhower-qc in https://github.com/riscv-software-src/riscv-unified-db/commit/7d10d70f122de6263ce7907dee8daeddc0a0af40 +# Waiting for response in https://github.com/riscv-software-src/riscv-unified-db/pull/271 +# +# def test_refs_in_the_same_document +# yaml = <<~YAML +# $defs: +# target1: A string +# target2: +# a: hash +# +# obj1: +# $ref: "#/$defs/target2" +# +# obj2: +# $ref: "#/$defs/target2" +# target2: Should disappear +# +# obj3: +# target2: Should disappear +# $ref: "#/$defs/target2" +# YAML +# +# f = Tempfile.new("yml") +# f.write(yaml) +# f.flush +# +# doc = YamlLoader.load(f.path) +# assert_equal({ "a" => "hash" }, doc["obj1"]) +# assert_equal({ "a" => "hash" }, doc["obj2"]) +# assert_equal({ "a" => "hash" }, doc["obj3"]) +# end +# +# def test_refs_in_the_different_document +# yaml1 = <<~YAML +# $defs: +# target1: A string +# target2: +# a: hash +# YAML +# +# f1 = Tempfile.new("yml") +# f1.write(yaml1) +# f1.flush +# f1_path = Pathname.new(f1.path) +# +# yaml2 = <<~YAML +# obj1: +# $ref: "#{f1_path.basename}#/$defs/target2" +# +# obj2: +# $ref: "#{f1_path.basename}#/$defs/target2" +# target2: Should disappear +# +# obj3: +# target2: Should disappear +# $ref: "#{f1_path.basename}#/$defs/target2" +# YAML +# +# f2 = Tempfile.new("yml") +# f2.write(yaml2) +# f2.flush +# +# doc = YamlLoader.load(f2.path) +# assert_equal({ "a" => "hash" }, doc["obj1"]) +# assert_equal({ "a" => "hash" }, doc["obj2"]) +# assert_equal({ "a" => "hash" }, doc["obj3"]) +# end def test_inherits_in_the_same_document yaml = <<~YAML diff --git a/lib/yaml_loader.rb b/lib/yaml_loader.rb index c545d369e..6a20ab155 100644 --- a/lib/yaml_loader.rb +++ b/lib/yaml_loader.rb @@ -63,8 +63,6 @@ def self.expand(filename, obj, yaml_opts = {}) # we handle the inherits key first so that any override will take priority inherits = obj["$inherits"] raise ArgumentError, "Missing reference after $inherits (did you forget to put a relative reference in quotes?)" if inherits.nil? - - # Create array of targets. If provided only one target as a string, convert it to an array of 1 element. inherits_targets = inherits.is_a?(String) ? [inherits] : inherits new_obj = {} @@ -87,7 +85,22 @@ def self.expand(filename, obj, yaml_opts = {}) unless File.exist?(target_filename) raise DereferenceError, "While locating $inherits in #{filename}, #{target_filename} does not exist" end - + + YamlLoader.load(target_filename, yaml_opts) + end + + inherits_target_suffix = inherits_target.split("#/")[1] + inherits_target_path = inherits_target_suffix.split("/") + begin + target_obj = target_obj.dig(*inherits_target_path) + rescue TypeError => e + if e.message == "no implicit conversion of String into Integer" + warn "$inherits: \"#{inherits_target}\" found in file #{filename} references an Array but needs to reference a Hash" + end + raise e + end + + raise DereferenceError, "JSON Path #{inherits_target_suffix} in file #{filename} does not exist in #{relative_path}" if target_obj.nil? raise ArgumentError, "$inherits: \"#{inherits_target}\" in file #{filename} references a #{target_obj.class} but needs to reference a Hash" unless target_obj.is_a?(Hash) target_obj = expand(filename, target_obj, yaml_opts) From b0f163f4059b43f05351555d37da735848ba60d1 Mon Sep 17 00:00:00 2001 From: James Ball Date: Sun, 17 Nov 2024 07:23:13 -0800 Subject: [PATCH 3/4] Fixed typo in mip (spurious character after name:) and fixed $copy path from mie to mip description to accomodate new schema change. --- arch/csr/mie.yaml | 2 +- arch/csr/mip.yaml | 2 +- lib/test/test_yaml_loader.rb | 68 ------------------------------------ lib/yaml_loader.rb | 20 +++++------ 4 files changed, 12 insertions(+), 80 deletions(-) diff --git a/arch/csr/mie.yaml b/arch/csr/mie.yaml index b4a6e8a98..f053fa484 100644 --- a/arch/csr/mie.yaml +++ b/arch/csr/mie.yaml @@ -8,7 +8,7 @@ address: 0x304 priv_mode: M length: MXLEN definedBy: Sm -description: "$copy: mip.yaml#/mip/description" +description: "$copy: mip.yaml#/description" fields: SSIE: location: 1 diff --git a/arch/csr/mip.yaml b/arch/csr/mip.yaml index 15b2367f7..9ff6f7c4d 100644 --- a/arch/csr/mip.yaml +++ b/arch/csr/mip.yaml @@ -2,7 +2,7 @@ $schema: "csr_schema.json#" kind: csr -name: mip> +name: mip long_name: Machine Interrupt Pending address: 0x344 priv_mode: M diff --git a/lib/test/test_yaml_loader.rb b/lib/test/test_yaml_loader.rb index ee3be540b..98f23a271 100644 --- a/lib/test/test_yaml_loader.rb +++ b/lib/test/test_yaml_loader.rb @@ -208,74 +208,6 @@ def test_that_double_inherits_doesnt_delete_keys assert_equal({ "key1" => "value1", "key2" => "value2", "key3" => "value3_new", "key4" => "value4", "key5" => "value5", "key6" => "value6_new" }, doc["child"]) end -# Removed by @dhower-qc in https://github.com/riscv-software-src/riscv-unified-db/commit/7d10d70f122de6263ce7907dee8daeddc0a0af40 -# Waiting for response in https://github.com/riscv-software-src/riscv-unified-db/pull/271 -# -# def test_refs_in_the_same_document -# yaml = <<~YAML -# $defs: -# target1: A string -# target2: -# a: hash -# -# obj1: -# $ref: "#/$defs/target2" -# -# obj2: -# $ref: "#/$defs/target2" -# target2: Should disappear -# -# obj3: -# target2: Should disappear -# $ref: "#/$defs/target2" -# YAML -# -# f = Tempfile.new("yml") -# f.write(yaml) -# f.flush -# -# doc = YamlLoader.load(f.path) -# assert_equal({ "a" => "hash" }, doc["obj1"]) -# assert_equal({ "a" => "hash" }, doc["obj2"]) -# assert_equal({ "a" => "hash" }, doc["obj3"]) -# end -# -# def test_refs_in_the_different_document -# yaml1 = <<~YAML -# $defs: -# target1: A string -# target2: -# a: hash -# YAML -# -# f1 = Tempfile.new("yml") -# f1.write(yaml1) -# f1.flush -# f1_path = Pathname.new(f1.path) -# -# yaml2 = <<~YAML -# obj1: -# $ref: "#{f1_path.basename}#/$defs/target2" -# -# obj2: -# $ref: "#{f1_path.basename}#/$defs/target2" -# target2: Should disappear -# -# obj3: -# target2: Should disappear -# $ref: "#{f1_path.basename}#/$defs/target2" -# YAML -# -# f2 = Tempfile.new("yml") -# f2.write(yaml2) -# f2.flush -# -# doc = YamlLoader.load(f2.path) -# assert_equal({ "a" => "hash" }, doc["obj1"]) -# assert_equal({ "a" => "hash" }, doc["obj2"]) -# assert_equal({ "a" => "hash" }, doc["obj3"]) -# end - def test_inherits_in_the_same_document yaml = <<~YAML $defs: diff --git a/lib/yaml_loader.rb b/lib/yaml_loader.rb index 6a20ab155..56079d88d 100644 --- a/lib/yaml_loader.rb +++ b/lib/yaml_loader.rb @@ -142,7 +142,7 @@ def self.expand(filename, obj, yaml_opts = {}) obj[key] = if value.is_a?(String) && value.start_with?("$copy:") copy_target = value.delete_prefix("$copy:").lstrip - self.get_ref_target_obj(filename, copy_target, yaml_opts) + self.get_copy_target_obj(filename, copy_target, yaml_opts) else expand(filename, value, yaml_opts) end @@ -162,21 +162,21 @@ def self.expand(filename, obj, yaml_opts = {}) end # @param filename [String,Pathname] path to the YAML file - # @param ref_target [String] + # @param copy_target [String] # @param yaml_opts [Hash] options to pass to YAML.load_file # @return [Object] - def self.get_ref_target_obj(filename, ref_target, yaml_opts) - relative_path = ref_target.split("#")[0] + def self.get_copy_target_obj(filename, copy_target, yaml_opts) + relative_path = copy_target.split("#")[0] if relative_path.empty? # this is a reference in the same document obj_doc = YAML.load_file(filename, **yaml_opts) - obj_path = ref_target.split("#")[1].split("/")[1..] + obj_path = copy_target.split("#")[1].split("/")[1..] target_obj = obj_doc.dig(*obj_path) - raise DereferenceError, "$ref: #{obj_path} cannot be found in file #{filename}" if target_obj.nil? + raise DereferenceError, "$copy: #{obj_path} referenced to same file cannot be found in file #{filename}" if target_obj.nil? ref = expand(filename, target_obj, yaml_opts) if ref.nil? - raise DereferenceError, "JSON Path #{obj_path} does not exist in file #{filename}" + raise DereferenceError, "$copy: JSON Path #{obj_path} referenced to same file does not exist in file #{filename}" end ref @@ -184,13 +184,13 @@ def self.get_ref_target_obj(filename, ref_target, yaml_opts) target_filename = File.realpath(File.join(filename.dirname, relative_path)) obj_doc = YamlLoader.load(target_filename, yaml_opts) - obj_path = ref_target.split("#")[1].split("/")[1..] + obj_path = copy_target.split("#")[1].split("/")[1..] target_obj = obj_doc.dig(*obj_path) - raise "$ref: #{obj_path} cannot be found in file #{target_filename}" if target_obj.nil? + raise DereferenceError, "$copy: #{obj_path} referenced from file #{filename} cannot be found in file #{target_filename}" if target_obj.nil? ref = expand(target_filename, target_obj, yaml_opts) if ref.nil? - raise DereferenceError, "JSON Path #{obj_path} does not exist in file #{target_filename}" + raise DereferenceError, "$copy: JSON Path #{obj_path} referenced from file #{filename} does not exist in file #{target_filename}" end ref From efe87b35a020b5aaeb432710995788e6b2b33293 Mon Sep 17 00:00:00 2001 From: James Ball Date: Tue, 19 Nov 2024 11:56:17 -0800 Subject: [PATCH 4/4] Changed $copy to be a key and only work with specific strings (right now just the description). --- arch/csr/mie.yaml | 3 ++- arch/csr/mip.yaml | 19 +++++++++++++------ lib/test/test_yaml_loader.rb | 12 ++++++++---- lib/yaml_loader.rb | 25 ++++++++++++------------- schemas/csr_schema.json | 19 +++++++++++++++++-- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/arch/csr/mie.yaml b/arch/csr/mie.yaml index f053fa484..2a51b862d 100644 --- a/arch/csr/mie.yaml +++ b/arch/csr/mie.yaml @@ -8,7 +8,8 @@ address: 0x304 priv_mode: M length: MXLEN definedBy: Sm -description: "$copy: mip.yaml#/description" +description: + $copy: "mip.yaml#/description" fields: SSIE: location: 1 diff --git a/arch/csr/mip.yaml b/arch/csr/mip.yaml index 9ff6f7c4d..b1e77a627 100644 --- a/arch/csr/mip.yaml +++ b/arch/csr/mip.yaml @@ -6,13 +6,20 @@ name: mip long_name: Machine Interrupt Pending address: 0x344 priv_mode: M + +# Description is shared with mie CSR (it copies it from here). description: | - The `mip` register is an MXLEN-bit read/write register containing - information on pending interrupts, while `mie` is the corresponding - MXLEN-bit read/write register containing interrupt enable bits. - Interrupt cause number _i_ (as reported in CSR `mcause`) - corresponds with bit _i_ in both `mip` and - `mie`. Bits 15:0 are allocated to standard interrupt causes only, while + The `mie` and `mip` CSRs are MXLEN-bit read/write registers used when + the CLINT or PLIC interrupt controllers are present. + Note that the CLINT refers to an interrupt controller + used by some RISC-V implementations but isn't a ratified + RISC-V International standard. + + The `mip` CSR contains information on pending interrupts, while `mie` is the corresponding + CSR containing interrupt enable bits. + Interrupt cause number _i_ (as reported in the `mcause` CSR) + corresponds to bit _i_ in both `mip` and `mie`. + Bits 15:0 are allocated to standard interrupt causes only, while bits 16 and above are designated for platform use. NOTE: Interrupts designated for platform use may be designated for custom use diff --git a/lib/test/test_yaml_loader.rb b/lib/test/test_yaml_loader.rb index 98f23a271..562d93d41 100644 --- a/lib/test/test_yaml_loader.rb +++ b/lib/test/test_yaml_loader.rb @@ -346,9 +346,11 @@ def test_copy_in_the_same_document obj1: target10: abc - target11: "$copy: #/$defs/target1" + target11: + $copy: "#/$defs/target1" target12: def - target13: "$copy: #/$defs/target3" + target13: + $copy: "#/$defs/target3" YAML @@ -382,9 +384,11 @@ def test_copy_in_the_different_document yaml2 = <<~YAML obj1: target10: abc - target11: "$copy: #{f1_path.basename}#/$defs/target1" + target11: + $copy: "#{f1_path.basename}#/$defs/target1" target12: def - target13: "$copy: #{f1_path.basename}#/$defs/target3" + target13: + $copy: "#{f1_path.basename}#/$defs/target3" YAML f2 = Tempfile.new("yml") diff --git a/lib/yaml_loader.rb b/lib/yaml_loader.rb index 56079d88d..06f994865 100644 --- a/lib/yaml_loader.rb +++ b/lib/yaml_loader.rb @@ -136,28 +136,27 @@ def self.expand(filename, obj, yaml_opts = {}) end final_obj + elsif obj.keys.include?("$copy") + self.get_copy_target_obj(filename, obj["$copy"], yaml_opts) else # Go through each hash entry. obj.each do |key, value| - obj[key] = - if value.is_a?(String) && value.start_with?("$copy:") - copy_target = value.delete_prefix("$copy:").lstrip - self.get_copy_target_obj(filename, copy_target, yaml_opts) - else - expand(filename, value, yaml_opts) - end + obj[key] = expand(filename, value, yaml_opts) end obj end - obj_keys = new_obj.keys - if obj_keys.include? "$remove" - remove_keys = obj["$remove"].is_a?(Array) ? obj["$remove"] : [obj["$remove"]] - remove_keys.each do |key| - new_obj.delete(key) + if new_obj.is_a?(Hash) + obj_keys = new_obj.keys + if obj_keys.include? "$remove" + remove_keys = obj["$remove"].is_a?(Array) ? obj["$remove"] : [obj["$remove"]] + remove_keys.each do |key| + new_obj.delete(key) + end end + new_obj.delete("$remove") end - new_obj.delete("$remove") + new_obj end diff --git a/schemas/csr_schema.json b/schemas/csr_schema.json index ae0e26d18..16ef9686c 100644 --- a/schemas/csr_schema.json +++ b/schemas/csr_schema.json @@ -193,8 +193,23 @@ "description": "Descriptive name for the CSR" }, "description": { - "type": "string", - "description": "A full Asciidoc description of the CSR, intended to be used as documentation." + "oneOf": [ + { + "type": "string", + "description": "A full Asciidoc description of the CSR, intended to be used as documentation." + }, + { + "type": "object", + "description": "A full Asciidoc description of the CSR, intended to be used as documentation.", + "properties": { + "$copy" : { + "type": "string", + "format": "uri-reference" + } + }, + "additionalProperties": false + } + ] }, "definedBy": { "$ref": "schema_defs.json#/$defs/requires_entry",