diff --git a/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/2026-08-02-NOTimothyLottes-for-those-planted-in-the-right-justified-current.md b/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/2026-08-02-NOTimothyLottes-for-those-planted-in-the-right-justified-current.md new file mode 100644 index 0000000..ea220d9 --- /dev/null +++ b/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/2026-08-02-NOTimothyLottes-for-those-planted-in-the-right-justified-current.md @@ -0,0 +1,83 @@ +--- +title: "For those planted in the right-justified current fixed point timeline, your bytes get loaded into the LSBs, and your addressing uses the LSBs." +type: archive +source: twitter +source_url: "https://x.com/NOTimothyLottes/status/2083758603945513244" +author: "NOTimothyLottes" +handle: NOTimothyLottes +post_id: "2083758603945513244" +date: 2026-08-02 +archived: 2026-08-24 +draft: false +tags: + - archive + - twitter + - NOTimothyLottes +description: "For those planted in the right-justified current fixed point timeline, your bytes get loaded into the LSBs, and your addressing uses the LSBs." +in_reply_to: "" +parent_post_id: "2083757874686161259" +--- + +## Source + +- URL: https://x.com/NOTimothyLottes/status/2083758603945513244 +- Author: NOTimothyLottes (@NOTimothyLottes) +- Posted: 2026-08-02 03:35:40 + +## Branch + +**1/** + +For those planted in the right-justified current fixed point timeline, your bytes get loaded into the LSBs, and your addressing uses the LSBs. The idea of doing the reverse of this is probably completely alien -> loading smaller bit types into the MSBs first ... + +**2/** + +Meaning standard integer thinking is to start with small things like an index and scale that by the stride into a larger number. But with what I'm suggesting you start with something large and instead make it smaller. + +**3/** + +In a traditional right-justified CPU, you'd have three ops {MUL, ADD, SHR} which could be reduced to {IMAD, SHR}, but would be just one op on a left justified machine. Meaning doing 'fixed point' stuff is a bit faster. + +**4/** + +32-bit left justified machine, can easily leverage say a 18-bit * 25-bit multiplier (Xlinix DSP) because the MSBs are always fed in, and the useful MSBs are always pulled out of the accumulator (P). Right justified machines really need that full 32-bit x 32-bit MUL + +**5/** + +Left justified variable bit-width loads make a lot more sense, partly because the LSBs index into bits (or sub-bits depending on mapping) effectively (instead of bytes), and one can do {4/8/16/32-bit} extraction with 8 SLICES and one CLB deep [a lot easier than right-justified] + +![](https://pbs.twimg.com/media/HOsENEBXEAAoWlx?format=png&name=orig) + +**6/** + +The core normalized IMAD can easily be made -/+ symmetrical simply by feeding in truncated LSBs of the add operand with simple logic based on MSBs of mul operands. In the example below the {-4} represents {-1.0}. Of course -4*-4 overflows to -4 [2's comp] + +![](https://pbs.twimg.com/media/HOsFs-nWAAAUlTy?format=png&name=orig) + +**7/** + +Predicate/bool logic becomes {0.0, -1.0} based, can leverage {a+b} wrap around to implement XOR, and -(a*b) to implement AND. + +![](https://pbs.twimg.com/media/HOsG3VBXAAA92kz?format=png&name=orig) + +**8/** + +Doing a "perspective divide" {s=x/z} can be transformed into a scaled perspective divide {s=x*(a/z)} to work around the {-1.0 to <1.0} range limitation. And one can transform the divide into a binary search for 's' - specifically largest 's' where {s*z<=x*a} + +![](https://pbs.twimg.com/media/HOsIBCBWcAA79PL?format=png&name=orig) + +**9/** + +Notice this binary search test takes just one IMAD {s*z+t} where t=-x*a. Meaning a smart machine can do a test step in 2 ops on a DSP (one IMAD for the test, the next to ADD to 's' for the next search step) ... + +**10/** + +Which requires the opcode ISA to have a latched delayed write to the register file that is conditional on the sign bit of a later IMAD op. This can make binary search for things like sqrt(x) also just as fast. + +**11/** + +I think there are ways to transcend typical integer machine IPCs by having uber ops in the ISA (this left-justified machine has a 1-clock {MUL,ADD,SHIFT,+ signed conditional register STORE,+ extra modifiers (like NOT)}. But still have extremely simple implementations. + +## Related + +- Spine: [[archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an]] diff --git a/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/index.md b/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/index.md index 1281ed2..a222501 100644 --- a/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/index.md +++ b/archive/threads/NOTimothyLottes/2026-08-02-re-fpga-land-alus-im-firmly-distracted-by-an/index.md @@ -7,7 +7,7 @@ author: "NOTimothyLottes" handle: NOTimothyLottes post_id: "2083757874686161259" date: 2026-08-02 -archived: 2026-08-23 +archived: 2026-08-24 draft: true tags: - archive @@ -50,21 +50,25 @@ In a traditional right-justified CPU, you'd have three ops {MUL, ADD, SHR} which Left justified variable bit-width loads make a lot more sense, partly because the LSBs index into bits (or sub-bits depending on mapping) effectively (instead of bytes), and one can do {4/8/16/32-bit} extraction with 8 SLICES and one CLB deep [a lot easier than right-justified] ![](https://pbs.twimg.com/media/HOsENEBXEAAoWlx?format=png&name=orig) + **7/** The core normalized IMAD can easily be made -/+ symmetrical simply by feeding in truncated LSBs of the add operand with simple logic based on MSBs of mul operands. In the example below the {-4} represents {-1.0}. Of course -4*-4 overflows to -4 [2's comp] ![](https://pbs.twimg.com/media/HOsFs-nWAAAUlTy?format=png&name=orig) + **8/** Predicate/bool logic becomes {0.0, -1.0} based, can leverage {a+b} wrap around to implement XOR, and -(a*b) to implement AND. ![](https://pbs.twimg.com/media/HOsG3VBXAAA92kz?format=png&name=orig) + **9/** Doing a "perspective divide" {s=x/z} can be transformed into a scaled perspective divide {s=x*(a/z)} to work around the {-1.0 to <1.0} range limitation. And one can transform the divide into a binary search for 's' - specifically largest 's' where {s*z<=x*a} ![](https://pbs.twimg.com/media/HOsIBCBWcAA79PL?format=png&name=orig) + **10/** Notice this binary search test takes just one IMAD {s*z+t} where t=-x*a. Meaning a smart machine can do a test step in 2 ops on a DSP (one IMAD for the test, the next to ADD to 's' for the next search step) ... diff --git a/scripts/twitter/tests/test_emit_op_owned.py b/scripts/twitter/tests/test_emit_op_owned.py index 0cc16b0..7e90bec 100644 --- a/scripts/twitter/tests/test_emit_op_owned.py +++ b/scripts/twitter/tests/test_emit_op_owned.py @@ -15,7 +15,7 @@ import unittest from pathlib import Path from twitter.emit_archive import emit -from twitter.models import PostData, PostMetrics +from twitter.models import PostData, PostMetrics, ThreadData def make_post( @@ -648,5 +648,169 @@ class SpinePrefersSameAuthorTests(unittest.TestCase): self.assertIn("early reply from outsider", branch_text) +class TipIsOpTests(unittest.TestCase): + """`--tip=` where the tip's reply_to_id is None. The walker + must walk DOWN from the OP via children_map, not stop at the + OP because the back-walk has no parent.""" + + def test_tip_with_no_parent_walks_down(self) -> None: + from twitter.tree import spine_from_tip + + thread_data = { + "root_post_id": "1000", + "source_url": "https://x.com/i/status/1000", + "posts": [ + { + "post_id": "1000", + "author": "X", + "handle": "X", + "text": "OP post", + "timestamp": "2023-01-01 00:00:00", + "media_urls": [], + "reply_to_id": None, + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + { + "post_id": "1001", + "author": "X", + "handle": "X", + "text": "self-reply 1", + "timestamp": "2023-01-01 00:05:00", + "media_urls": [], + "reply_to_id": "1000", + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + { + "post_id": "1002", + "author": "X", + "handle": "X", + "text": "self-reply 2", + "timestamp": "2023-01-01 00:10:00", + "media_urls": [], + "reply_to_id": "1001", + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + ], + } + thread = ThreadData( + root_post_id="1000", + posts=tuple( + PostData( + post_id=p["post_id"], + author=p["author"], + handle=p["handle"], + text=p["text"], + timestamp=p["timestamp"], + media_urls=tuple(p["media_urls"]), + reply_to_id=p["reply_to_id"], + quote_of_id=p["quote_of_id"], + metrics=PostMetrics(0, 0, 0, 0), + ) + for p in thread_data["posts"] + ), + source_url="https://x.com/i/status/1000", + ) + # --tip=OP, which has reply_to=None. Must walk DOWN. + spine = spine_from_tip(thread, "1000") + self.assertEqual(spine, ["1000", "1001", "1002"]) + + def test_tip_with_parent_walks_back(self) -> None: + from twitter.tree import spine_from_tip + + thread_data = { + "root_post_id": "2000", + "source_url": "https://x.com/i/status/2000", + "posts": [ + { + "post_id": "2000", + "author": "X", + "handle": "X", + "text": "OP", + "timestamp": "2023-01-01 00:00:00", + "media_urls": [], + "reply_to_id": None, + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + { + "post_id": "2001", + "author": "X", + "handle": "X", + "text": "self-reply", + "timestamp": "2023-01-01 00:05:00", + "media_urls": [], + "reply_to_id": "2000", + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + { + "post_id": "2002", + "author": "X", + "handle": "X", + "text": "tip reply", + "timestamp": "2023-01-01 00:10:00", + "media_urls": [], + "reply_to_id": "2001", + "quote_of_id": None, + "metrics": { + "reply_count": 0, + "repost_count": 0, + "like_count": 0, + "view_count": 0, + }, + }, + ], + } + thread = ThreadData( + root_post_id="2000", + posts=tuple( + PostData( + post_id=p["post_id"], + author=p["author"], + handle=p["handle"], + text=p["text"], + timestamp=p["timestamp"], + media_urls=tuple(p["media_urls"]), + reply_to_id=p["reply_to_id"], + quote_of_id=p["quote_of_id"], + metrics=PostMetrics(0, 0, 0, 0), + ) + for p in thread_data["posts"] + ), + source_url="https://x.com/i/status/2000", + ) + # --tip=middle post (has parent). Walks back. + spine = spine_from_tip(thread, "2002") + self.assertEqual(spine, ["2000", "2001", "2002"]) + + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/scripts/twitter/tree.py b/scripts/twitter/tree.py index b657054..bfb0f88 100644 --- a/scripts/twitter/tree.py +++ b/scripts/twitter/tree.py @@ -20,26 +20,43 @@ def children_map(thread: ThreadData) -> dict[str, list[str]]: def spine_from_tip(thread: ThreadData, tip_id: str) -> list[str]: - """Walk back from `tip_id` through `reply_to_id` to the OP (the - post with `reply_to_id == None` or a missing parent). Returns - all post ids on the linear chain in chronological order: OP at - index 0, tip at last. + """Resolve the OP chain from `tip_id`. - The walk crosses handle boundaries. For a chain that alternates - authors, every post is included. The archive dir for the - thread is owned by the OP at index 0. + If `tip_id` is the OP (its `reply_to_id` is `None`), walk DOWN + from it via `children_map` — same logic as `spine_ids`. This is + the common case when `--root=` and `--tip=` are the same + post id; the back-walk would otherwise return a single-element + chain and drop every self-reply. + + If `tip_id` has a parent, walk BACK through `reply_to_id` to + the OP and return the chain in chronological order (OP at + index 0, tip at last). The walk crosses handle boundaries. """ ids = by_id(thread) if tip_id not in ids: raise ValueError(f"tip {tip_id} not in dump") + if ids[tip_id].reply_to_id is None: + # Tip is the OP. Walk down its same-handle chain. + kids = children_map(thread) + handle = ids[tip_id].handle + out = [tip_id] + cur = tip_id + while True: + children = kids.get(cur, []) + if not children: + break + same = [c for c in children if ids[c].handle == handle] + cur = same[0] if same else children[0] + out.append(cur) + return out chain = [tip_id] cur = tip_id while True: - parent = ids[cur].reply_to_id - if not parent or parent not in ids: + parent_id = ids[cur].reply_to_id + if not parent_id or parent_id not in ids: break - chain.append(parent) - cur = parent + chain.append(parent_id) + cur = parent_id chain.reverse() return chain