Did you ever get an answer to your question?
I am still digging into the boot loader code and trying to modify it. Unfortunately I am very busy at work and do not have too much time. I am also very frightened about debugging the code, since I have no clue how to do that.
Do you have more experience on the usb protocol?
I don’t - that’s why I came here looking. I asked @robertbaruch on Twitter but didn’t get a reply… perhaps they’ll see the question here.
Yes, it does. On my Ubuntu Linux box, I see /dev/ttyACM0.
I have just tried modifying the USB code to blink an LED and it was quite straightforward.
I first changed the SPI pins to pins 1-4 so that if I get anything wrong it does not try to write to the flash memory.
I then commented out the led code in tinyfpga_bootloader.v and added an led ouput to usb_spi_bridge_ep.
In usb_spi_bridge_ep.v, you need to add the led output and then change the code for CMD_OP_BOOT to:
CMD_OP_BOOT : begin cmd_state_next <= CMD_IDLE; led <= ~led;
You can then toggle the led by typing tinyprog -b.
Or equivalently, you can do:
echo -n -e “\x00” >/dev/ttyACM0
The way that usb_spi_bridge.v works is that it receives commands from the USB CDC ACM device and executes them.
It currently recognises 2 commands:
- 0x00 - boot
- 0x01 - SPI transfer
I changed the boot command to toggle the LED.
The SPI transfer command is followed by a 16-bit value specifying the length of data to write, followed by a 16-bit value specifying the length of data to read, followed by the data to write (if any). Any data read is written to the usb CDC ACM endpoint.
By just changing the SPI pins you can use this to read and write other flash memory devices including another BX.
Or you could remove the SPI protocol engine and change it to something else, such as a uart.
Or you implement any other commands that you want.
Experiments with BX programmer and bootloader
wow, thank you very much!
Unfortunately I am very busy at work. But as soon as I get to it I will let you know.
Here is an example of using the TinyFPGA BX usb interface as a uart with a similar interface to that of the PicoSoc simpleuart module. It is currently output only and writes “Hello World!” to /dev/ttyACM0.
I tried to synthesize and upload your code. yosys is successful (although with a lot of warning message) but arachne-pnr fails at routing
fatal error: Top level port 'pin_usbp' assigned to an IO pad 'Y' and internal nodes
The same error occurs with the original bootloader code and seems to be due to a new version of yosys/arachne-pnr
The problem is caused by the tristate ports pin_usbp and pin_usbn. I tried to properly instantiate the ports
assign pin_pu = 1'b1; /* assign pin_usbp = usb_tx_en ? usb_p_tx : 1'bz; assign pin_usbn = usb_tx_en ? usb_n_tx : 1'bz; assign usb_p_rx = usb_tx_en ? 1'b1 : pin_usbp; assign usb_n_rx = usb_tx_en ? 1'b0 : pin_usbn; */ SB_IO #( .PIN_TYPE(6'b 1010_01), // PIN_OUTPUT_TRISTATE - PIN_INPUT .PULLUP(1'b 1) ) iobuf_usbp ( .PACKAGE_PIN(pin_usbp), .OUTPUT_ENABLE(usb_tx_en), .D_OUT_0(usb_p_tx), .D_IN_0(usb_p_rx) ); SB_IO #( .PIN_TYPE(6'b 1010_01), // PIN_OUTPUT_TRISTATE - PIN_INPUT .PULLUP(1'b 0) ) iobuf_usbn ( .PACKAGE_PIN(pin_usbn), .OUTPUT_ENABLE(usb_tx_en), .D_OUT_0(usb_n_tx), .D_IN_0(usb_n_rx) );
and now the code gets placed and routed by arachne-pnr. Unfortunately the code does not work and I do not see any serial communication device being list on usb. The full code is here
Does anybody see what I am doing wrong?
Is there a way of debugging it? I am pretty stuck since it just is not working and I do not know how to analyze it.
It seems a general problem, since the original bootloader code does not work with the actual version of yosys/arachne-pnr
This works for me:
wire usb_p_tx; wire usb_n_tx; wire usb_p_rx; wire usb_n_rx; wire usb_tx_en; wire usb_p_in; wire usb_n_in; assign pin_pu = 1'b1; assign usb_p_rx = usb_tx_en ? 1'b1 : usb_p_in; assign usb_n_rx = usb_tx_en ? 1'b0 : usb_n_in; /* assign pin_usbp = usb_tx_en ? usb_p_tx : 1'bz; assign pin_usbn = usb_tx_en ? usb_n_tx : 1'bz; assign usb_p_rx = usb_tx_en ? 1'b1 : pin_usbp; assign usb_n_rx = usb_tx_en ? 1'b0 : pin_usbn; */ SB_IO #( .PIN_TYPE(6'b 1010_01), // PIN_OUTPUT_TRISTATE - PIN_INPUT .PULLUP(1'b 0) ) iobuf_usbp ( .PACKAGE_PIN(pin_usbp), .OUTPUT_ENABLE(usb_tx_en), .D_OUT_0(usb_p_tx), .D_IN_0(usb_p_in) ); SB_IO #( .PIN_TYPE(6'b 1010_01), // PIN_OUTPUT_TRISTATE - PIN_INPUT .PULLUP(1'b 0) ) iobuf_usbn ( .PACKAGE_PIN(pin_usbn), .OUTPUT_ENABLE(usb_tx_en), .D_OUT_0(usb_n_tx), .D_IN_0(usb_n_in) );
Awesome, thank you very much for the help!
Now it works. I added comm.py to read from the FPGA
My usb uart code needs improvement. It only works because I was writing characters so slowly. I started fixing it but got distracted.
This is great. So nice to see “Hello World” coming back from my board.
Please let me urge you to continue! What you have made would be a fantastic building block for us less experienced TinyFPGA’ers.
If I could send and receive characters… that would be so sweet.
I will have another look at this when I take a break from trying to get my Atari 2600 emulation working.
I have improved the send example, so it sends a whole message about once per second. I’ll look at receiving characters next.
I can work with this!!
I now have both send and receive working, and top.v now echoes what you send to it. The old Hello World is still there as top.hello.
It has some diagnostics in it that should not be a problem.
The example still needs some tidying up but I thought I would check in what I had working.
I will problably get it to build under apio, so that Windows and Mac users, who are using apio can use it.
I will also look at encapsulating it better to make it easier to use in projects.
I have added an apio.ini and SConstruct file to the uart project, so that it builds with apio.
The only changes from the default SConstruct file is that I have changed:
# -- Get a list of all the verilog files in the src folfer, in ASCII, with # -- the full path. All these files are used for the simulation v_nodes = Glob('*.v')
# -- Get a list of all the verilog files in the src folfer, in ASCII, with # -- the full path. All these files are used for the simulation v_nodes = Glob('*.v') + Glob('../usb/*.v')
After building it in Atom on Windows, I connected to it using the Arduino IDE console, and it echoes what you send to it.
I added another example. that you send a stream of note letters to, and it plays a tune on pin 15. (I chose 15, so I could use my FPGC).
I tidied the code up a bit and added hello_world as a new example.
I have been looking at doing more examples of using the USB code in user designs but one thing that holds me back is that the code is very fragile. Some builds with very minor changes just stop working, including no longer functioning as a USB device. The exact same code sometimes builds and runs fine if I build it on my Windows machine but not on my Linux machine, or vice versa. Probably caused by different versions of yosys or archne-pnr.
And one thing I noticed is that timing analysis does not work for the bootloader or my usb examples derived from it. It says that the designs will run at 0 Mhz.
I just tried building the original bootloader and my usb examples using nextpnr instead of arachne-pnr, after upgrading to the latest version of nextpnr and yosys.
The bootloader would not build with that as it said it had combinatorial loops. I added a --force flag and that got a bit further but still failed as is failed a timing check. I had to set --freq 1, i.e. the required speed to 1Mhz to get it to build. With that my examples worked.
It would be a lot of work to investigate the cause of these problems, and with Luke doing a completely new implementation of USB for the EX, it is probably not worth it.
Another thing that would be good is to be able to support USB devices classes other than CDC ACM, such as HID devices for mice, keyboards, or midi input devices, or audio output devices. That looks quite feasible, but again a lot of work, and probably not worth it on this implementation.
Thanks for all the work you put in!
One thing I noticed as I was looking over the code is that top.v has a signal resetn which it uses, and usb_uart.v also has a resetn. But internally usb_uart.v uses reset (no -n) only. There is no reset = ~resetn anywhere hence they don’t seem to be connected. And the USB logic never gets reset. I wonder if this is what was causing the bad behavior under some conditions.
wire reset = !resetn; at line 74 and have had no problems running it.
Of course I might be completely wrong and be missing something critical… (-:,