From 5feb16d0738f57d220e5f2e6ba85e072c22135ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 17 Jan 2018 14:47:18 +0100 Subject: Update how configuration is handled in newdialog In previous implementation were dependencies described on two places. In NewDialog and in MachineConfig. Now NewDialog sets options in MachineConfig and configuration is then applied to NewDialog. --- qtmips_gui/NewDialog.ui | 31 ++++++- qtmips_gui/NewDialogCache.ui | 169 ++++++++++++++++++++++++---------- qtmips_gui/newdialog.cpp | 190 +++++++++++++++++++++++++------------- qtmips_gui/newdialog.h | 15 ++- qtmips_machine/machineconfig.cpp | 192 ++++++++++++++++++++++++++++++++++----- qtmips_machine/machineconfig.h | 50 ++++++++-- 6 files changed, 507 insertions(+), 140 deletions(-) diff --git a/qtmips_gui/NewDialog.ui b/qtmips_gui/NewDialog.ui index a39a014..0490ca3 100644 --- a/qtmips_gui/NewDialog.ui +++ b/qtmips_gui/NewDialog.ui @@ -37,7 +37,7 @@ - + Preset @@ -197,6 +197,35 @@ + + + + Access time (in cycles) + + + + + + Read: + + + + + + + + + + Write: + + + + + + + + + diff --git a/qtmips_gui/NewDialogCache.ui b/qtmips_gui/NewDialogCache.ui index 3c91824..04f15f7 100644 --- a/qtmips_gui/NewDialogCache.ui +++ b/qtmips_gui/NewDialogCache.ui @@ -6,63 +6,140 @@ 0 0 - 339 - 167 + 435 + 272 Form - - - - - Capacity: + + + + + - - - - - - - - - Number of sets: + + true + + + + + Capacity: + + + + + + + + + + Number of sets: + + + + + + + + + + Block size: + + + + + + + + + + Number of blocks: + + + + + + + + + + Degree of associativity: + + + + + + + + + + Replacement policy: + + + + + + + Writeback policy: + + + + + + + + Random + + + + + Least Recently Used (LRU) + + + + + Least Frequently Used (LFU) + + + + + Adaptive Replacement Cache (ARC) + + + + + + + + + Write trough + + + + + Write back + + + + + - - - - Block size: + + + + Qt::Vertical - - - - - - Number of blocks: + + + 20 + 40 + - - - - - - Degree of associativity: - - - - - - - - - - - - - - + diff --git a/qtmips_gui/newdialog.cpp b/qtmips_gui/newdialog.cpp index 6aec98a..033d0ce 100644 --- a/qtmips_gui/newdialog.cpp +++ b/qtmips_gui/newdialog.cpp @@ -5,32 +5,35 @@ NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { setWindowTitle("New machine"); + this->settings = settings; + config = nullptr; + ui = new Ui::NewDialog(); ui->setupUi(this); ui_cache_p = new Ui::NewDialogCache(); ui_cache_p->setupUi(ui->tab_cache_program); ui_cache_d = new Ui::NewDialogCache(); ui_cache_d->setupUi(ui->tab_cache_data); - // TODO setup more? settings and configuration pull - - this->settings = settings; - - QObject::connect(ui->pushButton_load, SIGNAL(clicked(bool)), this, SLOT(create())); - QObject::connect(ui->pushButton_cancel, SIGNAL(clicked(bool)), this, SLOT(cancel())); - // Signals on Base tab - QObject::connect(ui->preset_no_pipeline, SIGNAL(toggled(bool)), this, SLOT(preset(bool))); - QObject::connect(ui->preset_pipelined, SIGNAL(toggled(bool)), this, SLOT(preset(bool))); - QObject::connect(ui->pushButton_browse, SIGNAL(clicked(bool)), this, SLOT(browse_elf())); -#define CUSTOM_PRESET(UI) QObject::connect(UI, SIGNAL(clicked(bool)), this, SLOT(set_custom_preset())) - // Signals on Core tab - CUSTOM_PRESET(ui->pipelined); - // Signals on Memory tab - CUSTOM_PRESET(ui->mem_protec_write); - CUSTOM_PRESET(ui->mem_protec_exec); -#undef CUSTOM_PRESET - // Load setting after signals are configured so that we can have correct settings - load_settings(); + connect(ui->pushButton_load, SIGNAL(clicked(bool)), this, SLOT(create())); + connect(ui->pushButton_cancel, SIGNAL(clicked(bool)), this, SLOT(cancel())); + connect(ui->pushButton_browse, SIGNAL(clicked(bool)), this, SLOT(browse_elf())); + //connect(ui->preset_box, SIGNAL(clicked(bool)), this, SLOT(set_preset())); + connect(ui->preset_no_pipeline, SIGNAL(toggled(bool)), this, SLOT(set_preset())); + connect(ui->preset_pipelined, SIGNAL(toggled(bool)), this, SLOT(set_preset())); + + connect(ui->pipelined, SIGNAL(clicked(bool)), this, SLOT(pipelined_change(bool))); + connect(ui->delay_slot, SIGNAL(clicked(bool)), this, SLOT(delay_slot_change(bool))); + connect(ui->hazard_unit, SIGNAL(clicked(bool)), this, SLOT(hazard_unit_change())); + connect(ui->hazard_stall, SIGNAL(clicked(bool)), this, SLOT(hazard_unit_change())); + connect(ui->hazard_stall_forward, SIGNAL(clicked(bool)), this, SLOT(hazard_unit_change())); + connect(ui->mem_protec_exec, SIGNAL(clicked(bool)), this, SIGNAL(mem_protec_exec_change(bool))); + connect(ui->mem_protec_write, SIGNAL(clicked(bool)), this, SLOT(mem_protec_write_change(bool))); + + connect(ui_cache_p->enabled, SIGNAL(clicked(bool)), this, SLOT(cache_program_change(bool))); + connect(ui_cache_d->enabled, SIGNAL(clicked(bool)), this, SLOT(cache_data_change(bool))); + + load_settings(); // Also configures gui } NewDialog::~NewDialog() { @@ -38,6 +41,7 @@ NewDialog::~NewDialog() { delete ui_cache_p; delete ui; // Settings is freed by parent + delete config; } void NewDialog::closeEvent(QCloseEvent *) { @@ -55,13 +59,8 @@ void NewDialog::cancel() { void NewDialog::create() { MainWindow *prnt = (MainWindow*)parent(); - machine::MachineConfig mc; - mc.set_elf(ui->elf_file->text()); - mc.set_pipelined(ui->pipelined->isChecked()); - // TODO other settings - try { - prnt->create_core(mc); + prnt->create_core(*config); } catch (const machine::QtMipsExceptionInput &e) { QMessageBox msg(this); msg.setText(e.msg(false)); @@ -80,54 +79,119 @@ void NewDialog::create() { void NewDialog::browse_elf() { QFileDialog elf_dialog(this); elf_dialog.setFileMode(QFileDialog::ExistingFile); - if (elf_dialog.exec()) - ui->elf_file->setText(elf_dialog.selectedFiles()[0]); + if (elf_dialog.exec()) { + QString path = elf_dialog.selectedFiles()[0]; + ui->elf_file->setText(path); + config->set_elf(path); + } + // Elf shouldn't have any other effect so we skip config_gui here +} + +void NewDialog::set_preset() { + if (ui->preset_no_pipeline->isChecked()) + config->preset(machine::CP_SINGLE); + else if (ui->preset_pipelined->isChecked()) + config->preset(machine::CP_PIPE_WITH_CACHE); + else + // Skip apply configuration as we changed nothing. + return; + config_gui(); +} + +// Common end section of *_change slots +#define CHANGE_COMMON do { \ + ui->preset_custom->setChecked(true); \ + config_gui(); \ + } while(false) + +void NewDialog::pipelined_change(bool val) { + config->set_pipelined(val); + CHANGE_COMMON; +} + +void NewDialog::delay_slot_change(bool val) { + config->set_delay_slot(val); + CHANGE_COMMON; } -void NewDialog::preset(bool value) { - if (value) { - bool pip = ui->preset_pipelined->isChecked(); - // Core settings - ui->pipelined->setChecked(pip); - // Memory settings - ui->mem_protec_write->setChecked(true); - ui->mem_protec_exec->setChecked(true); - } // Else custom so do no changes +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); + else + config->set_hazard_unit(machine::MachineConfig::HU_NONE); + CHANGE_COMMON; } -void NewDialog::set_custom_preset() { - ui->preset_custom->setChecked(true); +void NewDialog::mem_protec_exec_change(bool v) { + config->set_memory_execute_protection(v); + CHANGE_COMMON; } -#define LOAD_BUTTON(NAME, DEF) ui->NAME->setChecked(settings->value(#NAME, DEF).toBool()) -#define LOAD_LINE(NAME, DEF) ui->NAME->setText(settings->value(#NAME, DEF).toString()) +void NewDialog::mem_protec_write_change(bool v) { + config->set_memory_write_protection(v); + CHANGE_COMMON; +} -#define STORE_BUTTON(NAME) settings->setValue(#NAME, ui->NAME->isChecked()) -#define STORE_LINE(NAME) settings->setValue(#NAME, ui->NAME->text()) +void NewDialog::cache_data_change(bool v) { + config->access_cache_data()->set_enabled(v); + CHANGE_COMMON; +} + +void NewDialog::cache_program_change(bool v) { + config->access_cache_program()->set_enabled(v); + CHANGE_COMMON; +} + +void NewDialog::config_gui() { + // Set values + ui->elf_file->setText(config->elf()); + ui->pipelined->setChecked(config->pipelined()); + ui->delay_slot->setChecked(config->delay_slot()); + ui->hazard_unit->setChecked(config->hazard_unit() != machine::MachineConfig::HU_NONE); + ui->hazard_stall->setChecked(config->hazard_unit() == machine::MachineConfig::HU_STALL); + // ui->hazard_stall_forward is configured automatically according to box exclusivity + ui->mem_protec_exec->setChecked(config->memory_execute_protection()); + ui->mem_protec_write->setChecked(config->memory_write_protection()); + ui->mem_time_read->setValue(config->memory_access_time_read()); + ui->mem_time_write->setValue(config->memory_access_time_write()); + ui_cache_p->enabled->setChecked(config->cache_program().enabled()); + ui_cache_d->enabled->setChecked(config->cache_data().enabled()); + // Disable various sections according to configuration + ui->delay_slot->setEnabled(!config->pipelined()); + ui->hazard_unit->setEnabled(config->pipelined()); +} void NewDialog::load_settings() { - // Core tab - LOAD_BUTTON(pipelined, false); - // Memory tab - LOAD_BUTTON(mem_protec_write, true); - LOAD_BUTTON(mem_protec_exec, true); - // Base tab - // We are doing this last so presets can reset previous configuration to somethin valid - LOAD_BUTTON(preset_no_pipeline, true); - LOAD_BUTTON(preset_pipelined, false); - LOAD_BUTTON(preset_custom, false); - LOAD_LINE(elf_file, ""); + if (config != nullptr) + delete config; + + // Load config + config = new machine::MachineConfig(settings); + + // Load preset + unsigned preset = settings->value("Preset", 1).toUInt(); + if (preset != 0) { + enum machine::ConfigPresets 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_PIPE_WITH_CACHE: + ui->preset_pipelined->setChecked(true); + break; + } + } else + ui->preset_custom->setChecked(true); + + config_gui(); } void NewDialog::store_settings() { - // Core tab - STORE_BUTTON(pipelined); - // Memory tab - STORE_BUTTON(mem_protec_write); - STORE_BUTTON(mem_protec_exec); - // Base tab - STORE_BUTTON(preset_no_pipeline); - STORE_BUTTON(preset_pipelined); - STORE_BUTTON(preset_custom); - STORE_LINE(elf_file); + config->store(settings); + + if (ui->preset_custom->isChecked()) + settings->setValue("Preset", 0); + else + settings->setValue("Preset", ui->preset_no_pipeline->isChecked() ? machine::CP_SINGLE + 1 : machine::CP_PIPE_WITH_CACHE + 1); } diff --git a/qtmips_gui/newdialog.h b/qtmips_gui/newdialog.h index a088777..5b50a41 100644 --- a/qtmips_gui/newdialog.h +++ b/qtmips_gui/newdialog.h @@ -7,6 +7,7 @@ #include #include "ui_NewDialog.h" #include "ui_NewDialogCache.h" +#include "machineconfig.h" class NewDialog : public QDialog { Q_OBJECT @@ -21,14 +22,24 @@ private slots: void cancel(); void create(); void browse_elf(); - void preset(bool); - void set_custom_preset(); + void set_preset(); + void pipelined_change(bool); + void delay_slot_change(bool); + void hazard_unit_change(); + void mem_protec_exec_change(bool); + void mem_protec_write_change(bool); + + void cache_data_change(bool); + void cache_program_change(bool); private: Ui::NewDialog *ui; Ui::NewDialogCache *ui_cache_p, *ui_cache_d; QSettings *settings; + machine::MachineConfig *config; + void config_gui(); // Apply configuration to gui + void load_settings(); void store_settings(); }; diff --git a/qtmips_machine/machineconfig.cpp b/qtmips_machine/machineconfig.cpp index e479d66..ab6f2a5 100644 --- a/qtmips_machine/machineconfig.cpp +++ b/qtmips_machine/machineconfig.cpp @@ -2,17 +2,64 @@ using namespace machine; +////////////////////////////////////////////////////////////////////////////// +/// Default config of MachineConfig +#define DF_PIPELINE false +#define DF_DELAYSLOT true +#define DF_HUNIT HU_STALL_FORWARD +#define DF_EXEC_PROTEC false +#define DF_WRITE_PROTEC false +#define DF_MEM_ACC_READ 10 +#define DF_MEM_ACC_WRITE 10 +#define DF_ELF QString("") +////////////////////////////////////////////////////////////////////////////// +/// Default config of MachineConfigCache +#define DFC_EN false +////////////////////////////////////////////////////////////////////////////// + MachineConfigCache::MachineConfigCache() { - // TODO + en = DFC_EN; } MachineConfigCache::MachineConfigCache(const MachineConfigCache *cc) { - // TODO + en = cc->enabled(); +} + +#define N(STR) (prefix + QString(STR)) + +MachineConfigCache::MachineConfigCache(const QSettings *sts, const QString &prefix) { + en = sts->value(N("Enabled"), DFC_EN).toBool(); +} + +void MachineConfigCache::store(QSettings *sts, const QString &prefix) { + sts->setValue(N("Enabled"), en); +} + +#undef N + +void MachineConfigCache::preset(enum ConfigPresets p) { + switch (p) { + case CP_SINGLE: + set_enabled(false); + break; + case CP_PIPE_WITH_CACHE: + set_enabled(false); + break; + } +} + +void MachineConfigCache::set_enabled(bool v) { + en = v; +} + +bool MachineConfigCache::enabled() const { + return en; } bool MachineConfigCache::operator==(const MachineConfigCache &c) const { - // TODO - return true; +#define CMP(GETTER) (GETTER)() == (c.GETTER)() + return CMP(enabled); +#undef CMP } bool MachineConfigCache::operator!=(const MachineConfigCache &c) const { @@ -20,18 +67,77 @@ bool MachineConfigCache::operator!=(const MachineConfigCache &c) const { } MachineConfig::MachineConfig() { - pipeline = false; - delayslot = true; - hunit = HU_STALL_FORWARD; + pipeline = DF_PIPELINE; + delayslot = DF_DELAYSLOT; + hunit = DF_HUNIT; + exec_protect = DF_EXEC_PROTEC; + write_protect = DF_WRITE_PROTEC; + mem_acc_read = DF_MEM_ACC_READ; + mem_acc_write = DF_MEM_ACC_WRITE; + elf_path = DF_ELF; + cch_program = MachineConfigCache(); + cch_data = MachineConfigCache(); } MachineConfig::MachineConfig(const MachineConfig *cc) { pipeline = cc->pipelined(); delayslot = cc->delay_slot(); + hunit = cc->hazard_unit(); + exec_protect = cc->memory_execute_protection(); + write_protect = cc->memory_write_protection(); + mem_acc_read = cc->memory_access_time_read(); + mem_acc_write = cc->memory_access_time_write(); elf_path = cc->elf(); cch_program = cc->cache_program(); cch_data = cc->cache_data(); - hunit = cc->hazard_unit(); +} + +#define N(STR) (prefix + QString(STR)) + +MachineConfig::MachineConfig(const QSettings *sts, const QString &prefix) { + pipeline = sts->value(N("Pipelined"), DF_PIPELINE).toBool(); + delayslot = sts->value(N("DelaySlot"), DF_DELAYSLOT).toBool(); + hunit = (enum HazardUnit)sts->value(N("HazardUnit"), DF_HUNIT).toUInt(); // TODO probably rather save as string + exec_protect = sts->value(N("MemoryExecuteProtection"), DF_EXEC_PROTEC).toBool(); + write_protect = sts->value(N("MemoryWriteProtection"), DF_WRITE_PROTEC).toBool(); + mem_acc_read = sts->value(N("MemoryRead"), DF_MEM_ACC_READ).toUInt(); + mem_acc_write = sts->value(N("MemoryWrite"), DF_MEM_ACC_WRITE).toUInt(); + elf_path = sts->value(N("Elf"), DF_ELF).toString(); + cch_program = MachineConfigCache(sts, N("ProgramCache_")); + cch_data = MachineConfigCache(sts, N("DataCache_")); +} + +void MachineConfig::store(QSettings *sts, const QString &prefix) { + sts->setValue(N("Pipelined"), pipelined()); + sts->setValue(N("DelaySlot"), delay_slot()); + sts->setValue(N("HazardUnit"), (unsigned)hazard_unit()); + sts->setValue(N("Elf"), elf_path); + cch_program.store(sts, N("ProgramCache_")); + cch_data.store(sts, N("DataCache_")); +} + +#undef N + +void MachineConfig::preset(enum ConfigPresets p) { + // Note: we set just a minimal subset to get preset (preserving as much of hidden configuration as possible) + switch (p) { + case CP_SINGLE: + set_pipelined(false); + set_delay_slot(true); + break; + case CP_PIPE_WITH_CACHE: + set_pipelined(true); + set_hazard_unit(MachineConfig::HU_STALL_FORWARD); + break; + } + // Some common configurations + set_memory_execute_protection(DF_EXEC_PROTEC); + set_memory_write_protection(DF_WRITE_PROTEC); + set_memory_access_time_read(DF_MEM_ACC_READ); + set_memory_access_time_write(DF_MEM_ACC_WRITE); + + access_cache_program()->preset(p); + access_cache_data()->preset(p); } void MachineConfig::set_pipelined(bool v) { @@ -40,8 +146,26 @@ void MachineConfig::set_pipelined(bool v) { void MachineConfig::set_delay_slot(bool v) { delayslot = v; - if (!delayslot) - pipeline = false; +} + +void MachineConfig::set_hazard_unit(enum MachineConfig::HazardUnit hu) { + hunit = hu; +} + +void MachineConfig::set_memory_execute_protection(bool v) { + exec_protect = v; +} + +void MachineConfig::set_memory_write_protection(bool v) { + write_protect = v; +} + +void MachineConfig::set_memory_access_time_read(unsigned v) { + mem_acc_read = v; +} + +void MachineConfig::set_memory_access_time_write(unsigned v) { + mem_acc_write = v; } void MachineConfig::set_elf(QString path) { @@ -56,42 +180,68 @@ void MachineConfig::set_cache_data(const MachineConfigCache &c) { cch_data = c; } -void MachineConfig::set_hazard_unit(enum MachineConfig::HazardUnit hu) { - hunit = hu; -} - bool MachineConfig::pipelined() const { return pipeline; } bool MachineConfig::delay_slot() const { - return delayslot; + // Delay slot is always on when pipeline is enabled + return pipeline || delayslot; +} + +enum MachineConfig::HazardUnit MachineConfig::hazard_unit() const { + // Hazard unit is always off when there is no pipeline + return pipeline ? hunit : machine::MachineConfig::HU_NONE; +} + +bool MachineConfig::memory_execute_protection() const { + return exec_protect; +} + +bool MachineConfig::memory_write_protection() const { + return write_protect; +} + +unsigned MachineConfig::memory_access_time_read() const { + return mem_acc_read; +} + +unsigned MachineConfig::memory_access_time_write() const { + return mem_acc_write; } QString MachineConfig::elf() const { return elf_path; } -MachineConfigCache MachineConfig::cache_program() const { +const MachineConfigCache &MachineConfig::cache_program() const { return cch_program; } -MachineConfigCache MachineConfig::cache_data() const { +const MachineConfigCache &MachineConfig::cache_data() const { return cch_data; } -enum MachineConfig::HazardUnit MachineConfig::hazard_unit() const { - return hunit; +MachineConfigCache *MachineConfig::access_cache_program() { + return &cch_program; +} + +MachineConfigCache *MachineConfig::access_cache_data() { + return &cch_data; } bool MachineConfig::operator==(const MachineConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(pipelined) && \ CMP(delay_slot) && \ + CMP(hazard_unit) && \ + CMP(memory_execute_protection) && \ + CMP(memory_write_protection) && \ + CMP(memory_access_time_read) && \ + CMP(memory_access_time_write) && \ CMP(elf) && \ CMP(cache_program) && \ - CMP(cache_data) && \ - CMP(hazard_unit); + CMP(cache_data); #undef CMP } diff --git a/qtmips_machine/machineconfig.h b/qtmips_machine/machineconfig.h index 10b8458..90c9b75 100644 --- a/qtmips_machine/machineconfig.h +++ b/qtmips_machine/machineconfig.h @@ -2,20 +2,35 @@ #define MACHINECONFIG_H #include +#include namespace machine { +enum ConfigPresets { + CP_SINGLE, + CP_PIPE_WITH_CACHE +}; + class MachineConfigCache { public: MachineConfigCache(); MachineConfigCache(const MachineConfigCache *cc); + MachineConfigCache(const QSettings*, const QString &prefix = ""); - // TODO + void store(QSettings*, const QString &prefix = ""); + + void preset(enum ConfigPresets); + + // If cache should be used or not + void set_enabled(bool); + + bool enabled() const; bool operator ==(const MachineConfigCache &c) const; bool operator !=(const MachineConfigCache &c) const; private: + bool en; // TODO }; @@ -23,6 +38,11 @@ class MachineConfig { public: MachineConfig(); MachineConfig(const MachineConfig *cc); + MachineConfig(const QSettings*, const QString &prefix = ""); + + void store(QSettings*, const QString &prefix = ""); + + void preset(enum ConfigPresets); enum HazardUnit { HU_NONE, @@ -36,29 +56,45 @@ public: // Configure if cpu should simulate delay slot in non-pipelined core // In default enabled. When disabled it also automatically disables pipelining. void set_delay_slot(bool); + // Hazard unit + void set_hazard_unit(enum HazardUnit); + // Protect data memory from execution. Only program sections can be executed. + void set_memory_execute_protection(bool); + // Protect program memory from accidental writes. + void set_memory_write_protection(bool); + // Set memory access times. Passed value is in cycles. + void set_memory_access_time_read(unsigned); + void set_memory_access_time_write(unsigned); // Set path to source elf file. This has to be set before core is initialized. void set_elf(QString path); // Configure cache void set_cache_program(const MachineConfigCache&); void set_cache_data(const MachineConfigCache&); - // Hazard unit - void set_hazard_unit(enum HazardUnit); bool pipelined() const; bool delay_slot() const; - QString elf() const; - MachineConfigCache cache_program() const; - MachineConfigCache cache_data() const; enum HazardUnit hazard_unit() const; + bool memory_execute_protection() const; + bool memory_write_protection() const; + unsigned memory_access_time_read() const; + unsigned memory_access_time_write() const; + QString elf() const; + const MachineConfigCache &cache_program() const; + const MachineConfigCache &cache_data() const; + + MachineConfigCache *access_cache_program(); + MachineConfigCache *access_cache_data(); bool operator ==(const MachineConfig &c) const; bool operator !=(const MachineConfig &c) const; private: bool pipeline, delayslot; + enum HazardUnit hunit; + bool exec_protect, write_protect; + unsigned mem_acc_read, mem_acc_write; QString elf_path; MachineConfigCache cch_program, cch_data; - enum HazardUnit hunit; }; } -- cgit v1.2.3