Luís Miguel Pedrosa de Moura Oliveira Henriques
    • 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
    • Invite by email
      Invitee

      This note has no invitees

    • 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
    • Note Insights New
    • Engagement control
    • Make a copy
    • 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 Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy 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
  • Invite by email
    Invitee

    This note has no invitees

  • 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
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Video-chamada Spike - [1. Modificar Spike](#1-Modificar-Spike) - [1.1. Adicionar Instruções](#11-Adicionar-instruções) - [1.2. Acrescentar funcionalidades](#12-Acrescentar-funcionalidades) - [1.3. Explicação do funcionamento das instruções](#13-Explicação-do-funcionamento-das-instruções) - [2. Recomendações](#2-Recomendações) - [#2.1. Ambiente de desenvolvimento](#21-Ambiente-de-desenvolvimento) - [#2.2. Arquitetura Software](#22-Arquitetura-Software) - [#2.3. Extras](#23-Extras) - [3. Compilação das ferramentas](#3-Observações-durante-compilação-das-ferramentas) - [3.1. Compilador](#31-Compilador) - [3.2. Proxy-Kernel](#32-Proxy-Kernel) - [3.3. Spike](#33-Spike) - [3.4. Encodings.h](#34-Encodingsh) - [4. Anexos](#4-Anexos) - [4.1. Compilar Spike](#41-Compilar-Spike) - [4.2. Binário de teste](#42-Binário-de-teste) - [4.3. Instrução ss.ld.w modificada](#43-Instrução-ssldw-modificada) # 1. Modificar Spike - [Spike modificado na minha dissertação, no branch `uve`.](https://github.com/MiguelPedrosa/riscv-isa-sim/tree/a3507fe124c2727e785eacd03589a87622d1a3f5) - [Alterações feitas durante a minha dissertação.](https://github.com/MiguelPedrosa/riscv-isa-sim/compare/55f664b6b712fbe351236c73936309bd9c60a5d1..a3507fe124c2727e785eacd03589a87622d1a3f5) - Ficheiros na pasta `UVE-Test-Impl` serviram somente para teste e não são compilados com o Spike. - [Instruções usadas nos benchmarks.](https://github.com/MiguelPedrosa/riscv-isa-sim/blob/a3507fe124c2727e785eacd03589a87622d1a3f5/UVE-instruction-usage.md) - Para criar um comportamento novo, o melhor é procurar instruções semelhantes já existentes para descobrir como o fazem. As instruções das extensões `F` e `D` são úteis para descobrir como mexer com números em virgula flutuante e a extensão `V` tem os registos contruídos de forma semelhante aos do UVE. - É difícil de navegar no projeto porque existem muitas macros e substituições de texto. É raro conseguir saltar para a definição/implementação duma função, por isso uma boa ferramenta de pesquisa de texto será o mais útil para navegar no projeto. ## 1.1. Adicionar instruções - Adicionar em `encoding.h` a informação da instrução. [Versão com todas instruções UVE](https://gist.github.com/MiguelPedrosa/89f003150f513fd5a5aa6b32131728c8#file-encoding-h). - Criar ficheiro em `riscv/insns/<ins-name>.h`, onde os pontos (`.`) do nome da instrução são substituídos por underscores (`_`). - Acrescentar instrução em `riscv/riscv.mk.in`. - Para testar, criei a instrução `ss.ld.w` e incluí o código na [secção 4.3](#43-Instrução-ssldw-modificada). ## 1.2 Acrescentar funcionalidades Numa instrução, para aceder aos valores passados pelos registos é preciso primeiro descobrir qual o registo que a instrução invocada usou (u0-u31, x0-x31, etc.) e depois extrair o valor que esse registo contém. 1. Como o UVE acrescenta registos novos, temos de acrescentar algumas funções na classe `insn_t`, do ficheiro `riscv/decode.h`. Na prática, muitas das funções que incluí a seguir fazem algo idêntico ao que já existe, mas acrescentar novas cria mais flexibilidade caso a encodificação das instruções do UVE mude. ```c // Registers for configuration instructions int64_t uve_conf_destination() { return x(7, 5); } int64_t uve_conf_base() { return x(15, 5); } int64_t uve_conf_size() { return x(20, 5); } int64_t uve_conf_stride() { return x(27, 5); } // Registers for computation instructions int64_t uve_comp_dest() { return x(7, 5); } int64_t uve_comp_src1() { return x(15, 5); } int64_t uve_comp_src2() { return x(20, 5); } // Registers for branching instructions int64_t uve_branch_reg() { return x(15, 5); } // Calculate offset for UVE branching instruction int64_t uve_branch_imm() { return (x(8, 4) << 1) + (x(22, 6) << 5) + (x(7, 1) << 11) + (imm_sign() << 12); } ``` - Se for preciso consultar o encoding das instruções e não houver uma versão mais atualizada, dá para o fazer [nesta tabela](https://docs.google.com/spreadsheets/d/1884rlTVR9CgEPJ85GKEnYcThA31_4Doyuy6GYNgTQYk/edit). - Explicação da função/macro `x`: O primeiro argumento é o bit onde começa a informação que queremos descobrir. O segundo argumento é a quantidade de bits que temos de ler. - Exemplo: Na função `uve_conf_destination`, queremos descobrir qual o registo em que deve ser programada uma stream: vendo o [encoding das instruções de configuração](https://docs.google.com/spreadsheets/d/1884rlTVR9CgEPJ85GKEnYcThA31_4Doyuy6GYNgTQYk/edit#gid=1027939292&range=B3:AG3), este registo começa no bit 7 e acaba no 11, portanto começa em 7 e requere 5 bits para computar. 2. No ficheiro duma instrução UVE (como `riscv/insns/ss_ld_w.h`), podemos aceder às funções de decode, usando o objeto `insn`. Acesso a registos nativo do RISC-V pode ser feito com a macro `READ_REG`. Acesso a registos UVE ainda não existe e tem de ser acrescentado como parte deste trabalho. Para aceder à memória do binário em execussão, usa-se a função `MMU.load<uint32>` e semelhantes. 3. (Sem certezas, não testei recentemente isto) Para acrescentar registos ou funcionalidades parecidas, o que fiz durante a dissertação foi criar uma classe que representasse todos os objetos, à semelhança do que é feito para a [extensão V](https://github.com/riscv-software-src/riscv-isa-sim/blob/32742924154a41e457f5b64e745485a3ebcd0daf/riscv/processor.h#L337). Qualquer ficheiro .h ou .cpp novo, tem de ser adicionado ao `riscv.mk.in`, nas variáveis [`riscv_install_hdrs`](https://github.com/riscv-software-src/riscv-isa-sim/blob/32742924154a41e457f5b64e745485a3ebcd0daf/riscv/riscv.mk.in#L16) e [`riscv_srcs`](https://github.com/riscv-software-src/riscv-isa-sim/blob/32742924154a41e457f5b64e745485a3ebcd0daf/riscv/riscv.mk.in#L45). ### 1.2.1. Instrução branch - A função `uve_branch_imm` não retorna um índice de registos, mas em vez um valor imediato. As contas para calcular este valor foram extraídas desta [linha da tabela de encoding](https://docs.google.com/spreadsheets/d/1884rlTVR9CgEPJ85GKEnYcThA31_4Doyuy6GYNgTQYk/edit#gid=1314391799&range=E5:AG5). - O imediato será um valor com 12 bits mais sinal: bit 0 ao bit 11. Nas instruções de branch da tabela de encoding, os valores entre parêntesis retos indicam os bits de índice do valor imediato. É sempre preciso ter em conta o bit 0 e não está representado porque será sempre implicitamente 0 por filosofia do RISC-V. É por causas do bit 0 que os cálculos estão feito como `x(8, 4) << 1` e não como `x(8, 4)`. - O [bit 12](https://docs.google.com/spreadsheets/d/1884rlTVR9CgEPJ85GKEnYcThA31_4Doyuy6GYNgTQYk/edit#gid=1314391799&range=E5) representa o sinal do imediato, ou seja, se fazemos um salto na execussão para a frente ou para trás. - (Não testado recentemente) Para realizar um salto, deve ser preciso fazer algo como `set_pc(pc + branchIMM)`. Só implementei [um salto](https://github.com/MiguelPedrosa/riscv-isa-sim/blob/a3507fe124c2727e785eacd03589a87622d1a3f5/riscv/insns/so_b_nc.h#L11) durante a dissertação. - Mais de metade do meu esforço a modificar o Spike foi nesta instrução :upside_down_face:. ## 1.3. Explicação do funcionamento das instruções Durante a compilação do Spike, será criada uma cópia do ficheiro [riscv/insn_template.cc](https://github.com/riscv-software-src/riscv-isa-sim/blob/32742924154a41e457f5b64e745485a3ebcd0daf/riscv/insn_template.cc) por cada instrução. Neste processo, o nome da instrução substitui o atributo `NAME` no template e o código de cada instrução substitui o atributo `#include "insns/NAME.h"`. Isto é uma forma "ágil" de criar várias versões da mesma instrução (32/64bits, com/sem logs, etc.), mas tem implicações no desenvolvimento: * Não podemos adicionar includes por instrução. Para incluír um header novo, é preciso modificar o ficheiro [`riscv/insn_template.h`](https://github.com/riscv-software-src/riscv-isa-sim/blob/32742924154a41e457f5b64e745485a3ebcd0daf/riscv/insn_template.h) e o novo header vai passar a ser incluído em todas as instruções. * Temos suporte mínimos de IDEs. Estamos a fazer código que será colocado dentro duma função, mas os IDEs acreditam que estamos a criar código no espaço global. * Temos acceso às variáveis `processor_t* p`, `insn_t insn` e `reg_t pc`, dado estas serem os argumentos da função. `pc` é o `process counter` e é útil para instruções que façam saltos. `insn` é a instrução que foi invocada (incluíndo valores dos registos) e até agora só vi a ser usada com funções semelhantes à [secção 1.2](#12-Acrescentar-funcionalidades). # 2. Recomendações ## 2.1. Ambiente de desenvolvimento - No final do trabalho, o build server não me serviu de muito, pois passei a fazer tudo localmente. - Em Windows, dá para instalar todas as ferramentas no WSL e fazer o desenvolvimento por aí. - É possível não compilar extensões no Spike que não usadas para poupar tempos de compilação. Eu comentei as extensões `q`, `k`, `v`, `zce` e `p`. Talvez seja possível ocultar mais, mas não testei o suficiente. - Usar flag `-j$(nproc)` durante compilação do Spike para acelerar processo. Flag `-B` para forçar compilação, é útil porque alterações nos headers não causam uma recompilação. - Por vezes o comando `objdump -S a.out` dá jeito para rever o assembly gerado pelo compilador. É preciso usar `$RISCV/bin/riscv64-unknown-elf-objdump`. - Symlinks para o gcc, Spike e proxy-kernel simplificam bastante a invocação de comandos. ## 2.2. Arquitetura Software - Ter em conta desde início a instrução `ss.cfg.vec`. Muitas instruções vão depender da existência desta flag e condiciona a geração de valores da streaming engine. - Uso de predicados cria 'lacunas' de valores nos registos vetoriais. Talvez nos de predicados também. ## 2.3. Extras - O livro ["The RISC-V Reader: An Open Architecture Atlas"](http://www.riscvbook.com/) ajuda a entender muito do que acontece no RISC-V e algumas das extensões. A maioria das versões é paga, mas existe uma [versão em português (BR)](http://www.riscvbook.com/portuguese/) gratuita no mesmo site. # 3. Observações durante compilação das ferramentas Nota: `$RISCV` é uma variável para um diretório. Isto é algo desatualizado, mas ainda é usado nos repositórios destas ferramentas. Dantes, as ferramentas procuravam nesta localização utilitários para fazer compilações e desenvolvimento. Agora é usado como um lugar de destino da instalação das várias ferramentas. ## 3.1. Compilador - O nome onde do repositório do compilador deve ser "riscv-opcodes". Os scripts de python assumem que é este o nome do projeto, ainda que não seja o nome do repositório no gitlab. - O QEMU e uma dependência sua têm o URL errado nos módulos. Só é possível corrigir isto após o repositório do QEMU ser descarregado. Primeiro correr o comando `git submodule update --init --recursive` (deve falhar só a descarregar o QEMU), de seguida editar o ficheiro `ext_modules/riscv-gnu-toolchain/.gitmodules` com o [novo URL do QEMU](https://github.com/riscvarchive/riscv-qemu) e depois correr os comandos `git submodule sync --recursive` e `git submodule update --init --recursive`. - A dependência do QEMU, `qemu-palcode`, vai ter o mesmo problema. É preciso editar `ext_modules/riscv-gnu-toolchain/riscv-qemu/.gitmodules` para que o `palcode` referencia o [novo URL](https://github.com/rth7680/qemu-palcode) (Não encontrei diferença, mas parece que existe) e correr os comandos `git submodule sync --recursive` e `git submodule update --init --recursive`. - O compilador de UVE é baseado no `gcc8.2` e depende duma package chamada `isl`. Para esta versão do `gcc` funcionar com uma versão moderna de `isl`, é preciso incluir as seguintes linhas no ficheiro `riscv-opcodes/ext_modules/riscv-gnu-toolchain/riscv-gcc/gcc/graphite.h`: ```c #include <isl/id.h> #include <isl/space.h> ``` - Notas: - Eu editei o `configure.sh` para modificar o `INSTALL_DIR` pois queria que o compilador e outras utilidades fossem instalados num diretório diferente. - A compilação do `gcc` é feita por `stages`, dado ser um cross-compiler. Isto significa que é preciso correr mais que uma vez o `make -j$(nproc)` e `make linux -j$(nproc)` conforme aparece no ficheiro `configure.sh`. Não consegui que uma execussão normal deste script gerasse o compilador, por isso fui comentando as linhas `make linux -j$(nproc)` e `make linux -j$(nproc)` e repetindo a execussão do ficheiro até que os outputs parecessem repetido. - O compilador final encontra-se em `$RISCV/bin/riscv64-unknown-elf-gcc`. ## 3.2. Proxy-Kernel O único comando diferente foi o do `configure` que exigiu referência explícita para o `CC` e `OBJCOPY`. ```bash $ CC=$RISCV/bin/riscv64-unknown-elf-gcc \ OBJCOPY=$RISCV/bin/riscv64-unknown-elf-objcopy \ ../configure --prefix=$RISCV --host=riscv64-unknown-elf ``` - Notas: - O proxy-kernel final encontra-se em `$RISCV/riscv64-unknown-elf/bin/pk`. ## 3.3. Spike - Não tive dificuldades em compilar o Spike diretamente do repositório. - Só quando adicionei um encoding mais recente com as instruções UVE tive dificuldades em compilar, mas isso é discutido na secção seguinte. ## 3.4. Encodings.h - Ao acrescentar as instruções do UVE tive problemas pois o ficheiro `encoding.h` que tinha esta desatualizado. - Gerei uma nova versão a partir do [projeto oficial](https://github.com/riscv/riscv-opcodes) com as instruções UVE, mas o Spike estava a contar com instruções mais desatualizadas, por isso reverti para um [commit mais antigo dos opcodes](https://github.com/riscv/riscv-opcodes/tree/8df0274582cedd9070bb92f6e7d9e225de005f1a). - Gerei novamente o ficheiro e o resultado final encontra-se [num gist público](https://gist.github.com/MiguelPedrosa/89f003150f513fd5a5aa6b32131728c8#file-encoding-h). - Para gerar o ficheiro, fui a uma pasta gerada pelo setup da compilação do compilador. Após correr o script `inject_uve.sh`, surge uma pasta chamada `build/templates/`. Os ficheiros desta pasta devem ser copiados para o repositório dos opcodes com o nome modificado para `rv_uve_...`. - Acredito que o ficheiro `m5ops.expanded` não precisa de ser copiado. Do meu entendimento, eram instruções para gerar logs quando um binário corria no gem5. Para correr no Spike não me parecem necessárias. - Depois é preciso modificar o ficheiro `constants.py` para este compreender os registos do UVE: ```python # Unlimited Vector Extension #arglut['vd'] = (11,7) # Same as vector #arglut['vs1'] = (19,15) # Same as vector #arglut['vs2'] = (24,20) # Same as vector #arglut['rs1'] = (19,15) # Same as RV32I #arglut['rs2'] = (24,20) # Same as RV32I #arglut['rs3'] = (31,27) # Same as RV32I #arglut['rd'] = (11,7) # Same as RV32I arg_lut['pd'] = (10,7) arg_lut['ps1'] = (18,15) arg_lut['ps2'] = (22,20) arg_lut['ps3'] = (27,25) arg_lut['imm12aHi'] = (28,22) arg_lut['imm12aLo'] = (11,7) ``` - Seguindo as instruções do repositório, é gerado um ficheiro com o nome `encodings.out.h`. É este que deve ser copiado para o Spike. # 4. Anexos Ficheiros adicionais produzidos. ## 4.1. Compilar Spike Como o rebuild do Spike era frequente, escrevi este script para facilitar o processo. O passo de `make install` exigiu-me permissões, por isso o script foi sempre corrido com `sudo`. Sem argumentos, o script faz só `make && make install`. Com o argumento `full`, é forçada uma compilação completa. Isto é útil porque alterações nos headers (excluíndo as instruções) não são detetadas pelo make, por isso forçamos a compilação destes. Com o argumento `all` faz o mesmo que o `full`, mas corre novamente o script `./configure`. Os paths devem ser modificados. ```bash #!/bin/bash SPIKE_DIR=/home/miguel/Projects/UVE/sources/spike RISCV=/home/miguel/Projects/UVE/tools # Move to spike' source directory and store current directory pushd $SPIKE_DIR # If required, empty the build directory [ "$1" = "all" ] && rm -rdf build && mkdir build cd build # If required, run the configure command [ "$1" = "all" ] && ../configure --prefix="$RISCV" # The "full" argument adds the additional "-B" argument to make, that forces # a full re-compilation. We don't need to test for "all" as all previous files # should have been deleted early in the script. Also, we're only interested in # installing if build is succesfull make -j$(nproc) $([ "$1" = "full" ] && echo "-B") && make install # Return to original directory popd ``` ## 4.2. Binário de teste Testei com o seguinte binário a execussão da instrução `ss.ld.w`. Como a instrução que escolhi usa um tamanho de words, preciso que o array seja dum tipo com mesmo tamanho: `int32_t`. ```c #include <stdio.h> int main() { #define SIZE 10 int32_t array[SIZE]; for (int32_t i = 0; i < SIZE; i++) { array[i] = i * 10; } asm volatile("ss.ld.w u9, %[offset], %[size], %[stride]\n\t" :: [offset] "r" (array), [size] "r" (SIZE), [stride] "r" (1) : "memory"); } ``` **IMPORTANTE**: No código acima, o compilador não consegue observar alterações no array, por isso acredita que é inútil modificar este e não gera as instruções de atribuição do loop. Podemos força-lo a escrevê-las de uma ou mais formas: - 1. Usando a diretiva "memory" no assembly. Isto força um flush da memória modificada até àquele instante. - 2. Declarando o array como `volatile` para indicar que pode ser modificado de formas não previstas. ## 4.3 Instrução `ss.ld.w` modificada Implementei esta instrução para testar acessos aos índices dos registos invocados, valores dos registos RISC-V e acesso a memória. De momento, a instrução faz um print de todos os valores que a stream deverá gerar. ```c /* This UVE instruction (ss.ld.w) uses 4 registers: 1 UVE vector register and * 3 RISC-V native registers. The READ_REG function is used to read the * contents of the native RISC-V registers. We would need to use a different * function to access a floating-point register. */ fprintf(stderr, "Start of logs inside ss.ld.w instruction\n"); /* Find all indexes of the used registers */ auto streamReg = insn.uve_conf_destination(); auto baseReg = insn.uve_conf_base(); auto sizeReg = insn.uve_conf_size(); auto strideReg = insn.uve_conf_stride(); /* Find the values that each register holds. Base has a different type so it can * points to an address. The UVE registers are not implemented, so they cannot * have any value to read. */ reg_t base = READ_REG(baseReg); int32_t size = READ_REG(sizeReg); int32_t stride = READ_REG(strideReg); /* For debug, print the indexes of each register as well as their contents */ fprintf(stderr, "UVE Register u%ld\n", streamReg); fprintf(stderr, "RISC-V Register x%2ld: %p (Base)\n", baseReg, (void*) base); fprintf(stderr, "RISC-V Register x%2ld: %d (Size)\n", sizeReg, size); fprintf(stderr, "RISC-V Register x%2ld: %d (Stride)\n", strideReg, stride); /* For testing, print all the values the configuration should generate */ for (int32_t i = 0; i < size; i++) { /* Find where the next element to print should be */ int32_t offset = i * stride; /* Stride does not considere byte sizes, so we need to adjust the offset */ int32_t byteOffset = offset * sizeof(std::int32_t); /* Read from memory our value stored in the location given as an argument */ int32_t value = MMU.load<int32_t>(base + byteOffset); fprintf(stderr, "array[%d]: %d\n", offset, value); } fprintf(stderr, "End of logs inside ss.ld.w instruction\n"); ```

    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