From CircleCI to GitHub Actions for Jekyll publishing

I’ve been a big fan of static site generation since I switched from WordPress to Jekyll back in 2018. I’m also a big fan of learning new technologies as they come along and now GitHub Actions are out in the wild I thought this would be a good opportunity to see how I can port my existing custom CircleCI build to Jekyll.

The CircleCI job

A quick recap from part 5 - Hosting & Building, my CircleCI configuration was basically two jobs that have subsequently been tweaked since then. They are:

Build

The build job’s responsibility was to configure a Ruby environment capable of executing Jekyll to build the site, removing the .html extension from the output filenames and then indexing the content using Algolia. Here is the start of the .circleci/config.yml I was using for that:

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.1
    working_directory: ~/jekyll
    environment:
      - JEKYLL_ENV=production
      - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
      - JOB_RESULTS_PATH=run-results
    steps:
      - checkout
      - restore_cache:
          key: jekyll-{{ .Branch }}-{{ checksum "Gemfile" }}
      - run:
          name: Update gems
          command: gem update --system
      - run:
          name: Install dependencies
          command: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3
      - save_cache:
          key: jekyll-{{ .Branch }}-{{ checksum "Gemfile" }}
          paths:
            - "vendor/bundle"
      - run:
          name: Create results directory
          command: mkdir -p $JOB_RESULTS_PATH
      - run:
          name: Build site
          command: bundle exec jekyll build --config _config.yml,_config-publish.yml 2>&1 | tee $JOB_RESULTS_PATH/build-results.txt
      - run:
          name: Remove .html suffixes
          command: find _site -name "*.html" -not -name "index.html" -exec rename -v 's/\.html$//' {} \;
      - run:
          name: Index with Algolia
          command: bundle exec jekyll algolia --config _config.yml,_config-publish.yml
      - store_artifacts:
          path: run-results/
          destination: run-results
      - persist_to_workspace:
          root: ~/jekyll
          paths:
            - _site

Deploy

The deploy’s job is to take the output from the build job and sync it with the S3 bucket I use to publish the site and then to apply a bunch of AWS S3 specific commands using the AWS CLI tool to ensure metadata, redirects and caching are correctly set all using a Python environment.

  deploy:
    docker:
      - image: circleci/python:2.7
    working_directory: ~/jekyll
    steps:
      - attach_workspace:
          at: ~/jekyll
      - run:
          name: Install AWS CLI
          command: sudo pip install awscli
      - run:
          name: Deploy to S3
          command: aws s3 sync _site s3://damieng-static/ --delete --content-type=text/html
      - run:
          name: Correct MIME for robots.txt automatically
          command: aws s3 cp s3://damieng-static/robots.txt s3://damieng-static/robots.txt --metadata-directive="REPLACE"
      - run:
          name: Correct MIME for sitemap.xml automatically
          command: aws s3 cp s3://damieng-static/sitemap.xml s3://damieng-static/sitemap.xml --metadata-directive="REPLACE"
      - run:
          name: Correct MIME for Atom feed manually
          command: aws s3 cp s3://damieng-static/feed.xml s3://damieng-static/feed.xml --no-guess-mime-type --content-type="application/atom+xml" --metadata-directive="REPLACE"
      - run:
          name: Redirect /damieng for existing RSS subscribers
          command: aws s3api put-object --bucket damieng-static --key "damieng" --website-redirect-location "https://damieng.com/feed.xml"
      - run:
          name: Latest Envy Code R redirect
          command: aws s3api put-object --bucket damieng-static --key "envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
      - run:
          name: Latest Envy Code R redirect #2
          command: aws s3api put-object --bucket damieng-static --key "fonts/envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
      - run:
          name: Latest Envy Code R download
          command: aws s3api put-object --bucket damieng-static --key "downloads/latest/EnvyCodeR" --website-redirect-location "https://download.damieng.com/fonts/original/EnvyCodeR-PR7.zip"
      - run:
          name: Correct MIME for CSS files
          command: aws s3 cp s3://damieng-static/css/ s3://damieng-static/css/ --metadata-directive="REPLACE" --recursive

GitHub Actions

So how could I go about this in GitHub Actions? I have to admit I spent far too long poking around and examining existing Jekyll actions but the reality is I have a bunch of steps here I need fine control of especially around Algolia and S3. I finally ended up on what was quite a simple port.

Unlike the CircleCI configuration I did not split these into two separate jobs because:

  1. There is no exact equivalent to persist_to_workspace and attach_workspace
  2. The alternative of storing and restoring the artifacts leaves large useless artifacts around
  3. I never ended up running the jobs separately
  4. GitHub Actions provides an environment with both Ruby and AWS CLI installed

So, on to the configuration which lives in .github/workflows/jekyll.yml in my case:

name: Build site and deploy

on:
  push:
    branches: [ master ]

jobs:
  build:
    name: Build + Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.1

      - name: Ruby gem cache
        uses: actions/cache@v1
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-
            
      - name: Install dependencies
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3

      - name: Build Jekyll site
        run:  bundle exec jekyll build --config _config.yml,_config-publish.yml

      - name: Remove .html suffixes except for index.html
        run: find _site -name "*.html" -not -name "index.html" | while read f; do mv "$f" "${f%.html}"; done                      

      - name: Index with Algolia
        env:
          ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
        run:  bundle exec jekyll algolia --config _config.yml,_config-publish.yml
    
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
    
      - name: Sync site with S3
        run:  aws s3 sync _site s3://damieng-static/ --delete --content-type=text/html

      - name: Correct MIME types
        run: |
          aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.txt" --metadata-directive="REPLACE"
          aws s3 cp s3://damieng-static/sitemap.xml s3://damieng-static/sitemap.xml --metadata-directive="REPLACE"
          aws s3 cp s3://damieng-static/feed.xml s3://damieng-static/feed.xml --no-guess-mime-type --content-type "application/atom+xml" --metadata-directive "REPLACE"
          aws s3 cp s3://damieng-static/css/ s3://damieng-static/css/ --metadata-directive "REPLACE" --recursive
          aws s3 cp s3://damieng-static/js/ s3://damieng-static/js/ --metadata-directive "REPLACE" --recursive
      
      - name: Redirect /damieng for existing RSS subscribers
        run:  aws s3api put-object --bucket damieng-static --key "damieng" --website-redirect-location "https://damieng.com/feed.xml"

      - name: Latest Envy Code R redirects
        run:  |
          aws s3api put-object --bucket damieng-static --key "fonts/envy-code-r" --website-redirect-location "https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released"
          aws s3api put-object --bucket damieng-static --key "downloads/latest/EnvyCodeR" --website-redirect-location "https://download.damieng.com/fonts/original/EnvyCodeR-PR7.zip"
        
      - name: Set caching for images at 30 days
        run:  aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.svg" --include "*.ico" --include "*.jpg" --include "*.png" --include "*.webp" --include "*.gif" --recursive --metadata-directive REPLACE --expires 2034-01-01T00:00:00Z --acl public-read --cache-control max-age=2592000,public

      - name: Set caching for CSS and JS at 1 hour
        run:  aws s3 cp s3://damieng-static/ s3://damieng-static/ --exclude "*" --include "*.css" --include "*.js" --recursive --metadata-directive REPLACE --expires 2034-01-01T00:00:00Z --acl public-read --cache-control max-age=3600,public

I also took the opportunity to fix a long-running issue in that my S3 objects would lose my manually-applied cache settings (and new posts and files would not have any).

Gotchas

There were only a couple of bumps in the road once I decided on a straight-port rather than trying to leverage higher-level existing actions:

  1. The secrets configured in the repo settings were not automatically exposed to the commands running in the action. Instead you have to expose them using the ${{ secrets.KEY_NAME }} syntax.
  2. Somehow I was using rename instead of mv. I can’t recall why, maybe it was my Windows-ness creeping in but it’s been dropped from newer distros.

Summary

The syntax is surprisingly similar and build-times were about the same as CircleCI. It’s just nice to have it in one place. Time to port some other repos over!

[)amien

Notes from my Spectrum +3 manual

I’ve recently been working on a full HTML5 conversion of the Sinclair Spectrum +3 manual with full canvas-drawn screenshots and diagrams for smooth scaling/high res displays as well as some close font matching and layout as well as cross-reference links all over the place. More on that in the future.

What I wanted to show today was the odd commands, pokes and outs that were hastily scribbled down in the back of my manual over the years.

Spectrum +3/+2A specific

  • You know you can type SPECTRUM to go from +3 BASIC to 48K BASIC but did you know you can get back? RANDOMIZE USR 23354. It leaves CHR$ 163/164 as UDGs.
  • Switch off the +3 drive with POKE 23399,4. In 48K mode you can use POKE 23611,221 if it somehow stayed on.

128K specific

  • 128K machines are very compatible but the last two user-defined graphics (UDGs) T and U have become the SPECTRUM and PLAY command (for switching to 48K mode and using the AY-3-8912 sound chip). You can however switch them back to UDGs using POKE 23611,205.
  • POKE 23611,205; RANDOMIZE USR 4867; alternative way to switch from 128K to 48K.

A couple of VTX-5000 modem specific hacks:

  • OUT 8189,0 to switch the 128K ROM back in.
  • POKE 23611,29 also to switch from 48K to 128K mode.

All machines

  • POKE 23617,n changes the input mode for INPUT prompts. 0=Alpha (C/L), 1=Extended (E), 2=Graphics (G), 4=Keyword (K).
  • POKE 23658,0 turns off CAPS LOCK while POKE 23658,1 turns it on.
  • POKE 23692,255 will suppress the Scroll? prompt 255 times so you can auto-scroll.
  • RANDOMIZE USR 3582 to scroll the screen one line.
  • RANDOMIZE USR 3330 to scroll the entire screen.
  • LET A=32768: POKE 23607,A/256: POKE 23606,A-(PEEK 23607 * 256)

A few pokes to stop people messing with your BASIC programs!

  • POKE 23756,n to change the first line number to n. If you set this to 0 then it is not editable without reversing the poke! In 128K mode it lists line 0 over and over and can cause the editor to crash while adding new lines!
  • POKE 23613,82 will disable the BREAK key in your BASIC program
  • POKE 23613,0 causes BREAK to crash.
  • POKE 23755,100 causes the program listing to be invisible on BREAK while running (use POKE 23755,0 to get it back)

Header-less tape loading and saving…

LD A,255
LD IX,@START
LD DE,@LENGTH
SCF
JP 1218 ; Save or
JP 1366 ; Load

Enjoy,

[)amien

My own Delphi story - celebrating 25 years

Setting the scene…

It’s 1995 and a wiry looking engineer in need of a haircut is working at a tech services company in the Channel Islands. The island is Jersey (you can the French coastline on a clear day) and he’s over here for a week or two for training from his nearby home of Guernsey.

This company does everything from IBM AS/400 maintenance to custom PC software development - the developer involved was brought in and trained to work on a banking package on those AS/400s but has ended up on the PC development side through a series of improbable events.

Visual Basic 3 has been the company tool of choice for these tasks - supplemented with Microsoft Access for the data-driven projects (normally backed by Microsoft’s brand-new SQL Server which is taking over from Sybase installs). He’s been helping work on the in-house “In/Out” software the company uses to track who is the office vs who is out on calls (it is 18 months before the first mainstream instant-messenger product - ICQ - will appear and offer similar functionality).

He’s also been working on a document imaging and indexing solution (iStation) for two local government organizations (they love to make a solution for one company then tweak it for others like many ISVs) and that is the limit of his VB experience. He’s faced the insurmountable wall of Borland C++ and OWL enough times to feel that when it comes to VB… “This is the way”.

Arrival

I’m kicking around in the narrow row of desks - unlike the sales team we’re not important enough to get dividers between our desks to turn them into semi-cubes.

Screenshot of Delphi 1 IDE in action A colleague named Mike strolls in, drops his bag and announces ambiguously “I got it” and proceeds to power on his PC. “It” is a copy of Delphi - and a bootleg pre-release version at that (if I recall correctly).

I watch and ask questions as he and a few other developers crowd round his giant 17” CRT to check it out. At first glance it looks like Visual Basic with a few more included controls, a slicker IDE, but a more confusing language. Over the week that follows he keeps showing us new discoveries and interest increases.

Before the week is up I have a copy of the ZIP on my person to fly back home with.

Back home I get past the confusion of unit dependencies and Pascal idiosyncrasies like := for assignment and dig deeper discovering the beauty of the VCL - Visual Component Library.

My journey is aided by a curious programming book the likes of which I have never come across before or since. It is a humorous tale of a private detective also writing Delphi apps on the side. It is called the Delphi Programming Explorer and helps me get over the hump.

The VCL is hard to describe if you’ve never used it but I’ll give it a shot. It was basically like the VB controls but they gave you the source code and there was a proper hierarchy to it. Up to this point in my life I thought Object Oriented meant you had classes and instances of classes. I had no idea about interfaces, inheritance or good OO design but Delphi and the VCL introduced it to me.

I was hooked and when Delphi 2 came out it was time to dig very deep and buy my own copy.

Using it

Icons for controls in the Envy Delphi Development packIn time I pumped out an About95 control then a Delphi Control Pack that included a font combo box, a gradient control, an improved list view, a button that popped out a menu control, a splitter control, a color combo box. Useful functionality that was otherwise missing but often seen in other non-Delphi apps.

I was shocked years later when I picked up the book Developing Custom Delphi 3 Components that my controls and source were included on the CD.

It got so many downloads that my local ISP got in contact and asked me very nicely, in the absence of any formal limits on bandwidth down back then, to host the ZIP elsewhere as that and a few other tools I’d uploaded to my personal site were now responsible for a significant % of their bandwidth. Double digits if my memory serves me correctly and so the site shifted to Demon Internet.

Hot off the heels of that “success” I tried my hand at shareware again with the Envy Delphi Development pack. It included polished and extended versions of everything in the previous pack but now also an image view control, a database-bound label, a progress bar, shading control (like Borland C++), a tip-of-the-day control that needed nothing more than a text file of tips, a frame control to allow inset/outset and a bitmap tiling control for more texturing. At this point icons are sufficiently basic in the world that I can mostly get away with designing my own. I am a team of one.

I also put out my first proper shareware tool that actually provided a trickle of income for a while in the form of my Windows monitoring tool for “Remote Access BBS” called Monitor/RA. At one point Diamond Multimedia purchased a commercial license to monitor their European support BBS.

Later on I’d even make some controls that drew themselves looking like MacOS Platinum and NextStep.

My site back then had a heavy Delphi focus and is in love with small fonts, tiny icons, pastel shades and almost-invisibMore tle watermark textures.

Alternatives

I never managed to really get into Java desktop development despite many attempts at getting behind the JWT and the later Swing libraries. The screenshots looked good but it never felt properly native like Delphi did. Having to install a non-trivial runtime was also off-putting especially for these small side-projects.

Borland C++ still felt like a large mountain to climb and I had no reason and so those books stayed on my shelf for many years in the “I should get to this”. Of course OWL died as Microsoft Visual C++ came up - a product that has never really delivered on the Visual unless you were prepared to commit to another mountain in the form of the MFC.. a mountain that might have been climbable with a Visual designer but that never materialized.

Visual Basic was hard to go back to. It immediately felt limiting and having to learn COM to develop controls was a show-stopper. After I had picked up Delphi 2 in fact I never touched VB again as far as I can recall. I instead spent a lot of time in Microsoft Access connected to SQL Server, then onto ASP and then finally ASP.NET.

I remember being extremely excited when WinForms was announced. C# is a language I came to love more than Pascal and WinForms was a definite improvement over VB - you could now write your own controls much more easily but .NET Framework eschewed nice object-orientated design and patterns and instead just went with some inheritance and helper methods - ironically something Java did a much better job of. Comparing IO in .NET vs IO in Java really highlights this - the latter more than happy to provide wrappers and layers for great composition vs .NET lumping tons of stuff into static methods on File and providing a bunch of Readers you can never remember.

During my degree we were also exposed to Borland C++ builder which took the VCL and provided you with the ability to use C++ instead of Pascal. I quite enjoyed using that and just this year I’ve found myself wanting to create a single-executable solution but C++ was again something nobody in my circles at that point was asking for.

Legacy

Screenshot of Disk Image Managers sector overviewI kept buying copies of Delphi (at every alternate release, it’s my own money) and found myself creating a tool for working with disk images of 3” floppy disks (yes, not 3.5”) used by Sinclair, Amstrad and Tatung machines. This was really for the archiving community and Delphi was the tool I fell back to - I had zero interest in telling people to install big run-time packages. It’s still maintained today although has been subsequently ported to the free-software Lazarus alternative.

At one point I was also with development of a Spectrum emulator developed in “Delphi” but the developers dove into x86 assembler too often for me to feel useful which irked me - I would rather work on something maintainable than cater to a small crowd of people that don’t want to upgrade their machines for more power.

Pascal always felt a little awkward to me but the VCL, IDE and those self-contained small executables (200KB in initial versions IIRC) were great and living on a small island meant that programming jobs were scarce and almost exclusively limited to COBOL on AS/400’s or Microsoft tools on Windows.

The draw for me was always this idea of small standalone exes which is hard to achieve on Windows with a decent GUI builder. There are third-party options but every one of them seems to fail to provide the seamless enjoyable experience Delphi and the VCL did and many of them are either poorly maintained or can cost a small fortune.

Interestingly Delphi and C++ Builder and now available in free community editions - confusingly bundled together into the name RAD Studio. I’ve been wanting to write a very specific Windows native app of late that needs to call a bunch of Windows APIs and so C++ Builder is back on my machine. (I just wish the small binaries of old hadn’t become megabyte monsters… I’m sure I don’t need most of what’s in there)

[)amien

I know some of this is a bit gushing, that’s nostalgia for you I guess. I can at least assure you that I receive no compensation/kickback/affiliate from Embarcadero who currently own Delphi/C++ Builder and RAD Studio (I had to Google their name, seriously).

DDR4 memory information in Linux

Background

If you’ve built a PC desktop in the last few years you’ve probably been exposed to the confusing array of DDR4 information when it comes to buying RAM.

A shot of G-Skill Trident Z RGB memory modules glowing in my PC

What it comes down to is not all RAM is created equal. Once you get past pin size and memory capacity you’ll have to filter down by speed. Speed isn’t measured in a simple way - you may see a rated speed like 2400MHz but you’ll also see another bunch of numbers 16-16-16-39 that indicate the necessary clock cycles to perform certain types of memory operations.

While JEDEC - the people behind the DDR4 standard - ratify the well known speeds like 2400 they’ve been quite behind the desires of consumers and manufacturers and so Intel created Extreme Memory Profile (XMP) which lets the RAM tell the PC a faster set of timings and clock speed it can support and what voltage it needs to do that. Things get a little more complicated as really these memory modules tend to only be qualified for certain speeds on certain motherboards and processors and XMP knows nothing about that - head to the RAM’s Qualified Vendor List (QVL) for that.

While all of this used to be really only of interest to people wishing to “overclock” their systems the latest Ryzen Zen 2 CPUs like the 3900X really benefit from increasing the memory speeds as the “Infinity Fabric” interconnect between the chiplets and the outside world runs at the speed of the memory controller - at least initially - and so it’s quite important to any Zen 2 owner wanting to take full advantage of the CPU they have purchased.

Thankfully memory modules for some time have a little bit of persistent memory called Serial Presence Detect (SPD) on them which is accessed over the “I2C/SMBus” device bus in your machine so your BIOS or other tools can find out exactly what the manufacturer has rated them for as well as who manufactured the actual memory chips themselves, when, where and using what kind of die/process.

Reading the Serial Presence Detect

So given all that what do you do to take a look at this info?

Well, on Windows you can just fire up a tool like Thaiphoon Burner, click EEPROM in the menu and then Read SPD on SMBus… which gives you something like this:

MEMORY MODULE
Manufacturer
Crucial Technology
Series
Ballistix Sport LT Red
Part Number
BLS16G4D240FSE.16FBD
Serial Number
3982077Bh
JEDEC DIMM Label
16GB 2Rx8 PC4-2400R-UB0-11
Architecture
DDR4 SDRAM UDIMM
Speed Grade
DDR4-2400R
Capacity
16 GB (16 components)
Organization
2048M x64 (2 ranks)
Register Model
N/A
Manufacturing Date
Undefined
Manufacturing Location
Boise, USA (SIG)
Revision / Raw Card
0000h / B0 (8 layers)
DRAM COMPONENTS
Manufacturer
Micron Technology
Part Number
D9TBH (MT40A1G8WE-083E:B)
Package
Standard Monolithic 78-ball FBGA
Die Density / Count
8 Gb B-die (Z01A / 20 nm) / 1 die
Composition
1024Mb x8 (64Mb x8 x 16 banks)
Clock Frequency
1200 MHz (0.833 ns)
Minimum Timing Delays
16-16-16-39-55
Read Latencies Supported
20T, 18T, 16T, 15T, 14T, 13T, 12T...
Supply Voltage
1.20 V
XMP Certified
1200 MHz / 16-16-16-39-55 / 1.20 V
XMP Extreme
Not programmed
SPD Revision
1.1 / September 2015
XMP Revision
2.0 / December 2013

You can then feed these values into something like DRAM Calculator for Ryzen to figure out good fast timings to enter into your BIOS.

What about Linux?

If you Google around you’ll find guides that tell you to run various modprobe, i2cdetect commands and probably decode-dimms however…

DDR4 use different type of SPD memory chips. Linux support is new and incomplete.

Previously these chips were a type known as eeprom - electronically-erasable programmable read-only-memory (the previous tech was just known as eprom as you needed an ultraviolet box to wipe them through a little window on the top).

DDR4 however has switched from the very dated eeprom technology to the incredibly popular flash memory.

So if you actually run those commands you’ll likely find decode-dimms doesn’t tell you much at all. In fact there are two possible scenarios:

  1. It tells you no eeprom found. This means the existing eeprom driver can’t find your SPD and you’re most likely going to need to obtain and load an alternative i2c driver.
  2. It shows you some generic RAM information but says SPD is invalid. This means your i2c is communicating fine with the SPD but Linux doesn’t understand the DDR4 flash eeprom. You just need to setup the ee1004 driver.

Identifying the right I2C bus

Either way you’re going to need to get the i2c device number your SPD is connected to by way of i2cdetect so run:

i2cdetect -l

Which will give you some output like this:

i2c-3   i2c         i915 gmbus dpd                          I2C adapter
i2c-1   i2c         i915 gmbus dpc                          I2C adapter
i2c-4   i2c         DPDDC-E                                 I2C adapter
i2c-2   i2c         i915 gmbus dpb                          I2C adapter
i2c-0   smbus       SMBus I801 adapter at f040              SMBus adapter

The results are system dependent but you need to deduce which one your SPD is on. The Intel Z370 chipset my Linux box runs uses the SMBus I801 interface so I need to make a note of i2c-0.

If you’re not sure then it may be a process of elimination based on the names. i915 is the Intel on-board CPU graphic support so it wouldn’t be that and a quick Google also associated the DPDDC-E with that too. I’d probably also give some preference to SMBus adapter ones rather than I2C but I couldn’t find any confirmation that this is always the case.

Now, if you’re not sure which one you can try dumping information from each to see if you can find the right-looking data using this command but changing 0 to the likely candidates to the i2c device number from the first column in i2cdetect -l.

i2cdetect -y 0

With any luck it should look something like this. If all you see are -- then try another.

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- 14 15 -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 31 -- -- 34 35 -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: 50 51 52 53 -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- 6a -- -- -- -- --
70: -- -- -- -- -- -- -- --

Now there’s a chance - especially if decode-dimms told you no eeprom found that you don’t actually have the i2c driver loaded. The i801 driver is compatible with many Intel chipsets. To load that you can use:

modprobe i2c-i801

If you’re running AMD you might get away with:

modprobe i2c-amd-mp2-pci

if your Linux is really up to date. If not check out the i2c-amd-mp2 repo for manual installation instructions.

You can also try running decode-dimms again and hopefully now you should see some generic memory information.

Introducing ee1004

Now that you have i2c working and identified we can move on to the ee1004 flash driver for DDR4.

If so then just typing:

modprobe ee1004

Should get you up and running and you can skip the next section and go straight to registering the SPD devices.

If you don’t have the kernel module (Ubuntu 18 doesn’t have it) you will see an error like this one:

modprobe: FATAL: Module ee1004 not found in directory /lib/modules/...

and so you’ll need to install it…

Installing latest ee1004 driver

So either you don’t have ee1004 or you need the latest. Lets CURL it down from the maintainers site. I wouldn’t normally recommend building kernel drivers from untrusted sources but…

curl -O http://jdelvare.nerim.net/devel/lm-sensors/drivers/ee1004/Makefile
curl -O http://jdelvare.nerim.net/devel/lm-sensors/drivers/ee1004/ee1004.c
sudo make install

(If make doesn’t work you’re going to need install things like build-essentials etc. via apt or your package manager of choice).

Now that is compiled and copied into /lib/modules/.../kernel/drivers/misc/eeprom/ee1004.ko we need to load it. Now there’s a small chance your system already has the older eeprom driver loaded so we’ll unload that as well so it doesn’t interfere:

modprobe -r eeprom
modprobe ee1004

Registering the SPD devices

Okay, so you now have the ee1004 driver installed, you can see the bus your SPD devices are on there’s just a few more steps to make sure ee1004 knows which SPD devices it’s supposed to be looking at.

Run the following command replacing 0 with the number after i2c- you identified as the bus your SPD devices are located on:

i2cdetect -y 0

Now this will dump out that basic I2C information we saw earlier. On my system it looks like this:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- 14 15 -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 31 -- -- 34 35 -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: 50 51 52 53 -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- 6a -- -- -- -- --
70: -- -- -- -- -- -- -- --

You see those 50 51 52 53 bytes? Those are my four DDR4 DIMM sticks and we need to register those numbers with the ee1004 driver.

Note: I couldn’t find out if it’s always those numbers or always that location. YMMV.

You can start by just registering one to see if it works (replace i2c-0 with your i2c device from the previous section and 0x50 with the bank number of your memory module you figured out in the chart above:

echo ee1004 0x50 > /sys/bus/i2c/devices/i2c-0/new_device

now run decode-dimms and with any luck you will see output like this:

Memory Serial Presence Detect Decoder
By Philip Edelbrock, Christian Zuckschwerdt, Burkart Lingner,
Jean Delvare, Trent Piepho and others


Decoding EEPROM: /sys/bus/i2c/drivers/ee1004/0-0050
Guessing DIMM is in                              bank 1

---=== SPD EEPROM Information ===---
EEPROM CRC of bytes 0-125                        OK (0x05EA)
# of bytes written to SDRAM EEPROM               384
Total number of bytes in EEPROM                  512
Fundamental Memory type                          DDR4 SDRAM
SPD Revision                                     1.1
Module Type                                      UDIMM
EEPROM CRC of bytes 128-253                      OK (0x27DE)

If it did not register correctly you should de-register the bank with the command:

echo 0x51 /sys/bus/i2c/devices/i2c-0/delete_device

If it worked however go ahead and register the rest of your modules depending on what banks you have memory sticks in. For example on my system the complete registration is:

echo ee1004 0x50 > /sys/bus/i2c/devices/i2c-0/new_device
echo ee1004 0x51 > /sys/bus/i2c/devices/i2c-0/new_device
echo ee1004 0x52 > /sys/bus/i2c/devices/i2c-0/new_device
echo ee1004 0x53 > /sys/bus/i2c/devices/i2c-0/new_device

Now you can finally run

decode-dimms --side-by-side

And get to see the useful timings, manufacturer etc.

Decoding EEPROM                                  0-0050                0-0051                0-0052                0-0053
Guessing DIMM is in                              bank 1                bank 2                bank 3                bank 4

---=== SPD EEPROM Information ===---
EEPROM CRC of bytes 0-125                        OK (0x05EA)           OK (0x699E)           OK (0x05EA)           OK (0x699E)
# of bytes written to SDRAM EEPROM               384
Total number of bytes in EEPROM                  512
Fundamental Memory type                          DDR4 SDRAM
SPD Revision                                     1.1                   1.0                   1.1                   1.0
Module Type                                      UDIMM
EEPROM CRC of bytes 128-253                      OK (0x27DE)

---=== Memory Characteristics ===---
Maximum module speed                             2400 MHz (PC4-19200)
Size                                             16384 MB
Banks x Rows x Columns x Bits                    16 x 16 x 10 x 64
SDRAM Device Width                               8 bits
Ranks                                            2
Rank Mix                                         Symmetrical
AA-RCD-RP-RAS (cycles)                           16-16-16-39
Supported CAS Latencies                          20T, 18T, 16T, 15T, 14T, 13T, 12T, 11T, 10T, 9T

---=== Timings at Standard Speeds ===---
AA-RCD-RP-RAS (cycles) as DDR4-2400              16-16-16-39
AA-RCD-RP-RAS (cycles) as DDR4-2133              15-15-15-35
AA-RCD-RP-RAS (cycles) as DDR4-1866              13-13-13-30
AA-RCD-RP-RAS (cycles) as DDR4-1600              11-11-11-26

---=== Timing Parameters ===---
Minimum Cycle Time (tCKmin)                      0.833 ns
Maximum Cycle Time (tCKmax)                      1.600 ns              1.500 ns              1.600 ns              1.500 ns
Minimum CAS Latency Time (tAA)                   13.320 ns
Minimum RAS to CAS Delay (tRCD)                  13.320 ns
Minimum Row Precharge Delay (tRP)                13.320 ns
Minimum Active to Precharge Delay (tRAS)         32.000 ns
Minimum Active to Auto-Refresh Delay (tRC)       45.320 ns
Minimum Recovery Delay (tRFC1)                   350.000 ns
Minimum Recovery Delay (tRFC2)                   260.000 ns
Minimum Recovery Delay (tRFC4)                   160.000 ns
Minimum Four Activate Window Delay (tFAW)        21.000 ns
Minimum Row Active to Row Active Delay (tRRD_S)  3.299 ns
Minimum Row Active to Row Active Delay (tRRD_L)  4.900 ns
Minimum CAS to CAS Delay (tCCD_L)                5.000 ns
Minimum Write Recovery Time (tWR)                15.000 ns             N/A                   15.000 ns             N/A
Minimum Write to Read Time (tWTR_S)              2.500 ns              N/A                   2.500 ns              N/A
Minimum Write to Read Time (tWTR_L)              7.500 ns              N/A                   7.500 ns              N/A

---=== Other Information ===---
Package Type                                     Monolithic
Maximum Activate Count                           Unlimited
Post Package Repair                              Not supported
Module Nominal Voltage                           1.2 V
Thermal Sensor                                   No

---=== Physical Characteristics ===---
Module Height                                    32 mm
Module Thickness                                 2 mm front, 2 mm back
Module Reference Card                            B revision 0

---=== Manufacturer Data ===---
Module Manufacturer                              Crucial Technology
DRAM Manufacturer                                Micron Technology
Assembly Serial Number                           0x3982077B            0xA02071FC            0x3982077D            0xA02071AC
Part Number                                      BLS16G4D240FSE.16FBD  BLS16G4D240FSE.16FAD  BLS16G4D240FSE.16FBD  BLS16G4D240FSE.16FAD

How to see die version?

One of the things you don’t see here is the die version. If you’re overclocking that is very important to figure out what timings and tweaks you can get at (right now Micron E-die is the newest hotness after Samsung B-die had a good run).

Die is not part of the specification and it seems from playing with Thaiphoon it’s a bunch of matching bytes. I’m looking into putting together a small Linux script/binary that will identify them but in the mean-time you have two options if you need this extra info:

Either way you’ll need to make a Thaiphoon-compatible hex dump (replacing 0-0050 with the i2c bus number followed by a dash then the memory bank number)

od -Ax -t x1 -v /sys/bus/i2c/drivers/ee1004/0-0050/eeprom

You’ll see some output like this which you should copy to the clipboard:

000000 23 11 0c 02 85 21 00 08 00 00 00 03 09 03 00 00
000010 00 00 07 0d fc 2b 00 00 6b 6b 6b 11 00 6b f0 0a
000020 20 08 00 05 00 a8 1b 28 28 00 78 00 14 3c 00 00
000030 00 00 00 00 00 00 00 00 00 00 00 00 16 36 16 36
000040 16 36 16 36 00 20 2b 0c 2b 0c 2b 0c 2b 0c 00 00
000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000070 00 00 00 00 00 00 9c b4 c9 c9 c9 c9 e7 d6 ea 05
000080 11 11 01 01 00 00 00 00 00 00 00 00 00 00 00 00
000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 de 27
000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000140 85 9b 00 00 00 39 82 07 7b 42 4c 53 31 36 47 34
000150 44 32 34 30 46 53 45 2e 31 36 46 42 44 00 80 2c
000160 00 44 50 41 47 48 34 47 30 30 31 ff 00 00 00 00
000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000180 0c 4a 05 20 00 00 00 00 00 94 00 00 07 ff 3f 00
000190 00 6a 6a 6a 11 00 6b f0 0a 20 08 00 05 00 a8 1b
0001a0 28 00 00 00 00 00 00 00 00 cf b5 ca ca ca ca d6
0001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000200

Now either:

  1. Send it to a Windows-using friend who can run Thaiphoon
  2. Run Thaiphoon via WINE (which should now work as it won’t need low-level hardware access to the SMBus)

Inside Thaiphoon in the File menu you’ll see Import from Clipboard which should reveal a lot of information including the DRAM Die Revision / Process Node you’re after.

If for some reason you want to read somebody else’s hex dumps on your Linux box decode-dimms can do that too:

decode-dimms -x myspd1.hex

Enjoy!

[)amien