{"id":16199878,"url":"https://github.com/bialger/risc-v-simulator","last_synced_at":"2026-02-15T20:03:57.189Z","repository":{"id":247480978,"uuid":"825961534","full_name":"bialger/risc-v-simulator","owner":"bialger","description":"C++ simulator for RISC-V assembler (RV32I, RV32M instruction set) with cache simulator (LRU, bit-pLRU policies)","archived":false,"fork":false,"pushed_at":"2024-07-08T21:07:46.000Z","size":136,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-25T01:43:37.347Z","etag":null,"topics":["assembler","emulator","risc-v","student-project"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bialger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-08T20:59:11.000Z","updated_at":"2025-01-07T14:46:28.000Z","dependencies_parsed_at":"2024-07-09T02:13:49.709Z","dependency_job_id":"ec933b7c-bbe0-4c2b-bad9-e667dc07320a","html_url":"https://github.com/bialger/risc-v-simulator","commit_stats":null,"previous_names":["bialger/risc-v-simulator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bialger/risc-v-simulator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bialger%2Frisc-v-simulator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bialger%2Frisc-v-simulator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bialger%2Frisc-v-simulator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bialger%2Frisc-v-simulator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bialger","download_url":"https://codeload.github.com/bialger/risc-v-simulator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bialger%2Frisc-v-simulator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29487793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T19:29:10.908Z","status":"ssl_error","status_checked_at":"2026-02-15T19:29:10.419Z","response_time":118,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assembler","emulator","risc-v","student-project"],"created_at":"2024-10-10T09:28:33.776Z","updated_at":"2026-02-15T20:03:57.170Z","avatar_url":"https://github.com/bialger.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"| Лабораторная работа №3   | M3104                       | АОВС |\n|--------------------------|-----------------------------|------|\n| Кэш и кодирование команд | Бигулов Александр Артурович | 2024 |\n\n## Инструментарий\n\nКомпилятор языка C++, стандарт C++20 (MSVC на Windows, GNU GCC и CLang на Linux).\n\n## Что реализовано\n\nВариант 2, реализованы все задания: рассчитаны параметры, данный код переведен в ассемблер RISC-V, реализован расчета\nколичества попаданий в кэш для политик LRU и bit-pLRU, а также перевод ассемблерных файлов в машинный код.\n\n## Результат работы на тестовых данных: [ссылка на последний успешный action с актуальными данными](https://github.com/skkv-itmo-comp-arch/se-comp-arch24-cache-isa-bialger/actions/runs/9131534094)\n\n# Описание:\n\nПрограмма состоит из трех подсистем - `argparser`, `asm_riscv`, `cache` и главной подпрограммы, которая отвечает за\nприменение средств из этих подсистем для выполнения задачи.\n\n## Подсистема argparser\n\nДанный модуль содержит набор связанных классов, предоставляющих функционал обработки пользовательского ввода -\nаргументов командной строки, которые определяют поведение программы.\nОсновной его класс - [ArgParser](./libraries/argparser/ArgParser.hpp).\nБолее подробно подсистема описана [в разделе документации подсистемы](./libraries/argparser/docs/README.md).\nОсобенно подробно описана [архитектура подсистемы](./libraries/argparser/docs/dev/architecture.md) и пользовательская\n[документация основного класса](./libraries/argparser/docs/ArgParser.md).\nСодержит вложенный модуль `argparser_basic`, который содержит некоторый набор вспомогательных функций, например,\nвывода ошибок красным цветом.\n\n## Подсистема asm_riscv\n\nДанный модуль содержит набор классов и связей между ними, предоставляющий функционал для псевдо-эмуляции исполнения\nограниченного набора команд RISC-V (RV32I, RV32M, без Zifence, Zicsr, `FENCE`, `FENCE.TSO` и `PAUSE` инструкций).\nДля этого эмулируются регистры процессора, псевдо-память процессора и исполнитель команд.\nПсевдо-эмуляция это потому что, вместо исполнения хранимого в памяти байт-кода непосредственно, происходит\nпоследовательные вызовы функций от объектов, хранящих некие нормированные переданные аргументы, а также имя инструкции.\n\nИз связанного с этой подсистемой: файл [rv32.asm](./rv32.asm) - перевод данной функции в ассемблер RISC-V.\nБыл выполнен вручную, в частности, с расчетами прыжковых инструкций и инструкций условного перехода.\nПодробнее про него можно прочитать в комментариях к ассемблерному коду - они достаточно обширны.\nЯ узнал, как записываются и что делают большинство инструкций вышеупомянутого подмножества RISC-V.\n\nРезультат перевода этого ассемблерного файла в байт-код - в файле [rv32.bin](rv32.bin).\n\nПеревод ассемблерного кода в байт-код описано в файле [RISCVAssemblerToBinary](./libraries/asm-riscv/RISCVAssemblerToBinary.cpp).\nЭтот класс переводит набор объектов-команд класса `RISCVAssemblerCommand`, полученных с помощью `RISCVAssemblerReader`,\nв набор их байт-кодов.\nГлавная подпрограмма, если был указан соответствующий аргумент, записывает эти коды последовательно эти коды в файл в\nпорядке Little Endian.\n\n### ProcessMemory\n\nДанный класс предоставляет интерфейс взаимодействия с квази-оперативной памятью.\nОн имеет поле, содержащее в себе оперативную память псевдо-эмулятора - `std::vector\u003cuint8_t\u003e`, каждый элемент которой -\nотдельный байт, а также два поля, объекты, эмулирующие кеш для расчета процента попаданий двух разных политик.\n\n#### Методы ReadByte, ReadHalfWord, ReadWord\n\nЭти методы нужны для чтения, соответственно, 1- 2- и 4-байтовых беззнаковых чисел из массива памяти по переданному\nадресу в порядке LittleEndian - младшие байты правее.\nВыполняются с помощью метода Read - получаемый оттуда `std::vector\u003cuint8_t\u003e` соответственной размерности.\nНапример, для числа `0x12345678` это будет `{0x78, 0x56, 0x34, 0x12}`.\nТакже вызывает одноименные функции обоих кешей.\n\n#### Методы WriteByte, WriteHalfWord, WriteWord\n\nЭти методы нужны для записи, соответственно, 1- 2- и 4-байтовых беззнаковых чисел из массива памяти по переданному\nадресу в порядке LittleEndian - младшие байты правее.\nВыполняются с помощью метода Write - туда передается `std::vector\u003cuint8_t\u003e` соответственной размерности.\nТакже вызывает одноименные функции обоих кешей.\n\n#### Метод Read\n\nДанный метод возвращает `std::vector\u003cuint8_t\u003e`, длина которого обусловлена одноименным параметром, а содержит он копию\nотрезка массива памяти, начиная с переданного адреса.\n\n#### Метод Write\n\nДанный метод принимает `std::vector\u003cuint8_t\u003e`, который копируется в массив памяти, начиная с переданного адреса.\n\n#### Методы GetLRUHitRate, GetBitpLRUHitRate\n\nЭти методы возвращают результат выполнения метода GetHitRate от кешей с соответствующими политиками.\n\n### RISCVRegisters\n\nЭтот класс является представлением регистров 32-битного процессора, а также регистра PC - Program Counter, который\nпри настоящей эмуляции хранил бы адрес текущей инструкции, но используется для перехода по массиву инструкций.\nСодержит методы для их получения и изменения в определенных пределах.\nНапример, нельзя изменить регистр `x0` - это всегда 0.\n\n#### Метод GetRegister\n\nДанный метод принимает номер регистра, возвращает его значение, как 32-битное знаковое число.\n\n#### Метод SetRegister\n\nДанный метод принимает номер регистра и его новое значение, заменяет старое значение, если регистр не `x0`.\n\n#### Метод GetPC\n\nДанный метод возвращает значение ProgramCounter, как 32-битное беззнаковое число.\n\n#### Метод SetPC\n\nДанный метод настраивает переданное значение как ProgramCounter.\n\n### RISCVAssemblerCommand\n\nДанный класс ответственен за хранение инструкции (функтора) с аргументами.\nПолучая в конструктор имя команды и ее аргументы, в конструкторе он выбирает соответствующий команде функтор.\nК примеру, если имя команды - `\"add\"`, то функтор сложит значения второго и третьего (то есть, формально из регистра\nпод номером значения) регистров, а затем положит результат в первый и вернет `0`.\nВсе команды возвращают коды возврата, например, `ecall` вернет `1`, а `jalr` в `0` (то есть в начало программы) - `-1`.\n\nАргументы от одного до трех (при необходимости третий хранится как значение) регистров или от нуля до двух значений.\nНомера регистров хранятся как беззнаковое 8-битное число, а значение - как знаковое 32-битное.\nИмеет метод выполнения команды, который не меняет объект, но может менять передаваемые по ссылке регистры и память.\n\n#### Метод Execute\n\nДанный метод получает по ссылке регистры и память, и выполняет функтор от них и хранимых аргументов.\n\n#### Методы GetCommandName, GetReg1, GetReg2, GetValue\n\nМетоды для получения информации о команде. Возвращают всю перечисленную информацию соответствующего типа.\n\n#### Методы SignExtended12Bits и прочие подобные\n\nЭти методы приводят переданное число к указанному в названии виде, причем в случае sign, расширение проходит с\nсохранением знака (заполнением старших бит единицами), а в unsigned - просто заполнением старших разрядов нулями.\n12-битные обрезаются (путем сдвига вправо, а затем влево на 20 бит) до первых 12 бит, а 20-битные - сдвигаются влево\nтак, чтобы старший их них стал 32-м.\nSignExtended13Bits работает аналогично SignExtended12Bits, но сдвигает сначала влево на 19, потом вправо на 20, и затем\nвлево на 1, таким образом оставляя байты со второго по 13-й.\n\n### RISCVAssemblerReader\n\nКласс, который приводит переданные ему строки в Base Form (все маленькими буквами), регистры имеют исключительно\nзначения `x0-x31`, нет комментариев и пустых строк и затем создает из них ассемблерные команды.\n\n#### Метод PreProcessLine\n\nДанный метод предварительно обрабатывает по ссылке _линию_ - `std::vector\u003cstd::string\u003e`, каждая из которых ранее была\nразделена пробелом.\nОн с помощью `std::transform` сначала приводит все элементы линии в нижний регистр.\nЗатем он поиском по каждому элементу убирает запятые, после этого - преобразовывает записи вида `val(reg)` в `val` и\n`reg`.\nПосле этого все ABI-имена регистров преобразуются в базовую форму (`x0-x31`).\n\n#### Метод PreProcess\n\nДанный предварительно обрабатывает набор _линий_ из всех инструкций. Сначала удаляются все комментарии - в каждой\n_линии_ все слова, начиная с `\"//\"`, если таковое есть, после чего удаляются пустые _линии_ и для каждой _линии_\nвызывается метод PreProcessLine.\n\n#### Метод ProcessLine\n\nДанный метод создает из _линии_ объект `RISCVAssemblerCommand`.\nПервое _слово_ строки становится кодом команды.\n\n* Если команда без аргументов, то регистры и значение устанавливается в `0`, первое _слово_ строки становится кодом\n  команды.\n* Если команда имеет один аргумент, то, если первый символ второго _слова_ - `x`, то переведенная `std::stoi` в число\n  оставшаяся часть _слова_ становится значением первого регистра, иначе - переведенное целое _слово_ в число с помощью\n  `GetInteger` становится значением.\n* Если команда имеет два аргумента, то первый из них по указанной выше методике становится первым регистром, а второй -\n  в зависимости от того, первых символ `x` или нет, по там же указанной методике становится вторым регистром или\n  значением.\n* Если команда имеет три аргумента, то первый из них по указанной выше методике становится первым регистром, а далее:\n    * Если второй аргумент начинается с `x`, то по указанной выше методике он становится вторым регистром, а третий\n      аргумент, в зависимости от того, первых символ `x` или нет, по там же указанной методике становится третьим\n      регистром или значением.\n    * Если второй аргумент не начинается с `x`, то по указанной выше методике он становится значением, а третий\n      аргумент, по там же указанной методике становится третьим регистром.\n\n#### Метод Process\n\nДанный метод обрабатывает набор _линий_, описывающий ассемблерные команды, и переводит их в\n`std::vector\u003cRISCVAssemblerCommand\u003e`. Сначала он вызывает метод `PreProcess`, а затем для каждой линии вызывает метод\n`ProcessLine`, сохраняя результат.\n\n#### Метод GetCommands\n\nДанный метод возвращает `std::vector\u003cRISCVAssemblerCommand\u003e`, в котором после вызова `Process` лежит упорядоченный набор\nкоманд.\n\n#### Метод GetInteger\n\nДанный метод переводит строку в число, используя `std::strtoll` с обработкой `C-style` ошибок.\n\n### Класс RISCVCommandExecutor\n\nДанный класс ответственен за выполнение набора последовательных инструкций из вышеупомянутого подмножества RISC-V,\nпереданных в виде `std::vector\u003cRISCVAssemblerCommand\u003e`.\nПамять процесса псевдо-эмулируется классом `ProcessMemory`, регистры (включая Program Counter) - `RISCVRegisters`.\n\n#### Метод Execute\n\nДанный метод производит псевдо-эмуляцию переданных ранее инструкций.\nЗначение `PC` - Program Counter - это адрес начала 4-байтового (для вышеуказанного подмножества) байт-кода инструкции,\nпоэтому считается, что `index = PC / 4`, где `index` - индекс в векторе команд.\nТаким образом, в цикле для `index` от `index = PC / 4` (в начале симуляции `PC = index = 0`), пока `index` не превысит\nразмер вектора команд, после каждой итерации `index = PC / 4`, происходит следующее:\n\n* Запоминается значение `PC`\n* Выполняется инструкция по индексу `index`\n* Происходит обработка кодов возврата:\n    * Если код возврата - `-1`, это значит, что инструкция `jalr` перевела `PC` в `0`, что почти всегда означает\n      бесконечный цикл, учитывая специфику инструкции, происходит выход из функции\n    * Если код возврата - `1`, это значит, что была исполнена инструкция `ecall`. Выбрасывается исключение\n      `std::runtime_error` с соответствующим сообщением.\n    * Если код возврата - `2`, это значит, что была исполнена инструкция `break`. Выбрасывается исключение\n      `std::runtime_error` с соответствующим сообщением.\n\nВ конце тела цикла, если `PC` не был изменен в инструкции, `PC` увеличивается на 4.\nПосле выхода из цикла, если `PC` не равен учетверенному размеру вектора команд, выбрасывается исключение\n`std::runtime_error` с соответствующим сообщением, поскольку это означает некорректное перемещение `PC`.\n\n### Класс RISCVAssemblerToBinary\n\nДанный класс ответственен за перевод набора команд в набор бинарных кодов для них. Содержит статические методы,\nнеобходимые для этого.\n\n#### Метод ToBinary\n\nДанный метод переводит одну команду, переданную как ссылка на `RISCVAssemblerCommand`, в байт-код в виде беззнакового\n32-битного числа.\nДля этого из команды берется ее имя, значения регистров и значение, к ним применяются всевозможные битовые маски\n(например, создается переменная, хранящая младшие 5 бит значения для кодирования величины сдвига).\nЗатем, согласно имени команды, результат конструируется с помощью битовых масок и побитовых операций согласно таблицам:\n\n![image](./assets/binary_codes.png)\n\nОбычно первый регистр соответствует `rd`, второй - `rs1`, третий - `rs2`.\nЕсли в команде нет `rd`, то первый - `rs1`, второй - `rs2` (кроме `s*` команд - там обратный порядок).\n\n## Подсистема cache\n\nДанная подсистема ответственна за псевдо-эмуляцию кеша процессора с целью получения статистики кеш-попаданий - какая\nдоля запросов в кеш была успешна, то есть запрашиваемые данные там уже были.\n\nРасчет [параметров системы](./libraries/cache/constants.hpp) был проведен следующим образом.\nИзвестно:\n\n```cpp\nconstexpr size_t MEM_SIZE = 524288;\nconstexpr size_t CACHE_INDEX_LEN = 4;\nconstexpr size_t CACHE_OFFSET_LEN = 5;\nconstexpr size_t CACHE_SIZE = 2048;\n```\n\nЗаметим, что `MEM_SIZE = 2 ^ ADDR_LEN`. Значит, длина адреса `ADDR_LEN = log2(MEM_SIZE) = 19`.\nТакже размер кеш-линии `CACHE_LINE_SIZE = 2 ^ CACHE_OFFSET_LEN = 32`.\nКроме того, количество кеш-линий `CACHE_LINE_COUNT = CACHE_SIZE / CACHE_LINE_SIZE = 64`.\nДалее, количество элементов в кеш-линии `CACHE_WAY = CACHE_LINE_COUNT / (2 ^ CACHE_INDEX_LEN) = 4`.\nНаконец, длина тега `CACHE_TAG_LEN = ADDR_LEN - CACHE_INDEX_LEN - CACHE_OFFSET_LEN = 10`.\n\n### Класс Cache\n\nДанный класс ответственен за симуляцию поведения кеша - он подсчитывает попадания в кеш и запросы, и имеет методы\nвозврата статистики.\nСодержит объект политики, набор кеш-тегов и числа статистики.\n\n#### Методы ReadByte и т.п.\n\nДанные методы вызывают метод `Read` с соответствующим размеров в байтах, передавая туда еще адрес. Ничего не возвращают -\nнужны для сбора статистики.\n\n#### Методы WriteByte и т.п.\n\nДанные методы вызывают метод `Write` с соответствующим размеров в байтах, передавая туда еще адрес. Ничего более не\nпринимают - нужны для сбора статистики.\n\n#### Метод GetHitRate\n\nВозвращает отношение количества попаданий к количеству запросов.\n\n#### Метод HandleRequest\n\nДанный метод нужен для обработки запроса и выяснения, произошло попадание или нет, и вызова соответствующих обновления\nполитик.\nСначала ищет индекс тега в наборе тегов для текущей кеш-линии (метод поиска - полный перебор, описан в\n[common_functions.hpp](./libraries/cache/common_functions.hpp)).\nЕсли тег был не найден, то вызывается метод добавления тега от политики `AddLine`.\nЕсли был, то количество попаданий инкрементируется и вызывается метод обновления политики `UpdatePolicy`.\n\n#### Метод Read\n\nДанный метод ответственнее за инициацию проверки на наличие в кеше информации по данному адресу при чтении.\nСначала адрес обрезается до `ADDR_LEN` битов, после чего вычисляется номер кеш-линии и тег.\nТег получается сдвигом на `CACHE_INDEX_LEN + CACHE_OFFSET_LEN`, давая длину тега `CACHE_TAG_LEN`.\nИндекс - сдвигом на длину оффсета `CACHE_OFFSET_LEN` и оставлением младших `CACHE_INDEX_LEN` битов.\nЗапускает от этих параметров `HandleRequest`.\n\n#### Метод Write\n\nДанный метод ответственнее за инициацию проверки на наличие в кеше информации по данному адресу при записи.\nСначала адрес обрезается до `ADDR_LEN` битов, после чего вычисляется номер кеш-линии и тег.\nТег получается сдвигом на `CACHE_INDEX_LEN + CACHE_OFFSET_LEN`, давая длину тега `CACHE_TAG_LEN`.\nИндекс - сдвигом на длину оффсета `CACHE_OFFSET_LEN` и оставлением младших `CACHE_INDEX_LEN` битов.\nЗапускает от этих параметров `HandleRequest`.\n\n### Класс LRUPolicy\n\nДанный класс описывает политику вытеснения `LRU`.\nВ ней из кеша вытесняются наиболее давно использовавшиеся теги.\nРеализовано это так: чем выше индекс тега в линии, тем позднее он был использован.\n\n#### Метод UpdatePolicy\n\nМетод, обновляющий таймер. Удаляет тег в линии, куда попал запрос, с его текущего места, и помещает в\nконец линии.\n\n#### Метод AddLine\n\nМетод, добавляющий новый тег. Удаляет самый старый тег в линии, если ее размер равен максимальному, и добавляет в конец\nпереданный тег.\n\n### Класс BitpLRUPolicy\n\nДанный класс описывает политику вытеснения `bit-pLRU`.\nТут все теги изначально проинициализированы невалидным значением (-1) и все линии имеют полный размер.\nХранит массив наличия замещений и попаданий.\n\n#### Метод UpdatePolicy\n\nМетод, обновляющий таймер.\nДля текущего тега в линии помечает соответствующий элемент массива как `true`.\nЕсли все элементы линии получали попадания, то соответсвующее значения в массиве попаданий и замещений становятся\n`false`, кроме текущего.\n\n#### Метод AddLine\n\nМетод, добавляющий новый тег. Ищет первый тег, не получавший попаданий после обновления, и замещает его новым, вызывая\nметод `UpdatePolicy` для этой линии и тега.\n\n## Результат работы\n\nСтатистика (вывод) для `lab3.exe --replacement 0 --asm ./rv32.asm --bin ./rv32.bin`:\n\n```text\nLRU     hit rate: 96.6571%\npLRU    hit rate: 96.6406%\n```\n\nФайл [rv32.bin](./rv32.bin):\n\n```bin\n93 0E 00 04 13 0F C0 03 93 0F 00 02 93 04 00 10 37 19 00 00 13 09 F9 7F 13 09 19 00 B3 02 00 00 63 D2 D2 09 33 03 00 00 63 50 E3 07 93 09 F0 7F 93 89 19 10 33 0A 00 00 B3 03 00 00 63 D8 F3 03 B3 8C 74 00 03 8B 0C 00 33 8D 69 00 33 0D 6D 00 83 1B 0D 00 33 0E 7B 03 33 0A CA 01 B3 89 E9 01 B3 89 E9 01 93 83 13 00 6F F0 51 FD B3 0D 69 00 B3 8D 6D 00 B3 8D 6D 00 B3 8D 6D 00 23 A0 4D 01 13 03 13 00 6F F0 51 FA B3 84 F4 01 33 09 E9 01 33 09 E9 01 33 09 E9 01 33 09 E9 01 93 82 12 00 6F F0 11 F8 67 80 00 00\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbialger%2Frisc-v-simulator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbialger%2Frisc-v-simulator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbialger%2Frisc-v-simulator/lists"}