Guix: How to Change Your Default CPU Frequency Governor

There are I believe a few ways to do this, but I think this is the simplest: Simply insert the following code, nested in your operating-system declaration:

  (kernel-arguments (cons "cpufreq.default_governor=conservative" %default-kernel-arguments))

Replace “conservative” with the governor you want. The same code in an example operating-system declaration:

(operating-system
  (locale "en_US.utf8")
  (timezone "America/Anchorage")
  (keyboard-layout
    (keyboard-layout "us" "altgr-intl"))
  (bootloader
    (bootloader-configuration
      (bootloader grub-bootloader)
      (target "/dev/sda")
      (keyboard-layout keyboard-layout)))
  (kernel-arguments (cons "cpufreq.default_governor=conservative" %default-kernel-arguments))
<etc...>

From what I was told (on IRC) this method will only work with linux kernel release 5.9 or later, which was only added to the Guix main repository five days ago. So you might need to run a guix pull first if you haven’t update Guix in a while. The newer kernel has a feature to allow you to pass in the default governor as a boot parameter, rather than having to recompile the kernel.

(As a side note, today’s commits of the Guix master repository have a broken gdm login manager so I actually used commit b618c15 from five days ago to test the feature being highlighted in this blog post.)

The cpupower command will give you a list of governors available for your system. I would recommend “ondemand” for most users, as that lowers your cpu frequency when the system is running at lower loads, saving you electricity and heat. However, I prefer “conservative”, which is the same, but waits a little longer before bumping up the frequency, so as to not overreact to quick load spikes. “Powersave”, I believe, keeps you running always at the lowest frequency, while “performance” keeps you running always at the highest frequency.

christopher@nightshade ~$ sudo cpupower frequency-info
analyzing CPU 0:
  driver: acpi-cpufreq
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency: 4.0 us
  hardware limits: 800 MHz - 3.30 GHz
  available frequency steps:  3.30 GHz, 2.60 GHz, 2.10 GHz, 800 MHz
  available cpufreq governors: conservative ondemand userspace powersave performance schedutil
  current policy: frequency should be within 800 MHz and 3.30 GHz.
                  The governor "conservative" may decide which speed to use
                  within this range.
  current CPU frequency: 800 MHz (asserted by call to hardware)
  boost state support:
    Supported: no
    Active: no
    Boost States: 0
    Total States: 4
    Pstate-P0:  3300MHz
    Pstate-P1:  2600MHz
    Pstate-P2:  2100MHz
    Pstate-P3:  800MHz

cpupower utility

I think this command-line utility is nifty:

christopher@nightshade ~ [env]$ guix show cpupower
name: cpupower
version: 5.8.12
outputs: out
systems: x86_64-linux i686-linux
dependencies: gettext-minimal@0.20.1 pciutils@3.6.4
location: gnu/packages/linux.scm:5458:2
homepage: https://www.gnu.org/software/linux-libre/
license: GPL 2
synopsis: CPU frequency and voltage scaling tools for Linux  
description: cpupower is a set of user-space tools that use the cpufreq feature of the Linux kernel to retrieve and control processor features related to
+ power saving, such as frequency and voltage scaling.

This command shows you what cpu governor you are using, and the current cpu frequency:

christopher@nightshade ~ [env]$ sudo cpupower frequency-info
analyzing CPU 0:
  driver: acpi-cpufreq
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency: 4.0 us
  hardware limits: 800 MHz - 3.30 GHz
  available frequency steps:  3.30 GHz, 2.60 GHz, 2.10 GHz, 800 MHz
  available cpufreq governors: conservative ondemand userspace powersave performance schedutil
  current policy: frequency should be within 800 MHz and 3.30 GHz.
                  The governor "userspace" may decide which speed to use
                  within this range.
  current CPU frequency: 800 MHz (asserted by call to hardware)
  boost state support:
    Supported: no
    Active: no
    Boost States: 0
    Total States: 4
    Pstate-P0:  3300MHz
    Pstate-P1:  2600MHz
    Pstate-P2:  2100MHz
    Pstate-P3:  800MHz

And this sets the cpu frequency:

christopher@nightshade ~ [env]$ sudo cpupower frequency-set -f 800Mhz
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2

A lower frequency is better when you aren’t busy using the computer, as that saves electricity and generates less heat. Of course, you might want to explorer the different governors as well.

BYOK: Bare Metal Forth

BYOK forth running on i386 qemu VM
BYOK Forth running on bare metal x86(-64) desktop computer

Forth is a language that was designed to be run on bare-metal – without an underlying operating system. Some interesting quotes from Chuck Moore:

The operating system is another concept that is curious. Operating systems are dauntingly complex and totally unnecessary. It’s a brilliant thing that Bill Gates has done in selling the world on the notion of operating systems. It’s probably the greatest con game the world has ever seen.

https://www.oreilly.com/library/view/masterminds-of-programming/9780596801670/ch04.html

Lisp did not address I/O. In fact, C did not address I/O and because it didn’t, it needed an operating system. Forth addressed I/O from the very beginning. I don’t believe in the most common denominator. I think that if you go to a new machine, the only reason it’s a new machine is because it’s different in some way and you want to take advantage of those differences. So, you want to be there at the input-output level so you can do that.

ibid

So, I was certainly interested in the idea of Forth running on bare-metal. What I found quickly was byok.

I was able to get it compiled fairly easily using the pre-built toolchain provided by the author. However, I had to delete two lines in the kernel directory Makefile:

diff --git a/kernel/Makefile b/kernel/Makefile
index b54cfb0..4ed0dc3 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -49,9 +49,7 @@ $(CRTN_OBJ) \
 
 OBJ_LINK_LIST:=\
 $(CRTI_OBJ) \
-$(CRTBEGIN_OBJ) \
 $(OBJS) \
-$(CRTEND_OBJ) \
 $(CRTN_OBJ) \
 
 all: byok.kernel

The system boots up using Grub. The words display as expected.

words display in BYOK

I was able to create variables and store/pull data from them. However, a slight oddity is that the @ symbol and the ” symbol are swapped around on the keyboard, which had me confused for about 10 minutes. But I was fine after figuring that out.

working with variables in BYOK

The system comes with a nice block editor, for saving a program to block memory, though I think actual disk I/O is not coded yet.

block editor in BYOK
loading code from block memory

And the dump word is available:

hex dump which appears after running v cell dump

I’m definitely interested in playing around with this some more, and exploring what x86 architecture functionality is accessible with memory reads and writes.

Forth Solution to Grecian Computer Puzzle

Grecian Computer Puzzle (Product of Project Genius)

I got this puzzle a while ago as a present. It is a wooden puzzle made up of rotating discs of numbers. The goal is to line up the discs so that all the numbers add up to 42. What makes this especially complicated is that most of the discs have gaps in them, and therefore propagate numbers up from below depending on their position.

As an exercise, I wanted to see if I could write a memory-space efficient Forth program which would solve this puzzle.

In principle, this seemed like a simple idea, as I only need to represent the discs in memory and rotate them methodically, checking along the way for the solution. The trickiest part, however, was figuring out how to represent these discs in memory, while preserving their holes, and figuring out how to overlay them properly.

In the approach I took, there is one 48 byte memory space representing the board as a composite of the discs:

decimal
create board 48 allot

The first disc, the bottom one, is easy because it has no holes. This is represented by another 48 byte array.

create disc0
 2 c,  5 c, 10 c,  7 c, 16 c,  8 c,  7 c,  8 c,  8 c,  3 c,  4 c, 12 c,
 3 c,  3 c, 14 c, 14 c, 21 c, 21 c,  9 c,  9 c,  4 c,  4 c,  6 c,  6 c,
 8 c,  9 c, 10 c, 11 c, 12 c, 13 c, 14 c, 15 c,  4 c,  5 c,  6 c,  7 c,
14 c, 11 c, 14 c, 14 c, 11 c, 14 c, 11 c, 14 c, 11 c, 11 c, 14 c, 11 c,

For the other discs, I will also use byte arrays. However, I have to represent the holes some how. The most practical choice is to use the number zero, which is not used anywhere on the actual board, and conveniently maps to the boolean false value in Forth. So, I create disc1, disc2, and so on. You see that my number of rows shrinks with each disc.

create disc1
 1 c,  0 c,  9 c,  0 c, 12 c,  0 c,  6 c,  0 c, 10 c,  0 c, 10 c,  0 c,
 3 c, 26 c,  6 c,  0 c,  2 c, 13 c,  9 c,  0 c, 17 c, 19 c,  3 c, 12 c,
 9 c, 20 c, 12 c,  3 c,  6 c,  0 c, 14 c, 12 c,  3 c,  8 c,  9 c,  0 c,
 7 c,  0 c,  9 c,  0 c,  7 c, 14 c, 11 c,  0 c,  8 c,  0 c, 16 c,  2 c,

create disc2
 5 c,  0 c, 10 c,  0 c,  8 c,  0 c, 22 c,  0 c, 16 c,  0 c,  9 c,  0 c,
21 c,  6 c, 15 c,  4 c,  9 c, 18 c, 11 c, 26 c, 14 c,  1 c, 12 c,  0 c,
 9 c, 13 c,  9 c,  7 c, 13 c, 21 c, 17 c,  4 c,  5 c,  0 c,  7 c,  8 c, 

create disc3
14 c,  0 c,  9 c,  0 c, 12 c,  0 c,  4 c,  0 c,  7 c, 15 c,  0 c,  0 c,
11 c,  6 c, 11 c,  0 c,  6 c, 17 c,  7 c,  3 c,  0 c,  6 c,  0 c, 11 c,

create disc4
 3 c,  0 c,  6 c,  0 c, 10 c,  0 c,  7 c,  0 c, 15 c,  0 c,  8 c,  0 c,

Now, how to manipulate the board? The computationally simple and space-efficient approach is to simple rotate the bytes in place. So, here is my row rotation function, followed by one that rotates a whole disc. (Please forgive some of my inconsistent parameter descriptions…)

: rot-row ( addr -- )
    dup 11 + c@ swap ( c a )
    11 0 u+do
        dup 10 + i - c@ swap ( c c a )
        dup 11 + i - ( c c a a )
        rot ( c a a c )
        swap c! ( c a )
    loop
    c!
;

: rot-disc ( addr n )
    0 u+do
        dup 12 i * +
        rot-row loop
    drop
;

Having all the discs, and a way to rotate each of them, eventually I would need a procedure to stack them onto the board:

: overlay ( a a u -- )
    12 * 0 u+do ( a1 a2 )
        dup i + c@ ( a1 a2 c )
        dup 0<> if
            2 pick ( a1 a2 c a1 )
            i + c! ( a1 a2 )
        else drop
        endif
    loop
    drop drop
;

: overlay-all
    board disc0 4 overlay
    board disc1 4 overlay
    board 12 + disc2 3 overlay
    board 24 + disc3 2 overlay
    board 36 + disc4 1 overlay
;

We are pretty close now. I need a function to check for a solution, which is simple addition of the columns on the board:

: solved? ( -- bool )
    true
    12 0 u+do
        board i + c@
        board 12 i + + c@
        board 24 i + + c@
        board 36 i + + c@
        + + +
        42 <> if drop false leave then
    loop
;

Now, I’ve got to walk through rotating all the discs, checking for a solution in each case. I chose to do this with five nested procedures, which each handle their disc with the appropriate minor variations. (A nested loop would have worked also.)

: solve4 ( -- bool )
    false
    12 0 u+do
        overlay-all
        solved? if drop true leave else disc4 1 rot-disc then
    loop
;

: solve3 ( -- bool )
    false
    12 0 u+do
        solve4 if drop true leave else disc3 2 rot-disc then
    loop
;

: solve2 ( -- bool )
    false
    12 0 u+do
        solve3 if drop true leave else disc2 3 rot-disc then
    loop
;

: solve1 ( -- bool )
    false
    12 0 u+do
        solve2 if drop true leave else disc1 4 rot-disc then
    loop
;
    
: solve ( -- bool )
    false
    12 0 u+do
        solve1 if drop true leave else disc0 4 rot-disc then
    loop
;

Now, I just need to run the solve procedure. It should return -1 (true) and then I can print the board with the command board 48 dump, which prints the board memory.

Unfortunately, I did this, and after a few seconds, the program instead return 0 (false) meaning there is no solution. Naturally, I expected there was some fault in my coding, and I dived into debugging. After an hour of carefully checking code, and inserting debugging code here and there, I was still getting the same result, and getting discouraged.

At one point, I inserted some code that would, at least, allow me to see the closest solution. I found one solution that had all columns adding up to 42, and one column adding up to 47. I did a quick Internet search, and found this revelation on the Project Genius website!

Notice from Project Genius Inc of a defective early Grecian Computer product.

Yes, indeed, I happened to own one of the defective early models, which had a misprint. I edited my disc0 array to read 3 instead of 8 in that location (already corrected above). The edited program found the solution quickly.

christopher@nightshade ~/Repos/grecian-computer$ gforth grecian-computer.fs
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
solve . board 48 dump -1 
7F1CC5748238: 01 05 09 07  0C 08 06 08 - 0A 03 0A 0C  16 1A 10 0E  ................
7F1CC5748248: 09 0D 05 09  0A 13 08 0C - 0B 04 0E 07  0F 0D 15 0E  ................
7F1CC5748258: 0F 09 09 0C  08 07 03 0E - 06 08 0A 0B  07 0B 0F 06  ................
 ok

One must translate between hexidecimal to decimal, and then map the four arrays of twelve bytes onto the board, which gives you the solution (SPOILER WARNING!)

The Forth code above is provided under the GPLv3+ license:

    Copyright 2020 Christopher Howard

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

CKOUT: 328P as clock source

Left: Nano serving as fuse bit programmer. Middle: Uno with 328P CKOUT fuse bit programmed. Right, 16Mhz signal coming out of pin 8 (PB0) of Uno.

Wanting to work with an AD9833 signal generator chip, which needs an external master clock source, I was wondering if I could somehow tap into the 16 Mhz clock already feeding the 328P. The answer was yes, it is possible on the 328P to feed this to pin PB0 (a.k.a., digital pin 8).

328P datasheet: Clock Output Buffer

The only tricky part is you have to program a fuse bit on the chip, which can not be done through self-programming, the way you normally upload code through the arduino bootloader. You need to set bit 6 (CKOUT) in the lfuse byte to 0:

So, you have to have a separate programmer, and also you must write the whole byte, meaning you want to be careful to preserve the other bit settings. Fortunately, through the ArduinoISP sketch, you can use another Arduino to program it.

https://www.arduino.cc/en/Tutorial/BuiltInExamples/ArduinoISP/

This is fairly straightforward to wire up. However, somebody on #arduino IRC had to explain to me that it is necessary to put a 60 Ohm resistor across VCC and rst pins on the programmer, if using an Uno or Nano for the programmer. I didn’t catch that reading the tutorial.

Once wired up, you can use avrdude to write the lfuse byte. In my case.

avrdude -c stk500v1 -p m328p -P /dev/ttyUSB0 -b 19200 -U lfuse:w:0xBF:m

The last part of the command instructs avrdude to write byte 0xBF to the lfuse byte. A few things that might not be obvious about that: (1) in 328P fuse bytes, a “0” is considered the “programmed” state, the opposite of what you would intuitively expect; (2) I had to look at the boards.txt file in the arduino ide could to figure out that the “standard” byte value (for an MC used on an Arduino Uno board) is 0xFF. So, if I’m just changing bit 6 to zero, 0xFF needs to become 0xBF.

Screen photo of writing 0xBF to lfuse using avrdude

The fuse change was successful, and I was able to measure 16 Mhz out of PB0 (normally digital pin 8).