Skip to main content

logicaffeine_compile/codegen_sva/hart/
soc.rs

1//! The system around the hart on Board B, defined once: the physical memory map, the device registers, how the
2//! 128 MB of DDR2 is laid out for the firmware, and the flash image the boot ROM loads. The hart model reads the map
3//! as its physical memory attributes, the RTL takes it as parameters ([`verilog_defines`]), and the boot ROM and the
4//! shim compile against the C header this module writes ([`c_header`]) — so no address is ever written twice.
5
6use std::collections::VecDeque;
7
8use super::model::{Bus, BusError, Hart, Region};
9
10/// The boot ROM: the hart's reset vector. Address 0 stays unmapped, so a null pointer faults.
11pub const ROM_BASE: u64 = 0x0001_0000;
12pub const ROM_SIZE: u64 = 0x4000;
13/// Scratch RAM in block RAM: the boot ROM's stack and data before the DDR2 is calibrated. It never holds code.
14pub const SRAM_BASE: u64 = 0x0002_0000;
15pub const SRAM_SIZE: u64 = 0x2000;
16/// Device registers.
17pub const IO_BASE: u64 = 0x1000_0000;
18pub const IO_SIZE: u64 = 0x1_0000;
19/// The DDR2.
20pub const RAM_BASE: u64 = 0x8000_0000;
21pub const RAM_SIZE: u64 = 128 << 20;
22
23pub const RESET_PC: u64 = ROM_BASE;
24
25/// The physical memory map the hart enforces.
26pub fn memory_map() -> Vec<Region> {
27    vec![
28        Region::rom(ROM_BASE, ROM_SIZE),
29        Region::data(SRAM_BASE, SRAM_SIZE),
30        Region::io(IO_BASE, IO_SIZE),
31        Region::memory(RAM_BASE, RAM_SIZE),
32    ]
33}
34
35/// A device register: its name in the C header and its address.
36pub struct Reg {
37    pub name: &'static str,
38    pub addr: u64,
39    pub doc: &'static str,
40}
41
42const fn reg(name: &'static str, addr: u64, doc: &'static str) -> Reg {
43    Reg { name, addr, doc }
44}
45
46pub const SYS: u64 = IO_BASE;
47pub const UART: u64 = IO_BASE + 0x1000;
48pub const LINK: u64 = IO_BASE + 0x2000;
49pub const BUTTONS: u64 = IO_BASE + 0x3000;
50pub const QSPI: u64 = IO_BASE + 0x4000;
51pub const DDR: u64 = IO_BASE + 0x5000;
52
53/// Every device register.
54pub const REGS: &[Reg] = &[
55    reg("SYS_ID", SYS, "read: the board's identity, SOC_ID"),
56    reg("SYS_CYCLES", SYS + 0x08, "read: clock cycles since reset"),
57    reg("SYS_FLAGS", SYS + 0x10, "read: bit 0 the DDR2 is ready, bit 1 its calibration failed"),
58    reg("SYS_RTL_MD5_LO", SYS + 0x18, "read: the first 8 bytes of the bitstream's RTL fingerprint"),
59    reg("SYS_RTL_MD5_HI", SYS + 0x20, "read: the last 8 bytes of the bitstream's RTL fingerprint"),
60    reg("SYS_SWITCHES", SYS + 0x28, "read: the 16 slide switches"),
61    reg("SYS_RESET", SYS + 0x30, "write SOC_RESET_KEY: restart the board from its reset vector"),
62    reg("SYS_EXIT", SYS + 0x38, "write: the firmware's exit status; the board stops there"),
63    reg("UART_TX", UART, "write: one byte to the log UART"),
64    reg("UART_STATUS", UART + 0x08, "read: bit 0 the transmit queue is full"),
65    reg("LINK_TX", LINK, "write: one byte into the terminal link, as the laptop would send it"),
66    reg("LINK_RX", LINK + 0x08, "read: bit 8 a byte is there, bits 7:0 the byte (the read takes it)"),
67    reg("LINK_STATUS", LINK + 0x10, "read: bit 0 the transmit queue is full, bit 1 a byte is waiting"),
68    reg("BUTTONS_STATE", BUTTONS, "read: the debounced buttons: bit 0 centre, 1 up, 2 left, 3 right, 4 down"),
69    reg("BUTTONS_LEDS", BUTTONS + 0x08, "read/write: the 16 LEDs"),
70    reg("QSPI_START", QSPI, "write: begin a streaming read of the flash at this byte address (it wraps at the end)"),
71    reg("QSPI_DATA", QSPI + 0x08, "read: the next 8 bytes of the stream (the read waits for them); all ones with no stream"),
72    reg("QSPI_STOP", QSPI + 0x10, "write: end the stream"),
73    reg("DDR_STATUS", DDR, "read: bit 0 calibrated, bit 1 failed; bits 15:8 the read latency found"),
74    reg("DDR_CAL", DDR + 0x08, "read: the calibration's per-lane taps"),
75];
76
77/// `SYS_ID`: "LOGOSHRT" read as a little-endian word.
78pub const SOC_ID: u64 = u64::from_le_bytes(*b"LOGOSHRT");
79/// The value `SYS_RESET` takes to restart the board.
80pub const SOC_RESET_KEY: u64 = 0x5245_5345_545f_4b45;
81
82/// How the DDR2 is laid out once the shim runs: the shim itself, the firmware's image and heap, the region its
83/// anonymous mappings come from, the boot image's staging area, and the firmware's stack.
84pub const SHIM_BASE: u64 = RAM_BASE;
85pub const SHIM_SIZE: u64 = 0x20_0000;
86pub const FIRMWARE_BASE: u64 = RAM_BASE + SHIM_SIZE;
87pub const MMAP_BASE: u64 = 0x8400_0000;
88pub const MMAP_SIZE: u64 = 0x200_0000;
89pub const STAGING_BASE: u64 = 0x8600_0000;
90pub const STAGING_SIZE: u64 = 0x180_0000;
91pub const STACK_BASE: u64 = 0x8780_0000;
92pub const STACK_SIZE: u64 = 0x80_0000;
93/// The boot ROM's working tables (the inflater's), in the mapping region: nothing uses it before the compiler runs,
94/// and the shim zeroes every page it hands out there.
95pub const BOOT_WORK_BASE: u64 = MMAP_BASE;
96
97/// The flash: the bitstream at 0, the boot image here.
98pub const FLASH_SIZE: u64 = 16 << 20;
99pub const IMAGE_OFFSET: u64 = 0x40_0000;
100
101/// The boot image's header: `IMAGE_MAGIC`, a version and the header's size, the RTL fingerprint it was built for,
102/// the body's inflated and compressed lengths, and the sha256 of the compressed body.
103pub const IMAGE_MAGIC: u64 = u64::from_le_bytes(*b"LOGOSIMG");
104pub const IMAGE_VERSION: u32 = 1;
105pub const IMAGE_HEADER_SIZE: u64 = 128;
106
107/// A section of the boot image's body.
108pub const SECTION_SHIM: u32 = 1;
109pub const SECTION_FIRMWARE: u32 = 2;
110pub const SECTION_BUNDLE: u32 = 3;
111pub const SECTION_ARGS: u32 = 4;
112
113/// Every constant the firmware compiles against, by its C name.
114pub const CONSTANTS: &[(&str, u64)] = &[
115    ("ROM_BASE", ROM_BASE),
116    ("ROM_SIZE", ROM_SIZE),
117    ("SRAM_BASE", SRAM_BASE),
118    ("SRAM_SIZE", SRAM_SIZE),
119    ("IO_BASE", IO_BASE),
120    ("IO_SIZE", IO_SIZE),
121    ("RAM_BASE", RAM_BASE),
122    ("RAM_SIZE", RAM_SIZE),
123    ("RESET_PC", RESET_PC),
124    ("SOC_ID", SOC_ID),
125    ("SOC_RESET_KEY", SOC_RESET_KEY),
126    ("SHIM_BASE", SHIM_BASE),
127    ("SHIM_SIZE", SHIM_SIZE),
128    ("FIRMWARE_BASE", FIRMWARE_BASE),
129    ("MMAP_BASE", MMAP_BASE),
130    ("MMAP_SIZE", MMAP_SIZE),
131    ("STAGING_BASE", STAGING_BASE),
132    ("STAGING_SIZE", STAGING_SIZE),
133    ("STACK_BASE", STACK_BASE),
134    ("STACK_SIZE", STACK_SIZE),
135    ("BOOT_WORK_BASE", BOOT_WORK_BASE),
136    ("FLASH_SIZE", FLASH_SIZE),
137    ("IMAGE_OFFSET", IMAGE_OFFSET),
138    ("IMAGE_MAGIC", IMAGE_MAGIC),
139    ("IMAGE_VERSION", IMAGE_VERSION as u64),
140    ("IMAGE_HEADER_SIZE", IMAGE_HEADER_SIZE),
141    ("SECTION_SHIM", SECTION_SHIM as u64),
142    ("SECTION_FIRMWARE", SECTION_FIRMWARE as u64),
143    ("SECTION_BUNDLE", SECTION_BUNDLE as u64),
144    ("SECTION_ARGS", SECTION_ARGS as u64),
145    ("HART_HZ", HART_HZ),
146    ("HART_HWCAP", HART_HWCAP),
147];
148
149/// The extensions the compiler sees, as Linux reports them in `AT_HWCAP` (a bit per extension letter): the hart's
150/// I, M, A and C, and the F and D the shim provides — exactly what `qemu-riscv64` reports for the same binary.
151pub const HART_HWCAP: u64 = letter(b'i') | letter(b'm') | letter(b'a') | letter(b'f') | letter(b'd') | letter(b'c');
152
153const fn letter(c: u8) -> u64 {
154    1 << (c - b'a')
155}
156
157/// The hart's clock: the DDR2 controller's divided clock, which the hart shares.
158pub const HART_HZ: u64 = 62_500_000;
159
160/// The C header the boot ROM and the shim compile against.
161pub fn c_header() -> String {
162    let mut s = String::from(
163        "// Generated from codegen_sva::hart::soc — the one definition of the board's addresses. Do not edit.\n\
164         #pragma once\n\n",
165    );
166    for (name, v) in CONSTANTS {
167        s.push_str(&format!("#define {name} 0x{v:x}ull\n"));
168    }
169    s.push('\n');
170    for r in REGS {
171        s.push_str(&format!("// {}\n#define {} 0x{:x}ull\n", r.doc, r.name, r.addr));
172    }
173    s
174}
175
176/// The RTL's view of the same map, as `define`s the SoC's Verilog includes (once, however many files include it).
177pub fn verilog_defines() -> String {
178    let mut s = String::from("// Generated from codegen_sva::hart::soc. Do not edit.\n`ifndef SOC_DEFINES_VH\n`define SOC_DEFINES_VH\n");
179    for (name, v) in CONSTANTS {
180        s.push_str(&format!("`define SOC_{name} 64'h{v:016x}\n"));
181    }
182    for r in REGS {
183        s.push_str(&format!("`define SOC_{} 64'h{:016x}\n", r.name, r.addr));
184    }
185    s.push_str("`endif\n");
186    s
187}
188
189/// The board's devices, as the model of the system around the hart: memory, the flash behind the streaming port,
190/// and every register in [`REGS`] with the behaviour its line documents. The RTL SoC is held to these. The bus
191/// completes every access the memory map permits ([`memory_map`]): a device address no register holds reads as
192/// zero and ignores writes.
193pub struct Devices {
194    rom: Vec<u8>,
195    sram: Vec<u8>,
196    ram: Vec<u8>,
197    /// The flash: the bitstream's 4 MB, then the boot image.
198    pub flash: Vec<u8>,
199    /// Every byte written to the log UART.
200    pub uart: Vec<u8>,
201    /// Bytes the hart sent into the terminal link.
202    pub link_out: VecDeque<u8>,
203    /// Bytes waiting for the hart from the terminal link.
204    pub link_in: VecDeque<u8>,
205    pub buttons: u64,
206    pub leds: u64,
207    pub switches: u64,
208    /// The bitstream's RTL fingerprint.
209    pub rtl_md5: [u8; 16],
210    /// Whether the DDR2 has calibrated (and whether it failed to).
211    pub ddr_ready: bool,
212    pub ddr_failed: bool,
213    /// Clock cycles since reset, as `SYS_CYCLES` reads them.
214    pub cycles: u64,
215    /// The status the firmware stopped the board with.
216    pub exit: Option<u64>,
217    /// The firmware asked the board to restart.
218    pub reset: bool,
219    stream: Option<u64>,
220}
221
222fn read_le(bytes: &[u8], o: usize, size: u8) -> u64 {
223    bytes[o..o + size as usize].iter().rev().fold(0, |v, &b| (v << 8) | b as u64)
224}
225
226fn write_le(bytes: &mut [u8], o: usize, size: u8, value: u64) {
227    for (i, b) in bytes[o..o + size as usize].iter_mut().enumerate() {
228        *b = (value >> (8 * i)) as u8;
229    }
230}
231
232impl Devices {
233    /// The devices at reset, with `rom` in the boot ROM and `flash` behind the streaming port.
234    pub fn new(rom: &[u8], flash: Vec<u8>, rtl_md5: [u8; 16]) -> Self {
235        assert!(rom.len() as u64 <= ROM_SIZE, "the boot ROM image is larger than the ROM");
236        assert_eq!(flash.len() as u64, FLASH_SIZE, "the flash is {FLASH_SIZE} bytes");
237        let mut rom_bytes = vec![0; ROM_SIZE as usize];
238        rom_bytes[..rom.len()].copy_from_slice(rom);
239        Devices {
240            rom: rom_bytes,
241            sram: vec![0; SRAM_SIZE as usize],
242            ram: vec![0; RAM_SIZE as usize],
243            flash,
244            uart: Vec::new(),
245            link_out: VecDeque::new(),
246            link_in: VecDeque::new(),
247            buttons: 0,
248            leds: 0,
249            switches: 0,
250            rtl_md5,
251            ddr_ready: true,
252            ddr_failed: false,
253            cycles: 0,
254            exit: None,
255            reset: false,
256            stream: None,
257        }
258    }
259
260    /// A whole-word read of a device register; an address no register holds reads as zero.
261    fn io_read(&mut self, addr: u64) -> u64 {
262        let md5 = |i: usize| u64::from_le_bytes(self.rtl_md5[8 * i..8 * i + 8].try_into().unwrap());
263        match addr {
264            a if a == SYS => SOC_ID,
265            a if a == SYS + 0x08 => self.cycles,
266            a if a == SYS + 0x10 => (self.ddr_ready as u64) | ((self.ddr_failed as u64) << 1),
267            a if a == SYS + 0x18 => md5(0),
268            a if a == SYS + 0x20 => md5(1),
269            a if a == SYS + 0x28 => self.switches,
270            a if a == UART + 0x08 => 0,
271            a if a == LINK + 0x08 => self.link_in.pop_front().map(|b| 0x100 | b as u64).unwrap_or(0),
272            a if a == LINK + 0x10 => (!self.link_in.is_empty() as u64) << 1,
273            a if a == BUTTONS => self.buttons,
274            a if a == BUTTONS + 0x08 => self.leds,
275            a if a == QSPI + 0x08 => match self.stream {
276                Some(at) => {
277                    let word = (0..8)
278                        .map(|i| self.flash[((at + i) & (FLASH_SIZE - 1)) as usize] as u64)
279                        .rev()
280                        .fold(0, |v, b| (v << 8) | b);
281                    self.stream = Some((at + 8) & (FLASH_SIZE - 1));
282                    word
283                }
284                None => u64::MAX,
285            },
286            a if a == DDR => (self.ddr_ready as u64) | ((self.ddr_failed as u64) << 1),
287            _ => 0,
288        }
289    }
290
291    /// A whole-word write of a device register; an address no register holds ignores it.
292    fn io_write(&mut self, addr: u64, value: u64) {
293        match addr {
294            a if a == SYS + 0x30 => self.reset = value == SOC_RESET_KEY,
295            a if a == SYS + 0x38 => self.exit = Some(value),
296            a if a == UART => self.uart.push(value as u8),
297            a if a == LINK => self.link_out.push_back(value as u8),
298            a if a == BUTTONS + 0x08 => self.leds = value & 0xffff,
299            a if a == QSPI => self.stream = Some(value & (FLASH_SIZE - 1)),
300            a if a == QSPI + 0x10 => self.stream = None,
301            _ => {}
302        }
303    }
304}
305
306impl Bus for Devices {
307    fn read(&mut self, addr: u64, size: u8) -> Result<u64, BusError> {
308        match addr {
309            a if (ROM_BASE..ROM_BASE + ROM_SIZE).contains(&a) => Ok(read_le(&self.rom, (a - ROM_BASE) as usize, size)),
310            a if (SRAM_BASE..SRAM_BASE + SRAM_SIZE).contains(&a) => Ok(read_le(&self.sram, (a - SRAM_BASE) as usize, size)),
311            a if (RAM_BASE..RAM_BASE + RAM_SIZE).contains(&a) => Ok(read_le(&self.ram, (a - RAM_BASE) as usize, size)),
312            a if (IO_BASE..IO_BASE + IO_SIZE).contains(&a) && size == 8 => Ok(self.io_read(a)),
313            _ => Err(BusError),
314        }
315    }
316
317    fn write(&mut self, addr: u64, size: u8, value: u64) -> Result<(), BusError> {
318        match addr {
319            a if (SRAM_BASE..SRAM_BASE + SRAM_SIZE).contains(&a) => {
320                write_le(&mut self.sram, (a - SRAM_BASE) as usize, size, value);
321                Ok(())
322            }
323            a if (RAM_BASE..RAM_BASE + RAM_SIZE).contains(&a) => {
324                write_le(&mut self.ram, (a - RAM_BASE) as usize, size, value);
325                Ok(())
326            }
327            a if (IO_BASE..IO_BASE + IO_SIZE).contains(&a) && size == 8 => {
328                self.io_write(a, value);
329                Ok(())
330            }
331            _ => Err(BusError),
332        }
333    }
334}
335
336/// How a run of the board ended.
337#[derive(Clone, Copy, Debug, PartialEq, Eq)]
338pub enum Stopped {
339    /// The firmware wrote this status to `SYS_EXIT`.
340    Exited(u64),
341    /// The step limit came first.
342    StepLimit,
343}
344
345/// The whole board: the hart at its reset vector over the devices.
346pub struct Board {
347    pub hart: Hart,
348    pub devices: Devices,
349}
350
351impl Board {
352    pub fn new(rom: &[u8], flash: Vec<u8>, rtl_md5: [u8; 16]) -> Self {
353        Board {
354            hart: Hart::new(RESET_PC, memory_map()),
355            devices: Devices::new(rom, flash, rtl_md5),
356        }
357    }
358
359    /// Run until the firmware exits or `steps` instructions have run.
360    pub fn run(&mut self, steps: u64) -> Stopped {
361        for _ in 0..steps {
362            self.hart.step(&mut self.devices);
363            self.devices.cycles += 1;
364            if let Some(status) = self.devices.exit {
365                return Stopped::Exited(status);
366            }
367            if self.devices.reset {
368                self.devices.reset = false;
369                self.hart = Hart::new(RESET_PC, memory_map());
370            }
371        }
372        Stopped::StepLimit
373    }
374}