# qtrvsim test
**Repository Path**: Paged/qtrvsim-test
## Basic Information
- **Project Name**: qtrvsim test
- **Description**: qtrvsim test project
- **Primary Language**: C
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2023-11-21
- **Last Updated**: 2024-11-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# QtRvSim—用于教育的 RISC-V CPU 模拟器

Developed by the [Computer Architectures Education](http://comparch.edu.cvut.cz) project
at [Czech Technical University](http://www.cvut.cz/).
## 目录
- [试试看! (网络运行)](#try-it-out-webassembly)
- [构建和打包](#build-and-packages)
- [安装依赖](#build-dependencies)
- [常规编译](#general-compilation)
- [在 macOS 上从源代码构建](#building-from-source-on-macos)
- [下载二进制包](#download-binary-packages)
- [Nix 包](#nix-package)
- [测试](#tests)
- [文档](#documentation)
- [可接受的二进制格式](#accepted-binary-formats)
- [LLVM工具链的使用](#llvm-toolchain-usage)
- [GNU工具链的使用](#gnu-toolchain-usage)
- [用于 RV32I 的 GNU 64 位工具链](#gnu-64-bit-toolchain-use-for-rv32i-target)
- [集成汇编器](#integrated-assembler)
- [支持调用外部make实用程序](#support-to-call-external-make-utility)
- [最新功能](#advanced-functionalities)
- [外部设备](#peripherals)
- [中断、控制和状态寄存器](#interrupts-and-control-and-status-registers)
- [系统调用支持](#system-calls-support)
- [实施的局限性](#limitations-of-the-implementation)
- [QtMips 原始限制](#qtmips-original-limitations)
- [当前支持的指令列表](#list-of-currently-supported-instructions)
- [资源和类似项目的链接](#links-to-resources-and-similar-projects)
- [版权](#copyright)
- [License](#license)
## 试试看! (网络运行)
QtRVSim 已适用于 [WebAssembly](https://webassembly.org/),无需安装即可在大多数浏览器中运行。 **[QtRVSim 在线](https://comparch.edu.cvut.cz/qtrvsim/app)**
**请注意,WebAssembly 的版本为测试版**
请通过以下方式报告任何困难 [GitHub issues](https://github.com/cvut/qtrvsim/issues/new).
## 构建和打包
[](https://repology.org/project/qtrvsim/versions)
### 安装依赖
- Qt 5(最低测试版本为5.9.5),实验性支持Qt 6
- elfutils(libelf 理论上也可以使用,但可能会出现一些问题)
### Linux上的快速编译
### 常规编译
在github中克隆官方项目
```
https://github.com/Pagerd/qtrvsim.git
```
```shell
cmake -DCMAKE_BUILD_TYPE=Release /path/to/qtrvsim
cd qtrvsim
make
```
/path/to/qtrvsim为qtrvsim项目目录,这里以./qtrvsim为准
在 Linux 上,您可以使用包装器 Makefile 并在项目根目录中运行`make`。 它将创建一个构建目录并在其中运行 CMake。 可用的目标有:`release`(默认)和`debug`。
打包人员注意:创建源存档时,CMake 会删除此 Makefile以避免任何歧义。 包应该直接调用 CMake。


其中`/path/to/qtrvsim`是该项目根目录的路径。 构建的二进制文件可以在构建目录(调用 cmake 的目录)的`target`目录中找到。
`-DCMAKE_BUILD_TYPE=Debug`构建开发版本。
如果未提供构建类型,则默认为`debug`。
### 在 macOS 上从源代码构建
从 App Store 安装最新版本的 **Xcode**。 然后打开终端并执行`xcode-select --install`来安装命令行工具。 然后打开 Xcode,接受许可协议并等待它安装任何其他组件。 最终看到“欢迎使用 Xcode”屏幕后,从顶部栏中选择“Xcode -> 首选项 -> 位置 -> 命令行工具”,然后选择 SDK 版本。
安装 [Homebrew](https://brew.sh/) 并使用它来安装 Qt 和 libelf。 (__安装 libelf 是可选的。如果系统中未找到 libelf,则使用本地回退.__)
```shell
brew install qt libelf
```
现在以与常规编译相同的方式构建项目 ([above](#general-compilation)).
### 下载二进制包
- [https://github.com/cvut/qtrvsim/releases](https://github.com/cvut/qtrvsim/releases)
- 包含 Windows 和通用 GNU/Linux 二进制文件的存档
- [https://build.opensuse.org/repositories/home:jdupak/qtrvsim](https://build.opensuse.org/repositories/home:jdupak/qtrvsim)
- [https://software.opensuse.org/download.html?project=home%3Ajdupak&package=qtrvsim](https://software.opensuse.org/download.html?project=home%3Ajdupak&package=qtrvsim)
- 打开构建服务二进制包
- [https://launchpad.net/~qtrvsimteam/+archive/ubuntu/ppa](https://launchpad.net/~qtrvsimteam/+archive/ubuntu/ppa)
- Ubuntu PPA
```bash
sudo add-apt-repository ppa:qtrvsimteam/ppa
sudo apt-get update
sudo apt-get install qtrvsim
```

### Nix包
QtRVSim 提供 Nix 包作为存储库的一部分。 您可以通过以下命令构建并安装它。 更新必须通过检查 git 手动完成。 NIXPKGS 软件包正处于 PR 阶段。
```shell
nix-env -if .
```

### 测试
测试由 CTest(CMake 的一部分)管理。 要构建并运行所有测试,请使用以下命令:
```bash
cmake -DCMAKE_BUILD_TYPE=Release /path/to/qtRVSim
make
ctest
```

安装完成后,输入`qtrvsim_gui`即可打开前端页面。

## 文档
主要文档在此自述文件和子目录 [`docs/user`](docs/user) 和 [`docs/developer`](docs/developer) 中提供。
该项目是根据 Karel Kočí、Jakub Dupak 和 Max Hollmann 的论文进行开发和扩展的。 有关链接和参考,请参阅[资源和出版物](#resources-and-publications) 部分。
## 可接受的二进制格式
模拟器接受为 RISC-V 目标编译的 ELF 静态链接可执行文件(`--march=rv64g`)。
模拟器会根据ELF文件头自动选择字节序。
仿真将根据 ELF 文件头以 XLEN=32 或 XLEN=32 执行。
- 支持 64 位 RISC-V ISA RV64IM 和 32 位 RV32IM ELF 可执行文件。
- 尚不支持压缩指令。
您可以使用专门的 RISC-V GCC/Binutils 工具链(`riscv32-elf`)或使用带有 [LLD](https://lld.llvm.org/) 的统一 Clang/LLVM 工具链来编译模拟代码。 如果您安装了 Clang,则不需要任何其他工具。 Clang 可以在 Linux、Windows、macOS 等上使用
### LLVM工具链的使用
```shell
clang --target=riscv32 -march=rv32g -nostdlib -static -fuse-ld=lld test.S -o test
llvm-objdump -S test
```
### GNU工具链的使用
```shell
riscv32-elf-as test.S -o test.o
riscv32-elf-ld test.o -o test
riscv32-elf-objdump -S test
```
或者
```shell
riscv32-elf-gcc test.S -o test
riscv32-elf-objdump -S test
```
### 用于 RV32I 的 GNU 64 位工具链
支持 64 位嵌入式工具链的 Multilib 可用于构建可执行文件
```shell
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -o test test.c crt0local.S -lgcc
```
必须设置全局指针和堆栈以设置运行时 C 代码的一致环境。 当没有使用其他 C 库时,可以使用简单的“crt0local.S”。
example code
```asm
/* minimal replacement of crt0.o which is else provided by C library */
.globl main
.globl _start
.globl __start
.option norelax
.text
__start:
_start:
.option push
.option norelax
la gp, __global_pointer$
.option pop
la sp, __stack_end
addi a0, zero, 0
addi a1, zero, 0
jal main
quit:
addi a0, zero, 0
addi a7, zero, 93 /* SYS_exit */
ecall
loop: ebreak
beq zero, zero, loop
.bss
__stack_start:
.skip 4096
__stack_end:
.end _start
```
## 集成汇编器
模拟器中包含基本的集成汇编器。 [GNU 汇编器](https://sourceware.org/binutils/docs/as/)指令的一小部分也被识别。 以下指令也可以识别:`.word`、`.orig`、`.set`、 `.equ`、`.ascii` 和 `.asciz`。 以下的一些指令则是简单的忽略:`.data`、`.text`、`.globl`、`.end` 和 `.ent`。
这允许编写可以由集成和全功能汇编器编译的代码。 地址被分配给存储在符号表中的标签/符号。 可以识别加法、减法、乘法、除法以及按位与或。
## 支持调用外部make实用程序
通过外部 make 构建可执行文件操作调用make程序。 如果调用该操作,并且在主窗口选项卡中选择了一些源编辑器,则在相应的目录中启动make。 否则将选择最后选择的编辑器的目录。 如果没有打开编辑器,则最后加载的 ELF 可执行文件的目录将用作make启动路径。 如果未使用该选项,则使用模拟器启动时的默认目录。
## 最新功能
### 外部设备
Emuated LCD, knobs, buttons, serial port...
模拟器目前实现了两个外设的模拟。
第一个是简单串行端口(UART)。 它支持发送(Tx)和接收(Rx)。 接收器状态寄存器(`SERP_RX_ST_REG`)实现两个位。 只读位 0 (`SERP_RX_ST_REG_READY`)
如果接收器数据寄存器(`SERP_RX_DATA_REG`)中有可用的未读字符,则设置为 1。 当有未读字符时,可以将位 1(`SERP_RX_ST_REG_IE`) 写入 1 以启用中断请求。 发送器状态寄存器 (`SERP_TX_ST_REG`) 位 00(SERP_TX_ST_REG_READY) 通过值 1 发出信号,表示 UART 已准备好并可以接受下一个要发送的字符。 位 1 (`SERP_TX_ST_REG_IE`) 允许生成中断。 寄存器“SERP_TX_DATA_REG”是实际的 Tx 缓冲区。 写入字的 LSB 字节被传输到终端窗口。 外设基址和寄存器偏移量 (`_o`) 以及各个字段掩码 (`_m`) 的定义如下
```
#define SERIAL_PORT_BASE 0xffffc000
#define SERP_RX_ST_REG_o 0x00
#define SERP_RX_ST_REG_READY_m 0x1
#define SERP_RX_ST_REG_IE_m 0x2
#define SERP_RX_DATA_REG_o 0x04
#define SERP_TX_ST_REG_o 0x08
#define SERP_TX_ST_REG_READY_m 0x1
#define SERP_TX_ST_REG_IE_m 0x2
#define SERP_TX_DATA_REG_o 0x0c
```
UART 寄存器区域镜像在地址 0xffff0000 上,以便能够使用 [SPIM](http://spimsimulator.sourceforge.net/) 或 [MARS](http://courses.missouristate.edu/KenVollmar/MARS/)模拟器。
另一个外设允许从用户面板通过旋钮设置连接到单个word(只读 KNOBS_8BIT 寄存器)的三个字节值,并以十六进制、十进制和二进制格式显示一个字(“LED_LINE”寄存器)。 还有另外两个可写word控制 RGB LED 1 和 2 的颜色(寄存器“LED_RGB1”和“LED_RGB2”)。
```
#define SPILED_REG_BASE 0xffffc100
#define SPILED_REG_LED_LINE_o 0x004
#define SPILED_REG_LED_RGB1_o 0x010
#define SPILED_REG_LED_RGB2_o 0x014
#define SPILED_REG_LED_KBDWR_DIRECT_o 0x018
#define SPILED_REG_KBDRD_KNOBS_DIRECT_o 0x020
#define SPILED_REG_KNOBS_8BIT_o 0x024
```
实现了简单的每像素 16 位 (RGB565) 帧缓冲区和 LCD。 帧缓冲区映射到从“LCD_FB_START”地址开始的范围。 显示尺寸为 480 x 320 像素。 像素格式 RGB565 期望红色分量位于位 11到15 中,绿色分量位于位 5到10 中,蓝色分量位于位 0到4 中。
```
#define LCD_FB_START 0xffe00000
#define LCD_FB_END 0xffe4afff
```
限制:内存视图更新和访问的实际概念不允许可靠地读取外设寄存器和 I/O 内存内容。 当选择对内存进行缓存(从 CPU 角度)访问时,可以写入帧缓冲内存。
### 中断、控制和状态寄存器
Implemented CSR registers and their usage
(注意:Coprocessor0 必须替换为 RISC-V 状态寄存器)
中断源列表:
| Irq number | Cause/Status Bit | Source |
| ---------: | ---------------: | :-------------------------- |
| 2 / HW0 | 10 | 串行端口准备好接受字符到 Tx |
| 3 / HW1 | 11 | 有接收到的字符可供读取 |
| 7 / HW5 | 15 | 计数器达到比较寄存器中的值 |
可以识别以下协处理器 0 寄存器
| Number | Name | Description |
| -----: | :-------- | :--------------------------- |
| $4,2 | UserLocal | 通常被操作系统用作 TLS 基础 |
| $8,0 | BadVAddr | 报告最近的地址相关异常的地址 |
| $9,0 | Count | 处理器周期计数 |
| $11,0 | Compare | 定时器中断控制 |
| $12,0 | Status | 处理器状态和控制 |
| $13,0 | Cause | 最后一个异常的原因 |
| $14,0 | EPC | 最后异常时的程序计数器 |
| $15,1 | EBase | 异常向量基址寄存器 |
| $16,0 | Config | 配置寄存器 |
`mtc0` 和 `mfc0` 用于将值从通用寄存器复制到协处理器 0 寄存器或从协处理器 0 寄存器复制值。
实现的硬件/特殊寄存器:
| Number | Name | Description |
| -----: | :--------- | :----------------------------- |
| 0 | CPUNum | CPU编号,固定为0 |
| 1 | SYNCI_Step | 指令缓存同步所需的增量 |
| 2 | CC | 循环计数器 |
| 3 | CCRes | 周期计数器分辨率,固定为 1 |
| 29 | UserLocal | 协处理器 0 $4,2 寄存器的只读值 |
使能串口接收中断的顺序:
首先确定中断服务程序的位置。 默认地址是0x80000180。 可以更改基址(“EBase”寄存器),然后将 PC 设置为地址 EBase + 0x180。 这符合 MIPS 第 1 版和第 2 版手册。
启用状态寄存器中的位 11(中断屏蔽)。 确保位 1 (`EXL`) 为零,位 0 (`IE`) 设置为 1。
在接收器状态寄存器中启用中断(“SERP_RX_ST_REG”的位 1)。
将字符写入终端。 如果在`SERP_RX_ST_REG`中启用了中断,它应该立即被串行端口接收器消耗。 CPU 应报告中断异常,并且当它传播到执行阶段时,“PC”被设置为中断例程的起始地址。
一些提示如何指导链接器将中断处理程序例程放置在适当的地址。 在新部分中实现中断例程
```
.section .irq_handler, "ax"
```
使用下一个链接器选项将节起始位置放置在正确的地址处
```
-Wl,--section-start=.irq_handler=0x80000180
```
### 系统调用支持
Syscall table and documentation
该模拟器支持一些 Linux 内核系统调用。 使用 RV32G ilp32 ABI。
| Register | use on input | use on output | Calling Convention |
|:-----------------------------------|:----------------------|:----------------|:-------------------------------|
| zero (x0) | — | - | Hard-wired zero |
| ra (x1) | — | - | Return address |
| sp (x2) | — | (caller saved) | Stack pointer |
| gp (x3) | — | (caller saved) | Stack pointer |
| tp (x4) | — | (caller saved) | Thread pointer |
| t0 .. t2 (x5 .. x7) | — | - | Temporaries |
| s0/fp (x8) | — | (caller saved) | Saved register/frame pointer |
| s1 (x9) | — | (caller saved) | Saved register |
| a0 (x10) | 1st syscall argument | return value | Function argument/return value |
| a1 (x11) | 2nd syscall argument | - | Function argument/return value |
| a2 .. a5 (x12 .. x15) | syscall arguments | - | Function arguments |
| a6 (x16) | - | - | Function arguments |
| a7 (x17) | syscall number | - | Function arguments |
| s2 .. s11 (x18 .. x27) | — | (caller saved) | Saved registers |
| t3 .. t6 (x28 .. x31) | — | - | Temporaries |
所有系统调用输入参数都在寄存器中传递。
支持的系统调用:
#### void [exit](http://man7.org/linux/man-pages/man2/exit.2.html)(int status) __NR_exit (93)
停止/结束程序的执行。 参数是退出状态代码,零表示正常,其他值表示错误。
#### ssize_t [read](http://man7.org/linux/man-pages/man2/read.2.html)(int fd, void *buf, size_t count) __NR_read (63)
从打开的文件描述符`fd`读取`count`字节。 模拟器将文件描述符 0、1 和 2 映射到内部终端/控制台模拟器。 它们可以在没有`open`调用的情况下使用。 如果没有更多字符可从控制台读取,则会附加换行符。 最多读取的 count 个字节存储到“buf”参数指定的内存位置。 返回实际读取的字节数。
#### ssize_t [write](http://man7.org/linux/man-pages/man2/write.2.html)(int fd, const void *buf, size_t count) __NR_write (64)
将内存位置`buf`中的`count`字节写入打开的文件描述符`fd`。 文件句柄 0、1 和 2 的控制台与`read`相同。
#### int [close](http://man7.org/linux/man-pages/man2/close.2.html)(int fd) __NR_close (57)
关闭与描述符`fd`关联的文件并释放描述符。
#### int [openat](http://man7.org/linux/man-pages/man2/open.2.html)(int dirfd, const char *pathname, int flags, mode_t mode) __NR_openat (56)
打开文件并将其与第一个未使用的文件描述符编号关联并返回该编号。 如果选项`OS Emulation`->`Filesystem root`不为空,则将从模拟环境接收的文件路径`pathname`附加到`Filesystem root`指定的路径。 主机文件系统受到保护,防止尝试使用`..`路径元素遍历到随机目录。 如果未指定根目录,则所有打开的文件都将定位到模拟终端。
#### void * [brk](http://man7.org/linux/man-pages/man2/brk.2.html)(void *addr) __NR_brk (214)
设置程序数据/bss 结束后标准堆使用的区域结束。 系统调用由虚拟实现模拟。 直至 0xffff0000 的整个地址空间由自动连接的 RAM 进行备份。
#### int [ftruncate](http://man7.org/linux/man-pages/man2/ftruncate.2.html)(int fd, off_t length) __NR_truncate (46)
将“fd”指定的打开文件的长度设置为新的`length`。 即使在 32 位系统上,`length`参数也是 64 位,对于大端 MIPS,它在第二个和第三个参数中作为较高部分和较低部分。
#### ssize_t [readv](http://man7.org/linux/man-pages/man2/readv.2.html)(int fd, const struct iovec *iov, int iovcnt) __NR_Linux (65)
`read`系统调用的变体,其中要读取的数据将存储到由`iovcnt`对指定的位置
存储在内存中的基址、长度对通过`iov`传递。
#### ssize_t [writev](http://man7.org/linux/man-pages/man2/writev.2.html)(int fd, const struct iovec *iov, int iovcnt) __NR_Linux (66)
`write`系统调用的变体,其中要写入的数据由`iovcnt`定义
存储在内存中地址处的基地址对、长度对通过`iov`传递。
## 实施的局限性
- 请参阅当前支持的指令列表。
- Coprocessor0 必须移植到 RISC-V 状态寄存器。
### QtMips 原始限制
* 目前仅实现了对特权指令的极少支持。 仅实现了 RDHWR、SYNCI、CACHE 和一些协处理器 0 寄存器。 TLB和虚拟内存以及完整的异常模型都没有实现。
* 协处理器(因此没有浮点单元,只有有限的协处理器 0)
* 内存访问停顿(由于缓存未命中而导致执行停顿对用户来说非常烦人,因此缓存和内存之间的差异仅在收集的统计数据中)
* 仅对中断和异常的有限支持。 当识别到`syscall`或`break`指令时,仿真就会停止。 接受到指令后单步继续。
### 当前支持的指令列表
- **RV32G**:
- **LOAD**: `lw, lh, lb, lwu, lhu, lbu`
- **STORE**: `sw, sh, sb, swu, shu, sbu`
- **OP**: `add, sub, sll, slt, sltu, xor, srl, sra, or, and`
- **MISC-MEM**: `fence, fence.i`
- **OP-IMM**: `addi, sll, slti, sltiu, xori, srli, srai, ori, andi, auipc, lui`
- **BRANCH**: `beq, bne, btl, bge, bltu, bgtu`
- **JUMP**: `jal, jalr`
- **SYSTEM**: `ecall, ebreak, csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci`
- **RV64G**:
- **LOAD/STORE**: `lwu, ld, sd`
- **OP-32**: `addw, subw, sllw, srlw, sraw, or, and`
- **OP-IMM-32**: `addiw, sllw, srliw, sraiw`
- **Pseudoinstructions**
- **BASIC**: `nop`
- **LOAD**: `la, li`,
- **OP**: `mv, not, neg, negw, sext.b, sext.h, sext.w, zext.b, zext.h, zext.w, seqz, snez, sltz, slgz`
- **BRANCH**: `beqz, bnez, blez, bgez, bltz, bgtz, bgt, ble, bgtu, bleu`
- **JUMP**: `j, jal, jr, jalr, ret, call, tail`
- **Extensions**
- **RV32M/RV64M**: `mul, mulh, mulhsu, div, divu, rem, remu`
- **RV64M**: `mulw, divw, divuw, remw, remuw`
- **Zicsr**: `csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci`
有关RISC-V的详细信息,请参考ISA规范:
[https://riscv.org/technical/specifications/](https://riscv.org/technical/specifications/).
## 资源和类似项目的链接
### 资源和出版物
- Computer architectures pages at Czech Technical University in Prague [https://comparch.edu.cvut.cz/](https://comparch.edu.cvut.cz/)
- Dupak, J.; Pisa, P.; Stepanovsky, M.; Koci, K. [QtRVSim – RISC-V Simulator for Computer Architectures Classes](https://comparch.edu.cvut.cz/publications/ewC2022-Dupak-Pisa-Stepanovsky-QtRvSim.pdf) In: [embedded world Conference 2022](https://events.weka-fachmedien.de/embedded-world-conference). Haar: WEKA FACHMEDIEN GmbH, 2022. p. 775-778. ISBN 978-3-645-50194-1. ([Slides](https://comparch.edu.cvut.cz/slides/ewc22-qtrvsim.pdf))
如果您在教育或研究相关材料和出版物中使用 QtRvSim,请参考上述文章。
- [FEE CTU - B35APO - Computer Architectures](https://cw.fel.cvut.cz/wiki/courses/b35apo)
- Undergraduate computer architecture class materials (
Czech) ([English](https://cw.fel.cvut.cz/wiki/courses/b35apo/en/start))
- [FEE CTU - B4M35PAP - Advanced Computer Architectures](https://cw.fel.cvut.cz/wiki/courses/b4m35pap/start)
- Graduate computer architecture class materials (Czech/English)
- [Graphical RISC-V Architecture Simulator - Memory Model and Project Management](https://dspace.cvut.cz/bitstream/handle/10467/94446/F3-BP-2021-Dupak-Jakub-thesis.pdf)
- Jakub Dupak's thesis
- Documents 2020-2021 QtMips and QtRvSim development
- [Graphical CPU Simulator with Cache Visualization](https://dspace.cvut.cz/bitstream/handle/10467/76764/F3-DP-2018-Koci-Karel-diploma.pdf)
- Karel Koci's thesis
- Documents initial QtMips development
### Projects
- **QtMips** - MIPS predecessor of this simulator [https://github.com/cvut/QtMips/](https://github.com/cvut/QtMips/)
- **RARS** - RISC-V Assembler and Runtime
Simulator [https://github.com/TheThirdOne/rars](https://github.com/TheThirdOne/rars)
## Copyright
- Copyright (c) 2017-2019 Karel Koci
- Copyright (c) 2019-2023 Pavel Pisa
- Copyright (c) 2020-2023 Jakub Dupak
- Copyright (c) 2020-2021 Max Hollmann
## License
This project is licensed under `GPL-3.0-or-later`. The full text of the license is in the [LICENSE](LICENSE) file. The
license applies to all files except for directories named `external` and files in them. Files in external directories
have a separate license compatible with the projects license.
> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
>
> This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
>
> You should have received a copy of the GNU General Public License along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
  