USB Communication


#1

First, let me congratulate you with this project! An Open Source FPGA hardware and toolchain is really exciting. Thanks for sharing this excellent work!

In the past I have used OpalKelly boards and implemented control hardware for our lab. The OpalKelly boards come with a ip core that makes data transfer from or to the FPGA very simple. I am now looking for something similar for the tinyFPGA BX board. The simplest example would be a verilog code that allows to turn on and off the led on the board via usb and a python script on the host computer.

Currently, I have downloaded the bootloader code and trying to figure out how to modify it. Unfortunately my knowledge of the USB protocol is very limited. What confuses me is, does the boot loader really implement a serial communication? As far as I understand it, it just announces itself as one, but the communication is done via writing to an endpoint. On my osx system, the device is listed as a ‘Communication Device’ but I do not see a serial port on /dev/tty* ?


#2

I’m not sure if he follows the happenings in here, but maybe ask @robertbaruch - he hinted on twitter a little while ago that he had something working along those lines:


#3

Did you ever get an answer to your question?


#4

unfortunately not.
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?


#5

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.


#6

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
#7

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.


#8

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.


#9

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


#10

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)
  );

#11

Awesome, thank you very much for the help!

Now it works. I added comm.py to read from the FPGA


#12

My usb uart code needs improvement. It only works because I was writing characters so slowly. I started fixing it but got distracted.


#13

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.


#14

I will have another look at this when I take a break from trying to get my Atari 2600 emulation working.


#15

I have improved the send example, so it sends a whole message about once per second. I’ll look at receiving characters next.


#16

Fantastic!

Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

I can work with this!!