diff --git a/extras/core_graphics/diagram.drawio b/extras/core_graphics/diagram.drawio index a83e6910..204c9c66 100644 --- a/extras/core_graphics/diagram.drawio +++ b/extras/core_graphics/diagram.drawio @@ -15,7 +15,7 @@ - + @@ -968,6 +968,16 @@ + + + + + + + + + + diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 636f1f72..074a5f26 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -25,6 +25,9 @@ set(gui_SOURCES windows/messages/messagesview.cpp dialogs/new/newdialog.cpp ui/hexlineedit.cpp + ui/pow2spinbox.cpp + windows/tlb/tlbview.cpp + windows/tlb/tlbdock.cpp windows/editor/highlighterasm.cpp windows/editor/highlighterc.cpp windows/editor/linenumberarea.cpp @@ -52,7 +55,7 @@ set(gui_SOURCES windows/predictor/predictor_btb_dock.cpp windows/predictor/predictor_bht_dock.cpp windows/predictor/predictor_info_dock.cpp - ) +) set(gui_HEADERS dialogs/about/aboutdialog.h windows/cache/cachedock.h @@ -71,6 +74,9 @@ set(gui_HEADERS windows/messages/messagesview.h dialogs/new/newdialog.h ui/hexlineedit.h + ui/pow2spinbox.h + windows/tlb/tlbview.h + windows/tlb/tlbdock.h windows/editor/highlighterasm.h windows/editor/highlighterc.h windows/editor/linenumberarea.h @@ -98,19 +104,19 @@ set(gui_HEADERS windows/predictor/predictor_btb_dock.h windows/predictor/predictor_bht_dock.h windows/predictor/predictor_info_dock.h - ) +) set(gui_UI dialogs/gotosymbol/gotosymboldialog.ui dialogs/new/NewDialog.ui windows/peripherals/peripheralsview.ui mainwindow/MainWindow.ui dialogs/new/NewDialogCache.ui - ) +) set(gui_RESOURCES resources/icons/icons.qrc resources/samples/samples.qrc windows/coreview/schemas/schemas.qrc - ) +) if ("${WASM}") @@ -160,7 +166,7 @@ set_target_properties(gui PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_ICONFILE ${ICON_NAME} - ) +) # END MACOS # ============================================================================= @@ -174,5 +180,5 @@ set_target_properties(gui PROPERTIES install(TARGETS gui RUNTIME DESTINATION bin BUNDLE DESTINATION ${EXECUTABLE_OUTPUT_PATH} - ) +) diff --git a/src/gui/dialogs/new/NewDialog.ui b/src/gui/dialogs/new/NewDialog.ui index 76b0afad..dd1073bb 100644 --- a/src/gui/dialogs/new/NewDialog.ui +++ b/src/gui/dialogs/new/NewDialog.ui @@ -66,6 +66,9 @@ + + 3 + Presets and ELF File @@ -263,6 +266,9 @@ + + true + Branch Predictor @@ -656,6 +662,134 @@ + + + Virtual Memory + + + + true + + + + 10 + 10 + 471 + 181 + + + + Virtual Memory + + + true + + + true + + + + true + + + + 20 + 30 + 431 + 131 + + + + Translation Lookaside Buffer (TLB) + + + false + + + + + + Number of sets: + + + + + + + 1 + + + 1024 + + + 1 + + + QAbstractSpinBox::DefaultStepType + + + 1 + + + 10 + + + + + + + Degree of associativity: + + + + + + + 1 + + + 1 + + + 1 + + + + + + + Replacement policy: + + + + + + + + Random + + + + + Least Recently Used (LRU) + + + + + Least Frequently Used (LFU) + + + + + Pseudo Least Recently Used (PLRU) + + + + + + + + Memory Timings @@ -955,6 +1089,13 @@ + + + Pow2SpinBox + QSpinBox +
ui/pow2spinbox.h
+
+
diff --git a/src/gui/dialogs/new/newdialog.cpp b/src/gui/dialogs/new/newdialog.cpp index 5fd5bae2..4d779582 100644 --- a/src/gui/dialogs/new/newdialog.cpp +++ b/src/gui/dialogs/new/newdialog.cpp @@ -33,79 +33,40 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { for (int i = 0; i < ui->config_pages->count(); ++i) { QString page_id = ui->config_pages->widget(i)->objectName(); QString page_name = ui->config_pages->widget(i)->accessibleName(); - config_pages_items.append(new QTreeWidgetItem(static_cast(nullptr), - QStringList{page_name, page_id})); + config_pages_items.append(new QTreeWidgetItem( + static_cast(nullptr), QStringList { page_name, page_id })); } ui->page_select_tree->insertTopLevelItems(0, config_pages_items); - connect( - ui->page_select_tree, &QTreeWidget::currentItemChanged, - this, &NewDialog::switch2page); + connect(ui->page_select_tree, &QTreeWidget::currentItemChanged, this, &NewDialog::switch2page); + connect(ui->pushButton_example, &QAbstractButton::clicked, this, &NewDialog::create_example); + connect(ui->pushButton_start_empty, &QAbstractButton::clicked, this, &NewDialog::create_empty); + connect(ui->pushButton_load, &QAbstractButton::clicked, this, &NewDialog::create); + connect(ui->pushButton_cancel, &QAbstractButton::clicked, this, &NewDialog::cancel); + connect(ui->pushButton_browse, &QAbstractButton::clicked, this, &NewDialog::browse_elf); + connect(ui->elf_file, &QLineEdit::textChanged, this, &NewDialog::elf_change); + connect(ui->preset_no_pipeline, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_no_pipeline_cache, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_pipelined_bare, &QAbstractButton::toggled, this, &NewDialog::set_preset); + connect(ui->preset_pipelined, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect( - ui->pushButton_example, &QAbstractButton::clicked, this, - &NewDialog::create_example); - connect( - ui->pushButton_start_empty, &QAbstractButton::clicked, this, - &NewDialog::create_empty); - connect( - ui->pushButton_load, &QAbstractButton::clicked, this, - &NewDialog::create); - connect( - ui->pushButton_cancel, &QAbstractButton::clicked, this, - &NewDialog::cancel); - connect( - ui->pushButton_browse, &QAbstractButton::clicked, this, - &NewDialog::browse_elf); - connect( - ui->elf_file, &QLineEdit::textChanged, this, &NewDialog::elf_change); - connect( - ui->preset_no_pipeline, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_no_pipeline_cache, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_pipelined_bare, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->preset_pipelined, &QAbstractButton::toggled, this, - &NewDialog::set_preset); - connect( - ui->reset_at_compile, &QAbstractButton::clicked, this, - &NewDialog::reset_at_compile_change); + ui->reset_at_compile, &QAbstractButton::clicked, this, &NewDialog::reset_at_compile_change); + connect(ui->xlen_64bit, &QAbstractButton::clicked, this, &NewDialog::xlen_64bit_change); + connect(ui->isa_atomic, &QAbstractButton::clicked, this, &NewDialog::isa_atomic_change); + connect(ui->isa_multiply, &QAbstractButton::clicked, this, &NewDialog::isa_multiply_change); + connect(ui->pipelined, &QAbstractButton::clicked, this, &NewDialog::pipelined_change); + connect(ui->delay_slot, &QAbstractButton::clicked, this, &NewDialog::delay_slot_change); + connect(ui->hazard_unit, &QGroupBox::clicked, this, &NewDialog::hazard_unit_change); + connect(ui->hazard_stall, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( - ui->xlen_64bit, &QAbstractButton::clicked, this, - &NewDialog::xlen_64bit_change); - connect( - ui->isa_atomic, &QAbstractButton::clicked, this, - &NewDialog::isa_atomic_change); - connect( - ui->isa_multiply, &QAbstractButton::clicked, this, - &NewDialog::isa_multiply_change); - connect( - ui->pipelined, &QAbstractButton::clicked, this, - &NewDialog::pipelined_change); - connect( - ui->delay_slot, &QAbstractButton::clicked, this, - &NewDialog::delay_slot_change); - connect( - ui->hazard_unit, &QGroupBox::clicked, this, - &NewDialog::hazard_unit_change); - connect( - ui->hazard_stall, &QAbstractButton::clicked, this, - &NewDialog::hazard_unit_change); - connect( - ui->hazard_stall_forward, &QAbstractButton::clicked, this, - &NewDialog::hazard_unit_change); + ui->hazard_stall_forward, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( - ui->mem_protec_exec, &QAbstractButton::clicked, this, - &NewDialog::mem_protec_exec_change); + ui->mem_protec_exec, &QAbstractButton::clicked, this, &NewDialog::mem_protec_exec_change); connect( - ui->mem_protec_write, &QAbstractButton::clicked, this, - &NewDialog::mem_protec_write_change); + ui->mem_protec_write, &QAbstractButton::clicked, this, &NewDialog::mem_protec_write_change); connect( ui->mem_time_read, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_read_change); @@ -113,8 +74,7 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { ui->mem_time_write, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_write_change); connect( - ui->mem_enable_burst, &QAbstractButton::clicked, this, - &NewDialog::mem_enable_burst_change); + ui->mem_enable_burst, &QAbstractButton::clicked, this, &NewDialog::mem_enable_burst_change); connect( ui->mem_time_burst, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_burst_change); @@ -122,9 +82,7 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { ui->mem_time_level2, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_level2_change); - connect( - ui->osemu_enable, &QAbstractButton::clicked, this, - &NewDialog::osemu_enable_change); + connect(ui->osemu_enable, &QAbstractButton::clicked, this, &NewDialog::osemu_enable_change); connect( ui->osemu_known_syscall_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_known_syscall_stop_change); @@ -158,9 +116,24 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { connect( ui->slider_bp_bht_bhr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_bhr_bits_change); - connect(ui->slider_bp_bht_addr_bits, &QAbstractSlider::valueChanged, this, + connect( + ui->slider_bp_bht_addr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_addr_bits_change); + // Virtual Memory + connect( + ui->group_vm, QOverload::of(&QGroupBox::toggled), this, + &NewDialog::vm_enabled_change); + connect( + ui->tlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::tlb_num_sets_changed); + connect( + ui->tlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, + &NewDialog::tlb_assoc_changed); + connect( + ui->tlb_replacement_policy, QOverload::of(&QComboBox::activated), this, + &NewDialog::tlb_policy_changed); + cache_handler_d = new NewDialogCacheHandler(this, ui_cache_d.data()); cache_handler_p = new NewDialogCacheHandler(this, ui_cache_p.data()); cache_handler_l2 = new NewDialogCacheHandler(this, ui_cache_l2.data()); @@ -177,17 +150,18 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { void NewDialog::switch2page(QTreeWidgetItem *current, QTreeWidgetItem *previous) { (void)previous; - QWidget *page = ui->config_pages->findChild(current->text(1), - Qt::FindDirectChildrenOnly); + QWidget *page + = ui->config_pages->findChild(current->text(1), Qt::FindDirectChildrenOnly); if (page != nullptr) { ui->config_pages->setCurrentWidget(page); ui->config_page_title->setText(current->text(0)); } } -void NewDialog::switch2custom() { +void NewDialog::switch_to_custom() { if (!ui->preset_custom->isChecked()) { - ui->preset_custom->setChecked(true); + ui->preset_custom->setChecked(true); // Select "Custom" preset and refresh GUI (no-op if + // already selected). config_gui(); } } @@ -196,9 +170,7 @@ void NewDialog::closeEvent(QCloseEvent *) { load_settings(); // Reset from settings // Close the main window if not already configured auto *prnt = (MainWindow *)parent(); - if (!prnt->configured()) { - prnt->close(); - } + if (!prnt->configured()) { prnt->close(); } } void NewDialog::cancel() { @@ -281,92 +253,91 @@ void NewDialog::xlen_64bit_change(bool val) { config->set_simulated_xlen(machine::Xlen::_64); else config->set_simulated_xlen(machine::Xlen::_32); - switch2custom(); + switch_to_custom(); } void NewDialog::isa_atomic_change(bool val) { - auto isa_mask = machine::ConfigIsaWord::byChar('A'); + auto isa_mask = machine::ConfigIsaWord::byChar('A'); if (val) config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); - switch2custom(); + switch_to_custom(); } void NewDialog::isa_multiply_change(bool val) { - auto isa_mask = machine::ConfigIsaWord::byChar('M'); + auto isa_mask = machine::ConfigIsaWord::byChar('M'); if (val) config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); - switch2custom(); + switch_to_custom(); } void NewDialog::pipelined_change(bool val) { config->set_pipelined(val); ui->hazard_unit->setEnabled(config->pipelined()); - switch2custom(); + switch_to_custom(); } void NewDialog::delay_slot_change(bool val) { config->set_delay_slot(val); - switch2custom(); + switch_to_custom(); } void NewDialog::hazard_unit_change() { if (ui->hazard_unit->isChecked()) { config->set_hazard_unit( - ui->hazard_stall->isChecked() - ? machine::MachineConfig::HU_STALL - : machine::MachineConfig::HU_STALL_FORWARD); + ui->hazard_stall->isChecked() ? machine::MachineConfig::HU_STALL + : machine::MachineConfig::HU_STALL_FORWARD); } else { config->set_hazard_unit(machine::MachineConfig::HU_NONE); } - switch2custom(); + switch_to_custom(); } void NewDialog::mem_protec_exec_change(bool v) { config->set_memory_execute_protection(v); - switch2custom(); + switch_to_custom(); } void NewDialog::mem_protec_write_change(bool v) { config->set_memory_write_protection(v); - switch2custom(); + switch_to_custom(); } void NewDialog::mem_time_read_change(int v) { if (config->memory_access_time_read() != (unsigned)v) { config->set_memory_access_time_read(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_write_change(int v) { if (config->memory_access_time_write() != (unsigned)v) { config->set_memory_access_time_write(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_enable_burst_change(bool v) { if (config->memory_access_enable_burst() != v) { config->set_memory_access_enable_burst(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_burst_change(int v) { if (config->memory_access_time_burst() != (unsigned)v) { config->set_memory_access_time_burst(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::mem_time_level2_change(int v) { if (config->memory_access_time_level2() != (unsigned)v) { config->set_memory_access_time_level2(v); - switch2custom(); + switch_to_custom(); } } @@ -490,20 +461,18 @@ void NewDialog::bp_type_change() { } } break; - default: - break; + default: break; } bp_toggle_widgets(); - if (need_switch2custom) - switch2custom(); + if (need_switch2custom) switch_to_custom(); } void NewDialog::bp_enabled_change(bool v) { if (config->get_bp_enabled() != v) { config->set_bp_enabled(v); bp_toggle_widgets(); - switch2custom(); + switch_to_custom(); } } @@ -511,14 +480,14 @@ void NewDialog::bp_init_state_change(void) { auto v = ui->select_bp_init_state->currentData().value(); if (v != config->get_bp_init_state()) { config->set_bp_init_state(v); - switch2custom(); + switch_to_custom(); } } void NewDialog::bp_btb_addr_bits_change(int v) { if (config->get_bp_btb_bits() != v) { config->set_bp_btb_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } ui->text_bp_btb_addr_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_bits_number->setText(QString::number(config->get_bp_btb_bits())); @@ -535,7 +504,7 @@ void NewDialog::bp_bht_bits_texts_update(void) { void NewDialog::bp_bht_bhr_bits_change(int v) { if (config->get_bp_bhr_bits() != v) { config->set_bp_bhr_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } bp_bht_bits_texts_update(); } @@ -543,11 +512,38 @@ void NewDialog::bp_bht_bhr_bits_change(int v) { void NewDialog::bp_bht_addr_bits_change(int v) { if (config->get_bp_bht_addr_bits() != v) { config->set_bp_bht_addr_bits((uint8_t)v); - switch2custom(); + switch_to_custom(); } bp_bht_bits_texts_update(); } +void NewDialog::vm_enabled_change(bool v) { + if (config->get_vm_enabled() != v) { + config->set_vm_enabled(v); + switch_to_custom(); + } +} + +void NewDialog::tlb_num_sets_changed(int v) { + config->access_tlb_program()->set_tlb_num_sets(static_cast(v)); + config->access_tlb_data()->set_tlb_num_sets(static_cast(v)); + switch_to_custom(); +} + +void NewDialog::tlb_assoc_changed(int v) { + config->access_tlb_program()->set_tlb_associativity(static_cast(v)); + config->access_tlb_data()->set_tlb_associativity(static_cast(v)); + switch_to_custom(); +} + +void NewDialog::tlb_policy_changed(int idx) { + config->access_tlb_program()->set_tlb_replacement_policy( + static_cast(idx)); + config->access_tlb_data()->set_tlb_replacement_policy( + static_cast(idx)); + switch_to_custom(); +} + void NewDialog::config_gui() { // Basic ui->elf_file->setText(config->elf()); @@ -607,6 +603,9 @@ void NewDialog::config_gui() { ui->text_bp_bht_entries_number->setText(QString::number(qPow(2, config->get_bp_bht_bits()))); bp_type_change(); + // Virtual + ui->group_vm->setChecked(config->get_vm_enabled()); + // Memory ui->mem_protec_exec->setChecked(config->memory_execute_protection()); ui->mem_protec_write->setChecked(config->memory_write_protection()); @@ -660,15 +659,9 @@ void NewDialog::load_settings() { auto p = (enum machine::ConfigPresets)(preset - 1); config->preset(p); switch (p) { - case machine::CP_SINGLE: - ui->preset_no_pipeline->setChecked(true); - break; - case machine::CP_SINGLE_CACHE: - ui->preset_no_pipeline_cache->setChecked(true); - break; - case machine::CP_PIPE_NO_HAZARD: - ui->preset_pipelined_bare->setChecked(true); - break; + case machine::CP_SINGLE: ui->preset_no_pipeline->setChecked(true); break; + case machine::CP_SINGLE_CACHE: ui->preset_no_pipeline_cache->setChecked(true); break; + case machine::CP_PIPE_NO_HAZARD: ui->preset_pipelined_bare->setChecked(true); break; case machine::CP_PIPE: ui->preset_pipelined->setChecked(true); break; } } else { @@ -689,14 +682,11 @@ void NewDialog::store_settings() { } } -NewDialogCacheHandler::NewDialogCacheHandler(NewDialog *nd, - Ui::NewDialogCache *cui) : Super(nd) { +NewDialogCacheHandler::NewDialogCacheHandler(NewDialog *nd, Ui::NewDialogCache *cui) : Super(nd) { this->nd = nd; this->ui = cui; this->config = nullptr; - connect( - ui->enabled, &QGroupBox::clicked, this, - &NewDialogCacheHandler::enabled); + connect(ui->enabled, &QGroupBox::clicked, this, &NewDialogCacheHandler::enabled); connect( ui->number_of_sets, &QAbstractSpinBox::editingFinished, this, &NewDialogCacheHandler::numsets); @@ -729,31 +719,30 @@ void NewDialogCacheHandler::config_gui() { void NewDialogCacheHandler::enabled(bool val) { config->set_enabled(val); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::numsets() { config->set_set_count(ui->number_of_sets->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::blocksize() { config->set_block_size(ui->block_size->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::degreeassociativity() { config->set_associativity(ui->degree_of_associativity->value()); - nd->switch2custom(); + nd->switch_to_custom(); } void NewDialogCacheHandler::replacement(int val) { - config->set_replacement_policy( - (enum machine::CacheConfig::ReplacementPolicy)val); - nd->switch2custom(); + config->set_replacement_policy((enum machine::CacheConfig::ReplacementPolicy)val); + nd->switch_to_custom(); } void NewDialogCacheHandler::writeback(int val) { config->set_write_policy((enum machine::CacheConfig::WritePolicy)val); - nd->switch2custom(); + nd->switch_to_custom(); } diff --git a/src/gui/dialogs/new/newdialog.h b/src/gui/dialogs/new/newdialog.h index c8c7175e..1abebdd4 100644 --- a/src/gui/dialogs/new/newdialog.h +++ b/src/gui/dialogs/new/newdialog.h @@ -19,7 +19,7 @@ class NewDialog : public QDialog { public: NewDialog(QWidget *parent, QSettings *settings); - void switch2custom(); + void switch_to_custom(); protected: void closeEvent(QCloseEvent *) override; @@ -64,6 +64,13 @@ private slots: void bp_bht_bhr_bits_change(int); void bp_bht_addr_bits_change(int); + + // Virtual Memory + void vm_enabled_change(bool); + void tlb_num_sets_changed(int); + void tlb_assoc_changed(int); + void tlb_policy_changed(int); + private: Box ui {}; Box ui_cache_p {}, ui_cache_d {}, ui_cache_l2 {}; diff --git a/src/gui/graphicsview.cpp b/src/gui/graphicsview.cpp index 4df78bd0..5ab7d4ca 100644 --- a/src/gui/graphicsview.cpp +++ b/src/gui/graphicsview.cpp @@ -30,14 +30,9 @@ void GraphicsView::update_scale() { prev_height = width(); prev_width = height(); - qreal scale = 1; - if (height() > h && width() > w) { - if (height() > width()) { - scale = (qreal)width() / w; - } else { - scale = (qreal)height() / h; - } - } + qreal scale_x = (qreal)width() / w; + qreal scale_y = (qreal)height() / h; + qreal scale = qMin(scale_x, scale_y); QTransform t; t.scale(scale, scale); setTransform(t, false); diff --git a/src/gui/mainwindow/MainWindow.ui b/src/gui/mainwindow/MainWindow.ui index 11de9c22..0edbd8de 100644 --- a/src/gui/mainwindow/MainWindow.ui +++ b/src/gui/mainwindow/MainWindow.ui @@ -53,7 +53,7 @@ 0 0 900 - 20 + 19 @@ -87,6 +87,8 @@ + + @@ -647,6 +649,22 @@ Branch Predictor (Info) + + + &Data TLB + + + Data TLB + + + + + Instruction TLB + + + Instruction TLB + + diff --git a/src/gui/mainwindow/mainwindow.cpp b/src/gui/mainwindow/mainwindow.cpp index 7e36afdf..fa56b237 100644 --- a/src/gui/mainwindow/mainwindow.cpp +++ b/src/gui/mainwindow/mainwindow.cpp @@ -115,6 +115,10 @@ MainWindow::MainWindow(QSettings *settings, QWidget *parent) cache_data->hide(); cache_level2.reset(new CacheDock(this, "L2")); cache_level2->hide(); + tlb_program.reset(new TLBDock(this, "Instruction")); + tlb_program->hide(); + tlb_data.reset(new TLBDock(this, "Data")); + tlb_data->hide(); bp_btb.reset(new DockPredictorBTB(this)); bp_btb->hide(); bp_bht.reset(new DockPredictorBHT(this)); @@ -161,7 +165,8 @@ MainWindow::MainWindow(QSettings *settings, QWidget *parent) connect(ui->actionProgram_Cache, &QAction::triggered, this, &MainWindow::show_cache_program); connect(ui->actionData_Cache, &QAction::triggered, this, &MainWindow::show_cache_data); connect(ui->actionL2_Cache, &QAction::triggered, this, &MainWindow::show_cache_level2); - + connect(ui->actionInstruction_TLB, &QAction::triggered, this, &MainWindow::show_tlb_program); + connect(ui->actionData_TLB, &QAction::triggered, this, &MainWindow::show_tlb_data); // Branch predictor connect( ui->actionBranch_Predictor_History_table, &QAction::triggered, this, @@ -336,6 +341,8 @@ void MainWindow::create_core( cache_data->setup(machine->cache_data()); bool cache_after_cache = config.cache_data().enabled() || config.cache_program().enabled(); cache_level2->setup(machine->cache_level2(), cache_after_cache); + tlb_program->setup(machine->get_tlb_program_rw()); + tlb_data->setup(machine->get_tlb_data_rw()); // Branch predictor bp_btb->setup(machine->core()->get_predictor(), machine->core()); @@ -447,6 +454,8 @@ SHOW_HANDLER(memory, Qt::RightDockWidgetArea, true) SHOW_HANDLER(cache_program, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_level2, Qt::RightDockWidgetArea, false) +SHOW_HANDLER(tlb_program, Qt::RightDockWidgetArea, false) +SHOW_HANDLER(tlb_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_btb, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_bht, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_info, Qt::RightDockWidgetArea, false) @@ -783,6 +792,7 @@ void MainWindow::compile_source() { } machine->cache_sync(); + machine->tlb_sync(); auto editor = editor_tabs->get_current_editor(); auto filename = editor->filename().isEmpty() ? "Unknown" : editor->filename(); diff --git a/src/gui/mainwindow/mainwindow.h b/src/gui/mainwindow/mainwindow.h index e206a3e0..cecee5a5 100644 --- a/src/gui/mainwindow/mainwindow.h +++ b/src/gui/mainwindow/mainwindow.h @@ -28,6 +28,7 @@ #include "windows/program/programdock.h" #include "windows/registers/registersdock.h" #include "windows/terminal/terminaldock.h" +#include "windows/tlb/tlbdock.h" #include #include @@ -78,6 +79,8 @@ public slots: void reset_state_cache_program(); void reset_state_cache_data(); void reset_state_cache_level2(); + void reset_state_tlb_program(); + void reset_state_tlb_data(); void reset_state_peripherals(); void reset_state_terminal(); void reset_state_lcd_display(); @@ -89,6 +92,8 @@ public slots: void show_cache_data(); void show_cache_program(); void show_cache_level2(); + void show_tlb_program(); + void show_tlb_data(); void show_peripherals(); void show_terminal(); void show_lcd_display(); @@ -143,6 +148,7 @@ public slots: Box program {}; Box memory {}; Box cache_program {}, cache_data {}, cache_level2 {}; + Box tlb_program {}, tlb_data {}; // Branch predictor Box bp_btb {}; diff --git a/src/gui/ui/pow2spinbox.cpp b/src/gui/ui/pow2spinbox.cpp new file mode 100644 index 00000000..13cc6549 --- /dev/null +++ b/src/gui/ui/pow2spinbox.cpp @@ -0,0 +1,79 @@ +#include "pow2spinbox.h" + +Pow2SpinBox::Pow2SpinBox(QWidget *parent) : QSpinBox(parent) { + setRange(1, 1024); + setValue(1); +} + +QValidator::State Pow2SpinBox::validate(QString &input, int &pos) const { + Q_UNUSED(pos); + + if (input.isEmpty()) return QValidator::Intermediate; + + bool ok = false; + qint64 v = input.toLongLong(&ok); + if (!ok || v <= 0) return QValidator::Invalid; + + if ((v & (v - 1)) == 0) return QValidator::Acceptable; + + return QValidator::Intermediate; +} + +int Pow2SpinBox::valueFromText(const QString &text) const { + return text.toInt(); +} + +QString Pow2SpinBox::textFromValue(int value) const { + return QString::number(value); +} + +void Pow2SpinBox::stepBy(int steps) { + int v = value(); + if (v < 1) v = 1; + + auto isPow2 = [](int x) { return x > 0 && (x & (x - 1)) == 0; }; + + auto nextPow2 = [](int x) -> int { + if (x <= 1) return 1; + int p = 1; + while (p < x && (p << 1) > 0) + p <<= 1; + return p; + }; + + auto prevPow2 = [](int x) -> int { + if (x <= 1) return 1; + int p = 1; + while ((p << 1) <= x) + p <<= 1; + if (p > x) p >>= 1; + return p; + }; + + if (steps > 0) { + if (!isPow2(v)) { + v = nextPow2(v); + } else { + for (int i = 0; i < steps; ++i) { + if (v > (maximum() >> 1)) { + v = maximum(); + break; + } + v <<= 1; + } + } + } else { + if (!isPow2(v)) { + v = prevPow2(v); + } else { + for (int i = 0; i < -steps; ++i) { + if (v <= 1) { + v = 1; + break; + } + v >>= 1; + } + } + } + setValue(qBound(minimum(), v, maximum())); +} diff --git a/src/gui/ui/pow2spinbox.h b/src/gui/ui/pow2spinbox.h new file mode 100644 index 00000000..1c87c12d --- /dev/null +++ b/src/gui/ui/pow2spinbox.h @@ -0,0 +1,19 @@ +#ifndef POW2SPINBOX_H +#define POW2SPINBOX_H + +#include +#include + +class Pow2SpinBox : public QSpinBox { + Q_OBJECT +public: + explicit Pow2SpinBox(QWidget *parent = nullptr); + +protected: + QValidator::State validate(QString &input, int &pos) const override; + int valueFromText(const QString &text) const override; + QString textFromValue(int value) const override; + void stepBy(int steps) override; +}; + +#endif // POW2SPINBOX_H diff --git a/src/gui/windows/coreview/data.h b/src/gui/windows/coreview/data.h index 8edbd786..35fb6bc2 100644 --- a/src/gui/windows/coreview/data.h +++ b/src/gui/windows/coreview/data.h @@ -52,6 +52,13 @@ static const std::unordered_map STALL_TEXT_TABLE = { { 2, QStringLiteral("FORWARD") }, }; +static const std::unordered_map PRIVILEGE_TEXT_TABLE = { + { static_cast(machine::CSR::PrivilegeLevel::UNPRIVILEGED), QStringLiteral("UNPRIV") }, + { static_cast(machine::CSR::PrivilegeLevel::SUPERVISOR), QStringLiteral("SUPERV") }, + { static_cast(machine::CSR::PrivilegeLevel::HYPERVISOR), QStringLiteral("HYPERV") }, + { static_cast(machine::CSR::PrivilegeLevel::MACHINE), QStringLiteral("MACHINE") }, +}; + /** * Link targets available for use in the SVG. * @@ -175,6 +182,8 @@ const struct { MULTITEXT_LENS(pipeline.memory.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("hazard"), MULTITEXT_LENS(pipeline.execute.internal.stall_status, STALL_TEXT_TABLE) }, + { QStringLiteral("Privilege"), + MULTITEXT_LENS(current_privilege_u, PRIVILEGE_TEXT_TABLE) }, }; const unordered_map> INSTRUCTION { diff --git a/src/gui/windows/coreview/schemas/forwarding.svg b/src/gui/windows/coreview/schemas/forwarding.svg index 1fdabac9..e06505fd 100644 --- a/src/gui/windows/coreview/schemas/forwarding.svg +++ b/src/gui/windows/coreview/schemas/forwarding.svg @@ -1000,6 +1000,23 @@ Stalls: + + + + Privilege: + + + + + + MACHINE + + Stalls: + + + + Privilege: + + + + + + MACHINE + + Stalls: + + + + Privilege: + + + + + + MACHINE + + addItem("Direct", 0); cached_access->addItem("Cached", 1); + cached_access->addItem("As CPU (VMA)", 2); auto *memory_content = new MemoryTableView(nullptr, settings); // memory_content->setSizePolicy(); @@ -37,7 +38,6 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { auto *layout_top = new QHBoxLayout; layout_top->addWidget(cell_size); layout_top->addWidget(cached_access); - auto *layout = new QVBoxLayout; layout->addLayout(layout_top); layout->addWidget(memory_content); @@ -47,14 +47,14 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { setWidget(content); + connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); + connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); connect( - this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); - connect( - cell_size, QOverload::of(&QComboBox::currentIndexChanged), - memory_content, &MemoryTableView::set_cell_size); + cell_size, QOverload::of(&QComboBox::currentIndexChanged), memory_content, + &MemoryTableView::set_cell_size); connect( - cached_access, QOverload::of(&QComboBox::currentIndexChanged), - memory_model, &MemoryModel::cached_access); + cached_access, QOverload::of(&QComboBox::currentIndexChanged), memory_model, + &MemoryModel::cached_access); connect( go_edit, &HexLineEdit::value_edit_finished, memory_content, [memory_content](uint32_t value) { @@ -62,12 +62,8 @@ MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { }); connect( memory_content, &MemoryTableView::address_changed, go_edit, - [go_edit](machine::Address addr) { - go_edit->set_value(addr.get_raw()); - }); - connect( - this, &MemoryDock::focus_addr, memory_content, - &MemoryTableView::focus_address); + [go_edit](machine::Address addr) { go_edit->set_value(addr.get_raw()); }); + connect(this, &MemoryDock::focus_addr, memory_content, &MemoryTableView::focus_address); connect( memory_model, &MemoryModel::setup_done, memory_content, &MemoryTableView::recompute_columns); diff --git a/src/gui/windows/memory/memorydock.h b/src/gui/windows/memory/memorydock.h index 5b103be6..53528eb9 100644 --- a/src/gui/windows/memory/memorydock.h +++ b/src/gui/windows/memory/memorydock.h @@ -3,10 +3,8 @@ #include "machine/machine.h" #include "machine/memory/address.h" - #include #include -#include class MemoryDock : public QDockWidget { Q_OBJECT @@ -18,11 +16,12 @@ class MemoryDock : public QDockWidget { void setup(machine::Machine *machine); -signals: - void machine_setup(machine::Machine *machine); + signals: + void machine_setup(machine::Machine *machine); void focus_addr(machine::Address); private: + machine::Machine *machinePtr; }; -#endif // MEMORYDOCK_H +#endif // MEMORYDOCK_H \ No newline at end of file diff --git a/src/gui/windows/memory/memorymodel.cpp b/src/gui/windows/memory/memorymodel.cpp index 3698e53c..6381603f 100644 --- a/src/gui/windows/memory/memorymodel.cpp +++ b/src/gui/windows/memory/memorymodel.cpp @@ -59,7 +59,7 @@ QVariant MemoryModel::data(const QModelIndex &index, int role) const { QString s, t; machine::Address address; uint32_t data; - const machine::FrontendMemory *mem; + const machine::FrontendMemory *mem = nullptr; if (!get_row_address(address, index.row())) { return QString(""); } if (index.column() == 0) { t = QString::number(address.get_raw(), 16); @@ -67,11 +67,20 @@ QVariant MemoryModel::data(const QModelIndex &index, int role) const { return { QString("0x") + s + t }; } if (machine == nullptr) { return QString(""); } - mem = mem_access(); - if (mem == nullptr) { return QString(""); } - if ((access_through_cache > 0) && (machine->cache_data() != nullptr)) { - mem = machine->cache_data(); + bool vm_enabled = machine->config().get_vm_enabled(); + if (!vm_enabled) { + mem = mem_access(); + if ((access_through_cache > 0) && (machine->cache_data() != nullptr)) { + mem = machine->cache_data(); + } + } else { + if (access_through_cache == 2) { + mem = machine->get_tlb_data(); + } else { + mem = mem_access_phys(); + } } + if (mem == nullptr) { return QString(""); } address += cellSizeBytes() * (index.column() - 1); if (address < index0_offset) { return QString(""); } switch (cell_size) { @@ -192,12 +201,10 @@ bool MemoryModel::adjustRowAndOffset(int &row, machine::Address address) { } return get_row_for_address(row, address); } - void MemoryModel::cached_access(int cached) { access_through_cache = cached; update_all(); } - Qt::ItemFlags MemoryModel::flags(const QModelIndex &index) const { if (index.column() == 0) { return QAbstractTableModel::flags(index); @@ -215,7 +222,18 @@ bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int r if (!ok) { return false; } if (!get_row_address(address, index.row())) { return false; } if (index.column() == 0 || machine == nullptr) { return false; } - mem = mem_access_rw(); + if (machine->config().get_vm_enabled()) { + if (access_through_cache == 2) { + mem = machine->get_tlb_data_rw(); + } else { + mem = mem_access_phys_rw(); + } + } else { + mem = mem_access_rw(); + if (access_through_cache > 0 && machine->cache_data_rw()) { + mem = machine->cache_data_rw(); + } + } if (mem == nullptr) { return false; } if ((access_through_cache > 0) && (machine->cache_data_rw() != nullptr)) { mem = machine->cache_data_rw(); @@ -230,3 +248,21 @@ bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int r } return true; } + +const machine::FrontendMemory *MemoryModel::mem_access_phys() const { + if (!machine) return nullptr; + if (access_through_cache > 0 && machine->cache_data()) { + return machine->cache_data(); + } else { + return machine->memory_data_bus(); + } +} + +machine::FrontendMemory *MemoryModel::mem_access_phys_rw() const { + if (!machine) return nullptr; + if (access_through_cache > 0 && machine->cache_data_rw()) { + return machine->cache_data_rw(); + } else { + return machine->memory_data_bus_rw(); + } +} diff --git a/src/gui/windows/memory/memorymodel.h b/src/gui/windows/memory/memorymodel.h index 331aaeb7..ae4cb871 100644 --- a/src/gui/windows/memory/memorymodel.h +++ b/src/gui/windows/memory/memorymodel.h @@ -69,7 +69,6 @@ class MemoryModel : public QAbstractTableModel { } return true; } - public slots: void setup(machine::Machine *machine); void set_cell_size(int index); @@ -83,6 +82,8 @@ public slots: private: [[nodiscard]] const machine::FrontendMemory *mem_access() const; [[nodiscard]] machine::FrontendMemory *mem_access_rw() const; + [[nodiscard]] const machine::FrontendMemory *mem_access_phys() const; + [[nodiscard]] machine::FrontendMemory *mem_access_phys_rw() const; enum MemoryCellSize cell_size; unsigned int cells_per_row; machine::Address index0_offset; diff --git a/src/gui/windows/tlb/tlbdock.cpp b/src/gui/windows/tlb/tlbdock.cpp new file mode 100644 index 00000000..a14bcf02 --- /dev/null +++ b/src/gui/windows/tlb/tlbdock.cpp @@ -0,0 +1,139 @@ +#include "tlbdock.h" + +TLBDock::TLBDock(QWidget *parent, const QString &type) + : QDockWidget(parent) + , tlbscene(nullptr) + , connected_tlb(nullptr) { + top_widget = new QWidget(this); + setWidget(top_widget); + layout_box = new QVBoxLayout(top_widget); + + top_form = new QWidget(top_widget); + top_form->setVisible(false); + layout_box->addWidget(top_form); + layout_top_form = new QFormLayout(top_form); + + l_hit = new QLabel("0", top_form); + l_hit->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Hit:", l_hit); + + l_miss = new QLabel("0", top_form); + l_miss->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Miss:", l_miss); + + l_m_reads = new QLabel("0", top_form); + l_m_reads->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory reads:", l_m_reads); + + l_m_writes = new QLabel("0", top_form); + l_m_writes->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory writes:", l_m_writes); + + l_stalled = new QLabel("0", top_form); + l_stalled->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Memory stall cycles:", l_stalled); + + l_hit_rate = new QLabel("0.000%", top_form); + l_hit_rate->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Hit rate:", l_hit_rate); + + l_speed = new QLabel("100%", top_form); + l_speed->setTextFormat(Qt::PlainText); + layout_top_form->addRow("Improved speed:", l_speed); + + graphicsview = new GraphicsView(top_widget); + graphicsview->setVisible(false); + layout_box->addWidget(graphicsview); + tlbscene = nullptr; + + no_tlb = new QLabel("No " + type + " TLB configured", top_widget); + layout_box->addWidget(no_tlb); + + setObjectName(type + "TLB"); + setWindowTitle(type + " TLB"); +} + +void TLBDock::setup(machine::TLB *tlb) { + memory_reads = 0; + memory_writes = 0; + hit = 0; + miss = 0; + stalled = 0; + speed_improv = 0.0; + hit_rate = 0.0; + + l_hit->setText("0"); + l_miss->setText("0"); + l_stalled->setText("0"); + l_m_reads->setText("0"); + l_m_writes->setText("0"); + l_hit_rate->setText("0.000%"); + l_speed->setText("100%"); + + if (tlb != nullptr) { + connect(tlb, &machine::TLB::hit_update, this, &TLBDock::hit_update, Qt::UniqueConnection); + connect(tlb, &machine::TLB::miss_update, this, &TLBDock::miss_update, Qt::UniqueConnection); + connect( + tlb, &machine::TLB::statistics_update, this, &TLBDock::statistics_update, + Qt::UniqueConnection); + connect( + tlb, &machine::TLB::memory_reads_update, this, &TLBDock::memory_reads_update, + Qt::UniqueConnection); + connect( + tlb, &machine::TLB::memory_writes_update, this, &TLBDock::memory_writes_update, + Qt::UniqueConnection); + } + connected_tlb = const_cast(tlb); + top_form->setVisible(tlb != nullptr); + no_tlb->setVisible(tlb == nullptr); + + delete tlbscene; + tlbscene = nullptr; + if (tlb != nullptr) { + tlbscene = new TLBViewScene(tlb); + graphicsview->setScene(tlbscene); + } else { + graphicsview->setScene(nullptr); + } + graphicsview->setVisible(tlb != nullptr); +} + +void TLBDock::paintEvent(QPaintEvent *event) { + l_stalled->setText(QString::number(stalled)); + l_hit_rate->setText(QString::number(hit_rate, 'f', 3) + QString("%")); + l_speed->setText(QString::number(speed_improv, 'f', 0) + QString("%")); + l_hit->setText(QString::number(hit)); + l_miss->setText(QString::number(miss)); + l_m_reads->setText(QString::number(memory_reads)); + l_m_writes->setText(QString::number(memory_writes)); + QDockWidget::paintEvent(event); +} + +void TLBDock::hit_update(unsigned val) { + hit = val; + update(); +} + +void TLBDock::miss_update(unsigned val) { + miss = val; + update(); +} + +void TLBDock::statistics_update(unsigned stalled_cycles, double speed_improv_v, double hit_rate_v) { + stalled = stalled_cycles; + speed_improv = speed_improv_v; + hit_rate = hit_rate_v; + update(); +} + +void TLBDock::memory_reads_update(unsigned val) { + memory_reads = val; + l_m_reads->setText(QString::number(memory_reads)); + update(); +} + +void TLBDock::memory_writes_update(unsigned val) { + memory_writes = val; + l_m_writes->setText(QString::number(memory_writes)); + update(); +} diff --git a/src/gui/windows/tlb/tlbdock.h b/src/gui/windows/tlb/tlbdock.h new file mode 100644 index 00000000..a89b9afa --- /dev/null +++ b/src/gui/windows/tlb/tlbdock.h @@ -0,0 +1,58 @@ +#ifndef TLBDOCK_H +#define TLBDOCK_H + +#include "tlbview.h" +#include "graphicsview.h" +#include "machine/machine.h" + +#include +#include +#include +#include + +class TLBDock : public QDockWidget { + Q_OBJECT +public: + TLBDock(QWidget *parent, const QString &type); + + void setup(machine::TLB *tlb); + + void paintEvent(QPaintEvent *event) override; + + private slots: + void hit_update(unsigned val); + void miss_update(unsigned val); + void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); + void memory_reads_update(unsigned val); + void memory_writes_update(unsigned val); + + +private: + QVBoxLayout *layout_box; + QWidget *top_widget; + QWidget *top_form; + QFormLayout *layout_top_form; + + QLabel *l_hit; + QLabel *l_miss; + QLabel *l_stalled; + QLabel *l_speed; + QLabel *l_hit_rate; + QLabel *no_tlb; + QLabel *l_m_reads; + QLabel *l_m_writes; + + GraphicsView *graphicsview; + TLBViewScene *tlbscene; + + unsigned memory_reads = 0; + unsigned memory_writes = 0; + unsigned hit = 0; + unsigned miss = 0; + unsigned stalled = 0; + double speed_improv = 0.0; + double hit_rate = 0.0; + + QPointer connected_tlb; +}; +#endif // TLBDOCK_H diff --git a/src/gui/windows/tlb/tlbview.cpp b/src/gui/windows/tlb/tlbview.cpp new file mode 100644 index 00000000..a1c2c529 --- /dev/null +++ b/src/gui/windows/tlb/tlbview.cpp @@ -0,0 +1,179 @@ +#include "tlbview.h" + +#include +#include +#include +#include + +static const int ROW_HEIGHT = 16; +static const int VCOL_WIDTH = 18; +static const int FIELD_WIDTH = 120; + +TLBAddressBlock::TLBAddressBlock(machine::TLB *tlb, unsigned width) { + this->width = width; + rows = tlb->get_config().get_tlb_num_sets(); + s_row = rows > 1 ? (32 - __builtin_clz(rows - 1)) : 0; + s_tag = 32 - s_row - 2; + tag = 0; + row = 0; + + connect(tlb, &machine::TLB::tlb_update, this, &TLBAddressBlock::tlb_update); +} + +QRectF TLBAddressBlock::boundingRect() const { + return QRectF(0, 0, width, 40); +} + +void TLBAddressBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { + QFont fnt; + fnt.setPointSize(8); + painter->setFont(fnt); + + painter->drawText(QRectF(0, 0, width, 14), Qt::AlignCenter, "TLB Address"); + painter->drawText(QRectF(5, 18, 100, 16), Qt::AlignLeft, QString("Set: %1").arg(row)); +} + +void TLBAddressBlock::tlb_update(unsigned, unsigned set, bool, unsigned, quint64, quint64, bool) { + this->row = set; + update(); +} + +/////////////////////////////////////////// + +TLBViewBlock::TLBViewBlock(machine::TLB *tlb, unsigned way) : tlb(tlb), way_index(way) { + rows = tlb->get_config().get_tlb_num_sets(); + curr_row = 0; + last_set = 0; + last_highlighted = false; + + QFont font; + font.setPixelSize(10); + + validity.reserve(rows); + asid.reserve(rows); + vpn.reserve(rows); + phys.reserve(rows); + + int y = 2; + for (unsigned i = 0; i < rows; ++i) { + int x = 2; + auto *v = new QGraphicsSimpleTextItem("0", this); + v->setFont(font); + v->setPos(x, y); + x += VCOL_WIDTH; + + auto *a = new QGraphicsSimpleTextItem("", this); + a->setFont(font); + a->setPos(x, y); + x += 60; + + auto *vpnItem = new QGraphicsSimpleTextItem("", this); + vpnItem->setFont(font); + vpnItem->setPos(x, y); + x += FIELD_WIDTH; + + auto *physItem = new QGraphicsSimpleTextItem("", this); + physItem->setFont(font); + physItem->setPos(x, y); + + validity.push_back(v); + asid.push_back(a); + vpn.push_back(vpnItem); + phys.push_back(physItem); + + y += ROW_HEIGHT; + } + + auto *l_v = new QGraphicsSimpleTextItem("V", this); + l_v->setFont(font); + l_v->setPos(2, -14); + + auto *l_asid = new QGraphicsSimpleTextItem("ASID", this); + l_asid->setFont(font); + l_asid->setPos(2 + VCOL_WIDTH + 2, -14); + + auto *l_vpn = new QGraphicsSimpleTextItem("VPN", this); + l_vpn->setFont(font); + l_vpn->setPos(2 + VCOL_WIDTH + 62, -14); + + auto *l_phys = new QGraphicsSimpleTextItem("PBASE", this); + l_phys->setFont(font); + l_phys->setPos(2 + VCOL_WIDTH + 62 + FIELD_WIDTH, -14); + + connect(tlb, &machine::TLB::tlb_update, this, &TLBViewBlock::tlb_update); +} + +QRectF TLBViewBlock::boundingRect() const { + return QRectF(-2, -18, VCOL_WIDTH + 60 + FIELD_WIDTH + 200, rows * ROW_HEIGHT + 40); +} + +void TLBViewBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { + int width = boundingRect().width(); + painter->drawRect(0, 0, width, rows * ROW_HEIGHT); + for (unsigned i = 0; i <= rows; ++i) { + painter->drawLine(0, i * ROW_HEIGHT, width, i * ROW_HEIGHT); + } +} + +void TLBViewBlock::tlb_update( + unsigned way, + unsigned set, + bool valid, + unsigned asid_v, + quint64 vpn_v, + quint64 phys_v, + bool write) { + if (way != way_index) return; + validity[set]->setText(valid ? "1" : "0"); + asid[set]->setText(valid ? QString::number(asid_v) : QString()); + vpn[set]->setText( + valid ? QString("0x%1").arg(QString::number(vpn_v, 16).toUpper()) : QString()); + phys[set]->setText( + valid ? QString("0x%1").arg(QString::number(phys_v, 16).toUpper()) : QString()); + + if (last_highlighted) { + validity[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + asid[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + vpn[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + phys[last_set]->setBrush(QBrush(QColor(0, 0, 0))); + } + if (valid) { + QColor c = write ? QColor(200, 0, 0) : QColor(0, 0, 150); + validity[set]->setBrush(QBrush(c)); + asid[set]->setBrush(QBrush(c)); + vpn[set]->setBrush(QBrush(c)); + phys[set]->setBrush(QBrush(c)); + } + last_highlighted = true; + last_set = set; +} + +/////////////////////////////////////////// + +TLBViewScene::TLBViewScene(machine::TLB *tlb) { + associativity = tlb->get_config().get_tlb_associativity(); + block.reserve(associativity); + int offset = 0; + for (unsigned i = 0; i < associativity; ++i) { + auto b = std::make_unique(tlb, i); + addItem(b.get()); + b->setPos(1, offset); + offset += b->boundingRect().height(); + block.push_back(std::move(b)); + } + ablock = std::make_unique( + tlb, block.empty() ? FIELD_WIDTH : static_cast(block[0]->boundingRect().width())); + addItem(ablock.get()); + ablock->setPos(0, -ablock->boundingRect().height() - 16); +} + +TLBViewScene::~TLBViewScene() { + for (auto &bptr : block) { + if (bptr) removeItem(bptr.get()); + } + block.clear(); + if (ablock) { + removeItem(ablock.get()); + ablock.reset(); + } +} diff --git a/src/gui/windows/tlb/tlbview.h b/src/gui/windows/tlb/tlbview.h new file mode 100644 index 00000000..53251e98 --- /dev/null +++ b/src/gui/windows/tlb/tlbview.h @@ -0,0 +1,64 @@ +#ifndef TLBVIEW_H +#define TLBVIEW_H + +#include "machine/memory/tlb/tlb.h" +#include "svgscene/utils/memory_ownership.h" + +#include +#include +#include +#include + +class TLBAddressBlock : public QGraphicsObject { + Q_OBJECT +public: + TLBAddressBlock(machine::TLB *tlb, unsigned width); + QRectF boundingRect() const override; + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override; + + public slots: + void tlb_update(unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); + +private: + unsigned width; + unsigned rows; + unsigned tag, row; + unsigned s_row, s_tag; +}; + +class TLBViewBlock : public QGraphicsObject { + Q_OBJECT +public: + TLBViewBlock(machine::TLB *tlb, unsigned way); + ~TLBViewBlock() override = default; + QRectF boundingRect() const override; + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override; + + public slots: + void tlb_update(unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); + +private: + QPointer tlb; + unsigned way_index; + unsigned rows; + BORROWED std::vector validity; + BORROWED std::vector asid; + BORROWED std::vector vpn; + BORROWED std::vector phys; + unsigned curr_row; + unsigned last_set; + bool last_highlighted; +}; + +class TLBViewScene : public QGraphicsScene { +public: + TLBViewScene(machine::TLB *tlb); + ~TLBViewScene() override; + +private: + unsigned associativity; + std::vector> block; + std::unique_ptr ablock; +}; + +#endif // TLBVIEW_H diff --git a/src/machine/CMakeLists.txt b/src/machine/CMakeLists.txt index f3cca553..60246ea0 100644 --- a/src/machine/CMakeLists.txt +++ b/src/machine/CMakeLists.txt @@ -22,6 +22,9 @@ set(machine_SOURCES memory/cache/cache_policy.cpp memory/frontend_memory.cpp memory/memory_bus.cpp + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.cpp programloader.cpp predictor.cpp registers.cpp @@ -68,6 +71,11 @@ set(machine_HEADERS utils.h execute/alu_op.h execute/mul_op.h + memory/virtual/virtual_address.h + memory/tlb/tlb.h + memory/virtual/sv32.h + memory/tlb/tlb_policy.h + memory/virtual/page_table_walker.h ) # Object library is preferred, because the library archive is never really @@ -106,6 +114,8 @@ if(NOT ${WASM}) add_test(NAME registers COMMAND registers_test) add_executable(memory_test + machineconfig.cpp + machineconfig.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h @@ -113,6 +123,12 @@ if(NOT ${WASM}) memory/backend/memory.test.h memory/frontend_memory.cpp memory/frontend_memory.h + memory/tlb/tlb.h + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.h + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.h + memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp @@ -138,6 +154,12 @@ if(NOT ${WASM}) memory/cache/cache_policy.h memory/frontend_memory.cpp memory/frontend_memory.h + memory/tlb/tlb.h + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.h + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.h + memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp @@ -205,6 +227,12 @@ if(NOT ${WASM}) memory/cache/cache_policy.h memory/frontend_memory.cpp memory/frontend_memory.h + memory/tlb/tlb.h + memory/tlb/tlb.cpp + memory/tlb/tlb_policy.h + memory/tlb/tlb_policy.cpp + memory/virtual/page_table_walker.h + memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h registers.cpp diff --git a/src/machine/core.cpp b/src/machine/core.cpp index c07cb797..f19aaf1f 100644 --- a/src/machine/core.cpp +++ b/src/machine/core.cpp @@ -5,6 +5,7 @@ #include "utils.h" #include +#include LOG_CATEGORY("machine.core"); @@ -61,6 +62,7 @@ void Core::reset() { state.cycle_count = 0; state.stall_count = 0; do_reset(); + state.set_current_privilege(CSR::PrivilegeLevel::MACHINE); } unsigned Core::get_cycle_count() const { @@ -158,7 +160,9 @@ bool Core::handle_exception( control_state->update_exception_cause(excause); if (control_state->read_internal(CSR::Id::MTVEC) != 0 && !get_step_over_exception(excause)) { - control_state->exception_initiate(CSR::PrivilegeLevel::MACHINE, CSR::PrivilegeLevel::MACHINE); + control_state->exception_initiate( + state.current_privilege(), CSR::PrivilegeLevel::MACHINE); + state.set_current_privilege(CSR::PrivilegeLevel::MACHINE); regs->write_pc(control_state->exception_pc_address()); } } @@ -192,16 +196,16 @@ static int32_t amo32_operations(enum AccessControl memctl, int32_t a, int32_t b) } static int64_t amo64_operations(enum AccessControl memctl, int64_t a, int64_t b) { - switch(memctl) { + switch (memctl) { case AC_AMOSWAP64: return b; - case AC_AMOADD64: return a + b; - case AC_AMOXOR64: return a ^ b; - case AC_AMOAND64: return a & b; - case AC_AMOOR64: return a | b; - case AC_AMOMIN64: return a < b? a: b; - case AC_AMOMAX64: return a < b? b: a; - case AC_AMOMINU64: return (uint64_t)a < (uint64_t)b? a: b; - case AC_AMOMAXU64: return (uint64_t)a < (uint64_t)b? b: a; + case AC_AMOADD64: return a + b; + case AC_AMOXOR64: return a ^ b; + case AC_AMOAND64: return a & b; + case AC_AMOOR64: return a | b; + case AC_AMOMIN64: return a < b ? a : b; + case AC_AMOMAX64: return a < b ? b : a; + case AC_AMOMINU64: return (uint64_t)a < (uint64_t)b ? a : b; + case AC_AMOMAXU64: return (uint64_t)a < (uint64_t)b ? b : a; default: break; } return 0; @@ -252,8 +256,7 @@ enum ExceptionCause Core::memory_special( towrite_val = 1; } break; - case AC_FISRT_AMO_MODIFY32 ... AC_LAST_AMO_MODIFY32: - { + case AC_FISRT_AMO_MODIFY32 ... AC_LAST_AMO_MODIFY32: { if (!memread || !memwrite) { break; } int32_t fetched_value; fetched_value = (int32_t)(mem_data->read_u32(mem_addr)); @@ -262,8 +265,7 @@ enum ExceptionCause Core::memory_special( towrite_val = fetched_value; break; } - case AC_FISRT_AMO_MODIFY64 ... AC_LAST_AMO_MODIFY64: - { + case AC_FISRT_AMO_MODIFY64 ... AC_LAST_AMO_MODIFY64: { if (!memread || !memwrite) { break; } int64_t fetched_value; fetched_value = (int64_t)(mem_data->read_u64(mem_addr)); @@ -287,9 +289,7 @@ FetchState Core::fetch(PCInterstage pc, bool skip_break) { if (!skip_break && hw_breaks.contains(inst_addr)) { excause = EXCAUSE_HWBREAK; } - if (control_state != nullptr) { - control_state->increment_internal(CSR::Id::MCYCLE, 1); - } + if (control_state != nullptr) { control_state->increment_internal(CSR::Id::MCYCLE, 1); } if (control_state != nullptr && excause == EXCAUSE_NONE) { if (control_state->core_interrupt_request()) { excause = EXCAUSE_INT; } @@ -315,9 +315,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { dt.inst.flags_alu_op_mem_ctl(flags, alu_op, mem_ctl); - if ((flags ^ check_inst_flags_val) & check_inst_flags_mask) { - excause = EXCAUSE_INSN_ILLEGAL; - } + if ((flags ^ check_inst_flags_val) & check_inst_flags_mask) { excause = EXCAUSE_INSN_ILLEGAL; } RegisterId num_rs = (flags & (IMF_ALU_REQ_RS | IMF_ALU_RS_ID)) ? dt.inst.rs() : 0; RegisterId num_rt = (flags & IMF_ALU_REQ_RT) ? dt.inst.rt() : 0; @@ -343,8 +341,7 @@ DecodeState Core::decode(const FetchInterstage &dt) { // TODO: EXCAUSE_ECALL_S, EXCAUSE_ECALL_U } } - if (flags & IMF_FORCE_W_OP) - w_operation = true; + if (flags & IMF_FORCE_W_OP) w_operation = true; return { DecodeInternalState { .alu_op_num = static_cast(alu_op.alu_op), @@ -366,8 +363,9 @@ DecodeState Core::decode(const FetchInterstage &dt) { .excause = excause, .ff_rs = FORWARD_NONE, .ff_rt = FORWARD_NONE, - .alu_component = (flags & IMF_AMO) ? AluComponent::PASS : - (flags & IMF_MUL) ? AluComponent::MUL : AluComponent::ALU, + .alu_component = (flags & IMF_AMO) ? AluComponent::PASS + : (flags & IMF_MUL) ? AluComponent::MUL + : AluComponent::ALU, .aluop = alu_op, .memctl = mem_ctl, .num_rs = num_rs, @@ -409,7 +407,8 @@ ExecuteState Core::execute(const DecodeInterstage &dt) { }(); const RegisterValue alu_val = [=] { if (excause != EXCAUSE_NONE) return RegisterValue(0); - return alu_combined_operate(dt.aluop, dt.alu_component, dt.w_operation, dt.alu_mod, alu_fst, alu_sec); + return alu_combined_operate( + dt.aluop, dt.alu_component, dt.w_operation, dt.alu_mod, alu_fst, alu_sec); }(); const Address branch_jal_target = dt.inst_addr + dt.immediate_val.as_i64(); @@ -502,11 +501,13 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { // Predictor update if (dt.branch_jal) { // JAL Jump instruction (J-type (alternative to U-type with different immediate bit order)) - predictor->update(dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::JUMP, BranchResult::TAKEN); + predictor->update( + dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::JUMP, BranchResult::TAKEN); } else if (dt.branch_jalr) { // JALR Jump register instruction (I-type) predictor->update( - dt.inst, dt.inst_addr, Address(get_xlen_from_reg(dt.alu_val)), BranchType::JUMP, BranchResult::TAKEN); + dt.inst, dt.inst_addr, Address(get_xlen_from_reg(dt.alu_val)), BranchType::JUMP, + BranchResult::TAKEN); } else if (dt.branch_bxx) { // BXX Conditional branch instruction (B-type (alternative to S-type with different // immediate bit order)) @@ -523,11 +524,15 @@ MemoryState Core::memory(const ExecuteInterstage &dt) { csr_written = true; } if (dt.xret) { - control_state->exception_return(CSR::PrivilegeLevel::MACHINE); + CSR::PrivilegeLevel restored + = control_state->exception_return(state.current_privilege()); + state.set_current_privilege(restored); if (this->xlen == Xlen::_32) - computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); + computed_next_inst_addr + = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); else - computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u64()); + computed_next_inst_addr + = Address(control_state->read_internal(CSR::Id::MEPC).as_u64()); csr_written = true; } } @@ -577,7 +582,7 @@ WritebackState Core::writeback(const MemoryInterstage &dt) { if (dt.regwrite) { regs->write_gp(dt.num_rd, dt.towrite_val); } return WritebackState { WritebackInternalState { - .inst = (dt.excause == EXCAUSE_NONE)? dt.inst: Instruction::NOP, + .inst = (dt.excause == EXCAUSE_NONE) ? dt.inst : Instruction::NOP, .inst_addr = dt.inst_addr, .value = dt.towrite_val, .num_rd = dt.num_rd, diff --git a/src/machine/core/core_state.h b/src/machine/core/core_state.h index 67c1f7a8..9322bfa4 100644 --- a/src/machine/core/core_state.h +++ b/src/machine/core/core_state.h @@ -18,6 +18,15 @@ struct CoreState { AddressRange LoadReservedRange; uint32_t stall_count = 0; uint32_t cycle_count = 0; + unsigned current_privilege_u = static_cast(CSR::PrivilegeLevel::MACHINE); + + [[nodiscard]] CSR::PrivilegeLevel current_privilege() const noexcept { + return static_cast(current_privilege_u); + } + + void set_current_privilege(CSR::PrivilegeLevel p) noexcept { + current_privilege_u = static_cast(p); + } }; } // namespace machine diff --git a/src/machine/csr/controlstate.cpp b/src/machine/csr/controlstate.cpp index 12aeab5e..b51eac6f 100644 --- a/src/machine/csr/controlstate.cpp +++ b/src/machine/csr/controlstate.cpp @@ -104,6 +104,27 @@ namespace machine { namespace CSR { write_signal(Id::CYCLE, register_data[Id::CYCLE]); } + void ControlState::sstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val) { + uint64_t s_mask + = Field::mstatus::SIE.mask() | Field::mstatus::SPIE.mask() | Field::mstatus::SPP.mask(); + + if (xlen == Xlen::_64) { + s_mask |= Field::mstatus::UXL.mask(); + s_mask |= Field::mstatus::SXL.mask(); + } + uint64_t write_val = val.as_u64() & desc.write_mask.as_u64(); + uint64_t mstatus_val = register_data[Id::MSTATUS].as_u64(); + mstatus_val = (mstatus_val & ~s_mask) | (write_val & s_mask); + register_data[Id::MSTATUS] = mstatus_val; + uint64_t new_sstatus = mstatus_val & s_mask; + if (xlen == Xlen::_32) new_sstatus &= 0xffffffff; + reg = new_sstatus; + emit write_signal(Id::MSTATUS, register_data[Id::MSTATUS]); + } + bool ControlState::operator==(const ControlState &other) const { return register_data == other.register_data; } @@ -173,14 +194,41 @@ namespace machine { namespace CSR { PrivilegeLevel ControlState::exception_return(enum PrivilegeLevel act_privlev) { size_t reg_id = Id::MSTATUS; RegisterValue ® = register_data[reg_id]; - PrivilegeLevel restored_privlev; - Q_UNUSED(act_privlev) - - write_field(Field::mstatus::MIE, read_field(Field::mstatus::MPIE).as_u32()); - write_field(Field::mstatus::MPIE, (uint64_t)1); - - restored_privlev = static_cast(read_field(Field::mstatus::MPP).as_u32()); - write_field(Field::mstatus::MPP, (uint64_t)0); + PrivilegeLevel restored_privlev = PrivilegeLevel::MACHINE; + if (act_privlev == PrivilegeLevel::MACHINE) { + // MRET semantics: + // MIE <- MPIE + // MPIE <- 1 + // restored_privlev <- MPP + // MPP <- 0 + write_field(Field::mstatus::MIE, read_field(Field::mstatus::MPIE).as_u32()); + write_field(Field::mstatus::MPIE, (uint64_t)1); + uint32_t raw_mpp + = static_cast(read_field(Field::mstatus::MPP).as_u32()) & 0x3; + switch (raw_mpp) { + case 0: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; + case 1: restored_privlev = PrivilegeLevel::SUPERVISOR; break; + case 2: restored_privlev = PrivilegeLevel::HYPERVISOR; break; + case 3: restored_privlev = PrivilegeLevel::MACHINE; break; + default: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; + } + write_field(Field::mstatus::MPP, (uint64_t)0); // clear MPP per spec + } else if (act_privlev == PrivilegeLevel::SUPERVISOR) { + // SRET semantics: + // SIE <- SPIE + // SPIE <- 1 + // restored_privlev <- SPP + // SPP <- 0 + write_field(Field::mstatus::SIE, read_field(Field::mstatus::SPIE).as_u32()); + write_field(Field::mstatus::SPIE, (uint64_t)1); + uint32_t raw_spp + = static_cast(read_field(Field::mstatus::SPP).as_u32()) & 0x1; + restored_privlev + = (raw_spp == 1) ? PrivilegeLevel::SUPERVISOR : PrivilegeLevel::UNPRIVILEGED; + write_field(Field::mstatus::SPP, (uint64_t)0); + } else { + restored_privlev = PrivilegeLevel::UNPRIVILEGED; + } emit write_signal(reg_id, reg); diff --git a/src/machine/csr/controlstate.h b/src/machine/csr/controlstate.h index 90918fd9..f2443678 100644 --- a/src/machine/csr/controlstate.h +++ b/src/machine/csr/controlstate.h @@ -46,7 +46,20 @@ namespace machine { namespace CSR { // ... MCYCLE, MINSTRET, - _COUNT, + // Supervisor Trap Setup + SSTATUS, + // ... + STVEC, + // ... + // Supervisor Trap Handling + SSCRATCH, + SEPC, + SCAUSE, + STVAL, + // ... + // Supervisor Protection and Translation + SATP, + _COUNT }; }; @@ -170,6 +183,10 @@ namespace machine { namespace CSR { const RegisterDesc &desc, RegisterValue ®, RegisterValue val); + void sstatus_wlrl_write_handler( + const RegisterDesc &desc, + RegisterValue ®, + RegisterValue val); }; struct RegisterDesc { @@ -201,6 +218,13 @@ namespace machine { namespace CSR { static constexpr const RegisterFieldDesc *fields[] = { &SIE, &MIE, &SPIE, &MPIE, &SPP, &MPP, &UXL, &SXL}; static constexpr unsigned count = sizeof(fields) / sizeof(fields[0]); } + namespace satp { + static constexpr RegisterFieldDesc MODE = { "MODE", Id::SATP, {1, 31}, "Address translation mode" }; + static constexpr RegisterFieldDesc ASID = { "ASID", Id::SATP, {9, 22}, "Address-space ID" }; + static constexpr RegisterFieldDesc PPN = { "PPN", Id::SATP, {22, 0}, "Root page-table physical page number" }; + static constexpr const RegisterFieldDesc *fields[] = { &MODE, &ASID, &PPN }; + static constexpr unsigned count = sizeof(fields)/sizeof(fields[0]); + } } /** Definitions of supported CSR registers */ @@ -231,6 +255,14 @@ namespace machine { namespace CSR { [Id::MCYCLE] = { "mcycle", 0xB00_csr, "Machine cycle counter.", 0, (register_storage_t)0xffffffffffffffff, &ControlState::mcycle_wlrl_write_handler}, [Id::MINSTRET] = { "minstret", 0xB02_csr, "Machine instructions-retired counter."}, + // Supervisor-level CSRs + [Id::SSTATUS] = { "sstatus", 0x100_csr, "Supervisor status register.", 0, 0xffffffff, &ControlState::sstatus_wlrl_write_handler }, + [Id::STVEC] = { "stvec", 0x105_csr, "Supervisor trap-handler base address." }, + [Id::SSCRATCH] = { "sscratch", 0x140_csr, "Scratch register for supervisor trap handlers." }, + [Id::SEPC] = { "sepc", 0x141_csr, "Supervisor exception program counter." }, + [Id::SCAUSE] = { "scause", 0x142_csr, "Supervisor trap cause." }, + [Id::STVAL] = { "stval", 0x143_csr, "Supervisor bad address or instruction." }, + [Id::SATP] = { "satp", 0x180_csr, "Supervisor address translation and protection", 0, 0xffffffff, &ControlState::default_wlrl_write_handler, { Field::satp::fields, Field::satp::count } } } }; /** Lookup from CSR address (value used in instruction) to internal id (index in continuous diff --git a/src/machine/machine.cpp b/src/machine/machine.cpp index c50ae78e..4ea27616 100644 --- a/src/machine/machine.cpp +++ b/src/machine/machine.cpp @@ -12,7 +12,6 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) : machine_config(std::move(config)) , stat(ST_READY) { regs = new Registers(); - if (load_executable) { ProgramLoader program(machine_config.elf()); this->machine_config.set_simulated_endian(program.get_endian()); @@ -78,7 +77,17 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) access_time_burst, access_enable_burst); - controlst = new CSR::ControlState(machine_config.get_simulated_xlen(), machine_config.get_isa_word()); + controlst + = new CSR::ControlState(machine_config.get_simulated_xlen(), machine_config.get_isa_word()); + + tlb_program = new TLB( + cch_program, PROGRAM, machine_config.access_tlb_program(), machine_config.get_vm_enabled()); + tlb_data = new TLB( + cch_data, DATA, machine_config.access_tlb_data(), machine_config.get_vm_enabled()); + controlst->write_internal(CSR::Id::SATP, 0); + tlb_program->on_csr_write(CSR::Id::SATP, 0); + tlb_data->on_csr_write(CSR::Id::SATP, 0); + predictor = new BranchPredictor( machine_config.get_bp_enabled(), machine_config.get_bp_type(), machine_config.get_bp_init_state(), machine_config.get_bp_btb_bits(), @@ -86,11 +95,12 @@ Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) if (machine_config.pipelined()) { cr = new CorePipelined( - regs, predictor, cch_program, cch_data, controlst, - machine_config.get_simulated_xlen(), machine_config.get_isa_word(), machine_config.hazard_unit()); + regs, predictor, tlb_program, tlb_data, controlst, machine_config.get_simulated_xlen(), + machine_config.get_isa_word(), machine_config.hazard_unit()); } else { - cr = new CoreSingle(regs, predictor, cch_program, cch_data, controlst, - machine_config.get_simulated_xlen(), machine_config.get_isa_word()); + cr = new CoreSingle( + regs, predictor, tlb_program, tlb_data, controlst, machine_config.get_simulated_xlen(), + machine_config.get_isa_word()); } connect( this, &Machine::set_interrupt_signal, controlst, &CSR::ControlState::set_interrupt_signal); @@ -197,6 +207,10 @@ Machine::~Machine() { regs = nullptr; delete mem; mem = nullptr; + delete tlb_program; + tlb_program = nullptr; + delete tlb_data; + tlb_data = nullptr; delete cch_program; cch_program = nullptr; delete cch_data; @@ -270,6 +284,31 @@ void Machine::cache_sync() { } } +void Machine::tlb_sync() { + if (tlb_program) { + tlb_program->sync(); + } + if (tlb_data) { + tlb_data->sync(); + } +} + +const TLB *Machine::get_tlb_program() const { + return tlb_program ? &*tlb_program : nullptr; +} + +const TLB *Machine::get_tlb_data() const { + return tlb_data ? &*tlb_data : nullptr; +} + +TLB *Machine::get_tlb_program_rw() { + return tlb_program ? &*tlb_program : nullptr; +} + +TLB *Machine::get_tlb_data_rw() { + return tlb_data ? &*tlb_data : nullptr; +} + const MemoryDataBus *Machine::memory_data_bus() { return data_bus; } diff --git a/src/machine/machine.h b/src/machine/machine.h index fc5ba5f9..0265df8d 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -12,6 +12,7 @@ #include "memory/backend/aclintsswi.h" #include "memory/cache/cache.h" #include "memory/memory_bus.h" +#include "memory/tlb/tlb.h" #include "predictor.h" #include "registers.h" #include "simulator_exception.h" @@ -20,6 +21,8 @@ #include #include #include +#include +#include namespace machine { @@ -41,6 +44,11 @@ class Machine : public QObject { const Cache *cache_level2(); Cache *cache_data_rw(); void cache_sync(); + const TLB *get_tlb_program() const; + const TLB *get_tlb_data() const; + TLB *get_tlb_program_rw(); + TLB *get_tlb_data_rw(); + void tlb_sync(); const MemoryDataBus *memory_data_bus(); MemoryDataBus *memory_data_bus_rw(); SerialPort *serial_port(); @@ -85,6 +93,14 @@ class Machine : public QObject { bool get_step_over_exception(enum ExceptionCause excause) const; enum ExceptionCause get_exception_cause() const; + Address virtual_to_physical(Address v) { + if (tlb_data) { + return tlb_data->translate_virtual_to_physical(v); + } else { + return v; + } + } + public slots: void play(); void pause(); @@ -131,6 +147,8 @@ private slots: Cache *cch_program = nullptr; Cache *cch_data = nullptr; Cache *cch_level2 = nullptr; + TLB *tlb_program = nullptr; + TLB *tlb_data = nullptr; CSR::ControlState *controlst = nullptr; BranchPredictor *predictor = nullptr; Core *cr = nullptr; diff --git a/src/machine/machineconfig.cpp b/src/machine/machineconfig.cpp index 0a4e74bf..8ca30cb4 100644 --- a/src/machine/machineconfig.cpp +++ b/src/machine/machineconfig.cpp @@ -1,8 +1,10 @@ #include "machineconfig.h" #include "common/endian.h" +#include "machine.h" #include +#include #include using namespace machine; @@ -27,6 +29,10 @@ using namespace machine; #define DFC_BP_BTB_BITS 2 #define DFC_BP_BHR_BITS 0 #define DFC_BP_BHT_ADDR_BITS 2 +/// Default config of Virtual Memory +#define DFC_VM_ENABLED false +#define DFC_TLB_SETS 1 +#define DFC_TLB_ASSOC 1 ////////////////////////////////////////////////////////////////////////////// /// Default config of CacheConfig #define DFC_EN false @@ -151,6 +157,67 @@ bool CacheConfig::operator==(const CacheConfig &c) const { bool CacheConfig::operator!=(const CacheConfig &c) const { return !operator==(c); } +////////////////////////////////////////////////////////////////////////////// + +TLBConfig::TLBConfig() { + vm_asid = 0; + n_sets = DFC_TLB_SETS; + d_associativity = DFC_TLB_ASSOC; + replac_pol = (enum ReplacementPolicy)DFC_REPLAC; +} + +TLBConfig::TLBConfig(const TLBConfig *tc) { + if (tc == nullptr) { + vm_asid = 0; + n_sets = DFC_TLB_SETS; + d_associativity = DFC_TLB_ASSOC; + replac_pol = (enum ReplacementPolicy)DFC_REPLAC; + return; + } + vm_asid = tc->get_vm_asid(); + n_sets = tc->get_tlb_num_sets(); + d_associativity = tc->get_tlb_associativity(); + replac_pol = tc->get_tlb_replacement_policy(); +} + +TLBConfig::TLBConfig(const QSettings *sts, const QString &prefix) { + vm_asid = sts->value(prefix + "VM_ASID", 0u).toUInt(); + n_sets = sts->value(prefix + "NumSets", DFC_TLB_SETS).toUInt(); + d_associativity = sts->value(prefix + "Associativity", DFC_TLB_ASSOC).toUInt(); + replac_pol = (enum ReplacementPolicy)sts->value(prefix + "Policy", DFC_REPLAC).toUInt(); +} + +void TLBConfig::set_vm_asid(uint32_t a) { + vm_asid = a; +} + +uint32_t TLBConfig::get_vm_asid() const { + return vm_asid; +} + +void TLBConfig::set_tlb_num_sets(unsigned v) { + n_sets = v > 0 ? v : 1; +} + +void TLBConfig::set_tlb_associativity(unsigned v) { + d_associativity = v > 0 ? v : 1; +} + +void TLBConfig::set_tlb_replacement_policy(TLBConfig::ReplacementPolicy p) { + replac_pol = p; +} + +unsigned TLBConfig::get_tlb_num_sets() const { + return n_sets; +} + +unsigned TLBConfig::get_tlb_associativity() const { + return d_associativity; +} + +TLBConfig::ReplacementPolicy TLBConfig::get_tlb_replacement_policy() const { + return replac_pol; +} MachineConfig::MachineConfig() { simulated_endian = LITTLE; @@ -186,6 +253,11 @@ MachineConfig::MachineConfig() { bp_bhr_bits = DFC_BP_BHR_BITS; bp_bht_addr_bits = DFC_BP_BHT_ADDR_BITS; bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = DFC_VM_ENABLED; + tlb_program = TLBConfig(); + tlb_data = TLBConfig(); } MachineConfig::MachineConfig(const MachineConfig *config) { @@ -222,6 +294,11 @@ MachineConfig::MachineConfig(const MachineConfig *config) { bp_bhr_bits = config->get_bp_bhr_bits(); bp_bht_addr_bits = config->get_bp_bht_addr_bits(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = config->get_vm_enabled(); + tlb_program = config->tlbc_program(); + tlb_data = config->tlbc_data(); } #define N(STR) (prefix + QString(STR)) @@ -266,6 +343,11 @@ MachineConfig::MachineConfig(const QSettings *sts, const QString &prefix) { bp_bhr_bits = sts->value(N("BranchPredictor_BitsBHR"), DFC_BP_BHR_BITS).toUInt(); bp_bht_addr_bits = sts->value(N("BranchPredictor_BitsBHTAddr"), DFC_BP_BHT_ADDR_BITS).toUInt(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; + + // Virtual memory + vm_enabled = sts->value(N("VMEnabled"), DFC_VM_ENABLED).toBool(); + tlb_data = TLBConfig(sts, N("DataTLB_")); + tlb_program = TLBConfig(sts, N("ProgramTLB_")); } void MachineConfig::store(QSettings *sts, const QString &prefix) { @@ -298,6 +380,9 @@ void MachineConfig::store(QSettings *sts, const QString &prefix) { sts->setValue(N("BranchPredictor_BitsBTB"), get_bp_btb_bits()); sts->setValue(N("BranchPredictor_BitsBHR"), get_bp_bhr_bits()); sts->setValue(N("BranchPredictor_BitsBHTAddr"), get_bp_bht_addr_bits()); + + // Virtual memory + sts->setValue(N("VMEnabled"), get_vm_enabled()); } #undef N @@ -558,6 +643,22 @@ CacheConfig *MachineConfig::access_cache_level2() { return &cch_level2; } +TLBConfig *MachineConfig::access_tlb_program() { + return &tlb_program; +} + +TLBConfig *MachineConfig::access_tlb_data() { + return &tlb_data; +} + +const TLBConfig &MachineConfig::tlbc_program() const { + return tlb_program; +} + +const TLBConfig &MachineConfig::tlbc_data() const { + return tlb_data; +} + Endian MachineConfig::get_simulated_endian() const { return simulated_endian; } @@ -629,6 +730,14 @@ uint8_t MachineConfig::get_bp_bht_bits() const { return bp_bht_bits; } +void MachineConfig::set_vm_enabled(bool v) { + vm_enabled = v; +} + +bool MachineConfig::get_vm_enabled() const { + return vm_enabled; +} + bool MachineConfig::operator==(const MachineConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(pipelined) && CMP(delay_slot) && CMP(hazard_unit) && CMP(get_simulated_xlen) diff --git a/src/machine/machineconfig.h b/src/machine/machineconfig.h index 5392d9ef..4623a6da 100644 --- a/src/machine/machineconfig.h +++ b/src/machine/machineconfig.h @@ -82,6 +82,42 @@ class CacheConfig { enum WritePolicy write_pol; }; +class TLBConfig { + +public: + TLBConfig(); + explicit TLBConfig(const TLBConfig *cc); + explicit TLBConfig(const QSettings *, const QString &prefix = ""); + + enum VmMode { VM_BARE, VM_SV32 }; + + enum ReplacementPolicy { + RP_RAND, // Random + RP_LRU, // Least recently used + RP_LFU, // Least frequently used + RP_PLRU // Pseudo Least recently used + }; + + // Virtual Memory + void set_vm_asid(uint32_t a); + uint32_t get_vm_asid() const; + + void set_tlb_num_sets(unsigned); + void set_tlb_associativity(unsigned); + void set_tlb_replacement_policy(ReplacementPolicy); + + unsigned get_tlb_num_sets() const; + unsigned get_tlb_associativity() const; + ReplacementPolicy get_tlb_replacement_policy() const; + +private: + bool vm_enabled = false; + uint32_t vm_asid = 0; + unsigned n_sets = 1; + unsigned d_associativity = 1; + enum ReplacementPolicy replac_pol = RP_RAND; +}; + class MachineConfig { public: MachineConfig(); @@ -161,6 +197,12 @@ class MachineConfig { Xlen get_simulated_xlen() const; ConfigIsaWord get_isa_word() const; + // Virtual memory + void set_vm_enabled(bool v); + bool get_vm_enabled() const; + const TLBConfig &tlbc_program() const; + const TLBConfig &tlbc_data() const; + // Branch predictor - Setters void set_bp_enabled(bool e); void set_bp_type(PredictorType t); @@ -181,6 +223,9 @@ class MachineConfig { CacheConfig *access_cache_data(); CacheConfig *access_cache_level2(); + TLBConfig *access_tlb_program(); + TLBConfig *access_tlb_data(); + bool operator==(const MachineConfig &c) const; bool operator!=(const MachineConfig &c) const; @@ -208,6 +253,11 @@ class MachineConfig { uint8_t bp_bhr_bits; uint8_t bp_bht_addr_bits; uint8_t bp_bht_bits; // = bp_bhr_bits + bp_bht_addr_bits + + + // Virtual memory + bool vm_enabled; + TLBConfig tlb_program, tlb_data; }; } // namespace machine diff --git a/src/machine/memory/frontend_memory.cpp b/src/machine/memory/frontend_memory.cpp index a5e2205e..bd3ece4b 100644 --- a/src/machine/memory/frontend_memory.cpp +++ b/src/machine/memory/frontend_memory.cpp @@ -1,6 +1,7 @@ #include "memory/frontend_memory.h" #include "common/endian.h" +#include "tlb/tlb.h" namespace machine { @@ -147,10 +148,9 @@ bool FrontendMemory::write_generic( const T value, AccessEffects type) { // See example in read_generic for byteswap explanation. - const T swapped_value - = byteswap_if(value, this->simulated_machine_endian != NATIVE_ENDIAN); + const T swapped_value = byteswap_if(value, this->simulated_machine_endian != NATIVE_ENDIAN); return write(address, &swapped_value, sizeof(T), { .type = type }).changed; } FrontendMemory::FrontendMemory(Endian simulated_endian) : simulated_machine_endian(simulated_endian) {} -} // namespace machine \ No newline at end of file +} // namespace machine diff --git a/src/machine/memory/tlb/tlb.cpp b/src/machine/memory/tlb/tlb.cpp new file mode 100644 index 00000000..6872a178 --- /dev/null +++ b/src/machine/memory/tlb/tlb.cpp @@ -0,0 +1,274 @@ +#include "tlb.h" + +#include "csr/controlstate.h" +#include "machine.h" +#include "memory/virtual/page_table_walker.h" +#include "memory/virtual/sv32.h" + +LOG_CATEGORY("machine.TLB"); + +namespace machine { + +static bool is_mmio_region(uint64_t virt) { + if (virt >= 0xFFFFC000u && virt <= 0xFFFFC1FFu) return true; + if (virt >= 0xFFE00000u && virt <= 0xFFE4AFFFu) return true; + if (virt >= 0xFFFD0000u && virt <= 0xFFFDBFFFu) return true; + return false; +} + +static Address bypass_mmio(Address vaddr) { + return vaddr; // VA == PA for devices +} + +TLB::TLB( + FrontendMemory *memory, + TLBType type_, + const TLBConfig *config, + bool vm_enabled, + uint32_t memory_access_penalty_r, + uint32_t memory_access_penalty_w, + uint32_t memory_access_penalty_b, + bool memory_access_enable_b) + : FrontendMemory(memory->simulated_machine_endian) + , mem(memory) + , type(type_) + , tlb_config(config) + , vm_enabled(vm_enabled) + , access_pen_r(memory_access_penalty_r) + , access_pen_w(memory_access_penalty_w) + , access_pen_b(memory_access_penalty_b) + , access_ena_b(memory_access_enable_b) { + num_sets_ = tlb_config.get_tlb_num_sets(); + associativity_ = tlb_config.get_tlb_associativity(); + auto pol = tlb_config.get_tlb_replacement_policy(); + repl_policy = make_tlb_policy(static_cast(pol), associativity_, num_sets_); + table.assign(num_sets_, std::vector(associativity_)); + const char *tag = (type == PROGRAM ? "I" : "D"); + LOG("TLB[%s] constructed; sets=%zu way=%zu", tag, num_sets_, associativity_); + + emit hit_update(hit_count_); + emit miss_update(miss_count_); + emit memory_reads_update(mem_reads); + emit memory_writes_update(mem_writes); + update_all_statistics(); +} + +void TLB::on_csr_write(size_t internal_id, RegisterValue val) { + if (internal_id != CSR::Id::SATP) return; + current_satp_raw = static_cast(val.as_u64()); + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + LOG("TLB: SATP changed → flushed all; new SATP=0x%08x", current_satp_raw); + update_all_statistics(); +} + +void TLB::flush_single(VirtualAddress va, uint16_t asid) { + uint64_t vpn = va.get_raw() >> 12; + size_t s = set_index(vpn); + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && e.vpn == vpn && e.asid == asid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + const char *tag = (type == PROGRAM ? "I" : "D"); + LOG("TLB[%s]: flushed VA=0x%llx ASID=%u", tag, (unsigned long long)va.get_raw(), asid); + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, + false); + update_all_statistics(); + } + } +} + +void TLB::flush() { + if (num_sets_ == 0 || associativity_ == 0) return; + const char *tag = (type == PROGRAM ? "I" : "D"); + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + change_counter++; + LOG("TLB[%s]: flushed all entries", tag); + update_all_statistics(); +} + +void TLB::sync() { + flush(); +} + +Address TLB::translate_virtual_to_physical(Address vaddr) { + uint64_t virt = vaddr.get_raw(); + if (is_mmio_region(virt)) { return bypass_mmio(vaddr); } + + if (!vm_enabled) { return vaddr; } + + constexpr unsigned PAGE_SHIFT = 12; + constexpr uint64_t PAGE_MASK = (1ULL << PAGE_SHIFT) - 1; + + uint64_t off = virt & ((1ULL << PAGE_SHIFT) - 1); + uint64_t vpn = virt >> PAGE_SHIFT; + size_t s = set_index(vpn); + const char *tag = (type == PROGRAM ? "I" : "D"); + uint16_t asid = (current_satp_raw >> 22) & 0x1FF; + + // Check TLB hit + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && e.vpn == vpn && e.asid == asid) { + repl_policy->notify_access(s, w, /*valid=*/true); + uint64_t pbase = e.phys.get_raw() & ~((1ULL << PAGE_SHIFT) - 1); + hit_count_++; + emit hit_update(hit_count_); + emit tlb_update( + static_cast(w), static_cast(s), true, e.asid, e.vpn, pbase, + false); + update_all_statistics(); + return Address { pbase + off }; + } + } + + // TLB miss -> resolve with page table walker + VirtualAddress va { static_cast(virt) }; + PageTableWalker walker(mem); + + Address resolved_pa = walker.walk(va, current_satp_raw); + mem_reads += 1; + emit memory_reads_update(mem_reads); + + // Cache the resolved mapping in the TLB + uint64_t phys_base = resolved_pa.get_raw() & ~PAGE_MASK; + size_t victim = repl_policy->select_way(s); + auto &ent = table[s][victim]; + ent.valid = true; + ent.asid = asid; + ent.vpn = vpn; + ent.phys = Address { phys_base }; + repl_policy->notify_access(s, victim, /*valid=*/true); + miss_count_++; + emit miss_update(miss_count_); + emit tlb_update( + static_cast(victim), static_cast(s), true, ent.asid, ent.vpn, phys_base, + false); + + LOG("TLB[%s]: cached VA=0x%llx -> PA=0x%llx (ASID=%u) on miss", tag, (unsigned long long)virt, + (unsigned long long)phys_base, asid); + update_all_statistics(); + return Address { phys_base + off }; +} + +WriteResult TLB::translate_and_write(Address dst, const void *src, size_t sz, WriteOptions opts) { + Address pa = translate_virtual_to_physical(dst); + return mem->write(pa, src, sz, opts); +} + +ReadResult TLB::translate_and_read(void *dst, Address src, size_t sz, ReadOptions opts) { + Address pa = translate_virtual_to_physical(src); + return mem->read(dst, pa, sz, opts); +} + +bool TLB::reverse_lookup(Address paddr, VirtualAddress &out_va) const { + uint64_t ppn = paddr.get_raw() >> 12; + uint64_t offset = paddr.get_raw() & 0xFFF; + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid && (e.phys.get_raw() >> 12) == ppn) { + out_va = VirtualAddress { (e.vpn << 12) | offset }; + return true; + } + } + } + return false; +} + +double TLB::get_hit_rate() const { + unsigned comp = hit_count_ + miss_count_; + if (comp == 0) return 0.0; + return (double)hit_count_ / (double)comp * 100.0; +} + +uint32_t TLB::get_stall_count() const { + uint32_t st_cycles = mem_reads * (access_pen_r - 1) + mem_writes * (access_pen_w - 1); + st_cycles += miss_count_; + if (access_ena_b) { + st_cycles -= burst_reads * (access_pen_r - access_pen_b) + + burst_writes * (access_pen_w - access_pen_b); + } + return st_cycles; +} + +const TLBConfig &TLB::get_config() const { + return tlb_config; +} + +double TLB::get_speed_improvement() const { + unsigned comp = hit_count_ + miss_count_; + if (comp == 0) return 100.0; + uint32_t lookup_time = comp; + uint32_t mem_access_time = mem_reads * access_pen_r + mem_writes * access_pen_w; + if (access_ena_b) { + mem_access_time -= burst_reads * (access_pen_r - access_pen_b) + + burst_writes * (access_pen_w - access_pen_b); + } + double baseline_time = (double)comp * (double)access_pen_r; + double with_tlb_time = (double)lookup_time + (double)mem_access_time; + if (with_tlb_time == 0.0) return 100.0; + return (baseline_time / with_tlb_time) * 100.0; +} + +void TLB::update_all_statistics() { + emit statistics_update(get_stall_count(), get_speed_improvement(), get_hit_rate()); +} + +void TLB::reset() { + for (size_t s = 0; s < num_sets_; s++) { + for (size_t w = 0; w < associativity_; w++) { + auto &e = table[s][w]; + if (e.valid) { + uint16_t old_asid = e.asid; + uint64_t old_vpn = e.vpn; + e.valid = false; + emit tlb_update( + static_cast(w), static_cast(s), false, old_asid, old_vpn, + 0ull, false); + } + } + } + + hit_count_ = 0; + miss_count_ = 0; + mem_reads = 0; + mem_writes = 0; + burst_reads = 0; + burst_writes = 0; + change_counter = 0; + + emit hit_update(get_hit_count()); + emit miss_update(get_miss_count()); + emit memory_reads_update(get_read_count()); + emit memory_writes_update(get_write_count()); + update_all_statistics(); +} + +} // namespace machine diff --git a/src/machine/memory/tlb/tlb.h b/src/machine/memory/tlb/tlb.h new file mode 100644 index 00000000..4b2d089a --- /dev/null +++ b/src/machine/memory/tlb/tlb.h @@ -0,0 +1,126 @@ +#ifndef TLB_H +#define TLB_H + +#include "common/logging.h" +#include "memory/frontend_memory.h" +#include "memory/virtual/sv32.h" +#include "tlb_policy.h" +#include "memory/virtual/virtual_address.h" + +#include + +namespace machine { +enum TLBType { PROGRAM, DATA }; +class Machine; + +// Implements a set-associative Translation Lookaside Buffer (TLB) frontend over physical memory, +// handling virtual to physical translation, flush, and replacement policy. +class TLB : public FrontendMemory { + Q_OBJECT +public: + TLB( + FrontendMemory *memory, + TLBType type, + const TLBConfig *config, + bool vm_enabled = true, + uint32_t memory_access_penalty_r = 1, + uint32_t memory_access_penalty_w = 1, + uint32_t memory_access_penalty_b = 0, + bool memory_access_enable_b = false); + + void on_csr_write(size_t internal_id, RegisterValue val); + void flush_single(VirtualAddress va, uint16_t asid); + + void flush(); + + void sync() override; + + Address translate_virtual_to_physical(Address va); + + WriteResult write(Address dst, const void *src, size_t sz, WriteOptions opts) override { + return translate_and_write(dst, src, sz, opts); + } + ReadResult read(void *dst, Address src, size_t sz, ReadOptions opts) const override { + return const_cast(this)->translate_and_read(dst, src, sz, opts); + } + + uint32_t get_change_counter() const override { + return mem->get_change_counter(); + } + + void set_replacement_policy(std::unique_ptr p) { + repl_policy = std::move(p); + } + + uint32_t root_page_table_ppn() const { + return current_satp_raw & ((1u << PPN_BITS) - 1); + } + + bool reverse_lookup(Address paddr, VirtualAddress &out_va) const; + + unsigned get_hit_count() const { return hit_count_; } + unsigned get_miss_count() const { return miss_count_; } + double get_hit_rate() const; + uint32_t get_read_count() const { return mem_reads; } + uint32_t get_write_count() const { return mem_writes; } + uint32_t get_burst_read_count() const { return burst_reads; } + uint32_t get_burst_write_count() const { return burst_writes; } + + uint32_t get_stall_count() const; + double get_speed_improvement() const; + const TLBConfig &get_config() const; + + void reset(); + void update_all_statistics(); + +signals: + void tlb_update(unsigned way, unsigned set, bool valid, uint16_t asid, uint64_t vpn, uint64_t phys_base, bool is_write); + void hit_update(unsigned val); + void miss_update(unsigned val); + void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); + void memory_reads_update(uint32_t val); + void memory_writes_update(uint32_t val); + +private: + struct Entry { + bool valid = false; + uint16_t asid = 0; + uint64_t vpn = 0; + Address phys = Address{0}; + uint32_t lru = 0; + }; + + FrontendMemory *mem; + TLBType type; + const TLBConfig tlb_config; + uint32_t current_satp_raw = 0; + const bool vm_enabled; + + size_t num_sets_; + size_t associativity_; + std::vector> table; + std::unique_ptr repl_policy; + + const uint32_t access_pen_r; + const uint32_t access_pen_w; + const uint32_t access_pen_b; + const bool access_ena_b; + + mutable unsigned hit_count_ = 0; + mutable unsigned miss_count_ = 0; + mutable uint32_t mem_reads = 0; + mutable uint32_t mem_writes = 0; + mutable uint32_t burst_reads = 0; + mutable uint32_t burst_writes = 0; + mutable uint32_t change_counter = 0; + + WriteResult translate_and_write(Address dst, const void *src, size_t sz, WriteOptions opts); + ReadResult translate_and_read(void *dst, Address src, size_t sz, ReadOptions opts); + inline size_t set_index(uint64_t vpn) const { + return vpn & (num_sets_ - 1); + } +}; + +} + +#endif //TLB_H diff --git a/src/machine/memory/tlb/tlb_policy.cpp b/src/machine/memory/tlb/tlb_policy.cpp new file mode 100644 index 00000000..93c7eb3f --- /dev/null +++ b/src/machine/memory/tlb/tlb_policy.cpp @@ -0,0 +1,108 @@ +#include "tlb_policy.h" + +#include +#include +#include +#include + +namespace machine { + +TLBPolicyRAND::TLBPolicyRAND(size_t assoc) : associativity(assoc) { + std::srand(1); +} +size_t TLBPolicyRAND::select_way(size_t) const { + return std::rand() % associativity; +} +void TLBPolicyRAND::notify_access(size_t, size_t, bool) { + /* no state */ +} + +TLBPolicyLRU::TLBPolicyLRU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { + stats.resize(sets, std::vector(assoc)); + for (auto &row : stats) { + std::iota(row.begin(), row.end(), 0); + } +} +size_t TLBPolicyLRU::select_way(size_t set) const { + return stats[set][0]; +} +void TLBPolicyLRU::notify_access(size_t set, size_t way, bool valid) { + auto &row = stats[set]; + uint32_t next = way; + if (valid) { + for (int i = int(row.size()) - 1; i >= 0; --i) { + std::swap(row[i], next); + if (next == way) break; + } + } else { + for (unsigned int &i : row) { + std::swap(i, next); + if (next == way) break; + } + } +} + +TLBPolicyLFU::TLBPolicyLFU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { + stats.assign(sets, std::vector(assoc, 0)); +} +size_t TLBPolicyLFU::select_way(size_t set) const { + const auto &row = stats[set]; + size_t idx = 0; + uint32_t minv = row[0]; + for (size_t i = 1; i < row.size(); ++i) { + if (row[i] == 0 || row[i] < minv) { + minv = row[i]; + idx = i; + if (minv == 0) break; + } + } + return idx; +} +void TLBPolicyLFU::notify_access(size_t set, size_t way, bool valid) { + if (valid) { + stats[set][way]++; + } else { + stats[set][way] = 0; + } +} + +TLBPolicyPLRU::TLBPolicyPLRU(size_t assoc, size_t sets) + : associativity(assoc) + , set_count(sets) + , c_log2(std::ceil(std::log2(float(assoc)))) { + size_t tree_size = (1u << c_log2) - 1; + tree.assign(sets, std::vector(tree_size, 0)); +} +size_t TLBPolicyPLRU::select_way(size_t set) const { + const auto &bits = tree[set]; + size_t idx = 0; + size_t node = 0; + for (size_t lvl = 0; lvl < c_log2; ++lvl) { + uint8_t b = bits[node]; + idx = (idx << 1) | b; + node = ((1u << (lvl + 1)) - 1) + idx; + } + return std::min(idx, associativity - 1); +} +void TLBPolicyPLRU::notify_access(size_t set, size_t way, bool) { + auto &bits = tree[set]; + size_t node = 0; + for (size_t lvl = 0; lvl < c_log2; ++lvl) { + uint8_t dir = (way >> (c_log2 - lvl - 1)) & 1; + bits[node] = dir ? 0 : 1; + node = ((1u << (lvl + 1)) - 1) + ((dir ? 1 : 0)); + } +} + +std::unique_ptr +make_tlb_policy(TLBPolicyKind kind, size_t associativity, size_t set_count) { + switch (kind) { + case TLBPolicyKind::RAND: return std::make_unique(associativity); + case TLBPolicyKind::LRU: return std::make_unique(associativity, set_count); + case TLBPolicyKind::LFU: return std::make_unique(associativity, set_count); + case TLBPolicyKind::PLRU: return std::make_unique(associativity, set_count); + } + return std::make_unique(associativity, set_count); +} + +} // namespace machine diff --git a/src/machine/memory/tlb/tlb_policy.h b/src/machine/memory/tlb/tlb_policy.h new file mode 100644 index 00000000..cf81d80c --- /dev/null +++ b/src/machine/memory/tlb/tlb_policy.h @@ -0,0 +1,71 @@ +#ifndef TLB_POLICY_H +#define TLB_POLICY_H + +#include +#include +#include +#include + +namespace machine { + +// Abstract TLB replacement policy interface & implementations (RAND, LRU, LFU, PLRU) for set-associative tables. +class TLBPolicy { +public: + virtual size_t select_way(size_t set) const = 0; + + virtual void notify_access(size_t set, size_t way, bool valid) = 0; + + virtual ~TLBPolicy() = default; +}; + +class TLBPolicyRAND final : public TLBPolicy { + size_t associativity; + +public: + explicit TLBPolicyRAND(size_t assoc); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + + +class TLBPolicyLRU final : public TLBPolicy { + size_t associativity; + size_t set_count; + std::vector> stats; +public: + TLBPolicyLRU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +class TLBPolicyLFU final : public TLBPolicy { + size_t associativity; + size_t set_count; + std::vector> stats; +public: + TLBPolicyLFU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +class TLBPolicyPLRU final : public TLBPolicy { + size_t associativity; + size_t set_count; + size_t c_log2; + std::vector> tree; +public: + TLBPolicyPLRU(size_t assoc, size_t sets); + size_t select_way(size_t set) const override; + void notify_access(size_t set, size_t way, bool valid) override; +}; + +enum class TLBPolicyKind { RAND, LRU, LFU, PLRU }; +std::unique_ptr make_tlb_policy( + TLBPolicyKind kind, + size_t associativity, + size_t set_count +); + +} + +#endif // TLB_POLICY_H diff --git a/src/machine/memory/virtual/page_table_walker.cpp b/src/machine/memory/virtual/page_table_walker.cpp new file mode 100644 index 00000000..da22b3a5 --- /dev/null +++ b/src/machine/memory/virtual/page_table_walker.cpp @@ -0,0 +1,54 @@ +#include "page_table_walker.h" + +#include "common/logging.h" +#include "machine.h" +#include "memory/backend/backend_memory.h" + +#include + +LOG_CATEGORY("machine.PTW"); + +namespace machine { + +Address PageTableWalker::walk(const VirtualAddress &va, uint32_t raw_satp) const { + if (((raw_satp >> 31) & 1) == 0) return Address { va.get_raw() }; + + auto root_ppn = raw_satp & ((1u << 22) - 1); + auto va_raw = static_cast(va.get_raw()); + auto vpn1 = (va_raw >> VPN1_SHIFT) & VPN_MASK; + auto vpn0 = (va_raw >> VPN0_SHIFT) & VPN_MASK; + auto ppn = root_ppn; + + for (int lvl = 1; lvl >= 0; --lvl) { + uint32_t idx = (lvl == 1 ? vpn1 : vpn0); + Address pte_addr { (uint64_t(ppn) << PAGE_SHIFT) + idx * 4 }; + + uint32_t raw_pte; + memory->read(&raw_pte, pte_addr, sizeof(raw_pte), { .type = ae::INTERNAL }); + LOG("PTW: L%u PTE@0x%08" PRIx64 " = 0x%08x", lvl, pte_addr.get_raw(), raw_pte); + + Sv32Pte pte = Sv32Pte::from_uint(raw_pte); + + if (!pte.is_valid()) { + throw SIMULATOR_EXCEPTION( + PageFault, "PTW: page fault, leaf PTE invalid", + QString::number(pte_addr.get_raw(), 16)); + } + + if (pte.is_leaf()) { + Address pa = make_phys(va_raw, pte, lvl); + LOG("PTW: L%u leaf → PA=0x%08" PRIx64, lvl, pa.get_raw()); + return pa; + } + + if (pte.r() || pte.w() || pte.x()) { + throw SIMULATOR_EXCEPTION( + PageFault, "PTW: invalid non-leaf", QString::number(raw_pte, 16)); + } + ppn = unsigned(pte.ppn()); + } + + throw SIMULATOR_EXCEPTION(PageFault, "PTW: no leaf found", ""); +} + +} // namespace machine \ No newline at end of file diff --git a/src/machine/memory/virtual/page_table_walker.h b/src/machine/memory/virtual/page_table_walker.h new file mode 100644 index 00000000..8d1d5ddc --- /dev/null +++ b/src/machine/memory/virtual/page_table_walker.h @@ -0,0 +1,25 @@ +#ifndef PAGE_TABLE_WALKER_H +#define PAGE_TABLE_WALKER_H + +#include "memory/frontend_memory.h" +#include "sv32.h" +#include "virtual_address.h" + +#include + +namespace machine { + +// Performs multi-level page-table walks (SV32) in memory to resolve a virtual address to a physical one. +class PageTableWalker { +public: + explicit PageTableWalker(FrontendMemory *mem) : memory(mem) {} + + Address walk(const VirtualAddress &va, uint32_t raw_satp) const; + +private: + FrontendMemory *memory; +}; + +} + +#endif //PAGE_TABLE_WALKER_H \ No newline at end of file diff --git a/src/machine/memory/virtual/sv32.h b/src/machine/memory/virtual/sv32.h new file mode 100644 index 00000000..e1efb4b6 --- /dev/null +++ b/src/machine/memory/virtual/sv32.h @@ -0,0 +1,123 @@ +#ifndef SV32_H +#define SV32_H + +// SV32-specific definitions: page-table entry (PTE) bitfields, shifts/masks, and PTE to physical address helpers. +// This header documents the SV32 layout (RISC-V 32-bit virtual memory): +// - Page size: 4 KiB (PAGE_SHIFT = 12). +// - Virtual page number (VPN) is split into two 10-bit indices VPN[1] and VPN[0]. +// - PTE low bits encode flags and low-order info; high bits encode the physical +// page number (PPN). +namespace machine { + static constexpr unsigned PAGE_SHIFT = 12; // Page size = 2^PAGE_SHIFT bytes. For SV32 this is 4 KiB. + static constexpr unsigned VPN_BITS = 10; // Number of bits for each VPN level in SV32: 10 bits for VPN[1] and 10 bits for VPN[0]. + + // Shift values for extracting VPN parts from a virtual address: + static constexpr unsigned VPN0_SHIFT = PAGE_SHIFT; // VPN0 is the low VPN field, it starts at the page offset (PAGE_SHIFT). + static constexpr unsigned VPN1_SHIFT = PAGE_SHIFT + VPN_BITS; // VPN1 is the next field above VPN0. + + static constexpr unsigned VPN_MASK = (1u << VPN_BITS) - 1; // Mask to extract a single VPN level (10 bits, value 0..1023). + + // Number of bits available for the physical page number (PPN) in SV32 PTE. + // SV32 uses 22 PPN bits (bits 10..31 of a 32-bit PTE) which permits addressing up to 4GiB of physical memory. + static constexpr unsigned PPN_BITS = 22; + static constexpr unsigned PPN_MASK = (1u << PPN_BITS) - 1; + + static constexpr uint64_t PHYS_PPN_START = 0x200; // I have noticed that programs are loaded into memory starting at 0x200. + + // Sv32Pte wraps the raw 32-bit PTE value and provides helpers to read flags and fields. + // Layout (bit indices): + // 0 V (valid) + // 1 R (read) + // 2 W (write) + // 3 X (execute) + // 4 U (user) + // 5 G (global) + // 6 A (accessed) + // 7 D (dirty) + // 8..9 RSW (reserved for supervisor use) + // 10..31 PPN (physical page number) + // + // A PTE is considered a "leaf" when it grants read or execute permission (R or X). + // Validation rules: PTE is valid if V==1 and (if W==1 then R must also be 1). + struct Sv32Pte { + uint64_t raw = 0; + + // Bit positions (shifts) for the fields described above. + static constexpr unsigned V_SHIFT = 0; + static constexpr unsigned R_SHIFT = 1; + static constexpr unsigned W_SHIFT = 2; + static constexpr unsigned X_SHIFT = 3; + static constexpr unsigned U_SHIFT = 4; + static constexpr unsigned G_SHIFT = 5; + static constexpr unsigned A_SHIFT = 6; + static constexpr unsigned D_SHIFT = 7; + static constexpr unsigned RSW_SHIFT = 8; // two bits: 8..9 + static constexpr unsigned PPN_SHIFT = 10; // PPN starts at bit 10 + + // Masks for each single-bit flag + static constexpr uint64_t V_MASK = (1u << V_SHIFT); + static constexpr uint64_t R_MASK = (1u << R_SHIFT); + static constexpr uint64_t W_MASK = (1u << W_SHIFT); + static constexpr uint64_t X_MASK = (1u << X_SHIFT); + static constexpr uint64_t U_MASK = (1u << U_SHIFT); + static constexpr uint64_t G_MASK = (1u << G_SHIFT); + static constexpr uint64_t A_MASK = (1u << A_SHIFT); + static constexpr uint64_t D_MASK = (1u << D_SHIFT); + + // Mask for the 2-bit RSW field (bits 8..9). + static constexpr uint64_t RSW_MASK = (0x3u << RSW_SHIFT); + + // Mask that selects the PPN field within the raw PTE (bits 10..(10 + PPN_BITS - 1)) + static constexpr uint64_t PPN_MASK32 = ((PPN_MASK) << PPN_SHIFT); + + constexpr Sv32Pte() = default; + constexpr explicit Sv32Pte(uint64_t raw_) : raw(raw_) {} + + constexpr uint64_t to_uint() const { return raw; } + static constexpr Sv32Pte from_uint(uint64_t r) { return Sv32Pte(r); } + + // Flag accessors + constexpr bool v() const noexcept { return (raw >> V_SHIFT) & 0x1u; } + constexpr bool r() const noexcept { return (raw >> R_SHIFT) & 0x1u; } + constexpr bool w() const noexcept { return (raw >> W_SHIFT) & 0x1u; } + constexpr bool x() const noexcept { return (raw >> X_SHIFT) & 0x1u; } + constexpr bool u() const noexcept { return (raw >> U_SHIFT) & 0x1u; } + constexpr bool g() const noexcept { return (raw >> G_SHIFT) & 0x1u; } + constexpr bool a() const noexcept { return (raw >> A_SHIFT) & 0x1u; } + constexpr bool d() const noexcept { return (raw >> D_SHIFT) & 0x1u; } + constexpr uint64_t rsw() const noexcept { return (raw >> RSW_SHIFT) & 0x3u; } + constexpr uint64_t ppn() const noexcept { return (raw >> PPN_SHIFT) & PPN_MASK; } + + // Convenience methods used by the page-table walker + bool is_leaf() const noexcept { return r() || x(); } + bool is_valid() const noexcept { return v() && (!w() || r()); } + + // Helper to construct a PTE from fields. + static constexpr Sv32Pte make(bool v_, bool r_, bool w_, bool x_, bool u_, bool g_, bool a_, bool d_, uint64_t rsw_, uint64_t ppn_) { + uint64_t r = 0; + r |= (uint64_t(v_) << V_SHIFT); + r |= (uint64_t(r_) << R_SHIFT); + r |= (uint64_t(w_) << W_SHIFT); + r |= (uint64_t(x_) << X_SHIFT); + r |= (uint64_t(u_) << U_SHIFT); + r |= (uint64_t(g_) << G_SHIFT); + r |= (uint64_t(a_) << A_SHIFT); + r |= (uint64_t(d_) << D_SHIFT); + r |= ((rsw_ & 0x3u) << RSW_SHIFT); + r |= ((ppn_ & PPN_MASK) << PPN_SHIFT); + return Sv32Pte(r); + } + }; + + inline Address make_phys(uint64_t va_raw, const Sv32Pte &pte, int level) { + uint64_t offset = va_raw & ((1u << PAGE_SHIFT) - 1); + uint64_t phys_ppn = pte.ppn(); + if (level == 1) { + uint64_t vpn0 = (va_raw >> PAGE_SHIFT) & VPN_MASK; + phys_ppn = (phys_ppn & ~VPN_MASK) | vpn0; + } + return Address { (uint64_t(phys_ppn) << PAGE_SHIFT) | offset }; + } +} + +#endif //SV32_H diff --git a/src/machine/memory/virtual/virtual_address.h b/src/machine/memory/virtual/virtual_address.h new file mode 100644 index 00000000..f0ad8119 --- /dev/null +++ b/src/machine/memory/virtual/virtual_address.h @@ -0,0 +1,173 @@ +#ifndef VIRTUAL_ADDRESS_H +#define VIRTUAL_ADDRESS_H + +#include "utils.h" + +#include +#include + +using std::uint64_t; + +namespace machine { + +// Lightweight VirtualAddress wrapper offering raw access, alignment checks, arithmetic, and comparisons. +class VirtualAddress { +private: + uint64_t data; // Raw virtual address + +public: + constexpr explicit VirtualAddress(uint64_t); + + constexpr VirtualAddress(); + + constexpr VirtualAddress(const VirtualAddress &address) = default; + constexpr VirtualAddress &operator=(const VirtualAddress &address) = default; + + [[nodiscard]] constexpr uint64_t get_raw() const; + + constexpr explicit operator uint64_t() const; + + constexpr static VirtualAddress null(); + + [[nodiscard]] constexpr bool is_null() const; + + template + [[nodiscard]] constexpr bool is_aligned() const; + + /* Equality */ + constexpr inline bool operator==(const VirtualAddress &other) const; + constexpr inline bool operator!=(const VirtualAddress &other) const; + + /* Ordering */ + constexpr inline bool operator<(const VirtualAddress &other) const; + constexpr inline bool operator>(const VirtualAddress &other) const; + constexpr inline bool operator<=(const VirtualAddress &other) const; + constexpr inline bool operator>=(const VirtualAddress &other) const; + + /* Offset arithmetic */ + constexpr inline VirtualAddress operator+(const uint64_t &offset) const; + constexpr inline VirtualAddress operator-(const uint64_t &offset) const; + inline void operator+=(const uint64_t &offset); + inline void operator-=(const uint64_t &offset); + + /* Bitwise */ + constexpr inline VirtualAddress operator&(const uint64_t &mask) const; + constexpr inline VirtualAddress operator|(const uint64_t &mask) const; + constexpr inline VirtualAddress operator^(const uint64_t &mask) const; + constexpr inline VirtualAddress operator>>(const uint64_t &size) const; + constexpr inline VirtualAddress operator<<(const uint64_t &size) const; + + /* Distance arithmetic */ + constexpr inline int64_t operator-(const VirtualAddress &other) const; +}; + +constexpr VirtualAddress operator"" _vaddr(unsigned long long literal) { + return VirtualAddress(literal); +} + +// Implementations + +constexpr VirtualAddress::VirtualAddress(uint64_t address) : data(address) {} + +constexpr VirtualAddress::VirtualAddress() : data(0) {} + +constexpr uint64_t VirtualAddress::get_raw() const { + return data; +} + +constexpr VirtualAddress::operator uint64_t() const { + return this->get_raw(); +} + +constexpr VirtualAddress VirtualAddress::null() { + return VirtualAddress(0x0); +} + +constexpr bool VirtualAddress::is_null() const { + return this->get_raw() == 0; +} + +template +constexpr bool VirtualAddress::is_aligned() const { + return is_aligned_genericdata), T>(this->data); +} + +/* Equality */ + +constexpr bool VirtualAddress::operator==(const VirtualAddress &other) const { + return this->get_raw() == other.get_raw(); +} + +constexpr bool VirtualAddress::operator!=(const VirtualAddress &other) const { + return this->get_raw() != other.get_raw(); +} + +/* Ordering */ + +constexpr bool VirtualAddress::operator<(const VirtualAddress &other) const { + return this->get_raw() < other.get_raw(); +} + +constexpr bool VirtualAddress::operator>(const VirtualAddress &other) const { + return this->get_raw() > other.get_raw(); +} + +constexpr bool VirtualAddress::operator<=(const VirtualAddress &other) const { + return this->get_raw() <= other.get_raw(); +} + +constexpr bool VirtualAddress::operator>=(const VirtualAddress &other) const { + return this->get_raw() >= other.get_raw(); +} + +/* Offset arithmetic */ + +constexpr VirtualAddress VirtualAddress::operator+(const uint64_t &offset) const { + return VirtualAddress(this->get_raw() + offset); +} + +constexpr VirtualAddress VirtualAddress::operator-(const uint64_t &offset) const { + return VirtualAddress(this->get_raw() - offset); +} + +void VirtualAddress::operator+=(const uint64_t &offset) { + data += offset; +} + +void VirtualAddress::operator-=(const uint64_t &offset) { + data -= offset; +} + +/* Bitwise */ + +constexpr VirtualAddress VirtualAddress::operator&(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() & mask); +} + +constexpr VirtualAddress VirtualAddress::operator|(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() | mask); +} + +constexpr VirtualAddress VirtualAddress::operator^(const uint64_t &mask) const { + return VirtualAddress(this->get_raw() ^ mask); +} + +constexpr VirtualAddress VirtualAddress::operator>>(const uint64_t &size) const { + return VirtualAddress(this->get_raw() >> size); +} + +constexpr VirtualAddress VirtualAddress::operator<<(const uint64_t &size) const { + return VirtualAddress(this->get_raw() << size); +} + +/* Distance arithmetic */ + +constexpr int64_t VirtualAddress::operator-(const VirtualAddress &other) const { + return this->get_raw() - other.get_raw(); +} + +} + +Q_DECLARE_METATYPE(machine::VirtualAddress) + +#endif // VIRTUAL_ADDRESS_H diff --git a/src/machine/simulator_exception.h b/src/machine/simulator_exception.h index cc69982e..4b655f51 100644 --- a/src/machine/simulator_exception.h +++ b/src/machine/simulator_exception.h @@ -48,6 +48,17 @@ class SimulatorException : public std::exception { * As we are simulating whole 32bit memory address space then this is most * probably QtRvSim bug if raised not program. Sanity: This is sanity check * exception + * PageFault: + * A page-fault occurs during virtual memory translation when the requested + * access cannot be satisfied. Typical causes: + * - No PTE present for the accessed virtual page (page not mapped). + * - PTE is present but not valid (V == 0). + * - Permissions violation (access type not allowed by PTE: read/write/execute). + * - Malformed or unexpected PTE contents (e.g. non-leaf with R/W/X set). + * - Wrong privilege level or ASID mismatch. + * PageFault is a runtime exception raised by the page-table walker and + * is recoverable after the page-fault handler allocates pages or installs + * mappings (demand paging). */ #define SIMULATOR_EXCEPTIONS \ EXCEPTION(Input, ) \ @@ -58,6 +69,7 @@ class SimulatorException : public std::exception { EXCEPTION(UnalignedJump, Runtime) \ EXCEPTION(UnknownMemoryControl, Runtime) \ EXCEPTION(OutOfMemoryAccess, Runtime) \ + EXCEPTION(PageFault, Runtime) \ EXCEPTION(Sanity, ) \ EXCEPTION(SyscallUnknown, Runtime) diff --git a/tests/cli/stalls/stdout.txt b/tests/cli/stalls/stdout.txt index d63a1a66..4019e4ab 100644 --- a/tests/cli/stalls/stdout.txt +++ b/tests/cli/stalls/stdout.txt @@ -2,4 +2,4 @@ Machine stopped on BREAK exception. Machine state report: PC:0x00000244 R0:0x00000000 R1:0x00000011 R2:0x00000022 R3:0x00000033 R4:0x00000000 R5:0x00000055 R6:0x00000000 R7:0x00000000 R8:0x00000000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000011 R22:0x00000022 R23:0x00000033 R24:0x00000044 R25:0x00000055 R26:0x00000000 R27:0x00000000 R28:0x00000000 R29:0x00000000 R30:0x00000000 R31:0x00000000 -cycle: 0x0000000c mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000000 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0x00000240 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000000c minstret: 0x0000000b +cycle: 0x0000000c mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000000 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0x00000240 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000000c minstret: 0x0000000b sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x00000000 diff --git a/tests/vm_assmebly/vm_template.s b/tests/vm_assmebly/vm_template.s new file mode 100644 index 00000000..698e64a0 --- /dev/null +++ b/tests/vm_assmebly/vm_template.s @@ -0,0 +1,116 @@ +// Test template: Sets up a page table, enables virtual memory, and prints "Hello world" via serial port. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build a leaf PTE value in t1: + // Take VA >> 12 (remove page offset) then shift left 10 to position PPN bits for a PTE, + // then OR in the PTE flags. + srli t1, t4, 12 // t1 = MAP_VA >> 12 (page number) + slli t1, t1, 10 // t1 <<= 10 to position as PPN bits for a PTE entry + li t6, PTE_FLAGS_FULL // t6 = flags + or t1, t1, t6 // t1 = (PPN << 10) | PTE_FLAGS_FULL + + // Calculate the root page table entry index for the high VPN (VPN[1]): + // t5 = MAP_VA >> 22 (VPN[1]) + // t2 = t5 << 2 (multiply by 4 bytes per PTE to get byte offset) + // t3 = root_pt_phys + offset (address of PTE in root page table) + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + + // Store the constructed PTE into the root page table (making a mapping) + sw t1, 0(t3) + fence + + // Ensure satp is cleared before setting new value (flush previous translations) + li t0, 0 + csrw satp, t0 + + // Enable the MMU by writing SATP; this switches address translation on + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus MPP so that mret will return to Supervisor mode: + // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP + + // Set mepc to the virtual address of vm_entry and return from machine mode to + // the prepared privilege level (Supervisor) using mret. + la t0, vm_entry // load address of vm_entry (virtual address after mapping) + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li a0, SERIAL_PORT_BASE + la a1, hello_str + +print_next_char: + // Load next byte from string; if zero (end), branch to done + lb t1, 0(a1) + beq t1, zero, print_done + addi a1, a1, 1 // advance to next character + +wait_tx_ready: + // Poll transmit status register until TX ready bit is set + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx_ready + + // Write byte to transmit-data register and loop for next char + sw t1, SERP_TX_DATA_REG_o(a0) + jal zero, print_next_char + +print_done: + ebreak + +1: auipc t0, 0 + jalr zero, 0(t0) + +.data +.org 0xC4000100 +hello_str: + .asciz "Hello world.\n" \ No newline at end of file diff --git a/tests/vm_assmebly/vm_test_dtlb.s b/tests/vm_assmebly/vm_test_dtlb.s new file mode 100644 index 00000000..283edb43 --- /dev/null +++ b/tests/vm_assmebly/vm_test_dtlb.s @@ -0,0 +1,115 @@ +// D-TLB test — map data pages at MAP_VA; write 'A'..'H' to the first byte of eight pages and read/print them. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Compute level-1 table index for MAP_VA and form address of PTE in root page table. + // srli/slli extracts bits for index and scales by 4 (word-sized PTEs). + srli t3, t2, 22 + slli t3, t3, 2 + add t4, t0, t3 + + // Build leaf PTE: physical page number (vpn->ppn shift) | flags. + // srli/slli moves MAP_VA to form the physical page number field in the PTE. + srli t5, t2, 12 + slli t5, t5, 10 + li t6, PTE_FLAGS_FULL + or t5, t5, t6 + sw t5, 0(t4) + fence + + // Enable paging: write SATP with implementation-specific value. + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP so mret will return to Supervisor (set bits appropriately). + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + // Set mepc to vm_entry and enter Supervisor with mret. + la t0, vm_entry + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li t0, SERIAL_PORT_BASE + la t1, MAP_VA // pointer to start of mapped virtual region + li t2, 0 // page counter + li t3, 8 // number of pages to write/read (A..H) + li t4, 65 // ASCII 'A' + li t5, 0x1000 // page size (4KB) + +// write_pages_loop: write one byte (A..H) at the start of each mapped page. +write_pages_loop: + sb t4, 0(t1) + add t1, t1, t5 + addi t4, t4, 1 + addi t2, t2, 1 + blt t2, t3, write_pages_loop + + // Reset pointer and counter to read back and print the first byte of each page. + la t1, MAP_VA + li t2, 0 + +read_print_loop: + lb t6, 0(t1) // load the byte stored at start of current page + + // wait_tx: poll transmitter status until ready, then write byte to TX data reg. +wait_tx: + lw t4, SERP_TX_ST_REG_o(t0) + andi t4, t4, SERP_TX_ST_REG_READY_m + beq t4, zero, wait_tx + sw t6, SERP_TX_DATA_REG_o(t0) + + add t1, t1, t5 + addi t2, t2, 1 + blt t2, t3, read_print_loop + + ebreak + +1: auipc t0, 0 + jalr zero, 0(t0) diff --git a/tests/vm_assmebly/vm_test_exec.s b/tests/vm_assmebly/vm_test_exec.s new file mode 100644 index 00000000..8d6cbf73 --- /dev/null +++ b/tests/vm_assmebly/vm_test_exec.s @@ -0,0 +1,93 @@ +// Place a tiny function in the mapped virtual page and jump to it (tests X bit). + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build leaf PTE + srli t1, t4, 12 + slli t1, t1, 10 + li t6, PTE_FLAGS_FULL + or t1, t1, t6 + + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + sw t1, 0(t3) + fence + + // Enable SATP + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP to return to Supervisor + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + la t0, vm_entry + csrw mepc, t0 + mret + +.org 0xC4000000 +.text +vm_entry: + li a0, SERIAL_PORT_BASE + + // Call the function placed in the same mapped page + la t0, mapped_function + jalr zero, 0(t0) + + ebreak + +// small function placed in the mapped page +.org 0xC4000100 +mapped_function: + li a0, SERIAL_PORT_BASE + li t1, 88 +wait_tx: + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx + sw t1, SERP_TX_DATA_REG_o(a0) + ebreak diff --git a/tests/vm_assmebly/vm_test_itlb.s b/tests/vm_assmebly/vm_test_itlb.s new file mode 100644 index 00000000..c345b268 --- /dev/null +++ b/tests/vm_assmebly/vm_test_itlb.s @@ -0,0 +1,198 @@ +// I-TLB test — map code pages at MAP_VA and execute tiny functions placed on eight +// pages to print A–H, exercising instruction fetch from different pages. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Compute root page table entry location for MAP_VA and write a leaf PTE. + srli t3, t2, 22 + slli t3, t3, 2 + add t4, t0, t3 + + // Build leaf PTE: PPN from MAP_VA + full flags and store into root PT. + srli t5, t2, 12 + slli t5, t5, 10 + li t6, PTE_FLAGS_FULL + or t5, t5, t6 + sw t5, 0(t4) + fence + + // Enable SATP: construct satp value from ROOT_PT_PHYS (ppn) and MODE bit(s), + // then write to satp and fence. This form shifts root physical >> 12 and ORs + // with the mode bit mask (implementation-specific). + li t0, ROOT_PT_PHYS + srli t0, t0, 12 + li t1, 0x80000000 + or t0, t0, t1 + csrw satp, t0 + fence + + // Prepare mstatus.MPP so mret will return to Supervisor. + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + // Set mepc to vm_entry and enter Supervisor with mret. + la t0, vm_entry + csrw mepc, t0 + mret + + +.org 0xC4007000 +.text +vm_entry: + li t0, SERIAL_PORT_BASE + li t4, 65 // 'A' + + // print_self: print current char, then step through executing code on other mapped pages. +print_self: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, print_self + sw t4, SERP_TX_DATA_REG_o(t0) + addi t4, t4, 1 + + // Execute code placed in separate mapped pages. Each page contains a tiny + // function that prints the current character then returns using the address + // stored in t3. The sequence tests I-TLB / instruction fetch of different pages. + la t1, page_func_0 + la t3, resume_0 + jalr zero, 0(t1) +resume_0: + addi t4, t4, 1 + + la t1, page_func_1 + la t3, resume_1 + jalr zero, 0(t1) +resume_1: + addi t4, t4, 1 + + la t1, page_func_2 + la t3, resume_2 + jalr zero, 0(t1) +resume_2: + addi t4, t4, 1 + + la t1, page_func_3 + la t3, resume_3 + jalr zero, 0(t1) +resume_3: + addi t4, t4, 1 + + la t1, page_func_4 + la t3, resume_4 + jalr zero, 0(t1) +resume_4: + addi t4, t4, 1 + + la t1, page_func_5 + la t3, resume_5 + jalr zero, 0(t1) +resume_5: + addi t4, t4, 1 + + la t1, page_func_6 + la t3, resume_6 + jalr zero, 0(t1) +resume_6: + addi t4, t4, 1 + + ebreak + + +.org 0xC4000000 +page_func_0: + // Each page_func_* polls transmitter, writes current char (t4) then jumps + // back to the resume address stored in t3. These functions live on + // separate mapped pages to exercise instruction fetch from different pages. + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_0 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4001000 +page_func_1: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_1 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4002000 +page_func_2: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_2 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4003000 +page_func_3: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_3 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4004000 +page_func_4: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_4 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4005000 +page_func_5: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_5 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) + +.org 0xC4006000 +page_func_6: + lw t6, SERP_TX_ST_REG_o(t0) + andi t6, t6, SERP_TX_ST_REG_READY_m + beq t6, zero, page_func_6 + sw t4, SERP_TX_DATA_REG_o(t0) + jalr zero, 0(t3) \ No newline at end of file diff --git a/tests/vm_assmebly/vm_test_memrw.s b/tests/vm_assmebly/vm_test_memrw.s new file mode 100644 index 00000000..537791c3 --- /dev/null +++ b/tests/vm_assmebly/vm_test_memrw.s @@ -0,0 +1,114 @@ +// Write ASCII bytes into the mapped virtual page and read them back printing to verify. + +.globl _start +.option norelax + +// Serial port/terminal registers +.equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region +.equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG +.equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG +.equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG +.equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte +.equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG + +.equ ROOT_PT_PHYS, 0x1000 +.equ MAP_VA, 0xC4000000 + +// PTE flags: 0xCF = 11001111 +// bit0 V = 1 (Valid) +// bit1 R = 1 (Readable) +// bit2 W = 1 Writable) +// bit3 X = 1 (Executable) +// bit4 U = 0 (NOT user-accessible) +// bit5 G = 0 (NOT global) +// bit6 A = 1 (Accessed) +// bit7 D = 1 (Dirty) +.equ PTE_FLAGS_FULL, 0xCF + +// mstatus MPP manipulation masks (for preparing mret to change privilege) +.equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) +.equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) + +.equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) + +.org 0x00000200 +.text +_start: + // t0 = physical address of root page table + li t0, ROOT_PT_PHYS + + // t4 = virtual address we want to map (MAP_VA) + li t4, MAP_VA + + // Build leaf PTE + srli t1, t4, 12 + slli t1, t1, 10 + li t6, PTE_FLAGS_FULL + or t1, t1, t6 + + srli t5, t4, 22 + slli t2, t5, 2 + add t3, t0, t2 + sw t1, 0(t3) + fence + + // Enable SATP + li t0, 0 + csrw satp, t0 + li t0, SATP_ENABLE + csrw satp, t0 + fence + + // Prepare mstatus.MPP to return to Supervisor + li t0, MSTATUS_MPP_CLEAR + csrrc zero, mstatus, t0 + li t0, MSTATUS_MPP_SET + csrrs zero, mstatus, t0 + + la t0, vm_entry + csrw mepc, t0 + mret + + .org 0xC4000000 + .text +vm_entry: + li a0, SERIAL_PORT_BASE + + // pointer to mapped virtual page + la a1, MAP_VA + + // write ASCII letters A..H into the first 8 bytes + li t0, 65 + sb t0, 0(a1) + li t0, 66 + sb t0, 1(a1) + li t0, 67 + sb t0, 2(a1) + li t0, 68 + sb t0, 3(a1) + li t0, 69 + sb t0, 4(a1) + li t0, 70 + sb t0, 5(a1) + li t0, 71 + sb t0, 6(a1) + li t0, 72 + sb t0, 7(a1) + + // Now read back and print each byte + li t5, 0 +read_print_loop: + lb t1, 0(a1) + // print t1 +wait_tx2: + lw t0, SERP_TX_ST_REG_o(a0) + andi t0, t0, SERP_TX_ST_REG_READY_m + beq t0, zero, wait_tx2 + sw t1, SERP_TX_DATA_REG_o(a0) + + addi a1, a1, 1 + addi t5, t5, 1 + li t6, 8 + blt t5, t6, read_print_loop + + ebreak \ No newline at end of file