<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pratham Srivastava</title>
    <description>The latest articles on DEV Community by Pratham Srivastava (@pratware).</description>
    <link>https://dev.to/pratware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4237794%2F1b96bdff-cf1e-4e41-b2ad-b55732201902.jpg</url>
      <title>DEV Community: Pratham Srivastava</title>
      <link>https://dev.to/pratware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pratware"/>
    <language>en</language>
    <item>
      <title>Building a Signed MAC Unit on the Shrike Lite FPGA: A Beginner's Guide</title>
      <dc:creator>Pratham Srivastava</dc:creator>
      <pubDate>Tue, 10 Nov 2026 20:19:57 +0000</pubDate>
      <link>https://dev.to/pratware/building-a-signed-mac-unit-on-the-shrike-lite-fpga-a-beginners-guide-1l9</link>
      <guid>https://dev.to/pratware/building-a-signed-mac-unit-on-the-shrike-lite-fpga-a-beginners-guide-1l9</guid>
      <description>&lt;p&gt;I built a working signed multiply-accumulate (MAC) unit on a low-cost FPGA board, ran it on real hardware, and got &lt;code&gt;RESULT: PASS&lt;/code&gt; on the first board run. This post shows every step, from an empty Windows PC to numbers coming back from the FPGA, written for someone who has never touched an FPGA tool before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a MAC, and why build one?
&lt;/h2&gt;

&lt;p&gt;A MAC does one tiny job over and over: &lt;strong&gt;multiply two numbers and add the result to a running total&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acc = acc + (a × b)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line is the heart of almost every digital filter, every audio effect and every neural-network layer. A dot product is just a MAC repeated: &lt;code&gt;1×4 + (−2)×5 + 3×(−6)&lt;/code&gt; is three MACs in a row. Phones and AI chips contain thousands of MAC units for exactly this reason.&lt;/p&gt;

&lt;p&gt;Building one yourself is the perfect first "real" FPGA project. It is small enough to finish in an evening, but it teaches the things every FPGA design needs: signed numbers, overflow, clocks, pipelining, talking to a microcontroller, pin mapping and checking timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will end up with
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An FPGA that keeps a 32-bit running total and multiplies two signed 8-bit numbers on every request.&lt;/li&gt;
&lt;li&gt;A MicroPython program on the board's RP2040 that loads the FPGA, sends numbers and prints the result.&lt;/li&gt;
&lt;li&gt;No extra wires: everything uses connections already on the Shrike Lite board.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Beginner (first or second FPGA project)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;About 3–4 hours the first time, including software installs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One Vicharak Shrike Lite board and a USB-C data cable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;You should know&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic Verilog (&lt;code&gt;always&lt;/code&gt;, &lt;code&gt;assign&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;) and basic Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;You do not need&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any previous FPGA tool experience, a JTAG programmer, or jumper wires&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Project specifications
&lt;/h2&gt;

&lt;p&gt;These are the targets I set before writing a single line of code. Every one of them was met and tested on the board.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Specification&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inputs &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Signed 8-bit, two's complement, −128 to 127&lt;/td&gt;
&lt;td&gt;One byte each, so each fits in one SPI byte; the same range as int8 in small neural networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Signed 16-bit, exact&lt;/td&gt;
&lt;td&gt;The extremes are (−128)×(−128) = +16 384 and −128×127 = −16 256, both fit in 16 bits, so nothing is ever rounded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accumulator&lt;/td&gt;
&lt;td&gt;Signed 32-bit&lt;/td&gt;
&lt;td&gt;Needs at least 131 072 worst-case MACs before it can overflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overflow&lt;/td&gt;
&lt;td&gt;Wrap around + a sticky OVF flag&lt;/td&gt;
&lt;td&gt;The value wraps like normal two's-complement hardware, and a flag stays on so the error is never hidden&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commands&lt;/td&gt;
&lt;td&gt;CLEAR, MAC, READ&lt;/td&gt;
&lt;td&gt;Start a new total, add one product, read the total back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initialisation&lt;/td&gt;
&lt;td&gt;CLEAR is mandatory before the first MAC&lt;/td&gt;
&lt;td&gt;A MAC sent before CLEAR is refused, so you never add to a garbage starting value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bad inputs&lt;/td&gt;
&lt;td&gt;Rejected, never wrapped&lt;/td&gt;
&lt;td&gt;128 must not silently become −128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;SPI over the board's built-in RP2040–FPGA wires&lt;/td&gt;
&lt;td&gt;No jumper wires needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clock&lt;/td&gt;
&lt;td&gt;50 MHz on-chip oscillator&lt;/td&gt;
&lt;td&gt;No external crystal needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why I needed this (and why you might too)
&lt;/h2&gt;

&lt;p&gt;I wanted a first project that was more than blinking an LED but still small enough to understand completely. A MAC hits the sweet spot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is useful.&lt;/strong&gt; It is the building block of DSP and machine-learning accelerators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It exercises both chips.&lt;/strong&gt; The FPGA does the hardware maths; the microcontroller does everything "software".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It forces good habits.&lt;/strong&gt; You have to think about signed numbers, overflow, clocks crossing between chips, and how to prove the design is correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Difficulty for a beginner
&lt;/h2&gt;

&lt;p&gt;I would rate this &lt;strong&gt;2 out of 5&lt;/strong&gt; if you follow this guide step by step.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Difficulty&lt;/th&gt;
&lt;th&gt;What makes it tricky&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Installing the software&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;A Renesas account is needed for the FPGA tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Understanding the Verilog&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Signed maths, synchronisers and edge detection are new ideas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go Configure (FPGA tool)&lt;/td&gt;
&lt;td&gt;Easy–medium&lt;/td&gt;
&lt;td&gt;The screens differ slightly from older tutorials; pin mapping needs care&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MicroPython side&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Copy four files and press Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging if it fails&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;The guide's status codes make most problems easy to spot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Meet the board: Vicharak Shrike Lite
&lt;/h2&gt;

&lt;p&gt;The Shrike Lite puts two chips on one small board: a &lt;strong&gt;Raspberry Pi RP2040&lt;/strong&gt; microcontroller and a &lt;strong&gt;Renesas ForgeFPGA SLG47910V&lt;/strong&gt;. The RP2040 runs MicroPython and loads the FPGA; the FPGA runs whatever hardware you design in Verilog.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FPGA&lt;/td&gt;
&lt;td&gt;Renesas SLG47910V (Rev BB), 1120 LUTs, 1120 flip-flops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPGA memory&lt;/td&gt;
&lt;td&gt;32 kb block RAM, 5 kb distributed RAM (not used here)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPGA clock&lt;/td&gt;
&lt;td&gt;Built-in 50 MHz oscillator, plus one PLL (not used here)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard multiplier / DSP block&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; The multiplier is built from ordinary logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microcontroller&lt;/td&gt;
&lt;td&gt;RP2040, dual Cortex-M0+, MicroPython&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;4 MB QSPI flash (holds MicroPython files and the FPGA bitstream)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I/O voltage&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3.3 V only.&lt;/strong&gt; Never connect 5 V signals to any pin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power&lt;/td&gt;
&lt;td&gt;USB-C or the 5 V header, never both&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two facts shaped the whole design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;There is no DSP block.&lt;/strong&gt; Big FPGAs have hard multiplier blocks; this one does not, so the 8×8 multiplier must be built from LUTs. It still fits easily (32 % of the chip, as you will see).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The FPGA forgets its design at power-off.&lt;/strong&gt; The RP2040 sends the design (the &lt;em&gt;bitstream&lt;/em&gt;) into the FPGA every time the board starts. The chip also has one-time-programmable memory, but this project never touches it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The built-in RP2040 ↔ FPGA link
&lt;/h3&gt;

&lt;p&gt;The board already wires several RP2040 pins to FPGA pins. Four of them are used first to load the bitstream, and then become a normal SPI bus for your design. That is why this project needs no jumper wires.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RP2040 pin&lt;/th&gt;
&lt;th&gt;FPGA pin&lt;/th&gt;
&lt;th&gt;While loading&lt;/th&gt;
&lt;th&gt;In this project&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPIO13 / GPIO12&lt;/td&gt;
&lt;td&gt;EN / PWR&lt;/td&gt;
&lt;td&gt;Turn the FPGA on&lt;/td&gt;
&lt;td&gt;Used only by the loader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIO2&lt;/td&gt;
&lt;td&gt;GPIO3&lt;/td&gt;
&lt;td&gt;Config clock&lt;/td&gt;
&lt;td&gt;SPI clock (SCK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIO1&lt;/td&gt;
&lt;td&gt;GPIO4&lt;/td&gt;
&lt;td&gt;Config select&lt;/td&gt;
&lt;td&gt;SPI chip select (CS, active low)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIO3&lt;/td&gt;
&lt;td&gt;GPIO5&lt;/td&gt;
&lt;td&gt;Config data in&lt;/td&gt;
&lt;td&gt;MOSI (RP2040 → FPGA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIO0&lt;/td&gt;
&lt;td&gt;GPIO6&lt;/td&gt;
&lt;td&gt;Config data out&lt;/td&gt;
&lt;td&gt;MISO (FPGA → RP2040)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIO14&lt;/td&gt;
&lt;td&gt;GPIO18&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Reset for our logic (active low)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;GPIO16&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;FPGA user LED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1p6afala73wcz3z76imn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1p6afala73wcz3z76imn.png" alt="Shrike Lite RP2040 to FPGA wiring used in this project" width="800" height="420"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Shrike Lite RP2040 to FPGA wiring used in this project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The four highlighted wires do double duty: the RP2040 uses them to load the bitstream, then the same wires carry every CLEAR, MAC and READ.&lt;/p&gt;
&lt;h2&gt;
  
  
  Design choices, explained simply
&lt;/h2&gt;

&lt;p&gt;The RP2040 is the &lt;strong&gt;SPI master&lt;/strong&gt;: it drives the clock and decides when to talk. The FPGA is the &lt;strong&gt;SPI target&lt;/strong&gt; (slave): it only answers. Every message is one &lt;em&gt;frame&lt;/em&gt;, which is everything sent between chip-select going low and going high.&lt;/p&gt;
&lt;h3&gt;
  
  
  The command language
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;RP2040 sends&lt;/th&gt;
&lt;th&gt;FPGA replies&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLEAR&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total = 0, all flags cleared, INIT = 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;02 aa bb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A5 00 00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total = total + aa × bb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ&lt;/td&gt;
&lt;td&gt;&lt;code&gt;03 00 00 00 00 00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A5 A3 A2 A1 A0 ST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Total (4 bytes, most significant first) + status byte&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SPI settings: &lt;strong&gt;mode 0&lt;/strong&gt; (clock idles low, both sides sample on the rising edge), &lt;strong&gt;MSB first&lt;/strong&gt;, 8-bit bytes, &lt;strong&gt;1 MHz&lt;/strong&gt; clock.&lt;/p&gt;

&lt;p&gt;Three small tricks make the link robust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every reply starts with &lt;code&gt;0xA5&lt;/code&gt;&lt;/strong&gt; (binary &lt;code&gt;1010 0101&lt;/code&gt;). A broken wire reads &lt;code&gt;0x00&lt;/code&gt; or &lt;code&gt;0xFF&lt;/code&gt;, never &lt;code&gt;0xA5&lt;/code&gt;, so the RP2040 knows straight away whether the FPGA is alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SPI is full duplex.&lt;/strong&gt; The RP2040 receives one byte for every byte it sends, so READ sends five dummy &lt;code&gt;00&lt;/code&gt; bytes to give the FPGA room to reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A command runs only when the frame ends, and only if its length is exactly right.&lt;/strong&gt; A broken message can never half-change the total; it is thrown away and an error flag lights up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The status byte
&lt;/h3&gt;

&lt;p&gt;The last byte of every READ is a status code built inside the FPGA. Each bit is one warning light.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bit&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;INIT&lt;/td&gt;
&lt;td&gt;The total has been cleared and is ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;OVF&lt;/td&gt;
&lt;td&gt;The total went past the 32-bit limit and wrapped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;ERR_UNINIT&lt;/td&gt;
&lt;td&gt;A MAC was sent before CLEAR (ignored)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ERR_FRAME&lt;/td&gt;
&lt;td&gt;A frame had the wrong length (ignored)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ERR_CMD&lt;/td&gt;
&lt;td&gt;Unknown command byte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7–5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;010&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Constant signature, proves the FPGA is answering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So &lt;strong&gt;65 (0x41) means "cleared, no errors"&lt;/strong&gt; and &lt;strong&gt;64 (0x40) means "just reset, call CLEAR first"&lt;/strong&gt;. The FPGA's user LED turns on only when INIT is set and every error light is off.&lt;/p&gt;
&lt;h3&gt;
  
  
  The clear-first rule
&lt;/h3&gt;

&lt;p&gt;The total must be cleared before any arithmetic. I enforce this twice: the Python driver refuses to send a MAC until CLEAR has succeeded, and the FPGA itself refuses a MAC (and sets ERR_UNINIT) if it has not been cleared since reset. Out-of-range inputs like 128 or −129 are rejected in Python before anything is sent.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the FPGA design works
&lt;/h2&gt;

&lt;p&gt;The Verilog is split into three files, each with one job.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_byte_target.v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns the wiggling SPI wires into whole bytes, and reply bytes back into wiggles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;top.v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Decodes commands, checks frame length, keeps the status flags and drives the LED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mac_core.v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiplies, adds, keeps the 32-bit total and detects overflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bkvo8t7wbc4nv0ijaih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bkvo8t7wbc4nv0ijaih.png" alt="Inside the FPGA: three blocks, left to right" width="800" height="349"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Inside the FPGA: three blocks, left to right&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bytes flow left to right; replies and the running total flow back. Nothing reaches the MAC core until the decoder has seen a complete, correctly sized frame.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Listening safely: synchronisers and edge detection
&lt;/h3&gt;

&lt;p&gt;The RP2040's SPI clock and the FPGA's 50 MHz clock are unrelated. If SCK changes exactly on an FPGA clock edge, a flip-flop can become &lt;em&gt;metastable&lt;/em&gt; (stuck between 0 and 1 for a moment). The cure is to pass every incoming signal through a short chain of flip-flops, then compare the last two clean samples to find edges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight verilog"&gt;&lt;code&gt;&lt;span class="c1"&gt;// three samples of SCK; a new one enters on every 50 MHz edge&lt;/span&gt;
&lt;span class="n"&gt;always&lt;/span&gt; @&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posedge&lt;/span&gt; &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;negedge&lt;/span&gt; &lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sck_s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s1"&gt;'b000;
    else        sck_s &amp;lt;= {sck_s[1:0], spi_sck};

wire sck_rise = ~sck_s[2] &amp;amp;  sck_s[1];   // was 0, now 1
wire sck_fall =  sck_s[2] &amp;amp; ~sck_s[1];   // was 1, now 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On every &lt;code&gt;sck_rise&lt;/code&gt; one MOSI bit is shifted in; after eight bits a full byte is announced. On every &lt;code&gt;sck_fall&lt;/code&gt; the reply register shifts left so the next bit sits on MISO. The FPGA drives MISO only while CS is low, which is why the pin needs an output-enable (&lt;code&gt;spi_miso_en&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deciding: the command decoder
&lt;/h3&gt;

&lt;p&gt;The decoder in &lt;code&gt;top.v&lt;/code&gt; waits for chip-select to go high, then checks the byte count before doing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight verilog"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame_end&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;byte_count&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s1"&gt;'d0) begin
    case (cmd)
        CMD_MAC:
            if (byte_count != 3'&lt;/span&gt;&lt;span class="n"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;err_frame&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s1"&gt;'b1;  // wrong length
            else if (!init)         err_uninit &amp;lt;= 1'&lt;/span&gt;&lt;span class="n"&gt;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// not cleared yet&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;                    &lt;span class="n"&gt;mac_p&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s1"&gt;'b1;  // go: one-cycle pulse
        // CLEAR and READ are handled the same way
    endcase
end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status byte is simply the flags packed together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight verilog"&gt;&lt;code&gt;&lt;span class="n"&gt;wire&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s1"&gt;'b010, err_cmd, err_frame, err_uninit, ovf, init};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Calculating: a two-stage pipeline
&lt;/h3&gt;

&lt;p&gt;Multiplying and adding in a single clock makes one long chain of logic. Splitting it over two clock edges keeps each step short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight verilog"&gt;&lt;code&gt;&lt;span class="n"&gt;wire&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;prod_ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// sign-extend 16 → 32 bits&lt;/span&gt;
&lt;span class="n"&gt;wire&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;prod_ext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;wire&lt;/span&gt;        &lt;span class="n"&gt;sum_ovf&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;prod_ext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;always&lt;/span&gt; @&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posedge&lt;/span&gt; &lt;span class="n"&gt;clk&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;negedge&lt;/span&gt; &lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac_go&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;prod&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; $&lt;span class="kt"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; $&lt;span class="kt"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// edge 1: multiply&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prod_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;begin&lt;/span&gt;                              &lt;span class="c1"&gt;// edge 2: add&lt;/span&gt;
        &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum_ovf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ovf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s1"&gt;'b1;                  // sticky until CLEAR
    end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sign extension&lt;/strong&gt; matters: −30 in 16 bits is &lt;code&gt;FFE2&lt;/code&gt;. Copying the sign bit into the upper half gives &lt;code&gt;FFFF FFE2&lt;/code&gt;, still −30; padding with zeros would give +65 506.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overflow rule:&lt;/strong&gt; if you add two numbers with the &lt;em&gt;same&lt;/em&gt; sign and the result has the &lt;em&gt;opposite&lt;/em&gt; sign, the true answer did not fit. For example, 2 147 483 647 + 1 wraps to −2 147 483 648 and OVF turns on.&lt;/p&gt;

&lt;h3&gt;
  
  
  One MAC from start to finish
&lt;/h3&gt;

&lt;p&gt;Here is what happens when Python calls &lt;code&gt;mac.mac(10, -3)&lt;/code&gt; with the total at 0:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Checks both numbers are in −128…127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Encodes the frame &lt;code&gt;02 0A FD&lt;/code&gt; (−3 is &lt;code&gt;0xFD&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SPI&lt;/td&gt;
&lt;td&gt;24 bits go out at 1 MHz; &lt;code&gt;A5 00 00&lt;/code&gt; comes back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;FPGA&lt;/td&gt;
&lt;td&gt;Frame ends, length is 3, INIT is set → go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;FPGA, edge 1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prod = 10 × −3 = −30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;FPGA, edge 2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acc = 0 + (−30) = −30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mac.read()&lt;/code&gt; returns &lt;code&gt;(-30, 65)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Steps 4–6 take about 100 ns. The whole Python call takes about 0.8 ms, so almost all the time is MicroPython, not the FPGA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software you need (Windows)
&lt;/h2&gt;

&lt;p&gt;Install these in order. Everything is free.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Thonny&lt;/strong&gt;, the MicroPython editor that talks to the RP2040. Download the Windows installer from &lt;a href="https://thonny.org/" rel="noopener noreferrer"&gt;thonny.org&lt;/a&gt; and install with the defaults.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go Configure Software Hub&lt;/strong&gt;, Renesas' FPGA tool (I used v6.56). Get it from the &lt;a href="https://www.renesas.com/en/software-tool/go-configure-software-hub" rel="noopener noreferrer"&gt;Renesas Go Configure page&lt;/a&gt;. You need a free Renesas account: register, confirm the email, log in, then download and install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shrike Lite MicroPython firmware.&lt;/strong&gt; From the &lt;a href="https://github.com/vicharak-in/shrike/releases" rel="noopener noreferrer"&gt;Shrike releases page&lt;/a&gt;, download &lt;code&gt;shrike-lite-micropython.uf2&lt;/code&gt;. Do not take the Shrike (RP2350) or Shrike-fi files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A test bitstream&lt;/strong&gt; to check the board, from Vicharak's &lt;a href="https://github.com/vicharak-in/shrike/tree/main/test/bitstreams" rel="noopener noreferrer"&gt;test bitstreams folder&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Put MicroPython on the board (once)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Unplug the board. Hold the &lt;strong&gt;BOOT&lt;/strong&gt; button, plug in USB-C, then release BOOT.&lt;/li&gt;
&lt;li&gt;A drive called &lt;code&gt;RPI-RP2&lt;/code&gt; appears. Drag &lt;code&gt;shrike-lite-micropython.uf2&lt;/code&gt; onto it.&lt;/li&gt;
&lt;li&gt;The drive disappears after a few seconds. The board has rebooted into MicroPython.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Connect Thonny
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open Thonny and click the interpreter name in the bottom-right corner.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;MicroPython (Raspberry Pi Pico)&lt;/strong&gt; on the board's COM port. Do not pick "Local Python 3".&lt;/li&gt;
&lt;li&gt;The Shell shows &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;. Type &lt;code&gt;import shrike&lt;/code&gt;: no reply means the Shrike firmware is installed.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;View → Files&lt;/strong&gt; to see your PC's files (top) and the board's files (bottom).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bpangf1ybe5lt4qxq7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bpangf1ybe5lt4qxq7x.png" alt="Pick MicroPython (Raspberry Pi Pico) on the board's COM port, not Local Python 3" width="456" height="168"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pick MicroPython (Raspberry Pi Pico) on the board's COM port, not Local Python 3&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Check the board before building anything
&lt;/h3&gt;

&lt;p&gt;Upload the test bitstream to the board (right-click → &lt;strong&gt;Upload to /&lt;/strong&gt;) and run, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shrike&lt;/span&gt;
&lt;span class="n"&gt;shrike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"blink_all.bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the blue FPGA LED blinks, the board, cable and Thonny all work. Fix any problem here before moving on; it saves a lot of confusion later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fysro9ff4t3p05l6wr78z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fysro9ff4t3p05l6wr78z.png" alt="My first board check. The first attempt failed with ENOENT because that file wasn't on the board; blink_all.bin was" width="800" height="822"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My first board check. The first attempt failed with ENOENT because that file wasn't on the board; blink_all.bin was&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Building the FPGA bitstream in Go Configure
&lt;/h2&gt;

&lt;p&gt;The tool turns Verilog into a &lt;em&gt;bitstream&lt;/em&gt;: the file that tells the FPGA how to wire itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verilog → Synthesize → I/O Planner → Place &amp;amp; Route → bitstream (.bin)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Create the project
&lt;/h3&gt;

&lt;p&gt;On the Go Configure home screen, click &lt;strong&gt;Start new project&lt;/strong&gt;, search for &lt;strong&gt;SLG47910&lt;/strong&gt; and pick &lt;strong&gt;SLG47910V (Rev BB)&lt;/strong&gt;, the chip on the Shrike Lite. Name the project &lt;code&gt;mac_shrike&lt;/code&gt; and save it in a folder without spaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvp8mqhc5wa74268br6pl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvp8mqhc5wa74268br6pl.png" alt="Go Configure v6.56 home screen. Newer versions use &amp;quot;Start new project&amp;quot; instead of a Develop tab" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Go Configure v6.56 home screen. Newer versions use "Start new project" instead of a Develop tab&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pyu7mxtxnctshpunh2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pyu7mxtxnctshpunh2u.png" alt="Pick SLG47910V (Rev BB), not SLG47910C" width="493" height="460"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pick SLG47910V (Rev BB), not SLG47910C&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Project settings
&lt;/h3&gt;

&lt;p&gt;A settings window opens. On the &lt;strong&gt;Specs&lt;/strong&gt; tab I used the same operating conditions as Vicharak's own examples: VDDC 1.05 / 1.1 / 1.15 V, VDDIO 1.71 / 2.5 / 3.465 V, temperature −40 / 25 / 85 °C. These only tell the timing checker which conditions to assume; they do not change the board.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9g9vv1abqwetuwtwj2s1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9g9vv1abqwetuwtwj2s1.png" alt="The Specs tab, before entering the operating conditions" width="800" height="424"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Specs tab, before entering the operating conditions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;Boot config&lt;/strong&gt; tab, leave &lt;strong&gt;Force Load from OTP = Disable&lt;/strong&gt; and &lt;strong&gt;Lock status = Unlocked&lt;/strong&gt;. OTP is one-time memory: turning that option on can stop the RP2040 from loading designs over SPI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5v2zggqgiyxehwd78tl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5v2zggqgiyxehwd78tl.png" alt="Keep these defaults: OTP is one-time memory" width="556" height="507"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Keep these defaults: OTP is one-time memory&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Paste the Verilog
&lt;/h3&gt;

&lt;p&gt;The chip view opens. Leave the Properties panel at its defaults and click &lt;strong&gt;FPGA Editor&lt;/strong&gt; on the toolbar. Open &lt;code&gt;main.v&lt;/code&gt;, select everything, and paste the whole project (all three modules in one file, the top module marked &lt;code&gt;(* top *)&lt;/code&gt;). Click &lt;strong&gt;Save All&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2cwjvjqbki9aa8h271xv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2cwjvjqbki9aa8h271xv.png" alt="The chip view: the big blue square is the FPGA core; the yellow boxes are the pins" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The chip view: the big blue square is the FPGA core; the yellow boxes are the pins&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptukfnn6lnjbyug38w99.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptukfnn6lnjbyug38w99.png" alt="The Verilog pasted into main.v, ready to synthesize" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Verilog pasted into main.v, ready to synthesize&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Synthesize
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Synthesize&lt;/strong&gt; (bottom-left) and wait for the green tick. The log should end with &lt;em&gt;0 problems&lt;/em&gt;. Two lines worth spotting: the multiplier was treated as &lt;strong&gt;signed&lt;/strong&gt;, and no DSP block was used.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Map the pins in the I/O Planner
&lt;/h3&gt;

&lt;p&gt;Open the &lt;strong&gt;I/O Planner&lt;/strong&gt; tab. Each row is a pin function on the chip; double-click the &lt;strong&gt;PORT&lt;/strong&gt; cell to choose your signal. Watch the &lt;code&gt;_IN&lt;/code&gt; / &lt;code&gt;_OUT&lt;/code&gt; / &lt;code&gt;_OE&lt;/code&gt; endings carefully.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal in &lt;code&gt;top.v&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Pin function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OSC_CLK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clk_en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OSC_EN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_sck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO3_IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_ss_n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO4_IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_mosi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO5_IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_miso&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO6_OUT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spi_miso_en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO6_OE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rst_n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO18_IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;led&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO16_OUT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;led_en&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GPIO16_OE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The clock rows appear under the &lt;strong&gt;CLK&lt;/strong&gt; and &lt;strong&gt;OSC_ctrl&lt;/strong&gt; filters; the rest under &lt;strong&gt;GPIO&lt;/strong&gt;. Every FPGA output needs an output-enable here, which is why &lt;code&gt;led_en&lt;/code&gt; and &lt;code&gt;spi_miso_en&lt;/code&gt; exist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83uggeltjg284gdvd8ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83uggeltjg284gdvd8ko.png" alt="The I/O Planner. Double-click a PORT cell to assign a signal" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The I/O Planner. Double-click a PORT cell to assign a signal&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: Generate the bitstream and read the reports
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;Save All&lt;/strong&gt;, then &lt;strong&gt;Generate Bitstream&lt;/strong&gt;. Place-and-route finished with 0 errors and 4 warnings, all harmless for this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"not packed with an IOB FF"&lt;/em&gt; for &lt;code&gt;spi_miso_en&lt;/code&gt;, &lt;code&gt;rst_n&lt;/code&gt; and &lt;code&gt;led&lt;/code&gt;: these signals change at 1 MHz or slower, so a flip-flop right at the pin is not needed.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Clock clk directly fed by port without CLKBUF"&lt;/em&gt;: expected, because the code uses the same &lt;code&gt;clkbuf_inhibit&lt;/code&gt; attribute as Vicharak's examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4lv8rljxonoiw4fmxrgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4lv8rljxonoiw4fmxrgx.png" alt="0 errors; the 4 warnings are harmless for this design" width="800" height="165"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;0 errors; the 4 warnings are harmless for this design&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My resource report:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Used&lt;/th&gt;
&lt;th&gt;Available&lt;/th&gt;
&lt;th&gt;%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LUT5s&lt;/td&gt;
&lt;td&gt;362&lt;/td&gt;
&lt;td&gt;1120&lt;/td&gt;
&lt;td&gt;32 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flip-flops&lt;/td&gt;
&lt;td&gt;163&lt;/td&gt;
&lt;td&gt;1120&lt;/td&gt;
&lt;td&gt;14 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logic blocks (CLBs)&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;td&gt;46 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPIOs&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;32 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PLL / BRAM&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9inbcbuh8vxunvwpojkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9inbcbuh8vxunvwpojkq.png" alt="The real resource report from Go Configure" width="343" height="407"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The real resource report from Go Configure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Timing Analysis&lt;/strong&gt;, ignore the scary negative slack: with no clock constraint the tool tests against 500 MHz. The number that matters is &lt;strong&gt;Achievable Frequency: 68.6 MHz&lt;/strong&gt;, comfortably above the 50 MHz the oscillator actually runs at.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Find the file for the board
&lt;/h3&gt;

&lt;p&gt;In your project folder, open &lt;code&gt;ffpga\build\bitstream\&lt;/code&gt;, copy &lt;strong&gt;&lt;code&gt;FPGA_bitstream_MCU.bin&lt;/code&gt;&lt;/strong&gt; (only that one), and rename the copy to &lt;code&gt;mac_shrike.bin&lt;/code&gt;. If Windows hides file extensions, rename it to just &lt;code&gt;mac_shrike&lt;/code&gt; so you don't end up with &lt;code&gt;mac_shrike.bin.bin&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it on the board
&lt;/h2&gt;

&lt;p&gt;The RP2040 side is three small Python files, each with one job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mac_protocol.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command bytes, range checks, decoding, and a Python reference model of the MAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shrike_mac.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;ShrikeMac&lt;/code&gt; driver class: &lt;code&gt;clear()&lt;/code&gt;, &lt;code&gt;mac(a, b)&lt;/code&gt;, &lt;code&gt;read()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The demo: loads the FPGA, clears, tests, prints PASS or FAIL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;mac_protocol.py&lt;/code&gt; is pure Python, so the same logic can be unit-tested on a PC without the board. The driver uses SPI0 at 1 MHz, mode 0, on the pins from the table above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baudrate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;firstbit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MSB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mosi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;miso&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upload and run
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In Thonny's top (PC) panel, open the folder with your four files: &lt;code&gt;mac_shrike.bin&lt;/code&gt;, &lt;code&gt;mac_protocol.py&lt;/code&gt;, &lt;code&gt;shrike_mac.py&lt;/code&gt;, &lt;code&gt;main.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Right-click each one → &lt;strong&gt;Upload to /&lt;/strong&gt;. Say yes to overwriting the board's old &lt;code&gt;main.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click the red &lt;strong&gt;STOP&lt;/strong&gt; button once, double-click &lt;code&gt;main.py&lt;/code&gt; in the board panel, and press &lt;strong&gt;F5&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt; calls &lt;code&gt;shrike.flash("mac_shrike.bin")&lt;/code&gt;, pulses the FPGA reset on GPIO14, checks the status is &lt;code&gt;0x40&lt;/code&gt;, sends CLEAR, then runs the tests. Because it is named &lt;code&gt;main.py&lt;/code&gt;, it also runs every time the board powers up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The result on my board
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Loading mac_shrike.bin into the FPGA ...
[shrike_fpga] flashing: mac_shrike.bin
[shrike_flash] FPGA programming done.
After reset : acc = 0  status = 0x40 [-]
After CLEAR : acc = 0  status = 0x41 [INIT]
Rejected (128, 1) -&amp;gt; a=128 is outside signed 8-bit range -128..127
Rejected (-129, 1) -&amp;gt; a=-129 is outside signed 8-bit range -128..127
Rejected (1, 300) -&amp;gt; b=300 is outside signed 8-bit range -128..127
Rejected (True, 2) -&amp;gt; a must be an int, got True
Rejected (1.5, 2) -&amp;gt; a must be an int, got 1.5
 -128 *  -128  -&amp;gt; acc =    16384  (expected    16384) ok
  127 *   127  -&amp;gt; acc =    32513  (expected    32513) ok
 -128 *   127  -&amp;gt; acc =    16257  (expected    16257) ok
  127 *  -128  -&amp;gt; acc =        1  (expected        1) ok
   -1 *    -1  -&amp;gt; acc =        2  (expected        2) ok
    0 *  -128  -&amp;gt; acc =        2  (expected        2) ok
   64 *    -2  -&amp;gt; acc =     -126  (expected     -126) ok
 -100 *   100  -&amp;gt; acc =   -10126  (expected   -10126) ok
    3 *     5  -&amp;gt; acc =   -10111  (expected   -10111) ok
   -7 *     9  -&amp;gt; acc =   -10174  (expected   -10174) ok
1000 random MACs in 806 ms: acc = 270106, expected 270106, status 0x41 [INIT]
RESULT: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blue FPGA LED stays on, meaning "cleared, no errors".&lt;/p&gt;

&lt;p&gt;One thing confused me at first: &lt;code&gt;127 * -128 -&amp;gt; acc = 1&lt;/code&gt; looks wrong. It isn't. &lt;code&gt;acc&lt;/code&gt; is the &lt;strong&gt;running total&lt;/strong&gt;, not the answer to that one multiplication: the previous total was 16 257, and 16 257 + (−16 256) = 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try your own numbers
&lt;/h3&gt;

&lt;p&gt;Type these one line at a time in the Shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first number is the total; 65 is the status byte 0x41.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I knew it was correct before touching the board
&lt;/h2&gt;

&lt;p&gt;I never trust a design just because it compiles. Each layer was checked on its own before the next one was built.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it proved&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simulation&lt;/td&gt;
&lt;td&gt;Icarus Verilog + a self-checking testbench&lt;/td&gt;
&lt;td&gt;Arithmetic, signed values, command framing, readback, overflow, SPI at 1, 2 and 4 MHz&lt;/td&gt;
&lt;td&gt;11 852 checks, 0 errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full overflow run&lt;/td&gt;
&lt;td&gt;Same testbench&lt;/td&gt;
&lt;td&gt;131 072 real SPI MACs push the total past +2 147 483 647; it wraps and OVF turns on&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug injection&lt;/td&gt;
&lt;td&gt;Deliberately broken Verilog&lt;/td&gt;
&lt;td&gt;The testbench actually catches unsigned maths, a missing overflow check, wrong byte order, a missing clear-first check&lt;/td&gt;
&lt;td&gt;All 4 caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PC unit tests&lt;/td&gt;
&lt;td&gt;Python &lt;code&gt;unittest&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Range rejection, encoding, decoding, driver framing&lt;/td&gt;
&lt;td&gt;10 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthesis + place &amp;amp; route&lt;/td&gt;
&lt;td&gt;Go Configure v6.56&lt;/td&gt;
&lt;td&gt;Fits in 32 % of the LUTs; 68.6 MHz achievable vs 50 MHz needed&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;Shrike Lite + Thonny&lt;/td&gt;
&lt;td&gt;10 corner cases + 1000 random MACs match the Python model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RESULT: PASS&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The testbench drives the real SPI pins exactly like the RP2040 does, so it tests the whole FPGA design, not just the multiplier. The expected answers come from a separate Python model, so the Verilog and the model check each other.&lt;/p&gt;

&lt;p&gt;The bug-injection step is the one beginners skip, and it is the most reassuring. A testbench that passes is only useful if it would also &lt;em&gt;fail&lt;/em&gt; on a broken design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;Likely cause and fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No &lt;code&gt;RPI-RP2&lt;/code&gt; drive when installing MicroPython&lt;/td&gt;
&lt;td&gt;Charge-only cable, or BOOT not held while plugging in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ImportError: no module named 'shrike'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generic MicroPython was installed; use &lt;code&gt;shrike-lite-micropython.uf2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[shrike_flash] File error: [Errno 2] ENOENT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;.bin&lt;/code&gt; file is not on the board, or its name is misspelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LinkError ... got ffffffffffff&lt;/code&gt; or &lt;code&gt;000000000000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;FPGA not answering: wrong bitstream, or &lt;code&gt;clk&lt;/code&gt;/&lt;code&gt;clk_en&lt;/code&gt;/MISO pins not mapped in the I/O Planner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status shows ERR_UNINIT (68)&lt;/td&gt;
&lt;td&gt;A MAC was sent before CLEAR; call &lt;code&gt;mac.clear()&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status shows ERR_FRAME (73)&lt;/td&gt;
&lt;td&gt;A frame had the wrong length; use the driver functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows won't rename the &lt;code&gt;.bin&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;You are inside the zip, not an extracted folder; or keep the name and change &lt;code&gt;BITSTREAM&lt;/code&gt; in &lt;code&gt;main.py&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timing report shows huge negative slack&lt;/td&gt;
&lt;td&gt;No clock constraint, so the tool assumes 500 MHz; read &lt;em&gt;Achievable Frequency&lt;/em&gt; instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;IndentationError&lt;/code&gt; in the Thonny Shell&lt;/td&gt;
&lt;td&gt;A space before the command; type it from the start of the line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two chips, two jobs.&lt;/strong&gt; The FPGA does fixed hardware work; the microcontroller does everything else, including loading the FPGA every boot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signals from another chip must be synchronised&lt;/strong&gt; before you use them, and edges are found by comparing two clean samples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed maths needs care:&lt;/strong&gt; use &lt;code&gt;$signed&lt;/code&gt;, sign-extend before adding, and detect overflow from the sign bits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make every message atomic.&lt;/strong&gt; Acting only on a complete, correctly sized frame made the design much easier to trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status codes pay for themselves.&lt;/strong&gt; A single status byte turned every failure into a readable message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the right number in a report.&lt;/strong&gt; Negative slack against an automatic 500 MHz target is not a failure at 50 MHz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tutorials age.&lt;/strong&gt; Go Configure v6.56 looks different from older guides ("Start new project" instead of a Develop tab), so expect small differences.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A signed 8×8 multiplier with a 32-bit accumulator fits in about a third of the Shrike Lite's FPGA, runs well above its 50 MHz clock, and talks to the RP2040 over wires that are already on the board. For a first real FPGA project, that is a very satisfying result.&lt;/p&gt;

&lt;p&gt;Ideas for where to go next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a whole vector in one SPI frame, so a dot product needs fewer Python calls.&lt;/li&gt;
&lt;li&gt;Make CLEAR + MAC a single command that starts a new total.&lt;/li&gt;
&lt;li&gt;Use the FPGA's block RAM to store weights, turning this into a tiny neural-network accelerator.&lt;/li&gt;
&lt;li&gt;Add a clock constraint file (&lt;code&gt;.sdc&lt;/code&gt;) so the timing report checks against 50 MHz directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/Pratham7767/shrike-lite-mac" rel="noopener noreferrer"&gt;https://github.com/Pratham7767/shrike-lite-mac&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you build this, tell me what you change. I'd love to see a faster or bigger version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/vicharak-in/shrike" rel="noopener noreferrer"&gt;Vicharak Shrike repository&lt;/a&gt; and &lt;a href="https://github.com/vicharak-in/shrike/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vicharak-in.github.io/shrike/shrike_pinouts.html" rel="noopener noreferrer"&gt;Shrike pin-outs and FPGA–MCU interconnect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vicharak-in.github.io/shrike/hardware_overview.html" rel="noopener noreferrer"&gt;Shrike hardware overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vicharak-in.github.io/shrike/generating_your_first_bitstream.html" rel="noopener noreferrer"&gt;Generating your first bitstream (Vicharak)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vicharak-in/shrike/tree/main/examples/spi_loopback_led" rel="noopener noreferrer"&gt;Vicharak &lt;code&gt;spi_loopback_led&lt;/code&gt; example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.renesas.com/en/products/slg47910" rel="noopener noreferrer"&gt;Renesas SLG47910 product page&lt;/a&gt; and &lt;a href="https://www.renesas.com/en/document/dst/slg47910-datasheet" rel="noopener noreferrer"&gt;datasheet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.renesas.com/en/software-tool/go-configure-software-hub" rel="noopener noreferrer"&gt;Renesas Go Configure Software Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.renesas.com/en/document/gde/forgefpga-workshop-user-guide" rel="noopener noreferrer"&gt;ForgeFPGA Workshop user guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.micropython.org/en/latest/library/machine.SPI.html" rel="noopener noreferrer"&gt;MicroPython &lt;code&gt;machine.SPI&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fpga</category>
      <category>verilog</category>
      <category>micropython</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
