├── .cargo └── config.toml ├── .editorconfig ├── .github └── workflows │ └── cargo-check.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.md ├── bors.toml ├── dts └── valheim.dts.template ├── pictures ├── rustsbi-booting.jpg └── xv6-booting.png ├── profiler ├── flamegraph.pl ├── profile.sh └── stackcollapse.pl ├── rust-toolchain ├── valheim-asm ├── Cargo.toml └── src │ ├── asm │ ├── encode16.rs │ ├── encode32.rs │ ├── mod.rs │ ├── riscv.rs │ ├── test.rs │ └── traits.rs │ ├── isa │ ├── data.rs │ ├── decode.rs │ ├── decode16.rs │ ├── mod.rs │ ├── rv32.rs │ ├── rv64.rs │ ├── typed.rs │ ├── untyped.rs │ └── untyped16.rs │ └── lib.rs ├── valheim-cli ├── Cargo.toml └── src │ └── main.rs ├── valheim-core ├── Cargo.toml └── src │ ├── cpu │ ├── bus.rs │ ├── csr.rs │ ├── data.rs │ ├── execute.rs │ ├── irq.rs │ ├── mmu.rs │ ├── mod.rs │ └── regs.rs │ ├── debug │ ├── mod.rs │ └── trace.rs │ ├── device │ ├── clint.rs │ ├── mod.rs │ ├── ns16550a.rs │ ├── plic.rs │ └── virtio.rs │ ├── dtb │ └── mod.rs │ ├── interp │ ├── mod.rs │ └── naive.rs │ ├── lib.rs │ ├── machine │ └── mod.rs │ └── memory │ └── mod.rs ├── valheim-testing ├── disabled-tests.txt ├── enabled-tests.txt └── sync-tests.sh └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --release --package xtask --" 3 | start = "run --release --package valheim-cli --" 4 | make = "build --release --package valheim-cli --" 5 | make-debug = "build --package valheim-cli --" 6 | run-riscv-tests = "xtask test" 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = false 7 | max_line_length = 120 8 | tab_width = 2 9 | ij_continuation_indent_size = 8 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = false 13 | ij_smart_tabs = false 14 | ij_visual_guides = none 15 | ij_wrap_on_typing = false 16 | 17 | [*.rs] 18 | indent_size = 2 19 | max_line_length = 100 20 | tab_width = 2 21 | ij_continuation_indent_size = 2 22 | ij_rust_align_multiline_chained_methods = false 23 | ij_rust_align_multiline_parameters = true 24 | ij_rust_align_multiline_parameters_in_calls = true 25 | ij_rust_align_ret_type = true 26 | ij_rust_align_type_params = false 27 | ij_rust_align_where_bounds = true 28 | ij_rust_align_where_clause = false 29 | ij_rust_allow_one_line_match = false 30 | ij_rust_block_comment_at_first_column = false 31 | ij_rust_indent_where_clause = true 32 | ij_rust_keep_blank_lines_in_code = 2 33 | ij_rust_keep_blank_lines_in_declarations = 2 34 | ij_rust_keep_indents_on_empty_lines = false 35 | ij_rust_keep_line_breaks = true 36 | ij_rust_line_comment_add_space = true 37 | ij_rust_line_comment_at_first_column = false 38 | ij_rust_min_number_of_blanks_between_items = 0 39 | ij_rust_preserve_punctuation = false 40 | ij_rust_spaces_around_assoc_type_binding = false 41 | 42 | [.editorconfig] 43 | ij_editorconfig_align_group_field_declarations = false 44 | ij_editorconfig_space_after_colon = false 45 | ij_editorconfig_space_after_comma = true 46 | ij_editorconfig_space_before_colon = false 47 | ij_editorconfig_space_before_comma = false 48 | ij_editorconfig_spaces_around_assignment_operators = true 49 | 50 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.qrc,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] 51 | ij_xml_align_attributes = true 52 | ij_xml_align_text = false 53 | ij_xml_attribute_wrap = normal 54 | ij_xml_block_comment_add_space = false 55 | ij_xml_block_comment_at_first_column = true 56 | ij_xml_keep_blank_lines = 2 57 | ij_xml_keep_indents_on_empty_lines = false 58 | ij_xml_keep_line_breaks = true 59 | ij_xml_keep_line_breaks_in_text = true 60 | ij_xml_keep_whitespaces = false 61 | ij_xml_keep_whitespaces_around_cdata = preserve 62 | ij_xml_keep_whitespaces_inside_cdata = false 63 | ij_xml_line_comment_at_first_column = true 64 | ij_xml_space_after_tag_name = false 65 | ij_xml_space_around_equals_in_attribute = false 66 | ij_xml_space_inside_empty_tag = false 67 | ij_xml_text_wrap = normal 68 | 69 | [{*.apinotes,*.yaml,*.yml,.clang-format,.clang-tidy,_clang-format}] 70 | indent_size = 2 71 | ij_yaml_align_values_properties = do_not_align 72 | ij_yaml_autoinsert_sequence_marker = true 73 | ij_yaml_block_mapping_on_new_line = false 74 | ij_yaml_indent_sequence_value = true 75 | ij_yaml_keep_indents_on_empty_lines = false 76 | ij_yaml_keep_line_breaks = true 77 | ij_yaml_sequence_on_new_line = false 78 | ij_yaml_space_before_colon = false 79 | ij_yaml_spaces_within_braces = true 80 | ij_yaml_spaces_within_brackets = true 81 | 82 | [{*.bash,*.sh,*.zsh}] 83 | indent_size = 2 84 | tab_width = 2 85 | ij_shell_binary_ops_start_line = false 86 | ij_shell_keep_column_alignment_padding = false 87 | ij_shell_minify_program = false 88 | ij_shell_redirect_followed_by_space = false 89 | ij_shell_switch_cases_indented = false 90 | ij_shell_use_unix_line_separator = true 91 | 92 | [{*.c,*.c++,*.cc,*.cp,*.cpp,*.cu,*.cuh,*.cxx,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.m,*.mm,*.pch,*.tcc,*.tpp}] 93 | ij_c_add_brief_tag = false 94 | ij_c_add_getter_prefix = true 95 | ij_c_add_setter_prefix = true 96 | ij_c_align_dictionary_pair_values = false 97 | ij_c_align_group_field_declarations = false 98 | ij_c_align_init_list_in_columns = true 99 | ij_c_align_multiline_array_initializer_expression = true 100 | ij_c_align_multiline_assignment = true 101 | ij_c_align_multiline_binary_operation = true 102 | ij_c_align_multiline_chained_methods = false 103 | ij_c_align_multiline_for = true 104 | ij_c_align_multiline_ternary_operation = true 105 | ij_c_array_initializer_comma_on_next_line = false 106 | ij_c_array_initializer_new_line_after_left_brace = false 107 | ij_c_array_initializer_right_brace_on_new_line = false 108 | ij_c_array_initializer_wrap = normal 109 | ij_c_assignment_wrap = off 110 | ij_c_binary_operation_sign_on_next_line = false 111 | ij_c_binary_operation_wrap = normal 112 | ij_c_blank_lines_after_class_header = 1 113 | ij_c_blank_lines_after_imports = 1 114 | ij_c_blank_lines_around_class = 1 115 | ij_c_blank_lines_around_field = 0 116 | ij_c_blank_lines_around_field_in_interface = 0 117 | ij_c_blank_lines_around_method = 1 118 | ij_c_blank_lines_around_method_in_interface = 1 119 | ij_c_blank_lines_around_namespace = 0 120 | ij_c_blank_lines_around_properties_in_declaration = 0 121 | ij_c_blank_lines_around_properties_in_interface = 0 122 | ij_c_blank_lines_before_imports = 1 123 | ij_c_blank_lines_before_method_body = 0 124 | ij_c_block_brace_placement = end_of_line 125 | ij_c_block_brace_style = end_of_line 126 | ij_c_block_comment_at_first_column = true 127 | ij_c_catch_on_new_line = false 128 | ij_c_class_brace_style = end_of_line 129 | ij_c_class_constructor_init_list_align_multiline = true 130 | ij_c_class_constructor_init_list_comma_on_next_line = false 131 | ij_c_class_constructor_init_list_new_line_after_colon = never 132 | ij_c_class_constructor_init_list_new_line_before_colon = if_long 133 | ij_c_class_constructor_init_list_wrap = normal 134 | ij_c_copy_is_deep = false 135 | ij_c_create_interface_for_categories = true 136 | ij_c_declare_generated_methods = true 137 | ij_c_description_include_member_names = true 138 | ij_c_discharged_short_ternary_operator = false 139 | ij_c_do_not_add_breaks = false 140 | ij_c_do_while_brace_force = never 141 | ij_c_else_on_new_line = false 142 | ij_c_enum_constants_comma_on_next_line = false 143 | ij_c_enum_constants_wrap = on_every_item 144 | ij_c_for_brace_force = never 145 | ij_c_for_statement_new_line_after_left_paren = false 146 | ij_c_for_statement_right_paren_on_new_line = false 147 | ij_c_for_statement_wrap = off 148 | ij_c_function_brace_placement = end_of_line 149 | ij_c_function_call_arguments_align_multiline = false 150 | ij_c_function_call_arguments_align_multiline_pars = false 151 | ij_c_function_call_arguments_comma_on_next_line = false 152 | ij_c_function_call_arguments_new_line_after_lpar = false 153 | ij_c_function_call_arguments_new_line_before_rpar = false 154 | ij_c_function_call_arguments_wrap = normal 155 | ij_c_function_non_top_after_return_type_wrap = normal 156 | ij_c_function_parameters_align_multiline = true 157 | ij_c_function_parameters_align_multiline_pars = false 158 | ij_c_function_parameters_comma_on_next_line = false 159 | ij_c_function_parameters_new_line_after_lpar = false 160 | ij_c_function_parameters_new_line_before_rpar = false 161 | ij_c_function_parameters_wrap = normal 162 | ij_c_function_top_after_return_type_wrap = normal 163 | ij_c_generate_additional_eq_operators = true 164 | ij_c_generate_additional_rel_operators = true 165 | ij_c_generate_class_constructor = true 166 | ij_c_generate_comparison_operators_use_std_tie = false 167 | ij_c_generate_instance_variables_for_properties = ask 168 | ij_c_generate_operators_as_members = true 169 | ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} 170 | ij_c_if_brace_force = never 171 | ij_c_in_line_short_ternary_operator = true 172 | ij_c_indent_block_comment = true 173 | ij_c_indent_c_struct_members = 2 174 | ij_c_indent_case_from_switch = true 175 | ij_c_indent_class_members = 2 176 | ij_c_indent_directive_as_code = false 177 | ij_c_indent_implementation_members = 0 178 | ij_c_indent_inside_code_block = 2 179 | ij_c_indent_interface_members = 0 180 | ij_c_indent_interface_members_except_ivars_block = false 181 | ij_c_indent_namespace_members = 2 182 | ij_c_indent_preprocessor_directive = 0 183 | ij_c_indent_visibility_keywords = 0 184 | ij_c_insert_override = true 185 | ij_c_insert_virtual_with_override = false 186 | ij_c_introduce_auto_vars = false 187 | ij_c_introduce_const_params = false 188 | ij_c_introduce_const_vars = false 189 | ij_c_introduce_generate_property = false 190 | ij_c_introduce_generate_synthesize = true 191 | ij_c_introduce_globals_to_header = true 192 | ij_c_introduce_prop_to_private_category = false 193 | ij_c_introduce_static_consts = true 194 | ij_c_introduce_use_ns_types = false 195 | ij_c_ivars_prefix = _ 196 | ij_c_keep_blank_lines_before_end = 2 197 | ij_c_keep_blank_lines_before_right_brace = 2 198 | ij_c_keep_blank_lines_in_code = 2 199 | ij_c_keep_blank_lines_in_declarations = 2 200 | ij_c_keep_case_expressions_in_one_line = true 201 | ij_c_keep_control_statement_in_one_line = true 202 | ij_c_keep_directive_at_first_column = true 203 | ij_c_keep_first_column_comment = true 204 | ij_c_keep_line_breaks = true 205 | ij_c_keep_nested_namespaces_in_one_line = false 206 | ij_c_keep_simple_blocks_in_one_line = true 207 | ij_c_keep_simple_methods_in_one_line = true 208 | ij_c_keep_structures_in_one_line = true 209 | ij_c_lambda_capture_list_align_multiline = false 210 | ij_c_lambda_capture_list_align_multiline_bracket = false 211 | ij_c_lambda_capture_list_comma_on_next_line = false 212 | ij_c_lambda_capture_list_new_line_after_lbracket = false 213 | ij_c_lambda_capture_list_new_line_before_rbracket = false 214 | ij_c_lambda_capture_list_wrap = off 215 | ij_c_line_comment_add_space = false 216 | ij_c_line_comment_at_first_column = true 217 | ij_c_method_brace_placement = end_of_line 218 | ij_c_method_call_arguments_align_by_colons = true 219 | ij_c_method_call_arguments_align_multiline = false 220 | ij_c_method_call_arguments_special_dictionary_pairs_treatment = true 221 | ij_c_method_call_arguments_wrap = off 222 | ij_c_method_call_chain_wrap = off 223 | ij_c_method_parameters_align_by_colons = true 224 | ij_c_method_parameters_align_multiline = false 225 | ij_c_method_parameters_wrap = off 226 | ij_c_namespace_brace_placement = end_of_line 227 | ij_c_parentheses_expression_new_line_after_left_paren = false 228 | ij_c_parentheses_expression_right_paren_on_new_line = false 229 | ij_c_place_assignment_sign_on_next_line = false 230 | ij_c_property_nonatomic = true 231 | ij_c_put_ivars_to_implementation = true 232 | ij_c_refactor_compatibility_aliases_and_classes = true 233 | ij_c_refactor_properties_and_ivars = true 234 | ij_c_release_style = ivar 235 | ij_c_retain_object_parameters_in_constructor = true 236 | ij_c_semicolon_after_method_signature = false 237 | ij_c_shift_operation_align_multiline = true 238 | ij_c_shift_operation_wrap = normal 239 | ij_c_show_non_virtual_functions = false 240 | ij_c_space_after_colon = true 241 | ij_c_space_after_colon_in_foreach = true 242 | ij_c_space_after_colon_in_selector = false 243 | ij_c_space_after_comma = true 244 | ij_c_space_after_cup_in_blocks = false 245 | ij_c_space_after_dictionary_literal_colon = true 246 | ij_c_space_after_for_semicolon = true 247 | ij_c_space_after_init_list_colon = true 248 | ij_c_space_after_method_parameter_type_parentheses = false 249 | ij_c_space_after_method_return_type_parentheses = false 250 | ij_c_space_after_pointer_in_declaration = false 251 | ij_c_space_after_quest = true 252 | ij_c_space_after_reference_in_declaration = false 253 | ij_c_space_after_reference_in_rvalue = false 254 | ij_c_space_after_structures_rbrace = true 255 | ij_c_space_after_superclass_colon = true 256 | ij_c_space_after_type_cast = true 257 | ij_c_space_after_visibility_sign_in_method_declaration = true 258 | ij_c_space_before_autorelease_pool_lbrace = true 259 | ij_c_space_before_catch_keyword = true 260 | ij_c_space_before_catch_left_brace = true 261 | ij_c_space_before_catch_parentheses = true 262 | ij_c_space_before_category_parentheses = true 263 | ij_c_space_before_chained_send_message = true 264 | ij_c_space_before_class_left_brace = true 265 | ij_c_space_before_colon = true 266 | ij_c_space_before_colon_in_foreach = false 267 | ij_c_space_before_comma = false 268 | ij_c_space_before_dictionary_literal_colon = false 269 | ij_c_space_before_do_left_brace = true 270 | ij_c_space_before_else_keyword = true 271 | ij_c_space_before_else_left_brace = true 272 | ij_c_space_before_for_left_brace = true 273 | ij_c_space_before_for_parentheses = true 274 | ij_c_space_before_for_semicolon = false 275 | ij_c_space_before_if_left_brace = true 276 | ij_c_space_before_if_parentheses = true 277 | ij_c_space_before_init_list = false 278 | ij_c_space_before_init_list_colon = true 279 | ij_c_space_before_method_call_parentheses = false 280 | ij_c_space_before_method_left_brace = true 281 | ij_c_space_before_method_parentheses = false 282 | ij_c_space_before_namespace_lbrace = true 283 | ij_c_space_before_pointer_in_declaration = true 284 | ij_c_space_before_property_attributes_parentheses = false 285 | ij_c_space_before_protocols_brackets = true 286 | ij_c_space_before_quest = true 287 | ij_c_space_before_reference_in_declaration = true 288 | ij_c_space_before_superclass_colon = true 289 | ij_c_space_before_switch_left_brace = true 290 | ij_c_space_before_switch_parentheses = true 291 | ij_c_space_before_template_call_lt = false 292 | ij_c_space_before_template_declaration_lt = true 293 | ij_c_space_before_try_left_brace = true 294 | ij_c_space_before_while_keyword = true 295 | ij_c_space_before_while_left_brace = true 296 | ij_c_space_before_while_parentheses = true 297 | ij_c_space_between_adjacent_brackets = false 298 | ij_c_space_between_operator_and_punctuator = false 299 | ij_c_space_within_empty_array_initializer_braces = false 300 | ij_c_spaces_around_additive_operators = true 301 | ij_c_spaces_around_assignment_operators = true 302 | ij_c_spaces_around_bitwise_operators = true 303 | ij_c_spaces_around_equality_operators = true 304 | ij_c_spaces_around_lambda_arrow = true 305 | ij_c_spaces_around_logical_operators = true 306 | ij_c_spaces_around_multiplicative_operators = true 307 | ij_c_spaces_around_pm_operators = false 308 | ij_c_spaces_around_relational_operators = true 309 | ij_c_spaces_around_shift_operators = true 310 | ij_c_spaces_around_unary_operator = false 311 | ij_c_spaces_within_array_initializer_braces = false 312 | ij_c_spaces_within_braces = true 313 | ij_c_spaces_within_brackets = false 314 | ij_c_spaces_within_cast_parentheses = false 315 | ij_c_spaces_within_catch_parentheses = false 316 | ij_c_spaces_within_category_parentheses = false 317 | ij_c_spaces_within_empty_braces = false 318 | ij_c_spaces_within_empty_function_call_parentheses = false 319 | ij_c_spaces_within_empty_function_declaration_parentheses = false 320 | ij_c_spaces_within_empty_lambda_capture_list_bracket = false 321 | ij_c_spaces_within_empty_template_call_ltgt = false 322 | ij_c_spaces_within_empty_template_declaration_ltgt = false 323 | ij_c_spaces_within_for_parentheses = false 324 | ij_c_spaces_within_function_call_parentheses = false 325 | ij_c_spaces_within_function_declaration_parentheses = false 326 | ij_c_spaces_within_if_parentheses = false 327 | ij_c_spaces_within_lambda_capture_list_bracket = false 328 | ij_c_spaces_within_method_parameter_type_parentheses = false 329 | ij_c_spaces_within_method_return_type_parentheses = false 330 | ij_c_spaces_within_parentheses = false 331 | ij_c_spaces_within_property_attributes_parentheses = false 332 | ij_c_spaces_within_protocols_brackets = false 333 | ij_c_spaces_within_send_message_brackets = false 334 | ij_c_spaces_within_switch_parentheses = false 335 | ij_c_spaces_within_template_call_ltgt = false 336 | ij_c_spaces_within_template_declaration_ltgt = false 337 | ij_c_spaces_within_template_double_gt = true 338 | ij_c_spaces_within_while_parentheses = false 339 | ij_c_special_else_if_treatment = true 340 | ij_c_superclass_list_after_colon = never 341 | ij_c_superclass_list_align_multiline = true 342 | ij_c_superclass_list_before_colon = if_long 343 | ij_c_superclass_list_comma_on_next_line = false 344 | ij_c_superclass_list_wrap = on_every_item 345 | ij_c_tag_prefix_of_block_comment = at 346 | ij_c_tag_prefix_of_line_comment = back_slash 347 | ij_c_template_call_arguments_align_multiline = false 348 | ij_c_template_call_arguments_align_multiline_pars = false 349 | ij_c_template_call_arguments_comma_on_next_line = false 350 | ij_c_template_call_arguments_new_line_after_lt = false 351 | ij_c_template_call_arguments_new_line_before_gt = false 352 | ij_c_template_call_arguments_wrap = off 353 | ij_c_template_declaration_function_body_indent = false 354 | ij_c_template_declaration_function_wrap = split_into_lines 355 | ij_c_template_declaration_struct_body_indent = false 356 | ij_c_template_declaration_struct_wrap = split_into_lines 357 | ij_c_template_parameters_align_multiline = false 358 | ij_c_template_parameters_align_multiline_pars = false 359 | ij_c_template_parameters_comma_on_next_line = false 360 | ij_c_template_parameters_new_line_after_lt = false 361 | ij_c_template_parameters_new_line_before_gt = false 362 | ij_c_template_parameters_wrap = off 363 | ij_c_ternary_operation_signs_on_next_line = true 364 | ij_c_ternary_operation_wrap = normal 365 | ij_c_type_qualifiers_placement = before 366 | ij_c_use_modern_casts = true 367 | ij_c_use_setters_in_constructor = true 368 | ij_c_while_brace_force = never 369 | ij_c_while_on_new_line = false 370 | ij_c_wrap_property_declaration = off 371 | 372 | [{*.cmake,CMakeLists.txt}] 373 | ij_cmake_align_multiline_parameters_in_calls = false 374 | ij_cmake_force_commands_case = 2 375 | ij_cmake_keep_blank_lines_in_code = 2 376 | ij_cmake_space_before_for_parentheses = true 377 | ij_cmake_space_before_if_parentheses = true 378 | ij_cmake_space_before_method_call_parentheses = false 379 | ij_cmake_space_before_method_parentheses = false 380 | ij_cmake_space_before_while_parentheses = true 381 | ij_cmake_spaces_within_for_parentheses = false 382 | ij_cmake_spaces_within_if_parentheses = false 383 | ij_cmake_spaces_within_method_call_parentheses = false 384 | ij_cmake_spaces_within_method_parentheses = false 385 | ij_cmake_spaces_within_while_parentheses = false 386 | 387 | [{*.har,*.json}] 388 | indent_size = 2 389 | ij_json_keep_blank_lines_in_code = 0 390 | ij_json_keep_indents_on_empty_lines = false 391 | ij_json_keep_line_breaks = true 392 | ij_json_space_after_colon = true 393 | ij_json_space_after_comma = true 394 | ij_json_space_before_colon = true 395 | ij_json_space_before_comma = false 396 | ij_json_spaces_within_braces = false 397 | ij_json_spaces_within_brackets = false 398 | ij_json_wrap_long_lines = false 399 | 400 | [{*.htm,*.html,*.sht,*.shtm,*.shtml}] 401 | ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 402 | ij_html_align_attributes = true 403 | ij_html_align_text = false 404 | ij_html_attribute_wrap = normal 405 | ij_html_block_comment_add_space = false 406 | ij_html_block_comment_at_first_column = true 407 | ij_html_do_not_align_children_of_min_lines = 0 408 | ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p 409 | ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot 410 | ij_html_enforce_quotes = false 411 | ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var 412 | ij_html_keep_blank_lines = 2 413 | ij_html_keep_indents_on_empty_lines = false 414 | ij_html_keep_line_breaks = true 415 | ij_html_keep_line_breaks_in_text = true 416 | ij_html_keep_whitespaces = false 417 | ij_html_keep_whitespaces_inside = span,pre,textarea 418 | ij_html_line_comment_at_first_column = true 419 | ij_html_new_line_after_last_attribute = never 420 | ij_html_new_line_before_first_attribute = never 421 | ij_html_quote_style = double 422 | ij_html_remove_new_line_before_tags = br 423 | ij_html_space_after_tag_name = false 424 | ij_html_space_around_equality_in_attribute = false 425 | ij_html_space_inside_empty_tag = false 426 | ij_html_text_wrap = normal 427 | 428 | [{*.markdown,*.md}] 429 | ij_markdown_force_one_space_after_blockquote_symbol = true 430 | ij_markdown_force_one_space_after_header_symbol = true 431 | ij_markdown_force_one_space_after_list_bullet = true 432 | ij_markdown_force_one_space_between_words = true 433 | ij_markdown_keep_indents_on_empty_lines = false 434 | ij_markdown_max_lines_around_block_elements = 1 435 | ij_markdown_max_lines_around_header = 1 436 | ij_markdown_max_lines_between_paragraphs = 1 437 | ij_markdown_min_lines_around_block_elements = 1 438 | ij_markdown_min_lines_around_header = 1 439 | ij_markdown_min_lines_between_paragraphs = 1 440 | 441 | [{*.py,*.pyw}] 442 | ij_python_align_collections_and_comprehensions = true 443 | ij_python_align_multiline_imports = true 444 | ij_python_align_multiline_parameters = true 445 | ij_python_align_multiline_parameters_in_calls = true 446 | ij_python_blank_line_at_file_end = true 447 | ij_python_blank_lines_after_imports = 1 448 | ij_python_blank_lines_after_local_imports = 0 449 | ij_python_blank_lines_around_class = 1 450 | ij_python_blank_lines_around_method = 1 451 | ij_python_blank_lines_around_top_level_classes_functions = 2 452 | ij_python_blank_lines_before_first_method = 0 453 | ij_python_call_parameters_new_line_after_left_paren = false 454 | ij_python_call_parameters_right_paren_on_new_line = false 455 | ij_python_call_parameters_wrap = normal 456 | ij_python_dict_alignment = 0 457 | ij_python_dict_new_line_after_left_brace = false 458 | ij_python_dict_new_line_before_right_brace = false 459 | ij_python_dict_wrapping = 1 460 | ij_python_from_import_new_line_after_left_parenthesis = false 461 | ij_python_from_import_new_line_before_right_parenthesis = false 462 | ij_python_from_import_parentheses_force_if_multiline = false 463 | ij_python_from_import_trailing_comma_if_multiline = false 464 | ij_python_from_import_wrapping = 1 465 | ij_python_hang_closing_brackets = false 466 | ij_python_keep_blank_lines_in_code = 1 467 | ij_python_keep_blank_lines_in_declarations = 1 468 | ij_python_keep_indents_on_empty_lines = false 469 | ij_python_keep_line_breaks = true 470 | ij_python_method_parameters_new_line_after_left_paren = false 471 | ij_python_method_parameters_right_paren_on_new_line = false 472 | ij_python_method_parameters_wrap = normal 473 | ij_python_new_line_after_colon = false 474 | ij_python_new_line_after_colon_multi_clause = true 475 | ij_python_optimize_imports_always_split_from_imports = false 476 | ij_python_optimize_imports_case_insensitive_order = false 477 | ij_python_optimize_imports_join_from_imports_with_same_source = false 478 | ij_python_optimize_imports_sort_by_type_first = true 479 | ij_python_optimize_imports_sort_imports = true 480 | ij_python_optimize_imports_sort_names_in_from_imports = false 481 | ij_python_space_after_comma = true 482 | ij_python_space_after_number_sign = true 483 | ij_python_space_after_py_colon = true 484 | ij_python_space_before_backslash = true 485 | ij_python_space_before_comma = false 486 | ij_python_space_before_for_semicolon = false 487 | ij_python_space_before_lbracket = false 488 | ij_python_space_before_method_call_parentheses = false 489 | ij_python_space_before_method_parentheses = false 490 | ij_python_space_before_number_sign = true 491 | ij_python_space_before_py_colon = false 492 | ij_python_space_within_empty_method_call_parentheses = false 493 | ij_python_space_within_empty_method_parentheses = false 494 | ij_python_spaces_around_additive_operators = true 495 | ij_python_spaces_around_assignment_operators = true 496 | ij_python_spaces_around_bitwise_operators = true 497 | ij_python_spaces_around_eq_in_keyword_argument = false 498 | ij_python_spaces_around_eq_in_named_parameter = false 499 | ij_python_spaces_around_equality_operators = true 500 | ij_python_spaces_around_multiplicative_operators = true 501 | ij_python_spaces_around_power_operator = true 502 | ij_python_spaces_around_relational_operators = true 503 | ij_python_spaces_around_shift_operators = true 504 | ij_python_spaces_within_braces = false 505 | ij_python_spaces_within_brackets = false 506 | ij_python_spaces_within_method_call_parentheses = false 507 | ij_python_spaces_within_method_parentheses = false 508 | ij_python_use_continuation_indent_for_arguments = false 509 | ij_python_use_continuation_indent_for_collection_and_comprehensions = false 510 | ij_python_use_continuation_indent_for_parameters = true 511 | ij_python_wrap_long_lines = false 512 | 513 | [{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] 514 | ij_toml_keep_indents_on_empty_lines = false 515 | -------------------------------------------------------------------------------- /.github/workflows/cargo-check.yml: -------------------------------------------------------------------------------- 1 | name : test 2 | on: 3 | push: 4 | branches: [main, staging, trying] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | cargo-check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: nightly 17 | - name: Install RISC-V GNU Toolchain 18 | run: | 19 | wget -O toolchain.tar.gz https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.03.09/riscv64-elf-ubuntu-20.04-nightly-2022.03.09-nightly.tar.gz 20 | mkdir toolchain 21 | tar zxvf toolchain.tar.gz -C toolchain 22 | echo "$PWD/toolchain/riscv/bin" >> $GITHUB_PATH 23 | - name: Install device-tree-compiler 24 | run: sudo apt update && sudo apt install device-tree-compiler 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: run-riscv-tests 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /.idea/ 3 | *.o 4 | *.bin 5 | *.elf 6 | valheim-trace*.txt 7 | flamegraph.svg 8 | out.stacks 9 | out.collapsed 10 | tests/ 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "valheim-testing/riscv-tests"] 2 | path = valheim-testing/riscv-tests 3 | url = git@github.com:riscv-software-src/riscv-tests.git 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "bytebuffer" 30 | version = "2.0.1" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "5891af766cd0e9623eb94a3e89697e3cb0bfa661e54adea9cd55d4b377f22ae9" 33 | dependencies = [ 34 | "byteorder", 35 | ] 36 | 37 | [[package]] 38 | name = "byteorder" 39 | version = "1.4.3" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "3.1.8" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" 48 | dependencies = [ 49 | "atty", 50 | "bitflags", 51 | "clap_derive", 52 | "indexmap", 53 | "lazy_static", 54 | "os_str_bytes", 55 | "strsim", 56 | "termcolor", 57 | "textwrap", 58 | ] 59 | 60 | [[package]] 61 | name = "clap_derive" 62 | version = "3.1.7" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" 65 | dependencies = [ 66 | "heck", 67 | "proc-macro-error", 68 | "proc-macro2", 69 | "quote", 70 | "syn", 71 | ] 72 | 73 | [[package]] 74 | name = "convert_case" 75 | version = "0.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 78 | 79 | [[package]] 80 | name = "derive_more" 81 | version = "0.99.17" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 84 | dependencies = [ 85 | "convert_case", 86 | "proc-macro2", 87 | "quote", 88 | "rustc_version", 89 | "syn", 90 | ] 91 | 92 | [[package]] 93 | name = "hashbrown" 94 | version = "0.11.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 97 | 98 | [[package]] 99 | name = "heck" 100 | version = "0.4.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 103 | 104 | [[package]] 105 | name = "hermit-abi" 106 | version = "0.1.19" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 109 | dependencies = [ 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "indexmap" 115 | version = "1.8.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" 118 | dependencies = [ 119 | "autocfg", 120 | "hashbrown", 121 | ] 122 | 123 | [[package]] 124 | name = "lazy_static" 125 | version = "1.4.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 128 | 129 | [[package]] 130 | name = "libc" 131 | version = "0.2.121" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" 134 | 135 | [[package]] 136 | name = "memchr" 137 | version = "2.4.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 140 | 141 | [[package]] 142 | name = "memmap2" 143 | version = "0.5.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" 146 | dependencies = [ 147 | "libc", 148 | ] 149 | 150 | [[package]] 151 | name = "modular-bitfield" 152 | version = "0.11.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" 155 | dependencies = [ 156 | "modular-bitfield-impl", 157 | "static_assertions", 158 | ] 159 | 160 | [[package]] 161 | name = "modular-bitfield-impl" 162 | version = "0.11.2" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "os_str_bytes" 173 | version = "6.0.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 176 | dependencies = [ 177 | "memchr", 178 | ] 179 | 180 | [[package]] 181 | name = "proc-macro-error" 182 | version = "1.0.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 185 | dependencies = [ 186 | "proc-macro-error-attr", 187 | "proc-macro2", 188 | "quote", 189 | "syn", 190 | "version_check", 191 | ] 192 | 193 | [[package]] 194 | name = "proc-macro-error-attr" 195 | version = "1.0.4" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 198 | dependencies = [ 199 | "proc-macro2", 200 | "quote", 201 | "version_check", 202 | ] 203 | 204 | [[package]] 205 | name = "proc-macro2" 206 | version = "1.0.36" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 209 | dependencies = [ 210 | "unicode-xid", 211 | ] 212 | 213 | [[package]] 214 | name = "quote" 215 | version = "1.0.17" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" 218 | dependencies = [ 219 | "proc-macro2", 220 | ] 221 | 222 | [[package]] 223 | name = "rustc_apfloat" 224 | version = "0.1.3" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "0ded734d8859ecf917c1f743bca773c8fb9793e5fb6ab61a96e3d75d62a50512" 227 | dependencies = [ 228 | "bitflags", 229 | ] 230 | 231 | [[package]] 232 | name = "rustc_version" 233 | version = "0.4.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 236 | dependencies = [ 237 | "semver", 238 | ] 239 | 240 | [[package]] 241 | name = "semver" 242 | version = "1.0.7" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" 245 | 246 | [[package]] 247 | name = "static_assertions" 248 | version = "1.1.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 251 | 252 | [[package]] 253 | name = "strsim" 254 | version = "0.10.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 257 | 258 | [[package]] 259 | name = "syn" 260 | version = "1.0.90" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" 263 | dependencies = [ 264 | "proc-macro2", 265 | "quote", 266 | "unicode-xid", 267 | ] 268 | 269 | [[package]] 270 | name = "termcolor" 271 | version = "1.1.3" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 274 | dependencies = [ 275 | "winapi-util", 276 | ] 277 | 278 | [[package]] 279 | name = "textwrap" 280 | version = "0.15.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 283 | 284 | [[package]] 285 | name = "unicode-xid" 286 | version = "0.2.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 289 | 290 | [[package]] 291 | name = "valheim-asm" 292 | version = "0.2.0" 293 | dependencies = [ 294 | "bytebuffer", 295 | "modular-bitfield", 296 | ] 297 | 298 | [[package]] 299 | name = "valheim-cli" 300 | version = "0.2.0" 301 | dependencies = [ 302 | "clap", 303 | "valheim-core", 304 | ] 305 | 306 | [[package]] 307 | name = "valheim-core" 308 | version = "0.2.0" 309 | dependencies = [ 310 | "derive_more", 311 | "memmap2", 312 | "rustc_apfloat", 313 | "valheim-asm", 314 | ] 315 | 316 | [[package]] 317 | name = "version_check" 318 | version = "0.9.4" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 321 | 322 | [[package]] 323 | name = "winapi" 324 | version = "0.3.9" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 327 | dependencies = [ 328 | "winapi-i686-pc-windows-gnu", 329 | "winapi-x86_64-pc-windows-gnu", 330 | ] 331 | 332 | [[package]] 333 | name = "winapi-i686-pc-windows-gnu" 334 | version = "0.4.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 337 | 338 | [[package]] 339 | name = "winapi-util" 340 | version = "0.1.5" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 343 | dependencies = [ 344 | "winapi", 345 | ] 346 | 347 | [[package]] 348 | name = "winapi-x86_64-pc-windows-gnu" 349 | version = "0.4.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 352 | 353 | [[package]] 354 | name = "xshell" 355 | version = "0.2.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "4884417669886d3abff14feec797179526ade713f212e54ec08b19bc6bdc86aa" 358 | dependencies = [ 359 | "xshell-macros", 360 | ] 361 | 362 | [[package]] 363 | name = "xshell-macros" 364 | version = "0.2.1" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "37d92065701c3611323f96eac5475b995421fc7eb2bcba1336cdd80b9b2fb68f" 367 | 368 | [[package]] 369 | name = "xtask" 370 | version = "0.1.0" 371 | dependencies = [ 372 | "xshell", 373 | ] 374 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "valheim-asm", 4 | "valheim-core", 5 | "valheim-cli", 6 | "xtask" 7 | ] 8 | default-members = ["xtask"] 9 | 10 | [profile.release] 11 | debug = true 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # valheim 2 | Learning purpose riscv64 (RV64GC) emulator. 3 | This project is built for [一生一芯](https://ysyx.org/) as a reference implementation. 4 | 5 | ### Highlights 6 | - [Type-safe instructions](valheim-asm/src/isa/typed.rs) which makes the decoding [less error-prone](valheim-asm/src/isa/decode.rs). 7 | - Full emulation trace (registers, memory, etc.) like persistent data structures, which is useful for debugging the real hardware. 8 | - [MISA]() = `RV64ACDFIMSU` 9 | - RV64G (IMAFD_Zicsr_Zifencei) instruction set 10 | - RVC extension 11 | - Supervisor mode extension 12 | - User mode extension 13 | 14 | ### Amazing Moments 15 | 16 | #### Running [openEuler Linux for RISC-V](https://github.com/openeuler-mirror/RISC-V) 17 | 18 | Currently, the `init` program cannot use serial device as console (I am trying hard to find the cause). 19 | But the kernel was indeed __successfully booted and initialized__. 20 | 21 | I was thinking that should I just give up trying to fix the console problem, instead, 22 | go and implement a `virtio-net` device and start the `sshd` service when booted? 23 | It's not that hard comparingly, and it's closer to the real-world use case. 24 | 25 | [![asciicast](https://asciinema.org/a/481577.svg)](https://asciinema.org/a/481577) 26 | 27 | #### Running [RustSBI-QEMU with its test kernel](https://github.com/rustsbi/rustsbi-qemu) 28 | With the following command: 29 | ```shell 30 | cargo run --release -- --kernel tests/test-kernel.bin --bios tests/rustsbi-qemu.bin 31 | ``` 32 | 33 | ![rustsbi-booting](./pictures/rustsbi-booting.jpg) 34 | 35 | #### Running [xv6 for RISC-V](https://github.com/mit-pdos/xv6-riscv) 36 | With the following command: 37 | ```shell 38 | $(CROSS)objcopy -O binary xv6/kernel xv6/kernel.bin 39 | cargo run --release -- --kernel xv6/kernel.bin --disk xv6/fs.img 40 | ``` 41 | 42 | ![xv6-booting](./pictures/xv6-booting.png) 43 | 44 | 57 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = ["cargo-check"] 2 | commit_title = "merge: ${PR_REFS}" 3 | delete_merged_branches = true 4 | -------------------------------------------------------------------------------- /dts/valheim.dts.template: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | 3 | / { 4 | #address-cells = <0x02>; 5 | #size-cells = <0x02>; 6 | compatible = "riscv-virtio"; 7 | model = "riscv-virtio,qemu"; 8 | 9 | chosen { 10 | bootargs = "${VALHEIM_BOOTARGS}"; 11 | stdout-path = "/uart@10000000"; 12 | }; 13 | 14 | uart@10000000 { 15 | interrupts = <0xa>; 16 | interrupt-parent = <0x03>; 17 | clock-frequency = <0x384000>; 18 | reg = <0x0 0x10000000 0x0 0x100>; 19 | compatible = "ns16550a"; 20 | }; 21 | 22 | virtio_mmio@10001000 { 23 | interrupts = <0x01>; 24 | interrupt-parent = <0x03>; 25 | reg = <0x0 0x10001000 0x0 0x1000>; 26 | compatible = "virtio,mmio"; 27 | }; 28 | 29 | cpus { 30 | #address-cells = <0x01>; 31 | #size-cells = <0x00>; 32 | timebase-frequency = <0x989680>; 33 | 34 | cpu-map { 35 | cluster0 { 36 | core0 { 37 | cpu = <0x01>; 38 | }; 39 | }; 40 | }; 41 | 42 | cpu@0 { 43 | phandle = <0x01>; 44 | device_type = "cpu"; 45 | reg = <0x00>; 46 | status = "okay"; 47 | compatible = "riscv"; 48 | riscv,isa = "rv64imafdcsu"; 49 | mmu-type = "riscv,sv48"; 50 | 51 | interrupt-controller { 52 | #interrupt-cells = <0x01>; 53 | interrupt-controller; 54 | compatible = "riscv,cpu-intc"; 55 | phandle = <0x02>; 56 | }; 57 | }; 58 | }; 59 | 60 | memory@80000000 { 61 | device_type = "memory"; 62 | reg = <${VALHEIM_MEMORY_REG}>; 63 | }; 64 | 65 | soc { 66 | #address-cells = <0x02>; 67 | #size-cells = <0x02>; 68 | compatible = "simple-bus"; 69 | ranges; 70 | 71 | interrupt-controller@c000000 { 72 | phandle = <0x03>; 73 | riscv,ndev = <0x35>; 74 | reg = <0x00 0xc000000 0x00 0x4000000>; 75 | interrupts-extended = <0x02 0x0b 0x02 0x09>; 76 | interrupt-controller; 77 | compatible = "riscv,plic0"; 78 | #interrupt-cells = <0x01>; 79 | #address-cells = <0x00>; 80 | }; 81 | 82 | clint@2000000 { 83 | interrupts-extended = <0x02 0x03 0x02 0x07>; 84 | reg = <0x00 0x2000000 0x00 0x10000>; 85 | compatible = "riscv,clint0"; 86 | }; 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /pictures/rustsbi-booting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkiva/valheim/e702b5cbff4a0b14ae0cc9187baa3af67eac7b9e/pictures/rustsbi-booting.jpg -------------------------------------------------------------------------------- /pictures/xv6-booting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imkiva/valheim/e702b5cbff4a0b14ae0cc9187baa3af67eac7b9e/pictures/xv6-booting.png -------------------------------------------------------------------------------- /profiler/profile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cargo build --release 4 | 5 | sudo dtrace -c \ 6 | '../target/release/valheim --kernel ../tests/test-kernel.bin --bios ../tests/rustsbi-qemu.bin' \ 7 | -o out.stacks \ 8 | -n 'profile-997/execname == "valheim"/{@[ustack(500)] = count();}' 9 | 10 | ./stackcollapse.pl out.stacks | tee out.collapsed | ./flamegraph.pl > flamegraph.svg 11 | -------------------------------------------------------------------------------- /profiler/stackcollapse.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # stackcollapse.pl collapse multiline stacks into single lines. 4 | # 5 | # Parses a multiline stack followed by a number on a separate line, and 6 | # outputs a semicolon separated stack followed by a space and the number. 7 | # If memory addresses (+0xd) are present, they are stripped, and resulting 8 | # identical stacks are colased with their counts summed. 9 | # 10 | # USAGE: ./stackcollapse.pl infile > outfile 11 | # 12 | # Example input: 13 | # 14 | # unix`i86_mwait+0xd 15 | # unix`cpu_idle_mwait+0xf1 16 | # unix`idle+0x114 17 | # unix`thread_start+0x8 18 | # 1641 19 | # 20 | # Example output: 21 | # 22 | # unix`thread_start;unix`idle;unix`cpu_idle_mwait;unix`i86_mwait 1641 23 | # 24 | # Input may contain many stacks, and can be generated using DTrace. The 25 | # first few lines of input are skipped (see $headerlines). 26 | # 27 | # Copyright 2011 Joyent, Inc. All rights reserved. 28 | # Copyright 2011 Brendan Gregg. All rights reserved. 29 | # 30 | # CDDL HEADER START 31 | # 32 | # The contents of this file are subject to the terms of the 33 | # Common Development and Distribution License (the "License"). 34 | # You may not use this file except in compliance with the License. 35 | # 36 | # You can obtain a copy of the license at docs/cddl1.txt or 37 | # http://opensource.org/licenses/CDDL-1.0. 38 | # See the License for the specific language governing permissions 39 | # and limitations under the License. 40 | # 41 | # When distributing Covered Code, include this CDDL HEADER in each 42 | # file and include the License file at docs/cddl1.txt. 43 | # If applicable, add the following below this CDDL HEADER, with the 44 | # fields enclosed by brackets "[]" replaced with your own identifying 45 | # information: Portions Copyright [yyyy] [name of copyright owner] 46 | # 47 | # CDDL HEADER END 48 | # 49 | # 14-Aug-2011 Brendan Gregg Created this. 50 | 51 | use strict; 52 | 53 | my $headerlines = 3; # number of input lines to skip 54 | my $includeoffset = 0; # include function offset (except leafs) 55 | my %collapsed; 56 | 57 | sub remember_stack { 58 | my ($stack, $count) = @_; 59 | $collapsed{$stack} += $count; 60 | } 61 | 62 | my $nr = 0; 63 | my @stack; 64 | 65 | foreach (<>) { 66 | next if $nr++ < $headerlines; 67 | chomp; 68 | 69 | if (m/^\s*(\d+)+$/) { 70 | my $count = $1; 71 | my $joined = join(";", @stack); 72 | 73 | # trim leaf offset if these were retained: 74 | $joined =~ s/\+[^+]*$// if $includeoffset; 75 | 76 | remember_stack($joined, $count); 77 | @stack = (); 78 | next; 79 | } 80 | 81 | next if (m/^\s*$/); 82 | 83 | my $frame = $_; 84 | $frame =~ s/^\s*//; 85 | $frame =~ s/\+[^+]*$// unless $includeoffset; 86 | 87 | # Remove arguments from C++ function names: 88 | $frame =~ s/(::.*)[(<].*/$1/; 89 | 90 | $frame = "-" if $frame eq ""; 91 | 92 | my @inline; 93 | for (split /\->/, $frame) { 94 | my $func = $_; 95 | 96 | # Strip out L and ; included in java stacks 97 | $func =~ tr/\;/:/; 98 | $func =~ s/^L//; 99 | $func .= "_[i]" if scalar(@inline) > 0; #inlined 100 | 101 | push @inline, $func; 102 | } 103 | 104 | unshift @stack, @inline; 105 | } 106 | 107 | foreach my $k (sort { $a cmp $b } keys %collapsed) { 108 | print "$k $collapsed{$k}\n"; 109 | } 110 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /valheim-asm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "valheim-asm" 3 | version = "0.2.0" 4 | edition = "2021" 5 | repository = "https://github.com/imkiva/valheim" 6 | description = "RISC-V instructions and assembler" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | modular-bitfield = "0.11.2" 11 | bytebuffer = "2.0.1" 12 | -------------------------------------------------------------------------------- /valheim-asm/src/asm/encode16.rs: -------------------------------------------------------------------------------- 1 | pub trait Encode16 { 2 | fn encode16(self) -> u16; 3 | } 4 | -------------------------------------------------------------------------------- /valheim-asm/src/asm/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | 3 | use std::collections::HashMap; 4 | 5 | use bytebuffer::{ByteBuffer, Endian}; 6 | 7 | use crate::asm::encode16::Encode16; 8 | use crate::asm::encode32::Encode32; 9 | use crate::isa::data::Fin; 10 | use crate::isa::typed::{Instr, Reg}; 11 | 12 | pub mod encode32; 13 | pub mod encode16; 14 | pub mod traits; 15 | pub mod riscv; 16 | pub mod test; 17 | 18 | pub const zero: Reg = Reg::ZERO; 19 | pub const ra: Reg = Reg::X(Fin::new(1)); 20 | pub const sp: Reg = Reg::X(Fin::new(2)); 21 | pub const gp: Reg = Reg::X(Fin::new(3)); 22 | pub const tp: Reg = Reg::X(Fin::new(4)); 23 | pub const t0: Reg = Reg::X(Fin::new(5)); 24 | pub const t1: Reg = Reg::X(Fin::new(6)); 25 | pub const t2: Reg = Reg::X(Fin::new(7)); 26 | pub const s0: Reg = Reg::X(Fin::new(8)); 27 | pub const s1: Reg = Reg::X(Fin::new(9)); 28 | pub const a0: Reg = Reg::X(Fin::new(10)); 29 | pub const a1: Reg = Reg::X(Fin::new(11)); 30 | pub const a2: Reg = Reg::X(Fin::new(12)); 31 | pub const a3: Reg = Reg::X(Fin::new(13)); 32 | pub const a4: Reg = Reg::X(Fin::new(14)); 33 | pub const a5: Reg = Reg::X(Fin::new(15)); 34 | pub const a6: Reg = Reg::X(Fin::new(16)); 35 | pub const a7: Reg = Reg::X(Fin::new(17)); 36 | pub const s2: Reg = Reg::X(Fin::new(18)); 37 | pub const s3: Reg = Reg::X(Fin::new(19)); 38 | pub const s4: Reg = Reg::X(Fin::new(20)); 39 | pub const s5: Reg = Reg::X(Fin::new(21)); 40 | pub const s6: Reg = Reg::X(Fin::new(22)); 41 | pub const s7: Reg = Reg::X(Fin::new(23)); 42 | pub const s8: Reg = Reg::X(Fin::new(24)); 43 | pub const s9: Reg = Reg::X(Fin::new(25)); 44 | pub const s10: Reg = Reg::X(Fin::new(26)); 45 | pub const s11: Reg = Reg::X(Fin::new(27)); 46 | pub const t3: Reg = Reg::X(Fin::new(28)); 47 | pub const t4: Reg = Reg::X(Fin::new(29)); 48 | pub const t5: Reg = Reg::X(Fin::new(30)); 49 | pub const t6: Reg = Reg::X(Fin::new(31)); 50 | 51 | pub struct Assembler { 52 | pub base: usize, 53 | pub code: ByteBuffer, 54 | pub label_id: usize, 55 | pub labels: HashMap, 56 | pub backfills: HashMap Instr>)>, 57 | } 58 | 59 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 60 | pub struct Label { 61 | /// The label position 62 | pub position: isize, 63 | } 64 | 65 | #[derive(Debug, Copy, Clone)] 66 | pub struct Current { 67 | /// Current position 68 | pub position: isize, 69 | } 70 | 71 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 72 | pub struct LabelId { 73 | pub idx: usize, 74 | } 75 | 76 | #[derive(Debug, Copy, Clone)] 77 | pub enum Compare { 78 | EQ, 79 | NE, 80 | LT, 81 | GE, 82 | LTU, 83 | GEU, 84 | } 85 | 86 | /// Common assembler 87 | impl Assembler { 88 | pub fn new(base: usize) -> Assembler { 89 | let mut asm = Assembler { 90 | base, 91 | code: ByteBuffer::new(), 92 | backfills: HashMap::new(), 93 | labels: HashMap::new(), 94 | label_id: 0, 95 | }; 96 | asm.code.set_endian(Endian::LittleEndian); 97 | asm 98 | } 99 | 100 | pub fn current(&self) -> Current { 101 | Current { position: self.code.get_wpos() as isize } 102 | } 103 | 104 | pub fn new_label(&mut self) -> LabelId { 105 | let id = LabelId { idx: self.label_id }; 106 | self.label_id += 1; 107 | id 108 | } 109 | 110 | pub fn emit_label(&mut self, id: LabelId) { 111 | let label = Label { position: self.current().position }; 112 | self.labels.insert(id, label); 113 | } 114 | 115 | pub fn backfill(&mut self, id: LabelId, compute: Box Instr>) { 116 | self.backfills.insert(id, (self.current(), compute)); 117 | self.code.write_u32(0); // the placeholder 118 | } 119 | 120 | pub fn freeze(&mut self) { 121 | for (id, label) in self.labels.iter() { 122 | if let Some((their, compute)) = self.backfills.remove(id) { 123 | let instr = compute(label.clone(), their); 124 | let current = self.current(); 125 | self.code.set_wpos(their.position as usize); 126 | self.code.write_u32(instr.encode32()); 127 | self.code.set_wpos(current.position as usize); 128 | } 129 | } 130 | } 131 | 132 | pub fn emit32(&mut self, code: T) { 133 | self.code.write_u32(code.encode32()); 134 | } 135 | 136 | pub fn emit16(&mut self, code: T) { 137 | self.code.write_u16(code.encode16()); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /valheim-asm/src/asm/riscv.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::asm::{Assembler, Compare, Current, Label, LabelId, ra, zero}; 4 | use crate::isa::rv32::RV32Instr::*; 5 | use crate::isa::rv64::RV64Instr::*; 6 | use crate::isa::typed::{Instr, Reg}; 7 | 8 | /// Pseudo-instructions 9 | impl Assembler { 10 | pub fn mov(&mut self, rd: Reg, rs: Reg) { 11 | self.addi(rd, rs, 0); 12 | } 13 | 14 | pub fn not(&mut self, rd: Reg, rs: Reg) { 15 | self.xori(rd, rs, -1); 16 | } 17 | 18 | pub fn nop(&mut self) { 19 | self.addi(zero, zero, 0); 20 | } 21 | 22 | pub fn ret(&mut self) { 23 | self.jalr_rv(zero, ra, 0); 24 | } 25 | 26 | pub fn call_offset(&mut self, offset: i32) { 27 | self.load_pc(ra, offset); // TODO: use offset in `jalr_rv`, instead of `addi` 28 | self.call_reg(ra); 29 | } 30 | 31 | pub fn call_reg(&mut self, rs: Reg) { 32 | self.jalr_rv(ra, rs, 0); 33 | } 34 | 35 | pub fn branch(&mut self, cond: Compare, rs1: Reg, rs2: Reg, id: LabelId) { 36 | self.backfill(id, Box::new(move |label, current| { 37 | let offset = offset(label, current); 38 | b_rv_(cond, rs1, rs2, offset) 39 | // TODO: check offset range, and select different instructions 40 | })); 41 | } 42 | 43 | pub fn jump(&mut self, id: LabelId) { 44 | self.backfill(id, Box::new(move |label, current| { 45 | let offset = offset(label, current); 46 | jal_rv_(zero, offset >> 1) 47 | // TODO: check offset range, and select different instructions 48 | })); 49 | } 50 | 51 | pub fn load_imm(&mut self, rd: Reg, imm: i32) { 52 | let high = ((imm as u32) & 0xfffff000) >> 12; 53 | let low = (imm as u32) & 0xfff; 54 | if high != 0 { 55 | self.lui_rv(rd, high as i32); 56 | self.addi(rd, rd, low as i32); 57 | } else { 58 | self.addi(rd, zero, low as i32); 59 | } 60 | } 61 | 62 | pub fn load_pc(&mut self, rd: Reg, offset: i32) { 63 | let high = ((offset as u32) & 0xfffff000) >> 12; 64 | let low = (offset as u32) & 0xfff; 65 | if high != 0 { 66 | self.auipc_rv(rd, high as i32); 67 | self.addi(rd, rd, low as i32); 68 | } else { 69 | self.addi(rd, zero, low as i32); 70 | } 71 | } 72 | 73 | pub fn inc(&mut self, rd: Reg) { 74 | self.addi_rv(rd, rd, 1); 75 | } 76 | 77 | pub fn dec(&mut self, rd: Reg) { 78 | self.addi_rv(rd, rd, -1); 79 | } 80 | 81 | pub fn add(&mut self, rd: Reg, rs1: Reg, rs2: Reg) { 82 | self.add_rv(rd, rs1, rs2); 83 | } 84 | 85 | pub fn addi(&mut self, rd: Reg, rs: Reg, imm: i32) { 86 | self.addi_rv(rd, rs, imm); 87 | } 88 | 89 | pub fn xori(&mut self, rd: Reg, rs: Reg, imm: i32) { 90 | self.xori_rv(rd, rs, imm); 91 | } 92 | } 93 | 94 | /// 32-bit operands 95 | impl Assembler { 96 | pub fn sign_extend_32(&mut self, rd: Reg, rs: Reg) { 97 | self.addi_32(rd, rs, 0); 98 | } 99 | 100 | pub fn addi_32(&mut self, rd: Reg, rs: Reg, imm: i32) { 101 | self.addiw_rv(rd, rs, imm); 102 | } 103 | } 104 | 105 | /// Raw instructions, prefixed with `_rv` 106 | impl Assembler { 107 | pub fn auipc_rv(&mut self, rd: Reg, imm: i32) { 108 | self.emit32(AUIPC(rd.into(), imm.into())); 109 | } 110 | 111 | pub fn lui_rv(&mut self, rd: Reg, imm: i32) { 112 | self.emit32(LUI(rd.into(), imm.into())); 113 | } 114 | 115 | pub fn jal_rv(&mut self, rd: Reg, offset: i32) { 116 | self.emit32(jal_rv_(rd, offset)); 117 | } 118 | 119 | pub fn jalr_rv(&mut self, rd: Reg, rs: Reg, offset: i32) { 120 | self.emit32(JALR(rd.into(), rs.into(), offset.into())); 121 | } 122 | 123 | pub fn b_rv(&mut self, cond: Compare, rs1: Reg, rs2: Reg, offset: i32) { 124 | self.emit32(b_rv_(cond, rs1, rs2, offset)); 125 | } 126 | 127 | pub fn addi_rv(&mut self, rd: Reg, rs: Reg, imm: i32) { 128 | self.emit32(ADDI(rd.into(), rs.into(), imm.into())); 129 | } 130 | 131 | pub fn xori_rv(&mut self, rd: Reg, rs: Reg, imm: i32) { 132 | self.emit32(XORI(rd.into(), rs.into(), imm.into())); 133 | } 134 | 135 | pub fn addiw_rv(&mut self, rd: Reg, rs: Reg, imm: i32) { 136 | self.emit32(ADDIW(rd.into(), rs.into(), imm.into())); 137 | } 138 | 139 | pub fn add_rv(&mut self, rd: Reg, rs1: Reg, rs2: Reg) { 140 | self.emit32(ADD(rd.into(), rs1.into(), rs2.into())); 141 | } 142 | } 143 | 144 | fn offset(label: Label, current: Current) -> i32 { 145 | (label.position - current.position) as i32 146 | } 147 | 148 | fn b_rv_(cmp: Compare, rs1: Reg, rs2: Reg, offset: i32) -> Instr { 149 | let offset = offset >> 1; 150 | match cmp { 151 | Compare::EQ => Instr::RV32(BEQ(rs1.into(), rs2.into(), offset.into())), 152 | Compare::NE => Instr::RV32(BNE(rs1.into(), rs2.into(), offset.into())), 153 | Compare::LT => Instr::RV32(BLT(rs1.into(), rs2.into(), offset.into())), 154 | Compare::GE => Instr::RV32(BGE(rs1.into(), rs2.into(), offset.into())), 155 | Compare::LTU => Instr::RV32(BLTU(rs1.into(), rs2.into(), offset.into())), 156 | Compare::GEU => Instr::RV32(BGEU(rs1.into(), rs2.into(), offset.into())), 157 | } 158 | } 159 | 160 | pub fn jal_rv_(rd: Reg, offset: i32) -> Instr { 161 | Instr::RV32(JAL(rd.into(), offset.into())) 162 | } 163 | -------------------------------------------------------------------------------- /valheim-asm/src/asm/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod masm { 3 | use crate::asm::{a0, Assembler, Compare, t0, t1}; 4 | use crate::isa::typed::Instr; 5 | 6 | pub fn decode_encode(di: u32) { 7 | let d = Instr::decode32(di); 8 | let mut a = Assembler::new(0); 9 | a.emit32(d.unwrap()); 10 | let ei = a.code.read_u32().unwrap(); 11 | println!("di = {:#b}", di); 12 | println!("ei = {:#b}", ei); 13 | let e = Instr::decode32(ei); 14 | println!("d = {:?}", d); 15 | println!("e = {:?}", e); 16 | assert_eq!(di, ei); 17 | assert_eq!(d, e); 18 | } 19 | 20 | #[test] 21 | pub fn encode_j_type() { 22 | decode_encode(0b_1_1111110100_1_11111111_00000_1101111); // jal x0, -6*4 23 | } 24 | 25 | #[test] 26 | pub fn encode_b_type() { 27 | decode_encode(0b11111110011000101000110011100011); // beq, t0, t1, -8 28 | } 29 | 30 | #[test] 31 | pub fn encode_u_type() { 32 | decode_encode(0x00110837); // li, x16, 1114112 33 | } 34 | 35 | #[test] 36 | pub fn sum() { 37 | let mut asm = Assembler::new(0); 38 | // int sum = 0; 39 | // int i = 0; 40 | // while (i <= 100) 41 | // sum = sum + i; 42 | // print(sum); 43 | 44 | // register allocation 45 | let sum = a0; 46 | let i = t0; 47 | let tmp = t1; 48 | // int sum = 0; 49 | // int i = 0; 50 | // int tmp = 101; 51 | // begin: 52 | // if (i >= tmp) goto end; 53 | // sum = sum + i; 54 | // goto begin; 55 | // end: 56 | // print(sum); 57 | let begin = asm.new_label(); 58 | let end = asm.new_label(); 59 | asm.load_imm(sum, 0); 60 | asm.load_imm(i, 0); 61 | asm.load_imm(tmp, 101); 62 | asm.emit_label(begin); 63 | asm.branch(Compare::GE, i, tmp, end); 64 | asm.add(sum, sum, i); 65 | asm.jump(begin); 66 | asm.emit_label(end); 67 | // done, freezing the holes 68 | asm.freeze(); 69 | // get the code 70 | asm.code.set_rpos(0); 71 | let mut pc = 0; 72 | while let Ok(raw) = asm.code.read_u32() { 73 | let inst = Instr::decode32(raw); 74 | println!("{:#010X} {:#010x} {:?}", pc, raw, inst.unwrap()); 75 | pc += 4; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /valheim-asm/src/asm/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::isa::typed::{Imm32, Rd, Reg, Rs1, Rs2, Rs3}; 2 | 3 | macro_rules! reg_into { 4 | ($ty:ident) => { 5 | impl Into<$ty> for Reg { 6 | fn into(self) -> $ty { 7 | $ty(self) 8 | } 9 | } 10 | }; 11 | } 12 | 13 | reg_into!(Rd); 14 | reg_into!(Rs1); 15 | reg_into!(Rs2); 16 | reg_into!(Rs3); 17 | 18 | impl Into> for u32 { 19 | fn into(self) -> Imm32 { 20 | Imm32::from(self) 21 | } 22 | } 23 | 24 | impl Into> for i32 { 25 | fn into(self) -> Imm32 { 26 | (self as u32).into() 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod test { 32 | use crate::isa::typed::Imm32; 33 | 34 | #[test] 35 | fn imm32() { 36 | let i: Imm32<31, 12> = 0b1111_1110_1101_1011_0111.into(); 37 | assert_eq!(i.decode(), 0b1111_1110_1101_1011_0111_000000000000); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/data.rs: -------------------------------------------------------------------------------- 1 | /// Bounded integer within the range `[0, MAX)`. 2 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 3 | pub struct Fin(u32); 4 | 5 | impl Fin { 6 | pub const fn new(value: u32) -> Self { 7 | assert!(value < MAX); 8 | Self(value) 9 | } 10 | 11 | #[inline(always)] 12 | pub fn value(self) -> u32 { 13 | self.0 14 | } 15 | } 16 | 17 | pub(crate) mod workaround { 18 | pub struct If; 19 | pub trait True {} 20 | impl True for If {} 21 | } 22 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/decode16.rs: -------------------------------------------------------------------------------- 1 | use crate::isa::data::Fin; 2 | use crate::isa::decode::{fp, gp}; 3 | use crate::isa::rv32::RV32Instr; 4 | use crate::isa::rv64::RV64Instr; 5 | use crate::isa::typed::{Imm32, Instr, Rd, Reg, Rs1, Rs2, Shamt}; 6 | use crate::isa::untyped16::Bytecode16; 7 | use crate::rv32; 8 | use crate::rv64; 9 | 10 | macro_rules! hint { 11 | () => {Instr::NOP}; 12 | } 13 | 14 | macro_rules! reserved { 15 | () => {return None}; 16 | } 17 | 18 | macro_rules! nzimm_is_0 { 19 | ($untyped:expr) => {$untyped.ci().imm_b5() == 0 && $untyped.ci().imm_b1() == 0}; 20 | } 21 | 22 | macro_rules! nzimm_not_0 { 23 | ($untyped:expr) => {!nzimm_is_0!($untyped)}; 24 | } 25 | 26 | macro_rules! rd_is { 27 | ($untyped:expr, $n:expr) => {$untyped.ci().rd() == $n}; 28 | } 29 | 30 | macro_rules! rd_is_0 { 31 | ($untyped:expr) => {rd_is!($untyped, 0)}; 32 | } 33 | 34 | macro_rules! arith { 35 | ($type:ident, $opcode:ident, $untyped:expr) => {{ 36 | let ca = $untyped.ca(); 37 | let rd = Rd(gp_3(ca.rd_or_rs1())); 38 | let rs2 = Rs2(gp_3(ca.rs2())); 39 | $type!($opcode, rd, Rs1(rd.0), rs2) 40 | }}; 41 | } 42 | 43 | impl Instr { 44 | pub fn try_from_compressed(untyped: Bytecode16) -> Option { 45 | let repr = untyped.repr(); 46 | match (repr, repr & 0b11) { 47 | // A 16-bit instruction with all bits zero is permanently reserved as an illegal instruction. 48 | (0, _) => None, 49 | (_, 0) => decode_untyped(untyped), 50 | (_, 1) => decode_untyped(untyped), 51 | (_, 2) => decode_untyped(untyped), 52 | _ => None, 53 | } 54 | } 55 | 56 | pub fn decode16(i: u16) -> Option { 57 | Instr::try_from_compressed(Bytecode16 { repr: i }) 58 | } 59 | } 60 | 61 | fn decode_untyped(untyped: Bytecode16) -> Option { 62 | let inst = untyped.repr(); 63 | let instr = match untyped.opcode() { 64 | 0b00 => match untyped.funct3() { 65 | // C.ADDI4SPN (RES for nzuimm = 0) 66 | 0b000 if untyped.ciw().imm_b8() == 0 => reserved!(), 67 | 0b000 => { 68 | let ciw = untyped.ciw(); 69 | let rd = Rd(gp_3(ciw.rd())); 70 | // nzuimm[5:4|9:6|2|3] = inst[12:11|10:7|6|5] 71 | let nzuimm = ((inst >> 1) & 0x3c0) // nzuimm[9:6] 72 | | ((inst >> 7) & 0x30) // nzuimm[5:4] 73 | | ((inst >> 2) & 0x8) // nzuimm[3] 74 | | ((inst >> 4) & 0x4); // nzuimm[2] 75 | rv32!(ADDI, rd, Rs1(Reg::X(Fin::new(2))), Imm32::from(nzuimm as u32)) 76 | } 77 | 78 | // C.FLD for RV32/64, C.LQ for RV128 (not supported) 79 | 0b001 => { 80 | let cl = untyped.cl(); 81 | let rd = Rd(fp_3(cl.rd())); 82 | let rs1 = Rs1(gp_3(cl.rs1())); 83 | // offset[5:3|7:6] = isnt[12:10|6:5] 84 | let offset = ((inst << 1) & 0xc0) // imm[7:6] 85 | | ((inst >> 7) & 0x38); // imm[5:3] 86 | 87 | rv32!(FLD, rd, rs1, Imm32::from(offset as u32)) 88 | } 89 | 90 | // C.LW 91 | 0b010 => { 92 | let cl = untyped.cl(); 93 | let rd = Rd(gp_3(cl.rd())); 94 | let rs1 = Rs1(gp_3(cl.rs1())); 95 | // offset[5:3|2|6] = isnt[12:10|6|5] 96 | let offset = ((inst << 1) & 0x40) // imm[6] 97 | | ((inst >> 7) & 0x38) // imm[5:3] 98 | | ((inst >> 4) & 0x4); // imm[2] 99 | 100 | rv32!(LW, rd, rs1, Imm32::from(offset as u32)) 101 | } 102 | 103 | // C.LD for RV64/128, C.FLW for RV32 (not supported) 104 | 0b011 => { 105 | let cl = untyped.cl(); 106 | let rd = Rd(gp_3(cl.rd())); 107 | let rs1 = Rs1(gp_3(cl.rs1())); 108 | // offset[5:3|7:6] = isnt[12:10|6:5] 109 | let offset = ((inst << 1) & 0xc0) // imm[7:6] 110 | | ((inst >> 7) & 0x38); // imm[5:3] 111 | 112 | rv64!(LD, rd, rs1, Imm32::from(offset as u32)) 113 | } 114 | 115 | // Reserved 116 | 0b100 => reserved!(), 117 | 118 | // C.FSD for RV32/64, C.SQ for RV128 (not supported) 119 | 0b101 => { 120 | let cs = untyped.cs(); 121 | let rs1 = Rs1(gp_3(cs.rs1())); 122 | let rs2 = Rs2(fp_3(cs.rs2())); 123 | // offset[5:3|7:6] = isnt[12:10|6:5] 124 | let offset = ((inst << 1) & 0xc0) // imm[7:6] 125 | | ((inst >> 7) & 0x38); // imm[5:3] 126 | 127 | rv32!(FSD, rs1, rs2, Imm32::from(offset as u32)) 128 | } 129 | 130 | // C.SW 131 | 0b110 => { 132 | let cs = untyped.cs(); 133 | let rs1 = Rs1(gp_3(cs.rs1())); 134 | let rs2 = Rs2(gp_3(cs.rs2())); 135 | // offset[5:3|2|6] = isnt[12:10|6|5] 136 | let offset = ((inst << 1) & 0x40) // imm[6] 137 | | ((inst >> 7) & 0x38) // imm[5:3] 138 | | ((inst >> 4) & 0x4); // imm[2] 139 | 140 | rv32!(SW, rs1, rs2, Imm32::from(offset as u32)) 141 | } 142 | 143 | // C.SD for RV64/128, C.FSW for RV32 (not supported) 144 | 0b111 => { 145 | let cs = untyped.cs(); 146 | let rs1 = Rs1(gp_3(cs.rs1())); 147 | let rs2 = Rs2(gp_3(cs.rs2())); 148 | // offset[5:3|7:6] = isnt[12:10|6:5] 149 | let offset = ((inst << 1) & 0xc0) // imm[7:6] 150 | | ((inst >> 7) & 0x38); // imm[5:3] 151 | 152 | rv64!(SD, rs1, rs2, Imm32::from(offset as u32)) 153 | } 154 | _ => return None, 155 | }, 156 | 0b01 => match untyped.funct3() { 157 | // C.NOP 158 | 0b000 if rd_is_0!(untyped) && nzimm_not_0!(untyped) => hint!(), 159 | 0b000 if rd_is_0!(untyped) => Instr::NOP, 160 | // C.ADDI 161 | 0b000 if nzimm_is_0!(untyped) => hint!(), 162 | 0b000 => { 163 | let ci = untyped.ci(); 164 | let rd = Rd(gp(ci.rd())); 165 | // imm[5|4:0] = inst[12|6:2] 166 | let imm = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 167 | // Sign-extended. 168 | let imm = match (imm & 0x20) == 0 { 169 | true => imm as u32, 170 | false => (0xc0 | imm) as i8 as i32 as u32, 171 | }; 172 | rv32!(ADDI, rd, Rs1(rd.0), Imm32::from(imm)) 173 | } 174 | 175 | // C.ADDIW for RV64/128 (RES for rd = 0), C.JAL for RV32 (not supported) 176 | 0b001 if rd_is_0!(untyped) => reserved!(), 177 | 0b001 => { 178 | let ci = untyped.ci(); 179 | let rd = Rd(gp(ci.rd())); 180 | // imm[5|4:0] = inst[12|6:2] 181 | let imm = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 182 | // Sign-extended. 183 | let imm = match (imm & 0x20) == 0 { 184 | true => imm as u32, 185 | false => (0xc0 | imm) as i8 as i32 as u32, 186 | }; 187 | rv64!(ADDIW, rd, Rs1(rd.0), Imm32::from(imm)) 188 | } 189 | 190 | // C.LI 191 | 0b010 => { 192 | let ci = untyped.ci(); 193 | let rd = Rd(gp(ci.rd())); 194 | // imm[5|4:0] = inst[12|6:2] 195 | let imm = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 196 | // Sign-extended. 197 | let imm = match (imm & 0x20) == 0 { 198 | true => imm as u32, 199 | false => (0xc0 | imm) as i8 as i32 as u32, 200 | }; 201 | rv32!(ADDI, rd, Rs1(Reg::ZERO), Imm32::from(imm)) 202 | } 203 | 204 | // C.ADDI16SP (RES for nzimm = 0) 205 | 0b011 if rd_is!(untyped, 2) && nzimm_is_0!(untyped) => reserved!(), 206 | 0b011 if rd_is!(untyped, 2) => { 207 | // nzimm[9|4|6|8:7|5] = inst[12|6|5|4:3|2] 208 | let nzimm = ((inst >> 3) & 0x200) // nzimm[9] 209 | | ((inst >> 2) & 0x10) // nzimm[4] 210 | | ((inst << 1) & 0x40) // nzimm[6] 211 | | ((inst << 4) & 0x180) // nzimm[8:7] 212 | | ((inst << 3) & 0x20); // nzimm[5] 213 | // Sign-extended. 214 | let nzimm = match (nzimm & 0x200) == 0 { 215 | true => nzimm as u32, 216 | false => (0xfc00 | nzimm) as i16 as i32 as u32, 217 | }; 218 | rv32!(ADDI, Rd(Reg::X(Fin::new(2))), Rs1(Reg::X(Fin::new(2))), Imm32::from(nzimm)) 219 | } 220 | 221 | // C.LUI (RES for imm = 0; HINT for rd = 0) 222 | 0b011 if nzimm_is_0!(untyped) => reserved!(), 223 | 0b011 if rd_is_0!(untyped) => hint!(), 224 | 0b011 => { 225 | let ci = untyped.ci(); 226 | let rd = Rd(gp(ci.rd())); 227 | // imm[17|16:12] = inst[12|6:2] 228 | let imm = (((inst as u32) << 5) & 0x20000) | (((inst as u32) << 10) & 0x1f000); 229 | // Sign-extended. 230 | let imm = match (imm & 0x20000) == 0 { 231 | true => imm, 232 | false => (0xfffc0000 | imm) as i32 as u32, 233 | }; 234 | rv32!(LUI, rd, Imm32::from(imm >> 12)) 235 | } 236 | 237 | 0b100 => match untyped.cbi().funct2() { 238 | // HINT for RV32/64, C.SRLI64 for RV128 (not supported) 239 | 0b00 if nzimm_is_0!(untyped) => hint!(), 240 | 241 | // C.SRLI 242 | 0b00 => { 243 | let cbi = untyped.cbi(); 244 | let rd = Rd(gp_3(cbi.rd_or_rs1())); 245 | // shamt[5|4:0] = inst[12|6:2] 246 | let shamt = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 247 | rv64!(SRLI, rd, Rs1(rd.0), Shamt(shamt as u8)) 248 | } 249 | 250 | 251 | // HINT for RV32/64, C.SRAI64 for RV128 (not supported) 252 | 0b01 if nzimm_is_0!(untyped) => hint!(), 253 | // C.SRAI 254 | 0b01 => { 255 | let cbi = untyped.cbi(); 256 | let rd = Rd(gp_3(cbi.rd_or_rs1())); 257 | // shamt[5|4:0] = inst[12|6:2] 258 | let shamt = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 259 | rv64!(SRAI, rd, Rs1(rd.0), Shamt(shamt as u8)) 260 | } 261 | 262 | // C.ANDI 263 | 0b10 => { 264 | let cbi = untyped.cbi(); 265 | let rd = Rd(gp_3(cbi.rd_or_rs1())); 266 | // imm[5|4:0] = inst[12|6:2] 267 | let imm = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 268 | // Sign-extended. 269 | let imm = match (imm & 0x20) == 0 { 270 | true => imm as u32, 271 | false => (0xc0 | imm) as i8 as i32 as u32, 272 | }; 273 | rv32!(ANDI, rd, Rs1(rd.0), Imm32::from(imm)) 274 | } 275 | 276 | 0b11 => match (untyped.ca().funct6(), untyped.ca().funct2()) { 277 | // C.SUB 278 | (0b100_0_11, 0b00) => arith!(rv32, SUB, untyped), 279 | // C.XOR 280 | (0b100_0_11, 0b01) => arith!(rv32, XOR, untyped), 281 | // C.OR 282 | (0b100_0_11, 0b10) => arith!(rv32, OR, untyped), 283 | // C.AND 284 | (0b100_0_11, 0b11) => arith!(rv32, AND, untyped), 285 | // C.SUBW 286 | (0b100_1_11, 0b00) => arith!(rv64, SUBW, untyped), 287 | // C.ADDW 288 | (0b100_1_11, 0b01) => arith!(rv64, ADDW, untyped), 289 | // Reserved 290 | (0b100_1_11, 0b10) => reserved!(), 291 | // Reserved 292 | (0b100_1_11, 0b11) => reserved!(), 293 | _ => return None, 294 | }, 295 | 296 | _ => return None, 297 | }, 298 | 299 | // C.J 300 | 0b101 => { 301 | // offset[11|4|9:8|10|6|7|3:1|5] = inst[12|11|10:9|8|7|6|5:3|2] 302 | let offset = ((inst >> 1) & 0x800) // offset[11] 303 | | ((inst << 2) & 0x400) // offset[10] 304 | | ((inst >> 1) & 0x300) // offset[9:8] 305 | | ((inst << 1) & 0x80) // offset[7] 306 | | ((inst >> 1) & 0x40) // offset[6] 307 | | ((inst << 3) & 0x20) // offset[5] 308 | | ((inst >> 7) & 0x10) // offset[4] 309 | | ((inst >> 2) & 0xe); // offset[3:1] 310 | // Sign-extended. 311 | let offset = match (offset & 0x800) == 0 { 312 | true => offset as u32, 313 | false => (0xf000 | offset) as i16 as i32 as u32, 314 | }; 315 | rv32!(JAL, Rd(Reg::ZERO), Imm32::from(offset >> 1)) 316 | } 317 | 318 | // C.BEQZ 319 | 0b110 => { 320 | let cb = untyped.cb(); 321 | let rs1 = Rs1(gp_3(cb.rd_or_rs1())); 322 | // offset[8|4:3|7:6|2:1|5] = inst[12|11:10|6:5|4:3|2] 323 | let offset = ((inst >> 4) & 0x100) // offset[8] 324 | | ((inst << 1) & 0xc0) // offset[7:6] 325 | | ((inst << 3) & 0x20) // offset[5] 326 | | ((inst >> 7) & 0x18) // offset[4:3] 327 | | ((inst >> 2) & 0x6); // offset[2:1] 328 | // Sign-extended. 329 | let offset = match (offset & 0x100) == 0 { 330 | true => offset as u32, 331 | false => (0xfe00 | offset) as i16 as i32 as u32, 332 | }; 333 | rv32!(BEQ, rs1, Rs2(Reg::ZERO), Imm32::from(offset >> 1)) 334 | } 335 | 336 | // C.BNEZ 337 | 0b111 => { 338 | let cb = untyped.cb(); 339 | let rs1 = Rs1(gp_3(cb.rd_or_rs1())); 340 | // offset[8|4:3|7:6|2:1|5] = inst[12|11:10|6:5|4:3|2] 341 | let offset = ((inst >> 4) & 0x100) // offset[8] 342 | | ((inst << 1) & 0xc0) // offset[7:6] 343 | | ((inst << 3) & 0x20) // offset[5] 344 | | ((inst >> 7) & 0x18) // offset[4:3] 345 | | ((inst >> 2) & 0x6); // offset[2:1] 346 | // Sign-extended. 347 | let offset = match (offset & 0x100) == 0 { 348 | true => offset as u32, 349 | false => (0xfe00 | offset) as i16 as i32 as u32, 350 | }; 351 | rv32!(BNE, rs1, Rs2(Reg::ZERO), Imm32::from(offset >> 1)) 352 | } 353 | 354 | _ => return None, 355 | }, 356 | 0b10 => match untyped.funct3() { 357 | // C.SLLI 358 | 0b000 if rd_is_0!(untyped) => hint!(), 359 | 0b000 if nzimm_is_0!(untyped) => hint!(), 360 | 0b000 => { 361 | let ci = untyped.ci(); 362 | let rd = Rd(gp(ci.rd())); 363 | // shamt[5|4:0] = inst[12|6:2] 364 | let shamt = ((inst >> 7) & 0x20) | ((inst >> 2) & 0x1f); 365 | rv64!(SLLI, rd, Rs1(rd.0), Shamt(shamt as u8)) 366 | } 367 | 368 | // C.FLDSP for RV32/64, C.LQSP for RV128 (not supported) 369 | 0b001 => { 370 | let ci = untyped.ci(); 371 | let rd = Rd(fp(ci.rd())); 372 | // offset[5|4:3|8:6] = inst[12|6:5|4:2] 373 | let offset = ((inst << 4) & 0x1c0) // offset[8:6] 374 | | ((inst >> 7) & 0x20) // offset[5] 375 | | ((inst >> 2) & 0x18); // offset[4:3] 376 | rv32!(FLD, rd, Rs1(Reg::X(Fin::new(2))), Imm32::from(offset as u32)) 377 | } 378 | 379 | // C.LWSP (RES for rd = 0) 380 | 0b010 if rd_is_0!(untyped) => reserved!(), 381 | 0b010 => { 382 | let ci = untyped.ci(); 383 | let rd = Rd(gp(ci.rd())); 384 | // offset[5|4:2|7:6] = inst[12|6:4|3:2] 385 | let offset = ((inst << 4) & 0xc0) // offset[7:6] 386 | | ((inst >> 7) & 0x20) // offset[5] 387 | | ((inst >> 2) & 0x1c); // offset[4:2] 388 | rv32!(LW, rd, Rs1(Reg::X(Fin::new(2))), Imm32::from(offset as u32)) 389 | } 390 | 391 | // C.LDSP for RV64/128 (RES for rd = 0), C.FLWSP for RV32 (not supported) 392 | 0b011 if rd_is_0!(untyped) => reserved!(), 393 | 0b011 => { 394 | let ci = untyped.ci(); 395 | let rd = Rd(gp(ci.rd())); 396 | // offset[5|4:3|8:6] = inst[12|6:5|4:2] 397 | let offset = ((inst << 4) & 0x1c0) // offset[8:6] 398 | | ((inst >> 7) & 0x20) // offset[5] 399 | | ((inst >> 2) & 0x18); // offset[4:3] 400 | rv64!(LD, rd, Rs1(Reg::X(Fin::new(2))), Imm32::from(offset as u32)) 401 | } 402 | 403 | // C.JR (RES for rs1/rd = 0) 404 | 0b100 if nzimm_is_0!(untyped) && rd_is_0!(untyped) => reserved!(), 405 | 0b100 if nzimm_is_0!(untyped) => { 406 | let cr = untyped.cr(); 407 | let rs1 = Rs1(gp(cr.rd_or_rs1())); 408 | rv32!(JALR, Rd(Reg::ZERO), rs1, Imm32::from(0)) 409 | } 410 | 411 | // C.MV 412 | 0b100 if untyped.ci().imm_b1() == 0 && untyped.ci().imm_b5() != 0 && rd_is_0!(untyped) => hint!(), 413 | 0b100 if untyped.ci().imm_b1() == 0 && untyped.ci().imm_b5() != 0 => { 414 | let cr = untyped.cr(); 415 | let rd = Rd(gp(cr.rd_or_rs1())); 416 | let rs2 = Rs2(gp(cr.rs2())); 417 | rv32!(ADD, rd, Rs1(Reg::ZERO), rs2) 418 | } 419 | 420 | // C.EBREAK 421 | 0b100 if untyped.repr() == 0b100_1_00000_00000_10 => rv32!(EBREAK), 422 | 423 | // C.JALR 424 | 0b100 if untyped.ci().imm_b1() == 1 && untyped.ci().imm_b5() == 0 => { 425 | let cr = untyped.cr(); 426 | let rs1 = Rs1(gp(cr.rd_or_rs1())); 427 | rv32!(JALR, Rd(Reg::X(Fin::new(1))), rs1, Imm32::from(0)) 428 | } 429 | 430 | // C.ADD 431 | 0b100 if untyped.ci().imm_b1() == 1 => { 432 | let cr = untyped.cr(); 433 | let rd = Rd(gp(cr.rd_or_rs1())); 434 | let rs2 = Rs2(gp(cr.rs2())); 435 | rv32!(ADD, rd, Rs1(rd.0), rs2) 436 | } 437 | 438 | // C.FSDSP for RV32/64, C.SQSP for RV128 (not supported) 439 | 0b101 => { 440 | let css = untyped.css(); 441 | let rs2 = Rs2(fp(css.rs2())); 442 | // offset[5:3|8:6] = isnt[12:10|9:7] 443 | let offset = ((inst >> 1) & 0x1c0) // offset[8:6] 444 | | ((inst >> 7) & 0x38); // offset[5:3] 445 | rv32!(FSD, Rs1(Reg::X(Fin::new(2))), rs2, Imm32::from(offset as u32)) 446 | } 447 | 448 | // C.SWSP 449 | 0b110 => { 450 | let css = untyped.css(); 451 | let rs2 = Rs2(gp(css.rs2())); 452 | // offset[5:2|7:6] = inst[12:9|8:7] 453 | let offset = ((inst >> 1) & 0xc0) // offset[7:6] 454 | | ((inst >> 7) & 0x3c); // offset[5:2] 455 | rv32!(SW, Rs1(Reg::X(Fin::new(2))), rs2, Imm32::from(offset as u32)) 456 | } 457 | 458 | // C.SDSP for RV64/128, C.FSWSP for RV32 (not supported) 459 | 0b111 => { 460 | let css = untyped.css(); 461 | let rs2 = Rs2(gp(css.rs2())); 462 | // offset[5:3|8:6] = isnt[12:10|9:7] 463 | let offset = ((inst >> 1) & 0x1c0) // offset[8:6] 464 | | ((inst >> 7) & 0x38); // offset[5:3] 465 | rv64!(SD, Rs1(Reg::X(Fin::new(2))), rs2, Imm32::from(offset as u32)) 466 | } 467 | 468 | _ => return None, 469 | }, 470 | _ => return None, 471 | }; 472 | Some(instr) 473 | } 474 | 475 | fn fp_3(reg: u8) -> Reg { 476 | let encoding = reg as u8; 477 | match encoding { 478 | 0b000 => Reg::F(Fin::new(8)), 479 | 0b001 => Reg::F(Fin::new(9)), 480 | 0b010 => Reg::F(Fin::new(10)), 481 | 0b011 => Reg::F(Fin::new(11)), 482 | 0b100 => Reg::F(Fin::new(12)), 483 | 0b101 => Reg::F(Fin::new(13)), 484 | 0b110 => Reg::F(Fin::new(14)), 485 | 0b111 => Reg::F(Fin::new(15)), 486 | _ => panic!("Inaccessible register encoding: {:b}", encoding), 487 | } 488 | } 489 | 490 | fn gp_3(reg: u8) -> Reg { 491 | let encoding = reg as u8; 492 | match encoding { 493 | 0b000 => Reg::X(Fin::new(8)), 494 | 0b001 => Reg::X(Fin::new(9)), 495 | 0b010 => Reg::X(Fin::new(10)), 496 | 0b011 => Reg::X(Fin::new(11)), 497 | 0b100 => Reg::X(Fin::new(12)), 498 | 0b101 => Reg::X(Fin::new(13)), 499 | 0b110 => Reg::X(Fin::new(14)), 500 | 0b111 => Reg::X(Fin::new(15)), 501 | _ => panic!("Inaccessible register encoding: {:b}", encoding), 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod data; 2 | pub mod typed; 3 | pub mod rv32; 4 | pub mod rv64; 5 | pub mod untyped; 6 | pub mod decode; 7 | pub mod untyped16; 8 | pub mod decode16; 9 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/rv32.rs: -------------------------------------------------------------------------------- 1 | use crate::isa::typed::{AQ, Imm32, Rd, RL, RoundingMode, Rs1, Rs2, Rs3, Shamt}; 2 | 3 | /// typed RV32 instructions 4 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 5 | #[allow(non_camel_case_types)] 6 | pub enum RV32Instr { 7 | // RV32I 8 | LUI(Rd, Imm32<31, 12>), 9 | AUIPC(Rd, Imm32<31, 12>), 10 | JAL(Rd, Imm32<20, 1>), 11 | JALR(Rd, Rs1, Imm32<11, 0>), 12 | BEQ(Rs1, Rs2, Imm32<12, 1>), 13 | BNE(Rs1, Rs2, Imm32<12, 1>), 14 | BLT(Rs1, Rs2, Imm32<12, 1>), 15 | BGE(Rs1, Rs2, Imm32<12, 1>), 16 | BLTU(Rs1, Rs2, Imm32<12, 1>), 17 | BGEU(Rs1, Rs2, Imm32<12, 1>), 18 | LB(Rd, Rs1, Imm32<11, 0>), 19 | LH(Rd, Rs1, Imm32<11, 0>), 20 | LW(Rd, Rs1, Imm32<11, 0>), 21 | LBU(Rd, Rs1, Imm32<11, 0>), 22 | LHU(Rd, Rs1, Imm32<11, 0>), 23 | SB(Rs1, Rs2, Imm32<11, 0>), 24 | SH(Rs1, Rs2, Imm32<11, 0>), 25 | SW(Rs1, Rs2, Imm32<11, 0>), 26 | ADDI(Rd, Rs1, Imm32<11, 0>), 27 | SLTI(Rd, Rs1, Imm32<11, 0>), 28 | SLTIU(Rd, Rs1, Imm32<11, 0>), 29 | XORI(Rd, Rs1, Imm32<11, 0>), 30 | ORI(Rd, Rs1, Imm32<11, 0>), 31 | ANDI(Rd, Rs1, Imm32<11, 0>), 32 | SLLI(Rd, Rs1, Shamt), 33 | SRLI(Rd, Rs1, Shamt), 34 | SRAI(Rd, Rs1, Shamt), 35 | ADD(Rd, Rs1, Rs2), 36 | SUB(Rd, Rs1, Rs2), 37 | SLL(Rd, Rs1, Rs2), 38 | SLT(Rd, Rs1, Rs2), 39 | SLTU(Rd, Rs1, Rs2), 40 | XOR(Rd, Rs1, Rs2), 41 | SRL(Rd, Rs1, Rs2), 42 | SRA(Rd, Rs1, Rs2), 43 | OR(Rd, Rs1, Rs2), 44 | AND(Rd, Rs1, Rs2), 45 | FENCE(Rd, Rs1, FenceSucc, FencePred, FenceFm), 46 | FENCE_TSO, 47 | PAUSE, 48 | ECALL, 49 | EBREAK, 50 | 51 | // RV32M 52 | MUL(Rd, Rs1, Rs2), 53 | MULH(Rd, Rs1, Rs2), 54 | MULHSU(Rd, Rs1, Rs2), 55 | MULHU(Rd, Rs1, Rs2), 56 | DIV(Rd, Rs1, Rs2), 57 | DIVU(Rd, Rs1, Rs2), 58 | REM(Rd, Rs1, Rs2), 59 | REMU(Rd, Rs1, Rs2), 60 | 61 | // RV32A 62 | LR_W(Rd, Rs1, AQ, RL), 63 | SC_W(Rd, Rs1, Rs2, AQ, RL), 64 | AMOSWAP_W(Rd, Rs1, Rs2, AQ, RL), 65 | AMOADD_W(Rd, Rs1, Rs2, AQ, RL), 66 | AMOXOR_W(Rd, Rs1, Rs2, AQ, RL), 67 | AMOAND_W(Rd, Rs1, Rs2, AQ, RL), 68 | AMOOR_W(Rd, Rs1, Rs2, AQ, RL), 69 | AMOMIN_W(Rd, Rs1, Rs2, AQ, RL), 70 | AMOMAX_W(Rd, Rs1, Rs2, AQ, RL), 71 | AMOMINU_W(Rd, Rs1, Rs2, AQ, RL), 72 | AMOMAXU_W(Rd, Rs1, Rs2, AQ, RL), 73 | 74 | // RV32F 75 | FLW(Rd, Rs1, Imm32<11, 0>), 76 | FSW(Rs1, Rs2, Imm32<11, 0>), 77 | FMADD_S(Rd, Rs1, Rs2, Rs3, RoundingMode), 78 | FMSUB_S(Rd, Rs1, Rs2, Rs3, RoundingMode), 79 | FNMSUB_S(Rd, Rs1, Rs2, Rs3, RoundingMode), 80 | FNMADD_S(Rd, Rs1, Rs2, Rs3, RoundingMode), 81 | FADD_S(Rd, Rs1, Rs2, RoundingMode), 82 | FSUB_S(Rd, Rs1, Rs2, RoundingMode), 83 | FMUL_S(Rd, Rs1, Rs2, RoundingMode), 84 | FDIV_S(Rd, Rs1, Rs2, RoundingMode), 85 | FSQRT_S(Rd, Rs1, RoundingMode), 86 | FSGNJ_S(Rd, Rs1, Rs2), 87 | FSGNJN_S(Rd, Rs1, Rs2), 88 | FSGNJX_S(Rd, Rs1, Rs2), 89 | FMIN_S(Rd, Rs1, Rs2), 90 | FMAX_S(Rd, Rs1, Rs2), 91 | FCVT_W_S(Rd, Rs1, RoundingMode), 92 | FCVT_WU_S(Rd, Rs1, RoundingMode), 93 | FMV_X_W(Rd, Rs1), 94 | FEQ_S(Rd, Rs1, Rs2), 95 | FLT_S(Rd, Rs1, Rs2), 96 | FLE_S(Rd, Rs1, Rs2), 97 | FCLASS_S(Rd, Rs1), 98 | FCVT_S_W(Rd, Rs1, RoundingMode), 99 | FCVT_S_WU(Rd, Rs1, RoundingMode), 100 | FMV_W_X(Rd, Rs1), 101 | 102 | // RV32D 103 | FLD(Rd, Rs1, Imm32<11, 0>), 104 | FSD(Rs1, Rs2, Imm32<11, 0>), 105 | FMADD_D(Rd, Rs1, Rs2, Rs3, RoundingMode), 106 | FMSUB_D(Rd, Rs1, Rs2, Rs3, RoundingMode), 107 | FNMSUB_D(Rd, Rs1, Rs2, Rs3, RoundingMode), 108 | FNMADD_D(Rd, Rs1, Rs2, Rs3, RoundingMode), 109 | FADD_D(Rd, Rs1, Rs2, RoundingMode), 110 | FSUB_D(Rd, Rs1, Rs2, RoundingMode), 111 | FMUL_D(Rd, Rs1, Rs2, RoundingMode), 112 | FDIV_D(Rd, Rs1, Rs2, RoundingMode), 113 | FSQRT_D(Rd, Rs1, RoundingMode), 114 | FSGNJ_D(Rd, Rs1, Rs2), 115 | FSGNJN_D(Rd, Rs1, Rs2), 116 | FSGNJX_D(Rd, Rs1, Rs2), 117 | FMIN_D(Rd, Rs1, Rs2), 118 | FMAX_D(Rd, Rs1, Rs2), 119 | FCVT_S_D(Rd, Rs1, RoundingMode), 120 | FCVT_D_S(Rd, Rs1, RoundingMode), 121 | FEQ_D(Rd, Rs1, Rs2), 122 | FLT_D(Rd, Rs1, Rs2), 123 | FLE_D(Rd, Rs1, Rs2), 124 | FCLASS_D(Rd, Rs1), 125 | FCVT_W_D(Rd, Rs1, RoundingMode), 126 | FCVT_WU_D(Rd, Rs1, RoundingMode), 127 | FCVT_D_W(Rd, Rs1, RoundingMode), 128 | FCVT_D_WU(Rd, Rs1, RoundingMode), 129 | } 130 | 131 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 132 | pub struct FenceFm(pub u32); 133 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 134 | pub struct FencePred(pub u32); 135 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 136 | pub struct FenceSucc(pub u32); 137 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/rv64.rs: -------------------------------------------------------------------------------- 1 | use crate::isa::typed::{AQ, Imm32, Rd, RL, RoundingMode, Rs1, Rs2, Shamt}; 2 | 3 | /// typed RV64 instructions 4 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 5 | #[allow(non_camel_case_types)] 6 | pub enum RV64Instr { 7 | // RV64I 8 | LWU(Rd, Rs1, Imm32<11, 0>), 9 | LD(Rd, Rs1, Imm32<11, 0>), 10 | SD(Rs1, Rs2, Imm32<11, 0>), 11 | SLLI(Rd, Rs1, Shamt), 12 | SRLI(Rd, Rs1, Shamt), 13 | SRAI(Rd, Rs1, Shamt), 14 | ADDIW(Rd, Rs1, Imm32<11, 0>), 15 | SLLIW(Rd, Rs1, Shamt), 16 | SRLIW(Rd, Rs1, Shamt), 17 | SRAIW(Rd, Rs1, Shamt), 18 | ADDW(Rd, Rs1, Rs2), 19 | SUBW(Rd, Rs1, Rs2), 20 | SLLW(Rd, Rs1, Rs2), 21 | SRLW(Rd, Rs1, Rs2), 22 | SRAW(Rd, Rs1, Rs2), 23 | 24 | // RV64M 25 | MULW(Rd, Rs1, Rs2), 26 | DIVW(Rd, Rs1, Rs2), 27 | DIVUW(Rd, Rs1, Rs2), 28 | REMW(Rd, Rs1, Rs2), 29 | REMUW(Rd, Rs1, Rs2), 30 | 31 | // RV64A 32 | LR_D(Rd, Rs1, AQ, RL), 33 | SC_D(Rd, Rs1, Rs2, AQ, RL), 34 | AMOSWAP_D(Rd, Rs1, Rs2, AQ, RL), 35 | AMOADD_D(Rd, Rs1, Rs2, AQ, RL), 36 | AMOXOR_D(Rd, Rs1, Rs2, AQ, RL), 37 | AMOAND_D(Rd, Rs1, Rs2, AQ, RL), 38 | AMOOR_D(Rd, Rs1, Rs2, AQ, RL), 39 | AMOMIN_D(Rd, Rs1, Rs2, AQ, RL), 40 | AMOMAX_D(Rd, Rs1, Rs2, AQ, RL), 41 | AMOMINU_D(Rd, Rs1, Rs2, AQ, RL), 42 | AMOMAXU_D(Rd, Rs1, Rs2, AQ, RL), 43 | 44 | // RV64F 45 | FCVT_L_S(Rd, Rs1, RoundingMode), 46 | FCVT_LU_S(Rd, Rs1, RoundingMode), 47 | FCVT_S_L(Rd, Rs1, RoundingMode), 48 | FCVT_S_LU(Rd, Rs1, RoundingMode), 49 | 50 | // RV64D 51 | FCVT_L_D(Rd, Rs1, RoundingMode), 52 | FCVT_LU_D(Rd, Rs1, RoundingMode), 53 | FMV_X_D(Rd, Rs1), 54 | FCVT_D_L(Rd, Rs1, RoundingMode), 55 | FCVT_D_LU(Rd, Rs1, RoundingMode), 56 | FMV_D_X(Rd, Rs1), 57 | 58 | // RV32/64 Zicsr 59 | CSRRW(Rd, Rs1, CSRAddr), 60 | CSRRS(Rd, Rs1, CSRAddr), 61 | CSRRC(Rd, Rs1, CSRAddr), 62 | CSRRWI(Rd, UImm, CSRAddr), 63 | CSRRSI(Rd, UImm, CSRAddr), 64 | CSRRCI(Rd, UImm, CSRAddr), 65 | 66 | // RV32/64 Zifencei 67 | FENCE_I(Rd, Rs1, Imm32<11, 0>), 68 | 69 | // Privileged 70 | SRET, 71 | MRET, 72 | WFI, 73 | SFENCE_VMA(Rs1, Rs2), 74 | SINVAL_VMA(Rs1, Rs2), 75 | SFENCE_W_INVAL, 76 | SFENCE_INVAL_IR, 77 | } 78 | 79 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 80 | pub struct CSRAddr(pub Imm32<11, 0>); 81 | 82 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 83 | pub struct UImm(pub Imm32<4, 0>); 84 | 85 | impl CSRAddr { 86 | pub fn value(self) -> u16 { 87 | self.0.decode() as u16 88 | } 89 | } 90 | 91 | impl UImm { 92 | pub fn value(self) -> u32 { 93 | self.0.decode() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/typed.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | 3 | use crate::isa::data::{Fin, workaround}; 4 | use crate::isa::rv32::RV32Instr; 5 | use crate::isa::rv64::RV64Instr; 6 | 7 | /// decoded instruction 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub enum Instr { 10 | RV32(RV32Instr), 11 | RV64(RV64Instr), 12 | NOP, 13 | } 14 | 15 | /// Destination register 16 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 17 | pub struct Rd(pub Reg); 18 | /// Source register 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 20 | pub struct Rs1(pub Reg); 21 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 22 | pub struct Rs2(pub Reg); 23 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 24 | pub struct Rs3(pub Reg); 25 | 26 | /// Atomic instruction flag: Acquire 27 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 28 | pub struct AQ(pub bool); 29 | /// Atomic instruction flag: Release 30 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 31 | pub struct RL(pub bool); 32 | 33 | /// Shift-amount 34 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 35 | pub struct Shamt(pub u8); 36 | 37 | /// typed-registers 38 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 39 | pub enum Reg { 40 | /// Same as `X(Fin(0))`, but for better performance 41 | ZERO, 42 | X(Fin<32>), 43 | F(Fin<32>), 44 | PC, 45 | FCSR, 46 | } 47 | 48 | /// Floating-point rounding mode 49 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 50 | #[repr(u8)] 51 | pub enum RoundingMode { 52 | /// Round to nearest, ties to even 53 | RNE = 0b000, 54 | /// Round towards zero 55 | RTZ = 0b001, 56 | /// Round towards -infinity 57 | RDN = 0b010, 58 | /// Round towards +infinity 59 | RUP = 0b011, 60 | /// Round to nearest, ties to max magnitude 61 | RMM = 0b100, 62 | /// In instruction's rm field, select dynamic rounding mode; 63 | /// In Rounding Mode register, reserved. 64 | DYN = 0b111, 65 | } 66 | 67 | /// This is used to represent lazily-decoded immediate value, 68 | /// which is written as `imm[HIGH_BIT:LOW_BIT]` in the risc-v specification. 69 | #[derive(Clone, Copy, Eq, PartialEq)] 70 | pub struct Imm32(pub u32); 71 | 72 | impl Imm32 { 73 | pub fn from(underlying: u32) -> Self { 74 | Self(underlying) 75 | } 76 | 77 | pub fn valid_bits(&self) -> usize { 78 | HIGH_BIT - LOW_BIT + 1 79 | } 80 | 81 | /// Decode the immediate value by placing its valid bits at the range of `[HIGH_BIT, LOW_BIT]` 82 | /// according to the risc-v specification. 83 | pub fn decode(self) -> u32 { 84 | let mask = (1 << self.valid_bits()) - 1; 85 | (self.0 & mask) << LOW_BIT 86 | } 87 | 88 | pub fn decode_sext(self) -> i32 { 89 | sign_extend32(self.decode(), HIGH_BIT) 90 | } 91 | } 92 | 93 | impl Debug for Imm32 { 94 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 95 | write!(f, "Imm({}, sext = {})", self.decode(), self.decode_sext()) 96 | } 97 | } 98 | 99 | /// `sign_bit` The sign bit position of the `data` 100 | #[inline(always)] 101 | fn sign_extend32(data: u32, sign_bit: usize) -> i32 { 102 | ((data << (31 - sign_bit)) as i32) >> (31 - sign_bit) 103 | } 104 | 105 | impl< 106 | const LHS_HIGH_BIT: usize, 107 | const LHS_LOW_BIT: usize, 108 | const RHS_HIGH_BIT: usize, 109 | const RHS_LOW_BIT: usize 110 | > std::ops::BitOr> for Imm32 111 | where workaround::If<{ LHS_HIGH_BIT >= LHS_LOW_BIT }>: workaround::True, 112 | workaround::If<{ RHS_HIGH_BIT >= RHS_LOW_BIT }>: workaround::True, 113 | workaround::If<{ LHS_LOW_BIT - 1 == RHS_HIGH_BIT }>: workaround::True, 114 | { 115 | type Output = Imm32; 116 | 117 | fn bitor(self, rhs: Imm32) -> Self::Output { 118 | Self::Output::from((self.decode() | rhs.decode()) >> RHS_LOW_BIT) 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | use std::ops::BitOr; 125 | 126 | use crate::isa::typed::Imm32; 127 | use crate::isa::untyped::JType; 128 | 129 | #[test] 130 | fn test_imm_decode() { 131 | // jal x0, -24 132 | let instr_asm: u32 = 0b_1_1111110100_1_11111111_00000_1101111; 133 | let instr = JType::from_bytes(instr_asm.to_le_bytes()); 134 | 135 | let imm19_12 = Imm32::<19, 12>::from(instr.imm19_12() as u32); 136 | let imm11 = Imm32::<11, 11>::from(instr.imm11() as u32); 137 | let imm10_1 = Imm32::<10, 1>::from(instr.imm10_1() as u32); 138 | let imm20 = Imm32::<20, 20>::from(instr.imm20() as u32); 139 | let all = imm20.bitor(imm19_12).bitor(imm11).bitor(imm10_1); 140 | 141 | assert_eq!(imm19_12.decode(), 0b00000000000011111111000000000000); 142 | assert_eq!(imm11.decode(), 0b00000000000000000000100000000000); 143 | assert_eq!(imm10_1.decode(), 0b00000000000000000000011111101000); 144 | assert_eq!(imm20.decode(), 0b00000000000100000000000000000000); 145 | 146 | let all_u32 = imm20.decode() | imm19_12.decode() | imm11.decode() | imm10_1.decode(); 147 | assert_eq!(all.decode(), all_u32); 148 | } 149 | 150 | #[test] 151 | fn test_jal_decode() { 152 | let instr_asm: u32 = 0x760c30ef; 153 | let instr = JType::from_bytes(instr_asm.to_le_bytes()); 154 | 155 | let imm19_12 = Imm32::<19, 12>::from(instr.imm19_12() as u32); 156 | let imm11 = Imm32::<11, 11>::from(instr.imm11() as u32); 157 | let imm10_1 = Imm32::<10, 1>::from(instr.imm10_1() as u32); 158 | let imm20 = Imm32::<20, 20>::from(instr.imm20() as u32); 159 | let all = imm20.bitor(imm19_12).bitor(imm11).bitor(imm10_1); 160 | 161 | let all_u32 = imm20.decode() | imm19_12.decode() | imm11.decode() | imm10_1.decode(); 162 | assert_eq!(all.decode(), all_u32); 163 | 164 | // imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12] 165 | let instr_asm = instr_asm as u64; 166 | let offset = (((instr_asm & 0x80000000) as i32 as i64 >> 11) as u64) // imm[20] 167 | | (instr_asm & 0xff000) // imm[19:12] 168 | | ((instr_asm >> 9) & 0x800) // imm[11] 169 | | ((instr_asm >> 20) & 0x7fe); // imm[10:1] 170 | 171 | dbg!(all.decode_sext()); 172 | dbg!(all.decode()); 173 | dbg!(offset); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/untyped.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | 3 | use modular_bitfield::prelude::*; 4 | 5 | #[bitfield(bits = 32)] 6 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 7 | pub struct RType { 8 | pub opcode: B7, 9 | pub rd: B5, 10 | pub funct3: B3, 11 | pub rs1: B5, 12 | pub rs2: B5, 13 | pub funct7: B7, 14 | } 15 | 16 | #[bitfield(bits = 32)] 17 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 18 | pub struct IType { 19 | pub opcode: B7, 20 | pub rd: B5, 21 | pub funct3: B3, 22 | pub rs1: B5, 23 | pub imm11_0: B12, 24 | } 25 | 26 | #[bitfield(bits = 32)] 27 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 28 | pub struct SType { 29 | pub opcode: B7, 30 | pub imm4_0: B5, 31 | pub funct3: B3, 32 | pub rs1: B5, 33 | pub rs2: B5, 34 | pub imm11_5: B7, 35 | } 36 | 37 | #[bitfield(bits = 32)] 38 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 39 | pub struct BType { 40 | pub opcode: B7, 41 | pub imm11: B1, 42 | pub imm4_1: B4, 43 | pub funct3: B3, 44 | pub rs1: B5, 45 | pub rs2: B5, 46 | pub imm10_5: B6, 47 | pub imm12: B1, 48 | } 49 | 50 | #[bitfield(bits = 32)] 51 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 52 | pub struct UType { 53 | pub opcode: B7, 54 | pub rd: B5, 55 | pub imm31_12: B20, 56 | } 57 | 58 | #[bitfield(bits = 32)] 59 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 60 | pub struct JType { 61 | pub opcode: B7, 62 | pub rd: B5, 63 | pub imm19_12: B8, 64 | pub imm11: B1, 65 | pub imm10_1: B10, 66 | pub imm20: B1, 67 | } 68 | 69 | #[bitfield(bits = 32)] 70 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 71 | pub struct R4Type { 72 | pub opcode: B7, 73 | pub rd: B5, 74 | pub funct3: B3, 75 | pub rs1: B5, 76 | pub rs2: B5, 77 | pub funct2: B2, 78 | pub rs3: B5, 79 | } 80 | 81 | #[bitfield(bits = 32)] 82 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 83 | pub struct RShamt32Type { 84 | pub opcode: B7, 85 | pub rd: B5, 86 | pub funct3: B3, 87 | pub rs1: B5, 88 | pub shamt: B5, 89 | pub funct7: B7, 90 | } 91 | 92 | #[bitfield(bits = 32)] 93 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 94 | pub struct RAType { 95 | pub opcode: B7, 96 | pub rd: B5, 97 | pub funct3: B3, 98 | pub rs1: B5, 99 | pub rs2: B5, 100 | pub rl: bool, 101 | pub aq: bool, 102 | pub funct5: B5, 103 | } 104 | 105 | #[bitfield(bits = 32)] 106 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 107 | pub struct RShamt64Type { 108 | pub opcode: B7, 109 | pub rd: B5, 110 | pub funct3: B3, 111 | pub rs1: B5, 112 | pub shamt: B6, 113 | pub funct6: B6, 114 | } 115 | 116 | #[bitfield(bits = 32)] 117 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 118 | pub struct FenceType { 119 | pub opcode: B7, 120 | pub rd: B5, 121 | pub funct3: B3, 122 | pub rs1: B5, 123 | pub succ: B4, 124 | pub pred: B4, 125 | pub fm: B4, 126 | } 127 | 128 | #[bitfield(bits = 32)] 129 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 130 | pub struct OpcodePeek { 131 | pub opcode: B7, 132 | pub dummy: B25, 133 | } 134 | 135 | /// untyped instruction 136 | #[derive(Clone, Copy)] 137 | #[repr(C)] 138 | pub union Bytecode { 139 | pub repr: u32, 140 | pub peek: OpcodePeek, 141 | pub r: RType, 142 | pub r4: R4Type, 143 | pub r_shamt32: RShamt32Type, 144 | pub r_shamt64: RShamt64Type, 145 | pub ra: RAType, 146 | pub i: IType, 147 | pub s: SType, 148 | pub b: BType, 149 | pub u: UType, 150 | pub j: JType, 151 | pub fence: FenceType, 152 | } 153 | 154 | #[macro_export] macro_rules! unsafe_wrapper { 155 | ($ident:ident, $ty:ident) => { 156 | #[inline(always)] 157 | pub fn $ident(&self) -> $ty { 158 | unsafe { self.$ident } 159 | } 160 | } 161 | } 162 | 163 | impl Bytecode { 164 | #[inline(always)] 165 | pub fn opcode(&self) -> u8 { 166 | unsafe { self.peek.opcode() } 167 | } 168 | unsafe_wrapper!(repr, u32); 169 | unsafe_wrapper!(r, RType); 170 | unsafe_wrapper!(r4, R4Type); 171 | unsafe_wrapper!(r_shamt32, RShamt32Type); 172 | unsafe_wrapper!(r_shamt64, RShamt64Type); 173 | unsafe_wrapper!(ra, RAType); 174 | unsafe_wrapper!(i, IType); 175 | unsafe_wrapper!(s, SType); 176 | unsafe_wrapper!(b, BType); 177 | unsafe_wrapper!(j, JType); 178 | unsafe_wrapper!(u, UType); 179 | unsafe_wrapper!(fence, FenceType); 180 | } 181 | 182 | impl Debug for Bytecode { 183 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 184 | write!(f, "Bytecode({:#010b})", self.repr()) 185 | } 186 | } 187 | 188 | impl PartialEq for Bytecode { 189 | fn eq(&self, other: &Self) -> bool { 190 | self.repr() == other.repr() 191 | } 192 | } 193 | 194 | impl Eq for Bytecode {} 195 | 196 | #[cfg(test)] 197 | mod tests { 198 | use crate::isa::untyped::JType; 199 | 200 | #[test] 201 | fn test_layout() { 202 | // jal x0, -6*4 203 | let instr_asm: u32 = 0b_1_1111110100_1_11111111_00000_1101111; 204 | let instr = JType::from_bytes(instr_asm.to_le_bytes()); 205 | assert_eq!(instr.opcode(), 0b_1101111); 206 | assert_eq!(instr.rd(), 0b0); 207 | assert_eq!(instr.imm19_12(), 0b11111111); 208 | assert_eq!(instr.imm11(), 0b1); 209 | assert_eq!(instr.imm10_1(), 0b1111110100); 210 | assert_eq!(instr.imm20(), 0b1); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /valheim-asm/src/isa/untyped16.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | 3 | use modular_bitfield::prelude::*; 4 | 5 | use crate::unsafe_wrapper; 6 | 7 | #[bitfield(bits = 16)] 8 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 9 | pub struct CRType { 10 | pub op: B2, 11 | pub rs2: B5, 12 | pub rd_or_rs1: B5, 13 | pub funct4: B4, 14 | } 15 | 16 | #[bitfield(bits = 16)] 17 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 18 | pub struct CIType { 19 | pub op: B2, 20 | pub imm_b5: B5, 21 | pub rd: B5, 22 | pub imm_b1: B1, 23 | pub funct3: B3, 24 | } 25 | 26 | #[bitfield(bits = 16)] 27 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 28 | pub struct CSSType { 29 | pub op: B2, 30 | pub rs2: B5, 31 | pub imm_b6: B6, 32 | pub funct3: B3, 33 | } 34 | 35 | #[bitfield(bits = 16)] 36 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 37 | pub struct CIWType { 38 | pub op: B2, 39 | pub rd: B3, 40 | pub imm_b8: B8, 41 | pub funct3: B3, 42 | } 43 | 44 | #[bitfield(bits = 16)] 45 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 46 | pub struct CLType { 47 | pub op: B2, 48 | pub rd: B3, 49 | pub imm_b2: B2, 50 | pub rs1: B3, 51 | pub imm_b3: B3, 52 | pub funct3: B3, 53 | } 54 | 55 | #[bitfield(bits = 16)] 56 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 57 | pub struct CSType { 58 | pub op: B2, 59 | pub rs2: B3, 60 | pub imm_b2: B2, 61 | pub rs1: B3, 62 | pub imm_b3: B3, 63 | pub funct3: B3, 64 | } 65 | 66 | #[bitfield(bits = 16)] 67 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 68 | pub struct CAType { 69 | pub op: B2, 70 | pub rs2: B3, 71 | pub funct2: B2, 72 | pub rd_or_rs1: B3, 73 | pub funct6: B6, 74 | } 75 | 76 | #[bitfield(bits = 16)] 77 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 78 | pub struct CBType { 79 | pub op: B2, 80 | pub imm5: B1, 81 | pub imm2_1: B2, 82 | pub imm7_6: B2, 83 | pub rd_or_rs1: B3, 84 | pub imm4_3: B2, 85 | pub imm8: B1, 86 | pub funct3: B3, 87 | } 88 | 89 | #[bitfield(bits = 16)] 90 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 91 | pub struct CBIType { 92 | pub op: B2, 93 | pub imm4_0: B5, 94 | pub rd_or_rs1: B3, 95 | pub funct2: B2, 96 | pub imm5: B1, 97 | pub funct3: B3, 98 | } 99 | 100 | #[bitfield(bits = 16)] 101 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 102 | pub struct CJType { 103 | pub op: B2, 104 | pub imm5: B1, 105 | pub imm3_1: B3, 106 | pub imm7: B1, 107 | pub imm6: B1, 108 | pub imm10: B1, 109 | pub imm9_8: B2, 110 | pub imm4: B1, 111 | pub imm11: B1, 112 | pub funct3: B3, 113 | } 114 | 115 | #[bitfield(bits = 16)] 116 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 117 | pub struct OpcodePeek16 { 118 | pub op: B2, 119 | pub dummy: B11, 120 | pub funct3: B3, 121 | } 122 | 123 | /// untyped instruction 124 | #[derive(Clone, Copy)] 125 | #[repr(C)] 126 | pub union Bytecode16 { 127 | pub repr: u16, 128 | pub peek: OpcodePeek16, 129 | pub cr: CRType, 130 | pub ci: CIType, 131 | pub css: CSSType, 132 | pub ciw: CIWType, 133 | pub cl: CLType, 134 | pub cs: CSType, 135 | pub ca: CAType, 136 | pub cb: CBType, 137 | pub cbi: CBIType, 138 | pub cj: CJType, 139 | } 140 | 141 | impl Bytecode16 { 142 | #[inline(always)] 143 | pub fn opcode(&self) -> u8 { 144 | unsafe { self.peek.op() } 145 | } 146 | #[inline(always)] 147 | pub fn funct3(&self) -> u8 { 148 | unsafe { self.peek.funct3() } 149 | } 150 | unsafe_wrapper!(repr, u16); 151 | unsafe_wrapper!(cr, CRType); 152 | unsafe_wrapper!(ci, CIType); 153 | unsafe_wrapper!(css, CSSType); 154 | unsafe_wrapper!(ciw, CIWType); 155 | unsafe_wrapper!(cl, CLType); 156 | unsafe_wrapper!(cs, CSType); 157 | unsafe_wrapper!(ca, CAType); 158 | unsafe_wrapper!(cb, CBType); 159 | unsafe_wrapper!(cbi, CBIType); 160 | unsafe_wrapper!(cj, CJType); 161 | } 162 | 163 | impl Debug for Bytecode16 { 164 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 165 | write!(f, "Bytecode16({:#06b})", self.repr()) 166 | } 167 | } 168 | 169 | impl PartialEq for Bytecode16 { 170 | fn eq(&self, other: &Self) -> bool { 171 | self.repr() == other.repr() 172 | } 173 | } 174 | 175 | impl Eq for Bytecode16 {} 176 | -------------------------------------------------------------------------------- /valheim-asm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | 3 | #![feature(core_intrinsics)] 4 | #![feature(generic_const_exprs)] 5 | #![feature(inline_const_pat)] 6 | 7 | pub mod asm; 8 | pub mod isa; 9 | -------------------------------------------------------------------------------- /valheim-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "valheim-cli" 3 | version = "0.2.0" 4 | edition = "2021" 5 | repository = "https://github.com/imkiva/valheim" 6 | description = "RISC-V emulator command-line interface" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | valheim-core = { path = "../valheim-core", version = "0.2.0" } 11 | clap = { version = "3.1.6", features = ["derive"] } 12 | -------------------------------------------------------------------------------- /valheim-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | 4 | use clap::Parser; 5 | 6 | use valheim_core::machine::Machine; 7 | 8 | #[derive(clap::Parser, Debug)] 9 | #[clap(author, version, about, long_about = None)] 10 | struct Args { 11 | #[clap(short, long)] 12 | pub kernel: String, 13 | #[clap(short, long)] 14 | pub bios: Option, 15 | #[clap(short, long)] 16 | pub cmdline: Option, 17 | #[clap(short, long)] 18 | pub disk: Option, 19 | #[clap(long)] 20 | pub trace: Option, 21 | #[clap(long)] 22 | pub test: bool, 23 | #[clap(long)] 24 | pub test_name: Option, 25 | } 26 | 27 | fn main() -> Result<(), std::io::Error> { 28 | let args = Args::parse(); 29 | let mut machine = Machine::new(args.cmdline, args.trace); 30 | 31 | let kernel = read_image(&args.kernel)?; 32 | let bios = args.bios.and_then(|bios| read_image(&bios).ok()); 33 | 34 | match bios { 35 | Some(bios) => { 36 | machine.load_memory(0x80000000, bios.as_slice()); 37 | machine.load_memory(0x80200000, kernel.as_slice()); 38 | } 39 | None => { 40 | machine.load_memory(0x80000000, kernel.as_slice()); 41 | } 42 | } 43 | 44 | if let Some(disk_file) = args.disk { 45 | machine.load_disk_file(disk_file)?; 46 | } 47 | 48 | match args.test { 49 | true => std::process::exit(machine.run_for_test(args.test_name.unwrap_or("".to_string()))), 50 | false => machine.run(), 51 | } 52 | Ok(()) 53 | } 54 | 55 | fn read_image(image: &str) -> Result, std::io::Error> { 56 | let mut file = match File::open(&image) { 57 | Ok(file) => file, 58 | Err(err) => { 59 | eprintln!("Error opening image file: {}", err); 60 | return Err(err); 61 | } 62 | }; 63 | let mut bytes = vec![]; 64 | file.read_to_end(&mut bytes).expect("Failed to read image file"); 65 | Ok(bytes) 66 | } 67 | -------------------------------------------------------------------------------- /valheim-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "valheim-core" 3 | version = "0.2.0" 4 | edition = "2021" 5 | repository = "https://github.com/imkiva/valheim" 6 | description = "RISC-V emulator core" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | valheim-asm = { path = "../valheim-asm", version = "0.2.0" } 11 | memmap2 = "0.5.3" 12 | derive_more = "0.99.0" 13 | rustc_apfloat = "0.1.3" 14 | 15 | [features] 16 | default = [] 17 | trace = [] 18 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/bus.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::fmt::{Debug, Formatter}; 3 | use std::sync::Arc; 4 | 5 | use crate::cpu::irq::Exception; 6 | use crate::device::clint::Clint; 7 | use crate::device::Device; 8 | use crate::device::plic::Plic; 9 | use crate::device::virtio::Virtio; 10 | use crate::memory::{CanIO, Memory, VirtAddr}; 11 | 12 | pub const RV64_MEMORY_BASE: u64 = 0x80000000; 13 | pub const RV64_MEMORY_SIZE: u64 = 128 * 1024 * 1024; 14 | pub const RV64_MEMORY_END: u64 = RV64_MEMORY_BASE + RV64_MEMORY_SIZE; 15 | 16 | pub const VIRT_MROM_BASE: u64 = 0x1000; 17 | pub const VIRT_MROM_SIZE: u64 = 0xf000; 18 | pub const VIRT_MROM_END: u64 = VIRT_MROM_BASE + VIRT_MROM_SIZE; 19 | 20 | pub const CLINT_BASE: u64 = 0x2000000; 21 | pub const CLINT_SIZE: u64 = 0x10000; 22 | pub const CLINT_END: u64 = CLINT_BASE + CLINT_SIZE; 23 | 24 | pub const PLIC_BASE: u64 = 0xc00_0000; 25 | pub const PLIC_SIZE: u64 = 0x208000; 26 | pub const PLIC_END: u64 = PLIC_BASE + PLIC_SIZE; 27 | 28 | /// The address which virtio starts. 29 | pub const VIRTIO_BASE: u64 = 0x1000_1000; 30 | pub const VIRTIO_SIZE: u64 = 0x1000; 31 | pub const VIRTIO_END: u64 = VIRTIO_BASE + VIRTIO_SIZE; 32 | 33 | /// System Bus, which handles DRAM access and memory-mapped IO. 34 | /// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c 35 | /// Builtin IO maps: 36 | /// - 0x1000 - 0x1000 + 0xf000 ==== Virt_MROM, like device trees 37 | /// - 0x0x2000000 - 0x2000000 + 0x10000 ==== CLINT 38 | pub struct Bus { 39 | pub mem: Memory, 40 | pub devices: Vec>, 41 | pub io_map: BTreeMap<(VirtAddr, VirtAddr), usize>, 42 | 43 | // Builtin IO devices 44 | pub device_tree: Memory, 45 | pub clint: Clint, 46 | pub plic: Plic, 47 | pub virtio: Virtio, 48 | } 49 | 50 | impl Debug for Bus { 51 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 52 | write!( 53 | f, 54 | "Bus {{ mem: {:?}, devices: {:?} }}", 55 | self.mem, 56 | self.devices.iter().map(|dev| dev.name()).collect::>(), 57 | ) 58 | } 59 | } 60 | 61 | impl Bus { 62 | pub fn new() -> Result { 63 | Ok(Bus { 64 | mem: Memory::new(RV64_MEMORY_BASE, RV64_MEMORY_SIZE as usize)?, 65 | devices: Vec::with_capacity(8), 66 | io_map: BTreeMap::new(), 67 | device_tree: Memory::new(VIRT_MROM_BASE, VIRT_MROM_SIZE as usize)?, 68 | clint: Clint::new(), 69 | plic: Plic::new(), 70 | virtio: Virtio::new(0), 71 | }) 72 | } 73 | 74 | pub unsafe fn add_device(&mut self, device: Arc) -> Result<(), ()> { 75 | let ranges = device.init()?; 76 | let idx = self.devices.len(); 77 | self.devices.push(device); 78 | for range in ranges { 79 | self.io_map.insert(range, idx); 80 | } 81 | Ok(()) 82 | } 83 | 84 | pub fn halt(&mut self) { 85 | self.devices.iter().for_each(|dev| match dev.destroy() { 86 | Ok(_) => (), 87 | Err(_) => eprintln!("Error destroying device: {}", (*dev).name()), 88 | }); 89 | } 90 | 91 | pub fn read(&self, addr: VirtAddr) -> Result { 92 | // fast-path for builtin io devices 93 | match addr.0 { 94 | RV64_MEMORY_BASE..=RV64_MEMORY_END => self.mem.read::(addr).ok_or(Exception::LoadAccessFault(addr)), 95 | VIRT_MROM_BASE..=VIRT_MROM_END => self.device_tree.read::(addr).ok_or(Exception::LoadAccessFault(addr)), 96 | CLINT_BASE..=CLINT_END => Ok(Bus::safe_reinterpret_as_T(self.clint.read::(addr)?)), 97 | PLIC_BASE..=PLIC_END => { 98 | assert_eq!(std::mem::size_of::(), 4); 99 | let val = self.plic.read(addr)?; 100 | Ok(Bus::safe_reinterpret_as_T(val as u64)) 101 | } 102 | VIRTIO_BASE..=VIRTIO_END => Ok(Bus::safe_reinterpret_as_T(self.virtio.read::(addr)? as u64)), 103 | 104 | _ => match self.select_device_for_read(addr) { 105 | Some(dev) => match std::mem::size_of::() { 106 | 1 => Ok(Bus::safe_reinterpret_as_T(dev.read(addr).ok_or(Exception::LoadAccessFault(addr))? as u64)), 107 | 2 => Ok(Bus::safe_reinterpret_as_T(dev.read16(addr).ok_or(Exception::LoadAccessFault(addr))? as u64)), 108 | 4 => Ok(Bus::safe_reinterpret_as_T(dev.read32(addr).ok_or(Exception::LoadAccessFault(addr))? as u64)), 109 | 8 => Ok(Bus::safe_reinterpret_as_T(dev.read64(addr).ok_or(Exception::LoadAccessFault(addr))? as u64)), 110 | _ => Err(Exception::LoadAccessFault(addr)), 111 | } 112 | None => Err(Exception::LoadAccessFault(addr)), 113 | }, 114 | } 115 | } 116 | 117 | pub fn write(&mut self, addr: VirtAddr, val: T) -> Result<(), Exception> { 118 | // fast-path for builtin io devices 119 | match addr.0 { 120 | RV64_MEMORY_BASE..=RV64_MEMORY_END => self.mem.write::(addr, val).ok_or(Exception::StoreAccessFault(addr)), 121 | VIRT_MROM_BASE..=VIRT_MROM_END => self.device_tree.write::(addr, val).ok_or(Exception::StoreAccessFault(addr)), 122 | CLINT_BASE..=CLINT_END => self.clint.write::(addr, Bus::safe_reinterpret_as_u64(val)), 123 | PLIC_BASE..=PLIC_END => { 124 | assert_eq!(std::mem::size_of::(), 4); 125 | let val = Bus::safe_reinterpret_as_u64(val) as u32; 126 | self.plic.write(addr, val) 127 | } 128 | VIRTIO_BASE..=VIRTIO_END => self.virtio.write::(addr, Bus::safe_reinterpret_as_u64(val) as u32), 129 | 130 | _ => match self.select_device_for_write(addr) { 131 | Some(dev) => match std::mem::size_of::() { 132 | 1 => dev.write(addr, Bus::safe_reinterpret_as_u64(val) as u8).map_err(|_| Exception::StoreAccessFault(addr)), 133 | 2 => dev.write16(addr, Bus::safe_reinterpret_as_u64(val) as u16).map_err(|_| Exception::StoreAccessFault(addr)), 134 | 4 => dev.write32(addr, Bus::safe_reinterpret_as_u64(val) as u32).map_err(|_| Exception::StoreAccessFault(addr)), 135 | 8 => dev.write64(addr, Bus::safe_reinterpret_as_u64(val) as u64).map_err(|_| Exception::StoreAccessFault(addr)), 136 | _ => Err(Exception::StoreAccessFault(addr)), 137 | } 138 | None => Err(Exception::StoreAccessFault(addr)), 139 | }, 140 | } 141 | } 142 | 143 | #[allow(non_snake_case)] 144 | fn safe_reinterpret_as_T(val: u64) -> T { 145 | // CanIO trait guarantees that the transmute is safe 146 | debug_assert!(std::mem::size_of::() <= std::mem::size_of::()); 147 | unsafe { *std::mem::transmute::<*const u64, *const T>(&val as *const u64) } 148 | } 149 | 150 | fn safe_reinterpret_as_u64(val: T) -> u64 { 151 | // CanIO trait guarantees that the transmute is safe 152 | match std::mem::size_of::() { 153 | 1 => (unsafe { *std::mem::transmute::<*const T, *const u8>(&val as *const T) }) as u64, 154 | 2 => (unsafe { *std::mem::transmute::<*const T, *const u16>(&val as *const T) }) as u64, 155 | 4 => (unsafe { *std::mem::transmute::<*const T, *const u32>(&val as *const T) }) as u64, 156 | 8 => (unsafe { *std::mem::transmute::<*const T, *const u64>(&val as *const T) }), 157 | _ => panic!("Invalid size for CanIO trait"), 158 | } 159 | } 160 | 161 | fn select_device_for_read(&self, addr: VirtAddr) -> Option> { 162 | for ((base, end), dev_id) in self.io_map.iter() { 163 | if addr >= *base && addr < *end { 164 | return match self.devices.get(*dev_id) { 165 | Some(dev) => Some(dev.clone()), 166 | None => None, 167 | }; 168 | } 169 | } 170 | None 171 | } 172 | 173 | fn select_device_for_write(&mut self, addr: VirtAddr) -> Option> { 174 | for ((base, end), dev_id) in self.io_map.iter_mut() { 175 | if addr >= *base && addr <= *end { 176 | return match self.devices.get(*dev_id) { 177 | Some(dev) => Some(dev.clone()), 178 | None => None, 179 | }; 180 | } 181 | } 182 | None 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/csr.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use valheim_asm::isa::rv64::CSRAddr; 4 | 5 | use crate::cpu::csr::CSRMap::{MEIP_MASK, MISA, MSIP_MASK, MSTATUS, MTIP_MASK, SEIP_MASK, SSIP_MASK, SSTATUS, STIP_MASK}; 6 | use crate::cpu::irq::Exception; 7 | use crate::cpu::mmu::{SATP64_MODE_MASK, SATP64_MODE_SHIFT, VM_V20211203_SV64}; 8 | use crate::cpu::PrivilegeMode; 9 | 10 | pub const MXLEN: usize = 64; 11 | pub const CSR_MAX: usize = 1 << 12; 12 | 13 | pub const VALHEIM_MISA: u64 = (2 << 62) // MXL[1:0]=2 (XLEN is 64) 14 | | (1 << 20) // Extensions[20] (User mode implemented) 15 | | (1 << 18) // Extensions[18] (Supervisor mode implemented) 16 | | (1 << 8) // Extensions[8] (I Base Instruction Set) 17 | | (1 << 12) // Extensions[12] (M extension) 18 | | (1 << 0) // Extensions[0] (A extension) 19 | | (1 << 5) // Extensions[5] (F extension) 20 | | (1 << 3) // Extensions[3] (D extension) 21 | | (1 << 2) // Extensions[2] (C extension) 22 | ; 23 | 24 | // WARL (Write Any Read Legal) restrictions on CSRs 25 | // https://github.com/qemu/qemu/blob/master/target/riscv/csr.c 26 | const M_MODE_INTERRUPTS: u64 = MSIP_MASK | MTIP_MASK | MEIP_MASK; 27 | const S_MODE_INTERRUPTS: u64 = SSIP_MASK | STIP_MASK | SEIP_MASK; 28 | const DELEGABLE_INTERRUPTS: u64 = S_MODE_INTERRUPTS; 29 | 30 | #[allow(non_snake_case)] 31 | #[allow(non_upper_case_globals)] 32 | pub mod CSRMap { 33 | // Unprivileged CSR addresses 34 | 35 | // User floating-point CSRs. 36 | /// Floating-point accrued exceptions. 37 | pub const FFLAGS: u16 = 0x001; 38 | /// Floating-point dynamic rounding mode. 39 | pub const FRM: u16 = 0x002; 40 | /// Floating-point control and status register (frm + fflags). 41 | pub const FCSR: u16 = 0x003; 42 | 43 | // Floating-point inexact 44 | pub const FCSR_NX_MASK: u64 = 1 << 0; 45 | // Floating-point underflow 46 | pub const FCSR_UF_MASK: u64 = 1 << 1; 47 | // Floating-point overflow 48 | pub const FCSR_OF_MASK: u64 = 1 << 2; 49 | // Floating-point divide-by-zero 50 | pub const FCSR_DZ_MASK: u64 = 1 << 3; 51 | // Floating-point invalid operation 52 | pub const FCSR_NV_MASK: u64 = 1 << 4; 53 | 54 | // User Counter/Timers. 55 | /// Cycle counter for RDCYCLE instruction. 56 | pub const CYCLE: u16 = 0xc00; 57 | /// Timer for RDTIME instruction. 58 | pub const TIME: u16 = 0xc01; 59 | 60 | // Supervisor-level CSR addresses 61 | 62 | // Supervisor trap setup. 63 | /// Supervisor status register. 64 | pub const SSTATUS: u16 = 0x100; 65 | /// Supervisor interrupt-enable register. 66 | pub const SIE: u16 = 0x104; 67 | /// Supervisor trap handler base address. 68 | pub const STVEC: u16 = 0x105; 69 | /// Supervisor counter enable 70 | pub const SCOUNTEREN: u16 = 0x106; 71 | 72 | // Supervisor configuration. 73 | /// Supervisor environment configuration register. 74 | pub const SENVCFG: u16 = 0x10a; 75 | 76 | // Supervisor trap handling. 77 | /// Scratch register for supervisor trap handlers. 78 | pub const SSCRATCH: u16 = 0x140; 79 | /// Supervisor exception program counter. 80 | pub const SEPC: u16 = 0x141; 81 | /// Supervisor trap cause. 82 | pub const SCAUSE: u16 = 0x142; 83 | /// Supervisor bad address or instruction. 84 | pub const STVAL: u16 = 0x143; 85 | /// Supervisor interrupt pending. 86 | pub const SIP: u16 = 0x144; 87 | 88 | // Supervisor protection and translation. 89 | 90 | /// Supervisor address translation and protection. 91 | pub const SATP: u16 = 0x180; 92 | 93 | // Debug/Trace registers 94 | pub const SCONTEXT: u16 = 0x5a8; 95 | 96 | // SSTATUS fields. 97 | pub const SSTATUS_SIE: u64 = 0x2; 98 | // sstatus[1] 99 | pub const SSTATUS_SPIE: u64 = 0x20; 100 | // sstatus[5] 101 | pub const SSTATUS_UBE: u64 = 0x40; 102 | // sstatus[6] 103 | pub const SSTATUS_SPP: u64 = 0x100; 104 | // sstatus[8] 105 | pub const SSTATUS_FS: u64 = 0x6000; 106 | // sstatus[14:13] 107 | pub const SSTATUS_XS: u64 = 0x18000; 108 | // sstatus[16:15] 109 | pub const SSTATUS_SUM: u64 = 0x40000; 110 | // sstatus[18] 111 | pub const SSTATUS_MXR: u64 = 0x80000; 112 | // sstatus[19] 113 | pub const SSTATUS_UXL: u64 = 0x3_00000000; 114 | // sstatus[33:32] 115 | pub const SSTATUS_SD: u64 = 0x80000000_00000000; 116 | // sstatus[63] 117 | pub const SSTATUS_MASK: u64 = SSTATUS_SIE 118 | | SSTATUS_SPIE 119 | | SSTATUS_UBE 120 | | SSTATUS_SPP 121 | | SSTATUS_FS 122 | | SSTATUS_XS 123 | | SSTATUS_SUM 124 | | SSTATUS_MXR 125 | | SSTATUS_UXL 126 | | SSTATUS_SD; 127 | 128 | // Machine-level CSR addresses 129 | 130 | // Machine information registers. 131 | 132 | /// Vendor ID. 133 | pub const MVENDORID: u16 = 0xf11; 134 | /// Architecture ID. 135 | pub const MARCHID: u16 = 0xf12; 136 | /// Implementation ID. 137 | pub const MIMPID: u16 = 0xf13; 138 | /// Hardware thread ID. 139 | pub const MHARTID: u16 = 0xf14; 140 | 141 | // Machine trap setup. 142 | 143 | /// Machine status register. 144 | pub const MSTATUS: u16 = 0x300; 145 | /// ISA and extensions. 146 | pub const MISA: u16 = 0x301; 147 | /// Machine exception delegate register. 148 | pub const MEDELEG: u16 = 0x302; 149 | /// Machine interrupt delegate register. 150 | pub const MIDELEG: u16 = 0x303; 151 | /// Machine interrupt-enable register. 152 | pub const MIE: u16 = 0x304; 153 | /// Machine trap-handler base address. 154 | pub const MTVEC: u16 = 0x305; 155 | /// Machine counter enable. 156 | pub const MCOUNTEREN: u16 = 0x306; 157 | 158 | // Machine trap handling. 159 | /// Scratch register for machine trap handlers. 160 | pub const MSCRATCH: u16 = 0x340; 161 | /// Machine exception program counter. 162 | pub const MEPC: u16 = 0x341; 163 | /// Machine trap cause. 164 | pub const MCAUSE: u16 = 0x342; 165 | /// Machine bad address or instruction. 166 | pub const MTVAL: u16 = 0x343; 167 | /// Machine interrupt pending. 168 | pub const MIP: u16 = 0x344; 169 | /// Machine trap instruction (transformed) 170 | pub const MTINST: u16 = 0x34a; 171 | /// Machine bad guest physical address. 172 | pub const MTVAL2: u16 = 0x34b; 173 | 174 | // Machine configuration. 175 | 176 | /// Machine environment configuration register. 177 | pub const MENVCFG: u16 = 0x30a; 178 | /// Machine secure configuration register. 179 | pub const MSECCFG: u16 = 0x747; 180 | 181 | // Machine memory protection. 182 | 183 | /// Physical memory protection configuration. includes PMPCFG0, 2, 4, 6, ... 14 for RV64 184 | pub const PMPCFG0: u16 = 0x3a0; 185 | /// Physical memory protection address register. from PMPADDR1 to PMPADDR63 186 | pub const PMPADDR0: u16 = 0x3b0; 187 | 188 | // Machine Counter/Timers 189 | 190 | /// Machine cycle counter 191 | pub const MCYCLE: u16 = 0xb00; 192 | 193 | // Standard portion of mip 194 | /// Supervisor software interrupt. 195 | pub const SSIP_MASK: u64 = 1 << 1; 196 | /// Machine software interrupt. 197 | pub const MSIP_MASK: u64 = 1 << 3; 198 | /// Supervisor timer interrupt. 199 | pub const STIP_MASK: u64 = 1 << 5; 200 | /// Machine timer interrupt. 201 | pub const MTIP_MASK: u64 = 1 << 7; 202 | /// Supervisor external interrupt. 203 | pub const SEIP_MASK: u64 = 1 << 9; 204 | /// Machine external interrupt. 205 | pub const MEIP_MASK: u64 = 1 << 11; 206 | 207 | // Standard portion of mie 208 | /// Supervisor software interrupt. 209 | pub const SSIE_MASK: u64 = 1 << 1; 210 | /// Machine software interrupt. 211 | pub const MSIE_MASK: u64 = 1 << 3; 212 | /// Supervisor timer interrupt. 213 | pub const STIE_MASK: u64 = 1 << 5; 214 | /// Machine timer interrupt. 215 | pub const MTIE_MASK: u64 = 1 << 7; 216 | /// Supervisor external interrupt. 217 | pub const SEIE_MASK: u64 = 1 << 9; 218 | /// Machine external interrupt. 219 | pub const MEIE_MASK: u64 = 1 << 11; 220 | } 221 | 222 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 223 | pub struct CSRRegs { 224 | pub csrs: [u64; CSR_MAX], 225 | } 226 | 227 | impl CSRRegs { 228 | pub fn new() -> Self { 229 | let mut csrs = [0; CSR_MAX]; 230 | csrs[MISA as usize] = VALHEIM_MISA; 231 | Self { csrs } 232 | } 233 | 234 | pub fn read(&self, addr: CSRAddr) -> u64 { 235 | self.read_unchecked(addr.value()) 236 | } 237 | 238 | pub fn write(&mut self, addr: CSRAddr, val: u64) -> Result<(), Exception> { 239 | self.write_unchecked(addr.value(), val) 240 | } 241 | 242 | pub fn read_unchecked(&self, addr: u16) -> u64 { 243 | use CSRMap::*; 244 | match addr { 245 | // 4.1.1 Supervisor Status Register (sstatus) 246 | // The sstatus register is a subset of the mstatus register. 247 | // In a straightforward implementation, reading or writing any field in sstatus 248 | // is equivalent to reading or writing the homonymous field in mstatus. 249 | SSTATUS => self.csrs[MSTATUS as usize] & SSTATUS_MASK, 250 | // 4.1.3 Supervisor Interrupt Registers (sip and sie) 251 | // The sip and sie registers are subsets of the mip and mie registers. 252 | // Reading any implemented field, or writing any writable field, of sip/sie 253 | // effects a read or write of the homonymous field of mip/mie. 254 | // 3.1.9 Machine Interrupt Registers (mip and mie) 255 | // If an interrupt is delegated to S-mode by setting a bit in the mideleg register, 256 | // it becomes visible in the sip register and is maskable using the sie register. 257 | // Otherwise, the corresponding bits in sip and sie are read-only zero. 258 | SIE => self.csrs[MIE as usize] & self.csrs[MIDELEG as usize], 259 | SIP => self.csrs[MIP as usize] & self.csrs[MIDELEG as usize], 260 | // 13.2 Floating-Point Control and Status Register 261 | // frm and fflags are inside fcsr 262 | FRM => (self.csrs[FCSR as usize] >> 5) & 0b111, 263 | FFLAGS => (self.csrs[FCSR as usize]) & 0b11111, 264 | addr => self.csrs[addr as usize], 265 | } 266 | } 267 | 268 | pub fn write_unchecked(&mut self, addr: u16, val: u64) -> Result<(), Exception> { 269 | use CSRMap::*; 270 | match addr { 271 | MVENDORID => {} 272 | MARCHID => {} 273 | MIMPID => {} 274 | MHARTID => {} 275 | MIDELEG => { 276 | // Some interrupts cannot be delegated to S-mode like Machine Timer Interrupt, etc. 277 | // https://github.com/qemu/qemu/blob/1d60bb4b14601e38ed17384277aa4c30c57925d3/target/riscv/csr.c#L828 278 | // https://github.com/riscv/riscv-isa-manual/issues/7 279 | let mask = DELEGABLE_INTERRUPTS; 280 | let mideleg = self.csrs[MIDELEG as usize]; 281 | let mideleg = (mideleg & !mask) | (val & mask); 282 | self.csrs[MIDELEG as usize] = mideleg; 283 | } 284 | MCYCLE => return Err(Exception::IllegalInstruction), 285 | // 4.1.1 Supervisor Status Register (sstatus) 286 | // The sstatus register is a subset of the mstatus register. 287 | // In a straightforward implementation, reading or writing any field in sstatus 288 | // is equivalent to reading or writing the homonymous field in mstatus. 289 | SSTATUS => { 290 | self.csrs[MSTATUS as usize] = 291 | (self.csrs[MSTATUS as usize] & !SSTATUS_MASK) | (val & SSTATUS_MASK); 292 | } 293 | // 4.1.3 Supervisor Interrupt Registers (sip and sie) 294 | // The sip and sie registers are subsets of the mip and mie registers. 295 | // Reading any implemented field, or writing any writable field, of sip/sie 296 | // effects a read or write of the homonymous field of mip/mie. 297 | // 3.1.9 Machine Interrupt Registers (mip and mie) 298 | // If an interrupt is delegated to S-mode by setting a bit in the mideleg register, 299 | // it becomes visible in the sip register and is maskable using the sie register. 300 | // Otherwise, the corresponding bits in sip and sie are read-only zero. 301 | SIE => { 302 | self.csrs[MIE as usize] = (self.csrs[MIE as usize] & !self.csrs[MIDELEG as usize]) 303 | | (val & self.csrs[MIDELEG as usize]); 304 | } 305 | SIP => { 306 | let mask = SSIP_MASK & self.csrs[MIDELEG as usize]; 307 | self.csrs[MIP as usize] = (self.csrs[MIP as usize] & !mask) | (val & mask); 308 | } 309 | SATP => { 310 | // 4.1.11 Supervisor Address Translation and Protection (satp) Register 311 | // Implementations are not required to support all MODE settings, and if satp is written 312 | // with an unsupported MODE, the entire write has no effect; no fields in satp are modified. 313 | let mode = (val & SATP64_MODE_MASK) >> SATP64_MODE_SHIFT; 314 | if mode as u8 != VM_V20211203_SV64 { 315 | // SV64 is not supported because spec does not say anything about it. 316 | self.csrs[SATP as usize] = val; 317 | } 318 | } 319 | // 13.2 Floating-Point Control and Status Register 320 | // frm and fflags are inside fcsr 321 | FRM => { 322 | let val = val & 0b111; 323 | let fcsr = self.csrs[FCSR as usize]; 324 | let fcsr = (fcsr & !(0b111 << 5)) | (val << 5); 325 | self.csrs[FCSR as usize] = fcsr; 326 | } 327 | FFLAGS => { 328 | let val = val & 0b11111; 329 | let fcsr = self.csrs[FCSR as usize]; 330 | let fcsr = (fcsr & !0b11111) | val; 331 | self.csrs[FCSR as usize] = fcsr; 332 | } 333 | addr => self.csrs[addr as usize] = val, 334 | } 335 | Ok(()) 336 | } 337 | 338 | pub fn write_bit(&mut self, addr: u16, bit: usize, val: bool) -> Result<(), Exception> { 339 | match val { 340 | true => self.write_unchecked(addr, self.read_unchecked(addr) | 1 << bit), 341 | false => self.write_unchecked(addr, self.read_unchecked(addr) & !(1 << bit)), 342 | } 343 | } 344 | 345 | pub fn read_bit(&self, addr: u16, bit: usize) -> bool { 346 | (self.read_unchecked(addr) & (1 << bit)) != 0 347 | } 348 | 349 | #[allow(non_snake_case)] 350 | pub fn read_mstatus_MPP(&self) -> PrivilegeMode { 351 | let mpp0 = self.read_bit(MSTATUS, 11); 352 | let mpp1 = self.read_bit(MSTATUS, 12); 353 | match (mpp1, mpp0) { 354 | (false, false) => PrivilegeMode::User, 355 | (false, true) => PrivilegeMode::Supervisor, 356 | (true, true) => PrivilegeMode::Machine, 357 | _ => panic!("invalid privilege mode in MPP (mstatus[11:12]) = {}{}", mpp1 as i32, mpp0 as i32), 358 | } 359 | } 360 | 361 | #[allow(non_snake_case)] 362 | pub fn read_sstatus_SPP(&self) -> PrivilegeMode { 363 | match self.read_bit(SSTATUS, 8) { 364 | false => PrivilegeMode::User, 365 | true => PrivilegeMode::Supervisor, 366 | } 367 | } 368 | 369 | #[allow(non_snake_case)] 370 | pub fn write_mstatus_MPP(&mut self, mode: PrivilegeMode) { 371 | match mode { 372 | PrivilegeMode::User => { 373 | // mstatus[12:11] = 0b00 374 | let _ = self.write_bit(MSTATUS, 11, false); 375 | let _ = self.write_bit(MSTATUS, 12, false); 376 | } 377 | PrivilegeMode::Supervisor => { 378 | // mstatus[12:11] = 0b01 379 | let _ = self.write_bit(MSTATUS, 11, true); 380 | let _ = self.write_bit(MSTATUS, 12, false); 381 | } 382 | PrivilegeMode::Machine => { 383 | // mstatus[12:11] = 0b11 384 | let _ = self.write_bit(MSTATUS, 11, true); 385 | let _ = self.write_bit(MSTATUS, 12, true); 386 | } 387 | } 388 | } 389 | 390 | #[allow(non_snake_case)] 391 | pub fn write_sstatus_SPP(&mut self, mode: PrivilegeMode) { 392 | let _ = match mode { 393 | PrivilegeMode::User => self.write_bit(SSTATUS, 8, false), 394 | PrivilegeMode::Supervisor => self.write_bit(SSTATUS, 8, true), 395 | PrivilegeMode::Machine => panic!("SPP cannot be Machine"), 396 | }; 397 | } 398 | 399 | #[allow(non_snake_case)] 400 | pub fn read_mstatus_MPRV(&self) -> bool { 401 | self.read_bit(MSTATUS, 17) 402 | } 403 | 404 | #[allow(non_snake_case)] 405 | pub fn write_mstatus_MPRV(&mut self, val: bool) { 406 | let _ = self.write_bit(MSTATUS, 17, val); 407 | } 408 | 409 | #[allow(non_snake_case)] 410 | pub fn read_mstatus_SUM(&self) -> bool { 411 | self.read_bit(MSTATUS, 18) 412 | } 413 | 414 | #[allow(non_snake_case)] 415 | pub fn write_mstatus_SUM(&mut self, val: bool) { 416 | let _ = self.write_bit(MSTATUS, 18, val); 417 | } 418 | 419 | #[allow(non_snake_case)] 420 | pub fn read_mstatus_MXR(&self) -> bool { 421 | self.read_bit(MSTATUS, 19) 422 | } 423 | 424 | #[allow(non_snake_case)] 425 | pub fn write_mstatus_MXR(&mut self, val: bool) { 426 | let _ = self.write_bit(MSTATUS, 19, val); 427 | } 428 | 429 | #[allow(non_snake_case)] 430 | pub fn read_mstatus_MIE(&self) -> bool { 431 | self.read_bit(MSTATUS, 3) 432 | } 433 | 434 | #[allow(non_snake_case)] 435 | pub fn write_mstatus_MIE(&mut self, val: bool) { 436 | let _ = self.write_bit(MSTATUS, 3, val); 437 | } 438 | 439 | #[allow(non_snake_case)] 440 | pub fn read_mstatus_MPIE(&self) -> bool { 441 | self.read_bit(MSTATUS, 7) 442 | } 443 | 444 | #[allow(non_snake_case)] 445 | pub fn write_mstatus_MPIE(&mut self, val: bool) { 446 | let _ = self.write_bit(MSTATUS, 7, val); 447 | } 448 | 449 | #[allow(non_snake_case)] 450 | pub fn read_sstatus_SIE(&self) -> bool { 451 | self.read_bit(SSTATUS, 1) 452 | } 453 | 454 | #[allow(non_snake_case)] 455 | pub fn write_sstatus_SIE(&mut self, val: bool) { 456 | let _ = self.write_bit(SSTATUS, 1, val); 457 | } 458 | 459 | #[allow(non_snake_case)] 460 | pub fn read_sstatus_SPIE(&self) -> bool { 461 | self.read_bit(SSTATUS, 5) 462 | } 463 | 464 | #[allow(non_snake_case)] 465 | pub fn write_sstatus_SPIE(&mut self, val: bool) { 466 | let _ = self.write_bit(SSTATUS, 5, val); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/data.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Either { 5 | Left(A), 6 | Right(B), 7 | } 8 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/irq.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::{PrivilegeMode, RV64Cpu}; 2 | use crate::cpu::csr::CSRMap::{MCAUSE, MEDELEG, MEIP_MASK, MEPC, MIDELEG, MIE, MIP, MSIP_MASK, MTIP_MASK, MTVAL, MTVEC, SCAUSE, SEIP_MASK, SEPC, SSIP_MASK, STIP_MASK, STVAL, STVEC}; 3 | use crate::device::virtio::Virtio; 4 | use crate::memory::VirtAddr; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub enum IRQ { 8 | /// Machine external IRQ 9 | MEI, 10 | /// Machine software IRQ 11 | MSI, 12 | /// Machine timer IRQ 13 | MTI, 14 | /// Supervisor external IRQ 15 | SEI, 16 | /// Supervisor software IRQ 17 | SSI, 18 | /// Supervisor timer IRQ 19 | STI, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 23 | pub enum Exception { 24 | IllegalInstruction, 25 | LoadAccessFault(VirtAddr), 26 | StoreAccessFault(VirtAddr), 27 | LoadAddressMisaligned(VirtAddr), 28 | StoreAddressMisaligned(VirtAddr), 29 | InstructionPageFault(VirtAddr), 30 | LoadPageFault(VirtAddr), 31 | StorePageFault(VirtAddr), 32 | UserEcall, 33 | SupervisorEcall, 34 | MachineEcall, 35 | Breakpoint, 36 | } 37 | 38 | impl RV64Cpu { 39 | pub fn pending_interrupt(&mut self) -> Option { 40 | // 3.1.6.1 Privilege and Global Interrupt-Enable Stack in mstatus register 41 | // Global interrupt-enable bits, MIE and SIE, are provided for M-mode and S-mode respectively. 42 | // When a hart is executing in privilege mode x, interrupts are globally enabled 43 | // when xIE=1 and globally disabled when xIE=0. 44 | // Interrupts for lower-privilege modes, wx, are always globally enabled 47 | // regardless of the setting of the global yIE bit for the higher-privilege mode. 48 | 49 | // Higher-privilege-level code can use separate per-interrupt enable bits 50 | // to disable selected higher-privilege-mode interrupts before ceding control 51 | // to a lower-privilege mode. 52 | 53 | // 3.3.3 Wait for Interrupt 54 | // The WFI instruction can also be executed when interrupts are disabled. 55 | // The operation of WFI must be unaffected by the global interrupt bits in mstatus (MIE and SIE) 56 | // and the delegation register mideleg (i.e., the hart must resume if a locally enabled interrupt 57 | // becomes pending, even if it has been delegated to a less-privileged mode), 58 | // but should honor the individual interrupt enables (e.g, MTIE) (i.e., implementations should 59 | // avoid resuming the hart if the interrupt is pending but not individually enabled). 60 | // WFI is also required to resume execution for locally enabled interrupts pending at any privilege level, 61 | // regardless of the global interrupt enable at each privilege level. 62 | 63 | let irq_globally_enabled = match (self.wfi, self.mode) { 64 | (true, _) => true, 65 | (_, PrivilegeMode::Machine) => self.csrs.read_mstatus_MIE(), 66 | (_, PrivilegeMode::Supervisor) => self.csrs.read_sstatus_SIE(), 67 | (_, PrivilegeMode::User) => true, 68 | }; 69 | 70 | if !irq_globally_enabled { 71 | return None; 72 | } 73 | 74 | // 3.1.9 Machine Interrupt Registers (mip and mie) 75 | // The machine-level interrupt fixed-priority ordering rules were developed with the following rationale: 76 | // 1. Interrupts for higher privilege modes must be serviced before interrupts 77 | // for lower privilege modes to support preemption. 78 | // 2. The platform-specific machine-level interrupt sources in bits 16 and above have 79 | // platform-specific priority, but are typically chosen to have the highest service priority 80 | // to support very fast local vectored interrupts. 81 | // 3. External interrupts are handled before internal (timer/software) interrupts 82 | // as external interrupts are usually generated by devices that might require 83 | // low interrupt service times. 84 | // 4. Software interrupts are handled before internal timer interrupts, because internal 85 | // timer interrupts are usually intended for time slicing, where time precision is less important, 86 | // whereas software interrupts are used for inter-processor messaging. 87 | // Software interrupts can be avoided when high-precision timing is required, 88 | // or high-precision timer interrupts can be routed via a different interrupt path. 89 | 90 | // In our implementation, external devices is only UART, currently. We may support 91 | // VirtIO disks, which is also a external devices. 92 | 93 | // check builtin virtio disk 94 | let external_irq = match self.bus.virtio.pending_interrupt() { 95 | Some(virtio_irq) => { 96 | // TODO: replace with our own DMA implementation 97 | // TODO: exception handling 98 | Virtio::disk_access(self).expect("failed to access the disk"); 99 | Some(virtio_irq) 100 | } 101 | _ => { 102 | let mut irq = None; 103 | // check other external devices like UART 104 | for dev in self.bus.devices.iter() { 105 | if let Some(irq_id) = dev.is_interrupting() { 106 | irq = Some(irq_id); 107 | break; 108 | } 109 | } 110 | irq 111 | } 112 | }; 113 | 114 | if let Some(irq_id) = external_irq { 115 | // tell PLIC that we have an external irq 116 | self.bus.plic.update_pending(irq_id); 117 | // 3.1.9 Machine Interrupt Registers (mip and mie) 118 | // SEIP is writable in mip, and may be written by M-mode software to 119 | // indicate to S-mode that an external interrupt is pending. Additionally, 120 | // the platform-level interrupt controller may generate supervisor-level external interrupts. 121 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) | SEIP_MASK); 122 | } 123 | 124 | // 3.1.9 Machine Interrupt Registers (mip and mie) 125 | // Multiple simultaneous interrupts destined for M-mode are handled 126 | // in the following decreasing priority order: MEI, MSI, MTI, SEI, SSI, STI. 127 | 128 | // only enable bit and pending bit are both set, an interrupt is treated as taken 129 | let mip = self.csrs.read_unchecked(MIP) & self.csrs.read_unchecked(MIE); 130 | 131 | // fast-path check 132 | if mip == 0 { 133 | return None; 134 | } 135 | 136 | if (mip & MEIP_MASK) != 0 { 137 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !MEIP_MASK); 138 | return Some(IRQ::MEI); 139 | } 140 | 141 | if (mip & MSIP_MASK) != 0 { 142 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !MSIP_MASK); 143 | return Some(IRQ::MSI); 144 | } 145 | 146 | if (mip & MTIP_MASK) != 0 { 147 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !MTIP_MASK); 148 | return Some(IRQ::MTI); 149 | } 150 | 151 | if (mip & SEIP_MASK) != 0 { 152 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !SEIP_MASK); 153 | return Some(IRQ::SEI); 154 | } 155 | 156 | if (mip & SSIP_MASK) != 0 { 157 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !SSIP_MASK); 158 | return Some(IRQ::SSI); 159 | } 160 | 161 | if (mip & STIP_MASK) != 0 { 162 | let _ = self.csrs.write_unchecked(MIP, self.csrs.read_unchecked(MIP) & !STIP_MASK); 163 | return Some(IRQ::STI); 164 | } 165 | 166 | None 167 | } 168 | } 169 | 170 | impl IRQ { 171 | /// Just the corresponding bit in MIP register 172 | fn mcause(&self) -> u64 { 173 | match self { 174 | IRQ::SSI => 1, 175 | IRQ::MSI => 3, 176 | IRQ::STI => 5, 177 | IRQ::MTI => 7, 178 | IRQ::SEI => 9, 179 | IRQ::MEI => 11, 180 | } 181 | } 182 | 183 | pub fn handle(&self, cpu: &mut RV64Cpu) -> Result<(), ()> { 184 | trap_entry(self.mcause(), 0, true, cpu) 185 | } 186 | } 187 | 188 | impl Exception { 189 | pub fn mcause_mtval(&self, cpu: &RV64Cpu) -> (u64, u64) { 190 | match self { 191 | Exception::IllegalInstruction => (2, cpu.instr), 192 | Exception::Breakpoint => (3, 0), 193 | Exception::LoadAddressMisaligned(addr) => (4, addr.0), 194 | Exception::LoadAccessFault(addr) => (5, addr.0), 195 | Exception::StoreAddressMisaligned(addr) => (6, addr.0), 196 | Exception::StoreAccessFault(addr) => (7, addr.0), 197 | Exception::InstructionPageFault(addr) => (12, addr.0), 198 | Exception::LoadPageFault(addr) => (13, addr.0), 199 | Exception::StorePageFault(addr) => (15, addr.0), 200 | Exception::UserEcall => (8, 0), 201 | Exception::SupervisorEcall => (9, 0), 202 | Exception::MachineEcall => (11, 0), 203 | } 204 | } 205 | 206 | pub fn handle(&self, cpu: &mut RV64Cpu) -> Result<(), ()> { 207 | let (mcause, mtval) = self.mcause_mtval(cpu); 208 | trap_entry(mcause, mtval, false, cpu) 209 | } 210 | } 211 | 212 | pub fn trap_entry(mcause: u64, mtval: u64, is_interrupt: bool, cpu: &mut RV64Cpu) -> Result<(), ()> { 213 | // clear the wait-for-interrupt flag 214 | if is_interrupt { cpu.wfi = false; } 215 | 216 | let previous_pc = cpu.regs.pc.0; 217 | let previous_mode = cpu.mode; 218 | let interrupt_bit = is_interrupt as u64; 219 | 220 | // 3.1.8 Machine Trap Delegation Registers (medeleg and mideleg) 221 | // In systems with S-mode, the medeleg and mideleg registers must exist, 222 | // and setting a bit in medeleg or mideleg will delegate the corresponding trap, 223 | // when occurring in S-mode or U-mode, to the S-mode trap handler. 224 | 225 | // 3.1.9 Machine Interrupt Registers (mip and mie) 226 | // An interrupt i will trap to M-mode (causing the privilege mode to change to M-mode) if all of the following are true: 227 | // (a) either the current privilege mode is M and the MIE bit in the mstatus register is set, 228 | // or the current privilege mode has less privilege than M-mode; 229 | // (b) bit i is set in both mip and mie; 230 | // (c) if register mideleg exists, bit i is not set in mideleg/medeleg. 231 | 232 | // 4.1.3 Supervisor Interrupt Registers (sip and sie) 233 | // An interrupt i will trap to S-mode if both of the following are true: 234 | // (a) either the current privilege mode is S and the SIE bit in the sstatus register is set, 235 | // or the current privilege mode has less privilege than S-mode; 236 | // (b) bit i is set in both sip and sie. 237 | 238 | // note: wo don't need to check the following, since we have 239 | // already checked it in `RV64Cpu::pending_interrupt`: 240 | // (a) the MIE bit in mstatus, or the SIE bit in sstatus 241 | // (b) the bit i in (mip & mie) or (sip & sie) 242 | 243 | // we only need to check: (c) bit i is not set in mideleg/medeleg. 244 | // note: according to 3.1.8, only interrupts/exceptions that occurred 245 | // not in M-mode can be delegated. 246 | 247 | let occurred_in_machine = previous_mode == PrivilegeMode::Machine; 248 | let not_delegated = match is_interrupt { 249 | true => ((cpu.csrs.read_unchecked(MIDELEG) >> mcause) & 1) == 0, 250 | false => ((cpu.csrs.read_unchecked(MEDELEG) >> mcause) & 1) == 0, 251 | }; 252 | 253 | let trap_to_machine = occurred_in_machine || (previous_mode <= PrivilegeMode::Machine && not_delegated); 254 | 255 | match trap_to_machine { 256 | true => { 257 | // enter machine mode 258 | cpu.mode = PrivilegeMode::Machine; 259 | 260 | // find the trap-vector offset according to the mode saved in mtvec[1:0] 261 | // 3.1.7 Machine Trap-Vector Base-Address Register (mtvec) 262 | // When MODE=Direct, all traps into machine mode cause the pc to be set to the address in the BASE field. 263 | // When MODE=Vectored, all synchronous exceptions into machine mode cause the pc to be set to the address in the BASE field, 264 | // whereas interrupts cause the pc to be set to the address in the BASE field plus four times the interrupt cause number. 265 | // For example, a machine-mode timer interrupt (see Table 3.6 on page 39) causes the pc to be set to BASE+0x1c. 266 | let mode = cpu.csrs.read_unchecked(MTVEC) & 0b11; 267 | let offset = match (is_interrupt, mode) { 268 | (false, _) => 0, // exceptions all cause the pc to the BASE field 269 | (true, 0) => 0, // direct mode 270 | (true, 1) => 4 * mcause, // vectored mode, here we are handling interrupts 271 | _ => panic!("invalid machine trap-vector address mode in mtvec[1:0]: {}", mode), 272 | }; 273 | 274 | cpu.regs.pc.0 = ((cpu.csrs.read_unchecked(MTVEC) & !(0b11)) + offset) as u64; 275 | 276 | // 3.1.14 Machine Exception Program Counter (mepc) 277 | // When a trap is taken into M-mode, mepc is written with the virtual address of the instruction 278 | // that was interrupted or that encountered the exception. Otherwise, mepc is never written 279 | // by the implementation, though it may be explicitly written by software. 280 | // The low bit of mepc (mepc[0]) is always zero. 281 | let _ = cpu.csrs.write_unchecked(MEPC, previous_pc & !1); 282 | 283 | // 3.1.15 Machine Cause Register (mcause) 284 | // When a trap is taken into M-mode, mcause is written with a code indicating the event 285 | // that caused the trap. Otherwise, mcause is never written by the implementation, 286 | // though it may be explicitly written by software. 287 | // The Interrupt bit (mcause[63]) in the mcause register is set if the trap was caused by an interrupt. 288 | // The Exception Code field(mcause[0:62]) contains a code identifying the last exception or interrupt. 289 | let _ = cpu.csrs.write_unchecked(MCAUSE, interrupt_bit << 63 | mcause); 290 | 291 | // 3.1.16 Machine Trap Value Register (mtval) 292 | // When a trap is taken into M-mode, mtval is either set to zero or written with exception-specific information 293 | // to assist software in handling the trap. Otherwise, mtval is never written by the implementation, 294 | // though it may be explicitly written by software. 295 | let _ = cpu.csrs.write_unchecked(MTVAL, mtval); 296 | 297 | // set MPIE to MIE 298 | let mie = cpu.csrs.read_mstatus_MIE(); 299 | cpu.csrs.write_mstatus_MPIE(mie); 300 | // set MIE to 0 to disable interrupts 301 | cpu.csrs.write_mstatus_MIE(false); 302 | 303 | // set MPP to previous privileged mode 304 | cpu.csrs.write_mstatus_MPP(previous_mode); 305 | Ok(()) 306 | } 307 | 308 | false => { 309 | // Comments omitted since there's only register differences 310 | if previous_mode == PrivilegeMode::Machine { 311 | panic!( 312 | "Machine trap (int={}, mcause={}, mtval={}, mpp={:?}, mepc={:#x}) cannot be handled in supervisor mode", 313 | is_interrupt, 314 | interrupt_bit << 63 | mcause, 315 | mtval, 316 | previous_mode, 317 | previous_pc, 318 | ); 319 | } 320 | 321 | cpu.mode = PrivilegeMode::Supervisor; 322 | let mode = cpu.csrs.read_unchecked(STVEC) & 0b11; 323 | let offset = match (is_interrupt, mode) { 324 | (false, _) => 0, // exceptions all cause the pc to the BASE field 325 | (true, 0) => 0, // direct mode 326 | (true, 1) => 4 * mcause, // vectored mode, here we are handling interrupts 327 | _ => panic!("invalid machine trap-vector address mode in stvec[1:0]: {}", mode), 328 | }; 329 | 330 | cpu.regs.pc.0 = ((cpu.csrs.read_unchecked(STVEC) & (!0b11)) + offset) as u64; 331 | let _ = cpu.csrs.write_unchecked(SEPC, previous_pc & !1); 332 | let _ = cpu.csrs.write_unchecked(SCAUSE, interrupt_bit << 63 | mcause); 333 | let _ = cpu.csrs.write_unchecked(STVAL, mtval); 334 | 335 | // set SPIE to SIE 336 | let sie = cpu.csrs.read_sstatus_SIE(); 337 | cpu.csrs.write_sstatus_SPIE(sie); 338 | // set SIE to 0 to disable interrupts 339 | cpu.csrs.write_sstatus_SIE(false); 340 | 341 | // set SPP to previous privileged mode 342 | cpu.csrs.write_sstatus_SPP(previous_mode); 343 | Ok(()) 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/mmu.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::cpu::{PrivilegeMode, RV64Cpu}; 4 | use crate::cpu::csr::CSRMap::SATP; 5 | use crate::cpu::irq::Exception; 6 | use crate::debug::trace::{MemTrace, Trace}; 7 | use crate::memory::{CanIO, VirtAddr}; 8 | 9 | pub const PAGE_SHIFT: u64 = 12; 10 | pub const PAGE_SIZE: u64 = 1 << PAGE_SHIFT; // 4096 11 | 12 | // RV64 satp CSR field masks 13 | pub const SATP64_MODE_MASK: u64 = 0xF000000000000000; 14 | pub const SATP64_MODE_SHIFT: u64 = 60; 15 | pub const SATP64_ASID_MASK: u64 = 0x0FFFF00000000000; 16 | pub const SATP64_ASID_SHIFT: u64 = 44; 17 | pub const SATP64_PPN_MASK: u64 = 0x00000FFFFFFFFFFF; 18 | pub const SATP64_PPN_SHIFT: u64 = 0; 19 | 20 | /* VM modes (satp.mode) privileged ISA V20211203 */ 21 | pub const VM_V20211203_MBARE: u8 = 0; 22 | pub const VM_V20211203_SV39: u8 = 8; 23 | pub const VM_V20211203_SV48: u8 = 9; 24 | pub const VM_V20211203_SV57: u8 = 10; 25 | pub const VM_V20211203_SV64: u8 = 11; 26 | 27 | // Page table entry (PTE) fields 28 | pub const PTE_V: u64 = 0; /* Valid */ 29 | pub const PTE_R: u64 = 1; /* Read */ 30 | pub const PTE_W: u64 = 2; /* Write */ 31 | pub const PTE_X: u64 = 3; /* Execute */ 32 | pub const PTE_U: u64 = 4; /* User */ 33 | pub const PTE_G: u64 = 5; /* Global */ 34 | pub const PTE_A: u64 = 6; /* Accessed */ 35 | pub const PTE_D: u64 = 7; /* Dirty */ 36 | 37 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 38 | #[repr(u8)] 39 | pub enum VMMode { 40 | /// 4.1.11 Supervisor Address Translation and Protection (satp) Register 41 | /// When MODE=Bare, supervisor virtual addresses are equal to supervisor physical addresses, 42 | /// and there is no additional memory protection beyond the physical memory protection scheme. 43 | MBARE = VM_V20211203_MBARE, 44 | SV39 = VM_V20211203_SV39, 45 | SV48 = VM_V20211203_SV48, 46 | SV57 = VM_V20211203_SV57, 47 | SV64 = VM_V20211203_SV64, 48 | } 49 | 50 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 51 | pub enum Reason { 52 | Fetch, 53 | Read, 54 | Write, 55 | } 56 | 57 | impl VMMode { 58 | /// returns Some((levels, ptidxbits, ptesize)) if the translation is supported. 59 | pub fn translation_args(&self) -> Option<(i64, u64, u64)> { 60 | match self { 61 | VMMode::MBARE => None, // MBARE does not need translation 62 | VMMode::SV39 => Some((3, 9, 8)), 63 | VMMode::SV48 => Some((4, 9, 8)), 64 | VMMode::SV57 => Some((5, 9, 8)), 65 | VMMode::SV64 => None, // SV64 is not described in the spec Volume 2 66 | } 67 | } 68 | 69 | /// returns the vpn structure for the given vaddr. 70 | /// For Sv39, the result only contains 3 elements (vpn[0], vpn[1], vpn[2]), vpn[3,4] are 0. 71 | /// For Sv48, the result contains 4 elements (vpn[0], vpn[1], vpn[2], vpn[3]), vpn[4] is 0. 72 | /// For Sv57, the result contains 5 elements (vpn[0], vpn[1], vpn[2], vpn[3], vpn[4]) 73 | /// For unsupported modes, the result contains all zero. 74 | pub fn vpn(&self, addr: u64) -> [u64; 5] { 75 | match self { 76 | VMMode::SV39 => { 77 | // addr[0:11] = page offset 78 | // addr[12:20] = vpn[0] 79 | // addr[21:29] = vpn[1] 80 | // addr[30:38] = vpn[2] 81 | [(addr >> 12) & 0x1ff, (addr >> 21) & 0x1ff, (addr >> 30) & 0x1ff, 0, 0] 82 | } 83 | VMMode::SV48 => { 84 | // addr[0:11] = page offset 85 | // addr[12:20] = vpn[0] 86 | // addr[21:29] = vpn[1] 87 | // addr[30:38] = vpn[2] 88 | // addr[39:47] = vpn[3] 89 | [(addr >> 12) & 0x1ff, (addr >> 21) & 0x1ff, (addr >> 30) & 0x1ff, (addr >> 39) & 0x1ff, 0] 90 | } 91 | VMMode::SV57 => { 92 | // addr[0:11] = page offset 93 | // addr[12:20] = vpn[0] 94 | // addr[21:29] = vpn[1] 95 | // addr[30:38] = vpn[2] 96 | // addr[39:47] = vpn[3] 97 | // addr[48:56] = vpn[4] 98 | [(addr >> 12) & 0x1ff, (addr >> 21) & 0x1ff, (addr >> 30) & 0x1ff, (addr >> 39) & 0x1ff, (addr >> 48) & 0x1ff] 99 | } 100 | _ => [0, 0, 0, 0, 0] 101 | } 102 | } 103 | 104 | /// returns the ppn fields of PTE. 105 | pub fn pte_ppn(&self, pte: u64) -> [u64; 5] { 106 | match self { 107 | VMMode::SV39 => { 108 | // pte[10:18] = ppn[0] 109 | // pte[19:27] = ppn[1] 110 | // pte[28:53] = ppn[2] 111 | [(pte >> 10) & 0x1ff, (pte >> 19) & 0x1ff, (pte >> 28) & 0x03ffffff, 0, 0] 112 | } 113 | VMMode::SV48 => { 114 | // pte[10:18] = ppn[0] 115 | // pte[19:27] = ppn[1] 116 | // pte[28:36] = ppn[2] 117 | // pte[37:53] = ppn[3] 118 | [(pte >> 10) & 0x1ff, (pte >> 19) & 0x1ff, (pte >> 28) & 0x1ff, (pte >> 37) & 0x1ffff, 0] 119 | } 120 | VMMode::SV57 => { 121 | // pte[10:18] = ppn[0] 122 | // pte[19:27] = ppn[1] 123 | // pte[28:36] = ppn[2] 124 | // pte[37:45] = ppn[3] 125 | // pte[46:53] = ppn[4] 126 | [(pte >> 10) & 0x1ff, (pte >> 19) & 0x1ff, (pte >> 28) & 0x1ff, (pte >> 37) & 0x1ff, (pte >> 46) & 0xff] 127 | } 128 | _ => [0, 0, 0, 0, 0], 129 | } 130 | } 131 | } 132 | 133 | impl Reason { 134 | pub fn to_page_fault(self, addr: VirtAddr) -> Result { 135 | return match self { 136 | Reason::Fetch => Err(Exception::InstructionPageFault(addr)), 137 | Reason::Read => Err(Exception::LoadPageFault(addr)), 138 | Reason::Write => Err(Exception::StorePageFault(addr)), 139 | }; 140 | } 141 | } 142 | 143 | impl RV64Cpu { 144 | #[inline(always)] 145 | pub fn fetch_mem(&mut self, addr: VirtAddr) -> Result { 146 | let paddr = self.translate(addr, Reason::Fetch)?; 147 | self.bus.read::(paddr) 148 | } 149 | 150 | #[inline(always)] 151 | pub fn read_mem(&mut self, addr: VirtAddr) -> Result { 152 | // TODO: mstatus MPRV 153 | let paddr = self.translate(addr, Reason::Read)?; 154 | let val = self.bus.read::(paddr); 155 | self.journal.trace(|| Trace::Mem(MemTrace::Read(addr, paddr, std::mem::size_of::(), format!("{:?}", val)))); 156 | val 157 | } 158 | 159 | #[inline(always)] 160 | pub fn write_mem(&mut self, addr: VirtAddr, val: T) -> Result<(), Exception> { 161 | // TODO: mstatus MPRV 162 | let paddr = self.translate(addr, Reason::Write)?; 163 | let res = self.bus.write::(paddr, val); 164 | self.journal.trace(|| Trace::Mem(MemTrace::Write(addr, paddr, std::mem::size_of::(), format!("{:?}", val)))); 165 | res 166 | } 167 | 168 | pub fn sync_pagetable(&mut self) { 169 | let satp = self.csrs.read_unchecked(SATP); 170 | let ppn = (satp & SATP64_PPN_MASK) >> SATP64_PPN_SHIFT; 171 | let _asid = (satp & SATP64_ASID_MASK) >> SATP64_ASID_SHIFT; 172 | let mode = (satp & SATP64_MODE_MASK) >> SATP64_MODE_SHIFT; 173 | 174 | self.vmppn = ppn << PAGE_SHIFT; 175 | match mode as u8 { 176 | VM_V20211203_MBARE => self.vmmode = VMMode::MBARE, 177 | VM_V20211203_SV39 => self.vmmode = VMMode::SV39, 178 | VM_V20211203_SV48 => self.vmmode = VMMode::SV48, 179 | VM_V20211203_SV57 => self.vmmode = VMMode::SV57, 180 | VM_V20211203_SV64 => self.vmmode = VMMode::SV64, 181 | _ => unreachable!(), 182 | } 183 | } 184 | 185 | pub fn translate(&mut self, addr: VirtAddr, reason: Reason) -> Result { 186 | if self.vmmode == VMMode::MBARE { 187 | return Ok(addr); 188 | } 189 | 190 | // 3.1.6.3 Memory Privilege in mstatus Register 191 | // The MPRV (Modify PRiVilege) bit modifies the effective privilege mode, 192 | // i.e., the privilege level at which loads and stores execute. 193 | // When MPRV=0, loads and stores behave as normal, using the translation and protection 194 | // mechanisms of the current privilege mode. 195 | // When MPRV=1, load and store memory addresses are translated and protected, 196 | // and endianness is applied, as though the current privilege mode were set to MPP. 197 | // Instruction address-translation and protection are unaffected by the setting of MPRV. 198 | // MPRV is read-only 0 if U-mode is not supported. 199 | let eff_mode = match reason { 200 | Reason::Fetch => self.mode, 201 | _ => match self.csrs.read_mstatus_MPRV() { 202 | true => self.csrs.read_mstatus_MPP(), 203 | false => self.mode, 204 | } 205 | }; 206 | 207 | if eff_mode == PrivilegeMode::Machine { 208 | return Ok(addr); 209 | } 210 | 211 | // 3.1.6.3 Memory Privilege in mstatus Register 212 | // The MXR (Make eXecutable Readable) bit modifies the privilege with which loads access virtual memory. 213 | // When MXR=0, only loads from pages marked readable (R=1 in Figure 4.18) will succeed. 214 | // When MXR=1, loads from pages marked either readable or executable (R=1 or X=1) will succeed. 215 | // MXR has no effect when page-based virtual memory is not in effect. 216 | // MXR is read-only 0 if S-mode is not supported. 217 | let mxr = self.csrs.read_mstatus_MXR(); 218 | 219 | // 3.1.6.3 Memory Privilege in mstatus Register 220 | // The SUM (permit Supervisor User Memory access) bit modifies the privilege with which S-mode loads and stores access virtual memory. 221 | // When SUM=0, S-mode memory accesses to pages that are accessible by U-mode (U=1 in Figure 4.18) will fault. 222 | // When SUM=1, these accesses are permitted. 223 | // SUM has no effect when page-based virtual memory is not in effect. Note that, 224 | // while SUM is ordinarily ignored when not executing in S-mode, 225 | // it is in effect when MPRV=1 and MPP=S. 226 | // SUM is read-only 0 if S-mode is not supported or if satp.MODE is read-only 0. 227 | let sum = self.csrs.read_mstatus_SUM(); 228 | 229 | // 3.1.6.3 Memory Privilege in mstatus Register 230 | // The MXR and SUM mechanisms only affect the interpretation of permissions encoded 231 | // in page-table entries. In particular, they have no impact on whether access-fault 232 | // exceptions are raised due to PMAs or PMP. 233 | 234 | // 4.3.2 Virtual Address Translation Process 235 | let addr = addr.0; 236 | let (levels, _ptidxbits, ptesize) = self.vmmode.translation_args().unwrap(); 237 | let vpn: [u64; 5] = self.vmmode.vpn(addr); 238 | 239 | // 1. Let a be satp.ppn × PAGESIZE, and let i = LEVELS − 1. 240 | let mut a = self.vmppn; 241 | let mut i: i64 = levels - 1; 242 | let mut pte: u64; 243 | let mut ppn: [u64; 5]; 244 | 245 | loop { 246 | loop { 247 | // 2. Let pte be the value of the PTE at address a + va.vpn[i] × PTESIZE. 248 | // If accessing pte violates a PMA or PMP check, raise an access exception 249 | // corresponding to the original access type. 250 | // TODO: PMA or PMP checks 251 | pte = self.bus.read::(VirtAddr(a + vpn[i as usize] * ptesize))?; 252 | 253 | // 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise a page-fault 254 | // exception corresponding to the original access type. 255 | let pte_v = (pte >> PTE_V) & 1; 256 | let pte_r = (pte >> PTE_R) & 1; 257 | let pte_w = (pte >> PTE_W) & 1; 258 | let pte_x = (pte >> PTE_X) & 1; 259 | if pte_v == 0 || (pte_r == 0 && pte_w == 1) { 260 | return reason.to_page_fault(VirtAddr(addr)); 261 | } 262 | 263 | // 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5. 264 | if pte_r == 1 || pte_x == 1 { 265 | break; 266 | } 267 | 268 | // Otherwise, this PTE is a pointer to the next level of the page table. 269 | // Let i = i − 1. If i < 0, stop and raise a page-fault exception corresponding 270 | // to the original access type. 271 | i = i - 1; 272 | if i < 0 { 273 | return reason.to_page_fault(VirtAddr(addr)); 274 | } 275 | 276 | // Otherwise, let a = pte.ppn × PAGESIZE and go to step 2. 277 | // ppn = pte[10:53] 278 | let ppn = (pte >> 10) & 0x0fffffffffff; 279 | a = ppn * PAGE_SIZE; 280 | } 281 | 282 | // make it immutable explicitly 283 | let i = i; 284 | 285 | // 5. A leaf PTE has been found. Determine if the requested memory access 286 | // is allowed by the pte.r, pte.w, pte.x, and pte.u bits, given the current 287 | // privilege mode and the value of the SUM and MXR fields of the mstatus register. 288 | // If not, stop and raise a page-fault exception corresponding to the original access type. 289 | 290 | // check access rights according to 3.1.6.3 Memory Privilege in mstatus Register 291 | // see: https://github.com/qemu/qemu/blob/aea6e471085f39ada1ccd637043c3ee3dfd88750/target/riscv/cpu_helper.c#L952 292 | let pte_r = (pte >> PTE_R) & 1; 293 | let pte_w = (pte >> PTE_W) & 1; 294 | let pte_x = (pte >> PTE_X) & 1; 295 | let pte_u = (pte >> PTE_U) & 1; 296 | 297 | match (pte_r, pte_w, pte_x) { 298 | // Reserved leaf PTE flags: PTE_W 299 | (0, 1, 0) | 300 | // Reserved leaf PTE flags: PTE_W + PTE_X 301 | (0, 1, 1) => return reason.to_page_fault(VirtAddr(addr)), 302 | _ => (), 303 | } 304 | 305 | if (pte_u == 1) && ((eff_mode != PrivilegeMode::User) && (!sum || reason == Reason::Fetch)) { 306 | // User PTE flags when not U mode and mstatus.SUM is not set, 307 | // or the access type is an instruction fetch 308 | return reason.to_page_fault(VirtAddr(addr)); 309 | } 310 | 311 | if (pte_u == 0) && (eff_mode != PrivilegeMode::Supervisor) { 312 | // Supervisor PTE flags when not S mode 313 | return reason.to_page_fault(VirtAddr(addr)); 314 | } 315 | 316 | if reason == Reason::Read && !((pte_r == 1) || ((pte_x == 1) && mxr)) { 317 | // Read access check failed 318 | return reason.to_page_fault(VirtAddr(addr)); 319 | } else if reason == Reason::Write && (pte_w == 0) { 320 | // Write access check failed 321 | return reason.to_page_fault(VirtAddr(addr)); 322 | } else if reason == Reason::Fetch && (pte_x == 0) { 323 | // Fetch access check failed 324 | return reason.to_page_fault(VirtAddr(addr)); 325 | } 326 | 327 | // 6. If i > 0 and pte.ppn[i−1 : 0] != 0, this is a misaligned superpage; 328 | // stop and raise a page-fault exception corresponding to the original access type. 329 | ppn = self.vmmode.pte_ppn(pte); 330 | if i > 0 { 331 | if ppn.split_at(i as usize).0.iter().any(|&x| x != 0) { 332 | return reason.to_page_fault(VirtAddr(addr)); 333 | } 334 | } 335 | 336 | // 7. If pte.a = 0, or if the memory access is a store and pte.d = 0, either raise a page-fault exception 337 | // corresponding to the original access type, or: 338 | // - If a store to pte would violate a PMA or PMP check, raise an access-fault exception corresponding to the original access type. 339 | // - Perform the following steps atomically: 340 | // - Compare pte to the value of the PTE at address a + va.vpn[i] × PTESIZE. 341 | // - If the values match, set pte.a to 1 and, if the original memory access is a store, also set pte.d to 1. 342 | // - If the comparison fails, return to step 2 343 | let pte_a = (pte >> PTE_A) & 1; 344 | let pte_d = (pte >> PTE_D) & 1; 345 | if pte_a == 0 || (reason == Reason::Write && pte_d == 0) { 346 | // Compare pte to the value of the PTE at address a + va.vpn[i] × PTESIZE. 347 | let pte_addr = VirtAddr(a + vpn[i as usize] * ptesize); 348 | let compare = self.bus.read::(pte_addr)?; 349 | 350 | // If the values match, set pte.a to 1 and 351 | if compare == pte { 352 | pte = pte | (1 << PTE_A); 353 | // if the original memory access is a store, also set pte.d to 1. 354 | if reason == Reason::Write { 355 | pte = pte | (1 << PTE_D); 356 | } 357 | 358 | // TODO: PMA or PMP checks 359 | self.bus.write::(pte_addr, pte)?; 360 | // note: and goto step 8 361 | break; 362 | } else { 363 | // If the comparison fails, return to step 2 364 | eprintln!("[Valheim] translate: compare pte failed: {} != {}, returning to step 2", compare, pte); 365 | continue; 366 | } 367 | } else { 368 | // note: step 7 checks successfully goto step 8 369 | break; 370 | } 371 | } 372 | 373 | // 8. The translation is successful. The translated physical address is given as follows: 374 | // - pa.pgoff = va.pgoff. 375 | // - If i > 0, then this is a superpage translation and pa.ppn[i−1:0] = va.vpn[i−1:0]. 376 | // - pa.ppn[LEVELS−1:i] = pte.ppn[LEVELS−1:i]. 377 | let page_offset = addr & 0xfff; 378 | let paddr = match i { 379 | 0 => { 380 | // pa.ppn[LEVELS−1:0] = pte.ppn[LEVELS−1:0] 381 | let ppn = (pte >> 10) & 0x0fffffffffff; 382 | (ppn << 12) | page_offset 383 | } 384 | 1 => { 385 | // pa.ppn[0:0] = va.vpn[0:0] 386 | // pa.ppn[LEVELS−1:1] = pte.ppn[LEVELS−1:1] 387 | match levels { 388 | 3 => (ppn[2] << 30) | (ppn[1] << 21) | (vpn[0] << 12) | page_offset, 389 | 4 => (ppn[3] << 39) | (ppn[2] << 30) | (ppn[1] << 21) | (vpn[0] << 12) | page_offset, 390 | 5 => (ppn[4] << 48) | (ppn[3] << 39) | (ppn[2] << 30) | (ppn[1] << 21) | (vpn[0] << 12) | page_offset, 391 | _ => unreachable!(), 392 | } 393 | } 394 | 2 => { 395 | // pa.ppn[1:0] = va.vpn[1:0] 396 | // pa.ppn[LEVELS−1:2] = pte.ppn[LEVELS−1:2] 397 | match levels { 398 | 3 => (ppn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 399 | 4 => (ppn[3] << 39) | (ppn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 400 | 5 => (ppn[4] << 48) | (ppn[3] << 39) | (ppn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 401 | _ => unreachable!() 402 | } 403 | } 404 | 3 => { 405 | // pa.ppn[2:0] = va.vpn[2:0] 406 | // pa.ppn[LEVELS−1:3] = pte.ppn[LEVELS−1:3] 407 | match levels { 408 | 3 => (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 409 | 4 => (ppn[3] << 39) | (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 410 | 5 => (ppn[4] << 48) | (ppn[3] << 39) | (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 411 | _ => unreachable!() 412 | } 413 | } 414 | 4 => { 415 | // pa.ppn[3:0] = va.vpn[3:0] 416 | // pa.ppn[LEVELS−1:4] = pte.ppn[LEVELS−1:4] 417 | match levels { 418 | 3 => (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 419 | 4 => (vpn[3] << 39) | (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 420 | 5 => (ppn[4] << 48) | (vpn[3] << 39) | (vpn[2] << 30) | (vpn[1] << 21) | (vpn[0] << 12) | page_offset, 421 | _ => unreachable!() 422 | } 423 | } 424 | _ => unreachable!(), 425 | }; 426 | 427 | // 4.3.2 Virtual Address Translation Process 428 | // TODO: The results of implicit address-translation reads in step 2 may be held in a read-only, 429 | // incoherent address-translation cache but not shared with other harts. 430 | Ok(VirtAddr(paddr)) 431 | } 432 | } -------------------------------------------------------------------------------- /valheim-core/src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::Debug; 3 | 4 | use valheim_asm::isa::typed::Reg; 5 | 6 | use crate::cpu::bus::{RV64_MEMORY_BASE, RV64_MEMORY_SIZE}; 7 | use crate::cpu::mmu::VMMode; 8 | use crate::debug::trace::{Journal, RegTrace, Trace}; 9 | use crate::memory::VirtAddr; 10 | 11 | pub mod regs; 12 | pub mod execute; 13 | pub mod bus; 14 | pub mod csr; 15 | pub mod data; 16 | pub mod irq; 17 | pub mod mmu; 18 | 19 | #[derive(Debug)] 20 | pub struct RV64Cpu { 21 | pub regs: regs::Regs, 22 | pub csrs: csr::CSRRegs, 23 | pub mode: PrivilegeMode, 24 | pub bus: bus::Bus, 25 | /// used by LR/SC 26 | pub reserved: Vec, 27 | /// used by wfi instruction 28 | pub wfi: bool, 29 | /// Virtual memory translation mode 30 | pub vmmode: VMMode, 31 | /// physical page number used in virtual memory translation 32 | pub vmppn: u64, 33 | /// current cycle instruction 34 | pub instr: u64, 35 | 36 | // used for debugging 37 | pub journal: Journal, 38 | } 39 | 40 | #[derive(Debug, PartialEq, PartialOrd, Eq, Copy, Clone)] 41 | pub enum PrivilegeMode { 42 | User = 0b00, 43 | Supervisor = 0b01, 44 | Machine = 0b11, 45 | } 46 | 47 | impl RV64Cpu { 48 | pub fn new(trace: Option) -> RV64Cpu { 49 | let regs = regs::Regs::new(); 50 | let csrs = csr::CSRRegs::new(); 51 | RV64Cpu { 52 | regs, 53 | csrs, 54 | mode: PrivilegeMode::Machine, 55 | bus: bus::Bus::new().expect("Failed to create Bus"), 56 | reserved: Vec::new(), 57 | wfi: false, 58 | vmppn: 0, 59 | vmmode: VMMode::MBARE, 60 | journal: Journal { 61 | init_regs: regs, 62 | init_mem_base: VirtAddr(RV64_MEMORY_BASE), 63 | init_mem_size: RV64_MEMORY_SIZE as usize, 64 | traces: RefCell::new(Vec::with_capacity(1024)), 65 | max_recent_traces: if trace.is_some() { 1024 } else { 0 }, 66 | trace_file: trace, 67 | }, 68 | instr: 0, 69 | } 70 | } 71 | 72 | #[inline(always)] 73 | pub fn read_reg(&self, reg: Reg) -> Option { 74 | let val = self.regs.read(reg); 75 | self.journal.trace(|| Trace::Reg(RegTrace::Read(reg, val))); 76 | val 77 | } 78 | 79 | #[inline(always)] 80 | pub fn read_reg_fp(&self, reg: Reg) -> Option { 81 | let val = self.regs.read_fp(reg); 82 | self.journal.trace(|| Trace::Reg(RegTrace::ReadFp(reg, val))); 83 | val 84 | } 85 | 86 | #[inline(always)] 87 | pub fn write_reg(&mut self, reg: Reg, val: u64) -> Option<()> { 88 | let res = self.regs.write(reg, val); 89 | self.journal.trace(|| Trace::Reg(RegTrace::Write(reg, val, res.is_some()))); 90 | res 91 | } 92 | 93 | #[inline(always)] 94 | pub fn write_reg_fp(&mut self, reg: Reg, val: f64) -> Option<()> { 95 | let res = self.regs.write_fp(reg, val); 96 | self.journal.trace(|| Trace::Reg(RegTrace::WriteFp(reg, val, res.is_some()))); 97 | res 98 | } 99 | 100 | /// internal fast-path 101 | #[inline(always)] 102 | pub fn read_pc(&self) -> VirtAddr { 103 | self.journal.trace(|| Trace::Reg(RegTrace::Read(Reg::PC, Some(self.regs.pc.0)))); 104 | self.regs.pc 105 | } 106 | 107 | /// internal fast-path 108 | #[inline(always)] 109 | pub fn write_pc(&mut self, pc: VirtAddr) { 110 | self.regs.pc = pc; 111 | self.journal.trace(|| Trace::Reg(RegTrace::Write(Reg::PC, pc.0, true))); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /valheim-core/src/cpu/regs.rs: -------------------------------------------------------------------------------- 1 | use valheim_asm::isa::typed::Reg; 2 | 3 | use crate::memory::VirtAddr; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct Regs { 7 | pub x: [u64; 32], 8 | pub f: [f64; 32], 9 | pub pc: VirtAddr, 10 | } 11 | 12 | impl Regs { 13 | pub fn new() -> Regs { 14 | Regs { 15 | x: [0; 32], 16 | f: [0.0; 32], 17 | pc: VirtAddr(0), 18 | } 19 | } 20 | 21 | pub fn read(&self, reg: Reg) -> Option { 22 | match reg { 23 | Reg::ZERO => Some(0), 24 | Reg::X(x) if x.value() == 0 => Some(0), 25 | Reg::X(x) => Some(self.x[x.value() as usize]), 26 | Reg::PC => Some(self.pc.0), 27 | _ => None, 28 | } 29 | } 30 | 31 | pub fn read_fp(&self, reg: Reg) -> Option { 32 | match reg { 33 | Reg::F(f) => Some(self.f[f.value() as usize]), 34 | _ => None, 35 | } 36 | } 37 | 38 | pub fn write(&mut self, reg: Reg, value: u64) -> Option<()> { 39 | match reg { 40 | Reg::ZERO => (), 41 | Reg::X(x) if x.value() == 0 => (), 42 | Reg::X(x) => self.x[x.value() as usize] = value, 43 | Reg::PC => self.pc = VirtAddr(value), 44 | _ => return None, 45 | } 46 | Some(()) 47 | } 48 | 49 | pub fn write_fp(&mut self, reg: Reg, value: f64) -> Option<()> { 50 | match reg { 51 | Reg::F(f) => self.f[f.value() as usize] = value, 52 | _ => return None, 53 | } 54 | Some(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /valheim-core/src/debug/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod trace; 2 | -------------------------------------------------------------------------------- /valheim-core/src/debug/trace.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | use std::cell::RefCell; 4 | 5 | use valheim_asm::isa::typed::{Instr, Reg}; 6 | use valheim_asm::isa::untyped::Bytecode; 7 | use valheim_asm::isa::untyped16::Bytecode16; 8 | 9 | use crate::cpu::regs::Regs; 10 | use crate::memory::VirtAddr; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Journal { 14 | pub init_regs: Regs, 15 | pub init_mem_base: VirtAddr, 16 | pub init_mem_size: usize, 17 | pub traces: RefCell>, 18 | pub max_recent_traces: usize, 19 | pub trace_file: Option, 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq)] 23 | pub enum Trace { 24 | Reg(RegTrace), 25 | Mem(MemTrace), 26 | Instr(InstrTrace), 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq, Eq)] 30 | pub enum InstrTrace { 31 | Fetched(VirtAddr, Bytecode, Bytecode16), 32 | Decoded(VirtAddr, Bytecode, Instr), 33 | DecodedCompressed(VirtAddr, Bytecode16, Instr), 34 | PrepareExecute(VirtAddr, Instr), 35 | Executed(VirtAddr, Instr), 36 | ExecutedCompressed(VirtAddr, Instr), 37 | } 38 | 39 | #[derive(Debug, Clone, PartialEq)] 40 | pub enum RegTrace { 41 | Read(Reg, Option), 42 | ReadFp(Reg, Option), 43 | Write(Reg, u64, bool), 44 | WriteFp(Reg, f64, bool), 45 | } 46 | 47 | #[derive(Debug, Clone, PartialEq, Eq)] 48 | pub enum MemTrace { 49 | Read(VirtAddr, VirtAddr, usize, String), 50 | Write(VirtAddr, VirtAddr, usize, String), 51 | } 52 | 53 | impl Journal { 54 | #[inline(always)] 55 | pub fn trace Trace>(&self, f: F) { 56 | #[cfg(feature = "trace")] 57 | self.write_traces(f()); 58 | } 59 | 60 | #[cfg(feature = "trace")] 61 | fn write_traces(&self, trace: Trace) { 62 | if self.max_recent_traces == 0 { return; } 63 | let mut traces = self.traces.borrow_mut(); 64 | traces.push(trace); 65 | if traces.len() > self.max_recent_traces { 66 | self.flush(); 67 | } 68 | } 69 | 70 | pub fn flush(&self) { 71 | #[cfg(feature = "trace")] { 72 | use std::io::Write; 73 | self.trace_file.as_ref() 74 | .and_then(|filename| std::fs::OpenOptions::new().create(true).write(true).append(true).open(filename).ok()) 75 | .map(|mut file| { 76 | self.traces.borrow_mut().iter().for_each(|t| { 77 | let _ = writeln!(file, "{:?}", t); 78 | }); 79 | let _ = file.flush(); 80 | let _ = file.sync_all(); 81 | drop(file); 82 | }); 83 | self.traces.borrow_mut().clear(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /valheim-core/src/device/clint.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/qemu/qemu/blob/master/hw/intc/sifive_clint.c 2 | // https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_clint.h 3 | 4 | use crate::cpu::bus::CLINT_BASE; 5 | use crate::cpu::csr::{CSRMap, CSRRegs}; 6 | use crate::cpu::csr::CSRMap::{*}; 7 | use crate::cpu::irq::Exception; 8 | use crate::memory::{CanIO, VirtAddr}; 9 | 10 | /// machine timer register 11 | const MTIME: u64 = CLINT_BASE + 0xbff8; 12 | const MTIME_END: u64 = MTIME + 8; 13 | 14 | /// machine timer compare register 15 | /// 3.2.1 Machine Timer Registers (mtime and mtimecmp) 16 | /// Lower privilege levels do not have their own timecmp registers. 17 | /// Instead, machine-mode software can implement any number of virtual timers on a hart by multiplexing the next timer interrupt into the mtimecmp register. 18 | const MTIMECMP: u64 = CLINT_BASE + 0x4000; 19 | const MTIMECMP_END: u64 = MTIMECMP + 8; 20 | 21 | /// machine software interrupt pending register 22 | const MSIP: u64 = CLINT_BASE; 23 | const MSIP_END: u64 = MSIP + 4; 24 | 25 | pub struct Clint { 26 | msip: u32, 27 | mtimecmp: u64, 28 | mtime: u64, 29 | } 30 | 31 | impl Clint { 32 | pub fn new() -> Self { 33 | Self { 34 | msip: 0, 35 | mtimecmp: 0, 36 | mtime: 0, 37 | } 38 | } 39 | 40 | pub fn tick(&mut self, csrs: &mut CSRRegs) { 41 | let old_mtime = self.mtime; 42 | let old_csr_time = csrs.csrs[CSRMap::TIME as usize]; 43 | debug_assert_eq!(old_mtime, old_csr_time); 44 | 45 | // 3.2.1 Machine Timer Registers (mtime and mtimecmp) 46 | // mtime must increment at constant frequency, and the platform must provide a mechanism for determining the period of an mtime tick. 47 | 48 | self.mtime = old_mtime.wrapping_add(1); 49 | csrs.csrs[CSRMap::TIME as usize] = old_csr_time.wrapping_add(1); 50 | 51 | // 3.2.1 Machine Timer Registers (mtime and mtimecmp) 52 | // A machine timer interrupt becomes pending whenever mtime contains a value greater than or 53 | // equal to mtimecmp, treating the values as unsigned integers. The interrupt remains posted 54 | // until mtimecmp becomes greater than mtime (typically as a result of writing mtimecmp). 55 | // The interrupt will only be taken if interrupts are enabled the MTIE bit is set in the mie register. 56 | 57 | // note: just set the pending bit, the CPU will check whether the interrupt should be taken 58 | // together with the enable bit. 59 | 60 | if (self.msip & 1) != 0 { 61 | let _ = csrs.write_unchecked(MIP, csrs.read_unchecked(MIP) | MSIP_MASK); 62 | } 63 | 64 | if self.mtime >= self.mtimecmp { 65 | // take the interrupt 66 | let _ = csrs.write_unchecked(MIP, csrs.read_unchecked(MIP) | MTIP_MASK); 67 | } else { 68 | // clear the interrupt 69 | let _ = csrs.write_unchecked(MIP, csrs.read_unchecked(MIP) & !MTIP_MASK); 70 | } 71 | } 72 | 73 | pub fn read(&self, addr: VirtAddr) -> Result { 74 | let (val, offset) = match addr.0 { 75 | MSIP..=MSIP_END => (self.msip as u64, addr.0 - MSIP), 76 | MTIMECMP..=MTIMECMP_END => (self.mtimecmp, addr.0 - MTIMECMP), 77 | MTIME..=MTIME_END => (self.mtime, addr.0 - MTIME), 78 | _ => return Err(Exception::LoadAccessFault(addr)), 79 | }; 80 | 81 | let val = match std::mem::size_of::() { 82 | 1 => (val >> (offset * 8)) & 0xff, 83 | 2 => (val >> (offset * 8)) & 0xffff, 84 | 4 => (val >> (offset * 8)) & 0xffffffff, 85 | 8 => val, 86 | _ => return Err(Exception::LoadAccessFault(addr)), 87 | }; 88 | Ok(val) 89 | } 90 | 91 | pub fn write(&mut self, addr: VirtAddr, value: u64) -> Result<(), Exception> { 92 | let (mut old, offset) = match addr.0 { 93 | MSIP..=MSIP_END => (self.msip as u64, addr.0 - MSIP), 94 | MTIMECMP..=MTIMECMP_END => (self.mtimecmp, addr.0 - MTIMECMP), 95 | MTIME..=MTIME_END => (self.mtime, addr.0 - MTIME), 96 | _ => return Err(Exception::StoreAccessFault(addr)), 97 | }; 98 | 99 | // Calculate the new value of the target register based on `size` and `offset`. 100 | match std::mem::size_of::() { 101 | 1 => { 102 | // Clear the target byte. 103 | old = old & (!(0xff << (offset * 8))); 104 | // Set the new `value` to the target byte. 105 | old = old | ((value & 0xff) << (offset * 8)); 106 | } 107 | 2 => { 108 | old = old & (!(0xffff << (offset * 8))); 109 | old = old | ((value & 0xffff) << (offset * 8)); 110 | } 111 | 4 => { 112 | old = old & (!(0xffffffff << (offset * 8))); 113 | old = old | ((value & 0xffffffff) << (offset * 8)); 114 | } 115 | 8 => { 116 | old = value; 117 | } 118 | _ => return Err(Exception::StoreAccessFault(addr)), 119 | } 120 | 121 | // Store the new value to the target register. 122 | match addr.0 { 123 | MSIP..=MSIP_END => self.msip = old as u32, 124 | MTIMECMP..=MTIMECMP_END => self.mtimecmp = old, 125 | MTIME..=MTIME_END => self.mtime = old, 126 | _ => return Err(Exception::StoreAccessFault(addr)), 127 | } 128 | Ok(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /valheim-core/src/device/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::memory::{Memory, VirtAddr}; 2 | 3 | pub mod ns16550a; 4 | pub mod clint; 5 | pub mod plic; 6 | pub mod virtio; 7 | 8 | pub trait Device { 9 | fn name(&self) -> &'static str; 10 | fn vendor_id(&self) -> u16; 11 | fn device_id(&self) -> u16; 12 | fn init(&self) -> Result, ()>; 13 | fn destroy(&self) -> Result<(), ()>; 14 | fn dma_read(&self, addr: VirtAddr) -> Option<&Memory>; 15 | fn dma_write(&self, addr: VirtAddr) -> Option<&mut Memory>; 16 | fn mmio_read(&self, addr: VirtAddr) -> Option; 17 | fn mmio_write(&self, addr: VirtAddr, val: u8) -> Result<(), ()>; 18 | fn is_interrupting(&self) -> Option; 19 | 20 | fn read(&self, addr: VirtAddr) -> Option { 21 | match self.dma_read(addr) { 22 | Some(mem) => mem.read(addr), 23 | None => self.mmio_read(addr), 24 | } 25 | } 26 | 27 | fn write(&self, addr: VirtAddr, data: u8) -> Result<(), ()> { 28 | match self.dma_write(addr) { 29 | Some(mem) => mem.write(addr, data).ok_or(()), 30 | None => self.mmio_write(addr, data), 31 | } 32 | } 33 | 34 | fn read16(&self, addr: VirtAddr) -> Option { 35 | match self.dma_read(addr) { 36 | Some(mem) => mem.read(addr), 37 | None => { 38 | let lo = self.mmio_read(addr)?; 39 | let hi = self.mmio_read(addr + VirtAddr(1))?; 40 | Some((hi as u16) << 8 | lo as u16) 41 | } 42 | } 43 | } 44 | 45 | fn read32(&self, addr: VirtAddr) -> Option { 46 | match self.dma_read(addr) { 47 | Some(mem) => mem.read(addr), 48 | None => { 49 | let lo = self.mmio_read(addr)?; 50 | let hi = self.mmio_read(addr + VirtAddr(1))?; 51 | let lo2 = self.mmio_read(addr + VirtAddr(2))?; 52 | let hi2 = self.mmio_read(addr + VirtAddr(3))?; 53 | Some((hi2 as u32) << 24 | (lo2 as u32) << 16 | (hi as u32) << 8 | lo as u32) 54 | } 55 | } 56 | } 57 | 58 | fn read64(&self, addr: VirtAddr) -> Option { 59 | match self.dma_read(addr) { 60 | Some(mem) => mem.read(addr), 61 | None => { 62 | let lo = self.mmio_read(addr)?; 63 | let hi = self.mmio_read(addr + VirtAddr(1))?; 64 | let lo2 = self.mmio_read(addr + VirtAddr(2))?; 65 | let hi2 = self.mmio_read(addr + VirtAddr(3))?; 66 | let lo3 = self.mmio_read(addr + VirtAddr(4))?; 67 | let hi3 = self.mmio_read(addr + VirtAddr(5))?; 68 | let lo4 = self.mmio_read(addr + VirtAddr(6))?; 69 | let hi4 = self.mmio_read(addr + VirtAddr(7))?; 70 | Some((hi4 as u64) << 56 | (lo4 as u64) << 48 | (hi3 as u64) << 40 | (lo3 as u64) << 32 71 | | (hi2 as u64) << 24 | (lo2 as u64) << 16 | (hi as u64) << 8 | lo as u64) 72 | } 73 | } 74 | } 75 | 76 | fn write16(&self, addr: VirtAddr, data: u16) -> Result<(), ()> { 77 | match self.dma_write(addr) { 78 | Some(mem) => mem.write(addr, data).ok_or(()), 79 | None => { 80 | self.mmio_write(addr, data as u8)?; 81 | self.mmio_write(addr + VirtAddr(1), (data >> 8) as u8) 82 | } 83 | } 84 | } 85 | 86 | fn write32(&self, addr: VirtAddr, data: u32) -> Result<(), ()> { 87 | match self.dma_write(addr) { 88 | Some(mem) => mem.write(addr, data).ok_or(()), 89 | None => { 90 | self.mmio_write(addr, data as u8)?; 91 | self.mmio_write(addr + VirtAddr(1), (data >> 8) as u8)?; 92 | self.mmio_write(addr + VirtAddr(2), (data >> 16) as u8)?; 93 | self.mmio_write(addr + VirtAddr(3), (data >> 24) as u8) 94 | } 95 | } 96 | } 97 | 98 | fn write64(&self, addr: VirtAddr, data: u64) -> Result<(), ()> { 99 | match self.dma_write(addr) { 100 | Some(mem) => mem.write(addr, data).ok_or(()), 101 | None => { 102 | self.mmio_write(addr, data as u8)?; 103 | self.mmio_write(addr + VirtAddr(1), (data >> 8) as u8)?; 104 | self.mmio_write(addr + VirtAddr(2), (data >> 16) as u8)?; 105 | self.mmio_write(addr + VirtAddr(3), (data >> 24) as u8)?; 106 | self.mmio_write(addr + VirtAddr(4), (data >> 32) as u8)?; 107 | self.mmio_write(addr + VirtAddr(5), (data >> 40) as u8)?; 108 | self.mmio_write(addr + VirtAddr(6), (data >> 48) as u8)?; 109 | self.mmio_write(addr + VirtAddr(7), (data >> 56) as u8) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /valheim-core/src/device/ns16550a.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::prelude::*; 3 | use std::sync::{ 4 | Arc, 5 | atomic::{AtomicBool, Ordering}, Condvar, Mutex, 6 | }; 7 | 8 | use crate::device::Device; 9 | use crate::memory::{Memory, VirtAddr}; 10 | 11 | pub const UART_BASE: u64 = 0x1000_0000; 12 | pub const UART_SIZE: u64 = 0x100; 13 | const UART_END: u64 = UART_BASE + 0x100; 14 | 15 | /// The interrupt request of UART. 16 | pub const UART_IRQ: u64 = 10; 17 | 18 | /// Receive holding register (for input bytes). 19 | pub const UART_RHR: u64 = UART_BASE + 0; 20 | 21 | /// Transmit holding register (for output bytes). 22 | pub const UART_THR: u64 = UART_BASE + 0; 23 | 24 | /// Interrupt enable register. 25 | pub const UART_IER: u64 = UART_BASE + 1; 26 | 27 | /// FIFO control register. 28 | pub const UART_FCR: u64 = UART_BASE + 2; 29 | 30 | /// Interrupt status register. 31 | /// ISR[0] = 0: an interrupt is pending and the ISR contents may be used as a pointer to the appropriate interrupt service routine. 32 | /// ISR[0] = 1: no interrupt is pending. 33 | pub const UART_ISR: u64 = UART_BASE + 2; 34 | 35 | /// Line control register. 36 | pub const UART_LCR: u64 = UART_BASE + 3; 37 | 38 | /// Line status register. 39 | /// LSR[0] = 0: no data in receive holding register or FIFO. 40 | /// LSR[0] = 1: data has been receive and saved in the receive holding register or FIFO. 41 | /// LSR[5] = 0: transmit holding register is full. 16550 will not accept any data for transmission. 42 | /// LSR[5] = 1: transmitter hold register (or FIFO) is empty. CPU can load the next character. 43 | pub const UART_LSR: u64 = UART_BASE + 5; 44 | /// LSR[0] mask 45 | pub const UART_LSR_RX: u8 = 1; 46 | /// LSR[5] mask 47 | pub const UART_LSR_TX: u8 = 1 << 5; 48 | 49 | pub struct Uart16550a { 50 | buffer: Arc<(Mutex<[u8; UART_SIZE as usize]>, Condvar)>, 51 | interrupting: Arc, 52 | } 53 | 54 | impl Uart16550a { 55 | pub fn new() -> Self { 56 | let uart = Arc::new((Mutex::new([0; UART_SIZE as usize]), Condvar::new())); 57 | let interrupting = Arc::new(AtomicBool::new(false)); 58 | { 59 | let (uart, _) = uart.as_ref(); 60 | let mut uart = uart.lock().expect("cannot lock uart buffer"); 61 | // Transmitter hold register is empty. It allows input anytime. 62 | uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_TX; 63 | } 64 | 65 | { 66 | let uart = uart.clone(); 67 | let interrupting = interrupting.clone(); 68 | std::thread::spawn(move || loop { 69 | let mut buffer = [0; 1]; 70 | match io::stdin().read(&mut buffer) { 71 | Ok(_) => { 72 | let (uart, cond) = uart.as_ref(); 73 | let mut uart = uart.lock().expect("cannot lock uart buffer"); 74 | // we can only write to the register if there's no previous data. 75 | // we achieve this by checking the bit 0 of LSR: 76 | // - 0: no data in receive holding register. 77 | // - 1: means data has been receive and saved in the receive holding register. 78 | while (uart[(UART_LSR - UART_BASE) as usize] & UART_LSR_RX) == 1 { 79 | uart = cond.wait(uart).expect("cannot wait on uart buffer"); 80 | } 81 | uart[0] = buffer[0]; 82 | interrupting.store(true, Ordering::Release); 83 | uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_RX; 84 | } 85 | Err(e) => { 86 | println!("[Valheim] uart input error: {}", e); 87 | } 88 | } 89 | }); 90 | } 91 | 92 | Self { buffer: uart, interrupting } 93 | } 94 | } 95 | 96 | impl Device for Uart16550a { 97 | fn name(&self) -> &'static str { 98 | // TODO: name 99 | "UART16550A" 100 | } 101 | 102 | fn vendor_id(&self) -> u16 { 103 | // TODO: vendor id 104 | 0x0000 105 | } 106 | 107 | fn device_id(&self) -> u16 { 108 | // TODO: device id 109 | 0x0000 110 | } 111 | 112 | fn init(&self) -> Result, ()> { 113 | Ok(vec![(VirtAddr(UART_BASE), VirtAddr(UART_END))]) 114 | } 115 | 116 | fn destroy(&self) -> Result<(), ()> { 117 | Ok(()) 118 | } 119 | 120 | fn dma_read(&self, _addr: VirtAddr) -> Option<&Memory> { 121 | None 122 | } 123 | 124 | fn dma_write(&self, _addr: VirtAddr) -> Option<&mut Memory> { 125 | None 126 | } 127 | 128 | fn mmio_read(&self, addr: VirtAddr) -> Option { 129 | let (uart, cond) = &*self.buffer; 130 | let mut uart = uart.lock().expect("cannot lock uart buffer"); 131 | match addr.0 { 132 | UART_RHR => { 133 | let val = uart[(UART_RHR - UART_BASE) as usize]; 134 | uart[(UART_LSR - UART_BASE) as usize] &= !UART_LSR_RX; 135 | cond.notify_one(); 136 | Some(val) 137 | } 138 | addr => Some(uart[(addr - UART_BASE) as usize]), 139 | } 140 | } 141 | 142 | fn mmio_write(&self, addr: VirtAddr, val: u8) -> Result<(), ()> { 143 | let (uart, _) = &*self.buffer; 144 | let mut uart = uart.lock().expect("cannot lock uart buffer"); 145 | match addr.0 { 146 | UART_THR => { 147 | print!("{}", val as char); 148 | io::stdout().flush().expect("cannot flush stdout"); 149 | } 150 | addr => { 151 | uart[(addr - UART_BASE) as usize] = val; 152 | } 153 | } 154 | Ok(()) 155 | } 156 | 157 | fn is_interrupting(&self) -> Option { 158 | let irq = self.interrupting.swap(false, Ordering::Acquire); 159 | match irq { 160 | true => Some(UART_IRQ), 161 | false => None, 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /valheim-core/src/device/plic.rs: -------------------------------------------------------------------------------- 1 | // TODO: rewrite this file. 2 | 3 | // https://github.com/qemu/qemu/blob/master/hw/intc/sifive_plic.c 4 | // https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_plic.h 5 | 6 | use crate::cpu::bus::PLIC_BASE; 7 | use crate::cpu::irq::Exception; 8 | use crate::memory::VirtAddr; 9 | 10 | /// The address for interrupt source priority. 1024 4-byte registers exist. Each interrupt into the 11 | /// PLIC has a configurable priority, from 1-7, with 7 being the highest priority. A value of 0 12 | /// means do not interrupt, effectively disabling that interrupt. 13 | const SOURCE_PRIORITY: u64 = PLIC_BASE; 14 | const SOURCE_PRIORITY_END: u64 = PLIC_BASE + 0xfff; 15 | 16 | /// The address range for interrupt pending bits. 32 4-byte (1024 bits) registers exist. 17 | /// 18 | /// https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#memory-map 19 | /// base + 0x001000: Interrupt Pending bit 0-31 20 | /// base + 0x00107C: Interrupt Pending bit 992-1023 21 | const PENDING: u64 = PLIC_BASE + 0x1000; 22 | const PENDING_END: u64 = PLIC_BASE + 0x107f; 23 | 24 | /// The address range for enable registers. The maximum number of contexts is 15871 but this PLIC 25 | /// supports only 2 contexts. 26 | /// 27 | /// https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#memory-map 28 | /// base + 0x002000: Enable bits for sources 0-31 on context 0 29 | /// base + 0x002004: Enable bits for sources 32-63 on context 0 30 | /// ... 31 | /// base + 0x00207F: Enable bits for sources 992-1023 on context 0 32 | /// base + 0x002080: Enable bits for sources 0-31 on context 1 33 | /// base + 0x002084: Enable bits for sources 32-63 on context 1 34 | /// ... 35 | /// base + 0x0020FF: Enable bits for sources 992-1023 on context 1 36 | const ENABLE: u64 = PLIC_BASE + 0x2000; 37 | const ENABLE_END: u64 = PLIC_BASE + 0x20ff; 38 | 39 | /// The address range for priority thresholds and claim/complete registers. The maximum number of 40 | /// contexts is 15871 but this PLIC supports only 2 contexts. 41 | /// 42 | /// https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#memory-map 43 | /// base + 0x200000: Priority threshold for context 0 44 | /// base + 0x200004: Claim/complete for context 0 45 | /// base + 0x200008: Reserved 46 | /// ... 47 | /// base + 0x200FFC: Reserved 48 | /// base + 0x201000: Priority threshold for context 1 49 | /// base + 0x201004: Claim/complete for context 1 50 | const THRESHOLD_AND_CLAIM: u64 = PLIC_BASE + 0x200000; 51 | const THRESHOLD_AND_CLAIM_END: u64 = PLIC_BASE + 0x201007; 52 | 53 | const WORD_SIZE: u64 = 0x4; 54 | const CONTEXT_OFFSET: u64 = 0x1000; 55 | const SOURCE_NUM: u64 = 1024; 56 | 57 | /// The platform-level-interrupt controller (PLIC). 58 | pub struct Plic { 59 | /// The interrupt priority for each interrupt source. A priority value of 0 is reserved to mean 60 | /// "never interrupt" and effectively disables the interrupt. Priority 1 is the lowest active 61 | /// priority, and priority 7 is the highest. 62 | priority: [u32; SOURCE_NUM as usize], 63 | /// Interrupt pending bits. If bit 1 is set, a global interrupt 1 is pending. A pending bit in 64 | /// the PLIC core can be cleared by setting the associated enable bit then performing a claim. 65 | pending: [u32; 32], 66 | /// Interrupt Enable Bit of Interrupt Source #0 to #1023 for 2 contexts. 67 | enable: [u32; 64], 68 | /// The settings of a interrupt priority threshold of each context. The PLIC will mask all PLIC 69 | /// interrupts of a priority less than or equal to `threshold`. 70 | threshold: [u32; 2], 71 | /// The ID of the highest priority pending interrupt or zero if there is no pending interrupt 72 | /// for each context. 73 | claim: [u32; 2], 74 | } 75 | 76 | impl Plic { 77 | pub fn new() -> Self { 78 | Self { 79 | priority: [0; 1024], 80 | pending: [0; 32], 81 | enable: [0; 64], 82 | threshold: [0; 2], 83 | claim: [0; 2], 84 | } 85 | } 86 | 87 | pub fn update_pending(&mut self, irq: u64) { 88 | let index = irq.wrapping_div(WORD_SIZE); 89 | self.pending[index as usize] = self.pending[index as usize] | (1 << irq); 90 | 91 | self.update_claim(irq); 92 | } 93 | 94 | fn clear_pending(&mut self, irq: u64) { 95 | let index = irq.wrapping_div(WORD_SIZE); 96 | self.pending[index as usize] = self.pending[index as usize] & !(1 << irq); 97 | 98 | self.update_claim(0); 99 | } 100 | 101 | fn update_claim(&mut self, irq: u64) { 102 | // TODO: Support highest priority to the `claim` register. 103 | // claim[1] is claim/complete registers for S-mode (context 1). SCLAIM. 104 | if self.is_enable(1, irq) || irq == 0 { 105 | self.claim[1] = irq as u32; 106 | } 107 | } 108 | 109 | fn is_enable(&self, context: u64, irq: u64) -> bool { 110 | let index = (irq.wrapping_rem(SOURCE_NUM)).wrapping_div(WORD_SIZE * 8); 111 | let offset = (irq.wrapping_rem(SOURCE_NUM)).wrapping_rem(WORD_SIZE * 8); 112 | return ((self.enable[(context * 32 + index) as usize] >> offset) & 1) == 1; 113 | } 114 | 115 | pub fn read(&self, addr: VirtAddr) -> Result { 116 | // TODO: support CanIO types 117 | let addr = addr.0; 118 | match addr { 119 | SOURCE_PRIORITY..=SOURCE_PRIORITY_END => { 120 | if (addr - SOURCE_PRIORITY).wrapping_rem(WORD_SIZE) != 0 { 121 | return Err(Exception::LoadAccessFault(VirtAddr(addr))); 122 | } 123 | let index = (addr - SOURCE_PRIORITY).wrapping_div(WORD_SIZE); 124 | Ok(self.priority[index as usize]) 125 | } 126 | PENDING..=PENDING_END => { 127 | if (addr - PENDING).wrapping_rem(WORD_SIZE) != 0 { 128 | return Err(Exception::LoadAccessFault(VirtAddr(addr))); 129 | } 130 | let index = (addr - PENDING).wrapping_div(WORD_SIZE); 131 | Ok(self.pending[index as usize]) 132 | } 133 | ENABLE..=ENABLE_END => { 134 | if (addr - ENABLE).wrapping_rem(WORD_SIZE) != 0 { 135 | return Err(Exception::LoadAccessFault(VirtAddr(addr))); 136 | } 137 | let index = (addr - ENABLE).wrapping_div(WORD_SIZE); 138 | Ok(self.enable[index as usize]) 139 | } 140 | THRESHOLD_AND_CLAIM..=THRESHOLD_AND_CLAIM_END => { 141 | let context = (addr - THRESHOLD_AND_CLAIM).wrapping_div(CONTEXT_OFFSET); 142 | let offset = addr - (THRESHOLD_AND_CLAIM + CONTEXT_OFFSET * context); 143 | if offset == 0 { 144 | Ok(self.threshold[context as usize]) 145 | } else if offset == 4 { 146 | Ok(self.claim[context as usize]) 147 | } else { 148 | return Err(Exception::LoadAccessFault(VirtAddr(addr))); 149 | } 150 | } 151 | _ => return Err(Exception::LoadAccessFault(VirtAddr(addr))), 152 | } 153 | } 154 | 155 | pub fn write(&mut self, addr: VirtAddr, value: u32) -> Result<(), Exception> { 156 | // TODO: support CanIO types 157 | let addr = addr.0; 158 | match addr { 159 | SOURCE_PRIORITY..=SOURCE_PRIORITY_END => { 160 | if (addr - SOURCE_PRIORITY).wrapping_rem(WORD_SIZE) != 0 { 161 | return Err(Exception::StoreAccessFault(VirtAddr(addr))); 162 | } 163 | let index = (addr - SOURCE_PRIORITY).wrapping_div(WORD_SIZE); 164 | self.priority[index as usize] = value; 165 | } 166 | PENDING..=PENDING_END => { 167 | if (addr - PENDING).wrapping_rem(WORD_SIZE) != 0 { 168 | return Err(Exception::StoreAccessFault(VirtAddr(addr))); 169 | } 170 | let index = (addr - PENDING).wrapping_div(WORD_SIZE); 171 | self.pending[index as usize] = value; 172 | } 173 | ENABLE..=ENABLE_END => { 174 | if (addr - ENABLE).wrapping_rem(WORD_SIZE) != 0 { 175 | return Err(Exception::StoreAccessFault(VirtAddr(addr))); 176 | } 177 | let index = (addr - ENABLE).wrapping_div(WORD_SIZE); 178 | self.enable[index as usize] = value; 179 | } 180 | THRESHOLD_AND_CLAIM..=THRESHOLD_AND_CLAIM_END => { 181 | let context = (addr - THRESHOLD_AND_CLAIM).wrapping_div(CONTEXT_OFFSET); 182 | let offset = addr - (THRESHOLD_AND_CLAIM + CONTEXT_OFFSET * context); 183 | if offset == 0 { 184 | self.threshold[context as usize] = value; 185 | } else if offset == 4 { 186 | //self.claim[context as usize] = value as u32; 187 | 188 | // Clear pending bit. 189 | self.clear_pending(value as u64); 190 | } else { 191 | return Err(Exception::StoreAccessFault(VirtAddr(addr))); 192 | } 193 | } 194 | _ => return Err(Exception::StoreAccessFault(VirtAddr(addr))), 195 | } 196 | 197 | Ok(()) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /valheim-core/src/dtb/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | use std::process::{Command, Stdio}; 3 | 4 | const DTS_TEMPLATE: &str = include_str!("../../../dts/valheim.dts.template"); 5 | 6 | pub fn generate_device_tree_rom(cmdline: String, memory_base: u64, memory_size: u64) -> Result, std::io::Error> { 7 | let memory_reg = format!( 8 | "{:#x} {:#x} {:#x} {:#x}", 9 | (memory_base >> 32), (memory_base & (u32::MAX as u64)), 10 | (memory_size >> 32), (memory_size & (u32::MAX as u64)), 11 | ); 12 | let instantiated: String = DTS_TEMPLATE.to_string() 13 | .replace("${VALHEIM_BOOTARGS}", cmdline.as_ref()) 14 | .replace("${VALHEIM_MEMORY_REG}", &memory_reg); 15 | 16 | let mut dtb_bytes = call_compiler(instantiated)?; 17 | let mut rom = vec![0; 32]; 18 | rom.append(&mut dtb_bytes); 19 | let align = 0x1000; 20 | rom.resize((rom.len() + align - 1) / align * align, 0); 21 | Ok(rom) 22 | } 23 | 24 | fn call_compiler(dts: String) -> Result, std::io::Error> { 25 | // TODO: self-made device-tree-compiler. crates.io have only readers, not writers. 26 | let compiler = match Command::new("dtc") 27 | .args(["-I", "dts", "-O", "dtb", "-o", "-", "-"]) 28 | .stdin(Stdio::piped()) 29 | .stdout(Stdio::piped()) 30 | .stderr(Stdio::null()) 31 | .spawn() { 32 | Ok(p) => p, 33 | Err(e) => { 34 | eprintln!("Cannot find device tree compiler command `dtc` on this machine ({}).", e); 35 | eprintln!("Try installing `dtc` by:"); 36 | eprintln!(" macOS : brew install dtc"); 37 | eprintln!(" Ubuntu: sudo apt install device-tree-compiler"); 38 | eprintln!("In the near future, Valheim should comes with a builtin device tree compiler."); 39 | return Err(e); 40 | } 41 | }; 42 | 43 | compiler.stdin.expect("device tree compiler unavailable").write_all(dts.as_bytes())?; 44 | let mut dtb_bytes = Vec::with_capacity(32); 45 | compiler.stdout.expect("device tree compiler unavailable").read_to_end(&mut dtb_bytes)?; 46 | Ok(dtb_bytes) 47 | } 48 | -------------------------------------------------------------------------------- /valheim-core/src/interp/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::irq::Exception; 2 | use crate::cpu::RV64Cpu; 3 | 4 | pub mod naive; 5 | 6 | pub trait RV64Interpreter { 7 | fn interp(&self, cpu: &mut RV64Cpu) -> Result<(), Exception>; 8 | } 9 | -------------------------------------------------------------------------------- /valheim-core/src/interp/naive.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::data::Either; 2 | use crate::cpu::irq::Exception; 3 | use crate::cpu::RV64Cpu; 4 | use crate::interp::RV64Interpreter; 5 | 6 | pub struct NaiveInterpreter; 7 | 8 | impl NaiveInterpreter { 9 | pub fn new() -> NaiveInterpreter { 10 | NaiveInterpreter {} 11 | } 12 | } 13 | 14 | impl RV64Interpreter for NaiveInterpreter { 15 | fn interp(&self, cpu: &mut RV64Cpu) -> Result<(), Exception> { 16 | if cpu.wfi { 17 | return Ok(()); 18 | } 19 | let (pc, untyped, compressed) = cpu.fetch()?; 20 | cpu.instr = untyped.repr() as u64; // used as mtval when illegal instruction 21 | let (from, decoded) = cpu.decode(pc, untyped, compressed)?; 22 | let is_compressed = match from { 23 | Either::Left(_) => false, 24 | Either::Right(_) => true, 25 | }; 26 | // println!("pc = {:x}, RVC = {}, instr = {:?}", pc.0, is_compressed, decoded); 27 | cpu.execute(pc, decoded, is_compressed) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /valheim-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | 3 | #![feature(core_intrinsics)] 4 | #![feature(generic_const_exprs)] 5 | #![feature(inline_const_pat)] 6 | 7 | extern crate core; 8 | #[macro_use] 9 | extern crate derive_more; 10 | pub mod cpu; 11 | pub mod memory; 12 | pub mod interp; 13 | pub mod device; 14 | pub mod debug; 15 | pub mod machine; 16 | pub mod dtb; 17 | 18 | -------------------------------------------------------------------------------- /valheim-core/src/machine/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs::OpenOptions; 2 | use std::sync::Arc; 3 | 4 | use memmap2::MmapMut; 5 | 6 | use valheim_asm::isa::data::Fin; 7 | use valheim_asm::isa::rv64::CSRAddr; 8 | use valheim_asm::isa::typed::{Imm32, Reg}; 9 | 10 | use crate::cpu::bus::VIRT_MROM_BASE; 11 | use crate::cpu::irq::Exception; 12 | use crate::cpu::RV64Cpu; 13 | use crate::device::ns16550a::Uart16550a; 14 | use crate::dtb::generate_device_tree_rom; 15 | use crate::interp::naive::NaiveInterpreter; 16 | use crate::interp::RV64Interpreter; 17 | use crate::memory::VirtAddr; 18 | 19 | const RV64_PC_RESET: u64 = 0x80000000; 20 | const DEFAULT_CMDLINE: &str = "root=/dev/vda ro console=ttyS0"; 21 | 22 | pub struct Machine { 23 | pub cpu: RV64Cpu, 24 | pub interpreter: Box, 25 | } 26 | 27 | macro_rules! csr { 28 | ($self:expr,$csr:ident) => {$self.cpu.csrs.read(CSRAddr(Imm32::from(crate::cpu::csr::CSRMap::$csr as u32)))}; 29 | } 30 | 31 | impl Machine { 32 | pub fn new(cmdline: Option, trace: Option) -> Machine { 33 | let mut machine = Machine { 34 | cpu: RV64Cpu::new(trace), 35 | interpreter: Box::new(NaiveInterpreter::new()), 36 | }; 37 | 38 | let cmdline = cmdline.unwrap_or(DEFAULT_CMDLINE.to_string()); 39 | let memory_size = machine.cpu.bus.mem.memory_size as u64; 40 | let memory_base = machine.cpu.bus.mem.memory_base.0; 41 | let device_tree_rom = generate_device_tree_rom(cmdline, memory_base, memory_size) 42 | .expect("Cannot generate device tree"); 43 | machine.load_device_tree(device_tree_rom.as_slice()) 44 | .expect("Cannot load device tree"); 45 | unsafe { 46 | machine.cpu.bus.add_device(Arc::new(Uart16550a::new())) 47 | .expect("Cannot install UART device") 48 | }; 49 | machine 50 | } 51 | 52 | pub fn run(&mut self) { 53 | self.cpu.write_pc(VirtAddr(RV64_PC_RESET)); 54 | self.cpu.write_reg(Reg::X(Fin::new(11)), 0x1020); 55 | loop { 56 | let cont = self.run_next(); 57 | match cont { 58 | true => (), 59 | false => break, 60 | } 61 | } 62 | self.halt(); 63 | } 64 | 65 | pub fn run_next(&mut self) -> bool { 66 | self.cpu.bus.clint.tick(&mut self.cpu.csrs); 67 | 68 | if let Some(irq) = self.cpu.pending_interrupt() { 69 | // TODO: can IRQ fail to handle? 70 | let _ = irq.handle(&mut self.cpu); 71 | } 72 | 73 | match self.interpreter.interp(&mut self.cpu) { 74 | Ok(_) => true, 75 | // TODO: stop treating breakpoint as good trap 76 | Err(Exception::Breakpoint) => false, 77 | Err(ex) => { 78 | // println!("[Valheim] Exception: {:?} at {:#x} in {:?}", ex, self.cpu.read_pc().0, self.cpu.mode); 79 | // self.show_status(); 80 | // TODO: add watchdog to prevent kernels that do not handle double fault? 81 | // note: double/triple-fault can be handled by the M-mode program. 82 | // see: https://github.com/riscv/riscv-isa-manual/issues/3#issuecomment-278495907 83 | let _ = ex.handle(&mut self.cpu); 84 | true 85 | } 86 | } 87 | } 88 | 89 | pub fn run_for_test(&mut self, test_name: String) -> i32 { 90 | self.cpu.write_pc(VirtAddr(RV64_PC_RESET)); 91 | self.cpu.write_reg(Reg::X(Fin::new(11)), 0x1020); 92 | loop { 93 | let cont = self.run_next_for_test(); 94 | match cont { 95 | true => (), 96 | false => { 97 | // riscv-tests uses ecall to tell test results 98 | let gp = self.cpu.read_reg(Reg::X(Fin::new(3))).unwrap(); 99 | let a0 = self.cpu.read_reg(Reg::X(Fin::new(10))).unwrap(); 100 | let a7 = self.cpu.read_reg(Reg::X(Fin::new(17))).unwrap(); 101 | if a7 != 93 { continue; } // not the result telling ecall 102 | return if a0 == 0 && gp == 1 { 103 | println!("[Valheim:{:?}] {}: Test passed!", self.cpu.mode, test_name); 104 | 0 105 | } else { 106 | let failed = gp >> 1; 107 | println!("[Valheim:{:?}] {}: Test {} failed!, gp = {}, a0 = {}", self.cpu.mode, test_name, failed, gp, a0); 108 | self.show_status(); 109 | 1 110 | }; 111 | } 112 | } 113 | } 114 | } 115 | 116 | pub fn run_next_for_test(&mut self) -> bool { 117 | self.cpu.bus.clint.tick(&mut self.cpu.csrs); 118 | 119 | if let Some(irq) = self.cpu.pending_interrupt() { 120 | let _ = irq.handle(&mut self.cpu); 121 | } 122 | 123 | match self.interpreter.interp(&mut self.cpu) { 124 | Ok(_) => true, 125 | // riscv-tests uses ecall to tell test results 126 | Err(Exception::MachineEcall) => false, 127 | Err(Exception::UserEcall) => false, 128 | Err(Exception::SupervisorEcall) => false, 129 | Err(ex) => { 130 | let _ = ex.handle(&mut self.cpu); 131 | true 132 | } 133 | } 134 | } 135 | 136 | pub fn halt(&mut self) { 137 | self.cpu.bus.halt(); 138 | self.cpu.journal.flush(); 139 | self.show_status(); 140 | } 141 | 142 | pub fn show_status(&self) { 143 | let xabi = [ 144 | "zero", " ra ", " sp ", " gp ", " tp ", " t0 ", " t1 ", " t2 ", " s0 ", " s1 ", " a0 ", 145 | " a1 ", " a2 ", " a3 ", " a4 ", " a5 ", " a6 ", " a7 ", " s2 ", " s3 ", " s4 ", " s5 ", 146 | " s6 ", " s7 ", " s8 ", " s9 ", " s10", " s11", " t3 ", " t4 ", " t5 ", " t6 ", 147 | ]; 148 | let fabi = [ 149 | " ft0", " ft1", " ft2", " ft3", " ft4", " ft5", " ft6", " ft7", 150 | " fs0", " fs1", 151 | " fa0", " fa1", 152 | " fa2", " fa3", " fa4", " fa5", " fa6", " fa7", 153 | " fs2", " fs3", " fs4", " fs5", " fs6", " fs7", " fs8", " fs9", "fs10", "fs11", 154 | " ft8", " ft9", "ft10", "ft11", 155 | ]; 156 | println!("======================================="); 157 | println!("Privileged mode: {:?}", self.cpu.mode); 158 | println!("General purpose registers:"); 159 | println!(" pc : {:#x}", self.cpu.read_pc().0); 160 | for i in 0..31 { 161 | println!(" x{:<2} ({}): {:<#18x} f{:<2} ({}): {:}", i, xabi[i], self.cpu.regs.x[i], i, fabi[i], self.cpu.regs.f[i]); 162 | } 163 | println!("CSR registers:"); 164 | println!(" Machine Level CSR register:"); 165 | println!(" mstatus: {:<#18x} mtvec: {:<#18x} mepc: {:<#18x}", csr!(self, MSTATUS), csr!(self, MTVEC), csr!(self, MEPC)); 166 | println!(" mcause: {:<#18x} medeleg: {:<#18x} mideleg: {:<#18x}", csr!(self, MCAUSE), csr!(self, MEDELEG), csr!(self, MIDELEG)); 167 | println!(" mscratch: {:<#18x}", csr!(self, MSCRATCH)); 168 | println!(" Supervisor Level CSR register:"); 169 | println!(" sstatus: {:<#18x} stvec: {:<#18x} sepc: {:<#18x}", csr!(self, SSTATUS), csr!(self, STVEC), csr!(self, SEPC)); 170 | println!(" scause: {:<#18x} satp: {:<#18x}", csr!(self, SCAUSE), csr!(self, SATP)); 171 | println!("======================================="); 172 | } 173 | 174 | pub fn load_memory(&mut self, offset: usize, mem: &[u8]) { 175 | self.cpu.bus.mem.load(offset, mem); 176 | } 177 | 178 | pub fn load_device_tree(&mut self, bytes: &[u8]) -> Option<()> { 179 | self.cpu.bus.device_tree.load(VIRT_MROM_BASE as usize, bytes) 180 | } 181 | 182 | pub fn load_disk_file(&mut self, file: String) -> Result<(), std::io::Error> { 183 | let file = OpenOptions::new().read(true).write(true).open(&file)?; 184 | let mmap = unsafe { MmapMut::map_mut(&file) }?; 185 | self.cpu.bus.virtio.set_image(mmap); 186 | Ok(()) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /valheim-core/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use memmap2::MmapMut; 4 | 5 | /// `transmutable` data types 6 | pub trait CanIO: Copy + Sized {} 7 | impl CanIO for u8 {} 8 | impl CanIO for u16 {} 9 | impl CanIO for u32 {} 10 | impl CanIO for u64 {} 11 | impl CanIO for i8 {} 12 | impl CanIO for i16 {} 13 | impl CanIO for i32 {} 14 | impl CanIO for i64 {} 15 | 16 | #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Add, Sub, AddAssign, SubAssign)] 17 | pub struct VirtAddr(pub u64); 18 | 19 | #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] 20 | pub struct PhysAddr(pub *const u8); 21 | 22 | impl Debug for VirtAddr { 23 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 24 | write!(f, "VirtAddr({:#x})", self.0) 25 | } 26 | } 27 | 28 | impl Debug for PhysAddr { 29 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 30 | write!(f, "PhysAddr({:#x})", self.0 as usize) 31 | } 32 | } 33 | 34 | #[derive(Debug)] 35 | pub struct Memory { 36 | pub memory_base: VirtAddr, 37 | pub memory_size: usize, 38 | pub memory: MmapMut, 39 | } 40 | 41 | impl Memory { 42 | pub fn new(memory_base: u64, memory_size: usize) -> Result { 43 | let memory = MmapMut::map_anon(memory_size)?; 44 | Ok(Memory { 45 | memory_base: VirtAddr(memory_base), 46 | memory_size, 47 | memory, 48 | }) 49 | } 50 | 51 | pub fn to_phys(&self, virt: VirtAddr) -> Option { 52 | if cfg!(debug_assertions) && !self.check_virt_bounds(virt) { return None; } 53 | let offset = virt - self.memory_base; 54 | let ptr = unsafe { self.memory.as_ptr().offset(offset.0 as isize) }; 55 | Some(PhysAddr(ptr)) 56 | } 57 | 58 | pub fn to_virt(&self, phys: PhysAddr) -> Option { 59 | if cfg!(debug_assertions) && !self.check_phys_bounds(phys) { return None; } 60 | let offset = phys.0 as usize - self.memory.as_ptr() as usize; 61 | Some(self.memory_base + VirtAddr(offset as u64)) 62 | } 63 | 64 | fn check_virt_bounds(&self, virt: VirtAddr) -> bool { 65 | virt >= self.memory_base && virt < self.memory_base + VirtAddr(self.memory_size as u64) 66 | } 67 | 68 | fn check_phys_bounds(&self, phys: PhysAddr) -> bool { 69 | let ptr = self.memory.as_ptr() as usize; 70 | (phys.0 as usize) >= ptr && (phys.0 as usize) < ptr + self.memory_size 71 | } 72 | 73 | pub fn get_mut(&mut self, virt: VirtAddr) -> Option<&mut T> { 74 | let phys = self.to_phys(virt)?; 75 | unsafe { 76 | // CanIO trait guarantees that the transmute is safe 77 | let ptr = std::mem::transmute::<*const u8, *mut T>(phys.0); 78 | Some(&mut *ptr) 79 | } 80 | } 81 | 82 | pub fn get(&self, virt: VirtAddr) -> Option<&T> { 83 | let phys = self.to_phys(virt)?; 84 | unsafe { 85 | // CanIO trait guarantees that the transmute is safe 86 | let ptr = std::mem::transmute::<*const u8, *const T>(phys.0); 87 | Some(&*ptr) 88 | } 89 | } 90 | 91 | pub fn read(&self, addr: VirtAddr) -> Option { 92 | self.get(addr).map(|v| *v) 93 | } 94 | 95 | pub fn write(&mut self, addr: VirtAddr, value: T) -> Option<()> { 96 | self.get_mut(addr).map(|v| *v = value) 97 | } 98 | 99 | pub fn load(&mut self, offset: usize, mem: &[T]) -> Option<()> { 100 | let phys = self.to_phys(VirtAddr(offset as u64))?; 101 | unsafe { 102 | std::ptr::copy_nonoverlapping( 103 | mem.as_ptr() as *const u8, 104 | phys.0 as *mut u8, 105 | mem.len() * std::mem::size_of::(), 106 | ); 107 | } 108 | Some(()) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod test { 114 | use crate::memory::{Memory, VirtAddr}; 115 | 116 | #[test] 117 | fn memory_init() { 118 | let mem = Memory::new(0x1000, 0x1000).unwrap(); 119 | std::mem::drop(mem); 120 | } 121 | 122 | #[test] 123 | pub fn memory_read() { 124 | let mut mem = Memory::new(0x1000, 0x1000).unwrap(); 125 | let addr = mem.memory_base + VirtAddr(0x4); 126 | let value = 0xCAFEBABE_DEADBEEF as u64; 127 | mem.write(addr, value); 128 | assert_eq!(mem.read::(addr), Some(value)); 129 | 130 | let low_addr = mem.memory_base + VirtAddr(0x4); 131 | let low_value = 0xDEADBEEF as u32; 132 | assert_eq!(mem.read::(low_addr), Some(low_value)); 133 | 134 | let high_addr = mem.memory_base + VirtAddr(0x8); 135 | let high_value = 0xCAFEBABE as u32; 136 | assert_eq!(mem.read::(high_addr), Some(high_value)); 137 | 138 | let invalid_addr1 = mem.memory_base + VirtAddr(0xffff); 139 | let invalid_addr2 = VirtAddr(0); 140 | assert_eq!(mem.read::(invalid_addr1), None); 141 | assert_eq!(mem.read::(invalid_addr2), None); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /valheim-testing/disabled-tests.txt: -------------------------------------------------------------------------------- 1 | rv64uf-p-fcmp 2 | rv64uf-p-fcvt_w 3 | rv64uf-p-fmin 4 | rv64uf-p-move 5 | rv64uf-p-fclass 6 | rv64ud-p-fcmp 7 | rv64ud-p-fcvt_w 8 | rv64ud-p-fmin 9 | rv64ud-p-move 10 | rv64ud-p-fclass 11 | rv64ud-p-ldst 12 | -------------------------------------------------------------------------------- /valheim-testing/enabled-tests.txt: -------------------------------------------------------------------------------- 1 | rv64ui-p-add 2 | rv64ui-p-addi 3 | rv64ui-p-addiw 4 | rv64ui-p-addw 5 | rv64ui-p-and 6 | rv64ui-p-andi 7 | rv64ui-p-auipc 8 | rv64ui-p-beq 9 | rv64ui-p-bge 10 | rv64ui-p-bgeu 11 | rv64ui-p-blt 12 | rv64ui-p-bltu 13 | rv64ui-p-bne 14 | rv64ui-p-fence_i 15 | rv64ui-p-jal 16 | rv64ui-p-jalr 17 | rv64ui-p-lb 18 | rv64ui-p-lbu 19 | rv64ui-p-ld 20 | rv64ui-p-lh 21 | rv64ui-p-lhu 22 | rv64ui-p-lui 23 | rv64ui-p-lw 24 | rv64ui-p-lwu 25 | rv64ui-p-or 26 | rv64ui-p-ori 27 | rv64ui-p-sb 28 | rv64ui-p-sd 29 | rv64ui-p-sh 30 | rv64ui-p-simple 31 | rv64ui-p-sll 32 | rv64ui-p-slli 33 | rv64ui-p-slliw 34 | rv64ui-p-sllw 35 | rv64ui-p-slt 36 | rv64ui-p-slti 37 | rv64ui-p-sltiu 38 | rv64ui-p-sltu 39 | rv64ui-p-sra 40 | rv64ui-p-srai 41 | rv64ui-p-sraiw 42 | rv64ui-p-sraw 43 | rv64ui-p-srl 44 | rv64ui-p-srli 45 | rv64ui-p-srliw 46 | rv64ui-p-srlw 47 | rv64ui-p-sub 48 | rv64ui-p-subw 49 | rv64ui-p-sw 50 | rv64ui-p-xor 51 | rv64ui-p-xori 52 | rv64um-p-div 53 | rv64um-p-divu 54 | rv64um-p-divuw 55 | rv64um-p-divw 56 | rv64um-p-mul 57 | rv64um-p-mulh 58 | rv64um-p-mulhsu 59 | rv64um-p-mulhu 60 | rv64um-p-mulw 61 | rv64um-p-rem 62 | rv64um-p-remu 63 | rv64um-p-remuw 64 | rv64um-p-remw 65 | rv64ua-p-amoadd_d 66 | rv64ua-p-amoadd_w 67 | rv64ua-p-amoand_d 68 | rv64ua-p-amoand_w 69 | rv64ua-p-amomax_d 70 | rv64ua-p-amomax_w 71 | rv64ua-p-amomaxu_d 72 | rv64ua-p-amomaxu_w 73 | rv64ua-p-amomin_d 74 | rv64ua-p-amomin_w 75 | rv64ua-p-amominu_d 76 | rv64ua-p-amominu_w 77 | rv64ua-p-amoor_d 78 | rv64ua-p-amoor_w 79 | rv64ua-p-amoswap_d 80 | rv64ua-p-amoswap_w 81 | rv64ua-p-amoxor_d 82 | rv64ua-p-amoxor_w 83 | rv64ua-p-lrsc 84 | rv64uf-p-fadd 85 | rv64uf-p-fcvt 86 | rv64uf-p-fdiv 87 | rv64uf-p-fmadd 88 | rv64uf-p-ldst 89 | rv64uf-p-recoding 90 | rv64ud-p-fadd 91 | rv64ud-p-fcvt 92 | rv64ud-p-fdiv 93 | rv64ud-p-fmadd 94 | rv64ud-p-recoding 95 | rv64ud-p-structural 96 | rv64uc-p-rvc 97 | -------------------------------------------------------------------------------- /valheim-testing/sync-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -x 4 | set -o pipefail 5 | 6 | function find_glob() { 7 | local pattern="$1" 8 | local dir="$2" 9 | local disabled="$3" 10 | find "$dir" -type f -name "$pattern" -exec basename {} \; \ 11 | | grep -v "\\.dump$" \ 12 | | grep -v "\\.bin$" \ 13 | | grep -v -x -F -f "$disabled" \ 14 | | sort \ 15 | | uniq 16 | } 17 | 18 | SELF="$(dirname $0)" 19 | 20 | ENABLED_TESTS_FILE="$SELF/enabled-tests.txt" 21 | DISABLED_TESTS_FILE="$SELF/disabled-tests.txt" 22 | ISA_TEST_DIR="$SELF/target/share/riscv-tests/isa" 23 | 24 | > "$ENABLED_TESTS_FILE" 25 | find_glob 'rv64ui-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 26 | find_glob 'rv64um-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 27 | find_glob 'rv64ua-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 28 | find_glob 'rv64uf-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 29 | find_glob 'rv64ud-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 30 | find_glob 'rv64uc-p-*' "$ISA_TEST_DIR" "$DISABLED_TESTS_FILE" >> "$ENABLED_TESTS_FILE" 31 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | xshell = "0.2.1" 10 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::path::{Path, PathBuf}; 4 | use std::process::exit; 5 | 6 | use xshell::{cmd, Error, Shell}; 7 | 8 | fn main() -> Result<(), Error> { 9 | let argv = std::env::args().skip(1).collect::>(); 10 | match argv.first().map(|s| s.as_str()) { 11 | Some("test") => run_riscv_tests(argv.into_iter().skip(1).collect()), 12 | _ => { 13 | eprintln!("Usage: xtask "); 14 | eprintln!("Available s:"); 15 | eprintln!(" test: run riscv-tests"); 16 | exit(1); 17 | } 18 | } 19 | } 20 | 21 | fn run_riscv_tests(_argv: Vec) -> Result<(), Error> { 22 | let sh = Shell::new()?; 23 | let testing_dir = project_root().join("valheim-testing"); 24 | let test_source_dir = testing_dir.join("riscv-tests"); 25 | let test_target_dir = testing_dir.join("target"); 26 | let nproc = cmd!(sh, "nproc").read().unwrap_or("1".to_string()); 27 | 28 | println!(":: Compiling debug variant of Valheim emulator"); 29 | sh.change_dir(project_root()); 30 | cmd!(sh, "cargo make-debug").run()?; 31 | 32 | println!(":: Compiling riscv-tests with {} threads...", nproc); 33 | sh.change_dir(test_source_dir); 34 | cmd!(sh, "./configure --prefix={test_target_dir}").run()?; 35 | cmd!(sh, "make -j{nproc}").run()?; 36 | cmd!(sh, "make install").run()?; 37 | 38 | println!(":: Running riscv-tests with Valheim emulator..."); 39 | let enabled_tests = include_str!("../../valheim-testing/enabled-tests.txt") 40 | .lines() 41 | .filter(|l| !l.is_empty()) 42 | .filter(|l| !l.starts_with("#")); 43 | for test in enabled_tests { 44 | let elf = isa(test); 45 | run_one(test, elf)?; 46 | } 47 | Ok(()) 48 | } 49 | 50 | fn run_one(test_name: &str, elf: PathBuf) -> Result<(), Error> { 51 | let sh = Shell::new()?; 52 | let emulator = binary_dir("debug").join("valheim-cli"); 53 | let bin = elf.parent().unwrap().join(format!("{}.bin", elf.file_name().unwrap().to_str().unwrap())); 54 | cmd!(sh, "riscv64-unknown-elf-objcopy -O binary {elf} {bin}").quiet().run()?; 55 | cmd!(sh, "{emulator} --test --test-name {test_name} --kernel {bin}").quiet().run()?; 56 | Ok(()) 57 | } 58 | 59 | fn riscv_tests_install_dir() -> PathBuf { 60 | project_root().join("valheim-testing").join("target") 61 | .join("share").join("riscv-tests") 62 | } 63 | 64 | fn benchmark(name: &str) -> PathBuf { 65 | riscv_tests_install_dir().join("benchmarks").join(format!("{}.riscv", name)) 66 | } 67 | 68 | fn isa(name: &str) -> PathBuf { 69 | riscv_tests_install_dir().join("isa").join(name) 70 | } 71 | 72 | // copied from https://github.com/rustsbi/rustsbi-qemu/blob/main/xtask/src/main.rs 73 | fn project_root() -> PathBuf { 74 | Path::new(&env!("CARGO_MANIFEST_DIR")) 75 | .ancestors() 76 | .nth(1) 77 | .unwrap() 78 | .to_path_buf() 79 | } 80 | 81 | fn binary_dir(mode: &str) -> PathBuf { 82 | project_root().join("target").join(mode) 83 | } 84 | --------------------------------------------------------------------------------