Skip to main content

logicaffeine_compile/codegen_sva/hart/
rtl.rs

1//! The hart's RTL, as Verilog source files the board image and the simulations read. The hart is held to
2//! [`super::model`] (and its decompressor to [`super::rvc`]) by `hart_rtl_lock`; the computer around it, to the board
3//! model [`super::soc::Board`] by `hart_soc_lock`; the DDR2 controller, to the part's data sheet by `hart_ddr2_lock`.
4
5use super::soc;
6
7/// The hart itself: (file name, source).
8pub fn core_files() -> Vec<(&'static str, String)> {
9    vec![
10        ("hart_rvc.v", include_str!("rtl/hart_rvc.v").to_string()),
11        ("hart_muldiv.v", include_str!("rtl/hart_muldiv.v").to_string()),
12        ("hart_core.v", include_str!("rtl/hart_core.v").to_string()),
13    ]
14}
15
16/// The hart with its caches, `hart_cpu`: the hart's files, the instruction and data caches, their one way to memory,
17/// and the maintenance (fence.i, restart) they share.
18pub fn cpu_files() -> Vec<(&'static str, String)> {
19    let mut files = core_files();
20    files.extend([
21        ("hart_icache.v", include_str!("rtl/hart_icache.v").to_string()),
22        ("hart_dcache.v", include_str!("rtl/hart_dcache.v").to_string()),
23        ("hart_mem.v", include_str!("rtl/hart_mem.v").to_string()),
24        ("hart_cpu.v", include_str!("rtl/hart_cpu.v").to_string()),
25    ]);
26    files
27}
28
29/// The memory map's `define`s, which the computer's files include.
30pub const DEFINES: &str = "soc_defines.vh";
31
32/// The whole computer, `hart_soc`: [`cpu_files`], the boot ROM, the scratch RAM, the device registers, and [`DEFINES`]
33/// written from [`soc::verilog_defines`]. The boot ROM's contents come from the `ROM_INIT` file the build names
34/// ([`rom_hex`]).
35pub fn soc_files() -> Vec<(&'static str, String)> {
36    let mut files = cpu_files();
37    files.extend([
38        ("hart_rom.v", include_str!("rtl/hart_rom.v").to_string()),
39        ("hart_sram.v", include_str!("rtl/hart_sram.v").to_string()),
40        ("hart_fifo.v", include_str!("rtl/hart_fifo.v").to_string()),
41        ("hart_qspi.v", include_str!("rtl/hart_qspi.v").to_string()),
42        ("hart_io.v", include_str!("rtl/hart_io.v").to_string()),
43        ("hart_soc.v", include_str!("rtl/hart_soc.v").to_string()),
44        (DEFINES, soc::verilog_defines()),
45    ]);
46    files
47}
48
49/// The DDR2's parameters, which the controller includes.
50pub const DDR2_PARAMS: &str = "hart_ddr2_params.vh";
51
52/// The DDR2 controller, `hart_ddr2`, with [`DDR2_PARAMS`] written from [`super::ddr2::verilog_params`] for the board's
53/// part, and its Artix-7 PHY, `hart_ddr2_phy` (device primitives: the board's build reads it, simulations do not).
54pub fn ddr2_files() -> Vec<(&'static str, String)> {
55    vec![
56        ("hart_ddr2.v", include_str!("rtl/hart_ddr2.v").to_string()),
57        (DDR2_PARAMS, super::ddr2::verilog_params(&super::ddr2::MT47H64M16)),
58        ("hart_ddr2_phy.v", include_str!("rtl/hart_ddr2_phy.v").to_string()),
59    ]
60}
61
62/// What the board's top adds around the computer and its DDR2: the queue that carries bytes between the hart's clock
63/// and the terminal's.
64pub fn board_files() -> Vec<(&'static str, String)> {
65    let mut files = soc_files();
66    files.extend(ddr2_files());
67    files.push(("hart_cdc_fifo.v", include_str!("rtl/hart_cdc_fifo.v").to_string()));
68    files
69}
70
71/// The bring-up image: the computer alone on Board B (`hart_bringup_top`), booting from the flash with its log on the
72/// USB UART — [`board_files`], the log UART's transmitter, the top and its pins.
73pub fn bringup_files() -> Vec<(&'static str, String)> {
74    let mut files = board_files();
75    files.extend([
76        ("hart_uart_tx.v", include_str!("rtl/hart_uart_tx.v").to_string()),
77        ("hart_bringup_top.v", include_str!("rtl/hart_bringup_top.v").to_string()),
78        ("hart_bringup_top.xdc", include_str!("rtl/hart_bringup_top.xdc").to_string()),
79    ]);
80    files
81}
82
83/// The DDR2 PHY's board probe, `hart_ddr2_loopback_top`: the PHY driving its own pads with the DDR2 held idle and
84/// sending what it reads back on the UART; its pins are the bring-up image's, those its ports use.
85pub fn loopback_files() -> Vec<(&'static str, String)> {
86    let xdc: String = include_str!("rtl/hart_bringup_top.xdc")
87        .lines()
88        .filter(|l| !(l.contains("{ qspi_") || l.contains("{ btn")))
89        .map(|l| format!("{l}\n"))
90        .collect();
91    vec![
92        ("hart_ddr2_phy.v", include_str!("rtl/hart_ddr2_phy.v").to_string()),
93        ("hart_uart_tx.v", include_str!("rtl/hart_uart_tx.v").to_string()),
94        ("hart_ddr2_loopback_top.v", include_str!("rtl/hart_ddr2_loopback_top.v").to_string()),
95        ("hart_ddr2_loopback_top.xdc", xdc),
96    ]
97}
98
99/// A bitstream's fingerprint — what a boot image is built for, and what `SYS_RTL_MD5` reports: the md5 of every file the
100/// bitstream is built from, in order, then of the boot ROM's words, so a changed ROM is another bitstream too.
101pub fn fingerprint(files: &[(&'static str, String)], rom_hex: &str) -> [u8; 16] {
102    let mut all = Vec::new();
103    for (name, text) in files {
104        all.extend_from_slice(name.as_bytes());
105        all.push(0);
106        all.extend_from_slice(text.as_bytes());
107        all.push(0);
108    }
109    all.extend_from_slice(rom_hex.as_bytes());
110    logicaffeine_base::hash::md5(&all)
111}
112
113/// A fingerprint as the SoC's `RTL_MD5_LO` / `RTL_MD5_HI` parameters take it: the words `SYS_RTL_MD5_LO` and
114/// `SYS_RTL_MD5_HI` read as, its bytes little-endian.
115pub fn fingerprint_words(f: [u8; 16]) -> (u64, u64) {
116    (u64::from_le_bytes(f[0..8].try_into().unwrap()), u64::from_le_bytes(f[8..16].try_into().unwrap()))
117}
118
119/// The boot ROM as the `$readmemh` words its block RAM starts with: a 64-bit little-endian word a line, zeros to the
120/// ROM's end.
121pub fn rom_hex(boot: &[u8]) -> Result<String, String> {
122    if boot.len() as u64 > soc::ROM_SIZE {
123        return Err(format!("the boot ROM is {} bytes; the ROM holds {}", boot.len(), soc::ROM_SIZE));
124    }
125    Ok((0..(soc::ROM_SIZE / 8) as usize)
126        .map(|i| {
127            let word = (0..8).rev().fold(0u64, |w, k| (w << 8) | *boot.get(8 * i + k).unwrap_or(&0) as u64);
128            format!("{word:016x}\n")
129        })
130        .collect())
131}