amdgpu_sysfs/gpu_handle/overdrive/
rdna.rs

1//! The format used by Vega20 and newer GPUs.
2use super::{
3    parse_line_item, parse_range_line, push_level_line, ClocksLevel, ClocksTable, ClocksTableGen,
4    Range,
5};
6use crate::{
7    error::{Error, ErrorContext, ErrorKind::ParseError},
8    gpu_handle::trim_sysfs_line,
9    Result,
10};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use std::{cmp, io::Write, str::FromStr};
14
15/// Vega20 clocks table.
16#[derive(Debug, Clone)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub struct Table {
19    /// The current core clock range.
20    pub current_sclk_range: Range,
21    /// The current core clock offset (RDNA4+)
22    pub sclk_offset: Option<i32>,
23    /// The current memory clock range. Empty on iGPUs.
24    pub current_mclk_range: Range,
25    /// The current voltage curve. May be empty if the GPU does not support it.
26    pub vddc_curve: Vec<ClocksLevel>,
27    /// Voltage offset(in mV) applied on target voltage calculation.
28    /// This is available for Sienna Cichlid, Navy Flounder and Dimgrey Cavefish.
29    ///
30    /// Note: editing this value directly does not check if it's in the allowed range!
31    pub voltage_offset: Option<i32>,
32    /// The allowed ranges for clockspeeds and voltages.
33    pub od_range: OdRange,
34}
35
36impl ClocksTable for Table {
37    fn write_commands<W: Write>(
38        &self,
39        writer: &mut W,
40        previous_table: &ClocksTableGen,
41    ) -> Result<()> {
42        let ClocksTableGen::Rdna(previous_table) = previous_table else {
43            return Err(Error::not_allowed(
44                "Mismatched clocks table format".to_owned(),
45            ));
46        };
47
48        let mut clocks_commands = Vec::with_capacity(4);
49
50        // If the new minimum clockspeed is higher than the previous maximum clockspeed,
51        // we need to first write the new maximum value to avoid an error on RDNA3
52        if let (Some(current_sclk_min), Some(old_sclk_max)) = (
53            self.current_sclk_range.min,
54            previous_table.current_sclk_range.max,
55        ) {
56            if current_sclk_min > old_sclk_max {
57                clocks_commands.push((self.current_sclk_range.max, 's', 1));
58            }
59        }
60
61        clocks_commands.extend([
62            (self.current_sclk_range.min, 's', 0),
63            (self.current_sclk_range.max, 's', 1),
64        ]);
65
66        if let (Some(current_mclk_min), Some(old_mclk_max)) = (
67            self.current_mclk_range.min,
68            previous_table.current_mclk_range.max,
69        ) {
70            if current_mclk_min > old_mclk_max {
71                clocks_commands.push((self.current_mclk_range.max, 'm', 1));
72            }
73        }
74
75        clocks_commands.extend([
76            (self.current_mclk_range.min, 'm', 0),
77            (self.current_mclk_range.max, 'm', 1),
78        ]);
79
80        if let Some(sclk_offset) = self.sclk_offset {
81            let line = format!("s {sclk_offset}\n");
82            writer
83                .write_all(line.as_bytes())
84                .context("Could not write sclk offset")?;
85        }
86
87        for (maybe_clockspeed, symbol, index) in clocks_commands {
88            if let Some(clockspeed) = maybe_clockspeed {
89                let line = clockspeed_line(symbol, index, clockspeed);
90                writer
91                    .write_all(line.as_bytes())
92                    .with_context(|| format!("Error when writing clockspeed line `{line}`"))?;
93            }
94        }
95
96        for (i, level) in self.vddc_curve.iter().enumerate() {
97            let line = vddc_curve_line(i, level.clockspeed, level.voltage);
98            writer
99                .write_all(line.as_bytes())
100                .with_context(|| format!("Error when writing VDDC line `{line}`"))?;
101        }
102
103        if let Some(offset) = self.voltage_offset {
104            let line = voltage_offset_line(offset);
105            writer
106                .write_all(line.as_bytes())
107                .with_context(|| format!("Error when writing voltage offset `{line}`"))?;
108        }
109
110        Ok(())
111    }
112
113    fn get_max_sclk_range(&self) -> Option<Range> {
114        self.od_range
115            .curve_sclk_points
116            .last()
117            .copied()
118            .or(self.od_range.sclk)
119    }
120
121    fn get_min_sclk_range(&self) -> Option<Range> {
122        self.od_range
123            .curve_sclk_points
124            .first()
125            .copied()
126            .or(self.od_range.sclk)
127    }
128
129    fn get_max_mclk_range(&self) -> Option<Range> {
130        self.od_range.mclk
131    }
132
133    fn get_min_mclk_range(&self) -> Option<Range> {
134        self.od_range.mclk
135    }
136
137    fn get_max_voltage_range(&self) -> Option<Range> {
138        self.od_range.curve_voltage_points.last().copied()
139    }
140
141    fn get_min_voltage_range(&self) -> Option<Range> {
142        self.od_range.curve_voltage_points.first().copied()
143    }
144
145    fn get_current_voltage_range(&self) -> Option<Range> {
146        let min = self.vddc_curve.first().map(|level| level.voltage)?;
147        let max = self.vddc_curve.last().map(|level| level.voltage)?;
148        Some(Range::full(min, max))
149    }
150
151    fn get_current_sclk_range(&self) -> Range {
152        self.current_sclk_range
153    }
154
155    fn get_current_mclk_range(&self) -> Range {
156        self.current_mclk_range
157    }
158
159    fn set_max_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()> {
160        self.current_sclk_range.max = Some(clockspeed);
161        if let Some(point) = self.vddc_curve.last_mut() {
162            point.clockspeed = clockspeed;
163        }
164        Ok(())
165    }
166
167    fn set_min_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()> {
168        self.current_sclk_range.min = Some(clockspeed);
169        if let Some(point) = self.vddc_curve.first_mut() {
170            point.clockspeed = clockspeed;
171        }
172        Ok(())
173    }
174
175    fn set_max_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()> {
176        self.current_mclk_range.max = Some(clockspeed);
177        Ok(())
178    }
179
180    fn set_min_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()> {
181        self.current_mclk_range.min = Some(clockspeed);
182        Ok(())
183    }
184
185    fn set_max_voltage_unchecked(&mut self, voltage: i32) -> Result<()> {
186        self.vddc_curve
187            .last_mut()
188            .ok_or_else(|| {
189                Error::not_allowed("The GPU did not report any voltage curve points".to_owned())
190            })?
191            .voltage = voltage;
192        Ok(())
193    }
194
195    fn set_min_voltage_unchecked(&mut self, voltage: i32) -> Result<()> {
196        self.vddc_curve
197            .first_mut()
198            .ok_or_else(|| {
199                Error::not_allowed("The GPU did not report any voltage curve points".to_owned())
200            })?
201            .voltage = voltage;
202        Ok(())
203    }
204
205    fn get_max_sclk_voltage(&self) -> Option<i32> {
206        self.vddc_curve.last().map(|level| level.voltage)
207    }
208}
209
210impl Table {
211    /// Sets the voltage offset, checking if it's in range if the GPU provided one
212    ///
213    /// Note: RDNA2 GPUs use a voltage offset but do not provide a range
214    pub fn set_voltage_offset(&mut self, offset: i32) -> Result<()> {
215        if let Some(offset_range) = self.od_range.voltage_offset {
216            if let Some((min, max)) = offset_range.into_full() {
217                if !(min..=max).contains(&offset) {
218                    return Err(Error::not_allowed(format!("Provided voltage offset {offset} is out of range, should be between {min} and {max}")));
219                }
220            }
221        }
222
223        self.voltage_offset = Some(offset);
224        Ok(())
225    }
226}
227
228impl FromStr for Table {
229    type Err = Error;
230
231    fn from_str(s: &str) -> Result<Self> {
232        let mut current_section = None;
233
234        let mut current_sclk_range = None;
235        let mut current_mclk_range = None;
236        let mut allowed_sclk_range = None;
237        let mut allowed_sclk_offset_range = None;
238        let mut allowed_mclk_range = None;
239
240        let mut vddc_curve = Vec::with_capacity(3);
241        let mut curve_sclk_points = Vec::with_capacity(3);
242        let mut curve_voltage_points = Vec::with_capacity(3);
243
244        let mut sclk_offset = None;
245        let mut voltage_offset = None;
246        let mut voltage_offset_range = None;
247
248        let mut lines = s
249            .lines()
250            .map(trim_sysfs_line)
251            .filter(|line| !line.is_empty());
252
253        let mut i = 1;
254        while let Some(line) = lines.next() {
255            match line {
256                "OD_SCLK:" => current_section = Some(Section::Sclk),
257                "OD_SCLK_OFFSET:" => current_section = Some(Section::SclkOffset),
258                "OD_MCLK:" => current_section = Some(Section::Mclk),
259                "OD_RANGE:" => current_section = Some(Section::Range),
260                "OD_VDDC_CURVE:" => current_section = Some(Section::VddcCurve),
261                "OD_VDDGFX_OFFSET:" => current_section = Some(Section::VddGfxOffset),
262                line => match current_section {
263                    // Voltage points will overwrite maximum clock info, with the last one taking priority
264                    Some(Section::Range) if line.starts_with("VDDC_CURVE_SCLK") => {
265                        let (range, _) = parse_range_line(line, i)?;
266                        curve_sclk_points.push(range);
267                    }
268                    Some(Section::Range)
269                        if line.starts_with("VDDC_CURVE_VOLT")
270                            || (line.starts_with("VDDC_CURVE:") && line.contains("mv")) =>
271                    {
272                        let (range, _) = parse_range_line(line, i)?;
273                        curve_voltage_points.push(range);
274                    }
275                    Some(Section::Range) if line.starts_with("CCLK_RANGE") => {
276                        lines.next();
277                        lines.next();
278                    }
279                    Some(Section::Range) => {
280                        let (range, name) = parse_range_line(line, i)?;
281                        match name {
282                            "SCLK" => allowed_sclk_range = Some(range),
283                            "SCLK_OFFSET" => allowed_sclk_offset_range = Some(range),
284                            "MCLK" => allowed_mclk_range = Some(range),
285                            "VDDGFX_OFFSET" => voltage_offset_range = Some(range),
286                            "CCLK" => (), // Ignore Van Gogh CPU clocks
287                            other => {
288                                return Err(ParseError {
289                                    msg: format!("Unexpected range item: {other}"),
290                                    line: i,
291                                }
292                                .into())
293                            }
294                        }
295                    }
296                    Some(Section::Sclk) => parse_min_max_line(line, i, &mut current_sclk_range)?,
297                    Some(Section::SclkOffset) => {
298                        let line = line.to_ascii_lowercase();
299                        let raw_value = line.trim_end_matches("mhz");
300                        let value = raw_value
301                            .parse()
302                            .context("Could not parse sclk offset value")?;
303                        sclk_offset = Some(value);
304                    }
305                    Some(Section::Mclk) => parse_min_max_line(line, i, &mut current_mclk_range)?,
306                    Some(Section::VddcCurve) => {
307                        let _ = push_level_line(line, &mut vddc_curve, i);
308                    }
309                    Some(Section::VddGfxOffset) => {
310                        let offset = parse_voltage_offset_line(line, i)?;
311                        voltage_offset = Some(offset);
312                    }
313                    None => {
314                        return Err(ParseError {
315                            msg: "Unexpected line without section".to_owned(),
316                            line: i,
317                        }
318                        .into())
319                    }
320                },
321            }
322            i += 1;
323        }
324
325        let od_range = OdRange {
326            sclk: allowed_sclk_range,
327            sclk_offset: allowed_sclk_offset_range,
328            mclk: allowed_mclk_range,
329            curve_sclk_points,
330            curve_voltage_points,
331            voltage_offset: voltage_offset_range,
332        };
333
334        Ok(Self {
335            current_sclk_range: current_sclk_range.unwrap_or_default(),
336            sclk_offset,
337            current_mclk_range: current_mclk_range.unwrap_or_default(),
338            vddc_curve,
339            od_range,
340            voltage_offset,
341        })
342    }
343}
344
345impl Table {
346    /// Clears the table of all "applicable" values.
347    ///
348    /// This removes all values except the allowed range and voltage curve.
349    /// You can use it to avoid overwriting the table with already present values, as it can be problematic on some cards.
350    /// It is intended to be used before calling `set_*` functions and generating commands/writing the table.
351    pub fn clear(&mut self) {
352        self.current_sclk_range = Range::empty();
353        self.current_mclk_range = Range::empty();
354        self.sclk_offset = None;
355        self.voltage_offset = None;
356    }
357
358    /// Normalizes the VDDC curve making sure all of the values are within the allowed range.
359    /// This is needed as some GPUs have default values outside of the allowed range.
360    pub fn normalize_vddc_curve(&mut self) {
361        for (i, point) in self.vddc_curve.iter_mut().enumerate() {
362            if let Some(sclk_range) = self.od_range.curve_sclk_points.get(i) {
363                let normalized_clockspeed = normalize_value(point.clockspeed, *sclk_range);
364                point.clockspeed = normalized_clockspeed;
365            }
366
367            if let Some(voltage_range) = self.od_range.curve_voltage_points.get(i) {
368                let normalized_voltage = normalize_value(point.voltage, *voltage_range);
369                point.voltage = normalized_voltage;
370            }
371        }
372    }
373}
374
375fn normalize_value(mut value: i32, range: Range) -> i32 {
376    if let Some(min_allowed) = range.min {
377        value = cmp::max(min_allowed, value);
378    }
379    if let Some(max_allowed) = range.max {
380        value = cmp::min(max_allowed, value);
381    }
382
383    value
384}
385
386/// The ranges for overclocking values which the GPU allows to be used.
387#[derive(Debug, Clone, PartialEq, Eq)]
388#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
389pub struct OdRange {
390    /// Clocks range for sclk (in MHz). Present on RDNA1-3.
391    pub sclk: Option<Range>,
392    /// Clocks offset range for sclk (in MHz). Present on at least RDNA4.
393    pub sclk_offset: Option<Range>,
394    /// Clocks range for mclk (in MHz). Present on discrete GPUs only.
395    pub mclk: Option<Range>,
396    /// Frequencies available at specific levels.
397    pub curve_sclk_points: Vec<Range>,
398    /// Ranges available at specific levels.
399    pub curve_voltage_points: Vec<Range>,
400    /// Allowed voltage offset range. Present on RDNA3+.
401    pub voltage_offset: Option<Range>,
402}
403
404#[derive(Debug)]
405enum Section {
406    Sclk,
407    SclkOffset,
408    Mclk,
409    VddcCurve,
410    Range,
411    VddGfxOffset,
412}
413
414fn parse_clockspeed_line(line: &str, i: usize) -> Result<(i32, usize)> {
415    let mut split = line.split_whitespace();
416    let num = parse_line_item(&mut split, i, "level number", &[":"])?;
417    let clockspeed = parse_line_item(&mut split, i, "clockspeed", &["mhz"])?;
418
419    Ok((clockspeed, num))
420}
421
422fn parse_min_max_line(line: &str, i: usize, range: &mut Option<Range>) -> Result<()> {
423    let (clockspeed, num) = parse_clockspeed_line(line, i)?;
424    match num {
425        0 => {
426            *range = Some(Range::min(clockspeed));
427            Ok(())
428        }
429        1 => {
430            if let Some(range) = range {
431                range.max = Some(clockspeed);
432            } else {
433                *range = Some(Range::max(clockspeed));
434            }
435            Ok(())
436        }
437        _ => Err(ParseError {
438            msg: format!("Unexpected range number {num}"),
439            line: i,
440        }
441        .into()),
442    }
443}
444
445fn parse_voltage_offset_line(line: &str, i: usize) -> Result<i32> {
446    match line.to_lowercase().strip_suffix("mv") {
447        Some(raw_value) => Ok(raw_value.parse()?),
448        None => Err(ParseError {
449            msg: format!("Could not find expected `mV` suffix in offset line {line}"),
450            line: i,
451        }
452        .into()),
453    }
454}
455
456fn clockspeed_line(symbol: char, index: usize, clockspeed: i32) -> String {
457    format!("{symbol} {index} {clockspeed}\n")
458}
459
460fn vddc_curve_line(index: usize, clockspeed: i32, voltage: i32) -> String {
461    format!("vc {index} {clockspeed} {voltage}\n")
462}
463
464fn voltage_offset_line(offset: i32) -> String {
465    format!("vo {offset}\n")
466}
467
468#[cfg(test)]
469mod tests {
470    use super::{OdRange, Table};
471    use crate::{
472        gpu_handle::overdrive::{
473            arr_commands, tests::TABLE_PHOENIX, ClocksLevel, ClocksTable, Range,
474        },
475        include_table,
476    };
477    use insta::assert_yaml_snapshot;
478    use pretty_assertions::assert_eq;
479    use std::str::FromStr;
480
481    const TABLE_5500XT: &str = include_table!("rx5500xt");
482    const TABLE_5700XT: &str = include_table!("rx5700xt");
483    const TABLE_6900XT: &str = include_table!("rx6900xt");
484    const TABLE_6700XT: &str = include_table!("rx6700xt");
485    const TABLE_6800: &str = include_table!("rx6800");
486    const TABLE_7900XTX: &str = include_table!("rx7900xtx");
487    const TABLE_7900XT: &str = include_table!("rx7900xt");
488    const TABLE_7800XT: &str = include_table!("rx7800xt");
489    const TABLE_9070XT: &str = include_table!("rx9070xt");
490    const TABLE_VANGOGH: &str = include_table!("vangogh");
491
492    #[test]
493    fn parse_5700xt_full() {
494        let table = Table::from_str(TABLE_5700XT).unwrap();
495
496        assert_eq!(table.current_sclk_range, Range::full(800, 2100));
497        assert_eq!(table.current_mclk_range, Range::max(875));
498
499        let vddc_curve = [(800, 711), (1450, 801), (2100, 1191)]
500            .map(|(clockspeed, voltage)| ClocksLevel::new(clockspeed, voltage));
501        assert_eq!(table.vddc_curve, vddc_curve);
502
503        let curve_sclk_points = vec![
504            Range::full(800, 2150),
505            Range::full(800, 2150),
506            Range::full(800, 2150),
507        ];
508        let curve_voltage_points = vec![
509            Range::full(750, 1200),
510            Range::full(750, 1200),
511            Range::full(750, 1200),
512        ];
513
514        let od_range = OdRange {
515            sclk: Some(Range::full(800, 2150)),
516            mclk: Some(Range::full(625, 950)),
517            curve_sclk_points,
518            curve_voltage_points,
519            sclk_offset: None,
520            voltage_offset: None,
521        };
522        assert_eq!(table.od_range, od_range);
523    }
524
525    #[test]
526    fn generic_actions_5700xt() {
527        let mut table = Table::from_str(TABLE_5700XT).unwrap();
528        assert_eq!(table.get_max_sclk(), Some(2100));
529        assert_eq!(table.get_max_mclk(), Some(875));
530        assert_eq!(table.get_max_sclk_voltage(), Some(1191));
531
532        table.set_max_sclk(2050).unwrap();
533        assert_eq!(table.get_max_sclk(), Some(2050));
534        assert_eq!(table.current_sclk_range.max, Some(2050));
535
536        table.set_max_mclk(950).unwrap();
537        assert_eq!(table.get_max_mclk(), Some(950));
538        assert_eq!(table.current_mclk_range.max, Some(950));
539
540        table.set_max_voltage(1150).unwrap();
541        assert_eq!(table.vddc_curve[2].voltage, 1150);
542
543        let sclk_range = table.get_max_sclk_range();
544        let mclk_range = table.get_max_mclk_range();
545        let voltage_range = table.get_max_voltage_range();
546        assert_eq!(sclk_range, Some(Range::full(800, 2150)));
547        assert_eq!(mclk_range, Some(Range::full(625, 950)));
548        assert_eq!(voltage_range, Some(Range::full(750, 1200)));
549    }
550
551    #[test]
552    fn write_commands_5700xt() {
553        let mut table = Table::from_str(TABLE_5700XT).unwrap();
554
555        table.set_max_sclk(2150).unwrap();
556        table.set_min_sclk(850).unwrap();
557        table.set_max_mclk(950).unwrap();
558        table.set_max_voltage(1200).unwrap();
559
560        let mut buf = Vec::new();
561        table
562            .write_commands(&mut buf, &table.clone().into())
563            .unwrap();
564        let commands = String::from_utf8(buf).unwrap();
565
566        let expected_commands = arr_commands([
567            "s 0 850",
568            "s 1 2150",
569            "m 1 950",
570            "vc 0 850 711",
571            "vc 1 1450 801",
572            "vc 2 2150 1200",
573        ]);
574
575        assert_eq!(expected_commands, commands);
576    }
577
578    #[test]
579    fn normalize_vddc_curve_5700xt() {
580        let mut table = Table::from_str(TABLE_5700XT).unwrap();
581        let voltage_range = table.od_range.curve_voltage_points[0];
582        // The table has points outside of the allowed range by default
583        assert!(table.vddc_curve.iter().any(|level| {
584            level.voltage < voltage_range.min.unwrap() || level.voltage > voltage_range.max.unwrap()
585        }));
586
587        table.normalize_vddc_curve();
588
589        // The table does not have any points outside of the allowed range after normalization
590        assert!(!table.vddc_curve.iter().any(|level| {
591            level.voltage < voltage_range.min.unwrap() || level.voltage > voltage_range.max.unwrap()
592        }));
593        assert_eq!(750, table.vddc_curve[0].voltage);
594    }
595
596    #[test]
597    fn write_commands_5500xt() {
598        let mut table = Table::from_str(TABLE_5500XT).unwrap();
599        table.clear();
600        table.set_max_sclk(1900).unwrap();
601        table.set_max_voltage(1140).unwrap();
602
603        let commands = table.get_commands(&table.clone().into()).unwrap();
604        let expected_commands = vec![
605            "s 1 1900",
606            "vc 0 500 710",
607            "vc 1 1162 794",
608            "vc 2 1900 1140",
609        ];
610        assert_eq!(expected_commands, commands);
611    }
612
613    #[test]
614    fn write_commands_custom_5700xt() {
615        let table = Table {
616            current_sclk_range: Range::empty(),
617            current_mclk_range: Range::full(500, 1000),
618            sclk_offset: None,
619            vddc_curve: vec![ClocksLevel::new(300, 600), ClocksLevel::new(1000, 1000)],
620            voltage_offset: None,
621            od_range: OdRange {
622                sclk: None,
623                sclk_offset: None,
624                mclk: None,
625                curve_sclk_points: Vec::new(),
626                curve_voltage_points: Vec::new(),
627                voltage_offset: None,
628            },
629        };
630
631        let mut buf = Vec::new();
632        table
633            .write_commands(&mut buf, &table.clone().into())
634            .unwrap();
635        let commands = String::from_utf8(buf).unwrap();
636
637        let expected_commands =
638            arr_commands(["m 0 500", "m 1 1000", "vc 0 300 600", "vc 1 1000 1000"]);
639
640        assert_eq!(expected_commands, commands);
641    }
642
643    #[test]
644    fn parse_6900xt_full() {
645        let table = Table::from_str(TABLE_6900XT).unwrap();
646        assert_yaml_snapshot!(table);
647    }
648
649    #[test]
650    fn write_commands_6900xt_default() {
651        let table = Table::from_str(TABLE_6900XT).unwrap();
652        let commands = table.get_commands(&table.clone().into()).unwrap();
653
654        assert_yaml_snapshot!(commands);
655    }
656
657    #[test]
658    fn write_commands_6900xt_custom() {
659        let mut table = Table::from_str(TABLE_6900XT).unwrap();
660        table.clear();
661
662        table.set_min_sclk(800).unwrap();
663        table.set_max_sclk(2400).unwrap();
664        table.set_max_mclk(900).unwrap();
665        assert!(table.set_min_voltage(1000).is_err());
666
667        let commands = table.get_commands(&table.clone().into()).unwrap();
668        assert_yaml_snapshot!(commands);
669    }
670
671    #[test]
672    fn parse_6700xt_full() {
673        let table = Table::from_str(TABLE_6700XT).unwrap();
674        assert_yaml_snapshot!(table);
675    }
676
677    #[test]
678    fn generic_actions_6700xt() {
679        let table = Table::from_str(TABLE_6700XT).unwrap();
680
681        let max_sclk = table.get_max_sclk().unwrap();
682        assert_eq!(max_sclk, 2725);
683        let sclk_range = table.get_max_sclk_range().unwrap();
684        assert_eq!(sclk_range, Range::full(500, 2800));
685
686        let max_mclk = table.get_max_mclk().unwrap();
687        assert_eq!(max_mclk, 1000);
688        let mclk_range = table.get_max_mclk_range().unwrap();
689        assert_eq!(mclk_range, Range::full(674, 1075));
690
691        assert!(table.get_max_sclk_voltage().is_none());
692
693        let current_sclk_range = table.get_current_sclk_range();
694        assert_eq!(current_sclk_range, Range::full(500, 2725));
695
696        let current_mclk_range = table.get_current_mclk_range();
697        assert_eq!(current_mclk_range, Range::full(97, 1000));
698    }
699
700    #[test]
701    fn write_only_max_values_6700xt() {
702        let mut table = Table::from_str(TABLE_6700XT).unwrap();
703
704        table.clear();
705        table.set_max_sclk(2800).unwrap();
706        table.set_max_mclk(1075).unwrap();
707
708        let commands = table.get_commands(&table.clone().into()).unwrap();
709        assert_yaml_snapshot!(commands);
710    }
711
712    #[test]
713    fn write_new_min_over_old_max_7900xt() {
714        let original_table = Table::from_str(TABLE_7900XT).unwrap();
715
716        let mut new_table = original_table.clone();
717        new_table.clear();
718
719        new_table.set_min_mclk(1350).unwrap();
720        new_table.set_max_mclk(1350).unwrap();
721
722        new_table.set_min_sclk(3000).unwrap();
723        new_table.set_max_sclk(3000).unwrap();
724
725        let commands = new_table.get_commands(&original_table.into()).unwrap();
726        assert_yaml_snapshot!(commands);
727    }
728
729    #[test]
730    fn parse_6800_full() {
731        let table = Table::from_str(TABLE_6800).unwrap();
732        assert_yaml_snapshot!(table);
733    }
734
735    #[test]
736    fn set_max_values_6800() {
737        let mut table = Table::from_str(TABLE_6800).unwrap();
738
739        table.clear();
740        table.set_max_sclk(2400).unwrap();
741        assert!(table.set_max_sclk(2700).is_err());
742        table.set_max_mclk(1050).unwrap();
743        table.voltage_offset = Some(10);
744
745        assert_yaml_snapshot!(table.get_commands(&table.clone().into()).unwrap());
746    }
747
748    #[test]
749    fn parse_7900xtx_full() {
750        let table = Table::from_str(TABLE_7900XTX).unwrap();
751        assert_yaml_snapshot!(table);
752    }
753
754    #[test]
755    fn parse_7900xt_full() {
756        let table = Table::from_str(TABLE_7900XT).unwrap();
757        assert_yaml_snapshot!(table);
758    }
759
760    #[test]
761    fn parse_7800xt_full() {
762        let table = Table::from_str(TABLE_7800XT).unwrap();
763        assert_yaml_snapshot!(table);
764    }
765
766    #[test]
767    fn parse_9070xt_full() {
768        let table = Table::from_str(TABLE_9070XT).unwrap();
769        assert_yaml_snapshot!(table);
770    }
771
772    #[test]
773    fn set_clock_offset_9070xt() {
774        let mut table = Table::from_str(TABLE_9070XT).unwrap();
775        table.clear();
776        table.sclk_offset = Some(200);
777        table.voltage_offset = Some(-50);
778        assert_yaml_snapshot!(table.get_commands(&table.clone().into()).unwrap());
779    }
780
781    #[test]
782    fn set_7800xt_voltage() {
783        let mut table = Table::from_str(TABLE_7800XT).unwrap();
784        table.set_voltage_offset(-300).unwrap();
785        table.set_voltage_offset(100).unwrap_err();
786    }
787
788    #[test]
789    fn parse_phoenix_full() {
790        let table = Table::from_str(TABLE_PHOENIX).unwrap();
791        assert_yaml_snapshot!(table);
792    }
793
794    #[test]
795    fn parse_vangogh_full() {
796        let table = Table::from_str(TABLE_VANGOGH).unwrap();
797        assert_yaml_snapshot!(table);
798    }
799}