How to configure Xilinx FPGA with MCU using JTAG? This is actually pretty simple, pseudocode:
send_header();
send_raw_bistream(bitstream);
send footer();
The functions send_header and send_footer do some TDI/TMS toggling, send_raw_bistream sends raw bitstream data from the bitstream to FPGA TDI pin (note bit order in the bitstream!).
How to implement send_header/send_footer? The easiest and smallest footprint for the MCU is to implement 2 bit playback function, so the pseudocode would look like this:
send_tdi_tms_bits(header_2bit_file);
send_raw_bistream(bitstream);
send_tdi_tms_bits(footer_2bit_file);
How to get the 2 bit sequences for the header/footer? OK, this is one known working and relatively simple approach:
1) You generate with Xilinx Vivado an SVF file for your target device. If you look at the generated SVF you can quickly see three sections there, there is a header, then raw content, then a footer. Look at header/footer only, you do not want to convert to 2bits the raw data.
2) cut out the header and footer with some text editor
3) remove the ID code check lines from the header, you do not need that! And your MCU playback would be WRITE only with no readback or verify functions.
5) modify the SVF player to emit 2 lower bits of each processed byte, this is an easy exercise for anyone with moderate C experience.
6) play header and footer SVF files with your SVF_to_2bitsfile.exe you created in the previous step.
7) implement for your embedded MCU function send_tdi_tms_bits() and send_raw_bistream(). Use the pseudo code from above on your MCU to configure the FPGA.
OK, there is a little bit of tweaking required, you possible need to insert a delay during header playback, and the line RUNTEST 10000 TCK; should possible also be implemented as direct code with your MCU sending 10,000 TCK cycles without change on TDI/TMS.
OK, the idea should be clear. Happy bit toggling!
How to do it on Rasperry Pi PICO? First of all we need wiring, to save time on the documentation we can take the Pico pin mapping from here:
https://github.com/phdussud/pico-dirtyJtag/ Already saved some minutes of work :)
Now we need the function send_tdi_tms_bits as this function is rather short compared to the main junk bitstream pushing we can use machine.Pin class it will be slow but it does not matter. We assume that the 2bit file is generated with the svfplayer with TMS being bit 0 and TDI being bit 1 in the saved file.
from machine import Pin
tms = Pin(19, Pin.OUT) # TMS: create output pin on GPIO19
tms.on() # set TMS to "on" (high) level
tdi = Pin(16, Pin.OUT)
tdi.on()
tck = Pin(18, Pin.OUT)
tck.off()
The above creates pins for TCK, TDI, and TMS, as we are WRITE only interface we do not need to assign TDO to anything, we will not use TDO at all.
Now my python skills are not enough :( but let's give some hints how to proceed:
def send_tdi_tms_bits(byte_with_2bits_in_it)
tms.value(byte_with_2bits_in_it & 1)
tdi.value(byte_with_2bits_in_it >>1 & 1)
tck.on()
tck.off()
ready :) OK I mixed C and python programming(or did I?). This function needs to be called with the bytes our svf_to_bits converter wrote for the header and footer.
Now, what's next? We need to send the bitstream too right? OK, we can do it using the machine.Pin functions but it would be really slow. OK, for the proof of concept it does not matter.
Later we should optimize the bitstream push using the Pico PIO peripheral, some links:
This is now beyond my current python skills, so it is left to the reader to implement the bit sending with PIO.