在 2023 年初,达坦科技发动成立硬件设计学习社区,邀请所有有志于从事数字芯片设计的同学退出咱们的学习互助自学小组,以了解数字芯片设计的精华,强化理论知识的同时晋升实操技能,继而整体晋升设计能力。当初,实现第一期学习的同学整顿了 MIT6.175 和 MIT6.375 的要害内容以及 Lab 实际的学习笔记。
6.175 和 6.375 的课程和 Lab 学习都有肯定的难度,要求采纳 Bluespec 语言实现 RISC- V 处理器,并反对多级流水、分支预测、缓存、异样解决、缓存一致性等性能。此外,Lab 环节还波及软硬件联合开发,要求基于所实现的 RISC- V 处理器运行实在的 RISC- V 程序,并给出性能评估。心愿第一期学员(GitHub:kazutoiris)的学习笔记对想从事数字芯片设计的工程师有所帮忙。
MIT 6.175
环境搭建虚拟机场景倡议应用 Ubuntu 20.04 + bsc + connectal 进行环境初始化。
⚠️ Alma Linux/Rocky Linux/CentOS 对于 connectal 反对无限,可能会影响到前期仿真。
💡 应用 bsvbuild.sh 能够很不便的进行仿真,而且还反对了一键波形导出等性能。
Docker 场景倡议应用 kazutoiris/connectal 镜像。在 Docker Hub 上的局部镜像应用的 bsc 是旧版,而且并没有装置 connectal。我的镜像由 Ubuntu 22.04 + bsc 2023.1 + connectal + bsvbuild.sh 四局部组成,足够满足 Lab 要求。
version: "3.9"
services:
connectal:
image: kazutoiris/connectal:latest
volumes:
- .:/root
network_mode: none
四种 FIFO (Lab 4)
Lab4 的要求是设计各种 Depth-N FIFO。可并发 FIFO 容许流水线级与级之间相互连接而不引入额定的调度束缚。一个可并发 FIFO 须要实现可并发的 enqueue 和 dequeue 办法。要实现较大深度的 FIFO,须要应用循环缓冲。
💡 握手罕用的 FIFO 实例有三种,别离是:Depth-1 Pipeline FIFO、Depth-1 Bypass FIFO、Depth-2 Conflict-Free FIFO。这三种 FIFO 面积最小(能够省略 notEmpty、notFull 寄存器),且可能满足大多数流水线设计的需要。
- Conflict FIFO 不可同时 deq 和 enq。
-
Conflict-Free FIFO
{notFull, enq, notEmpty, first, deq} < clear
非空时可同时 deq 和 enq。
-
Pipeline FIFO
{notEmpty, first, deq} < {notFull, enq} < clear
满可同时 deq 和 enq。级联会产生较长的 Ready 组合信号链,从后级始终指向前级。
-
Bypass FIFO
{notFull, enq} < {notEmpty, first, deq} < clear
空可同时 deq 和 enq。级联会产生较长的 Valid、Payload 组合信号链,从前级始终指向后级。
Ready 和 Valid 握手
- Valid 打一拍,Ready 不打拍
因为 Ready 快于 Valid,所以当 Ready 由无效变为有效时,须要缓存前一个周期的一拍数据。
(因为这一拍数据刚好就在实现提早一拍的寄存器上,所以能够不必多加寄存器就能够间接实现)
因为第一拍数据送入之前为空,而之后都是处于满的状态,所以能够当成 Depth-1 Pipeline FIFO,在满时可同时进行 deq 和 enq 操作。
case class Delay1() extends Component {
val io = new Bundle {val m = slave Stream UInt(8 bits)
val s = master Stream UInt(8 bits)
}
io.m.ready := ~io.s.valid
io.s.payload := RegNextWhen(io.m.payload, io.m.fire) init 0
io.s.valid := RegNextWhen(Mux(io.s.fire, False, True), io.m.fire || io.s.fire) init False
}
- Ready 打一拍,Valid 不打拍
因为 Valid 快于 Ready,所以当 Ready 由无效变为有效时,线路上多发了一拍数据。这一拍数据就要新增一个 buffer 去进行缓存。
因为第一拍数据送入之前为满,而之后都是处于空的状态,所以能够当成 Depth-1 Bypass FIFO,在空时可同时进行 deq 和 enq 操作。
(这里能够考虑一下传给 master 的 Ready 信号。如果 buffer 为空,那么必然是 Ready 的;如果 buffer 为满,那么必然不是 Ready 的。这样就能够省掉一个寄存器去为 slave 的 Ready 打一拍。)
case class Delay2() extends Component {
val io = new Bundle {val m = slave Stream UInt(8 bits)
val s = master Stream UInt(8 bits)
}
private val buffer = Reg(UInt(8 bits)) init 0
private val bufferValid = RegInit(False)
io.m.ready := !bufferValid // buffer 为空
io.s.payload := Mux(!bufferValid, io.m.payload, buffer) // buffer 为空则直通
io.s.valid := io.m.valid || bufferValid // buffer 为空则直通
when(io.m.fire && !io.s.ready) { // buffer 为空且 m 无效且 s 不 ready
buffer := io.m.payload
bufferValid := True
}
when(io.s.fire) { // buffer 输入后生效
bufferValid := False
}
}
- Ready 和 Valid 各打一拍,要求所有输入都为寄存器输入。
Ready 从无效变为有效的时候,Ready 达到晚一拍,Valid 有一拍无效数据曾经送出去了。等 Ready 达到 master,曾经多送出了两拍无效数据。所以这两拍数据必须被缓存起来。
在数据传输刚开始的时候,第一拍数据是从 FIFO 中直出的。第二拍的时候,slave 的 Ready 送至 master。在之后的数据传输过程中,其中停滞的只有一个数据。
不言而喻,能够应用 Depth-2 Conflict-Free FIFO,在非满非空的时候能够同时 deq 和 enq。master 的 Ready 信号就是 notFull,slave 的 Valid 信号就是 notEmpty。可见,所有输入均为寄存器输入。
case class Delay3() extends Component {
val io = new Bundle {val m = slave Stream UInt(8 bits) // 要求 Ready 为寄存器输入
val s = master Stream UInt(8 bits) // 要求 Valid、Payload 为寄存器输入
}
private val delay1 = Delay1()
private val delay2 = Delay2()
io.m <> delay2.io.m // Ready 为寄存器输入
delay2.io.s <> delay1.io.m
io.s <> delay1.io.s // Valid、Payload 为寄存器输入
}
EHR 寄存器 (Lab4)EHR 寄存器是一种非凡的寄存器,它能够进行并发操作。这意味着能够同时读取和写入 EHR 寄存器,而不须要任何同步或锁定,这使得 EHR 寄存器非常适合于流水线设计。
⚠️ EHR 往往会导致要害门路过长,而且难以跟踪具体门路,所以要尽可能防止在大型项目中应用。
图上能够看出,r[0] 为寄存器输入,r[1] 为组合逻辑输入。
w[1] 具备最高优先级,当 w[1] 写入时,会疏忽 w[0] 的写入。
RISC-V (Lab5)
elf2hex 能够通过 GitHub – riscvarchive/riscv-fesvr: RISC-V Frontend Server 编译失去。只管仓库目前是弃用状态,然而编译进去的工具依然可用。现行仓库 GitHub – riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator 也有这个工具,然而附带了很多不必要的工具。
🔥 trans_vmh.py 须要用 Python 3 运行,须要手动在 Makefile 里批改 python 为 python3。当然,你也能够间接批改文件头,间接作为可执行文件运行。
⚠️ CSR 寄存器在 gcc 中有改变,现行的命名、序号都与测试程序中不同。须要你在编译测试程序时进行批改。
- 双周期(冯诺依曼架构)
取指 -> 执行
在第一个周期中,处理器从内存中读取以后指令并对其进行解码。在第二个周期中,处理器读取寄存器文件,执行指令,进行 ALU 操作、内存操作,并将后果写入寄存器文件。
- 四周期(反对内存提早)
取指 -> 解指 -> 执行 -> 写回
在取指阶段,向内存发送 PC 地址申请,然而不要求立即响应。在解指阶段,读取内存申请响应。在执行阶段,向内存发送数据读取或写入申请,然而不要求立即响应。在写回阶段,读取内存申请响应。
- 两级流水线
取指 -> 执行
与双周期不同的是,在执行的同时在进行取指。当处理器执行分支指令时,下一条指令的地址可能是未知的,这就是所谓的“控制流不确定性”。
这种不确定性会导致处理器在执行分支指令时进展,直到下一条指令的地址被确定。这种进展会升高处理器的性能。为了解决这个问题,能够应用 PC+ 4 预测器来预测下一条指令的地址。如果预测正确,处理器能够继续执行下一条指令。如果预测谬误,则须要回退并从新执行分支指令前面的指令。这种回退会导致处理器进展,从而升高性能。
- 六级流水线 (Lab6)
取指 -> 解指 -> 读取寄存器 -> 执行 -> 内存 -> 写回
取指:向 iMem 申请指令。解指:从 iMem 接管响应并解码指令。读取寄存器:从寄存器文件中读取。执行:执行指令并在必要时重定向。内存:向 dMem 发送申请写回:从 dMem 接管响应,并写入寄存器。
💡 一点调试小技巧能够指定一个 cnt 计数器,在达到肯定时钟周期后强制终止。在调试的时候,测试程序往往会进入死循环,导致日志文件相当宏大,而且过程也无奈被终止。
Branch Prediction (Lab6)
- BHT 记录分支指令最近一次或几次的执行状况(胜利或不胜利),预测是否采纳 BTB 的指标地址跳转(即:跳不跳?taken or not taken)。
- BTB 次要记录分支指令跳转指标地址(即:往哪里跳?branch target)
- RAS 仅用于函数返回地址场景的预测。当程序执行到分支跳转指令时,RAS 须要判断指令是否属于“函数调用”类型的分支跳转指令。如果遇到 rd = x1 的 JAL/JALR 指令时,RAS 入栈返回地址;如果遇到 rd = x0 且 rs1 = x1 的 JALR 指令出栈 RAS,作为函数返回地址。
- BONUS 是一种基于 BHT 和 BTB 的分支预测算法,它将两者的后果进行加权均匀,以失去更为精确的预测后果。
分支预测的记录局部是放在执行环节,预测局部放在取指环节。
💡 在分支预测中,倡议应用 cononicalize 规定去独自解决 PC 指针的重定向。如果你在取指、解码、执行阶段间接进行重定向,往往会导致各种各样的时序环。
ℹ️ 分支预测谬误须要洗刷流水线,能够通过设置一位 canary 并判断值是否统一。相较于间接 clear 全副前级,会更加不便。
❗ 只须要洗刷前级,禁止洗刷后级。
All the images in this section are cited from [jdelgado].
DDR3 Memory (Lab7)
DDR3 提供了大容量、高带宽、高提早的内存。读取形式和之前读取 Mem 基本一致,不过响应位宽是 512 位。返回的指令能够存入 Cache,通过指定 index 来顺次拜访。也正是因为这个起因,DDR3 在拜访时须要对地址做对齐。
⚠️ 务必批改 trans_vmh.py。导入的位宽是按 vmh 文件的位宽来算的,不是按在 RegFile 中指定的位宽来算的。
Cache Coherence (Project)
在我的项目中,须要实现多个外围之间的缓存一致性。
MessageFIFO 优先出栈 resp 申请。MessageRoute 通过遍历所有外围,如果存在对应外围的回应,则会将回应送入对应外围的 MessageFIFO。c2m 是来自 L1 的 DCache(实际上是来自 MessageRoute)的 MessageFIFO 的接口,向上申请和向下响应能够从此接口读取。m2c 是 MessageFIFO 到 L1 的 DCache(实际上是到 MessageRoute)的接口,应将向下申请和向上响应发送到此接口。
ℹ️ 多核运行时会反复打印,能够指定只有 0 号核才能够打印。
阻塞式缓存
阻塞式缓存在接管到未命中的申请后,会期待内存响应后能力持续解决其余申请。这种形式会导致处理器进展,从而升高性能。
整体流程:Ready → StrtMiss → SndFillReq → WaitFillResp → Resp → Ready
Ready:处理器能够继续执行下一条指令。StrtMiss:处理器发动未命中申请。SndFillReq:处理器发送申请到内存。WaitFillResp:处理器期待内存响应。Resp:处理器接管到内存响应。
非阻塞式缓存
非阻塞式缓存可能在接管到未命中的申请后,持续解决其余申请,而不是期待内存响应。
非阻塞式缓存的实现形式是 MSHR [1]。MSHR 是一个队列,用于存储未命中的申请。
非阻塞式缓存的 miss 有三种可能:primary miss:某个块的第一次 miss。secondary miss:对某个曾经在 fetch 的 block 又一次 miss。structural-stall miss:因为 MSHR 不够导致的 miss,会产生阻塞。
MSI 状态机转换模型
- MSI
MSI 是一种缓存一致性协定的状态类型,它代表了缓存行的三种状态:Modified(已批改)、Shared(共享)和 Invalid(有效)。其中,Modified 示意该缓存行已被批改且未被写回主存,Shared 示意该缓存行被多个缓存共享,而 Invalid 示意该缓存行有效,不蕴含无效数据。
- MESI
MESI 协定将“Exclusive”状态增加到 MSI 状态机中,能够缩小只在单个缓存中存在的缓存行的回写次数。
- MOSI
MOSI 协定将“Owner”状态增加到 MSI,能够缩小由从其余处理器读取而触发的写回操作。
- MOESI
MOESI 中的 O 是 Owner 的意思,示意该缓存行已被批改且已被写回主存,E 是 Exclusive 的意思,示意该缓存行只被一个缓存独享。这两个状态能够被用到,也能够被疏忽。All the images in this section are cited from [kshitizdange].
Snooping 协定
所有的一致性控制器依照雷同的程序察看(嗅探)一致性申请,独特保护一致性。依附以后块的所有申请,分布式一致性控制器可能正确地更新示意块状态的状态机。Snooping 协定向所有一致性控制器播送申请,包含发动申请的控制器。一致性申请通常在有序播送网络(如总线)上传输,这能够确保每个一致性控制器都以雷同的程序察看到所有申请,从而保障所有一致性控制器都能够正确地更新缓存块的状态。
Directory 协定
Directory 协定是一种用于解决多处理器中缓存一致性的硬件策略,实用于在分布式共享存储多处理器零碎中实现。它应用目录表来保护缓存块的状态,以便在多个处理器之间共享数据时放弃一致性。当一个处理器想要读取或写入一个缓存块时,它会向目录表发送申请,以确定该块是否已被其余处理器缓存。如果是,则确定哪些处理器缓存了该块。而后,该处理器能够与其余处理器通信以获取或更新该块的正本。
ℹ️ Snooping & Directory
Snooping 协定采纳播送的模式。如果缓存控制器须要发动一个申请,是通过播送申请音讯到所有其它的一致性控制器。
Directory 协定则是缓存控制器单播申请到那个块所在的控制器。每个控制器都保护一个目录,其中蕴含了每个块的状态,例如以后所有者和以后共享者的信息等。当一个申请达到根目录时,控制器递归会查找这个块的目录状态,并进行定向转发
Snooping 协定会播送所有申请,须要较少的硬件资源,然而在大型零碎中可能会导致总线流量过大。
Directory 协定须要更多的硬件资源,但能够提供更好的可扩展性和更少的总线流量。
❗ 对于 Memory ConsistencyCache Coherence → 缓存一致性 Memory Consistency → 内存连贯性
为什么不同时应用 coherence 呢?为什么要辨别统一和连贯呢?
起因:缓存一致性是指多个处理器或外围共享同一缓存时,确保它们都能够拜访雷同的数据并且具备雷同的值。当一个处理器或外围批改了缓存中的数据时,其余处理器或外围应该可能看到这个批改并且更新它们本人的缓存。例如,假如处理器有两个外围,它们都有本人的缓存,并且它们都在读取和写入同一块内存。如果一个外围写入了一个值,那么另一个外围应该可能看到这个值的变动并且更新本人的缓存,确保它也能够读取到最新的值。
内存连贯性是多线程共享同一内存并应用缓存层次结构的要害因素之一。例如,如果一个线程上写入了一个变量,那么在另一个线程读取该变量时,可能读取到该变量的最新值。
缓存一致性的次要对象是外围,而内存连贯性的次要对象是线程。所以,缓存一致性实现依附硬件,而内存一致性依附软件。在具备缓存的零碎中,缓存一致性协定确保了缓存数据的 Cache coherence;而在不具备缓存的零碎中,因为不存在缓存一致性问题,因而只须要思考 Memory consistency。
Part of the content presented in this section has been cited from [AP].
MIT 6.375
Bluespec 中的一些个性
调度属性
- no_implicit_conditions 属性用于断言:一个规定中所有的办法不含隐式条件。
- fire_when_enabled 属性用于断言:当某个规定的显式和隐式条件为真时,该规定必须被触发。也就是说,fire_when_enabled 查看一个规定是否因为与其余规定有抵触,且紧急水平较低,而导致在原本该激活(显式和隐式条件都满足)时被克制了激活。
- descending_urgency 在抵触产生时,指定多个规定的紧急水平,紧急的规定克制不紧急的规定的激活。mutually_exclusive 在多个规定互斥(不会同时激活)的状况下,如果编译器剖析不进去互斥关系,认为抵触会产生,用 mutually_exclusive 通知编译器它们是互斥的。
- conflict_free 在多个规定可能同时激活,但它们中的引起抵触的语句并不会同时执行时,如果编译器剖析不进去互斥关系,认为抵触会产生,用 conflict_free 通知编译器抵触并不会产生。
- preempts 给两个规定强制加上抵触(即便他们之间不抵触),同时指定紧急水平。
❗只管 Bluespec 中能够独自为 rule 指定不同的优先级,然而这往往会引起各种各样的时序抵触。所以个别都用于断言。
泛型束缚💡 和 Java/Kotlin 中的泛型束缚相似,规定了泛型的上界。
CORDIC 算法
CORDIC 算法的运算流程如下:
- 抉择一个初始向量和指标角度。
- 将指标角度合成为多个旋转角度,每个旋转角度都是一个固定的值,能够通过查表失去。
- 对于每个旋转角度,计算出旋转后的向量,并将其作为下一次迭代的初始向量。
- 反复步骤 3 直到达到目标精度或迭代次数。
具体能够参考 第三章 CORDIC · FPGA 并行编程。
一些罕用的概念
Setup Time、Hold Time 和 Clock-to-Q Time
概念
- Setup time 指的是数据在时钟沿(回升沿或降落沿)到来之前必须被稳固放弃的最短时间。如果数据没有在 Setup time 内稳定下来,那么接收端可能无奈正确辨认数据,并产生谬误后果。
- Hold time 指的是数据在时钟沿(回升沿或降落沿)到来之后须要放弃的最短时间。如果数据没有在 Hold time 内失去放弃,那么接收端可能无奈正确地接管到数据,并产生谬误后果。
- Clock-to-Q time 是指从时钟(Clock)信号的回升沿到输入数据(Q)稳固的工夫。
时序剖析
上面以“D 型数据锁存器边际触发器”为示例。
🔥 网上的一些“D 触发器”图片并不是边际触发的,而是电平触发的。写着触发器,实际上是锁存器。
- Setup time 假如此时 CLK 从 0 跳变到 1。
Setup time 指的是数据在时钟沿(回升沿或降落沿)到来之前必须被稳固放弃的最短时间。
如果不稳固会怎么样?如果 D 信号发生变化,会间接影响到 S 端的信号变动。因为前级处于直通模式,前级的变动会间接反映给后级。如果此时进行状态切换,则会导致前级记录谬误数据。这段时间用于洗刷前级谬误数据。
所以,此时的 Setup time 蕴含了前级的四个与非门和一个非门电路。即 D 信号通过门路上的所有门电路。
- Hold time 假如此时 CLK 从 0 跳变到 1。
Hold time 指的是数据在时钟沿(回升沿或降落沿)到来之后须要放弃的最短时间。
其放弃起因和 Setup time 简直统一。因为信号存在提早,并没有立即实现状态切换。所以,此时 CLK 的信号跳变所通过的所有门电路也应该一并思考。
所以,此时的 Hold time 蕴含了前级的四个与非门和两个非门电路。即 D 信号、CLK 信号通过门路上的所有门电路。
- Clock-to-Q time
Clock-to-Q time 是指从时钟(Clock)信号的回升沿到输入数据(Q)稳固的工夫。在图中就是后级的门电路,包含了四个与非门。 - 降落沿时序剖析
下面的都是在剖析回升沿,这是因为两个很显著的起因: - 这张图中的 D 触发器是时钟回升沿触发的。
- 很显著,CLK 信号直通前级,然而通过一个反相器达到后级。所以前级状态切换的速度必定比后级快,所以就没有必要去思考 Setup time 和 Hold time。
ℹ️ 然而,如果是降落沿触发的 D 触发器呢?那么这个反相器会加在前级,前级的延时会比后级高。这种状况为什么在剖析回升沿时候也能够不思考 Setup time 和 Hold time 呢?
剖析:先明确一点,Setup time 和 Hold time 指的是整个电路中最开始的输出信号,对应着图上的 D 信号。
后级从直通进入放弃状态。在 CLK 产生跳变前,前级处于放弃状态,所以前级进入后级的信号在回升沿到降落沿的半个时钟周期内都不会发生变化,这早已满足后级 Setup time 要求,所以不须要思考 Setup time。
当 CLK 产生跳变后,前级处于直通模式。此时,任何 D 信号的变动都会对前级产生影响。然而,D 信号产生的任何变动都须要通过前级的所有门电路。前级门电路的数量大于后级数量,所以前级带来的提早也比后级大。即便变动传递至后级时,后级也早已进入稳固。所以不须要思考 Hold time。
SRAM 单端口
SRAM(1R/W)
SRAM 是通过 BL 和 ~BL 管制读写,WL 管制具体的行。
- SRAM 写 0BL = 0, ~BL = 1, WL = 1。此时 Y = 0。
- SRAM 写 1BL = 1, ~BL = 0, WL = 1。此时 Y = 1。
- SRAM 读 BL = 1, ~BL = 1, WL = 1。通过查看 BL 和 ~BL 的电压升高状况能够晓得 Y 的值。如果 BL 电压升高,那么 Y = 0;如果 ~BL 电压升高,那么 Y = 1。
伪双端口 SRAM(1R1W)
伪双端口具备独立的读写字线(RWL,WWL)和读写位线(RBL,WBL 和 WBLB)。
真双端口 SRAM(2R2W)
具备两套残缺的读写字线和读写位线。
All the images in this section are cited from [宇芯电子].
DRAM
- DRAM 写
- 双侧的 BL 预充电到 0.5V;
- 接通 WL 后,BL 设置成相应的电压;
- 数据写入。
- DRAM 读
- 在 WL 不被激活的时候,两侧的 BL 的电压放弃为 0.5V;
- 接通 WL 后,电容与右侧 BL 接通,局部电荷从电容流出或流入,使其电压小幅减少或减小(足够被检测到);
- 在放大器的作用下,电压较高的一侧越来越高,低的越来越低,最初输入可辨认信号;
- 在电压的帮忙下,电荷从新流入电容,使其充斥电。
ℹ️ SRAM 要 6 个 transistor,而 DRAM 只须要 1 个 transistor。为什么 CPU 外面个别放的是 SRAM,不是 DRAM 呢?
起因:在 CMOS 中,正反馈是指输入信号被放大并反馈到输出端,从而加强输出信号的幅度。这种放大器被称为正反馈放大器。在 CMOS 中,正反馈放大器是由 PMOS 和 NMOS 两个互补的 MOSFET 电晶体管组成的。
SRAM 用了正反馈的锁存器 / 寄存器,速度显然比相似于模仿电路(就是一个模仿的开关对电容充电)的 DRAM 要快很多。
References
[jdelgado] José G. Delgado-Frias, https://eecs.wsu.edu/~jdelgado/CompArch/branch_predictionF12….[kshitizdange] https://kshitizdange.github.io/418CacheSim/final-report[AP] Nagarajan, V., Sorin, D. J., Hill, M. D., & Wood, D. A. (2020). A Primer on Memory Consistency and Cache Coherence, Second Edition. Synthesis Lectures on Computer Architecture, 15(1), 1–294.[宇芯电子] https://www.cnblogs.com/wridy/p/13273377.html
- MSHR 是指“Miss Status Holding Register”,是一种用于缓存中的数据结构,用于保留未命中的缓存申请的状态信息。
达坦科技硬件设计学习社区继续凋谢,点击原文(GitHub – kazutoiris/MIT6.175)理解社区学习详情。若想询问退出细节,请增加小助手微信号:Apathy_no
达坦科技(DatenLord)专一下一代云计算——“天空计算”的基础设施技术,致力于拓宽云计算的边界。达坦科技打造的新一代开源跨云存储平台 DatenLord,通过软硬件深度交融的形式买通云云壁垒,实现无限度跨云存储、跨云联通,建设海量异地、异构数据的对立存储拜访机制,为云上利用提供高性能平安存储反对。以满足不同行业客户对海量数据跨云、跨数据中心高性能拜访的需要。
公众号:达坦科技 DatenLordDatenLord
官网:www.datenlord.io
知乎账号:https://www.zhihu.com/org/da-tan-ke-ji
B 站:https://space.bilibili.com/2017027518