はじめに
TenstorrentのBlackholeには、SiFiveのX280が16個搭載されていることは、下記でこのブログでも紹介しました。
そのX280上でLinuxを動かすというのが github にて公開されました。
NotebookLMの音声概要
Tenstorrent Blackhole P100/P150 Linux demo
Linux にて、RISC-V の CPUをサポートしているということなので、dts を dtc にてコンパイルして、それを使う感じのようです。
L2CPU
README.md によると、
- Contains 4x L2CPU blocks
- Each L2CPU contains 4x X280 cores (i.e. 16 cores total; Linux-capable)
- Each L2CPU can run as a single coherent SMP
- Separate L2CPUs aren't coherent
のように、
- 4つのX280が1つのブロックになっていて、これを L2CPU と呼んでいる
- L2CPU が4つある
- 各L2CPU 内の4つのX280間は、coherent である
- 各L2CPU 間は、coherent ではない
各L2CPUにて1つのLinuxが動くので、全体で4つのLinuxが動くことになる
下記のように、0番と1番のL2CPUは独立した4GBのGDDR6メモリを占有できるけど、2番と3番のL2CPUは1つの4GBのGDDR6を共有するので、オーバーラップしないようにメモリ割り当てをする必要があるということ。

2番目と3番目の dts ファイルは、
2番目のdts ファイルの中身は、下記のように
- CPU (4つのコア)
- Memory
- SoC
- タイマー ( clint: time )
- 割り込みコントローラ ( plic: interrupt-controller )
- キャッシュコントローラ (ccache: cache-controller)
- UART (uart)
といたってシンプルな感じです。
/dts-v1/;
// SPDX-FileCopyrightText: © 2025 Tenstorrent AI ULC
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/ {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "tt,whisper";
chosen {
bootargs = "rw console=hvc0 earlycon=sbi panic=-1 root=/dev/pmem0";
};
cpus {
#address-cells = <0x1>;
#size-cells = <0x0>;
timebase-frequency = <50000000>;
cpu@0 {
device_type = "cpu";
reg = <0x0>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcv_zicsr_zifencei_zfh_zba_zbb_zvfh_sscofpmf";
mmu-type = "riscv,sv57";
riscv,pmpregions = <0x10>;
riscv,pmpgranularity = <0x4>;
clock-frequency = <0x3B9ACA00>;
riscv,cboz-block-size = <0x40>;
cpu0_intc: interrupt-controller {
compatible = "riscv,cpu-intc";
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <0>;
};
};
cpu@1 {
device_type = "cpu";
reg = <0x1>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcv_zicsr_zifencei_zfh_zba_zbb_zvfh_sscofpmf";
mmu-type = "riscv,sv57";
riscv,pmpregions = <0x10>;
riscv,pmpgranularity = <0x4>;
clock-frequency = <0x3B9ACA00>;
riscv,cboz-block-size = <0x40>;
cpu1_intc: interrupt-controller {
compatible = "riscv,cpu-intc";
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <0>;
};
};
cpu@2 {
device_type = "cpu";
reg = <0x2>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcv_zicsr_zifencei_zfh_zba_zbb_zvfh_sscofpmf";
mmu-type = "riscv,sv57";
riscv,pmpregions = <0x10>;
riscv,pmpgranularity = <0x4>;
clock-frequency = <0x3B9ACA00>;
riscv,cboz-block-size = <0x40>;
cpu2_intc: interrupt-controller {
compatible = "riscv,cpu-intc";
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <0>;
};
};
cpu@3 {
device_type = "cpu";
reg = <0x3>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcv_zicsr_zifencei_zfh_zba_zbb_zvfh_sscofpmf";
mmu-type = "riscv,sv57";
riscv,pmpregions = <0x10>;
riscv,pmpgranularity = <0x4>;
clock-frequency = <0x3B9ACA00>;
riscv,cboz-block-size = <0x40>;
cpu3_intc: interrupt-controller {
compatible = "riscv,cpu-intc";
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <0>;
};
};
};
// This l2cpu uses first 2G of memory tile
memory@0 {
device_type = "memory";
reg = <0x4000 0x30000000 0x0 0x80000000>;
};
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
// Don't map last 1200MB of memory
pmem: memory@400065000000 {
reg = <0x4000 0x65000000 0x0 0x4b000000>;
no-map;
};
};
// Last 1200MB of 2G Memory used for rootfs
// PMEM regions are not part of 'memory'
pmem@2896 {
compatible = "pmem-region";
reg = <0x4000 0x65000000 0x0 0x4b000000>;
};
soc {
#address-cells = <0x2>;
#size-cells = <0x2>;
compatible = "simple-bus";
ranges;
clint: timer@2000000 {
compatible = "riscv,clint0";
reg = <0x0 0x2000000 0x0 0x10000>;
// 3 = software interrupt
// 7 = timer interrupt
interrupts-extended = <&cpu0_intc 0x3>, <&cpu0_intc 0x7>, <&cpu1_intc 0x3>, <&cpu1_intc 0x7>,
<&cpu2_intc 0x3>, <&cpu2_intc 0x7>, <&cpu3_intc 0x3>, <&cpu3_intc 0x7>;
};
plic: interrupt-controller@c000000 {
compatible = "sifive,plic-1.0.0";
reg = <0x0 0x0c000000 0x0 0x04000000>,
<0x0 0x2FF60000 0x0 0xF>; /* TT MSI catcher */
interrupts-extended = <&cpu0_intc 11>, <&cpu0_intc 9>, <&cpu1_intc 11>, <&cpu1_intc 9>,
<&cpu2_intc 11>, <&cpu2_intc 9>, <&cpu3_intc 11>, <&cpu3_intc 9>;
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <0>;
riscv,ndev = <128>;
};
ccache: cache-controller@2010000 {
compatible = "starfive,jh7100-ccache", "cache";
cache-block-size = <64>;
cache-level = <2>;
cache-sets = <2048>;
cache-size = <2097152>;
cache-unified;
interrupt-parent = <&plic>;
interrupts = <1>, <3>, <4>, <2>;
reg = <0x0 0x2010000 0x0 0x1000>;
status = "disabled";
};
uart@2030000000 {
compatible = "ns16550a";
reg = <0x20 0x30000000 0x0 0x100>;
reg-shift = <0x2>;
reg-io-width = <0x4>;
clock-frequency = <0x2FAF080>;
current-speed = <460800>;
status = "disabled"; // no physical UART in scrappy
};
};
};
L2CPU の位置
にて、L2CPUの位置は、
L2CPU_CORES_NOC0 = {{8, 10}, {8, 4}, {8, 8}, {8, 6}}
ということでしたが、
ドキュメントの「What are the NOC0 coordinates of the L2CPU blocks?」に、
- L2CPU0: (8, 3)
- L2CPU1: (8, 9)
- L2CPU2: (8, 5)
- L2CPU3: (8, 7)
とあり、上の値とは違います。また、「NOC0?」
There are two independent NOCs: NOC0 and NOC1. Only NOC0 is used in this demo. NOC1 coordinates of the L2CPU blocks are as follows.
- L2CPU0: (8, 8)
- L2CPU1: (8, 2)
- L2CPU2: (8, 6)
- L2CPU3: (8, 4)
とあります。[8, 10] と [8, 2] が違いますね。
再度、ここ を確認したら、
L2CPU_CORES_NOC0 = {{8, 3}, {8, 9}, {8, 5}, {8, 7}};
に変わっていました。これだと、最初のものと同じになりますね。
L2CPU と GDDR6 の関係

のように、L2CPU-0 と L2CPU-1 は、4GB、L2CPU-2 と L2CPU-3 は4GBを共用になっています。
ソースコード解析によって、GDDR6とNoCとの接続は下記のように繋がっているようです。Blackholeは32GBのGDDR6を搭載しています。8個のGDDR6なので1個4GBです。

上記の図からL2CPU-0 と L2CPU-1 は、1つのGDDR6に接続していますが、L2CPU-2 と L2CPU-3 は同じGDDR6に接続しています。これは、上記の図と一致します。ね。
右下のGDDR6-7には、ARCが接続していますね。しかしながら、tt-zephyr-platforms のここによると、このGDDR6は使わないで ARC 内にある 512KB のSRAM を使うようです。
Blackhole 用の Linux
ここにあります。
dts ファイルは、
です。
ネットワークとして、
がありますね。
おわりに
TenstorrentのBlackholeは、
と、色々なものが入っていて、かなりの部分のソフトウェアがオープンソースとして公開されています。
github に公開されているソフトウェアを読み込むことで色々なことを学ぶことができます。
そして、$999 の Blackhole を購入して、実際に手を動かして、動作確認すれば、より理解が深まると思います。
現在のAIシステムを学ぶには本当に素晴らしい教材だと思います。
みなさん、いかがですか?