blob: 090ad5426fecc0fc753ad3e7f78ce6771042680c [file] [log] [blame]
#include "public/gonk/fpga_control.h"
#include <Arduino.h>
#include <bitset>
#include <cstddef>
#include <stdint.h>
#include "gonk/fpga_control.h"
#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
#define PW_LOG_MODULE_NAME "FpgaControl"
#include "pw_log/log.h"
#include "pw_result/result.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
using gonk::pin_config::FlashCLK;
using gonk::pin_config::FlashCS;
using gonk::pin_config::FlashMISO;
using gonk::pin_config::ICE40Done;
using gonk::pin_config::PinConfig;
namespace gonk::fpga_control {
FpgaControl::FpgaControl(PinConfig *pin_config) : pin_config_(pin_config) {}
Status FpgaControl::StartConfig() {
bitstream_file_position = 0;
memset(read_buffer, 0, 4);
memset(bitstream_file, 0, BitstreamFileLength);
bytes_written = 0;
WaitForBitstreamStart();
ReadBitstream();
auto verify_result = VerifyBitstream();
if (!verify_result.ok()) {
return verify_result;
}
return SendBitstreamToFpga();
}
Status FpgaControl::WaitForBitstreamStart() {
uint32_t last_update = millis();
uint32_t this_update = millis();
const uint32_t update_interval_ms = 1000;
while (true) {
// Output a heartbeat message while waiting for data.
this_update = millis();
if (bytes_written == 0 && this_update > last_update + update_interval_ms) {
last_update = this_update;
PW_LOG_INFO("Waiting for bitstream");
}
// If no data waiting start over.
if (!Serial.available()) {
continue;
}
uint8_t data_in = Serial.read();
read_buffer[bytes_written % 4] = data_in;
bytes_written++;
PW_LOG_INFO("Discard byte: %d", bytes_written);
// If the last 4 bytes recieved match the start sequence, break.
if (read_buffer[(bytes_written + 0) % 4] == 0xff &&
read_buffer[(bytes_written + 1) % 4] == 0x00 &&
read_buffer[(bytes_written + 2) % 4] == 0x00 &&
read_buffer[(bytes_written + 3) % 4] == 0xff) {
PW_LOG_INFO("Start Sequence found.");
break;
}
}
return pw::OkStatus();
}
Status FpgaControl::ReadBitstream() {
char bitstream_buffer[4096];
while (true) {
size_t read_byte_count = Serial.readBytes(bitstream_buffer, 4096);
PW_LOG_INFO("Got bytes: %d", read_byte_count);
bytes_written += read_byte_count;
memcpy(&bitstream_file[bitstream_file_position], bitstream_buffer,
read_byte_count);
bitstream_file_position += read_byte_count;
if (bytes_written >= 135100) {
PW_LOG_INFO("All 135100 bytes recieved.");
break;
}
}
return pw::OkStatus();
}
Status FpgaControl::VerifyBitstream() {
// TODO(tonymd): Verify the bitstream with somehow, perhaps move to pw_hdlc.
PW_LOG_INFO("File ready: %d", bitstream_file_position);
PW_LOG_INFO("First 12 bytes");
for (int i = 0; i < 12; i++) {
PW_LOG_INFO("%x", bitstream_file[i]);
}
PW_LOG_INFO("Last 12 bytes");
for (int i = bitstream_file_position - 12; i < bitstream_file_position; i++) {
PW_LOG_INFO("%x", bitstream_file[i]);
}
return pw::OkStatus();
}
Status FpgaControl::SendBitstreamToFpga(const uint32_t config_timeout_ms) {
PW_LOG_INFO("Sending bitstream file to the FPGA.");
pin_config_->SPIDisable();
pin_config_->FpgaSpiConfigMode();
digitalWrite(FlashCS, LOW);
// Write out the bitstream file similar to an SPI transaction but using MISO
// as the output pin.
for (int i = 0; i < bitstream_file_position; i++) {
uint8_t d = bitstream_file[i];
// Write out each 8 bits.
for (int b = 0; b < 8; b++) {
if (d & 0x80) {
digitalWrite(FlashCLK, LOW);
digitalWrite(FlashMISO, HIGH);
digitalWrite(FlashCLK, HIGH);
} else {
digitalWrite(FlashCLK, LOW);
digitalWrite(FlashMISO, LOW);
digitalWrite(FlashCLK, HIGH);
}
d <<= 1;
}
digitalWrite(FlashCLK, HIGH);
}
digitalWrite(FlashCS, HIGH);
uint32_t last_update = millis();
uint32_t this_update = millis();
bool done = false;
int ice40_done = 0;
// Wait for ICE40Done (CDONE) signal to go high
while (!done) {
// Write one clock pulse
digitalWrite(FlashCLK, LOW);
delayMicroseconds(100);
digitalWrite(FlashCLK, HIGH);
delayMicroseconds(100);
ice40_done = digitalRead(ICE40Done);
if (ice40_done == 1) {
PW_LOG_INFO("FPGA Config Success.");
break;
}
this_update = millis();
// If more than two seconds have passed something likely went wrong.
if (this_update > last_update + config_timeout_ms) {
PW_LOG_ERROR("FPGA Config failed.");
last_update = this_update;
return pw::Status::Aborted();
}
}
// Send 50 more clock pulses to release release the SPI bus to user control.
for (int i = 0; i < 50; i++) {
digitalWrite(FlashCLK, LOW);
delayMicroseconds(100);
digitalWrite(FlashCLK, HIGH);
delayMicroseconds(100);
}
// Re-enable SPI
pin_config_->SPIEnable();
return pw::OkStatus();
}
} // namespace gonk::fpga_control