RFC 1936

Implementing the Internet Checksum in Hardware

πŸ“… published April 1996, from ietf

RFC 1936: Implementing the Internet Checksum in Hardware πŸŽ€

Okay bestie, first the paperwork πŸ“‹. This is RFC 1936, titled Implementing the Internet Checksum in Hardware. It was written by J. Touch and B. Parham, both at ISI, and it's dated April 1996. The category is Informational, so it shares info with the Internet community but doesn't set a standard ✨. It doesn't obsolete or update any other RFCs.

Status of This Memo πŸ“œ

This memo provides information for the Internet community. It does not specify an Internet standard of any kind. Distribution of this memo is unlimited πŸ’–.

Abstract ✨

This memo presents a technique for efficiently implementing the Internet Checksum in hardware πŸ”₯. It includes PLD code for programming a single, low cost part to perform checksumming at 1.26 Gbps πŸ’….

Introduction πŸ¦‹

Okay so the Internet Checksum is used in a bunch of Internet protocols to catch data corruption. It's used in headers (e.g., IP) [4] and in packet bodies (e.g, UDP, TCP) [5][6]. People have already written about how to do this efficiently in software in earlier RFCs [1][2][3][7], so that's covered 🫢.

Here's the twist: efficient software versions of the checksum are often squished into data copying operations ([1], Section 2). Copying data is increasingly done by dedicated direct memory access (DMA) hardware, so DMA designs are starting to include special hardware that computes the Internet Checksum during the transfer. It's giving hardware glow-up 😭🌸.

This note lays out the architecture of an efficient, pipelined Internet Checksum mechanism that's a good fit for DMA hardware [8] ✨. It can run on a relatively inexpensive programmable logic device (PLD), which cost about $40 in 1995 πŸ’Έ, and it handles 1.26 Gbps transfer rates, at 26 ns per 32-bit word ⭐. Appendix A has the pseudocode for this device 🧚. The design has already been built into the PC-ATOMIC host interface hardware [8] πŸ‘‘. The authors believe this design is of general use to the Internet community, and honestly? Same πŸ’œ.

The rest of the doc is organized like this:

Review of the Internet Checksum
One's Complement vs. Two's Complement Addition
Interfaces
Summary
Appendix A - PLD source code

Main character energy, and it's all coming up next πŸ¦‹πŸ’–

Interfaces ✨

Okay so this device lives on a VL-Bus PC host interface card [8] πŸ’». It has both a hardware interface and a software interface, and here's how they work 🫢.

Hardware Interface πŸ”Œ

The Internet Checksum hardware shows up as a single-port 32-bit register with clock and control signals [8], and it looks like this:

                   +----------------------+
            CLR--->|                      |
            OE---->|  32-bit register as  |
            CLK--->|  2 adjacent 16-bit   |<---/---> 32-bit data bus
            ICLK-->| ones-complement sums |
            ADD--->|                      |
                   +----------------------+

            CLR    = zero the register
            OE     = write the register onto the data bus
            CLK    = clock to cycle the pipeline operation
            ICLK   = input data latch clock
            ADD    = initiating an add of latched input data

CLR wipes the checksum register and the input latch back to zero 🧹. There's no explicit load, bestie, but a CLR followed by a write of the load value to a dummy location does the same thing ✨.

OE either writes the register onto the data bus or tri-states it πŸŽ€.

CLK keeps the pipeline moving πŸƒβ€β™€οΈ. If no new input data is latched to be added (via ICLK, ADD), a virtual "zero" gets summed into the register so the pipeline can empty out 🫧.

ICLK transparently latches whatever is on the data bus, so it can be summed into the accumulator on the next ADD signal 🧲. The ADD signal then pushes that latched input data into the checksum pipeline πŸ’…. ADD and ICLK are commonly tied together πŸŽ€. One 32-bit data value can be latched and accumulated into the pipeline adder every 26-ns clock, as long as the data is stable when the ADD/ICLK signal happens ⏱️. It's giving speed πŸ”₯.

The internal 32-bit register is organized as two 16-bit ones-complement sums, one over the even 16-bit words of the data stream and one over the odd ones 🌈. To get the Internet Checksum out of that, ones-complement add the two halves together, then invert the result πŸ¦‹.

Software Interface πŸ’Ύ

The device is used as a memory-mapped register. You read the register by reading its equivalent memory location πŸ‘€.

The device is controlled through an external memory-mapped register. Bits in this control register clear the device (set/clear the CLR line), and enable and disable the device (set/clear the ADD line) πŸŽ›οΈ. The CLR line can instead be mapped to a memory write. That way, reading the location is a non-destructive read of the checksum register, and writing any value clears the checksum register 🧼. The enable/disable control must be stored in an external register.

The device is designed to run in the background during memory transfers, whether DMA or programmed I/O 🏎️. Once it's enabled, every transfer across that bus gets summed into the checksum register πŸ’–. The checksum is available 5 clocks after the last enabled data accumulation. This delay is often hidden by memory access mechanisms and bus arbitration πŸ™ˆ. If it's needed, "stall" instructions can be executed for the appropriate delay ⭐.

For the following example, we assume the device is located at CKSUMLOC, and that reading that location reads the checksum register while writing any value to it clears the register 🧚. The control register is located at CTLLOC, and the checksum enable/disable bit is CKSUMBIT, where 1 is enabled and 0 is disabled. To perform a checksum, a programmer clears the register, (optionally) initializes the checksum, starts a series of transfers, and then uses the result πŸ“:

         /******* initialization *******/
         *(CTLLOC) &= ~((ctlsize)(CKSUMBIT));     /* disable sum */
         (word32)(*(CKSUMLOC)) = 0;               /* clear reg   */
         *(CTLLOC) |= CKSUMBIT;                   /* enable sum  */
         { (optional) write initial value to a dummy location }

         /***** perform a transfer *****/
         { do one or more DMA or PIO transfers - read or write }

         /***** gather the results *****/
         *(CTLLOC) &= ~((ctlsize)(CKSUMBIT));     /* disable sum  */
         sum = (word32)(*(CKSUMLOC));             /* read sum     */
         sum = (sum & 0xFFFF) + (sum >> 16);      /* fold halves  */
         sum = (sum & 0xFFFF) + (sum >> 16);      /* add in carry */
         ipcksum = (halfword16)(~(sum & 0xFFFF)); /* 1's negative */

Summary 🌸

This note describes the design of a hardware Internet Checksum that can be implemented in an inexpensive PLD and achieves 1.26 Gbps πŸš€. The design has been implemented in the PC-ATOMIC host interface hardware [8]. We believe this design is of general use to the Internet community πŸŒπŸ’œ.

Security Considerations πŸ”

Security considerations are not addressed here. The Internet Checksum is not intended as a security measure. πŸ’€ Do not let anyone tell you a checksum is a lock. It is not, and it was never meant to be one πŸ™…β€β™€οΈ.

Acknowledgements 🫢

The authors would like to thank the members of the "High-Performance Computing and Communications" group, notably Mike Carlton, and the "Advanced Systems" divisions at ISI πŸŽ€. Thank you for helping with the development of the hardware and of this memo 🌷.

References πŸ“š

  • [1] Braden, R., Borman, D., and Partridge, C., "Computing the Internet Checksum," Network Working Group RFC-1071, ISI, Cray Research, and BBN Labs, Sept. 1988.
  • [2] Mallory, T., and Kullberg, A., "Incremental Updating of the Internet Checksum," Network Working Group RFC-1141, BBN Comm., Jan. 1990.
  • [3] Plummer, W., "TCP Checksum Function Design," IEN-45, BBN, 1978, included as an appendix in RFC-1071.
  • [4] Postel, Jon, "Internet Protocol," Network Working Group RFC-791/STD-5, ISI, Sept. 1981.
  • [5] Postel, Jon, "User Datagram Protocol," Network Working Group RFC-768/STD-6, ISI, Aug. 1980.
  • [6] Postel, Jon, "Transmission Control Protocol," Network Working Group RFC-793/STD-7, ISI, Sept. 1981.
  • [7] Rijsinghani, A., "Computation of the Internet Checksum via Incremental Update," Network Working Group RFC-1624, Digital Equipment Corp., May 1994.
  • [8] Touch, J., "PC-ATOMIC", ISI Tech. Report. SR-95-407, June 1995.

Authors' Addresses πŸ’Œ

Joe Touch
University of Southern California/Information Sciences Institute
4676 Admiralty Way
Marina del Rey, CA 90292-6695
USA
Phone: +1 310-822-1511 x151
Fax:   +1 310-823-6714
URL:   http://www.isi.edu/~touch
EMail: touch@isi.edu


Bruce Parham
University of Southern California/Information Sciences Institute
4676 Admiralty Way
Marina del Rey, CA 90292-6695
USA
Phone: +1 310-822-1511 x101
Fax:   +1 310-823-6714
EMail: bparham@isi.edu

Appendix A: PLD source code 🧚

The following is the PLD source code for an AMD MACH-435 PLD. The MACH-435 is made up of 8 22V10-equivalent PLD blocks, connected by a configurable internal matrix 🧩.

---- (PLD source code follows) ----

TITLE    PC-ATOMIC IP Sum Accelerator - 1-clock 2- and 3-bit 26 ns version
PATTERN  ip_sum
REVISION 1.01
AUTHOR   J. Touch & B. Parham
COMPANY  USC/ISI
DATE     06/21/94

CHIP    ip_sum          MACH435

; accumulates in 1 clock (1 level of logic)
;
; resources allocated to reduce fitting time
;
; uses an input register "dl" to latch the data bus values on rising edge
; accumulates a hi/lo ones-complement sum in register "q"
; the input and output are accessed via bidirectional pins "dq"
;
; uses 2 groups of 6 carry bit registers "cy"
;
; use 3-bit full-adders with carry lookahead (settles in 6 clocks)
; group 16 bits as      [000102 030405 0607 080910 111213 1415]
;                       [161718 192021 2223 242526 272829 3031]
;
; locking the pins down speeds up fitting and is designed to force
; 4-bit components into single "segments" of the PLD.
; we could have indicated the same thing via:
;       GROUP MACH_SEG_A        dq[6..0]
;       GROUP MACH_SEG_B        dq[14..8]
;       GROUP MACH_SEG_C        dq[22..16]
;       GROUP MACH_SEG_D        dq[30..24]

;
; control pins:
;
PIN     20      clk             ; adder clock
PIN     62      ip_add          ; add current data to sum
PIN     83      ip_sum_ena      ; output current sum
PIN     41      ip_clr          ; clear current sum
PIN 23  ip_dclk                 ; input data latch (tied to clk, or not)
;
; dq are data bus pins
; dl is the input register
;
PIN     [9..3]          dq[6..0] IPAIR dl[6..0]         ; IO port
PIN     [18..12]        dq[14..8] IPAIR dl[14..8]       ; IO port
PIN     [30..24]        dq[22..16] IPAIR dl[22..16]     ; IO port
PIN     [39..33]        dq[30..24] IPAIR dl[30..24]     ; IO port
PIN     ?       dq[31,23,15,7] IPAIR dl[31,23,15,7]     ; IO port

;
; q  is the partial checksum register
; dl is the input register
; dq are the data bus pins
;
NODE    ?       q[31..0] OPAIR dq[31..0]        ; internal data in reg
NODE    ?       dl[31..0] REG                   ; input reg

;
; cy are the carry register bits
;
NODE    ?       cy[31,29,26,23,21,18,15,13,10,7,5,2] REG
                                                ;1-bit internal carry bits

EQUATIONS

;
; .trst is the tri-state control, 0 means these are always inputs
;
ip_add.trst             = 0
ip_clr.trst             = 0
ip_sum_ena.trst         = 0

;
; grab data to the input register on every clock (irrelevant if invalid)
;
dl[31..0].clkf          = ip_dclk       ; grab data all the time
        ; don't use setf, rstf, or trst for dl
        ; we want dl to map to input registers, not internal cells
        ; besides, input registers don't need setf, rstf, or trst

;
; control of the checksum register
;
dq[31..0].clkf          = clk                   ; clk clocks everything
dq[31..0].setf          = gnd                   ; never preset registers
dq[31..0].rstf          = ip_clr                ; clear on reset
dq[31..0].trst          = ip_sum_ena            ; ena outputs sum - read
;
; control for the carry register
;
cy[31,29,26,23,21,18,15,13,10,7,5,2].clkf       = clk
cy[31,29,26,23,21,18,15,13,10,7,5,2].setf       = gnd    ; never preset
cy[31,29,26,23,21,18,15,13,10,7,5,2].rstf       = ip_clr ; clear on reset

;
; INPUT DATA LATCH
; nothing fancy here - grab all inputs when ip_add signal is high
; i.e., grab data in input register
;
dl[31..0]             := dq[31..0]

;
; COMBINATORIAL ADDER
;
; built as a series of 2-bit and 3-bit (carry-lookahead) full-adders
; with carries sent to the carry register "pipeline"
;
; sum[n] are sum bits
; cy[m] are carry bits
; ":+:" is XOR

;
; SUM[0] = (A0 :+: B0 :+: CARRY_IN)
;
; CY[0] = ((A0 * B0) + ((A0 :+: B0) * CARRY_IN))
;
; actually, the latter can be rewritten as
;
; CY[0] = ((A0 * B0) + ((A0 + B0) * CARRY_IN))
;
; because the XOR won't be invalidated by the AND case, since the
; result is always 1 from the first term then anyway
; this helps reduce the number of XOR terms required, which are
; a limited resource in PLDs
;
; SUM THE LOW-ORDER WORD

;
; the first 5 bits [0..4] of the low-order word
;
q[0]    := (q[0] :+: (ip_add * dl[0]) :+: cy[15])

q[1]    := (q[1] :+: (ip_add * dl[1]) :+:
        ((ip_add *
                (q[0] * dl[0] +
                dl[0] * cy[15])) +
        (q[0] * cy[15])))

q[2]    := (q[2] :+: (ip_add * dl[2]) :+:
        ((ip_add *
                (q[1] * dl[1] +
                 q[1] *  q[0] *  dl[0] +
                dl[1] *  q[0] *  dl[0] +
                 q[1] * dl[0] * cy[15] +
                dl[1] * dl[0] * cy[15] +
                dl[1] *  q[0] * cy[15])) +
        (q[1] * q[0] * cy[15])))

cy[2]   := ((ip_add *
                (q[2] * dl[2] +
                 q[2] *  q[1] * dl[1] +
                dl[2] *  q[1] * dl[1] +
                 q[2] *  q[1] *  q[0] *  dl[0] +
                 q[2] * dl[1] *  q[0] *  dl[0] +
                dl[2] *  q[1] *  q[0] *  dl[0] +
                dl[2] * dl[1] *  q[0] *  dl[0] +
                 q[2] *  q[1] * dl[0] * cy[15] +
                 q[2] * dl[1] *  q[0] * cy[15] +
                 q[2] * dl[1] * dl[0] * cy[15] +
                dl[2] *  q[1] *  q[0] * cy[15] +
                dl[2] *  q[1] * dl[0] * cy[15] +
                dl[2] * dl[1] *  q[0] * cy[15] +
                dl[2] * dl[1] * dl[0] * cy[15])) +
        (q[2] * q[1] * q[0] * cy[15]))

q[3]    := (q[3] :+: (ip_add * dl[3]) :+: cy[2])

q[4]    := (q[4] :+: (ip_add * dl[4]) :+:
        ((ip_add *
                (q[3] * dl[3] +
                dl[3] * cy[2])) +
        (q[3] * cy[2])))

;
; the next 3 bits [5..7] of the low-order word
;
q[5]    := (q[5] :+: (ip_add * dl[5]) :+:
        ((ip_add *
                (q[4] * dl[4] +
                 q[4] *  q[3] * dl[3] +
                dl[4] *  q[3] * dl[3] +
                 q[4] * dl[3] * cy[2] +
                dl[4] * dl[3] * cy[2] +
                dl[4] *  q[3] * cy[2])) +
        (q[4] * q[3] * cy[2])))

cy[5]   := ((ip_add * (
                 q[5] * dl[5] +
                 q[5] *  q[4] * dl[4] +
                dl[5] *  q[4] * dl[4] +
                 q[5] *  q[4] *  q[3] * dl[3] +
                 q[5] * dl[4] *  q[3] * dl[3] +
                dl[5] *  q[4] *  q[3] * dl[3] +
                dl[5] * dl[4] *  q[3] * dl[3] +
                 q[5] *  q[4] * dl[3] * cy[2] +
                 q[5] * dl[4] *  q[3] * cy[2] +
                 q[5] * dl[4] * dl[3] * cy[2] +
                dl[5] *  q[4] *  q[3] * cy[2] +
                dl[5] *  q[4] * dl[3] * cy[2] +
                dl[5] * dl[4] *  q[3] * cy[2] +
                dl[5] * dl[4] * dl[3] * cy[2])) +
        (q[5] * q[4] * q[3] * cy[2]))

q[6]    := (q[6] :+: (ip_add * dl[6]) :+: cy[5])

q[7]    := (q[7] :+: (ip_add * dl[7]) :+:
        ((ip_add *
                (q[6] * dl[6] +
                dl[6] * cy[5])) +
        (q[6] * cy[5])))

cy[7]   := ((ip_add *
                (q[7] * dl[7] +
                 q[7] *  q[6] * dl[6] +
                dl[7] *  q[6] * dl[6] +
                 q[7] * dl[6] * cy[5] +
                dl[7] * dl[6] * cy[5] +
                dl[7] *  q[6] * cy[5])) +
        (q[7] * q[6] * cy[5]))

;
; the next 5 bits [8..12] of the low-order word
;
q[8]    := (q[8] :+: (ip_add * dl[8]) :+: cy[7])

q[9]    := (q[9] :+: (ip_add * dl[9]) :+:
        ((ip_add *
                (q[8] * dl[8] +
                dl[8] * cy[7])) +
        (q[8] * cy[7])))

q[10]   := (q[10] :+: (ip_add * dl[10]) :+:
        ((ip_add *
                (q[9] * dl[9] +
                 q[9] *  q[8] * dl[8] +
                dl[9] *  q[8] * dl[8] +
                 q[9] * dl[8] * cy[7] +
                dl[9] * dl[8] * cy[7] +
                dl[9] *  q[8] * cy[7])) +
        (q[9] * q[8] * cy[7])))

cy[10]  := ((ip_add *
                (q[10] * dl[10] +
                 q[10] *  q[9] * dl[9] +
                dl[10] *  q[9] * dl[9] +
                 q[10] *  q[9] *  q[8] * dl[8] +
                 q[10] * dl[9] *  q[8] * dl[8] +
                dl[10] *  q[9] *  q[8] * dl[8] +
                dl[10] * dl[9] *  q[8] * dl[8] +
                 q[10] *  q[9] * dl[8] * cy[7] +
                 q[10] * dl[9] *  q[8] * cy[7] +
                 q[10] * dl[9] * dl[8] * cy[7] +
                dl[10] *  q[9] *  q[8] * cy[7] +
                dl[10] *  q[9] * dl[8] * cy[7] +
                dl[10] * dl[9] *  q[8] * cy[7] +
                dl[10] * dl[9] * dl[8] * cy[7])) +
        (q[10] * q[9] * q[8] * cy[7]))

q[11]   := (q[11] :+: (ip_add * dl[11]) :+: cy[10])

q[12]   := (q[12] :+: (ip_add * dl[12]) :+:
        ((ip_add *
                (q[11] * dl[11] +
                dl[11] * cy[10])) +
        (q[11] * cy[10])))

;
; the final 3 bits [13..15] of the low-order word
;
q[13]   := (q[13] :+: (ip_add * dl[13]) :+:
        ((ip_add *
                (q[12] * dl[12] +
                 q[12] *  q[11] * dl[11] +
                dl[12] *  q[11] * dl[11] +
                 q[12] * dl[11] * cy[10] +
                dl[12] * dl[11] * cy[10] +
                dl[12] *  q[11] * cy[10])) +
        (q[12] * q[11] * cy[10])))

cy[13]  := ((ip_add * (
                 q[13] * dl[13] +
                 q[13] *  q[12] * dl[12] +
                dl[13] *  q[12] * dl[12] +
                 q[13] *  q[12] *  q[11] * dl[11] +
                 q[13] * dl[12] *  q[11] * dl[11] +
                dl[13] *  q[12] *  q[11] * dl[11] +
                dl[13] * dl[12] *  q[11] * dl[11] +
                 q[13] *  q[12] * dl[11] * cy[10] +
                 q[13] * dl[12] *  q[11] * cy[10] +
                 q[13] * dl[12] * dl[11] * cy[10] +
                dl[13] *  q[12] *  q[11] * cy[10] +
                dl[13] *  q[12] * dl[11] * cy[10] +
                dl[13] * dl[12] *  q[11] * cy[10] +
                dl[13] * dl[12] * dl[11] * cy[10])) +
        (q[13] * q[12] *  q[11] * cy[10]))

q[14]   := (q[14] :+: (ip_add * dl[14]) :+: cy[13])

q[15]   := (q[15] :+: (ip_add * dl[15]) :+:
        ((ip_add *
                (q[14] * dl[14] +
                dl[14] * cy[13])) +
        (q[14] * cy[13])))

cy[15]  := ((ip_add *
                (q[15] * dl[15] +
                 q[15] *  q[14] * dl[14] +
                dl[15] *  q[14] * dl[14] +
                 q[15] * dl[14] * cy[13] +
                dl[15] * dl[14] * cy[13] +
                dl[15] *  q[14] * cy[13])) +
        (q[15] * q[14] * cy[13]))

; SUM THE HIGH-ORDER WORD
;

okay bestie, we've just hit the high-order word, and it's giving a whole new vibe πŸ‘‘βœ¨ The same pattern carries on here, just with the next bits in line. Sixteen bits, five at a time, same energy 🫢


;
; the first 5 bits [16..20] of the high-order word
;
q[16]   := (q[16] :+: (ip_add * dl[16]) :+: cy[31])

q[17]   := (q[17] :+: (ip_add * dl[17]) :+:
        ((ip_add *
                (q[16] * dl[16] +
                dl[16] * cy[31])) +
        (q[16] * cy[31])))

q[18]   := (q[18] :+: (ip_add * dl[18]) :+:
        ((ip_add *
                (q[17] * dl[17] +
                 q[17] *  q[16] * dl[16] +
                dl[17] *  q[16] * dl[16] +
                 q[17] * dl[16] * cy[31] +
                dl[17] * dl[16] * cy[31] +
                dl[17] *  q[16] * cy[31])) +
        (q[17] * q[16] * cy[31])))

cy[18]  := ((ip_add *
                (q[18] * dl[18] +
                 q[18] *  q[17] * dl[17] +
                dl[18] *  q[17] * dl[17] +
                 q[18] *  q[17] *  q[16] * dl[16] +
                 q[18] * dl[17] *  q[16] * dl[16] +
                dl[18] *  q[17] *  q[16] * dl[16] +
                dl[18] * dl[17] *  q[16] * dl[16] +
                 q[18] *  q[17] * dl[16] * cy[31] +
                 q[18] * dl[17] *  q[16] * cy[31] +
                 q[18] * dl[17] * dl[16] * cy[31] +
                dl[18] *  q[17] *  q[16] * cy[31] +
                dl[18] *  q[17] * dl[16] * cy[31] +
                dl[18] * dl[17] *  q[16] * cy[31] +
                dl[18] * dl[17] * dl[16] * cy[31])) +
        (q[18] * q[17] * q[16] * cy[31]))

q[19]   := (q[19] :+: (ip_add * dl[19]) :+: cy[18])

q[20]   := (q[20] :+: (ip_add * dl[20]) :+:
        ((ip_add *
                (q[19] * dl[19] +
                dl[19] * cy[18])) +
        (q[19] * cy[18])))

;
; the next 3 bits [21..23] of the high-order word
;
q[21]   := (q[21] :+: (ip_add * dl[21]) :+:
        ((ip_add *
                (q[20] * dl[20] +
                 q[20] *  q[19] * dl[19] +
                dl[20] *  q[19] * dl[19] +
                 q[20] * dl[19] * cy[18] +
                dl[20] * dl[19] * cy[18] +
                dl[20] *  q[19] * cy[18])) +
        (q[20] * q[19] * cy[18])))

cy[21]  := ((ip_add * (
                 q[21] * dl[21] +
                 q[21] *  q[20] * dl[20] +
                dl[21] *  q[20] * dl[20] +
                 q[21] *  q[20] *  q[19] * dl[19] +
                 q[21] * dl[20] *  q[19] * dl[19] +
                dl[21] *  q[20] *  q[19] * dl[19] +
                dl[21] * dl[20] *  q[19] * dl[19] +
                 q[21] *  q[20] * dl[19] * cy[18] +
                 q[21] * dl[20] *  q[19] * cy[18] +
                 q[21] * dl[20] * dl[19] * cy[18] +
                dl[21] *  q[20] *  q[19] * cy[18] +
                dl[21] *  q[20] * dl[19] * cy[18] +
                dl[21] * dl[20] *  q[19] * cy[18] +
                dl[21] * dl[20] * dl[19] * cy[18])) +
        (q[21] * q[20] * q[19] * cy[18]))

q[22]   := (q[22] :+: (ip_add * dl[22]) :+: cy[21])

q[23]   := (q[23] :+: (ip_add * dl[23]) :+:
        ((ip_add *
                (q[22] * dl[22] +
                dl[22] * cy[21])) +
        (q[22] * cy[21])))

cy[23]  := ((ip_add *
                (q[23] * dl[23] +
                 q[23] *  q[22] * dl[22] +
                dl[23] *  q[22] * dl[22] +
                 q[23] * dl[22] * cy[21] +
                dl[23] * dl[22] * cy[21] +
                dl[23] *  q[22] * cy[21])) +
        (q[23] * q[22] * cy[21]))

;
; the next 5 bits [24..28] of the high-order word
;
q[24]   := (q[24] :+: (ip_add * dl[24]) :+: cy[23])

q[25]   := (q[25] :+: (ip_add * dl[25]) :+:
        ((ip_add *
                (q[24] * dl[24] +
                dl[24] * cy[23])) +
        (q[24] * cy[23])))

q[26]   := (q[26] :+: (ip_add * dl[26]) :+:
        ((ip_add *
                (q[25] * dl[25] +
                 q[25] *  q[24] * dl[24] +
                dl[25] *  q[24] * dl[24] +
                 q[25] * dl[24] * cy[23] +
                dl[25] * dl[24] * cy[23] +
                dl[25] *  q[24] * cy[23])) +
        (q[25] * q[24] * cy[23])))

cy[26]  := ((ip_add *
                (q[26] * dl[26] +
                 q[26] *  q[25] * dl[25] +
                dl[26] *  q[25] * dl[25] +
                 q[26] *  q[25] *  q[24] * dl[24] +
                 q[26] * dl[25] *  q[24] * dl[24] +
                dl[26] *  q[25] *  q[24] * dl[24] +
                dl[26] * dl[25] *  q[24] * dl[24] +
                 q[26] *  q[25] * dl[24] * cy[23] +
                 q[26] * dl[25] *  q[24] * cy[23] +
                 q[26] * dl[25] * dl[24] * cy[23] +
                dl[26] *  q[25] *  q[24] * cy[23] +
                dl[26] *  q[25] * dl[24] * cy[23] +
                dl[26] * dl[25] *  q[24] * cy[23] +
                dl[26] * dl[25] * dl[24] * cy[23])) +
        (q[26] * q[25] * q[24] * cy[23]))

q[27]   := (q[27] :+: (ip_add * dl[27]) :+: cy[26])

q[28]   := (q[28] :+: (ip_add * dl[28]) :+:
        ((ip_add *
                (q[27] * dl[27] +
                dl[27] * cy[26])) +
        (q[27] * cy[26])))

;
; the final 3 bits [29..31] of the high-order word
;
q[29]   := (q[29] :+: (ip_add * dl[29]) :+:
        ((ip_add *
                (q[28] * dl[28] +
                 q[28] *  q[27] * dl[27] +
                dl[28] *  q[27] * dl[27] +
                 q[28] * dl[27] * cy[26] +
                dl[28] * dl[27] * cy[26] +
                dl[28] *  q[27] * cy[26])) +
        (q[28] * q[27] * cy[26]))

Okay bestie, the carry chain is basically done πŸ’… Here's where the last three bits do their thing and the whole multiply gets wrapped up πŸ¦‹

cy[29]  := ((ip_add * (
                 q[29] * dl[29] +
                 q[29] *  q[28] * dl[28] +
                dl[29] *  q[28] * dl[28] +
                 q[29] *  q[28] *  q[27] * dl[27] +
                 q[29] * dl[28] *  q[27] * dl[27] +
                dl[29] *  q[28] *  q[27] * dl[27] +
                dl[29] * dl[28] *  q[27] * dl[27] +
                 q[29] *  q[28] * dl[27] * cy[26] +
                 q[29] * dl[28] *  q[27] * cy[26] +
                 q[29] * dl[28] * dl[27] * cy[26] +
                dl[29] *  q[28] *  q[27] * cy[26] +
                dl[29] *  q[28] * dl[27] * cy[26] +
                dl[29] * dl[28] *  q[27] * cy[26] +
                dl[29] * dl[28] * dl[27] * cy[26])) +
        (q[29] * q[28] * q[27] * cy[26]))

Next up are q[30] and q[31], which are giving main character energy with all those :+: operators 🌸

q[30]   := (q[30] :+: (ip_add * dl[30]) :+: cy[29])

q[31]   := (q[31] :+: (ip_add * dl[31]) :+:
        ((ip_add *
                (q[30] * dl[30] +
                dl[30] * cy[29])) +
        (q[30] * cy[29])))

cy[31]  := ((ip_add *
                (q[31] * dl[31] +
                 q[31] *  q[30] * dl[30] +
                dl[31] *  q[30] * dl[30] +
                 q[31] * dl[30] * cy[29] +
                dl[31] * dl[30] * cy[29] +
                dl[31] *  q[30] * cy[29])) +
        (q[31] * q[30] * cy[29]))

Now the final step πŸŽ€ The nodes get pushed out onto the output pins, and this part is still pending enable, so we're not totally done yet ✨

;
; output nodes onto output pins (pending enable..)
;
dq[0]   := {q[0]}
dq[1]   := {q[1]}
dq[2]   := {q[2]}
dq[3]   := {q[3]}
dq[4]   := {q[4]}
dq[5]   := {q[5]}
dq[6]   := {q[6]}
dq[7]   := {q[7]}
dq[8]   := {q[8]}
dq[9]   := {q[9]}
dq[10]  := {q[10]}
dq[11]  := {q[11]}
dq[12]  := {q[12]}
dq[13]  := {q[13]}
dq[14]  := {q[14]}
dq[15]  := {q[15]}

dq[16]  := {q[16]}
dq[17]  := {q[17]}
dq[18]  := {q[18]}
dq[19]  := {q[19]}
dq[20]  := {q[20]}
dq[21]  := {q[21]}
dq[22]  := {q[22]}
dq[23]  := {q[23]}
dq[24]  := {q[24]}
dq[25]  := {q[25]}
dq[26]  := {q[26]}
dq[27]  := {q[27]}
dq[28]  := {q[28]}
dq[29]  := {q[29]}
dq[30]  := {q[30]}
dq[31]  := {q[31]}

;
; end.
;
πŸ“¦ where this page comes from