elialm / ecgc-firmware

FPGA firmware for the ecgc Gameboy cartridge written in VHDL
MIT License
3 stars 0 forks source link

Slow deploy #2

Closed elialm closed 1 year ago

elialm commented 2 years ago

It takes ages for me to upload changes to the boot ROM. The boot ROM is programmed alongside the FPGA fabric, so changing a line in the code means recompiling and uploading the entire FPGA. This takes multiple clicks and ~2 min of waiting, which adds up after 50 code changes.

A method needs to be implemented to make changes to boot ROM on the fly. It would also be nice to access the program memory this way as well. I could use it to test the memory or to upload larger programs.

elialm commented 2 years ago

I planned initially to make something with JTAG. Maybe I could access the boot ROM via JTAG. This seems to be impossible according to the documentation.

image

Looking at the diagram above, it shows no connection between JTAG and the user logic. Maybe I could use I2C or SPI to talk to user logic. However, this means I'd have to get some bridge between my PC and the board. Something like an USB-to-I2C device. Drivers for this probably exist, meaning I could use the i2c-tools in Linux to do stuff with it.

elialm commented 2 years ago

I've looked into the datasheet to see what is possible. It is possible to use the SPI or I2C peripheral in slave mode to wait for data, which can then be accessed inside the FPGA fabric. I've made a block diagram of a possible idea.

SmartSelect_20220919_150407_Concepts

I could make some sort of debug component, which can be used to address the wishbone bus. This would allow me to access the entire memory map of the cartridge. I could also access the MBCH directly. However, I have a feeling that will be more difficult to implement. More investigation into this will be required.

As there will be 2 bus masters with this solution, it would be wise to inhibit the GB decoder and keep the Gameboy in reset. There could be some danger in letting the Gameboy run while the program is changed (eg. accidental writes to the SD card, possibly corrupting the filesystem). I could have an external pin I keep asserted which activates the debug core and takes control of the wishbone bus.

I also need the aforementioned bridge. The cheapest and most flexible way for me would be to use an Arduino. I can treat it as a serial device, which interprets commands from a script on my PC and does "stuff" with the debug core.

elialm commented 2 years ago

I've chosen to use I2C to communicate with the FPGA. Since SPI is already being used by the FPGA to master communication with the SD card, using it also as a slave is going to be difficult since it would need to be able to change its configuration while online. This will probably require an extra FSM to r/w over WishBone, which can be quite large to implement. Since I am not using and don't plan to use I2C, I'll just set it as a slave and use that.

elialm commented 2 years ago

Holy crap, I just got a great idea! Instead of developing an entire debug core to master the WishBone bus, I could just implement a debug program to run on the boot ROM. The program could just look for a boot condition (eg. SELECT pressed) and boot into some sort of debug mode when that condition is satisfied.

The functionality to R/W from/to memory is then activated by the boot software. The boot software could then listen for incoming commands and react accordingly. E.g. it can receive a write command and then write the next few bytes to some address. I could also finally implement a DMA to hopefully speed up this process. It will be slow compared to a firmware method due to the speed limitations of the GB bus, but it'll be much easier to implement.

This should also eliminate the need of extra firmware, giving me more freedom when adding other (more important) components. I'd rather have a software solution, since adding extra memory is easier than using a bigger FPGA. The extra I2C controller won't be needed as well, since I can easily configure the SPI controller to be a slave via the firmware.

I'll look further into this possibility and see what I need to add to implement it.

elialm commented 2 years ago

Project ecgc_220928_142738.jpg

elialm commented 2 years ago

I made this design based on the software approach.

The way it should work is like so:

  1. The bridge resets the cart and asserts flash enable
  2. After the reset, the cart will read the flash enable signal setup the write system to the boot ROM
  3. It will then assert the ready signal to indicate it can receive data
  4. The bridge will start sending bytes, which will to written in sequence to some start address (start is determined by the cart)
    • The bridge will only send bytes when ready is asserted. The SPI peripheral on the MachXO3D does not have a receive buffer, so care should be taken to not overwrite the sent data.
  5. After sending the entire program, the done signal is asserted
  6. The cart will read the asserted done signal and proceed to run the uploaded program

This is all assuming the program is on the bridge. I plan to create some kind of command based protocol between the bridge and my PC. This would give me great flexibility in how I use it. I will probably write a script that uses this protocol to ultimately write the program to the cart.

elialm commented 2 years ago

There were some issues with the hard routed SPI CSS pin on the breakout board. Apparently this pin is also connected to some optional SPI Flash under the breakout board (see images below).

image image

I don't have access to this pin via the pin headers on the board, so I'll just route a different pin to be the SPI CSS.

elialm commented 1 year ago

After exploring the software approach, I've determined it to be unsatisfactory. Having the bootloader handle code uploads had some unforseen problems. The main issue was timing constraints with the software controlled pins. Since the program pins were controlled by software, I miss pin changes. The GPIO controller is implemented on the cartridge, so I cannot set interrupts on the Gameboy to check for pin changes. This meant that I could miss some bytes coming in while uploading. To solve this issue, I would need to decrease the SPI speed drastically, which would not only be annoyingly slow, but also guarantee nothing. With program uploads, I want to be sure that the program is uploaded correctly. I don't want the uploaded program to have wierd bugs in it due to some transmission error.

I am therefore moving to the firmware based approach. I think I can get away with the following:

image

The programming sequence will look something like so:

  1. The bridge resets the cart and asserts flash enable
  2. After the reset, the cart will read the flash enable signal setup the write system to the boot ROM
  3. It will then assert the ready signal to indicate it can receive data
  4. The bridge will start sending bytes, which will to written in sequence to some start address (start is determined by the cart)
    • The bridge will only send bytes when ready is asserted. The SPI peripheral on the MachXO3D does not have a receive buffer, so care should be taken to not overwrite the sent data.
  5. After sending the entire program, the FLASH_READ signal is asserted to start the reading process
  6. The bridge will read the program back to check it's validity
  7. After it is verified to be correct, the bridge will deassert the FLASH_ENABLE signal
  8. The cart will read the deasserted FLASH_ENABLE signal and proceed to run the uploaded program
elialm commented 1 year ago

I've been able to get SPI communication working with the simple SPI programmer I made for the Arduino Pro Mini. I can now send bytes of data to the FPGA, which can be read from the WishBone bus. For testing, I've used the memory testing program on the Gameboy to test the registers.

To test the SPI functionality, I just write a byte from the computer and read it on the Gameboy. I then send a byte from the Gameboy and read it from my laptop. I used hterm for serial communication with the SPI programmer. I also included an exurb from the SPI status register definition to read the status bits from the test.

Here was the test procedure (reading the Gameboy screen memory transaction history from bottom-to-top):

  1. Write 0x00 to 0xA056. This is done to put the cartridge's SPI controller in slave mode. This is normally not necessary since it is slave by default, but the memory testing program puts it master mode due to me using it previously to communicate with the SD card.
  2. Read 0x10 from 0xA05A. To illustrate that the RRDY bit is 0. After this read, I send a command to the SPI programmer to write 0x69 to the SPI bus.
  3. Read 0x1C from 0xA05A. To illustrate that the RRDY bit is now 1.
  4. Read 0x69 from 0xA05B. This shows that the byte sent from the SPI programmer was read successfully.
  5. Read 0x14 from 0xA05A. This shows that the RRDY bit is now cleared after a read. Also notice the TRDY bit is 1.
  6. Write 0xE1 to 0xA059. This will write this value to the SPI master during the next SPI transaction.
  7. Read 0x00 from 0xA05A. Notice that bit RRDY and TRDY are both 0. After this, I send the byte 0x00 from the SPI programmer.
  8. Read 0x1C from 0xA05A. Afterwards, the RRDY and TRDY are both 1 again. On the hterm window, I can also observe that the value 0xE1 was received successfully by the programmer.
  9. Read 0x00 from 0xA05B. This was the byte sent by the programmer.

20230107_162235 image

SPI status register definition

image

Registers used on the test are:

elialm commented 1 year ago

Having the SPI experiment success, the end now seems nearer. There are some problems yet needing fixing, but progress is coming along nicely.

Now that SPI works, I can focus on the debug core mentioned in https://github.com/elialm/ecgc-firmware/issues/2#issuecomment-1251012782. This core will take the data sent over the SPI bus to write to the boot ROM following the ideas presented in https://github.com/elialm/ecgc-firmware/issues/2#issuecomment-1370960490.

There are some things to keep in mind:

Continuing on this issue, I plan to first design the debug core state machine. With this, I know what is needed from the DMA core and I can proceed to design that. With both of them designed, I'll finally develop them for testing and deploying.

elialm commented 1 year ago

sigh... I changed the design again... but it's a very important change.

I've decided to forgo using the EFB SPI controller in favor of a softend solution. This will provide the following benefits:

I also thought about making the debug core more flexible. There will be a point in time where I need to write software to be ran from DRAM. I would also benefit me to be able to write code from my laptop directly to the DRAM. Therefore, I decided to make the core so that I can tell it which address it needs to write to. This way, I can write to any arbitrary address (including DRAM). I'll probably make some kind of command protocol in firmware. I was avoiding this since the beginning, but it seems like it really is the way to go. I'll use it as an experiment to see how big such a command decoding component can be.

elialm commented 1 year ago

I think I completely implemented the debug core. I now just need to make a few adjustments to the rest of the cartridge to be able to add it:

elialm commented 1 year ago

HAHA, IT WORKS!

I've implemented the debug core and integrated it with the rest of the cartridge. I can now send commands via the ecgc-spi-programmer and master the entire bus from a serial monitor on my PC. I've also changed the boot_rom component to be RAM, so I can write to it whenever the cart is in debug more.

I'll document the changes at a later date (probably tomorrow). I also still need to write the upload script, but I'll make that into another repository.

Anyhow, this issue is now closed for this repo, since everything is implemented on the FPGA side.