@samitouri / QOSamiQemu / commits / 7778f4b27b

hw/riscv: Add Tenstorrent Atlantis machine

The Tenstorrent Atlantis platform is a collaboration between Tenstorrent and CoreLab Technology. It is based on the Atlantis SoC, which includes the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology. The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant RISC-V CPU. Add the tt-atlantis machine containing serial console, interrupt controllers, and device tree support. The Atlantis boot images loaded from include OpenSBI and an initial DTB that is passed to OpenSBI. This is approximated in the model by having QEMU build the device tree rather than load a DTB image directly. Subsequent stages may use the modified DTB provided by OpenSBI or opt to supply their own. qemu-system-riscv64 -M tt-atlantis -m 512M \ -kernel Image -initrd rootfs.cpio -nographic Co-Developed-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> Signed-off-by: Joel Stanley <joel@jms.id.au> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Message-ID: <20260630024952.1520546-8-joel@jms.id.au> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>

Joel Stanley committed Jun 30, 2026 at 12:19 UTC 7778f4b27bb1068fb361ca60e6f3f9a43080619f
7 files changed +638
MAINTAINERS
+12
@@ -1790,6 +1790,18 @@ F: hw/*/*sifive*.c
1790 F: include/hw/*/*sifive*.h
1791 F: tests/functional/riscv64/test_sifive_u.py
1792
1793 +Tenstorrent Machines
1794 +M: Joel Stanley <joel@jms.id.au>
1795 +M: Nicholas Piggin <npiggin@gmail.com>
1796 +R: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
1797 +R: Michael Ellerman <mpe@kernel.org>
1798 +R: Portia Stephens <portias@oss.tenstorrent.com>
1799 +L: qemu-riscv@nongnu.org
1800 +S: Supported
1801 +F: docs/system/riscv/tt_*.rst
1802 +F: hw/riscv/tt_*.c
1803 +F: include/hw/riscv/tt_*.h
1804 +
1805 AMD Microblaze-V Generic Board
1806 M: Sai Pavan Boddu <sai.pavan.boddu@amd.com>
1807 S: Maintained
docs/system/riscv/tt_atlantis.rst new
+41
@@ -0,0 +1,41 @@
1 +Tenstorrent Atlantis (``tt-atlantis``)
2 +======================================
3 +
4 +The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
5 +and CoreLab Technology. It is based on the Atlantis SoC, which includes
6 +the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
7 +
8 +The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
9 +RISC-V CPU.
10 +
11 +tt-atlantis QEMU model features
12 +-------------------------------
13 +
14 +* 8-core Ascalon-X CPU Cluster
15 +* RISC-V compliant Advanced Interrupt Architecture
16 +* 16550A compatible UART
17 +
18 +Known limitations
19 +-----------------
20 +
21 +The QEMU tt-atlantis machine does not yet model every device on the
22 +real platform. Notably:
23 +
24 +* There is no PCI host bridge, so virtio-pci devices cannot be
25 + attached. Boots that need block storage must use ``-initrd`` with
26 + an initramfs.
27 +* The DesignWare UART is modelled with QEMU's ns16550-compatible
28 + ``serial_mm`` device; DesignWare-specific registers beyond that
29 + set return 0.
30 +
31 +Supported software
32 +------------------
33 +
34 +The Tenstorrent Ascalon CPUs avoid proprietary or non-standard
35 +extensions, so compatibility with existing software is generally
36 +good. The QEMU tt-atlantis machine works with upstream OpenSBI
37 +and Linux with default configurations.
38 +
39 +The development board hardware will require some implementation
40 +specific setup in firmware which is being developed and may
41 +become a requirement or option for the tt-atlantis machine.
docs/system/target-riscv.rst
+1
@@ -72,6 +72,7 @@ undocumented; you can get a complete list by running
72 riscv/mips
73 riscv/shakti-c
74 riscv/sifive_u
75 + riscv/tt_atlantis
76 riscv/virt
77 riscv/xiangshan-kunminghu
78
hw/riscv/Kconfig
+10
@@ -120,6 +120,16 @@ config SPIKE
120 select RISCV_ACLINT
121 select SIFIVE_PLIC
122
123 +config TENSTORRENT
124 + bool
125 + default y
126 + depends on RISCV64
127 + select RISCV_ACLINT
128 + select RISCV_APLIC
129 + select RISCV_IMSIC
130 + select SERIAL_MM
131 + select DEVICE_TREE
132 +
133 config XIANGSHAN_KUNMINGHU
134 bool
135 default y
hw/riscv/meson.build
+1
@@ -11,6 +11,7 @@ riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c'))
11 riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c'))
12 riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c'))
13 riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c'))
14 +riscv_ss.add(when: 'CONFIG_TENSTORRENT', if_true: files('tt_atlantis.c'))
15 riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c'))
16 riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
17 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
hw/riscv/tt_atlantis.c new
+523
@@ -0,0 +1,523 @@
1 +/*
2 + * Tenstorrent Atlantis RISC-V System on Chip
3 + *
4 + * SPDX-License-Identifier: GPL-2.0-or-later
5 + *
6 + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
7 + */
8 +
9 +#include "qemu/osdep.h"
10 +#include "qemu/cutils.h"
11 +#include "qemu/error-report.h"
12 +#include "qemu/guest-random.h"
13 +#include "qemu/units.h"
14 +
15 +#include "hw/core/boards.h"
16 +#include "hw/core/loader.h"
17 +#include "hw/core/sysbus.h"
18 +
19 +#include "target/riscv/cpu.h"
20 +#include "target/riscv/pmu.h"
21 +
22 +#include "hw/riscv/boot.h"
23 +#include "hw/riscv/fdt-common.h"
24 +#include "hw/riscv/machines-qom.h"
25 +#include "hw/riscv/riscv_hart.h"
26 +
27 +#include "hw/char/serial-mm.h"
28 +#include "hw/intc/riscv_aclint.h"
29 +#include "hw/misc/unimp.h"
30 +
31 +#include "system/system.h"
32 +#include "system/device_tree.h"
33 +
34 +#include "hw/riscv/tt_atlantis.h"
35 +
36 +#include "aia.h"
37 +
38 +#define TT_IRQCHIP_NUM_MSIS 255
39 +#define TT_IRQCHIP_NUM_SOURCES 128
40 +#define TT_IRQCHIP_NUM_PRIO_BITS 3
41 +#define TT_IRQCHIP_GUESTS 63 /* aia_guests, gives guest_index_bits=6 */
42 +#define TT_IRQCHIP_MIMSIC_STRIDE 0x40000
43 +
44 +#define TT_ACLINT_MTIME_SIZE 0x8050
45 +#define TT_ACLINT_MTIME 0x0
46 +#define TT_ACLINT_MTIMECMP 0x8000
47 +#define TT_ACLINT_TIMEBASE_FREQ 1000000000
48 +
49 +static const MemMapEntry tt_atlantis_memmap[] = {
50 + /* Keep sorted with :'<,'>!sort -g -k 4 */
51 + [TT_ATL_DDR_LO] = { 0x00000000, 0x80000000 },
52 + [TT_ATL_BOOTROM] = { 0x80000000, 0x2000 },
53 + [TT_ATL_MIMSIC] = { 0xa0000000, 0x200000 },
54 + [TT_ATL_ACLINT] = { 0xa2180000, 0x10000 },
55 + [TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 },
56 + [TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 },
57 + [TT_ATL_UART1] = { 0xd4110000, 0x10000 },
58 + [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
59 + [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
60 +};
61 +
62 +static uint32_t fdt_phandle = 1;
63 +static uint32_t next_phandle(void)
64 +{
65 + return fdt_phandle++;
66 +}
67 +
68 +static void create_fdt_memory(TTAtlantisState *s)
69 +{
70 + void *fdt = MACHINE(s)->fdt;
71 + hwaddr size_lo = MACHINE(s)->ram_size;
72 + hwaddr size_hi = 0;
73 +
74 + if (size_lo > s->memmap[TT_ATL_DDR_LO].size) {
75 + size_lo = s->memmap[TT_ATL_DDR_LO].size;
76 + size_hi = MACHINE(s)->ram_size - size_lo;
77 + }
78 +
79 + create_fdt_socket_memory(fdt, s->memmap[TT_ATL_DDR_LO].base, size_lo,
80 + 0, false);
81 + if (size_hi) {
82 + /*
83 + * The first part of the HI address is aliased at the LO address
84 + * so do not include that as usable memory. Is there any way
85 + * (or good reason) to describe that aliasing 2GB with DT?
86 + */
87 + create_fdt_socket_memory(fdt, s->memmap[TT_ATL_DDR_HI].base + size_lo,
88 + size_hi, 0, false);
89 + }
90 +}
91 +
92 +static void create_fdt_aclint(TTAtlantisState *s, uint32_t *intc_phandles)
93 +{
94 + void *fdt = MACHINE(s)->fdt;
95 + g_autofree char *name = NULL;
96 + g_autofree uint32_t *aclint_mtimer_cells = NULL;
97 + uint32_t aclint_cells_size;
98 + hwaddr addr;
99 +
100 + aclint_mtimer_cells = g_new0(uint32_t, s->soc.num_harts * 2);
101 +
102 + for (int cpu = 0; cpu < s->soc.num_harts; cpu++) {
103 + aclint_mtimer_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
104 + aclint_mtimer_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_M_TIMER);
105 + }
106 + aclint_cells_size = s->soc.num_harts * sizeof(uint32_t) * 2;
107 +
108 + addr = s->memmap[TT_ATL_ACLINT].base;
109 +
110 + name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIX, addr);
111 + qemu_fdt_add_subnode(fdt, name);
112 + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aclint-mtimer");
113 + qemu_fdt_setprop_sized_cells(fdt, name, "reg",
114 + 2, addr + TT_ACLINT_MTIME,
115 + 2, 0x1000,
116 + 2, addr + TT_ACLINT_MTIMECMP,
117 + 2, 0x1000);
118 + qemu_fdt_setprop(fdt, name, "interrupts-extended",
119 + aclint_mtimer_cells, aclint_cells_size);
120 +}
121 +
122 +static void create_fdt_one_imsic(void *fdt, const MemMapEntry *mem, int cpus,
123 + uint32_t *intc_phandles, uint32_t msi_phandle,
124 + int irq_line, uint32_t imsic_guest_bits)
125 +{
126 + g_autofree char *name = NULL;
127 + g_autofree uint32_t *imsic_cells = g_new0(uint32_t, cpus * 2);
128 +
129 + for (int cpu = 0; cpu < cpus; cpu++) {
130 + imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
131 + imsic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
132 + }
133 +
134 + name = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
135 + qemu_fdt_add_subnode(fdt, name);
136 + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,imsics");
137 +
138 + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 0);
139 + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
140 + qemu_fdt_setprop(fdt, name, "msi-controller", NULL, 0);
141 + qemu_fdt_setprop(fdt, name, "interrupts-extended",
142 + imsic_cells, sizeof(uint32_t) * cpus * 2);
143 + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
144 + qemu_fdt_setprop_cell(fdt, name, "riscv,num-ids", TT_IRQCHIP_NUM_MSIS);
145 +
146 + if (imsic_guest_bits) {
147 + qemu_fdt_setprop_cell(fdt, name, "riscv,guest-index-bits",
148 + imsic_guest_bits);
149 + }
150 + qemu_fdt_setprop_cell(fdt, name, "phandle", msi_phandle);
151 +}
152 +
153 +static void create_fdt_one_aplic(void *fdt,
154 + const MemMapEntry *mem,
155 + uint32_t msi_phandle,
156 + uint32_t *intc_phandles,
157 + uint32_t aplic_phandle,
158 + uint32_t aplic_child_phandle,
159 + int irq_line, int num_harts)
160 +{
161 + g_autofree char *name =
162 + g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
163 + g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2);
164 +
165 + for (int cpu = 0; cpu < num_harts; cpu++) {
166 + aplic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
167 + aplic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
168 + }
169 +
170 + qemu_fdt_add_subnode(fdt, name);
171 + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aplic");
172 + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 0);
173 + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 2);
174 + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
175 +
176 + qemu_fdt_setprop(fdt, name, "interrupts-extended",
177 + aplic_cells, num_harts * sizeof(uint32_t) * 2);
178 + qemu_fdt_setprop_cell(fdt, name, "msi-parent", msi_phandle);
179 +
180 + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
181 + qemu_fdt_setprop_cell(fdt, name, "riscv,num-sources",
182 + TT_IRQCHIP_NUM_SOURCES);
183 +
184 + if (aplic_child_phandle) {
185 + qemu_fdt_setprop_cell(fdt, name, "riscv,children",
186 + aplic_child_phandle);
187 + qemu_fdt_setprop_cells(fdt, name, "riscv,delegation",
188 + aplic_child_phandle, 1, TT_IRQCHIP_NUM_SOURCES);
189 + }
190 +
191 + qemu_fdt_setprop_cell(fdt, name, "phandle", aplic_phandle);
192 +}
193 +
194 +static void create_fdt_pmu(TTAtlantisState *s)
195 +{
196 + char pmu_name[] = "/pmu";
197 + void *fdt = MACHINE(s)->fdt;
198 + RISCVCPU *hart = &s->soc.harts[0];
199 +
200 + qemu_fdt_add_subnode(fdt, pmu_name);
201 + qemu_fdt_setprop_string(fdt, pmu_name, "compatible", "riscv,pmu");
202 + riscv_pmu_generate_fdt_node(fdt, hart->pmu_avail_ctrs, pmu_name);
203 +}
204 +
205 +static void create_fdt_cpu(TTAtlantisState *s, const MemMapEntry *memmap,
206 + uint32_t aplic_s_phandle,
207 + uint32_t imsic_s_phandle)
208 +{
209 + MachineState *ms = MACHINE(s);
210 + void *fdt = MACHINE(s)->fdt;
211 + g_autofree uint32_t *intc_phandles = g_new0(uint32_t, ms->smp.cpus);
212 +
213 + fdt_create_cpu_socket_subnode(fdt, TT_ACLINT_TIMEBASE_FREQ);
214 +
215 + create_fdt_socket_cpus(fdt, s->soc.harts, 0, s->soc.num_harts,
216 + s->soc.hartid_base, &fdt_phandle, intc_phandles,
217 + false, false);
218 +
219 + create_fdt_memory(s);
220 +
221 + create_fdt_aclint(s, intc_phandles);
222 +
223 + uint32_t imsic_guest_bits = imsic_num_bits(TT_IRQCHIP_GUESTS + 1);
224 +
225 + /* M-level IMSIC node */
226 + uint32_t msi_m_phandle = next_phandle();
227 + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_MIMSIC], ms->smp.cpus,
228 + intc_phandles, msi_m_phandle,
229 + IRQ_M_EXT, imsic_guest_bits);
230 +
231 + /* S-level IMSIC node */
232 + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_SIMSIC], ms->smp.cpus,
233 + intc_phandles, imsic_s_phandle,
234 + IRQ_S_EXT, imsic_guest_bits);
235 +
236 + uint32_t aplic_m_phandle = next_phandle();
237 +
238 + /* M-level APLIC node */
239 + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_MAPLIC],
240 + msi_m_phandle, intc_phandles,
241 + aplic_m_phandle, aplic_s_phandle,
242 + IRQ_M_EXT, s->soc.num_harts);
243 +
244 + /* S-level APLIC node */
245 + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_SAPLIC],
246 + imsic_s_phandle, intc_phandles,
247 + aplic_s_phandle, 0,
248 + IRQ_S_EXT, s->soc.num_harts);
249 +}
250 +
251 +static void create_fdt_uart(void *fdt, const MemMapEntry *mem, int irq,
252 + int irqchip_phandle)
253 +{
254 + g_autofree char *name = g_strdup_printf("/soc/serial@%"HWADDR_PRIX,
255 + mem->base);
256 +
257 + qemu_fdt_add_subnode(fdt, name);
258 + qemu_fdt_setprop_string(fdt, name, "compatible", "ns16550a");
259 + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
260 + qemu_fdt_setprop_cell(fdt, name, "reg-shift", 2);
261 + qemu_fdt_setprop_cell(fdt, name, "reg-io-width", 4);
262 + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 3686400);
263 + qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
264 + qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
265 +
266 + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", name);
267 + qemu_fdt_setprop_string(fdt, "/aliases", "serial0", name);
268 +}
269 +
270 +static void create_fdt_rng(void *fdt)
271 +{
272 + uint8_t rng_seed[32];
273 +
274 + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
275 + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
276 +}
277 +
278 +static void finalize_fdt(TTAtlantisState *s)
279 +{
280 + uint32_t aplic_s_phandle = next_phandle();
281 + uint32_t imsic_s_phandle = next_phandle();
282 + void *fdt = MACHINE(s)->fdt;
283 +
284 + create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
285 +
286 + /*
287 + * We want to do this, but the Linux aplic driver was broken before v6.16
288 + *
289 + * qemu_fdt_setprop_cell(MACHINE(s)->fdt, "/soc", "interrupt-parent",
290 + * aplic_s_phandle);
291 + */
292 +
293 + create_fdt_uart(fdt, &s->memmap[TT_ATL_UART1], TT_ATL_UART1_IRQ,
294 + aplic_s_phandle);
295 +}
296 +
297 +static void create_fdt(TTAtlantisState *s)
298 +{
299 + MachineState *ms = MACHINE(s);
300 +
301 + ms->fdt = create_board_device_tree("Tenstorrent Atlantis RISC-V Machine",
302 + "tenstorrent,atlantis", &s->fdt_size);
303 +
304 + qemu_fdt_add_subnode(ms->fdt, "/chosen");
305 +
306 + create_fdt_rng(ms->fdt);
307 +
308 + qemu_fdt_add_subnode(ms->fdt, "/aliases");
309 +
310 + create_fdt_pmu(s);
311 +}
312 +
313 +static void load_fdt(TTAtlantisState *s)
314 +{
315 + MachineState *ms = MACHINE(s);
316 + char **node_path;
317 + Error *err = NULL;
318 +
319 + ms->fdt = load_device_tree(ms->dtb, &s->fdt_size);
320 + if (!ms->fdt) {
321 + error_report("load_device_tree() failed");
322 + exit(1);
323 + }
324 +
325 + qemu_fdt_add_path(ms->fdt, "/chosen");
326 +
327 + /* Clear memory nodes and update with the specified RAM size */
328 + node_path = qemu_fdt_node_unit_path(ms->fdt, "memory", &err);
329 + if (err) {
330 + warn_report_err(err);
331 + } else {
332 + for (int i = 0; node_path[i]; i++) {
333 + warn_report("Replacing device tree %s with the requested RAM size",
334 + node_path[i]);
335 + qemu_fdt_nop_node(ms->fdt, node_path[i]);
336 + }
337 + g_strfreev(node_path);
338 + }
339 +
340 + create_fdt_memory(s);
341 +}
342 +
343 +static void tt_atlantis_machine_done(Notifier *notifier, void *data)
344 +{
345 + TTAtlantisState *s = container_of(notifier, TTAtlantisState, machine_done);
346 + MachineState *machine = MACHINE(s);
347 + hwaddr start_addr = s->memmap[TT_ATL_DDR_LO].base;
348 + hwaddr mem_size;
349 + target_ulong firmware_end_addr, kernel_start_addr;
350 + const char *firmware_name = riscv_default_firmware_name(&s->soc);
351 + uint64_t fdt_load_addr;
352 + uint64_t kernel_entry;
353 + RISCVBootInfo boot_info;
354 +
355 + /*
356 + * A user provided dtb must include everything, including
357 + * dynamic sysbus devices. Our FDT needs to be finalized.
358 + */
359 + if (machine->dtb == NULL) {
360 + finalize_fdt(s);
361 + }
362 +
363 + mem_size = machine->ram_size;
364 + if (mem_size > s->memmap[TT_ATL_DDR_LO].size) {
365 + mem_size = s->memmap[TT_ATL_DDR_LO].size;
366 + }
367 + riscv_boot_info_init_discontig_mem(&boot_info, &s->soc,
368 + s->memmap[TT_ATL_DDR_LO].base,
369 + mem_size);
370 +
371 + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
372 + firmware_name,
373 + &start_addr, NULL);
374 +
375 + kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
376 + firmware_end_addr);
377 + if (machine->kernel_filename) {
378 + riscv_load_kernel(machine, &boot_info, kernel_start_addr,
379 + true, NULL);
380 + }
381 + kernel_entry = boot_info.image_low_addr;
382 +
383 + fdt_load_addr = riscv_compute_fdt_addr(s->memmap[TT_ATL_DDR_LO].base,
384 + s->memmap[TT_ATL_DDR_LO].size,
385 + machine, &boot_info);
386 + riscv_load_fdt(fdt_load_addr, machine->fdt);
387 +
388 + /* load the reset vector */
389 + riscv_setup_rom_reset_vec(machine, &s->soc, start_addr,
390 + s->memmap[TT_ATL_BOOTROM].base,
391 + s->memmap[TT_ATL_BOOTROM].size,
392 + kernel_entry,
393 + fdt_load_addr);
394 +}
395 +
396 +static void tt_atlantis_machine_init(MachineState *machine)
397 +{
398 + TTAtlantisState *s = TT_ATLANTIS_MACHINE(machine);
399 +
400 + MemoryRegion *system_memory = get_system_memory();
401 + MemoryRegion *ram_hi = g_new(MemoryRegion, 1);
402 + MemoryRegion *ram_lo = g_new(MemoryRegion, 1);
403 + MemoryRegion *bootrom = g_new(MemoryRegion, 1);
404 + ram_addr_t lo_ram_size;
405 + int hart_count = machine->smp.cpus;
406 +
407 + s->memmap = tt_atlantis_memmap;
408 +
409 + object_initialize_child(OBJECT(machine), "soc", &s->soc,
410 + TYPE_RISCV_HART_ARRAY);
411 + object_property_set_str(OBJECT(&s->soc), "cpu-type", machine->cpu_type,
412 + &error_abort);
413 + object_property_set_int(OBJECT(&s->soc), "hartid-base", 0,
414 + &error_abort);
415 + object_property_set_int(OBJECT(&s->soc), "num-harts", hart_count,
416 + &error_abort);
417 + object_property_set_int(OBJECT(&s->soc), "resetvec",
418 + s->memmap[TT_ATL_BOOTROM].base,
419 + &error_abort);
420 + sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal);
421 +
422 + s->irqchip = riscv_create_aia(true, TT_IRQCHIP_GUESTS,
423 + TT_IRQCHIP_MIMSIC_STRIDE,
424 + TT_IRQCHIP_NUM_SOURCES,
425 + &s->memmap[TT_ATL_MAPLIC],
426 + &s->memmap[TT_ATL_SAPLIC],
427 + &s->memmap[TT_ATL_MIMSIC],
428 + &s->memmap[TT_ATL_SIMSIC],
429 + 0, 0, hart_count,
430 + TT_IRQCHIP_NUM_MSIS,
431 + TT_IRQCHIP_NUM_PRIO_BITS);
432 +
433 + riscv_aclint_mtimer_create(s->memmap[TT_ATL_ACLINT].base,
434 + TT_ACLINT_MTIME_SIZE,
435 + 0, hart_count,
436 + TT_ACLINT_MTIMECMP,
437 + TT_ACLINT_MTIME,
438 + TT_ACLINT_TIMEBASE_FREQ, true);
439 +
440 + /*
441 + * DDR
442 + *
443 + * The high address is where RAM lives. It is always present and may be
444 + * up to 64GB. The low address is an alias of the first 2GB of that RAM.
445 + */
446 + if (machine->ram_size > s->memmap[TT_ATL_DDR_HI].size) {
447 + char *sz = size_to_str(s->memmap[TT_ATL_DDR_HI].size);
448 + error_report("RAM size is too large, maximum is %s", sz);
449 + g_free(sz);
450 + exit(EXIT_FAILURE);
451 + }
452 +
453 + memory_region_init_alias(ram_hi, OBJECT(machine), "ram.high", machine->ram,
454 + 0, machine->ram_size);
455 + memory_region_add_subregion(system_memory,
456 + s->memmap[TT_ATL_DDR_HI].base, ram_hi);
457 +
458 + lo_ram_size = MIN(machine->ram_size, s->memmap[TT_ATL_DDR_LO].size);
459 + memory_region_init_alias(ram_lo, OBJECT(machine), "ram.low", machine->ram,
460 + 0, lo_ram_size);
461 + memory_region_add_subregion(system_memory,
462 + s->memmap[TT_ATL_DDR_LO].base, ram_lo);
463 +
464 + /* Boot ROM */
465 + memory_region_init_rom(bootrom, NULL, "tt-atlantis.bootrom",
466 + s->memmap[TT_ATL_BOOTROM].size, &error_fatal);
467 + memory_region_add_subregion(system_memory, s->memmap[TT_ATL_BOOTROM].base,
468 + bootrom);
469 +
470 + /* UART1, the soc console (UART0 is for the boot microcontroller) */
471 + serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2,
472 + qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ),
473 + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
474 + /*
475 + * Atlantis contains a DesignWare uart while the QEMU machine
476 + * uses the serial_mm model with the base ns16550 register set.
477 + * Linux's dw driver writes outside of serial_mm's 0x20 sized
478 + * mapping and faults.
479 + *
480 + * Create an unimplemented device region so writes don't fault
481 + * and reads return zero, which keeps Linux happy.
482 + */
483 + create_unimplemented_device("tt-atlantis.uart0",
484 + s->memmap[TT_ATL_UART1].base,
485 + s->memmap[TT_ATL_UART1].size);
486 +
487 + /* Load or create device tree */
488 + if (machine->dtb) {
489 + load_fdt(s);
490 + } else {
491 + create_fdt(s);
492 + }
493 +
494 + s->machine_done.notify = tt_atlantis_machine_done;
495 + qemu_add_machine_init_done_notifier(&s->machine_done);
496 +}
497 +
498 +static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data)
499 +{
500 + MachineClass *mc = MACHINE_CLASS(oc);
501 +
502 + mc->desc = "Tenstorrent Atlantis RISC-V SoC (Experimental)";
503 + mc->init = tt_atlantis_machine_init;
504 + mc->max_cpus = 8;
505 + mc->default_cpus = 8;
506 + mc->default_ram_size = 4 * GiB;
507 + mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON;
508 + mc->block_default_type = IF_VIRTIO;
509 + mc->no_cdrom = 1;
510 + mc->default_ram_id = "tt_atlantis.ram";
511 +}
512 +
513 +static const TypeInfo tt_atlantis_types[] = {
514 + {
515 + .name = MACHINE_TYPE_NAME("tt-atlantis"),
516 + .parent = TYPE_MACHINE,
517 + .class_init = tt_atlantis_machine_class_init,
518 + .instance_size = sizeof(TTAtlantisState),
519 + .interfaces = riscv64_machine_interfaces,
520 + },
521 +};
522 +
523 +DEFINE_TYPES(tt_atlantis_types)
include/hw/riscv/tt_atlantis.h new
+50
@@ -0,0 +1,50 @@
1 +/*
2 + * Tenstorrent Atlantis RISC-V System on Chip
3 + *
4 + * SPDX-License-Identifier: GPL-2.0-or-later
5 + *
6 + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
7 + */
8 +
9 +#ifndef HW_RISCV_TT_ATLANTIS_H
10 +#define HW_RISCV_TT_ATLANTIS_H
11 +
12 +#include "hw/core/boards.h"
13 +#include "hw/core/sysbus.h"
14 +#include "hw/intc/riscv_imsic.h"
15 +#include "hw/riscv/riscv_hart.h"
16 +
17 +#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
18 +OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
19 +
20 +struct TTAtlantisState {
21 + /*< private >*/
22 + MachineState parent;
23 +
24 + /*< public >*/
25 + Notifier machine_done;
26 + const MemMapEntry *memmap;
27 +
28 + RISCVHartArrayState soc;
29 + DeviceState *irqchip;
30 +
31 + int fdt_size;
32 +};
33 +
34 +enum {
35 + TT_ATL_UART1_IRQ = 39,
36 +};
37 +
38 +enum {
39 + TT_ATL_ACLINT,
40 + TT_ATL_BOOTROM,
41 + TT_ATL_DDR_LO,
42 + TT_ATL_DDR_HI,
43 + TT_ATL_MAPLIC,
44 + TT_ATL_MIMSIC,
45 + TT_ATL_SAPLIC,
46 + TT_ATL_SIMSIC,
47 + TT_ATL_UART1,
48 +};
49 +
50 +#endif