HP16C LFSR Plus

Download as pdf or txt
Download as pdf or txt
You are on page 1of 70

LINEAR FEEDBACK SHIFT REGISTERS ON THE HP-16C

───────────────────────────────────────────────
╔════╗
║ or ║
╚════╝

IF YOU WISH TO WRITE A PROGRAM FOR A POCKET CALCULATOR FROM SCRATCH,


──────────────────────────────────────────────────────────────────────
YOU MUST FIRST INVENT, AND THEN EXPLAIN, THE ENTIRE UNIVERSE. APPARENTLY.
───────────────────────────────────────────────────────────────────────────
-CARL SAGAN, FOR REAL.
────────────────────────
╔════╗
║ or ║
╚════╝

LOCAL IDIOT WRITES FIRST PROGRAM, THINKS THAT'S ENOUGH TO START TEACHING
──────────────────────────────────────────────────────────────────────────
COMPUTER SCIENCE. NEWS AT 11.
───────────────────────────────
╔════╗
║ or ║
╚════╝

LITERALLY EVERYTHING FOR DUMMIES


──────────────────────────────────

(by some yokel named badacktor)


Copyright © badacktor, 2021

Permission is hereby granted, free of charge, to any person obtaining a


copy of this... whatever the heck it is and associated documentation or
ramblings (the "Software" if you can say it with a straight face), to deal
in the subject material (henceforth referred to as "computer science"
though that likely lays a heavier burden upon the logical and semantic
resources of the english language than they may be reasonably expected to
bear) without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the completely incomprehensible mess, and to permit persons to whom the
whole pile of words is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of this thing.

THE PROGRAM LISTINGS AND INFORMATION HEREIN ARE PROVIDED "AS IS", WITHOUT
WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT, OR JUST THE OVERALL VERACTIY OF THE FACTS AS PRESENTED. IN
NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN TAKING ANYTHING THAT FOLLOWS EVEN REMOTELY
SERIOUSLY OR RELYING UPON IT AS A SOURCE OF WELL-RESEARCHED AND FACTUAL
INFORMATION REGARDING COMPUTER SCIENCE IN GENERAL. YOUR'RE ON YOUR OWN,
BUCKEROO. GOOD LUCK.

Furthermore, the author (badacktor, remember?) is not affiliated with The


Hewlett-Packard Company or any subsidiaries, and the views and information
herein do not reflect pretty much anything from them. Not that he doesn't
wanna be, y'know. hmu hp. How cool would a 16C anniversary edition be?


A note to the poor sap whose mission it is to read any of the following:

This write-up got dummy long. I swear I spent ages editing and cutting,
but it's still really long. It's just, I wrote with a specific goal in mind
- ANYONE can read it and understand it. At least most of it.

Which means I'm gonna explain things that you might not need explained.

To make things easier to navigate, here's a list of starting points,


given your own background and interest in reading a hot mash of mostly
letters and spaces, some symbols, and even a few numbers:

• If you haven't got any real background aside from reading comprehension,
just try to power through the whole thing. I believe in you.

• If you already know about HP calculators that use RPN, you can skip
aside #1.

• If you know about boolean algebra, skip aside #2.

• If you know about shift registers, skip aside #3.

• If you know about the HP-16C and keystroke programming, you can pretty
much skip parts 1 and 2.

• If you know about linear feedback shift registers, well bully for you.
Skip part 3.

• If you've got a better idea for extracting bits than I do, read part 4
anyway. And maybe find a way to communicate that information to me?

• If you're not looking for program listings that are burdened with twenty
tons of notes and explanations, skip part 5 and jump straight to part 6.
And if you already know what you're looking at when you see a typical
keystroke program listing, skip aside #whatever on your way.

• If you don't need to see a run-through of each program doing the math,
skip part 7 and just, you're done. You're done reading. Go home.

Finally, we're gonna talk in terms of binary, decimal, and hexadecimal


when necessary and/or convenient. I did explain these things in agonizing
detail in a previous draft. It was awful and I hated it. You're gonna have
to bring that one to the table yourself. Look, maybe check out simple
english wikipedia's article on hex if it's got you stumped. We're not
judging here. This is a safe space. Learn at your own pace. But I just
couldn't get that part to be a pleasant experience for anyone. The jokes
didn't save it.
(psssst, skip to the very end to see the cut draft of this awful mess)
https://simple.wikipedia.org/wiki/Hexadecimal

Anyway, good luck. I'll see you on the other side.


╔═══════════╗
║ Prologue: ║
╚═══════════╝
INTRODUCTIONS, OR, HOW TO SPEND A LOT OF MONEY ON A VERY OLD CALCULATOR
───────────────────────────────────────────────────────────────────────────
(the author's empty list of credentials)

Hi, I'm badacktor but you can call me ack. This is a write-up I'm
spending way too much time writing up all about some programs I wrote for a
very old calculator that I've been learning to use. Let's make one thing
perfectly clear: I literally have zero credentials that make me suited or
qualified to write anything you're about to attempt to read. I have never
programmed before this moment in my entire life, and my grasp on computer
sciences could best be described as tentatively experiential.

But, c'mon.

I know programming by reputation. It's a hopelessly complicated way of


getting computational machines to do things that take, ideally, more effort
for us to do any more than once than it takes for us to persuade the
computational machine to do as often as we like. Or until the machine gets
too old and tired and our perception of the speed at which we require it to
function outpaces the actual speed of the machine itself. Or until it comes
in a new and exciting color! Then the machine either goes to a dump, a
proper electronic waste recycling service, or is transferred into the
possession of an owner whose perception of speed or functionality matches
the machine's capabilities and they refuse to just let it die. For better
or worse. And on that note...

I recently decided to purchase an old HP-16C calculator for a sum of


money I shall not disclose. Used of course, they were produced between
1983-1988 and it's now 2021. I'd heard about them when I was about 16 and
it took me 16 years to get one. Which is all very cute and means I need to
finish this before my 33rd birthday, which is less than a month away, for
the bit to keep being very cute. Sweet mercy, just type less and type
faster you fool.
╔═════════╗
║ Part I: ║
╚═════════╝
INTRODUCTIONS, BUT THIS TIME THE CALCULATOR, NOT THE APE BASHING ON IT
───────────────────────────────────────────────────────────────────────────
(starting to understand what the HP-16C can do)

HP's 10C or Voyager series of programmable pocket calculators debuted


in 1981 with the 11C (a very standard and good scientific) and the 12C (an
arguably legendary financial calculator that has been in continuous
production since its 1981 introduction which is wild.) It also included the
10C (a very basic and stripped-down scientific) and the 15C (the advanced
and intimidatingly powerful super scientific calculator, you don't even
know.)

But! It also included the precious HP-16C. And our story begins here.

The 16C is a special calculator, one of HP's many programmable pocket


calculators, but unique in being their only "computer scientist" model. One
of the few programmer's calculators made by any company, and the only one
(that I'm aware of) that is programmable itself. Buried in its keyboard and
firmware is a host of features and functions somewhat uncommon in the world
of pocket calculators, and...

You know what, I'll let HP speak for themselves. From the 1983 Hewlett-
Packard Measurement / Computation Electronic Instruments And Systems
catalog, page 582:

HP-16C Programmable Calculator for Computer Science

The HP-16C is a programmable calculator specifically


designed for computer science and digital electronic
applications. With the HP-16C's number base modes, users can
easily convert between binary, octal, decimal and hexadecimal
bases. The advanced programmability of the HP-16C enables the
user to call and edit programs easily. The HP-16C has extensive
bit manipulation capability: shift, rotate, set, test, checksum
and mask. Select word size, 1's and 2's complements and unsigned
mode. Through a program, the user can emulate instructions of
most available processors. The calculator has four logical
Boolean operators: AND, OR, XOR, NOT.

The HP-16C comes complete with Owner's Handbook, long-life


disposable batteries, and a soft, carrying case.

Cool!
So, the suite of features is complete enough that, I guess
theoretically, you can use it to emulate the machine code level operations
of any computer that's running up to 64 bits. Theoretically??? Memory is a
little limited; 203 program steps if no storage registers are used. You've
got 16 program labels (0-9, A-F) and 32 directly addressable storage
registers (0-9, A-F as well, but also .0-.9, .A-.F which is the same as 0-
9, A-F just with a decimal point before the number or letter do you get it
its pretty cool righ-.) And no, we're not getting into indirect addressing
here. As memory goes on old calculators like this, it's not all that bad,
but if you start getting ambitious, you'll easily run up against the
calculator's limitations. Maybe stop aiming so high?

For a really charming and wonderful deep dive, see calculator culture's
video covering the HP-16C in detail here:

https://youtu.be/bRvGoRbR_bA

And obviously just poking at the manual a li'l bit would be useful. HP
calculator manuals are notoriously good and just fun to read on their own,
as well as extremely helpful for learning to use the calculator in
question. You can find the 16C's manual here:

https://literature.hpcalc.org/community/hp16c-oh-en.pdf
***** < f-shifted function (yellow/gold)
┌─────┐
│*****│ < main function
╞═════╡
│*****│ < g-shifted funtion (blue)
└─────┘

SL SR RL RR RLn RRn MASKL MASKR RMD XOR


┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ A │ │ B │ │ C │ │ D │ │ E │ │ F │ │ 7 │ │ 8 │ │ 9 │ │ ÷ │
╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡
│ LJ │ │ ASR │ │ RLC │ │ RRC │ │ RLCn│ │ RRCn│ │ #B │ │ ABS │ │ DBLR│ │ DBL÷│
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘

x⇌(i) x⇌I ┌──────────────SHOW──────────────┐ SB CB B? AND


┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ GSB │ │ GTO │ │ HEX │ │ DEC │ │ OCT │ │ BIN │ │ 4 │ │ 5 │ │ 6 │ │ × │
╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡
│ RTN │ │ LBL │ │ DSZ │ │ ISZ │ │ √x │ │ 1/x │ │ SF │ │ CF │ │ F? │ │ DBL×│
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
┌─────────CLEAR─────────┐ ┌───────SET COMPL───────┐
(i) I │PRGM REG PREFIX│ WINDOW │ 1'S 2'S UNSGN│ NOT
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ R/S │ │ SST │ │ R↓ │ │ x⇌y │ │ BSP │ │ │ │ 1 │ │ 2 │ │ 3 │ │ − │
╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ │ E │ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡
│ P/R │ │ BST │ │ R↑ │ │ PSE │ │ CLx │ │ N │ │ x≤y │ │ x<0 │ │ x>y │ │ x>0 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ T │ └─────┘ └─────┘ └─────┘ └─────┘
│ E │
WSIZE FLOAT │ R │ MEM STATUS EEX OR
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ ON │ │ f │ │ g │ │ STO │ │ RCL │ │ │ │ 0 │ │ . │ │ CHS │ │ + │
└─────┘ └─────┘ └─────┘ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡ ╞═════╡
│ < │ │ > │ │LST x│ │ x≠y │ │ x≠0 │ │ x=y │ │ x=0 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘

FIG. 1: A ROADMAP OF THE HP-16C's KEYBOARD


╔══════════════╗
║ Aside No. 1: ║
╚══════════════╝
WH-... RPN, WHAT???
───────────────────────────────────────────────────────────────────────────
(Reverse Polish Notation: A Guide For The Completely Disinterested)

As a "quick" aside, HP's old calculators used an entry method called


RPN. There's lots of information out there about what it is and how it
works but here's a quick'n'dirty crash course if you don't yet know.

The calculator has a stack, a series of registers (four in this case)


that constitute its working space. A stack means the data stored in these
registers is last in, first out. Like a stack of physical objects. Place
something on top of something else, and the only way to get the thing
beneath is to first remove the thing on top of it. Simple. If you're not
sure what a register is, check out aside #3.

X is whatever is shown on the screen. Y is right behind it, then Z and


then T. We're not gonna worry about Z and T right now. If you type
something into the calculator and it shows up on screen, it's now in the X
register. (If it doesn't show up on screen, the calculator may not be
powered on. Or it might be broken. That's not RPN's fault.) Press enter and
it's copied to Y. Start entering something else, and it overwrites X, and
whatever you bumped to Y is still left in Y. Then you can press a function
key to apply a function to the last two registers in the stack, X and Y.
The stack drops (let's, uh, just say X and Y are cleared for now) and the
result is then put into the X register.

So if you wanted to add the numbers 1 and 2, you'd type:

1 // X: 1, Y: 0

[ENTER] // X: 1, Y: 1

2 // X: 2, Y: 1

[+] // X: 3, Y: 0

If you perform a function that only requires a single value, it will


perform that function on X, and place the result in X, leaving the rest of
the stack alone. So if you wanted to turn that 3 into a -3, you would hit
[CHS] (change sign), the 3 in the X register would technically drop out of
the stack, then the stack would be lifted with a -3, leaving it in X.

Easy.
There's more to it than that, like why the fourth register is labeled
T, how to resolve complicated operations without using parenthesis, what
the heck "Last X" is and how it even works, or why I said the stack "drops"
then said "cleared" and then "drop" and then "lifted", and then sighed
heavily, removed my glasses and rubbed the bridge of my nose, and decided I
just can't do this right here and right now. Go look for better tutorials
from people with sturdier brains. This is really all you need to know for
what we're doing here.

Though a helpful little detail is the [x⇌y] key. It just swaps whatever
is in X with whatever is in Y. It's good for double checking that Y
actually contains what you think it contains. And of course I'd be
chastised into an early grave by very concerned and I'm sure perfectly
lovely enthusiasts if I did not mention that, yes, this is why nearly all
old HP calculators don't have [=] keys. Just the iconic [ENTER]. Anyway...
╔══════════╗
║ Part II: ║
╚══════════╝
SMASH CUT BACK TO THE APE BASHING ON THE CALCULATOR
───────────────────────────────────────────────────────────────────────────
(how programming works on a fully merged keystroke programmable calculator)

The programming is keystroke, so it just repeats a series of keystrokes


entered while in "program" mode. It can execute them in "run" mode. Fairly
simple, but it's also "fully merged" which means some complex keystrokes
may only take one line. If I want to define a routine label as "A" it would
be 3 keystrokes. The [g] shifting / alternate function key, [LBL] (the blue
shifted function of the [GTO] key), and A. But all three keystrokes take up
one line in this case. Just [g][LBL] A. There's also conditional branching.
But on keystroke programmables, conditional branching isn't... great.

Conditional branching just means "if/then/else". So "if" some condition


is true, "then" do one thing. If not ("else" or maybe "otherwise") do a
different thing. Let's check an example from a routine we'll look at closer
later. And for clarity let's say we've entered "1011" and "0010" into the
calculator in that order, so X: 0010 and Y: 1011.

[f][B?] //a bit test!

When the test is executed, it checks the bit position value (in the
stack's X register) of the number in the Y register. If the bit is set (if
the test returns a true condition) it executes the line following the test.
If it is false, it skips the line following the test. In this case, the
result is true (the bit is set) so it executes the first line following the
test by pressing a 1 and then it presses a 0 too. That leaves a "0010" in
the X register.

If X had "0011" before the test had executed, the result would have
been false, the "1" line would be skipped and we'd be left with a "0000" in
the X register.

Let's make that conditional branching model really clear.

If: the condition is true.


Then: execute the following line as normal and continue.
Else: skip the following line, execute the line after, and continue.

I've definitely found fitting logic into this model to be tricky. But
I'm very new to this. Again, this is my first experience programming
literally anything whatsoever, so please be nice to me I'm scared. I'm
certain my stumbling around blindly in the dark must look adorable at best
and infuriating at worst to veteran programmable calculator people. I'm
just trying to live my best life out here.
Additionally, I'm gonna talk about the program pointer real quick.
Imagine, if you will, you're reading a LOT of text. Maybe a bunch of words
written by some yokel who is trying to explain stuff they're not qualified
to explain. Just imagine. While you're reading, your eyes trace across each
letter and word and sentence and paragraph in the order they appear. In
english, it goes left to right first, then top to bottom as lines break. As
you read, you divine some meaning from the words (god at least I hope so)
and your brain synthesizes that meaning into thought and memory and
whatever the heck else brains do I have no idea I barely understand these
computational machines.

You can think of wherever your eyes are looking as the program pointer.
It's where the computational machine is looking right now. In the previous
bit test example, it started on the line with [f][B?] and either moved to 1
then 0, or skipped 1, then moved to 0. Each time the computational machine
single steps, it moves from one line to the next and executes whatever is
on that next line.

Incidentally, if it halts execution for any reason, it just keeps


looking at the last line it was on. Staring at it. Like a dang gargoyle.
Then when execution resumes, it resumes where it left off unless you tell
it to resume from somewhere else.
╔══════════════╗
║ Aside No. 2: ║
╚══════════════╝
YOU'RE GONNA START SAYING "XOR" A LOT LIKE I KNOW WHAT IT IS
───────────────────────────────────────────────────────────────────────────
(but maybe you don't know what it is)

XOR is short for "Exclusive Or." It's a boolean function. Honestly,


there's a good chance that if you're reading this, you already know what
that all means, but just in case. It is a function whose binary logical
truth table looks like this: (X and Y are inputs/operands, X' is the
output/result)

┏━━━━━━┳━━━┓
┃ X Y ┃ X'┃
┡━━━━━━╇━━━┩
│ 0 0 │ 0 │
│ 0 1 │ 1 │
│ 1 0 │ 1 │
│ 1 1 │ 0 │
└──────┴───┘

We're not getting into boolean algebra here. And if you're the sort
that might read a write-up on implementations of linear feedback shift
registers on an HP-16C programmable calculator but also has not yet learned
about boolean algebra (really, you sound fun and exciting and I like you)
I'd encourage you to go check any wikipedia articles or just do some light
web searching (you might even find more extremely well formatted tables
chilling near the bottom of this text wall, maybe, maybe not, I don't know)

But all we need to understand are two things:

First, it's a function with the given inputs X and Y (specifically the
X and Y registers of the stack) that will result in the given output X'
(the result left in X register after the function...functions, whatever.)

Second, X' is 0 when both inputs are the same value (either both 0s or
both 1s) and 1 when both inputs are different values (either "0, 1" or "1,
0".) You could even consider it a "difference comparison." Ohhhh?

More on that later.


╔══════════════╗
║ Aside No. 3: ║
╚══════════════╝
STOP. I DON'T EVEN KNOW WHAT A REGULAR SHIFT REGISTER IS.
───────────────────────────────────────────────────────────────────────────
(that's fair and it'll be obvious I'm not sure I do either)

A shift register is a register that shifts.

...okay, that's not helpful.

A register is a spot in a computational machine's memory that can hold


data. It can hold a certain number of bits (0s or 1s.) (I'm not explaining
bits or getting any more granular than this. I'm sorry. My hands hurt from
all this typing.) If it is a 16 bit register, it can hold 16 bits. Easy. We
call that number of bits the "word size." Because numbers are words for
computers or something. Usually those bits would be written to a register
sort of all at once. If you want the bits in the register to look like:

"1010110010101011" (an arbitrary selection of 0s and 1s)

then all 16 bits would just be written to the register in that order
without making a big deal about it.

A shift register makes a big deal about it.

Kinda like a child who hates peas but we've got peas for dinner and now
you have to feed each pea to them one by one.

For this example, the register will shift right. And it will begin
empty, which just means it's full of 0s. To fill it with our completely
arbitrary conga line of peas, I mean, of 0s and 1s shown above, we'd feed
it a 1 first. This gets put in the leftmost position (which we can call the
least significant bit or LSB, because we're just rude like that.) That 1
came from the desired conga line's rightmost position (which we can call
the most significant bit or MSB, because now we're just bullying the least
significant bit.)

So now the register has "0000000000000001" in it. Which is about 50%


correct compared to the conga line we wanted, so let's keep pushing bits
in. We push in a 0 next:

"0000000000000010"

But hold up.

Because even if it's not obvious, it's the second time this has
happened. When we push a bit in from the left, a bit gets dropped off the
right, and everything in between moves over one bit. We haven't cared
though because all the register had in it up until now was a bunch of 0s,
which are boring, and even now we're just losing 0s that don't affect much
about our data. You can call those 0s "leading 0s" if you want, because
funny joke haha, etc.
Okay, but, where the heck were we?

"0000000000000000"
"0000000000000001"

Right, then we plopped in a 0:

"0000000000000010"

Still not the full conga line we want, so we'll keep pushing bits in
until the job is done.

"0000000000000101"
"0000000000001010"
"0000000000010101"
"0000000000101011"

You can see how they're partying along as we push bits in. So much fun!
Keep pushing more in. The job's not yet done.

"0000000001010110"
"0000000010101100"
"0000000101011001"
"0000001010110010"
"0000010101100101"
"0000101011001010"
"0001010110010101"
"0010101100101010"
"0101011001010101"
"1010110010101011"

And now the gang's all here! The job is done. It took 16 pushes to do
it, but we put the whole 16 bit conga line in the register. Now what?

Push another bit in.

"0101100101010110"

Well, we pushed a 0 in and a 1 dropped off the right side. Now either
we don't care about the bits dropping off the right or we've got a system
for catching them, but the register doesn't care. Push a bit in, everything
shifts over to make room, and the bit on the opposite end drops off. That's
it. That's all it does. There is, however, one more trick it can do.

It's easy to think of a shift register as having one input and one
output. Like a straw you use to transfer bits in a nice orderly conga line
from the milkshake to your face and the metaphors have completely detached
from our logical plane of existence, oops.
But, you can read any or all bits in the shift register at any time. In
the example above, we could see the full conga line of "1010110010101011"
without having to wait for each bit to pop out the other side. Some shift
registers even let you write to any bit at any given time, with the
understanding that the bit you wrote is gonna start shifting pretty soon.
But if that's cool with you, it's cool with me.

So that's a poor explanation of shift registers. Radical. Of course,


I'd suggest you just hit up wikipedia. I tried to, but it said something
about "cascading flip flops" and I couldn't stop giggling so I dunno.
╔═══════════╗
║ Part III: ║
╚═══════════╝
OKAY, DUDE, WHAT ARE WE EVEN DOING HERE, WHAT IS THE POINT TO ANY OF THIS?
───────────────────────────────────────────────────────────────────────────
(a disservicing description of linear feedback shift registers)

Pretty sure disservicing isn't word, ya dingus...

And hey, sorry, we just hit two "asides" back to back and I feel like
we're losing focus. But those topics are important to keep in mind while we
mash a bunch of buttons on an old calculator, I guess.

With all of that out of the way, let's get into why we're here. When I
got my new old toy, I wanted to give myself a challenge to program my way
out of. The challenge was a random number generator. So I decided to write
a linear feedback shift register function for the HP-16C. Actually, I ended
up writing two, just for the sheer thrill of feeling alive. Let's do
another real quick 'n real dirty, this time about linear feedback shift
registers.

Yet again, wikipedia of course has a very good article you should read:

https://en.wikipedia.org/wiki/Linear-feedback_shift_register

And the topic was introduced to me through two videos from Julian Ilett:

https://youtu.be/juoM7VhXQDM and https://youtu.be/cpVA6BP8ajM

So I'd highly recommend hitting up those.

The fundamental idea is that it is a shift register whose input is


determined by a function (or set of functions) performed on its own
contents.

The function is linear.


The result of the function feeds back into the input.
The input of the shift register.

Linear. Feedback. Shift Register.

Look, it does what it says on the tin!

But why? I mean, sometimes stuff like this is just math for the heck of
it, which I totally dig, but this time there is a common use for these
things. Pseudo-random number generators. See, the resulting string of bits
that can be read from the register after even a single cycle might bear
little resemblance to the initial seed, depending on the implementation,
and the string of bits it generates before repeating a concrete pattern can
be sufficiently long and complex enough that it can easily be perceived and
utilized as random by humans, with our square little brains.
Computational machines are good at discrete and definite calculations,
but not so good at generating random garbage, (despite the countless
anecdotal examples you're currently and furiously hollering at your screen
right now, but I cannot hear you!) So if we need something it's doing to
look random or behave randomly, these can help.

(An aside so quick I'm not parting it out: I'm gonna say "seed" a lot
and I don't know if that's the right term. I just mean a conga line of 1s
and 0s that we're starting out with just to give the LFSR something meaty
to chew on. If you start with all 0s, they usually just keep spitting out
0s. Too boring. Furthermore, the resulting seed of a single cycle becomes
the initial seed of the next because that's how a cyclic process is gonna
work. I guess in the context of this write-up, "seed" and "conga line" are
probably interchangeable which is a bananas thing I just typed what.)

The two I implemented are the two that wikipedia examined in detail. A
Fibonacci LFSR, and a Galois LFSR, named after the mathematicians Leonardo
Bonacci and Evariste Galois respectively. I chose these two because they're
the two wikipedia examined in detail. And there were diagrams. I am but a
small child lost in the woods. And you can be too, so let's start stumbling
around in the dark forest of logical circuits together!
╔═════╗
║ uh. ║
╚═════╝
WHAT?
───────────────────────────────────────────────────────────────────────────
(well...)

Okay, real, REAL quick. we need to address the turing elephant in the
room. The calculator has six system flags that can be set. Two of them
(flags 4 and 5) are for the carry bit and overflow. Those are weird. Maybe
we'll hit 'em up style next time. Three other flags (flags 0, 1 and 2) are
user flags. You can incorporate setting and checking them into your
programs for doing things I guess? I haven't gotten that deep yet. But the
last flag, flag 3, is relevant to us right now. Setting it means leading 0s
are displayed. Clearing it means they're not displayed.

To set it, we just key in:

[g][SF] 3 // SF for Set Flag

And to clear it we key:

[g][CF] 3 // CF for Clear Flag

Let's look at a number in binary with the flag set and cleared; the
same number each time.

flag 3 set: 0000000010101010


flag 3 clear: 10101010

I'm really hoping it's obvious what's going on here.

Additionally, our word size is set to 16 bits, which we can set by


keying 16 (dec, 10 hex, or 10000 bin) into the X register, then [f][WSIZE].
And we really should be in unsigned mode. We're EXTREMELY not talking about
signed 1s and 2 complement modes today. But to set unsigned mode, hit [f]
[UNSGN]. with all of that done, if you've:

(1) Set flag 3 [g][SF] 3


(2) Set the word size to 16 [HEX], 10, [f][WSIZE]
(3) Set unsigned mode [f][UNSGN]

Then you can check your handiwork by keying in [f][STATUS] and you
should see something like:

0-16-1000

(complement/unsigned modes) - (word size) - (flags 3, 2, 1, 0)


(set is 1, clear is 0)

Cool, cool, I think that's everything? God I hope so.


╔══════════╗
║ Part IV: ║
╚══════════╝
JUST DO THE DANG THING ALREADY
───────────────────────────────────────────────────────────────────────────
(nope hold on)

First we'll declare one subroutine. It's also worth noting now that all
bit position values are counted from 00 in the least significant bit (the
rightmost bit) to 15 in the most significant bit (the leftmost bit),
because computers obviously. So in the case of "10": 0 is bit 00, and 1 is
bit 01. This is already getting dumb, I know.

And whoa, when I say one subroutine, what do I really mean? A


subroutine is defined by how you treat it. If you jump to a label with a
[GSB] (go to subroutine) and the lines after that label end with a [RTN]
(return) then that's a subroutine. See, because we jumped to it with a
[GSB], it left a little bookmark wherever it was when it jumped. The next
[RTN] we find now will jump us back to that bookmark, one line after the
[GSB] we jumped from. That way if there's something we're doing a lot, like
extracting bits, we don't have to write those keystrokes out every single
time. Just jump around for a hot minute until everything gets sorted out.
It's a smaller program to make your bigger program's life easier. Which is
nice.

Oh, if the keystrokes I'm using seem like gibberish, I heard you can
find poorly written descriptions of all the keycodes used in these programs
as an appendix (A) at the end. Wait dangit, I keep spoiling surprises...

Plus one final, kinda critical, note.

To begin entering these programs, you just press:

[g][P/R]

And then you start typing all of the following keystrokes. Take your
time, check you work, things gotta be in the same order they're written
here. If the textual assault of notes and comments occurring all over them
are making them hard to parse, skip to part 6 for just the bare program
listings. When you're done, press [g][P/R] again to exit program mode.
Okay, ONE MORE “final note”:

Both programs end with a looping conditional. To use it, you need to
enter a value into the index register. This should be the number of times
you want the program to loop. Yeah, I know but if I don't explain it,
you'll ask. So let's say we want 16 loops. Here's a quick guide on the key
sequence you need to push while in run mode, not program mode!

[HEX]

[STO][I]

Yep. that's it. If you don't put anything in before you start, It'll
loop infinitely until the battery dies, or you intervene by pressing [R/S]
or even just [ON]. It doesn't have to be a big deal.

To run them, in this case, you'll press [GSB] E for the Fibonacci LFSR,
and [GSB] F for the Galois LFSR. But we gotta write 'em before we can run
'em so let's get into it.
………………………………………………………………………………………………………………………………………………………………………………………………………
B - "Get Bit" subroutine. (B for bit!)
………………………………………………………………………………………………………………………………………………………………………………………………………

/*
* This one is real important. It's used to extract single bits from
* the conga line. It assumes the seed is in the stack's Y register and the
* bit position value is in the stack's X register, extracts the bit (0 or
* 1) and places it in the X register's least significant digit. This
* stupid thing is how I figured out that keystroke programming conditional
* branching can be kinda janky. This eight line subroutine took me longer
* to write (by endless trial and error) than the rest of everything else
* we'll cover took me to finish. Like I said, first program, please be
* kind, I'm a soft baby with a mushy brain. I'm willing to bet there's a
* better way of doing this. And if you know what it is, please find a way
* of communicating that information. I'd even take a note taped to a small
* rock thrown at my head.
*/

[g][LBL] B

[BIN] // Set integer mode to binary. leaving it in hex results in


// "...10000" when there is a bit set. Very annoying!

[f][B?]

1 // Now if there's a bit set, the result is "...10" and


// just "...00" if there isn't. Less annoying!

[f][SR] // [SR] is just a single "shift right" command. For this,


// "...10" becomes "...01", and "...00" doesn't change.

[HEX] // Put the calculator back in hex. Just in case.

[g][RTN] // The bit, either a 0 or a 1, is now in the X register's


// least significant bit. A good place for it. [RTN] just
// sends us back to wherever we were before we started.

Now let's make some linear feedback shift registers!


╔═════════╗
║ Part V: ║
╚═════════╝
NO FOR REAL, JUST DO THE DANG THING ALREADY
───────────────────────────────────────────────────────────────────────────
(okay fine)

I'm going to "attempt" to draw some diagrams here. They're gonna be at


least a little iffy-lookin', but it can't be helped. They're just thicc.
Look, UTF-8's special box drawing characters are really fun, if a little
tedious, to play with. I honestly suggest you also try it sometime.

………………………………………………………………………………………………………………………………………………………………………………………………………
E - Linear Feedback Shift Register #1
(Fibonacci, 16 bit with 4 tap points)
………………………………………………………………………………………………………………………………………………………………………………………………………

<── shifting direction is right, from LSB to MSB <──

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ 00 │<─┐
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ │
│ │ │ │ ┌───┐final result bit │
│ │ │ │ ┌──>│ ⊜ │──> ──────────────────────────────────────┘
│ │ │ │ │ └───┘
│ │ │ └─>┌───┐
│ │ │ 3rd XOR│░⊕░│
│ │ │ ┌──────>└───┘
│ │ │ │
│ │ └─>┌───┐
│ │2nd XOR│▒⊕▒│
│ │ ┌─>└───┘
│ │ │
│ └─>┌───┐
│ 1st XOR│▓⊕▓│
└───────────>└───┘

/*
* Assumes the seed is in the stack's X register.
*
* This LFSR XORs bits 15 and 13, then XORs that result with bit 12,
* then XORs that result with bit with bit 10. The final result is then fed
* into the register from the right, shifting the contents left.
*
* Let's try it!
*/
[g][LBL] E

[HEX]

[STO] 4 // Save the seed to register 4.

F // The seed moves to the stack's Y register and the first


// bit position value (15) is in the stack's X register.

[GSB] B // Execute the "Get Bit" routine.

[STO] 5 // We've got bit 15. Let's save it for later.

[RCL] 4 // Recall the seed to the X register.

D // Enter the bit position value for bit 13 to the X


// register, bumping the seed to the Y register.

[GSB] B // Get the next bit! Bit 13.

[RCL] 5 // It is now "later." Bit 13 is in Y, and bit 15 is in X.

[f][XOR] // It's an LFSR! you gotta XOR bits! Let's do that now.

[STO] 5 // Save the result for later again.

[RCL] 4

[GSB] B // "Get bit" 12 now.

[RCL] 5 // It is now "later again." Bit 12 is in y, and the


// resulting bit from XORing bits 15 and 13 is in X.

[f][XOR] // XOR those two bits together. Madness.

[STO] 5 // And save the result for later again.


[RCL] 4

[GSB] B // blah blah blah bit 10.

[RCL] 5 // It is now "later again" again. Y is bit 10. X is the


// result of XORing bit 12 with the resulting bit of XORing
// bits 13 and 15. The snake eyes its tail.
[f][XOR] // One more XOR then we can dip.

[RCL] 4 // Bring the seed back.

[f][SL] // Now let's put the "shift" in "shift register." We're


// shifting left because I'm the one driving this car.
// (The wikipedia diagram is the one driving this car.)
[+] // Append the result of all that XORing, which is in Y, to
// the shifted seed, which is in X.

[f]SHOW[BIN] // And we're done. The result of the whole operation is in


// X. This is a victory lap. Showing the result in binary
// was useful for debugging but it's also fun to watch the
[g][PSE] // bits traveling along. At least from the first window.
// [PSE] is a pause command. For us, it just shows the
// result in hex too.

[g][DSZ] // Now it's just the loop stuff.

[GTO] E // If the index register doesn't equal zero, jump back to


// the start of E.

[R/S] // If the index register does equal zero, you can stop now.

/*
* That wasn't so bad. Running the routine with 16 in the index
* register completely shifts the initial seed out of the register, and
* replaces it with a new set of bits that should appear more than
* sufficiently random. More than sufficient to whom, you may ask?
*
* ...
*
* Anyway, let's do another.
*/
………………………………………………………………………………………………………………………………………………………………………………………………………
F - Linear Feedback Shift Register #2
(Galois, 16 bit with three tap points and three "reinsertion" points.)
………………………………………………………………………………………………………………………………………………………………………………………………………
──> shifting direction is left, from MSB to LSB ──>

┌────┬────┐┄┄┄┄┄┄┌────┐┄┄┄┄┄┄┌────┬────┐┄┄┄┄┄┄┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐┄┌────┐
┌─>│ 15 │ 14 │ ┌─>│ 13 │ ┌─>│ 12 │ 11 │ ┌─>│ 10 │ 09 │ 08 │ 07 │ 06 │ 05 │ 04 │ 03 │ 02 │ 01 │ │ 00 │
│ └────┴────┘┄┄┄│┄┄└────┘┄┄┄│┄┄└────┴────┘┄┄┄│┄┄└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘┄└────┘
│ └───>┌───┐ └───>┌───┐ └───>┌───┐ │
│ 3rd XOR│░⊕░│2nd XOR│▒⊕▒│ 1st XOR│▓⊕▓│ │
│ ┌───>└───┘ ┌───>└───┘ ┌───>└───┘ │
└────── <─┴──────── <─┴───────────── <─┴──────────────────────────────────────────────────────────── <─┘

/*
* This one also assumes the seed is in X. Things get a bit more
* complicated because the logical circuit performs the XORing between
* bits, placing multiple results inside of the shifted (well, rotated)
* seed, not just one pushed into one end or the other. The order of
* operations also matters. Bits need to be extracted before they move, or
* at least I think so? I'll be honest, the wikipedia article didn't seem
* super clear, and this was the best I could figure when I worked it out
* on paper and did some dry runs of the math by hand. And I refuse to go
* back and check now. It's too late.
*
* Let's break down the steps we'll be taking in the order we'll be
* taking them: (and again, we're working with a 16 bit word, the least
* significant bit being bit 00 and the most significant bit being bit 15)
*
* 1. Save the initial seed in a storage register.
*
* 2. Rotate the seed right once. (Wanna know more about rotating? I heard
* from a friend of a friend you can go look for appendix c I think,
* but I don't even know that means)
*
* 3. Save the rotated seed in a different storage register. This becomes
* a workspace for the rest of the operations. The initial seed is now
* just used as a reference for the XORing.
*
* 4. Extract the least significant bit from the initial seed (bit 00) and
* save it in yet another separate storage register.
*
* 5. Extract bit 11 from the initial seed.
*
* 6. XOR bits 11 and 00.
*
* 7. Write the result to bit 10 of the rotated seed, overwriting whatever
* is there.
*
* 8. Extract bit 13 from the initial seed.
*
* 9. XOR bits 13 and 00.
*
* 10. Write the result to bit 12 of the rotated seed.
*
* 11. Extract bit 14 from the initial seed.
*
* 12. XOR bits 14 and 00.
*
* 13. Write the result to bit 13 of the rotated seed.
*
* 14. The rotated seed is now fully edited and can be considered the
* "new" initial seed. We did it! I think?
*
* ...yikes.
*
* Let's just see how that all plays out on a calculator released in 1983.
*/

[g][LBL] F

[HEX]

[STO] 4 // As before, store the seed in register 4.


// Don't wanna lose it.

[f][RR] // Rotate the seed right once. This pops the least
// significant bit off, shifts everything right once, and
// drops the bit into the most significant bit. That's
[STO] 6 // numberwa-, a bit rotation! Store the result separately.

[RCL] 4

0 // The seed is in Y and bit 0's positional value is in X.

[GSB] B // "Get bit" 0. This bit is gonna get used a lot.

[STO] 7 // Save it for several laters.

[RCL] 4

[GSB] B // "Get bit" 11

[RCL] 7 // Recall bit 00. X: bit 00 and Y: bit 11.


/*
* ...!
*
* ╔══════════╗
* ║ Hold Up! ║
* ╚══════════╝
* AN INTERRUPTION APPEARS
* ────────────────────────────────────────────────────────────────────────
* (steady yourself)
*
* This is where I felt a little proud.
*
* Remember earlier I mentioned XORing is really just a difference
* comparison? If the input bits are the same, the output is 0. If they're
* different, the output is 1. So why bother XORing and then doing a
* conditional branch on the output if I can do the same by just testing if
* the bits are equal or not? I've got these lovely x=y and x≠y conditional
* tests just sitting here. And now I'm really amused that my XORing LFSR
* doesn't actually use the calculator's native XOR function.
*
* But keep an eye on what I do with the branching. Rather than the
* jank we saw in the "get bit" subroutine, this time I was smart! If X
* equals Y, we jump to label 0. If not, we jump to label 1. And both
* branch-routines end with a jump to label 2 which just continues the main
* routine. I no longer have to fit any logic into that clunky "skip a line
* if false" branching that was giving me trouble earlier in the "get bit"
* subroutine.
*
* And then another revelation! In reading the 16C's manual, I saw
* something on page 81 (of the May 1983, Rev. B printing) that hit me like
* an asteroid to the Yucatan Peninsula. To paraphrase: labels are searched
* from the current program pointer downward. It is possible ("though not
* advisable") to use a label multiple times.
*
* Now I'm sure local and global labels and variables and junk are not
* even remotely exciting to most people, programmers or not, but once
* again:
*
* a) This is my first time programming anything and I'd just worked it out
* for myself!
*
* b) This is a pocket calculator from the early 80s! Whaaat???
*
* I was so excited. The local labeling means we're only using three
* labels for each of the three instances of this test, rather than nine
* labels. Again the 16C only has 16 directly addressable labels (stop
* turning to page 68 in the 16C's manual, I'm warning you) and I was
* pushing into thirteen, which didn't feel cozy. Instead, for everything
* we're doing here today, we're only using eight labels and four
* registers.
*
* Nice!
*
* SO! IF TRUE: We execute the following line and jump to label 0,
* which performs a 'clear bit' (overwriting whatever was in the bit
* position in question with a 0), then jump to label 2. IF FALSE: We skip
* the following line then execute the subsequent line that jumps to label
* 1, which performs a 'set bit' (overwriting whatever was in the bit
* position in question with a 1), then jump to label 2 as well. Label 2
* just acts a local "continue the main routine." Are these just normal
* programming things, even for calculators like these? Probably. Did I
* figure it out on my own and am now so embarrassingly proud that I just
* gushed about it for 493 words in some extremely disruptive program
* comments? Definitely.
*
* Alright, let's get back into it and see everything in action.
* Reminder, X holds bit 00 and Y holds bit 11.
*/

[g][x=y] // Test whether the two bits are the same or not.

[GTO] 0 // If so, jump to the next instance of label 0.

[GTO] 1 // If not, jump to the next instance of label 1.

[g][LBL] 0 // Label 0!

[RCL] 6 // Recall the rotated seed.

A // Bit position value for bit 10.

[f][CB] // Regardless of what's there, clear the bit (set it to 0.)

[GTO] 2 // Jump to the next instance of label 2, the local


// continue.

[g][LBL] 1 // Label 1!

[RCL] 6 // Recall the blah blah blah...

A // Yadda yadda yadda...

[f][SB] // Regardless of etc...


// ...this time, set the bit (set it to 1.)
[GTO] 2 // Jump to the hamana hamana hamana label 2 continue stuff.

[g][LBL] 2 // Label 2!

[STO] 6 // Save our work, whatever it was.

[RCL] 4 // And now we continue with the main routine. Bring the
// initial seed back.

[GSB] B // "Get bit" 13.

[RCL] 7 // Recall bit 00.

[g][x=y] // Let's do all that fun stuff again for bit 12 this time.

[GTO] 0

[GTO] 1

[g][LBL] 0

[RCL] 6

[f][CB]

[GTO] 2 // See, when I stop writing so much, now it feels weird.

[g][LBL] 1 // But this is all the same as before.

[RCL] 6 // Just with bits 13 and 12.

C
[f][SB]

[GTO] 2

[g][LBL] 2

[STO] 6 // Remember: save your work often, friends.

[RCL] 4

[GSB] B // "Get bit" 14.

[RCL] 7

[g][x=y] // One mo' 'gain!

[GTO] 0

[GTO] 1

[g][LBL] 0

[RCL] 6

[f][CB]

[GTO] 2

[g][LBL] 1

[RCL] 6
D

[f][SB] // Honestly, that's it. The X register already holds the


// new seed now. The rest of this is just housekeeping.

[GTO] 2

[g][LBL] 2

[g][PSE] // Give us a look at that new hot mash of numbers and/or


// letters in hex. Fabulous!

[g][DSZ]

[GTO] F // Looping stuff.

[R/S] // We're finally done.


╔═════════════════════╗
║ Aside No. Whatever: ║
╚═════════════════════╝
MORE GOBBLEDYGOOK?
───────────────────────────────────────────────────────────────────────────
(yeah a li'l more gobbledygook)

The "bare" program listings are gonna look weird, so let's de-weird
them beforehand. There's a convention we'll follow with every line being
split into two halves. The first half is what you should see on the display
when you press the right keys. The second half is the right keys to press.
It looks something like this: 001 - 43, 22, b.

Except what the heck is that display even telling us?

Alright well, the first three digit number is the line number. That's
easy. Next, it's helpful to understand how the calculator views its own
keys. Contrary to typical convention in computers and stuff, the calculator
counts rows 1-4, and columns 1-9, 0. Now, ideally it would go 0-3, and 0-9.
I'm sure there are very good reasons that the designers chose this
convention. Perhaps starting the bytes stored in program memory with 0s was
a problem. It's also worth remembering this general architecture was used
in all models in the Voyager/10C series, probably other series as well, but
I don't know those as well yet. However this series includes the 12C. The
financial/business model. Maybe the designers didn't want to confuse that
crowd? I don't know. It's a small but genuine irritation for me. Whatever,
I'll get over it.

Regardless, let's look at line 001 again:

001 - 43, 22, b

Okay, line 001, we got that. "43" means row 4 (the bottom row), column
3. Which is out blue [g] shifting key. Cool. Next we've got 22. Same thing,
row 2, column 2. But bearing in mind we've pressed [g], this means we're
using [LBL], not [GTO]. Cool cool. Finally b. 16 keys aren't referenced by
the row/column convention, but by their label. It's just the 0-9 and A-F
keys. Which might poke a hole in my starting a byte with a 0 theory but
maybe not? I dunno. This applies to their shifted functions as well. [f]
[SL] displays as "42 A". Sometimes there are commas or dots separating the
numbers, sometimes not. It's not all that important. Honestly, it's all
fairly easy to parse once you get used to it.

With all of that, we can translate "001 - 43, 22, b" into:

line 001
row 4/column 3 "[g]"
row 2/column 2 "[LBL]"
b "B"

or [g][LBL] B.
But why do we care about this symbolic pimiento loaf? Writing the
listings out like this can help out while you're wandering the wastelands
of a long program listing trying to replicate them on your own calculator.
It gives you a line by line check that everything is still kosher. Just
check your line number and the keystroke/keycode display to make sure it
all still matches up.

You can use [SST] to move forward a single line in program mode, [BST]
to move back a single line, and [BSP] to delete a line. That's single step,
back step, and backspace. And if you're on, say, line 025 and you start
entering keystrokes, it will insert a new line as 026 and push all
following lines up one. Very intuitive.

And be ready to make mistakes. Just in copying out this program listing
myself, I've caught several mistakes both in my calculator and in proof-
reading the listing afterward. Debugging and tracking down errors might
take a fat minute. It'll be okay. Once things are done and working right,
you're left with a very cool feeling of accomplishment. And some functional
linear feedback shift registers.

Good luck!
╔══════════╗
║ Part VI: ║
╚══════════╝
JUST THE "BARE" PROGRAM LISTINGS, THEN. NO MORE TALKING.
───────────────────────────────────────────────────────────────────────────
(...)

Bit Extraction Subroutine


──────────────────┬──────────────────

001 - 43, 22, b │ [g][LBL] B

002 - 26 │ [BIN]

003 - 42 6 │ [f][B?]

004 - 1 │ 1

005 - 0 │ 0

006 - 42 b │ [f][SR]

007 - 23 │ [HEX]

008 - 43 21 │ [g][RTN]

Linear Feedback Shift Register #1 (Fibonacci)


──────────────────┬────────────────────────────

009 - 43, 22, E │ [g][LBL] E

010 - 23 │ [HEX]

011 - 44 4 │ [STO] 4

012 - F │ F

013 - 21 b │ [GSB] B

014 - 44 5 │ [STO] 5

015 - 45 4 │ [RCL] 4

016 - d │ D

017 - 21 b │ [GSB] B

018 - 45 5 │ [RCL] 5

019 - 42 10 │ [f][XOR]

020 - 44 5 │ [STO] 5

021 - 45 4 │ [RCL] 4

022 - C │ C

023 - 21 b │ [GSB] B

024 - 45 5 │ [RCL] 5

025 - 42 10 │ [f][XOR]

026 - 44 5 │ [STO] 5

027 - 45 4 │ [RCL] 4

028 - A │ A

029 - 21 b │ [GSB] B

030 - 45 5 │ [RCL] 5

031 - 42 10 │ [f][XOR]

032 - 45 4 │ [RCL] 4

033 - 42 A │ [f][SL]

034 - 40 │ [+]

035 - 42 26 │ [f] SHOW [BIN]

036 - 43 34 │ [g][PSE]

037 - 43 23 │ [g][DSZ]

038 - 22 E │ [GTO] E

039 - 31 │ [R/S]

Linear Feedback Shift Register #2 (Galois)


──────────────────┬─────────────────────────

040 - 43, 22, F │ [g][LBL] F

041 - 23 │ [HEX]

042 - 44 4 │ [STO] 4

043 - 42 d │ [f][RR]

044 - 44 6 │ [STO] 6

045 - 45 4 │ [RCL] 4

046 - 0 │ 0

047 - 21 b │ [GSB] B

048 - 44 7 │ [STO] 7

049 - 45 4 │ [RCL] 4

050 - b │ B

051 - 21 b │ [GSB] B

052 - 45 7 │ [RCL] 7

053 - 43 49 │ [g][x=y]

054 - 22 0 │ [GTO] 0

055 - 21 1 │ [GTO] 1

056 - 43, 22, 0 │ [g][LBL] 0

057 - 45 6 │ [RCL] 6

058 - A │ A

059 - 42 5 │ [f][CB]

060 - 22 2 │ [GTO] 2

061 - 43, 22, 1 │ [g][LBL] 1

062 - 45 6 │ [RCL] 6

063 - A │ A

064 - 42 4 │ [f][SB]

065 - 22 2 │ [GTO] 2

066 - 43, 22, 2 │ [g][LBL] 2

067 - 44 6 │ [STO] 6

068 - 45 4 │ [RCL] 4

069 - d │ D

070 - 21 b │ [GSB] B

071 - 45 7 │ [RCL] 7

072 - 43 49 │ [g][x=y]

073 - 22 0 │ [GTO] 0

074 - 22 1 │ [GTO] 1

075 - 43, 22, 0 │ [g][LBL] 0

076 - 45 6 │ [RCL] 6

077 - C │ C

078 - 42 5 │ [f][CB]

079 - 22 2 │ [GTO] 2

080 - 43, 22, 1 │ [g][LBL] 1

081 - 45 6 │ [RCL] 6

082 - C │ C

083 - 42 4 │ [f][SB]

084 - 22 2 │ [GTO] 2

085 - 43, 22, 2 │ [g][LBL] 2

086 - 44 6 │ [STO] 6

087 - 45 4 │ [RCL] 4

088 - E │ E

089 - 21 b │ [GSB] B

090 - 45 7 │ [RCL] 7

091 - 43 49 │ [g][x=y]

092 - 22 0 │ [GTO] 0

093 - 22 1 │ [GTO] 1

094 - 43, 22, 0 │ [g][LBL] 0

095 - 45 6 │ [RCL] 6

096 - d │ D

097 - 42 5 │ [f][CB]

098 - 22 2 │ [GTO] 2

099 - 43, 22, 1 │ [g][LBL] 1

100 - 45 6 │ [RCL] 6

101 - d │ D

102 - 42 4 │ [f][SB]

103 - 22 2 │ [GTO] 2

104 - 43, 22, 2 │ [g][LBL] 2

105 - 43 34 │ [g][PSE]

106 - 43 23 │ [g][DSZ]

107 - 22 F │ [GTO] F

108 - 31 │ [R/S]

╔═══════════╗
║ Part VII: ║
╚═══════════╝
THE MATH OF EACH IMPLEMENTATION WORKED OUT FOR A SINGLE CYCLE
───────────────────────────────────────────────────────────────────────────
(and you thought this couldn't get any longer or more boring)

Let's start with the Fibonacci LFSR. "⊕" will represent Exclusive Or,
and we'll use the completely arbitrary seed "1010110010101011" as our
jumping-off point.

1010110010101011 // initial seed


┆ ┆┆ ┆
1⊕1┆_┆__________ // XOR bits 15 and 13, result is 0
┆ ┆ ┆
_0⊕0_┆__________ // XOR previous result with bit 12, result is 0
┆ ┆
__0-⊕1__________ // XOR previous result with bit 10

____1___________ // final result is 1

0101100101010110 // shift initial seed left once

______________+1 // append final result to shifted seed

0101100101010111 // resulting seed

Not bad. When I was testing to make sure the program was working
correctly, I ended up working the math out on paper 32 times just to be
reeaally sure it was doing what I thought it should be doing. I encourage
you to do the same. It's a fine distraction from the chaos of merely
existing in this world right now.
Anyway, Galois next. Same seed.

1010110010101011 // initial seed

____1⊕1_________ // XOR bits 11 and 00



_____0__________ // bit 10 result is 0

__1⊕1___________ // XOR bits 13 and 00



___0____________ // bit 12 result is 0

_0⊕1____________ // XOR bits 14 and 00



__1_____________ // bit 13 result is 1

0101100101010111 // rotate initial seed


┆┆ ┆
__10_0__________ // overwrite bits of rotated seed with result bits
┆┆ ┆
0110100101010111 // resulting seed

See? It's simple. If you're a lunatic like me. Which you'd almost have
to be to have stuck with me this long.
╔═══════════╗
║ Epilogue: ║
╚═══════════╝
YO, STOP. WHY?

WHY DID YOU JUST DO ALL THIS?

THE PROGRAMS. THE WRITE-UP. ALL OF IT.

DON'T YOU WORK A FULL TIME JOB?


───────────────────────────────────────────────────────────────────────────
("why?" is weird question.)

Hey! Welcome to the other side! I'll try to explain/defend myself, now.
As best I can.

There are a few answers here, but let's get the simple one out of the
way first. I really enjoyed this. It was really fun. Learning how to use my
new li'l toy by giving myself a challenge like this was a really solid way
of accomplishing the goal. And I've had a blast taking notes, documenting
the process, and writing this whole thing you're reading now. If this
hadn't been fun, I wouldn't have done it. Which might've saved us both a
lot of time, but it's too late for that now.

But there are two other, very related reasons.

Because when I got my 16C, the first thing I naturally did was look up
example programs other people had written to try out on it. To get my feet
wet in its cool waters. And you know what I found? Not much. rskey dot org
(a wonderful site you need to just spend an afternoon or twenty browsing)
wrote a very nice gamma function, including subroutines for an exponential
function and a natural logarithm. It's a lot.

Check it out here: http://rskey.org/hp16c

But the point of the 16C is that it can emulate the machine code level
of computers. This feels like missing the forest for the hundreds of
joggers and bikers. And yeah, their dedication to their physical welfare is
honestly impressive, but I'm just trying to unwind with fresh air and birds
out here and now I feel like we're missing the point with this metaphor as
well. Might be a simile. Whatever. It's just, if you want a gamma function,
hundreds of other calculators can either be used to generate one more
efficiently, or already have one built in natively.

The only other programs I found were for converting integers to and
from floating point (again, feels like missing the point) and a package of
routines for manipulating IPv4 addresses and network masks and man I dunno
that was getting weird? Wasn't missing the point, at least. But that was
all I could find?! In my head, I wanted to add to a seemingly nearly empty
software library for a reputedly legendary calculator.
And on that note, the second thought in my head was that maybe I can
generate some buzz??? Get some more people interested and involved??? Maybe
I'm being a li'l selfish, but heck, I did just spend a stupid amount of
money on a completely obsolete calculator. This might be me very publicly
justifying the purchase. Maybe this can start a little community around
this computational machine in particular or something.

I mean, there's already a community of HP calculator enthusiasts, but


it's a very old community, tracing its roots back to the 70s. It's gone
through zines and newsletters, to usenet groups, to forums, to whatever the
heck kids are doing these days, I think tik tok? And old communities can be
a little weird, hard to slip yourself into. Might just be me, I dunno.
Besides, this one tends to focus on the HP-41C, which is a very nice
computational machine, and I'm trying to dig into it as well, but still.
Not a lot of attention for my 16C.

(Minor Update: Okay I don't know why, but I completely forgot to search
the museum of hp calculators' "general software library" section of their
current forums for "16C" and there's more stuff, but again I still feel
like most of the results are still missing the "emulating machine code"
point? Or they're kinda plain and boring? Now I feel like I'm just being a
really obnoxious pedant??? Yikes. Anyway, go check out the Museum of HP
Calculators. It's a really cool place.)

So, let this be this initial conga line of a new community of


enthusiasts focusing specifically on the 16C's abilities and strengths in
emulating achine code. A young, hip crowd of idiot babies like me, trying
to make this weird computational machine run a small portion of the code
from doom or something. And I invite you to join in, dear reader.

BUT. How can you if you don't have an HP-16C for yourself?

I mean, let's imagine you're not inclined to spend a bunch of money on


a calculator from an auction site that may or may not keep working the day
after it gets delivered because, let's face it, they're anywhere from 32 to
38 years old. We're asking a lot for them to keep working. This is a
justifiable use of our imagination.

(Note: I keep saying they were released in 1983, even though a lot of
sources online say 1982. But I found no documentation from HP to suggest
that. First mentions in the HP Journal and HP catalogs are both in 1983.
Similarly, the last catalog advertising it is from 1988. So whatever. I'm
gonna make this weird for the heck of it.)

Anyway, you've got options. There are emulators, including a REALLY


nice and free browser emulator from STENDEC Info that you can find here:

https://stendec.io/ctb/hp16c.html
They also have iOS and Android versions, but those cost money. If
costing money isn't a problem, and you want a physical calculator as well,
the company SwissMicros has carved out a market for themselves making
clones of them good ol' HP calculators, including the 16C. Not affiliated,
not a paid endorsement, I don't have the 16C clone myself, so I can't
attest to the quality personally, but it seems legit. Look it up and decide
for yourself.

And with that, I thank you; dear, kind, admirable, noble reader. Thank
you for taking this journey with me. Thank you for your attention, time,
and patience. And if you start writing programs for this little champ of a
computational machine, then thank you for that too.

Be well. Drink water. Eat vegetables. I'll catch y'all later.

-badacktor, May 2021


╔═══════════╗
║ ╔═══════╗ ║
║ ║ sike. ║ ║
║ ╚═══════╝ ║
╚═══════════╝

HERE ARE SOME APPENDICES, INEXCUSABLE IN THIER VERY PRESENCE


───────────────────────────────────────────────────────────────────────────
(i do what i want, huff my duff)

╔═════════════╗
║ Appendix A: ║
╚═════════════╝
A LIST OF KEYSTROKE CODES USED IN THE PREVIOUS LISTINGS AND THEIR MEANINGS
───────────────────────────────────────────────────────────────────────────
(like I'm just supposed to know what [g][DSZ] means on sight)

[key label] - full name


description

[f] - f / yellow / gold shift


It's just a shift key used to access an alternate function of any key
with yellow / gold text above it.

[g] - g / blue shift


It's the same thing, but a lovely blue, and the blue text is printed on
the lower, chamfered portion of the key. Very fetching.

[GTO] - go to
As a command in a program, it's used to jump to a label, which are
searched for downward from the program pointer's current spot in program
memory. In programming mode, you can type [GTO] . nnn, where nnn is a three
digit number between 000 and 203, letting you quickly jump to that line for
easier editing and stuff. You can't enter [GTO] . nnn as a command, though.
But it's not a deal-breaker.

[GSB] - go to subroutine
Jumps to a routine label, but if you end that routine with a return
[RTN] command, it'll return to wherever you were before the time jump. I
mean subroutine jump. So [GSB] B jumps to label B. Searches downward from
the program pointer's current location, like [GTO]. As a keyboard command
in run mode, it lets you directly execute a specifically labeled routine.

[R/S] - run / stop


If a program is not running, the calculator will start executing lines
from program memory starting with wherever the program pointer was last
pointing. If a program is running, it'll just stop running.
[P/R] - program / run
Switches the calculator's mode between "run" (normal old calculator
stuff mode, and you can also run programs from here) and "program"
(entering and editing lines in program memory, doing the actual
programming.)

[PSE] - pause
It's pretty much a no-op / no-operation command. While the calculator
is running a program, all it displays is some blinking "running" text.
[PSE] just pauses execution for a moment and displays whatever's currently
in X. You can chain multiple pauses together for a longer pause!
So playful!

[RTN] - return
Normally this just stops a program and puts the program pointer back at
line 000. But if it happens after a [GSB] jumps to a label, then the
program pointer jumps back to the line after that [GSB] and continues
execution.

[DSZ] - decrement and skip if zero


This subtracts one from the index register and skips the following line
if the value in that register equals zero after that subtraction. There's a
similar [ISZ] / "increment and skip if zero" option. That one feels less
intuitive to me but that's probably just me.

[B?] - bit set?


A conditional test. The number being tested should be in the stack's Y
register, and the bit position value should be in the stack's X register.
All conditional branching follows a "skip the following line if true"
system of logic.

[x=y] - x equals y?
Another conditional test. Checks if the values in the stack's X and Y
registers are equal. There are also other similar tests, such as [x≠y],
[x=0], [x≠0], [x>0], [x<0], [x>y], and [x≤y].

[HEX] - hexadecimal integer mode


Sets the calculator's integer mode to hexadecimal.
You could've guessed.

[BIN] - binary integer mode


You can guess. There's also [DEC] for decimal and [OCT] for octal.

[STO] - store
Stores whatever is currently in the X register of the stack to a
register label specified after the command. So [STO] A would store whatever
is in X to a register that's now labeled A and that's its home now.
Life-changing stuff.
[RCL] - recall
Takes whatever is in the register with the label specified after the
command and copies it to the X register of the stack. The stack will lift
to accept this new treasure into its life. So [RCL] A lifts the stack and
copies whatever is in the register with label A into the X register of the
stack. If no register has that label, it'll just copy a 0.

[I] / [i] - index register / indirect address


[I] is used to write a value to the index register, and [i] is used to
use the value written to the index register as an indirect address for
writing to other registers or jumping to other labels and stuff. But like,
I'm not gonna explain that any further. It's not...it won't be...we're not
gonna like any of this. Please. C'mon.

...

[SL] / [SR] - shift left / shift right


Shifts all bits in the stack's X register one position either left or
right. Bits that pop off the end are discarded, and 0s feed in from the
opposite side.

[RL] / [RR] - rotate left / rotate right


Shifts all bits in the stack's X register one position either left or
right again, but this time any bits that pop off the end are what gets fed
into the opposite side.
╔═════════════╗
║ Appendix B: ║
╚═════════════╝
A NOTE ABOUT "LIFTING" AND "DROPPING" THE STACK
──────────────────────────────────────────────────────────────────────────
(I kinda glossed over this but it's a key mechanic that isn't obvious so)

Sometimes when you do a thing on this calculator, and other calculators


like it, it'll overwrite the X register with new data. Other times, it
won't. Instead, it'll "lift" the stack. This is easier to explain if I just
show it first. Let's look at an example stack.

T: 0000
Z: 0001
Y: 0010
X: 0011

That's a nice stack. Now let's perform some command or something that
will "lift" the stack and put a "0100" into X. For the sake of example,
lets say I've got that value in a storage register I've labeled A and I've
just keyed in [RCL] A.

T: 0001
Z: 0010
Y: 0011
X: 0100

You can see how each register copied its contents to the one above it,
and T just yeeted its contents into oblivion. (If you're old and reading
this, you can replace "yeeted" with "threw" and it'll mean roughly the same
thing.) Luckily, T just had a 0 so who cares. If it was holding something
important, though, we'd have lost it.

Now let's perform an addition. That will take whatever values are in X
and Y, add them together, and "drop" the stack.

T: 0001
Z: 0001
Y: 0010
X: 0111

Okay, that was weird. the value of X and Y added together was put into
X, that seems right. Z "dropped" to Y, cool. T "dropped" to Z, radical. But
T also retained its previous value. Yeah. It just does that. There are
reasons for that. I mean, really, what are you gonna "drop" into T? A 0?
Garbage data??? Why do that? Just leave T alone! That way RPN users who
are, like, REALLY good at what's called "stack manipulation" can use T as a
spot to drop a constant value that they'll need a bunch of times for a lot
of really complex calculations, and hang onto it without needing to use a
storage register. Real galaxy brain genius types.
In fact, one of my early drafts of the Fibonacci LFSR actually tried to
do something like this. But the number of program lines used, and the
amount of memory those lines took up, was greater than the memory it took
to just use storage registers. Besides, the program listing would've been
disgustingly difficult for anyone but me to parse. And even hard for me to
parse as well, really. It was a mess. I drew a lot of diagrams and tables.
But I'm not a galaxy brain genius. This should be patently obvious.

Finally, HP explains things in terms of functions that leave the stack


"lift-enabled," "lift-disabled," or "stack neutral"; or "terminating digit
entry" or "saves X to Last X" or "do not reset scrolling" or "prefix keys"
and it's all just. A lot. I'm not the manual. Read the manual.
Coincidentally, you can check their Appendix B, and Section 2. Good luck.
╔═════════════╗
║ Appendix C: ║
╚═════════════╝
HEY, YOU ALSO GLOSSED OVER THE DIFFERENCE BETWEEN SHIFTING AND ROTATING
───────────────────────────────────────────────────────────────────────────
(clearly, glossing over small details isn't like me, so here we go!)

I think by now we understand the basic premise of shifting. Here's a


quick example using a totally arbitrary little conga line of bits:

the conga line: 1010110010101011


shifted left: 0101100101010110 <─ 0
shifted right: 0 ─> 0101011001010101

See, if we're just shifting arbitrarily, we're just taking all of the
bits that are set (or all bits containing a 1) and moving them over once.
And any space we've created is filled with 0. Shift once, push one 0 in.
Shift 16 times, push sixteen 0s in. If you perform a [SL] or [SR] on the
16C, that's what you'll get. Then, whatever bit (or bits) pops (or pop) off
the side opposite the 0 (or 0s) we pushed in just gets (or get) discarded.

It's a very cavalier way of manipulating bits. Very wasteful.

But you can also rotate. It's a great example of a word fitting its
meaning really well. Lets try rotating left:

the conga line: 1010110010101011


rotate left: 1 <─ 0101100101010111 <┐
└──> ───────────────> ─┘

Oooooh! The bit that was gonna get discarded from the left side of the
conga line is the bit we actually pushed into the right side! No bits are
wasted, and in these uncertain times, we cannot afford to waste bits. Now
let's try rotating right!

the conga line: 1010110010101011


rotate left: ┌> 1101011001010101 ─> 1
└─ <─────────────── <──┘

So, the same thing, just in the opposite direction? Yep, because words!

Hope that all makes sense! (he said, ostensibly referring to this whole
ongoing text wall nightmare.)
╔═════════════╗
║ Appendix D: ║
╚═════════════╝
...FINE, LET'S TALK ABOUT THE INDEX REGISTER IN DETAIL
───────────────────────────────────────────────────────────────────────────
(don't blame me, you made this weird)

Fine. FINE! Whatever. The index register.

The index register does two things. I used it in my programs as a loop


counter, that was simple enough. For the [DSZ] (decrement and skip if zero)
conditional, it subtracts 1 from the index register then checks if the
value is now zero. If not, it executes the next line, which is probably a
jump to the start of the program, but it doesn't have to be. If the value
in the index register is zero, it skips the following the line (the line
that would've looped) and executes the line after, which is probably a
run / stop, but also doesn't have to be.

However, the index register can also be used to do things indirectly.


Such as indirect storage register addressing. See, when you assign storage
registers on this particular computational machine, you've only got 32
addresses available. (0-9, A-F, .0-.9, .A-.F)

But what if you've already used all of those? Maybe you've got a REALLY
good reason for needing more or different labels for registers. One I'm
struggling to come up with right now for the sake of a good example, but
just pretend it's there and it's REALLY good. What you can do is store the
value of the register's address to the index register, then put whatever
you want stored the stack's X register, and tell the calculator to store X
into a register with the address whose value is stored in the index
register at the moment. Let's try it. We're gonna store the value 3A in
register 5F.

(in hex, because letters and numbers and stuff, whatever)

F // X: 5F

[STO][I] // store 5F in the index register

A // X: 3A, a value I want to save. in a register labeled 5F.

[STO][i]

Stupendous. The value 3A is now stored in a register labeled 5F.


Now let's pretend I did a bunch of stuff. Maybe even stuff that altered
the contents of the index register. So if I want to recall my special 3A
register, I'll need to un-alter it first:

[STO][I]

[RCL][i]

Spectacular. X now contains the value 3A.

There are some caveats. Which is a word that does not look like you
spelled it correctly even when you spelled it correctly. First, this only
opens up addresses between 0-100, because c'mon, stop being greedy.

Also, recall that our direct addressing allowed for 0-9 and A-F which
translates from hex to decimal as 0-15 very nicely, sure. But what about .
0-.9 and .A-.F? There aren't actually any registers labeled that way. Those
labels translate to 16-31 in decimal. So the register directly addressed by
.F and the register indirectly addressed by 31 (decimal) are the very same
register. Worth keeping in mind so you don't go clobbering data
accidentally. You should only clobber data intentionally, with the data's
full and explicit consent.

Now something you might've noticed if you've got the calculator (or
even just a picture of it) in front of you: [I] and [i] are technically
shifted functions. Don't we need to push [f] first? Nope! The non-shifted
functions of those keys are [SST] and [R↓] and since [STO][SST] or [RCL]
[R↓] would be meaningless, the calculator assumes you mean [STO][I] or
[RCL][i]. It's a very smart little friend trying to save you precious
keystrokes. Very thoughtful.

There's also a handy little [x⇌I] command that lets you swap the
contents of the stack's X register with the contents of the index register.
It seems like another one of those things for, like, "power users" and
people who are really good at stack manipulation. I dunno.

And of course, this works with labeling as well. Sort of. See, it
doesn't actually increase the number of possible labels. You still only get
the 16 labels of 0-9 and A-F. This is honestly more really cool power user
stuff, I think. And it should still be just so abundantly be clear that's
not me. Anyway, now we've covered indirect addressing. I hope you're happy.
╔═════════════╗
║ Appendix E: ║
╚═════════════╝
A LIST OF THE TRUTH TABLES OF ALL BOOLEAN FUNCTIONS AVAILABLE ON THE 16C
───────────────────────────────────────────────────────────────────────────
(in case wikipedia burns down I guess)

AND OR XOR NOT


┏━━━━━━┳━━━┓ ┏━━━━━━┳━━━┓ ┏━━━━━━┳━━━┓ ┏━━━┳━━━┓
┃ X Y ┃ X'┃ ┃ X Y ┃ X'┃ ┃ X Y ┃ X'┃ ┃ X ┃ X'┃
┡━━━━━━╇━━━┩ ┡━━━━━━╇━━━┩ ┡━━━━━━╇━━━┩ ┡━━━╇━━━┩
│ 0 0 │ 0 │ │ 0 0 │ 0 │ │ 0 0 │ 0 │ │ 0 │ 1 │
│ 0 1 │ 0 │ │ 0 1 │ 1 │ │ 0 1 │ 1 │ │ 1 │ 0 │
│ 1 0 │ 0 │ │ 1 0 │ 1 │ │ 1 0 │ 1 │ └───┴───┘
│ 1 1 │ 1 │ │ 1 1 │ 1 │ │ 1 1 │ 0 │
└──────┴───┘ └──────┴───┘ └──────┴───┘

╔═════════════╗
║ Appendix F: ║
╚═════════════╝
ONE MORE TABLE FOR THE ROAD: BIN / DEC / HEX
───────────────────────────────────────────────────────────────────────────
(goodnight everybody)

┏━━━━━━━┳━━━━━━━┳━━━━━━━┓
┃ BIN ┃ DEC ┃ HEX ┃
┡━━━━━━━╇━━━━━━━╇━━━━━━━┩
│ 0000 │ 00 │ 0 │
│ 0001 │ 01 │ 1 │
│ 0010 │ 02 │ 2 │
│ 0011 │ 03 │ 3 │
│ 0100 │ 04 │ 4 │
│ 0101 │ 05 │ 5 │
│ 0110 │ 06 │ 6 │
│ 0111 │ 07 │ 7 │
│ 1000 │ 08 │ 8 │
│ 1001 │ 09 │ 9 │
│ 1010 │ 10 │ A │
│ 1011 │ 11 │ B │
│ 1100 │ 12 │ C │
│ 1101 │ 13 │ D │
│ 1110 │ 14 │ E │
│ 1111 │ 15 │ F │
│ 10000 │ 16 │ 10 │
│ ... │ ... │ ... │
└───────┴───────┴───────┘
╔════════════════════════════════════════════════════════════════╗
║ PS - THANK YOU TO THE FOLLOWING PERSONS AND GROUPS OF PERSONS: ║
╚════════════════════════════════════════════════════════════════╝

• Mom + Dad - Yeah, I know. But like, they've both listened to me talk very
excitedly and unintelligibly about my new stupid hobby, about all my
little discoveries and accomplishments, about this very write-up. They've
just been very kind and supportive. Y'all are good li'l beans, thank you.

• authorblues - auth is a gamer, streamer, speedrunner, and an ACTUAL


PROFESSOR OF COMPUTER SCIENCE and his community has been a kind and
loving home especially in the wildfire chaos that's been the last few
years. I'm endlessly grateful for him and for his friends who've become
my friends.

I'd like to shout out some people from his community by name:
arael, cari, casper, lexi, bob, red, aroymart, gren, tina, norestump,
glumbaron, loracia, liz, goost, eon, gt, smartball, and honestly so many,
many more. Y'all have been endlessly kind to me.

God I hope none of them read this. I'm gonna look like a buffoon.

Anyway, go check him out: https://www.twitch.tv/authorblues

• Hewlett-Packard - I mean, feels weird, but also appropriate? Founder of


the feast sort of deal. If I were gonna be more specific, I'd thank their
Corvallis Division, which was the site dedicated to making a buncha
really cool calculators and other things. Sadly, they don't do that
anymore. But HP does still host an archive of the HP Journal online. A
journal written by their own engineers and designers about the products
and stuff they were introducing. It's all really charming and cool and
you can feel the excitement and passion they were putting into it:

https://www.hpl.hp.com/hpjournal/pdfs/IssuePDFs/hpjindex.html

And may I recommend page 36 of the May, 1983 issue:

https://www.hpl.hp.com/hpjournal/pdfs/IssuePDFs/1983-05.pdf

• rskey.org - When I first learned about the 16C, I googled the heck out of
it, desperate for any information I could find. And pictures. And while I
can't remember if I've been looking at R/S Key since day one, I've been
poking at that site for well over a decade now. It's dedicated to any and
all programmable calculators, desktop and pocket alike, with big chunky,
eloquent write-ups on hundreds of excellent computational machines. If
you're anything like me, you could spend years combing through it. And if
you are, and do, I really think we should be friends, just sayin'.

http://rskey.org/CMS/
• Museum of HP Calculators - Look, if you want to learn about the history
of HP Calculators, you're gonna end up here. It's a deep and detailed
examination of nearly every significant model made during what I guess
you could call the "good ol' days" if you wanna be that person.

And of course, why not make a special trip to their page for the 16C:

https://www.hpmuseum.org/hp16.htm

• Julian Ilett - He's a very smart, really sweet, and honestly kind of
adorable youtuber from the united kingdom and, as previously mentioned,
he's the one who introduced the topic of linear feedback shift registers
to me. Go check his videos the heck out:

https://www.youtube.com/c/JulianIlett/about

• archive.org - When you get into a hobby that's got some age to it, you
will inevitably start finding links to websites that you've heard have
some really good info or something. And then you click the link and it's
dead. Then you take that link to the internet archive and it's been
archived. Not always, and not always fully, but they're honestly a
godsend, and I don't know what I'd do without them. Here's my favorite
example:

https://web.archive.org/web/20060324195015/http://gallery.brouhaha.com/roms
ucker-voyager

Yep. Internal shots of the PCBs of the voyager series calculators. Get
hype. And maybe go donate to archive.org okay? it's important.

• wikipedia - Well I mean c'mon. It's wikipedia. Go donate to them too.

• hpcalc.org - There's a lot of great stuff here, but between you and me,
don't go making a big deal about this, they've pretty much got all HP
calculator manuals available in pdf form for free.

https://literature.hpcalc.org/

I popped off when I realized this. Thanks y'all.


• Calculator Culture - Look, I'll be honest. The content is gonna seem dry
to most people out there, the presentation is very understated and
simple, and apparently some people I've shared his videos with said his
voice was too monotone or something? I don't know, his channel is amazing
to me, his collection of calculators is jaw-dropping, and his
understanding and ability to explain is genuinely impressive. Plus I like
his voice and his simple style makes these videos clean and I'd imagine
much easier to produce, which could potentially mean a long life for the
channel.

Just, whatever, just go watch a bunch, say nice things in the comments,
and don't forget to SMASH THAT SUBSCRIBE BUTTON I guess.

https://www.youtube.com/user/akuzi/videos

• STENDEC Info - Like, straight up, y'all made some really solid emulators.
The mobile ones costing money is honestly fair if you know anything about
what it takes to keep apps in those app stores (it takes money, whoops,
there are fees!) and the browser emulators being free just proves y'all
are v. cool and v. chill. ty.

https://stendec.io/ctb/hp16c.html

• SwissMicros - These cats are buck wild. Like, if you want a brand new
calculator that replicates the features of legendary old collector's
calculators, that come with a five year warranty, for less than what
you'd pay for the original version used and no longer serviced... like,
if you want that, you're my kind of chum and feel free to contact me to
say fun and cool things to each other. And thanks swissmicros. I got your
DM41X. It rules. I'm gonna save up for that DM16L, but do you think you
can do what you did with the DM15L's firmware and provide modified
version with increased memory capabilities? That might not actually be
possible, I don't know, really. You're also probably not reading this.

https://www.swissmicros.com/

what were we talking about...?

• the hacker known as "Alex" - wait what's this doing here

Yo, but really, your kind words of encouragement got me to actually take
this project seriously and really do something with it.
Let's see where it goes!

https://mango.pdf.zone/
╔════════════════════════════════════════════════════════════════════════╗
║ PPS - I KNOW THE // INLINE COMMENT AND /* */ PROLOGUE COMMENT ║
║ DELIMITERS WEREN'T NECESSARY BUT THEY MADE ME FEEL COOL AND LEGIT AND ║
║ REMINDED ME OF READING NQC PROGRAMS WRITTEN BY RADICAL PEOPLE FOR THE ║
║ ORIGINAL LEGO MINDSTORMS RCX BACK IN LIKE 2000-2006 WHEN I WAS A ║
║ TEENAGER AND I DON'T KNOW SORRY IF IT WAS ANNOYING I GUESS BYE ║
╚════════════════════════════════════════════════════════════════════════╝
╔═════════════╗
║ ??????????: ║
╚═════════════╝
WHAT'S BINARY AND DECIMAL AND HEXADECIMAL ALL ABOUT?
───────────────────────────────────────────────────────────────────────────
(uuuuuuuuuuuuuuuuuuuuuuuuuuuuugh...)

WARNING: THIS SECTION SUCKS. I CUT IT FROM THE FINAL DRAFT AND A FRIEND
MADE ME INCLUDE IT HERE AS A SECRET AT THE END. I DON'T KNOW WHY. IT SUCKS.
BUT THEY ALSO GAVE ME A HOT PROOF-READING FOR FREE SO FINE.

Let's talk about number bases for a fat second.

When you start looking at it hard, counting is weird. Honestly, when


you start looking at anything too hard, the whole thin fabric of
comprehensible reality starts breaking down in a number of upsetting ways.
Counting is just another example.

See, here's counting:


••
•••
••••
•••••
••••••
•••••••
••••••••
•••••••••
••••••••••
•••••••••••
••••••••••••
•••••••••••••
••••••••••••••
•••••••••••••••
••••••••••••••••

and so on. Just a bunch of stuff, really. More of it each time. But you saw
that and in your head maybe you thought:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

and so on. Actually, if you were cool, you thought:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

and so on. And if you were kind of annoying you thought:


00
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
00
and stop it. Counting like this is counting in base 10 or decimal. It's
called that because there are 10 available digits. You can see them all up
there, but let's rewrite them because I'm not paying for the line count
anyway.
0 =
1 = •
2 = ••
3 = •••
4 = ••••
5 = •••••
6 = ••••••
7 = •••••••
8 = ••••••••
9 = •••••••••

If you add another dot to the conga line of dots we called 9, you'd get:

10 = ••••••••••

(It's all just conga lines, that's the secret to math.)

Now let's assume for a moment that you understand place values and the
differences between 1, 10, 100, and so on. That every time you reach the
highest digit value you for that place, it drops back to 0 and adds one to
the place immediately to its left. But let's now also assume we don't have
10 digits. Maybe we have 2 digits. We have 0 and 1.

Well, then counting looks a little different:

0 =
1 = •
10 = ••
11 = •••
100 = ••••
101 = •••••
110 = ••••••
111 = •••••••
1000 = ••••••••
1001 = •••••••••
1010 = ••••••••••

But it honestly works the same. In this case, our place values don't
jump by powers of 10 but by powers of 2. From right to left, each place
value represents 1, 2, 4, 8, 16, and so on.

10(b10) was represented as 1010(b2) which is just 8(b10) + 2(b10).


7(b10) was 111(b2) which is 4(b10) + 2(b10) + 1(b10). And with that, you've
got the basics of working in binary. Now what about hexadecimal? Well, it
has 16 digits! That's a lot! Let's make a quick table:
┏━━━━━━━┳━━━━━━━┳━━━━━━━┓
┃ BIN ┃ DEC ┃ HEX ┃
┡━━━━━━━╇━━━━━━━╇━━━━━━━┩
│ 0000 │ 00 │ 0 │
│ 0001 │ 01 │ 1 │
│ 0010 │ 02 │ 2 │
│ 0011 │ 03 │ 3 │
│ 0100 │ 04 │ 4 │
│ 0101 │ 05 │ 5 │
│ 0110 │ 06 │ 6 │
│ 0111 │ 07 │ 7 │
│ 1000 │ 08 │ 8 │
│ 1001 │ 09 │ 9 │
│ 1010 │ 10 │ A │
│ 1011 │ 11 │ B │
│ 1100 │ 12 │ C │
│ 1101 │ 13 │ D │
│ 1110 │ 14 │ E │
│ 1111 │ 15 │ F │
│ 10000 │ 16 │ 10 │
│ . │ .. │ ... │
│ and │ so │ on... │
└───────┴───────┴───────┘

Okay, great. And you can see that if we add 1(base anything, really) to
F(b16) we get 10(b16), or 16(b10), or 10000(b2). Numbers are weird.

Anyway, bye.
╔════════════════════╗
║ Update / Addendum: ║
╚════════════════════╝
INCOMING!
───────────────────────────────────────────────────────────────────────────
(someone taped a note to a small rock and threw it at my head)

Courtesy of swissmicros forum user pwarden42, I present, without


comment, alternatives to my "get bit" subroutine:

For the "get bit" routine I would probably use masking


instead of branching, something like:

[g][LBL] B

[RRn]

[f][AND]

[g][RTN]

If you really wanted to use branching, you could use


the carry flag:

[g][LBL] B

[g][CF] 4

[f][B?]

[g][SF] 4

[g][RLC]

[g][RTN]
Just kidding, let's comment the heck outta the first one.

╔════════════════════════════════════╗
║ Alternate "Get Bit" Subroutine #1: ║
╚════════════════════════════════════╝
HEY THIS ONE LOOKS WAY SHORTER THAN YOURS HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA
───────────────────────────────────────────────────────────────────────────
(style. elegance. simplicity.)

First off, thank you pwarden42! I'm particularly fond of this first
alternative, it effortlessly does in three simple steps what my solution
struggled to do in six! That's incredible. You're incredible. This is what
I meant by galaxy brain geniuses. These folks are really good at this.

To start, let's talk about what pwarden42 means by "masking" by


breaking down each step of the routine. And for the sake of examples, we'll
start with our favorite completely arbitrary set of bits "1010110010101011"
and we'll run through bit positions 15 and 14, which should return "set"
and "not set" (or 1 and 0 respectively):

[RRn]

This rotates Y (the seed) right a number of times specified by the


value in X, which in our case is the number of bits specified by the bit
position. For bit 15, this returns "0101100101010111". What this actually
does, though, is rotate the seed until the bit in question is in the least
significant bit position, or position 0.

This is called a 1, or one. it's a fundamental part of mathematics. But


let's view it from the perspective of the calculator. Remember, our word
size is 16 bits and we're displaying leading 0s. So "1" is actually
"0000000000000001".

After these two simple keystrokes, Y contains "0101100101010111" and X


contains "0000000000000001"

[f][AND]

Alright, we kinda covered XOR, but it's time for more boolean algebra.
It's a system that tries to mathematically describe logic. We've got a
handful of logical functions and it uses binary values to do things.
Aside from XOR, the three other fundamental functions are NOT, OR, and AND.

NOT. It's simple. One input, one output. The output is the inverse of
the input. 1 in, 0 out. 0 in, 1 out.

There's also OR. Two inputs, one output. The output is 1 if either
input is, or both inputs are, 1. The output is 0 when both inputs are 0.
We also had XOR, of course. Two inputs, one output. The output is 1 if
both inputs are different, and 0 if they're the same, etc etc etc.

Finally, we've got AND. Again, two inputs, one output. The output is 1
only when both inputs are 1, and 0 when they're the same.

Refer to appendix E for the truth tables of these boolean functions.


(They're called "truth" tables because I didn't make any mistakes when I
wrote them out, I think.) (Okay no, it's actually because the binary values
1 and 0 refer to the concepts of True and False respectively. Whatever,)

So let's take the two values in Y and X, and let's AND them. And how!

0000000000000001
0101100101010111
──────────────────
0000000000000001

See, for every 0 in X, we're gonna get a 0 back in the output. It's
like a mask! The 15 leading bits that we don't care about in the seed are
guaranteed to get thrown out. For the bit in question, well, it's a 1. 1
AND 1 returns 1. The bit is set, so we get a 1 back. Wonderful!

But what if one 1 wasn't 1! let's try this again but with position 14.

[RRn]

Okay, this time we rotate right 14 times. That gives us "1011001010101110".

So Y contains "1011001010101110" and X contains "0000000000000001"

[AND]

Let's AND 'em!

0000000000000001
1011001010101110
──────────────────
0000000000000000

Once again, the leading 0s mask the rest of the bits we don't care
about. But this time 1 AND 0 is 0. The bit isn't set, so we got a 0! This
is so exciting.

And it works identically to my routine but cleaner, better, and faster.


I would HIGHLY encourage you to substitute my subroutine with this one.
It's so neat.
What about that other alternative?

╔════════════════════════════════════╗
║ Alternate "Get Bit" Subroutine #2: ║
╚════════════════════════════════════╝
...HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA wait what's this one doing?
───────────────────────────────────────────────────────────────────────────
(yeah, now you're gonna need to pay attention)

[g][LBL] B

[g][CF] 4

[f][B?]

[g][SF] 4

[g][RLC]

[RTN]

Phoo, okay. This one's a doozy. This one's gonna break things. I'm
making no promises about how well I can explain any of this. Okay? Okay.

The carry flag. This is a topic I'm approaching with some hesitation.

Flags are sort of easy to start understanding when you begin learning
about them and even easier to stop understanding after that. Moreover, I'm
gonna have to address number systems again.
Flags are just system registers that are 1 bit in length. Or sometimes
they're a single register that is multiple bits in length and each bit
represents a flag. And each flag represents a system status.

Remember earlier when we set flag 3 so leading 0s would be displayed?


The calculator, when it's getting ready to drive the actual display, checks
that flag. If it's set, it knows to display the leading 0s as well as the
rest of whatever chunky number you've got it chewing on. Standard system
flags in most computational machines include sign flags, overflow flags,
zero flags, and carry flags. Of these, the HP-16C has a carry flag and an
overflow flag.

Ugh, this is gonna be worse than the index register stuff, but let's
try our best. Let's say we've got a 16 bit binary number that is all 1s.
There's only one like it. It's "1111111111111111".

Now, adding 1 to 1 in binary returns 10. It's like adding 1 to 9 in


decimal. We've already reached the highest digit we can represent, so we
roll back around to 0 and add one to the place value to the left. 1 + 9
gives us 10. We've got a 0 in the 1s place, and a 1 in the 10s place. That
makes sense.

And as I said at the start of the previous paragraph as well, adding 1


to 1 in binary returns 10. A 0 in the 1s place, a 1 in the 2s place. 1 plus
1 equals 2. Excellent!

Which... look, if you're reading this far along, you probably at least
skimmed that secret "cut" section where I tried my best to explain the
fundamentals of positional numeral base systems without doing any extremely
advisable or even requisite research before. Just winging it. That's what
this whole thing has been.

And will continue to be. I'm going to hope that at this point we
understand number systems, even systems that aren't decimal (or base-10) if
need be. And the need definitely be.

Getting back to things, we've got a 16 bit memory register that


contains "1111111111111111" and we've just decided to add 1 to it. Can you
see a problem with that?

The answer would be "10000000000000000" but that's a 17 digit binary


number. The calculator, in 16 bit mode, can't... do that. Except, it kinda
can? It can cheat. If you perform a function that causes the calculator to
carry a bit outside the left-most bit (the most significant bit, remember?)
then it won't actually just throw it away. It actually stores it somewhere!
It stores it in the bit for the carry flag.
In fact, if the screen says "0000000000000000" but shows a little "C"
annunciator, that actually means we're kinda looking at "10000000000000000"
maybe probably. Or we're looking at "0000000000000000" and someone forgot
to clear the carry flag when it was set during some earlier operation.
Either way the point is, you can use it to cheat your word size; to store
more data than you actually have the provisions for! I'm pretty sure that
means *put on a pair of sunglasses and steeples fingers* we're hackers now.

Okay great. Now what's the carry flag got to do with this routine?
Well, let's step through it one line at a time and find out!

[g][CF] 4

Flag 4 is the carry flag, we're making sure it's cleared first before
we do anything dumb.

[f][B?]

Ideally, we started this subroutine the same we started the other two,
Y is the seed, X is the value of the bit position we're trying to extract.
Now we're testing if it's set.

[g][SF] 4

If it is set, then we set flag four.

If not, we'll just enter a 0 into the X register.

[g][RLC]

This is the really clever bit, I love this. It's honestly no less
clunky than my solution, but it is SO much more interesting. The 16C can
treat the carry flag as an additional bit one position left of the most
significant bit. It's like the even more most significant bit. And there
are four additional rotate functions on the calculator we have touched on
yet.

We know about just bog-standard rotating. Left and right. Covered that
in Appendix C. And we've just talked about [RRn], which rotates right "n"
number of bits. You can intuit what [RLn] does, then. But we've got four
more versions of those same functions. [RLC], [RRC], [RLCn], [RRCn].

The "C" stands for the carry bit. You can rotate through, into and out
of, the carry bit! Whaaaaaat??
Let's make this UNNECESSARILY CLEAR! Starting with the ol' classic:

• "1010110010101011" the carry flag is clear.

• [g][RLC]

• "0101100101010110" the carry flag is now set.

• [g][RLC] again!

• "1011001010101101" the carry flag is cleared.

Actually, let's make this EVEN MORE UNNECESSARILY CLEAR!

• "0" "1010110010101011"

• [g][RLC]

• "0"<─"1010110010101011"<┐
└──> ───────────────> ─┘

• The 1 in the MSB shifts into the carry bit. This sets the carry flag.
The 0 that was in the carry bit pops into the LSB, and everything else
shifts left.

• "1" "0101100101010110"

• [g][RLC] again!

• "1"<─"0101100101010110"<┐
└──> ───────────────> ─┘

• This time, the 0 in the MSB shifts into the carry bit, clearing the
carry flag. The 1 that was in the carry bit pops into the LSB, and
everything shifts left again.

• "0" "1011001010101101"

Moreover, if we start with just all 0s, set the carry flag, then [g]
[RLC] once, we've just generated a "0000000000000001" without ever touching
the 1 key! Which is really dang cool because now we can use the carry flag
to generate bits!
So how's this second alternative subroutine using this junk? Start over
because I forgot where we were.

[g][CF] 4

Clear the flag, cool cool.

[f][B?]

Test the bit, yeah yeah.

[g][SF] 4

Set the carry flag, c'mon c'mon.

Enter a 0 into X, NOW EVERY EVENING WHEN ALL MY DAY'S WORK IS THR-...

[g][RLC]

We're coming out of a conditional branch. "If false" is straightforward


enough, skip to the "entering 0" line. Since the carry flag is still clear,
the [g][RLC] just rotates a bunch of 0s around for fun or something. When
it's done playing marbles, you've got a 0 if the bit we were extracting was
also a 0. Tubular!

"If true," we set the carry flag, putting a 1 in the carry bit. Then we
enter a 0 into X. Things look like this:

"1" "0000000000000000"

...and then we rotate left through the carry bit...

"1"<─"0000000000000000"<┐
└──> ───────────────> ─┘

"0" "0000000000000001"

OHHHHHHHH! PWARDEN42 THE ABSOLUTE MAD LAD DONE DID IT. And once again
the subroutine works consistently as a replacement for either of the other
two. You've got so many options for extracting bits now! A clunky but
earnest option. A super elegant and streamlined option. And a option so
clever you have to use this one, c'mon.

And that's it. Someday we'll talk about the overflow flag and signed
integer arithmetic. For now, go play with these routines and subroutines
and see if you too can think of different, better, worse, or just more
interesting ways of doing the same things! Or different things! Whatever,
just go have some fun.

Thanks for hanging out. Goodnight everybody.

You might also like