[{"content":"With v0.2.0, my Deno‑based HTTP kernel has taken a decisive step forward: instead of chaining every middleware dynamically for each request, the entire chain is now compiled once. This reduces overhead, simplifies error handling—and makes routing noticeably faster.\nBaseline The first generation of the kernel relied on a recursive dispatch() mechanism:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private async executePipeline( ctx: TContext, middleware: Middleware\u0026lt;TContext\u0026gt;[], handler: Handler\u0026lt;TContext\u0026gt;, ): Promise\u0026lt;Response\u0026gt; { const handleInternalError = (ctx: TContext, err?: unknown) =\u0026gt; this.cfg.httpErrorHandlers[HTTP_500_INTERNAL_SERVER_ERROR]( ctx, normalizeError(err), ); let lastIndex = -1; const dispatch = async (currentIndex: number): Promise\u0026lt;Response\u0026gt; =\u0026gt; { if (currentIndex \u0026lt;= lastIndex) { throw new Error(\u0026#39;Middleware called `next()` multiple times\u0026#39;); } lastIndex = currentIndex; const isWithinMiddleware = currentIndex \u0026lt; middleware.length; const fn = isWithinMiddleware ? middleware[currentIndex] : handler; if (isWithinMiddleware) { if (!isMiddleware(fn)) { throw new Error(\u0026#39;Expected middleware function, but received invalid value\u0026#39;); } return await fn(ctx, () =\u0026gt; dispatch(currentIndex + 1)); } if (!isHandler(fn)) { throw new Error(\u0026#39;Expected request handler, but received invalid value\u0026#39;); } return await fn(ctx); }; try { const response = await dispatch(0); return this.cfg.decorateResponse(response, ctx); } catch (e) { return handleInternalError(ctx, e); } } This approach was easy to understand, but for each request it created several closures, index checks and type checks – all things that waste precious time under high concurrency.\nThe new approach When registering a route, a statically linked chain of functions is now generated. The result is a single function runRoute(ctx) that inlines all middlewares and the handler.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private compile( route: Omit\u0026lt;IInternalRoute\u0026lt;TContext\u0026gt;, \u0026#39;runRoute\u0026#39; | \u0026#39;matcher\u0026#39; | \u0026#39;method\u0026#39;\u0026gt;, ): (ctx: TContext) =\u0026gt; Promise\u0026lt;Response\u0026gt; { if (!isHandler\u0026lt;TContext\u0026gt;(route.handler)) { throw new TypeError(\u0026#39;Route handler must be a function returning a Promise\u0026lt;Response\u0026gt;.\u0026#39;); } let composed = route.handler; for (let i = route.middlewares.length - 1; i \u0026gt;= 0; i--) { if (!isMiddleware\u0026lt;TContext\u0026gt;(route.middlewares[i])) { throw new TypeError(`Middleware at index ${i} is not a valid function.`); } const current = route.middlewares[i]; const next = composed; composed = async (ctx: TContext): Promise\u0026lt;Response\u0026gt; =\u0026gt; { let called = false; return await current(ctx, async () =\u0026gt; { if (called) { throw new Error(`next() called multiple times in middleware at index ${i}`); } called = true; return await next(ctx); }); }; } return composed; } Important: every middleware still receives its next() function – but thanks to the called flag it can now be invoked only once, reliably preventing multiple executions.\nBenchmarks Measured with 10 000 parallel requests on a Ryzen 7 8845HS:\nBenchmark (parallel) v0.1.0 (dynamic) v0.2.0 (pre‑compiled) Gain Simple Route 5.4 ms 2.1 ms ≈ 61 % Complex Route 11.2 ms 7.9 ms ≈ 29 % For single requests, the numbers are almost identical – the speed advantage becomes apparent mainly under heavy load.\nConclusion Static routing reduces overhead and significantly increases throughput. next() errors are now detected immediately. The API remained compatible – upgrading from 0.1.x to 0.2.0 requires no code changes. The kernel is already running fast and stable in my build‑cache‑server – a Deno‑based build cache for act; compatible with the GitHub Actions Cache API.\n👉 Source code \u0026amp; release notes: https://github.com/0xMax42/http-kernel\n","date":"2025-05-27T13:00:00Z","image":"https://0xMax42.io/p/http-kernel-v0.2.0-von-dynamischem-dispatch-zur-vorkompilierten-pipeline/http-kernel-v0.2.0-cover_hu_7ba9f7ef043ef58e.webp","permalink":"https://0xMax42.io/en/p/http-kernel-v0.2.0-from-dynamic-dispatch-to-a-precompiled-pipeline/","title":"HTTP Kernel v0.2.0 – From Dynamic Dispatch to a Pre‑Compiled Pipeline"},{"content":"If you\u0026rsquo;ve ever tried writing systemd timers by hand, you know: it\u0026rsquo;s not exactly fun. Between .service and .timer files, path syntax, and OnCalendar expressions, it\u0026rsquo;s easy to get lost. That’s why I built a small tool: systemd-timer.\nWhat does it do? It’s simple: You call a CLI command, provide a few arguments, and the tool writes the appropriate .service and .timer files for you — with logging, dependencies, and all the systemd details you’d otherwise have to remember. No copy-pasting templates, no typos, no wondering whether it’s WantedBy=timers.target or not.\nWhy not just use cron? Sure, cron works. But systemd has some clear advantages:\nBetter logging (including to file) Easier integration with other services (like waiting for network) Works per-user (no root needed) Everything stays in one ecosystem: systemd This CLI lowers the entry barrier while still encouraging \u0026ldquo;proper\u0026rdquo; systemd usage.\nWhat can systemd-timer do? Generates .service + .timer unit files\nSupports --user timers (great for desktop or containers)\nLogging to file via --logfile\nStandard options like:\n--exec, --calendar, --after, --environment --description, --output, --dry-run CLI built with Cliffy, typed with Deno\nPlatform-independent install via shell script\nA quick example 1 2 3 4 5 6 systemd-timer create \\ --exec \u0026#34;/usr/local/bin/backup.sh\u0026#34; \\ --calendar \u0026#34;Mon..Fri 02:00\u0026#34; \\ --description \u0026#34;Backup Job\u0026#34; \\ --user \\ --logfile \u0026#34;/var/log/backup.log\u0026#34; This creates:\n~/.config/systemd/user/backup.service ~/.config/systemd/user/backup.timer To activate:\n1 2 systemctl --user daemon-reload systemctl --user enable --now backup.timer One-liner install 1 curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh | sh The script auto-detects your architecture (amd64/arm64), fetches the right binary, and verifies it via SHA256. And yes, you can inspect the code before piping it into your shell.\nDevelopment \u0026amp; testing The tool is written entirely in TypeScript with Deno, strictly typed and covered by tests. The core is modular enough that you could use it as a library if needed.\nRun tests with:\n1 deno task test Conclusion systemd-timer isn’t a monster with 1000 options — it’s just what you want when you need a clean way to run small systemd-timed tasks without rewriting the same files over and over. It’s aimed at people who prefer declarative setups over improvisation.\nSource code, releases, and more: 👉 git.0xmax42.io/maxp/systemd-timer\n","date":"2025-05-24T21:30:00Z","image":"https://0xMax42.io/p/systemd-timer-per-cli-erstellen-einfach-schnell-nervenschonend/systemd-timer-cover_hu_47c3e5ed4845fb39.webp","permalink":"https://0xMax42.io/en/p/creating-systemd-timers-via-cli-simple-fast-hassle-free/","title":"Creating systemd Timers via CLI – Simple, Fast, Hassle-Free"},{"content":"I\u0026rsquo;ve released a new open-source project: PyDEPP – a Python library for communicating with Digilent FPGA boards via the DEPP protocol. The goal was to create a modern, object-oriented interface for accessing registers, data streams, and debugging functions, built directly on top of Digilent\u0026rsquo;s Adept SDK.\nWhy this project?\nDigilent\u0026rsquo;s DEPP protocol is powerful, but its native interface is rather clunky. If you\u0026rsquo;ve ever worked with the C APIs, you know it could be more elegant. PyDEPP abstracts this complexity and brings register and stream access to Python-level simplicity – including context management via with, flexible timeout control, and debug dumps.\nHighlights:\nget_reg() / set_reg() – easy register access put_stream() / get_stream() – efficient data transfer Customizable timeouts Context manager for clean resource handling Register dumps for debugging The library is fully open source and MIT licensed. If you\u0026rsquo;re into Python-based hardware communication, this is for you.\nTested with:\nDigilent Nexys2 FPGA board Python 3.11 on Debian Shared libraries: libdmgr.so, libdepp.so from the Adept SDK ➡️ If you\u0026rsquo;re interested in embedded development with Python or want to make better use of the DEPP interface, you\u0026rsquo;re welcome to join in.\n","date":"2025-04-24T09:16:09Z","image":"https://0xMax42.io/p/pydepp-python-trifft-fpga/cover_hu_5cd014ca2f7c0df0.webp","permalink":"https://0xMax42.io/en/p/pydepp-python-meets-fpga/","title":"PyDEPP: Python Meets FPGA"},{"content":"Today I’m focusing on optimizing a small pipeline I created for a project. The pipeline is responsible for calculating the address offset of a sprite and checking whether it\u0026rsquo;s visible in the currently requested line.\nSo far, I’ve relied on register rebalancing to do the job. This does work, but of course, there’s still room for optimization.\nHere’s the current code as a starting point:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; use work.SpriteRom.all; entity VerticalSpritePipeline is generic ( --@ Width of the Y position (Line) register G_Y_Width : integer := 10; --@ The height of the sprite in pixels G_Sprite_Height : integer := 16; --@ Width of the sprite offset (Line address) register G_Offset_Width : integer := 8; --@ The pipeline stages for the calculating pipeline (multiply by 2 for the the latency of the pipeline) G_PipelineStages : integer := 2 ); port ( --@ Clock signal; (**Rising edge** triggered) I_CLK : in std_logic := \u0026#39;0\u0026#39;; --@ Clock enable signal (**Active high**) I_CE : in std_logic := \u0026#39;1\u0026#39;; --@ @virtualbus VSpritePipeline-OP @dir In Vertical sprite pipeline operation interfacee --@ AXI like ready; (**Synchronous**, **Active high**) O_VSpritePipeline_OP_Ready : out std_logic := \u0026#39;0\u0026#39;; --@ AXI like valid; (**Synchronous**, **Active high**) I_VSpritePipeline_OP_Valid : in std_logic := \u0026#39;0\u0026#39;; --@ The line to check if the sprite is in the line visible. I_VSpritePipeline_OP_Y_Request : in std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ The sprite Y position to check if the sprite is in the line visible. I_VSpritePipeline_OP_Y_Sprite : in std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ @end --@ @virtualbus VSpritePipeline-Result @dir Out Vertical sprite pipeline result interface --@ AXI like ready; (**Synchronous**, **Active high**) I_VSpritePipeline_Ready : in std_logic := \u0026#39;0\u0026#39;; --@ AXI like valid; (**Synchronous**, **Active high**) O_VSpritePipeline_Valid : out std_logic := \u0026#39;0\u0026#39;; --@ Indicates if the sprite is visible in the line. O_VSpritePipeline_IsVisible : out std_logic := \u0026#39;0\u0026#39;; --@ The calculated offset address of the sprite. O_VSpritePipeline_Offset : out std_logic_vector(G_Offset_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;) --@ @end ); end entity VerticalSpritePipeline; architecture Rtl of VerticalSpritePipeline is --@ Line to check if the sprite is in the line visible signal R_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ The sprite Y position to check if the sprite is in the line visible signal R_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ Calculated visibility signal signal C_IsVisible : std_logic := \u0026#39;0\u0026#39;; --@ The calculated offset address of the sprite signal C_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ Pipeline enable signal signal S_CalculatingPipeline_Enable : std_logic := \u0026#39;0\u0026#39;; begin --@ Pipeline controller for the calculating pipeline I_CalculatingPipelineCtrl : entity work.PipelineController generic map( G_PipelineStages =\u0026gt; G_PipelineStages * 2 ) port map( I_CLK =\u0026gt; I_CLK, I_CE =\u0026gt; I_CE, O_Enable =\u0026gt; S_CalculatingPipeline_Enable, I_Valid =\u0026gt; I_VSpritePipeline_OP_Valid, O_Ready =\u0026gt; O_VSpritePipeline_OP_Ready, O_Valid =\u0026gt; O_VSpritePipeline_Valid, I_Ready =\u0026gt; I_VSpritePipeline_Ready ); --@ Input register for the Y position of the sprite I_Y_InputRegister : entity work.PipelineRegister generic map( G_PipelineStages =\u0026gt; G_PipelineStages, G_Width =\u0026gt; G_Y_Width, G_RegisterBalancing =\u0026gt; \u0026#34;forward\u0026#34; ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; S_CalculatingPipeline_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Sprite, O_Data =\u0026gt; R_Y_Sprite ); --@ Input register for the line to check if the sprite is in the line visible I_YToCheck_InputRegister : entity work.PipelineRegister generic map( G_PipelineStages =\u0026gt; G_PipelineStages, G_Width =\u0026gt; G_Y_Width, G_RegisterBalancing =\u0026gt; \u0026#34;forward\u0026#34; ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; S_CalculatingPipeline_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Request, O_Data =\u0026gt; R_Y_Request ); --@ Combinatory process to calculate the visibility and offset of the sprite. P_CalculateVisibility : process (R_Y_Sprite, R_Y_Request) variable V_Y_Sprite : unsigned(R_Y_Sprite\u0026#39;range); variable V_Y_Request : unsigned(R_Y_Request\u0026#39;range); variable V_SpriteYBottom : unsigned(R_Y_Sprite\u0026#39;range); variable V_OffsetLine : integer; variable V_Offset : unsigned(C_Offset\u0026#39;range); begin V_Y_Sprite := unsigned(R_Y_Sprite); V_Y_Request := unsigned(R_Y_Request); V_SpriteYBottom := V_Y_Sprite + to_unsigned(G_Sprite_Height - 1, R_Y_Sprite\u0026#39;length); if V_Y_Request \u0026gt;= V_Y_Sprite and V_Y_Request \u0026lt;= V_SpriteYBottom then C_IsVisible \u0026lt;= \u0026#39;1\u0026#39;; else C_IsVisible \u0026lt;= \u0026#39;0\u0026#39;; end if; V_OffsetLine := to_integer(V_Y_Request - V_Y_Sprite); -- pragma translate_off if V_OffsetLine \u0026lt; 0 or V_OffsetLine \u0026gt;= K_SPRITE_ROW_OFFSETS\u0026#39;length then V_OffsetLine := 0; end if; -- pragma translate_on V_Offset := to_unsigned(K_SPRITE_ROW_OFFSETS(V_OffsetLine), C_Offset\u0026#39;length); C_Offset \u0026lt;= std_logic_vector(V_Offset); end process; --@ Output register for the visibility of the sprite I_IsVisible_OutputRegister : entity work.PipelineRegister generic map( G_PipelineStages =\u0026gt; G_PipelineStages, G_Width =\u0026gt; 1, G_RegisterBalancing =\u0026gt; \u0026#34;backward\u0026#34; ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; S_CalculatingPipeline_Enable, I_Data(0) =\u0026gt; C_IsVisible, O_Data(0) =\u0026gt; O_VSpritePipeline_IsVisible ); --@ Output register for the offset of the sprite I_Offset_OutputRegister : entity work.PipelineRegister generic map( G_PipelineStages =\u0026gt; G_PipelineStages, G_Width =\u0026gt; G_Offset_Width, G_RegisterBalancing =\u0026gt; \u0026#34;backward\u0026#34; ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; S_CalculatingPipeline_Enable, I_Data =\u0026gt; C_Offset, O_Data =\u0026gt; O_VSpritePipeline_Offset ); end architecture; As you can see, the splitting of the computation is currently left entirely to the synthesis. Now it’s about finding a performance-optimized breakdown of the logic.\nWithin the calculation process, we find two relevant calculations and one comparison:\nCalculate the bottom edge of the sprite:\n1 V_SpriteYBottom := V_SpriteY + to_unsigned(G_Sprite_Height - 1, R_Y_Sprite\u0026#39;length); Visibility check:\n1 2 if V_YToCheck \u0026gt;= V_SpriteY and V_YToCheck \u0026lt;= V_SpriteYBottom then Calculate the address offset:\n1 V_OffsetLine := to_integer(V_YToCheck - V_SpriteY); Since operations 2 and 3 depend on the result of the first, we\u0026rsquo;ll perform that one in a separate stage.\nTo prepare, we’ll first refactor the registers. We\u0026rsquo;ll rename them and switch from dynamically to statically defined pipeline stages.\nWe modify the pipeline controller as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 INST_VSpritePipeline_Ctrl : entity work.PipelineController generic map( G_PipelineStages =\u0026gt; 3 -- TODO ) port map( I_CLK =\u0026gt; I_CLK, I_CE =\u0026gt; I_CE, O_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Valid =\u0026gt; I_VSpritePipeline_OP_Valid, O_Ready =\u0026gt; O_VSpritePipeline_OP_Ready, O_Valid =\u0026gt; O_VSpritePipeline_Valid, I_Ready =\u0026gt; I_VSpritePipeline_Ready ); We also reduce the input registers to a single stage:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 INST0_VSpritePipeline_Y_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Sprite, O_Data =\u0026gt; R0_Y_Sprite ); INST0_VSpritePipeline_Y_Request : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Request, O_Data =\u0026gt; R0_Y_Request ); We introduce new combinatory and registered signals C_Y_Bottom_Sprite and R_Y_Bottom_Sprite and calculate the first stage using a concurrent signal assignment to avoid embedding this logic in a process:\n1 2 3 4 --@ Calculate the bottom Y position of the sprite C_Y_Bottom_Sprite \u0026lt;= std_logic_vector( unsigned(R_Y_Sprite) + to_unsigned(G_Sprite_Height - 1, G_Y_Width) ); And we need a register for the result:\n1 2 3 4 5 6 7 8 9 10 INST_VSpritePipeline_Y_Bottom_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; C_Y_Bottom_Sprite, O_Data =\u0026gt; R_Y_Bottom_Sprite ) To carry forward base values, we introduce additional registers:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 INST1_VSpritePipeline_Y_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; R0_Y_Sprite, O_Data =\u0026gt; R1_Y_Sprite ); INST1_VSpritePipeline_Y_Request : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; R0_Y_Request, O_Data =\u0026gt; R1_Y_Request ); With the registered signals R1_Y_Sprite, R1_Y_Request, and R_Y_Bottom_Sprite in the next stage, we can now perform operations 2 and 3 in parallel:\n1 2 3 4 5 6 7 8 9 10 11 12 --@ Calculate the visibility of the sprite C_IsVisible \u0026lt;= \u0026#39;1\u0026#39; when ( (unsigned(R1_Y_Request) \u0026gt;= unsigned(R1_Y_Sprite)) and (unsigned(R1_Y_Request) \u0026lt;= unsigned(R_Y_Bottom_Sprite)) ) else \u0026#39;0\u0026#39;; --@ Calculate the offset address of the sprite C_Offset \u0026lt;= std_logic_vector( to_unsigned( K_SPRITE_ROW_OFFSETS(to_integer(unsigned(R1_Y_Request) - unsigned(R1_Y_Sprite))), C_Offset\u0026#39;length) ); Finally, we store these values in the output registers:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 INST_IsVisible_OutputRegister : entity work.PipelineRegister generic map( G_Width =\u0026gt; 1 ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data(0) =\u0026gt; C_IsVisible, O_Data(0) =\u0026gt; O_VSpritePipeline_IsVisible ); INST_Offset_OutputRegister : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Offset_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipelineCtrl_Enable, I_Data =\u0026gt; C_Offset, O_Data =\u0026gt; O_VSpritePipeline_Offset ); The only remaining change is setting G_PipelineStages to 3, which we’ve already done at the top. We can now remove the -- TODO comment.\nThe code now looks like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; use work.SpriteRom.all; entity VerticalSpritePipeline is generic ( --@ Width of the Y position (Line) register G_Y_Width : integer := 10; --@ The height of the sprite in pixels G_Sprite_Height : integer := 16; --@ Width of the sprite offset (Line address) register G_Offset_Width : integer := 8; --@ The pipeline stages for the calculating pipeline (multiply by 2 for the the latency of the pipeline) G_PipelineStages : integer := 2 ); port ( --@ Clock signal; (**Rising edge** triggered) I_CLK : in std_logic := \u0026#39;0\u0026#39;; --@ Clock enable signal (**Active high**) I_CE : in std_logic := \u0026#39;1\u0026#39;; --@ @virtualbus VSpritePipeline-OP @dir In Vertical sprite pipeline operation interfacee --@ AXI like ready; (**Synchronous**, **Active high**) O_VSpritePipeline_OP_Ready : out std_logic := \u0026#39;0\u0026#39;; --@ AXI like valid; (**Synchronous**, **Active high**) I_VSpritePipeline_OP_Valid : in std_logic := \u0026#39;0\u0026#39;; --@ The line to check if the sprite is in the line visible. I_VSpritePipeline_OP_Y_Request : in std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ The sprite Y position to check if the sprite is in the line visible. I_VSpritePipeline_OP_Y_Sprite : in std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ @end --@ @virtualbus VSpritePipeline-Result @dir Out Vertical sprite pipeline result interface --@ AXI like ready; (**Synchronous**, **Active high**) I_VSpritePipeline_Ready : in std_logic := \u0026#39;0\u0026#39;; --@ AXI like valid; (**Synchronous**, **Active high**) O_VSpritePipeline_Valid : out std_logic := \u0026#39;0\u0026#39;; --@ Indicates if the sprite is visible in the line. O_VSpritePipeline_IsVisible : out std_logic := \u0026#39;0\u0026#39;; --@ The calculated offset address of the sprite. O_VSpritePipeline_Offset : out std_logic_vector(G_Offset_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;) --@ @end ); end entity VerticalSpritePipeline; architecture Rtl of VerticalSpritePipeline is --@ Line to check if the sprite is in the line visible signal R0_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); signal R1_Y_Request : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ The sprite Y position to check if the sprite is in the line visible signal R0_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); signal R1_Y_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ The bottom Y position of the sprite signal C_Y_Bottom_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); signal R_Y_Bottom_Sprite : std_logic_vector(G_Y_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ Calculated visibility signal signal C_IsVisible : std_logic := \u0026#39;0\u0026#39;; --@ The calculated offset address of the sprite signal C_Offset : std_logic_vector(G_Offset_Width - 1 downto 0) := (others =\u0026gt; \u0026#39;0\u0026#39;); --@ Pipeline enable signal signal O_VSpritePipeline_Ctrl_Enable : std_logic := \u0026#39;0\u0026#39;; begin INST_VSpritePipeline_Ctrl : entity work.PipelineController generic map( G_PipelineStages =\u0026gt; 3 ) port map( I_CLK =\u0026gt; I_CLK, I_CE =\u0026gt; I_CE, O_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Valid =\u0026gt; I_VSpritePipeline_OP_Valid, O_Ready =\u0026gt; O_VSpritePipeline_OP_Ready, O_Valid =\u0026gt; O_VSpritePipeline_Valid, I_Ready =\u0026gt; I_VSpritePipeline_Ready ); INST0_VSpritePipeline_Y_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Sprite, O_Data =\u0026gt; R0_Y_Sprite ); INST0_VSpritePipeline_Y_Request : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; I_VSpritePipeline_OP_Y_Request, O_Data =\u0026gt; R0_Y_Request ); --@ Calculate the bottom Y position of the sprite C_Y_Bottom_Sprite \u0026lt;= std_logic_vector( unsigned(R0_Y_Sprite) + to_unsigned(G_Sprite_Height - 1, G_Y_Width) ); INST_VSpritePipeline_Y_Bottom_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; C_Y_Bottom_Sprite, O_Data =\u0026gt; R_Y_Bottom_Sprite ); INST1_VSpritePipeline_Y_Sprite : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; R0_Y_Sprite, O_Data =\u0026gt; R1_Y_Sprite ); INST1_VSpritePipeline_Y_Request : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Y_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; R0_Y_Request, O_Data =\u0026gt; R1_Y_Request ); --@ Calculate the visibility of the sprite C_IsVisible \u0026lt;= \u0026#39;1\u0026#39; when ( (unsigned(R1_Y_Request) \u0026gt;= unsigned(R1_Y_Sprite)) and (unsigned(R1_Y_Request) \u0026lt;= unsigned(R_Y_Bottom_Sprite)) ) else \u0026#39;0\u0026#39;; --@ Calculate the offset address of the sprite C_Offset \u0026lt;= std_logic_vector( to_unsigned( K_SPRITE_ROW_OFFSETS(to_integer(unsigned(R1_Y_Request) - unsigned(R1_Y_Sprite))), C_Offset\u0026#39;length) ); INST_IsVisible_OutputRegister : entity work.PipelineRegister generic map( G_Width =\u0026gt; 1 ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data(0) =\u0026gt; C_IsVisible, O_Data(0) =\u0026gt; O_VSpritePipeline_IsVisible ); INST_Offset_OutputRegister : entity work.PipelineRegister generic map( G_Width =\u0026gt; G_Offset_Width ) port map( I_CLK =\u0026gt; I_CLK, I_Enable =\u0026gt; O_VSpritePipeline_Ctrl_Enable, I_Data =\u0026gt; C_Offset, O_Data =\u0026gt; O_VSpritePipeline_Offset ); end architecture; Compared to the previous version, we now have a pre-processing calculation (stage 1) and have parallelized the visibility and offset calculations (stage 2). This should provide a slight performance improvement. In my overall design, the pipeline has gone from being a bottleneck to a timing-irrelevant component.\nDuring synthesis (pre place \u0026amp; route), we achieved a significant improvement on the used Spartan 3:\nBefore: Minimum period: 5.739ns (Max Frequency: 174.246MHz)\nAfter: Minimum period: 4.857ns (Max Frequency: 205.888MHz)\nThus, we’ve successfully accelerated the pipeline and simplified the code.\n","date":"2025-04-22T09:32:40Z","image":"https://0xMax42.io/p/pipeline-optimieren/cover_hu_7691051397864291.webp","permalink":"https://0xMax42.io/en/p/optimizing-the-pipeline/","title":"Optimizing the Pipeline"},{"content":"Created in less than an hour:\nA compact skidbuffer that only decouples the ready signal – with zero added latency.\nThe background here is the use of AXI-like handshaking and the need to decouple the ready signal in order to optimize data processing. In the desisngs I use, the ready signal is often the bottleneck, as it usually has to be propagated through several pipelines. A skid buffer can help here to reduce latency and optimize data processing. In contrast, the ‘valid’ signal is not decoupled as it is decoupled by each pipeline itself.\nTranslated with DeepL.com (free version)\nThe design follows a simple principle:\nIf ready = '1', the signal is passed through directly (MUX = 0) If ready = '0', a buffer is activated (MUX = 1) valid is either forwarded directly or taken from the buffer The system implements complete AXI-like handshaking\nand was successfully tested with randomly delayed upstream and downstream.\nResource usage (after synthesis, Xilinx Spartan-3):\n1 flip-flop 4 LUTs 0 added latency 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 --@ Set mux to buffered mode if data is available in the buffer. C_MUX \u0026lt;= R_IsBuffered; --@ Enable the buffer register if not buffered and chip enable is high. C_Enable \u0026lt;= I_CE and not R_IsBuffered; --@ Set the ready signal to high if not buffered. O_Ready \u0026lt;= not R_IsBuffered; --@ Set the valid signal to high if data is available in the buffer or if data is valid. O_Valid \u0026lt;= R_IsBuffered or I_Valid; process (I_CLK) begin if rising_edge(I_CLK) then if I_RST = G_ResetActiveAt then R_IsBuffered \u0026lt;= \u0026#39;0\u0026#39;; elsif I_CE = \u0026#39;1\u0026#39; then if R_IsBuffered = \u0026#39;0\u0026#39; and I_Valid = \u0026#39;1\u0026#39; then R_IsBuffered \u0026lt;= \u0026#39;1\u0026#39;; elsif I_Ready = \u0026#39;1\u0026#39; and (R_IsBuffered or I_Valid) = \u0026#39;1\u0026#39; then R_IsBuffered \u0026lt;= \u0026#39;0\u0026#39;; end if; end if; end if; end process; This architecture is especially suited for deep pipeline systems with timing bottlenecks on ready.\nNo overhead – just data flow.\n","date":"2025-04-19T19:14:03Z","image":"https://0xMax42.io/p/minimaler-skidbuffer-mit-axi-like-handshaking/cover_hu_dc8e01e57f90c5f.webp","permalink":"https://0xMax42.io/en/p/minimal-skidbuffer-with-axi-like-handshaking/","title":"Minimal Skidbuffer with AXI-like Handshaking"},{"content":"Introduction This post documents my custom Gitea Pages stack – a privacy-friendly alternative to GitHub Pages. With GT-RUNNER, the Codeberg Pages server, and Traefik as a reverse proxy, I’ve built a self-managed hosting stack that fully automates domain management, TLS certificates, and deployment – without centralized dependencies and with complete control.\nArchitecture Overview The following diagram shows the structure of my stack. All core components – Gitea, the runner, the Pages server, and the reverse proxy – are connected within a private network. TLS certificates are issued in two stages using Let\u0026rsquo;s Encrypt (Staging internally, Production externally):\nDetailed Functionality To be honest: at its core, it\u0026rsquo;s just a normal Git server. Gitea runs internally, and the GT-RUNNER is connected – based on act with a fairly large image that provides everything GitHub Actions does. For my use case, that’s more than compatible enough.\nNext is the Codeberg Pages server – also internal. It communicates with Gitea via API, just as intended. Important: everything Gitea, the runner, and the Pages server do happens strictly inside the private network. Nothing leaks outside. That was important to me.\nNow about TLS: the Pages server can\u0026rsquo;t operate without certificates. So it gets them – in the standard way via my DNS provider, through its API and the ACME protocol. But only using Let\u0026rsquo;s Encrypt’s staging certificates. Why? Because that\u0026rsquo;s enough. This is about internal TLS, not external trust.\nOnce the Pages server has its certs, it can serve content cleanly – internally, over HTTPS, with a proper structure. My reverse proxy (Traefik) takes care of the rest.\nTraefik is configured to not care whether the internal cert is trusted. It terminates TLS on the public side, fetches valid production certificates via DNS-01 and Let\u0026rsquo;s Encrypt, and rewrites the domain paths accordingly.\nTo make this work, I rewrite the requests. For example, Traefik receives something like home.pagessub.0xmax42.io, extracts which repository it’s supposed to serve, and rewrites the request internally to user.pagessub.0xmax42.io/repo/. This lets the Pages server route the request correctly.\nFrom the outside, everything looks just like GitHub Pages – every subdomain serves its own static project. But this time: privacy-friendly, self-hosted, and with no vendor lock-in.\nOutlook In the long term, I want repositories to define their own deployment domains – e.g. via a .traffic file. The Gitea API could then automatically generate the corresponding Traefik config and hand it off. The goal: a fully automated, self-healing static web hosting stack.\nComponents Used 🧃 Gitea – Self-hosted Git service 🌀 Traefik – Reverse proxy with ACME support 📦 Codeberg Pages Server – Static pages server 🔀 traefik-subdomain-path-rewrite-plugin – For dynamic URL rewriting based on subdomains 🔐 Let’s Encrypt – Free TLS certificate management via ACME 🐳 Docker – Containerization of all components for easy management and isolation 📁 This setup is part of my infrastructure foundation for 0xMax42.io. More posts to follow.\n","date":"2025-04-19T00:00:00Z","image":"https://0xMax42.io/p/gitea-pages-stack/cover_hu_6b67d33469f93b45.webp","permalink":"https://0xMax42.io/en/p/gitea-pages-stack/","title":"Custom Gitea Pages Stack with Wildcard Subdomains and ACME – My GitHub Pages Replacement"},{"content":"I\u0026rsquo;m back – with a new name, new clarity, and a new digital home.\nFrom now on, you\u0026rsquo;ll find me here as 0xMax42.\nThis site is more than just a blog. It’s my space for ideas, systems, thoughts – and sometimes resistance.\nEverything you see here comes directly from me – independent, transparent, and without detours.\nWelcome to 0xMax42.\n","date":"2025-04-17T00:00:00Z","image":"https://0xMax42.io/p/ich-bin-zurueck/cover_hu_46c3152cd99b6df6.webp","permalink":"https://0xMax42.io/en/p/im-back/","title":"I'm Back!"}]