Risc-V CPU 编写 0x01

首先我们来了解下CPU的架构。

为了直到要设计出什么样的架构,必须先确定CPU的需求。我希望这个CPU能够直接运行 Risc-V 工具链从 C 编译出的可执行程序(elf文件)。那么我们必须支持RV32I的指令集,同时编写一个初始化程序,可以是内置在CPU的一段bootloader。我决定让我的CPU支持官方的riscv-tests(https://github.com/riscv/riscv-tests)。因此我会将它添加到我的github上面的submodule里面。同样,riscv-gnu-toolchain 和 verilator 也会添加到submodule里面。至于能不能下载编译成功,还要看github的访问速度了。

好的,现在我们明确了CPU所要支持的测试。接下来确定下 CPU 需要额外支持的功能。

我希望能够让CPU具有一条总线,来连接类似于 RAM,ROM,GPIO,SPI,UART等等的外设。首先写的是一个单周期的版本(非流水线)。

关于一些更复杂的东西可以参考:

Risc-V CPU 编写 0x00

这又是一个新的系列,具体内容是写出一个(多个)risc-v 指令集的 CPU 。具体技术选择使用 chisel 来编写,仿真优先使用 varilator ,最后板级调试优先使用 openocd 。具体流程为先写一个简单的单周期 CPU (支持有限的指令),而后重新编写一个5级流水线、能够移植linux,并且能够板级调试,最好能够实现双发射,并且能够运行 Risc-V 官方的测试程序的一个完整的 CPU ,如果还有时间可能尝试实现用户态与内核态的切换。

首先,毫无疑问应该准备好 chisel 的相关知识。它是基于 scala 语言扩展而来,能够编译出完全可综合的代码。因此首先应该学会 scala 和 chisel。链接如下,较为详细。

之后就应该配置相应的工具链了。由于是开发同时写博客,不能提前预知需要的工具,故这一节可能随时更新。

推荐使用 linux 系统, windows 不提供任何帮助与支持。(最好是 arch 哈哈哈)。

首先安装 java,我用的是 jdk15,其次要安装 scala,确保 scalac 和 scala 命令好用。之后安装chisel,sbt,firrtl,verilator,我用的都是软件源里最新的版本,直接跟着滚就行,喜欢用最新的版本。

之后要测试工具链。具体过程就省略了,随便查查都有的。

这一项目的地址是:

单周期:https://github.com/dupeiran001/SC_RiscV

diy 编译器 0x03 技术栈选择

为了更好的学习rust,我决定将开发全部迁移到 Rust 上,之前由 C++ 构建的代码暂时全部作废。选择 Rust 的原因如下:

  • 更加安全,运行时能够保证更少的错误
  • 效率与C++不相上下,且能构建操作系统,对下一个项目(手撕操作系统)更加友好
  • 喜欢!!!

Rust 的学习可以参考 Rust 的官方文档,有中文的文档,保证能很快的上手,不会耽误项目的进度

diy编译器 0x02 lexical

想实现的目标

首先,我想给自己定一个小目标,有点挑战性的目标。

单纯模仿 c 实现一个编译器并无难度,我希望学到更多的东西。

首先,我想实现模板的一小部分,了解下模板和范型的底层实现原理。

其次,如果可能的话,我想加入多文件的编译控制,并非简单的替换。

最后,如果还有时间的话,我希望能够实现 new 出的指针的自动回收机制(参考Rust的垃圾回收)

当然,如果这些也知识尽量去做,模板是我最想实现的。

文法定义

为了让它像一门语言,我认为 lcc 至少应包含一下几种语句:

预处理 ;函数 ;简单的模板 ;表达式 ;迭代与分支 ;

因此,我对我的文法定义如下:

暂时语法中未添加类与自动回收的规则,如果条件允许会在之后补充。

其中语法规则的命名参考了 c++17 的标准。c++ 17 的EBNF 表示如下:

文法规则概述

文法定义从 <translation-unit> 开始

预处理定义从 <preprocessing-seq> 开始

函数与模板的声明从 <function-defination> 和 <template-declaration> 开始

语句定义从 <statement-seq> 开始

表达式定义从 <expression> 开始

基本符号定义全部归为 <literal>

变量定义从 <type-id> 开始


lcc 的文法支持一定的预处理,函数,模板函数,指针,条件,循环,表达式

变量分为 char bool short int long fload void,和复杂类型 enum

其中 string 和 char 支持转义字符

定义的运算符如下:

? : 条件赋值||逻辑或
连接多个表达式&&逻辑与
|按位或^按位异或
&按位与==判断相等
!=判断不等<小于
>大于<=小于等于
>=大于等于<<左移
>>右移+
*
/%模除
(typeid)强制类型转换++前置自增
前置自减*解引用
&取地址+
!逻辑非
~按位取反[]数组
++后置自增后置自减
->enum指针的元素.enum的元素
static_cast<>逻辑类型转换()括号

运算符全部按照优先级从文法中依次定义。

预处理可以支持:

#if #ifdef #ifndef #elif #else #endif 
#include ""     #include <>
#define #undef
#pragma once
#pragma message()
#pragma error()

条件语句和循环语句支持:

if else
switch case
while
do while
break
continue
goto:

模板定义格式必须如下

template <typename A> 
A* func(A a)
{
    return &a;
}

数字只能为十进制。

diy编译器 0x01 项目组织

项目预计分为多个模块,模块间低耦合,编译器前后端分离,方便移植多平台与多语言。

模块:

(ing)

词法分析模块

中间代码生成模块

代码优化模块

目标代码生成模块

表格管理模块

项目架构

预计项目通过 cmake 编译,各模块独立编译成库,便于相互调用。目录结构如下:

.
├── bin
│   └── tcc
├── cmake
│   ├── toolchain_options.cmake
│   └── utils.cmake
├── CMakeLists.txt
├── conf
├── doc
│   ├── c++17_lexical.html
│   └── lex_lcc.xlsx
├── lib
│   ├── CMakeLists.txt
│   ├── cmdline
│   │   ├── cmdline.cxx
│   │   └── cmdline.h
│   └──libcmdline.a
├── LICENSE
├── platform
├── README.md
├── scripts
├── src
│   ├── CMakeLists.txt
│   ├── common
│   │   └── version.hpp
│   ├── lex
│   │   ├── CMakeLists.txt
│   │   ├── lex.cpp
│   │   ├── lex.hpp
│   │   ├── liblex.a
│   │   └── Makefile
│   ├── main
│   │   ├── main.cpp
│   │   └── main.hpp
└── test

/lib:引用的外部库,如命令行解析,日志生成,多线程调用,数据库

/src:编写的各个模块与上层模块,除顶层模块外全部编译为静态链接库

/test:存放一些测试代码

/scripts:存放测试脚本

diy编译器 0x00 General

why?

这将会是一系列文章,从0开始造轮子。这是本系列的第一个部分,编译原理。本部分会从头编写一个编译器,对应的语言就叫 lcc 吧(less cc),编译器叫做 tcc (tiny cc),计划实现 c++ 语言的一个子集。(包含部分的模板)。短期内暂时没有支持类(class)的计划。系列将会在之后统一在 B 站更新视频,希望补充下在重复造轮子的领域内的学习资源的空白。项目的代码仓库为 https://gitee.com/dupeiran/tcc。

how?

系列目录如下(ing):