1use derive_more::{Debug, Display};
2use itertools::Itertools;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
6pub struct Major(pub u8);
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
10pub struct Minor(pub u8);
11
12#[must_use]
13fn version_map(major: Major, minor: Minor) -> String {
14 let major = major.0;
15 let minor = minor.0;
16 match major {
17 0 => "older than v0.4".to_string(),
18 0x01..=0x06 => format!("v0.{}", major + 3),
19 0x0A..=0x15 => format!("v1.{}", major - 0x0A),
20 0x80..=0x89 => format!("v2.{}.{}", major - 0x80, minor),
21 0x8A..=0x8A => format!("v3.{}.{}", major - 0x8A, minor),
22 0x8B..=0x8C => format!("v4.{}.{}", major - 0x8B, minor),
23 0x8D..=0x8E => format!("v5.{}.{}", major - 0x8D, minor),
24 0x8F..=0x90 => format!("v6.{}.{}", major - 0x8F, minor),
25 0x91..=0x91 => format!("v7.{}.{}", major - 0x91, minor),
26 0x92..=0x92 => format!("v8.{}.{}", major - 0x92, minor),
27 0xA0..=0xA1 => format!("v9.{}.{}", major - 0xA0, minor),
28 0xA2..=0xA2 => format!("v10.{}.{}", major - 0xA2, minor),
29 0xA3..=0xA3 => format!("v11.{}.{}", major - 0xA3, minor),
30 0xA4..=0xA4 => format!("v12.{}.{}", major - 0xA4, minor),
31 _ => format!("unknown ({major})"),
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct FPGAVersion {
38 #[doc(hidden)]
39 pub major: Major,
40 #[doc(hidden)]
41 pub minor: Minor,
42 #[doc(hidden)]
43 pub function_bits: u8,
44}
45
46impl FPGAVersion {
47 #[doc(hidden)]
48 pub const ENABLED_EMULATOR_BIT: u8 = 1 << 7;
49
50 #[doc(hidden)]
51 #[must_use]
52 pub const fn is_emulator(&self) -> bool {
53 (self.function_bits & Self::ENABLED_EMULATOR_BIT) == Self::ENABLED_EMULATOR_BIT
54 }
55}
56
57impl std::fmt::Display for FPGAVersion {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "{}", version_map(self.major, self.minor))?;
60 let features = [self.is_emulator().then_some("Emulator")]
61 .iter()
62 .filter_map(Option::as_ref)
63 .join(", ");
64 if !features.is_empty() {
65 write!(f, " [{}]", features)?;
66 }
67 Ok(())
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
73#[display("{}", version_map(self.major, self.minor))]
74pub struct CPUVersion {
75 #[doc(hidden)]
76 pub major: Major,
77 #[doc(hidden)]
78 pub minor: Minor,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
83#[display(
84 "{}: CPU = {}, FPGA = {}",
85 idx,
86 self.cpu,
87 self.fpga,
88)]
89#[debug("{}", self)]
90pub struct FirmwareVersion {
91 #[doc(hidden)]
92 pub idx: usize,
93 #[doc(hidden)]
94 pub cpu: CPUVersion,
95 #[doc(hidden)]
96 pub fpga: FPGAVersion,
97}
98
99impl FirmwareVersion {
100 #[doc(hidden)]
101 pub const LATEST_VERSION_NUM_MAJOR: Major = Major(0xA4);
102 #[doc(hidden)]
103 pub const LATEST_VERSION_NUM_MINOR: Minor = Minor(0x00);
104
105 #[doc(hidden)]
106 #[must_use]
107 pub const fn is_emulator(&self) -> bool {
108 self.fpga.is_emulator()
109 }
110
111 #[must_use]
113 pub fn latest() -> String {
114 version_map(
115 Self::LATEST_VERSION_NUM_MAJOR,
116 Self::LATEST_VERSION_NUM_MINOR,
117 )
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[rstest::rstest]
126 #[test]
127 #[case("older than v0.4", 0)]
128 #[case("v0.4", 1)]
129 #[case("v0.5", 2)]
130 #[case("v0.6", 3)]
131 #[case("v0.7", 4)]
132 #[case("v0.8", 5)]
133 #[case("v0.9", 6)]
134 #[case("unknown (7)", 7)]
135 #[case("unknown (8)", 8)]
136 #[case("unknown (9)", 9)]
137 #[case("v1.0", 10)]
138 #[case("v1.1", 11)]
139 #[case("v1.2", 12)]
140 #[case("v1.3", 13)]
141 #[case("v1.4", 14)]
142 #[case("v1.5", 15)]
143 #[case("v1.6", 16)]
144 #[case("v1.7", 17)]
145 #[case("v1.8", 18)]
146 #[case("v1.9", 19)]
147 #[case("v1.10", 20)]
148 #[case("v1.11", 21)]
149 #[case("v2.0.0", 128)]
150 #[case("v2.1.0", 129)]
151 #[case("v2.2.0", 130)]
152 #[case("v2.3.0", 131)]
153 #[case("v2.4.0", 132)]
154 #[case("v2.5.0", 133)]
155 #[case("v2.6.0", 134)]
156 #[case("v2.7.0", 135)]
157 #[case("v2.8.0", 136)]
158 #[case("v2.9.0", 137)]
159 #[case("v3.0.0", 138)]
160 #[case("v4.0.0", 139)]
161 #[case("v4.1.0", 140)]
162 #[case("v5.0.0", 141)]
163 #[case("v5.1.0", 142)]
164 #[case("v6.0.0", 143)]
165 #[case("v6.1.0", 144)]
166 #[case("v7.0.0", 145)]
167 #[case("v8.0.0", 146)]
168 #[case("v9.0.0", 160)]
169 #[case("v9.1.0", 161)]
170 #[case("v10.0.0", 162)]
171 #[case("v11.0.0", 163)]
172 #[case("v12.0.0", 164)]
173 #[case("unknown (147)", 147)]
174 fn version(#[case] expected: &str, #[case] num: u8) {
175 let info = FirmwareVersion {
176 idx: 0,
177 cpu: CPUVersion {
178 major: Major(num),
179 minor: Minor(0),
180 },
181 fpga: FPGAVersion {
182 major: Major(num),
183 minor: Minor(0),
184 function_bits: 0,
185 },
186 };
187 assert_eq!(expected, info.cpu.to_string());
188 assert_eq!(expected, info.fpga.to_string());
189 }
190
191 #[test]
192 fn latest() {
193 assert_eq!("v12.0.0", FirmwareVersion::latest());
194 }
195
196 #[rstest::rstest]
197 #[case(false, 0)]
198 #[case(true, FPGAVersion::ENABLED_EMULATOR_BIT)]
199 #[test]
200 fn is_emulator(#[case] expected: bool, #[case] function_bits: u8) {
201 assert_eq!(
202 expected,
203 FirmwareVersion {
204 idx: 0,
205 cpu: CPUVersion {
206 major: Major(0),
207 minor: Minor(0)
208 },
209 fpga: FPGAVersion {
210 major: Major(0),
211 minor: Minor(0),
212 function_bits
213 }
214 }
215 .is_emulator()
216 );
217 }
218
219 #[rstest::rstest]
220 #[test]
221 #[case(
222 "0: CPU = v0.4, FPGA = v0.5",
223 FirmwareVersion {
224 idx: 0,
225 cpu: CPUVersion {
226 major: Major(1),
227 minor: Minor(3)
228 },
229 fpga: FPGAVersion {
230 major: Major(2),
231 minor: Minor(4),
232 function_bits: 0
233 }
234 }
235 )]
236 #[case(
237 "0: CPU = v0.4, FPGA = v0.5 [Emulator]",
238 FirmwareVersion {
239 idx: 0,
240 cpu: CPUVersion {
241 major: Major(1),
242 minor: Minor(3)
243 },
244 fpga: FPGAVersion {
245 major: Major(2),
246 minor: Minor(4),
247 function_bits: FPGAVersion::ENABLED_EMULATOR_BIT
248 }
249 }
250 )]
251 fn display(#[case] expected: &str, #[case] info: FirmwareVersion) {
252 assert_eq!(expected, format!("{}", info));
253 assert_eq!(expected, format!("{:?}", info));
254 }
255}