HotMercury
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# Computer Architecture Final Project > Contributed by: < `HotMercury` (p76111741) >, < `freshLiver` (P76114016) >, < `tinhanho` (P76121364) > ## Summary - <`HotMercury` (p76111741)> - <`freshLiver` (P76114016)> - Fix Issue #258 (Immediate Bit Range Checking) - Add testing unit for checking bit range - <`tinhanho` (P76121364)> - Add cache replacement policy: FIFO ## Setup ### Build from Source 1. Clone the official repo and use the provided [dockerfile](https://github.com/mortbopet/Ripes/tree/master/docker) to build the environment - It may take long time to build the environment 2. Clone your ripes repo - **MUST** clone with `--recurse-submodules` option, or you will fail to build it!!! - Otherwise, conduct ```git submodule update --init --recursive``` after cloning because there are external sources called VSRTL, ELFIO and libelfin. 3. Enter the environment with `docker run --rm --name=ripes -it --entrypoint=/bin/bash ripes:latest` - Add new user in the docker with `useradd -m user` (==assume the host $UID is also 1000==) - Commit the (in another terminal, make sure the docker guest is idle) with `docker commit ripes cafinal:user` - Stop the current guest with `docker stop ripes` 4. Enter the environment with `docker run --rm --name=ripes -u 1000 -it -v $YOUR_RIPES_DIR:/ripes cafinal:user`: - This command maps your ripes directory into guest environment - This command runs your environment in user permission (instead of using root permission, for keeping the owner and group) - Don't forget to **REPLACE *$YOUR_RIPES_DIR*** in the command 5. Goto the mapped dir inside the guest environment 6. Build your ripes with the following command ```bash! $ cmake -S . -B ./build -Wno-dev -DRIPES_BUILD_TESTS=ON -DVSRTL_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="/6.5.0/gcc_64/lib/cmake" $ make -C build ``` 7. Your Ripes should be successfully built 8. Test your ripes - Goto the test directory inside the build path `cd /ripes/build/test` - Testing `./tst_assembler && ./tst_expreval && ./tst_riscv` 9. The tests should be all passed: ```bash! $ ./tst_assembler && ./tst_expreval && ./tst_riscv [...] Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 11495ms ********* Finished testing of tst_RISCV ********* ``` ### Run it 1. Run the compiled binary with the following command ```bash docker run --rm -it -v $YOUR_RIPES_DIR:/ripes -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --entrypoint=/ripes/build/Ripes cafinal:user ``` ### Build in local 1. git clone ripes - Suffix ```--recurse-submodules``` are recommended. 2. Make sure that all tools below are installed. - Notice the version that using qt6 instead of qt5. ``` sudo apt install qt6-base-dev sudo apt-get install qt6-tools-dev sudo apt-get install qt6-tools-dev-tools sudo apt-get install libqt6charts6-dev ``` 3. Make sure that submodule(VSRTL ELFIO and libelfin) are all included. - If they are not, we shall update them. ```git submodule update --init --recursive``` 5. Mind that if 6.5.0 directory exists or not. If not, conduct ```sudo aqt install-qt linux desktop 6.5.0 gcc_64 -m qtcharts``` 6. Under ripes directory - Note that Cmake should create build directory and configuration in the directory. ``` cmake -B build \ -Wno-dev \ -DRIPES_BUILD_TESTS=ON \ -DVSRTL_BUILD_TESTS=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=$(pwd)/6.5.0/gcc_64/ ``` 7. Under build directory conduct ```sudo make install``` 8. Execute ```./Ripes``` - Note that use ```sudo ./Ripes``` if we want to test system call which likes file open and so on. <!--5. Go to build/test and make ```cd build/test && make```--> ## Issue ### [Issue #196](https://github.com/mortbopet/Ripes/issues/196) We found that when using the `open` system call, the mode permission settings need to be specified in octal form. According to the [Linux man page](https://man7.org/linux/man-pages/man2/open.2.html), when we set `00200`, it means the user has write permission. However, when attempting a write operation, we observed a write error. The issue appears to be related to Ripes interpreting the set number as decimal instead of the octal mode. Read write test in Ripes ```c! .data file_address: .asciz "/tmp/hello_world.txt" greeting: .asciz "hello world" open_error: .asciz "open error" read_error: .asciz "read error" write_error: .asciz "write error" successs: .asciz "success!" .text main: # open syscall 1024 # a0 Pointer to null terminated string for the path # a1 flags # flags -> O_RDONLY 00, O_WRONLY 01, O_RDWR 02 # S_IWUSR 00200 user has write permission li s0, -1 la a0, file_address li a1, 0x2 # here is the problem ori a1, a1, 00200 li a7, 1024 ecall beq a0, s0, open_fail # write syscall 64 # a0 the file discriptor # a1 address of the buffer # a2 number of bytes to write addi s1, a0, 0 la a1, greeting li a2, 11 li a7, 64 ecall beq a0, s0, write_fail close: # close syscall 57 # a0 the file descriptor to close mv a0, s1 li a7, 57 ecall j end open_fail: la a0, open_error li a7, 4 ecall j end write_fail: la a0, write_error li a7, 4 ecall j close end: nop ``` When using the `open(fd, flags)` syscall implemented by Ripes, it cannot write data to the file if we set the `a1` (`flags`) to 1 (`O_WRONLY`) or 2 (`O_RDWR`). Instead, we need to set it to 3. :::info However, the `QFile::write` doesn't require the flag to be 3, we can write a simple program to test: ```cpp // test.cpp #include <QFile> using namespace std; int main (int argc, const char *argv[1]) { const char* msg = argv[2]; QFile file(argv[1]); file.open(QIODevice::WriteOnly); file.write(msg, qstrlen(msg)); file.close(); return 0; } ``` Then, create a `CMakeLists.txt` file with the following lines: ```cmake cmake_minimum_required(VERSION 3.16) project(test) find_package(Qt6 COMPONENTS Core REQUIRED) qt_add_executable(test test.cpp) ``` And build and test the simple program with: ```bash $ cmake -S . -B build -DCMAKE_PREFIX_PATH="/6.5.0/gcc_64/lib/cmake" $ cmake --build build $ ./build/test yoyo aaaa $ cat yoyo ``` The created file `yoyo` should contain the string `aaaa`. ::: If we check the `write` syscall implementation, we could find that it explicitly requires `O_WRONLY | O_RDWR`: ```c // src/syscall/systemio.h static int writeToFile(int fd, const QString &myBuffer, int lengthRequested) { SystemIO::get(); // Ensure that SystemIO is constructed if (fd == STDOUT || fd == STDERR) { emit get().doPrint(myBuffer); return myBuffer.size(); } if (!FileIOData::fdInUse( fd, O_WRONLY | O_RDWR)) // Check the existence of the "write" fd { s_fileErrorString = "File descriptor " + QString::number(fd) + " is not open for writing"; return -1; } // retrieve FileOutputStream from storage auto &outputStream = FileIOData::getStreamInUse(fd); outputStream << myBuffer; outputStream.flush(); return lengthRequested; } // end writeToFile ``` :::warning Why the implementation require the flags to be `O_WRONLY | O_RDWR` ??? ::: ### [Issue #258](https://github.com/mortbopet/Ripes/issues/258) ([RP #339](https://github.com/mortbopet/Ripes/pull/339)) :::spoiler Details #### Description As the issue desciption said, in the latest Ripes, most of the I-type instructions will check whether the given immediate part can fit in the instruction limitation. For example, the `addi` instruction will ensure the given immediate is less than 13 bits, as the following image shows: ![image](https://hackmd.io/_uploads/r1CZ8fWda.png) However, the `lui` instruction's immediate part should only accept an immediate value that could fit in 20 bits, but current version doesn't handle this limitation correctly, as shown in the above image. #### Tracing To find the problem, I use `std::cout` to dump the `width` in the `checkFitsInWidth` function, which is for checking the immediate range: ```cpp static Result<> checkFitsInWidth(Reg_T_S value, const Location &sourceLine, ImmConvInfo &convInfo, QString token = QString()) { std::cout << __PRETTY_FUNCTION__ << std::endl << "check token '" << token.toStdString() << "' (expected width=" << width << ") at #" << sourceLine.sourceLine() << std::endl; ... ``` Then, rebuild the Ripes and test it with the following codes: ```asm main: addi x1, x1, 0x123455 lui x1, 0x12345678 ``` And found that the `width` of `lui` instruction is misconfigured as 32, instead of 20: ```cpp! static Ripes::Result<> Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::checkFitsInWidth(Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::Reg_T_S, const Ripes::Location&, Ripes::ImmConvInfo&, QString) [with unsigned int tokenIndex = 2; unsigned int width = 12; Ripes::Repr repr = Ripes::Repr::Signed; ImmParts = Ripes::ImmPartBase<0, Ripes::BitRange<20, 31, 32> >; Ripes::SymbolType symbolType = Ripes::SymbolType::None; Ripes::Reg_T (* transformer)(Ripes::Reg_T) = Ripes::defaultTransformer; Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::Reg_T_S = long int] check token '0x123455' (expected width=12) at #1 static Ripes::Result<> Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::checkFitsInWidth(Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::Reg_T_S, const Ripes::Location&, Ripes::ImmConvInfo&, QString) [with unsigned int tokenIndex = 1; unsigned int width = 32; Ripes::Repr repr = Ripes::Repr::Hex; ImmParts = Ripes::ImmPartBase<0, Ripes::BitRange<12, 31, 32> >; Ripes::SymbolType symbolType = Ripes::SymbolType::None; Ripes::Reg_T (* transformer)(Ripes::Reg_T) = Ripes::defaultTransformer; Ripes::ImmBase<tokenIndex, width, repr, ImmParts, symbolType, transformer>::Reg_T_S = long int] check token '0x12345678' (expected width=32) at #2 ``` And this is origin from the the U-type instruction implementation, we can see that the `width` is configured as 32: ```cpp /// A RISC-V immediate field with an input width of 32 bits. /// Used in U-Type instructions. /// /// It is defined as: /// - Imm[31:12] = Inst[31:12] /// - Imm[11:0] = 0 constexpr static unsigned VALID_INDEX = 1; template <unsigned index, SymbolType symbolType> struct ImmU : public ImmSym<index, 32, Repr::Hex, ImmPart<0, 12, 31>, symbolType> { static_assert(index == VALID_INDEX, "Invalid token index"); }; /// A U-Type RISC-V instruction template <typename InstrImpl, RVISA::OpcodeID opcodeID, SymbolType symbolType = SymbolType::None> class Instr : public RV_Instruction<InstrImpl> { template <unsigned index> using Imm = ImmU<index, symbolType>; public: struct Opcode : public OpcodeSet<OpPartOpcode<opcodeID>> {}; struct Fields : public FieldSet<RegRd, Imm> {}; }; struct Auipc : public Instr<Auipc, RVISA::OpcodeID::AUIPC, SymbolType::Absolute> { constexpr static std::string_view NAME = "auipc"; }; struct Lui : public Instr<Lui, RVISA::OpcodeID::LUI> { constexpr static std::string_view NAME = "lui"; }; } // namespace TypeU ``` #### Solve By changing the width to 20, the bit range checking now works as expected, on the `lui` and `auipc` now: ![image](https://hackmd.io/_uploads/B1cQ2M-Oa.png) #### Testing Currently I only use the provided testing utility for checking my changes didn't break it: ```bash /ripes/build/test$ ./tst_assembler && ./tst_expreval && ./tst_riscv ... Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted, 9676ms ********* Finished testing of tst_RISCV ********* ``` However, since I'm not that familiar with C++, I'm not sure whether I understood the bit range checking process correctly. If there is any problem in my changes, please me know. ::: #### Add testing unit (Commit [#85e406b](https://github.com/mortbopet/Ripes/pull/339/commits/85e406b9d59e4f27b4ae5140f6d87600478fe998)) ### Minor Issues #### Typo (PR [#336](https://github.com/mortbopet/Ripes/pull/336), Commit [#07e2ef9](https://github.com/mortbopet/Ripes/commit/07e2ef9a50d2ce99130a2c18b6c445d78599e899)) In file `Ripes/src/syscall/ripes_syscall.h`: ```c! /** * @brief The SyscallManager class * * It is expected that the syscallManager can be called outside of the main GUI * thread. As such, all syscalls who require GUI interaction must handle this * explicitely. */ class SyscallManager { ... } ``` The word `explicitely` should be `explicitly`. ## Interactions :::info Hi @matsievskiysv According to the code in `systemio.h` at line 80, ``` static constexpr int O_RDONLY = 0x00000000; static constexpr int O_WRONLY = 0x00000001; static constexpr int O_RDWR = 0x00000002; ``` which defines the value of the flags used by the `open` syscall. However, it cannot write data into the file correctly, if we only set `O_WRONLY` when calling `open` syscall. Looking at the conditional statements at lines 408-409, it input `fd` and `3` as parameter ``` if (!FileIOData::fdInUse(fd, O_WRONLY | O_RDWR)) ``` and then checks for the existence of the "write" flag using fdInUse at line193 ``` else if ((fileFlags[fd] & flag) == static_cast<unsigned>(flag)) ``` it requires the flag to be `0x3` to be true. Therefore, setting it only to `O_WRONLY` may result in a write failure. Hi @mortbopet Ideally, writing to a file should be allowed as long as it satisfies either `O_WRONLY` or `O_RDWR`. However, here it is specified that both conditions must be met for a write operation to proceed. I would like to inquire whether the author has any specific reason for this additional requirement. ::: ## Add Replacement Mechanism ### Motivation <!-- The issue which have already existed is not relative with our course. Therefore, we decide to open a new issue by ourselves. After checking the ripes, we find that ### FIFO Add one more condition in [cachesim.cpp](https://github.com/tinhanho/Ripes/blob/master/src/cachesim/cachesim.cpp). FIFO policy is relatively simple in comparison with LRU. What we do is just adding a counter and it shall circularly evict cache slots because of the first in first out rule. ```c else if (m_replPolicy == ReplPolicy::FIFO){ ew.first = CacheSim::counter; ew.second = &cacheLine[ew.first]; CacheSim::counter += 1; CacheSim::counter %= getWays(); } ``` In [cachesim.h](https://github.com/tinhanho/Ripes/blob/master/src/cachesim/cachesim.h), add int counter in CacheSim class and append one more rule. ```c enum ReplPolicy { Random, LRU, FIFO}; ... class CacheSim : public CacheInterface { Q_OBJECT public: static constexpr unsigned s_invalidIndex = static_cast<unsigned>(-1); int counter = 0; ... const static std::map<ReplPolicy, QString> s_cacheReplPolicyStrings{ {ReplPolicy::Random, "Random"}, {ReplPolicy::LRU, "LRU"}, {ReplPolicy::FIFO, "FIFO"}}; ``` After building it, we test it with the assembly code below, ```c lw a1 0(x0) lw a1 512(x0) lw a1 0(x0) lw a1 1024(x0) ``` LRU ![ezgif-1-a490ed85c8](https://hackmd.io/_uploads/Skf_PXEw6.gif) FIFO ![FIFO](https://hackmd.io/_uploads/By72wQ4w6.gif) It works as our expectation. --> [Ripes #334](https://github.com/mortbopet/Ripes/issues/334) :::info Hi @mortbopet, I find that contributing to the cache replacement policy is a good way to work with. The replacement policy which possesses from now on are Random and LRU. Intuitively, FIFO can be added and it has been done through few lines of code. What I am doing now is trying to add policy like least frequently used. However, I cannot find out where the code is to expand the slot just like LRU doing. For example, when 2 ways cache used, you click Repl. policy LRU and the LRU slots show up. ![image](https://hackmd.io/_uploads/Bkd_CUrDa.png) I guess that it might be the UI problem but I do not know how to modify it. Could you help me? ::: ### Pull Request [Link](https://github.com/mortbopet/Ripes/pull/335) :::info About Issue [#334](https://github.com/mortbopet/Ripes/issues/334). I think that we could add a FIFO mechanism to the cache policy. A new replacement policy, FIFO (First In, First Out), has been added to the existing enumeration. Additionally, a new counter has been introduced in the cachesim.h file. This counter plays a crucial role in cyclically determining which cache entry should be evicted. ```c // line 19 in cachesim.h enum ReplPolicy { Random, LRU, FIFO}; ``` The code below handles the eviction process based on the FIFO replacement policy. ```c //line 124 in cachesim.cpp else if (m_replPolicy == ReplPolicy::FIFO){ ew.first = CacheSim::counter; ew.second = &cacheLine[ew.first]; CacheSim::counter += 1; CacheSim::counter %= getWays(); } ``` ::: ### Author Reply :::success Thank you for looking into adding a new cache replacement policy! A few comments: As per the coding style currently used in Ripes, member variables should be prefixed with m_ and not with the class name. I think the counter should be named better, e.g. fifoIndexCounter. (optional) do you think there is an accompanying visualization to this? i.e., for LRU, we also show the LRU bits. Could one imagine a similar column which indicates what way in the cache line is currently up for eviction as per. FIFO? ::: ### Work on Author's suggestion :::info Thanks for the reply. I had already renamed the counter and tried my best to follow the coding style. If there are any problem still, please let me know. As for third comments, I rework all the framework to implement visualizing the FIFO bit. Now, there is no need about fifoIndexCounter. Instead, boolean fifoflag is presented. This flag is set under two circumstances, 1. When an invalid entry is selected, we set the fifoflag. 2. When all the entries are full and cache miss occurs, we need to choose a entry to evicted and we set the fifoflag. When this flag is set, we shall add 1 to fifo bits if entry is valid. In this way, we'll find that when fifo bits equal to the way of the cache, that entry should be evicted. ```c //Line 167 in cachesim.cpp if (it != cacheLine.end()) { ew.first = it->first; ew.second = &it->second; m_fifoflag = true; } if (ew.second == nullptr) { for (auto &way : cacheLine) { if (static_cast<long>(way.second.fifo) == getWays()){ ew.first = way.first; ew.second = &way.second; m_fifoflag = true; break; } } } ``` Furthermore, the undo part needs to be taken into consideration as well. If the policy is FIFO, we set the fifoflag again and we need to restore the oldway. ```c //Line 442 in cachesim.cpp if (!trace.transaction.isHit && getReplacementPolicy() == ReplPolicy::FIFO) { m_fifoflag = true; way = oldWay; } ``` ```c //Line 82 in cachesim.cpp if (getReplacementPolicy() == ReplPolicy::FIFO) { for(auto &set : line){ if(set.second.valid && m_fifoflag) set.second.fifo--; } m_fifoflag = false; line[wayIdx].fifo = oldWay.fifo; } ``` Demo video here, https://github.com/mortbopet/Ripes/assets/67796326/b67aab30-17ee-4b3c-8fa7-ba56f905a282 Demo video demostrates the assembly code below, ```c lw a1 0(x0) lw a1 512(x0) lw a1 0(x0) lw a1 512(x0) lw a1 512(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 0(x0) lw a1 0(x0) lw a1 0(x0) lw a1 1536(x0) lw a1 1536(x0) lw a1 1536(x0) addi a0 x0 1024 addi a0 a0 1024 lw a1 0(a0) ``` Full code is in the branch FIFO of my fork, https://github.com/tinhanho/Ripes/commit/6772d1abec6a6bff5e0e40374fd5922c67e3a427 Let me know if there are any problems of my think and implement. ::: <!-- ### LFU LFU means least frequently used. We take the advantage of structure of LRU. Therefore, we have a slot in cache as well. The usage of the slot is to record the frequency of the entry. If the entry is hit again, We add one to the values inside the slot. When all the entry is full and we need to evict one entry, we select the less frequency entry to be evicted. Through the LFU slot, we can easily to find out which one is least freqently used. The UI problem is solved and we find that we need to modify the code in [cachesim.cpp](https://github.com/tinhanho/Ripes/blob/master/src/cachesim/cachegraphic.cpp) and [cahcesim.h](https://github.com/tinhanho/Ripes/blob/master/src/cachesim/cachegraphic.h). As FIFO, we append a new rule LFU and trace the code carefully to realize how LRU works. It helps us a lot to create the new rule. The main work is to deal with initial value of LFU and find a way to add one to the values inside the slot. We test our code with assembly code below, ```c lw a1 0(x0) lw a1 512(x0) lw a1 0(x0) lw a1 512(x0) lw a1 512(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 1024(x0) lw a1 1536(x0) lw a1 1536(x0) lw a1 1536(x0) addi a0 x0 1024 addi a0 a0 1024 lw a1 0(a0) ``` ![錄製內容 2023-12-25 202221](https://hackmd.io/_uploads/rkBXdlwwT.gif) It works as our expectation. #### Problem: - How to deal with the situation if frequency bit is too high. - The variable and data structure which are newly added need more optimization. [pull request](https://github.com/mortbopet/Ripes/pull/335/commits/90f083d191398f54d43da8832dbe7fa2ee34b4e6) --> --- ## Trace Code ### Instruction Definition The base of the instruction is the class `InstructionBase` defined in the `src/isa/instruction.h`. ```cpp /** @brief A no-template, abstract class that defines an instruction. */ class InstructionBase { public: InstructionBase(unsigned byteSize) : m_byteSize(byteSize) {} virtual ~InstructionBase() = default; /// Assembles a line of tokens into an encoded program. virtual AssembleRes assemble(const TokenizedSrcLine &tokens) = 0; /// Disassembles an encoded program into a tokenized assembly program. virtual Result<LineTokens> disassemble(const Instr_T instruction, const Reg_T address, const ReverseSymbolMap &symbolMap) const = 0; ... /** * @brief size * @return size of assembled instruction, in bytes. */ unsigned size() const { return m_byteSize; } ... protected: ... unsigned m_byteSize; }; ``` And this class is inheritted by the `Instruction` structure: ```cpp template <typename InstrImpl> struct Instruction : public InstructionBase { Instruction() : InstructionBase(InstrByteSize<InstrImpl>::byteSize), m_name(InstrImpl::NAME.data()) {} AssembleRes assemble(const TokenizedSrcLine &tokens) override { ... } Result<LineTokens> disassemble(const Instr_T instruction, const Reg_T address, const ReverseSymbolMap &symbolMap) const override { ... } const QString &name() const override { return m_name; } unsigned numOpParts() const override { return InstrImpl::Opcode::numParts(); } private: const QString m_name; }; ``` This structure implement the `assemble` and `disassemble` functions used for assembling/disassembling the instructions at runtime. But Since the real instruction implementations are defined in the `src/isa/rv_[icm]_ext.h` files, take I-type for example: #### I-Type Definitions The I-type instructions are defined under the namespace `TypeI` in `src/isa/rv_i_ext.h`: ```cpp namespace TypeI { enum class Funct3 : unsigned { ADDI = 0b000, SLTI = 0b010, SLTIU = 0b011, XORI = 0b100, ORI = 0b110, ANDI = 0b111, }; ... /// An I-Type RISC-V instruction template <typename InstrImpl, OpcodeID opcodeID, Funct3 funct3> struct Instr : public RV_Instruction<InstrImpl> { struct Opcode : public OpcodeSet<OpPartOpcode<opcodeID>, OpPartFunct3<static_cast<unsigned>(funct3)>> {}; struct Fields : public FieldSet<RegRd, RegRs1, ImmCommon12> {}; }; ... template <typename InstrImpl, Funct3 funct3> using Instr32 = Instr<InstrImpl, OpcodeID::OPIMM, funct3>; ... struct Addi : public Instr32<Addi, Funct3::ADDI> { constexpr static std::string_view NAME = "addi"; }; ... struct Jalr : public RV_Instruction<Jalr> { struct Opcode : public OpcodeSet<OpPartOpcode<RVISA::OpcodeID::JALR>, OpPartFunct3<static_cast<unsigned>(0b000)>> { }; struct Fields : public FieldSet<RegRd, RegRs1, ImmCommon12> {}; constexpr static std::string_view NAME = "jalr"; }; } ``` :::info **`RV_Instruction` is a simple wrapper of `Instruction`** ```cpp // src/isa/rvisainfo_common.h namespace RVISA { ... template <typename InstrImpl> struct RV_Instruction : public Instruction<InstrImpl> { constexpr static unsigned instrBits() { return INSTR_BITS; } // 32 }; ... } ``` ::: These instructions have the common format `imm[11:0] | rs1 | funct3 | rd | opcode (0b0010011)` where the fields `imm` (`RegRd`), `rs1` (`RegRs1`), `rd` (`ImmCommon12`) are available when parsing the assembly codes. So, in the definition of `Addi`, the most important thing is to define the `funct3` (`Funct3::ADDI`). :::warning **The `JALR` instruction:** Note that an exception is the `JALR` instruction, its opcode is `0b1100111` and thus inherit the `RV_Instruction` directly. ::: #### U-Type Definitions ```cpp namespace TypeU { constexpr static unsigned VALID_INDEX = 1; template <unsigned index, SymbolType symbolType> struct ImmU : public ImmSym<index, 32, Repr::Hex, ImmPart<0, 12, 31>, symbolType> { static_assert(index == VALID_INDEX, "Invalid token index"); }; /// A U-Type RISC-V instruction template <typename InstrImpl, RVISA::OpcodeID opcodeID, SymbolType symbolType = SymbolType::None> class Instr : public RV_Instruction<InstrImpl> { template <unsigned index> using Imm = ImmU<index, symbolType>; public: struct Opcode : public OpcodeSet<OpPartOpcode<opcodeID>> {}; struct Fields : public FieldSet<RegRd, Imm> {}; }; struct Auipc : public Instr<Auipc, RVISA::OpcodeID::AUIPC, SymbolType::Absolute> { constexpr static std::string_view NAME = "auipc"; }; struct Lui : public Instr<Lui, RVISA::OpcodeID::LUI> { constexpr static std::string_view NAME = "lui"; }; } // namespace TypeU ``` ### Instruction Initialization During runtime, the instructions will be initialized by using the `enableInstructions` function, defined in the `src/isa/instruction.h`, to add the instructions into the `InstrVec m_instructions` of the `RV_ISAInfoBase` class: ```cpp // src/isa/rvisainfo_common.h class RV_ISAInfoBase : public ISAInfoBase { ... const InstrVec &instructions() const override { return m_instructions; } ... void initialize(const std::set<Option> &options = {}) { RVISA::ExtI::enableExt(this, m_instructions, m_pseudoInstructions, options); ... } ... } // src/isa/rv_i_ext.cpp namespace ExtI { ... void enableExt(const ISAInfoBase *isa, InstrVec &instructions, PseudoInstrVec &pseudoInstructions, const std::set<Option> &options) { ... enableInstructions<Addi, Andi, Slti, Sltiu, Xori, Ori, Lb, Lh, Lw, Lbu, Lhu, Ecall, Auipc, Lui, Jal, Jalr, Sb, Sw, Sh, Add, Sub, Sll, Slt, Sltu, Xor, Srl, Sra, Or, And, Beq, Bne, Blt, Bge, Bltu, Bgeu>(instructions); ... } ``` Then, later will use the function `setInstructions` to initialize the map `InstrMap m_instructionMap` of all the previously defined instructions: ```cpp class Assembler : public AssemblerBase { ... void setInstructions() { if (m_instructionMap.size() != 0) { throw std::runtime_error("Instructions already set"); } for (const auto &iter : m_isa->instructions()) { const auto instr_name = iter.get()->name(); if (m_instructionMap.count(instr_name) != 0) { throw std::runtime_error("Error: instruction with opcode '" + instr_name.toStdString() + "' has already been registerred."); } m_instructionMap[instr_name] = iter; } } ... } ``` ### Instruction Assembling Then, the function `Assembler::assemble`, defined in `src/assembler/assembler.h`, will be the entry point for assembling the instructions, during runtime: ```cpp #define runPass(resName, resType, passFunction, ...) \ auto passFunction##_res = passFunction(__VA_ARGS__); \ if (auto *errors = std::get_if<Errors>(&passFunction##_res)) { \ result.errors.insert(result.errors.end(), errors->begin(), errors->end()); \ assert(result.errors.size() != 0); \ return result; \ } \ auto resName = std::get<resType>(passFunction##_res); ... class Assembler : public AssemblerBase { ... assemble(const QStringList &programLines, const SymbolMap *symbols = nullptr, const QString &sourceHash = QString()) const override { AssembleResult result; ... // tokenize and expand pseudo instructions /** Assemble. During assembly, we generate: * - linkageMap: Recording offsets of instructions which require linkage * with symbols */ LinkRequests needsLinkage; runPass(program, Program, pass2, expandedLines, needsLinkage); // Symbol linkage runPass(unused, NoPassResult, pass3, program, needsLinkage); Q_UNUSED(unused); result.program = program; result.program.sourceHash = sourceHash; result.program.entryPoint = m_sectionBasePointers.at(".text"); return result; } ... } ``` Then, in this function, the function `pass2` is used for translating the instruction: ```cpp #define runOperation(resName, operationFunction, ...) \ auto operationFunction##_res = operationFunction(__VA_ARGS__); \ if (operationFunction##_res.isError()) { \ errors.push_back(operationFunction##_res.error()); \ continue; \ } \ auto resName = operationFunction##_res.value(); ... class Assembler : public AssemblerBase { std::variant<Errors, Program> pass2(const SourceProgram &tokenizedLines, LinkRequests &needsLinkage) const { ...// Initialize program with initialized segments: for (const auto &line : tokenizedLines) { ...// adjust the symbol addr based on the section base address ...// handle directive addr_offset = currentSection->data.size(); if (!wasDirective) { /// Maintain a pointer to the instruction that was assembled. std::shared_ptr<InstructionBase> assembledWith; runOperation(machineCode, assembleInstruction, line, assembledWith); assert(assembledWith && "Expected the assembler instruction to be set"); program.sourceMapping[addr_offset].insert(line.sourceLine()); if (!machineCode.linksWithSymbol.symbol.isEmpty()) { LinkRequest req(line.sourceLine()); req.offset = addr_offset; req.fieldRequest = machineCode.linksWithSymbol; req.section = m_currentSection; req.instrAlignment = m_isa->instrByteAlignment(); needsLinkage.push_back(req); } ...// handle misalignment currentSection->data.append( QByteArray(reinterpret_cast<char *>(&machineCode.instruction), assembledWith->size())); } // This was a directive; append any assembled bytes to the segment. currentSection->data.append(directiveBytes); } if (errors.size() != 0) { return {errors}; } ... return {program}; } } ``` And the function `assembleInstruction` will be used for assembling and checking an instruction, if any error is found, the error message will be pushed to the error list and highlighted on the editor: ```cpp virtual AssembleRes assembleInstruction(const TokenizedSrcLine &line, std::shared_ptr<InstructionBase> &assembledWith) const { if (line.tokens.empty()) { return { Error(line, "Empty source lines should be impossible at this point")}; } const auto &opcode = line.tokens.at(0); auto instrIt = m_instructionMap.find(opcode); if (instrIt == m_instructionMap.end()) { return {Error(line, "Unknown opcode '" + opcode + "'")}; } assembledWith = instrIt->second; return assembledWith->assemble(line); } ``` It will first check whether the opcode (instruction name) is legal. If true, then it will retrieve the instruction implementation from `Assembler::m_instructionMap`, and call the `assemble` function implemented by that instruction. As explained above, because the instruction implementations are inheritted from the `Instruction` structure, if the instruction didn't override the `assemble` function, the default `assemble` implementation should be defined by the `Instruction` structure: ```cpp template <typename InstrImpl> struct Instruction : public InstructionBase { ... AssembleRes assemble(const TokenizedSrcLine &tokens) override { Instr_T instruction = 0; FieldLinkRequest linksWithSymbol; InstrImpl::Opcode::apply(instruction, linksWithSymbol); if (auto fieldRes = InstrImpl::Fields::apply(tokens, instruction, linksWithSymbol); fieldRes.isError()) { return std::get<Error>(fieldRes); } InstrRes res; res.linksWithSymbol = linksWithSymbol; res.instruction = instruction; return res; } ... } ``` When the implementation being called, if the `apply` function didn't be overridden by the instruction implementation, it will first use that provided by `struct OpPartBase` defined in `src/isa/instruction.h`, to combine the fields into a complete instruction: ```cpp struct OpPartBase { ... const BitRangeBase range; ... constexpr void apply(Instr_T &instruction) const { instruction |= range.apply(value); } ... } ... struct BitRangeBase { ... constexpr unsigned width() const { return stop - start + 1; } constexpr Instr_T getMask() const { return vsrtl::generateBitmask(width()); } constexpr Instr_T apply(Instr_T value) const { return (value & getMask()) << start; } ... } ``` ### SysCall Handling ```c // src/syscall/ripes_syscall.cpp bool SyscallManager::execute(SyscallID id) { if (m_syscalls.count(id) == 0) { postToGUIThread([=] { if (auto reg = ProcessorHandler::currentISA()->syscallReg(); reg.has_value()) { ... } }); return false; } else { const auto &syscall = m_syscalls.at(id); ... syscall->execute(); ... return true; } } ``` When `syscall->execute()` is executed, the corresponding syscall implementation will be called. For the `open` syscall: ```c // src/syscall/file.h template <typename BaseSyscall> class OpenSyscall : public BaseSyscall { static_assert(std::is_base_of<Syscall, BaseSyscall>::value); public: OpenSyscall() : BaseSyscall("Open", "Opens a file from a path", {{0, "Pointer to null terminated string for the path"}, {1, "flags"}}, {{0, "the file decriptor or -1 if an error occurred"}}) {} void execute() { const AInt arg0 = BaseSyscall::getArg(BaseSyscall::REG_FILE, 0); const AInt arg1 = BaseSyscall::getArg(BaseSyscall::REG_FILE, 1); QByteArray string; char byte; unsigned int address = arg0; do { byte = static_cast<char>( ProcessorHandler::getMemory().readMemConst(address++, 1) & 0xFF); string.append(byte); } while (byte != '\0'); int ret = SystemIO::openFile(QString::fromUtf8(string), arg1); BaseSyscall::setRet(BaseSyscall::REG_FILE, 0, ret); } }; ``` And then call the `SystemIO::openFile(...)`: ```c // src/syscall/systemio.h static int openFile(QString filename, int flags) { SystemIO::get(); // Ensure that SystemIO is constructed // Internally, a "file descriptor" is an index into a table // of the filename, flag, and the File???putStream associated with // that file descriptor. int retValue = -1; int fdToUse; // Check internal plausibility of opening this file fdToUse = FileIOData::nowOpening(filename, flags); retValue = fdToUse; // return value is the fd if (fdToUse < 0) { return -1; } // fileErrorString would have been set try { FileIOData::openFilestream(fdToUse, filename); } catch (int) { s_fileErrorString = "File " + filename + " could not be opened."; retValue = -1; } return retValue; // return the "file descriptor" } ``` This function first allocate a `fd` for the specified file by using `FileIOData::nowOpening()`, which saves the parameter `flags` into an map called `fileFlags[i]`: ```c static int nowOpening(const QString &filename, int flag) { ... fileNames[i] = filename; // our table has its own copy of filename fileFlags[i] = flag; ... return i; } ``` Then, it will try to open the file with `FileIOData::openFilestream()`: ```c // src/syscall/systemio.h static constexpr int O_RDONLY = 0x00000000; static constexpr int O_WRONLY = 0x00000001; static constexpr int O_RDWR = 0x00000002; static constexpr int O_APPEND = 0x00000008; static constexpr int O_CREAT = 0x00000200; // 512 static constexpr int O_TRUNC = 0x00000400; // 1024 static constexpr int O_EXCL = 0x00000800; // 2048 ... static void openFilestream(int fd, const QString &filename) { files.emplace(fd, filename); const auto flags = fileFlags[fd]; const auto qtOpenFlags = // Translate from stdlib file flags to Qt flags (flags & O_RDONLY ? QIODevice::ReadOnly : QIODevice::NotOpen) | (flags & O_WRONLY ? QIODevice::WriteOnly : QIODevice::NotOpen) | (flags & O_RDWR ? QIODevice::ReadWrite : QIODevice::NotOpen) | (flags & O_TRUNC ? QIODevice::Truncate : QIODevice::Append) | (flags & O_EXCL ? QIODevice::NewOnly : QIODevice::NotOpen); // Try to open file with the given flags files[fd].open(qtOpenFlags); ... } ``` In this function, we can find that the `open` syscall relies on the [`QFile::open()`](https://doc.qt.io/qt-6/qfile.html#open) provided by Qt. The given `flags` will be converted to the [Qt defined flags](https://doc.qt.io/qt-6/qiodevicebase.html#OpenModeFlag-enum): | **Constant** | **Value** | |:--------------------------- |:--------------------- | | QIODeviceBase::NotOpen | 0x0000 | | QIODeviceBase::ReadOnly | 0x0001 | | QIODeviceBase::WriteOnly | 0x0002 | | QIODeviceBase::ReadWrite | ReadOnly \| WriteOnly | | QIODeviceBase::Append | 0x0004 | | QIODeviceBase::Truncate | 0x0008 | | QIODeviceBase::Text | 0x0010 | | QIODeviceBase::Unbuffered | 0x0020 | | QIODeviceBase::NewOnly | 0x0040 | | QIODeviceBase::ExistingOnly | 0x0080 | :::warning Note that the Ripes defined flags listed above are **NOT** identical to [the (**standard? linux defined?**) flags](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h#L22): ```c! #define O_ACCMODE 00000003 #define O_RDONLY 00000000 #define O_WRONLY 00000001 #define O_RDWR 00000002 #ifndef O_CREAT #define O_CREAT 00000100 /* not fcntl */ #endif #ifndef O_EXCL #define O_EXCL 00000200 /* not fcntl */ #endif #ifndef O_NOCTTY #define O_NOCTTY 00000400 /* not fcntl */ #endif #ifndef O_TRUNC #define O_TRUNC 00001000 /* not fcntl */ #endif #ifndef O_APPEND #define O_APPEND 00002000 #endif ``` ::: ### Cache Handling In `src/cachesim/cachesim.h`, there is a enumeration `ReplPolicy` which is used for listing the available cache replacement policies: ```c enum ReplPolicy { Random, LRU }; ``` #### Select Victim And in the `src/cachesim/cachesim.h`, the function `CacheSim::access` is the main function for accessing the cache. And in this function, if cache miss is happened, the function `CacheSim::evictAndUpdate` will be used for selecting the victim: ```c void CacheSim::access(AInt address, MemoryAccess::Type type) { ... if (!transaction.isHit) { if (type == MemoryAccess::Read || (type == MemoryAccess::Write && getWriteAllocPolicy() == WriteAllocPolicy::WriteAllocate)) { oldWay = evictAndUpdate(transaction); } } else { oldWay = m_cacheLines[transaction.index.line][transaction.index.way]; } ... } ``` The function `CacheSim::evictAndUpdate` will first determine which way the victim should be selected from, by using [the function `CacheSim::locateEvictionWay`](#Select-Victim-Way). And after the victim way is determined, it will update the cache line flags: ```c CacheSim::CacheWay CacheSim::evictAndUpdate(CacheTransaction &transaction) { const auto [wayIdx, wayPtr] = locateEvictionWay(transaction); ... // ignored, explained later *wayPtr = CacheWay(); wayPtr->valid = true; wayPtr->dirty = false; wayPtr->tag = getTag(transaction.address); ... return eviction; } ``` However, we can find that the victim cache line is not updated directly, it's substituted with a new cache line instead. The reasons are: - The victim cache line may be dirty - The old value is be recorded for rolling back Therefore, the function will also record the updates into `transaction` and return the evicted cache line. ```c CacheSim::CacheWay CacheSim::evictAndUpdate(CacheTransaction &transaction) { ... CacheWay eviction; if (!wayPtr->valid) { transaction.transToValid = true; } else { eviction = *wayPtr; if (eviction.dirty) { transaction.isWriteback = true; } } ... transaction.tagChanged = true; transaction.index.way = wayIdx; return eviction; } ``` :::info #### Select Victim Way As mentioned before, the function `CacheSim::locateEvictionWay` is used for selecting the victim way. In this function, it first check the replacement policy. It randomly select a way if `ReplPolicy::Random` is in used: ```c std::pair<unsigned, CacheSim::CacheWay *> CacheSim::locateEvictionWay(const CacheTransaction &transaction) { ... std::pair<unsigned, CacheSim::CacheWay *> ew; ... if (m_replPolicy == ReplPolicy::Random) { ew.first = std::rand() % getWays(); ew.second = &cacheLine[ew.first]; } ... return ew; } ``` Otherwise, the LRU will be performed to find the first invalid cache line. If all the cache lines are valid, it will select the LRU line: ```c std::pair<unsigned, CacheSim::CacheWay *> CacheSim::locateEvictionWay(const CacheTransaction &transaction) { ... else if (m_replPolicy == ReplPolicy::LRU) { ... auto it = std::find_if(cacheLine.begin(), cacheLine.end(), [=](const auto &way) { return !way.second.valid; }); if (it != cacheLine.end()) { ew.first = it->first; ew.second = &it->second; } if (ew.second == nullptr) { for (auto &way : cacheLine) { if (static_cast<long>(way.second.lru) == getWays() - 1) { ew.first = way.first; ew.second = &way.second; break; } } } } ... return ew; } ``` ::: ## Reference - [The Ripes Official Repository](https://github.com/mortbopet/Ripes) - [temp reference](https://hackmd.io/@Rwbh0z6QRXqUP7ovs7txiQ/HySbbdSCw) - [draft](https://hackmd.io/@M1Il4baLQwe1hoqHMQez_g/S1AMESO86)

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully