diff --git a/.gitignore b/.gitignore index 7de3fcb..3f7ccac 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,27 @@ *.bak pcb/l130-backups docs/*.pdf + +## Quartus II +*.qws +*.qpf +*.qsf +*.sof +*.pof +*.rbf +*.rpt +*.summary +*.smsg +*.done +*.jdi +*.pin +*.sld +db/ +incremental_db/ +output_files/ +simulation/ +greybox_tmp/ +*.ppf +*.ddb +*.cmp +*.qdf diff --git a/README.md b/README.md index 1973277..d5155a3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Status: - ✅ [Blinky in FPGA](fpga_blink/) - ✅ [VGA output](fpga_blink/) - ✅ [Retrocomputing using multicomp](multicomp/) -- ❌ Persist FPGA bitstream in serial flash +- ✅ [Persist FPGA bitstream in serial flash](cpld_flash_loader/) - ❌ Serial port - ❌ Sound output - ❌ Keyboard and mouse @@ -36,6 +36,7 @@ KiCad files can be found in the `pcb/` folder. The following folders contain `vhd` files that can be used to create a _Quartus II_ project for this board: - cpld_passthrough: This generates a CPLD bitstream that makes it work as a passthrough so we can send the signals required for programming the FPGA. +- cpld_flash_loader: CPLD design that automatically loads the FPGA bitstream from the onboard AT45DB041B serial flash on power-up (requires flash to be pre-programmed). - fpga_blink: Blinks the LEDs that are connected to the FPGA. - fpga_vga: Generates a 640x480 VGA signal that has some characters of a 32x32 monochromatic font. Uses an internal PLL to convert the 80 MHz clock into the 25 MHz pixel clock required. - multicomp: Details on mods to Grant Searle's guide to create a retro computer diff --git a/cpld_flash_loader/QUICKSTART.md b/cpld_flash_loader/QUICKSTART.md new file mode 100644 index 0000000..10f991e --- /dev/null +++ b/cpld_flash_loader/QUICKSTART.md @@ -0,0 +1,172 @@ +# Quick Start Guide - CPLD Flash Loader + +This guide provides step-by-step instructions to use the CPLD flash loader for automatic FPGA configuration. + +## Prerequisites + +- NComputing L130 board (rev 1.3A) +- Altera USB Blaster JTAG programmer +- Quartus II 13.0sp1 or compatible version +- Pre-programmed AT45DB041B flash with FPGA bitstream + +## Step 1: Build the CPLD Project + +1. Open Quartus II +2. Create a new project: + - **Project name**: cpld_flash_loader + - **Working directory**: Point to the `cpld_flash_loader` folder + - **Top-level entity**: cpld_flash_loader + +3. Add all VHDL files to the project: + - cpld_flash_loader.vhd (top-level) + - fpga_config.vhd + - flash_reader.vhd + - spi_master.vhd + - blink.vhd + +4. Configure the device: + - **Device**: EPM3128ATC100-10 + - **Family**: MAX3000A + +5. Set unused pins: + - Go to: Assignments → Device → Device and Pin Options → Unused Pins + - Select: "As input tri-stated" + - Click OK + +6. Compile the project: + - Processing → Start Compilation + - Wait for completion (should take ~30 seconds) + +## Step 2: Verify Pin Assignments + +Open the Pin Planner (Assignments → Pin Planner) and verify: + +| Signal | Pin | Description | +|--------------|-----|--------------------------| +| clk_i | 87 | 80 MHz system clock | +| led_o | 1 | Status LED | +| fpga_data0 | 68 | FPGA configuration data | +| fpga_dclk | 63 | FPGA configuration clock | +| fpga_nconfig | 67 | FPGA nCONFIG | +| fpga_conf_done | 16 | FPGA CONF_DONE | +| flash_sck | 23 | Flash SPI clock | +| flash_si | 22 | Flash SPI MOSI | +| flash_so | 21 | Flash SPI MISO | +| flash_cs_n | 24 | Flash chip select | +| flash_reset_n | 27 | Flash reset | +| flash_wp_n | 28 | Flash write protect | + +These should be set automatically via the `chip_pin` attributes in the VHDL code. + +## Step 3: Program the CPLD + +1. Connect the USB Blaster to the CPLD JTAG header (right connector) + - Observe pin 1 marking (red stripe on cable) + - Power on the board + +2. Open the Programmer: + - Tools → Programmer + - Click "Hardware Setup" + - Select "USB-Blaster" + - Click "Close" + +3. Add the programming file: + - Click "Add File" + - Navigate to `output_files/cpld_flash_loader.pof` + - Select and open + +4. Program the device: + - Check "Program/Configure" + - Click "Start" + - Wait for "100% (Successful)" message + +## Step 4: Verify Operation + +After programming the CPLD, power cycle the board: + +### LED Behavior + +- **Blinking (~1 Hz)**: Configuration in progress - this is normal +- **Solid ON**: Configuration successful - FPGA is running +- **OFF**: Configuration error - check flash contents and connections + +### Expected Timeline + +1. Power on → LED starts blinking (CPLD initializing) +2. After ~50-100ms → Configuration complete +3. LED goes solid ON → FPGA is now running the bitstream from flash + +## Troubleshooting + +### LED stays OFF +- **Problem**: Configuration error +- **Solutions**: + - Verify flash chip is properly programmed + - Check flash connections (especially CS, SCK, MOSI, MISO) + - Verify FPGA configuration pins are not damaged + - Check power supplies (3.3V, 1.5V) + +### LED keeps blinking continuously +- **Problem**: Configuration never completes +- **Solutions**: + - Flash may be empty or corrupted + - Bitstream in flash may be invalid + - Verify flash programming starting at address 0x000000 + - Check that .rbf file (not .sof) was used + +### FPGA doesn't function after configuration +- **Problem**: Configuration succeeded but FPGA design doesn't work +- **Solutions**: + - This is not a flash loader issue + - Debug your FPGA design separately + - Verify the .rbf file works when programmed via JTAG first + - Check that FPGA pin assignments match your design + +### How to go back to JTAG programming +- Simply program the CPLD with `cpld_passthrough` design +- This allows external JTAG programming of the FPGA again +- No hardware changes needed + +## Next Steps + +Once the flash loader is working: + +1. **Test different FPGA designs**: + - Program different bitstreams to flash + - Each power cycle will load the new design + +2. **Optimize timing**: + - Current SPI clock: 5 MHz (conservative) + - Can increase to 10-20 MHz if needed + +3. **Add features**: + - Multi-image support (store multiple bitstreams) + - Boot image selection via GPIO + - Watchdog timer for failed configurations + - Fallback to secondary image + +## Important Notes + +- **Flash programming**: Must be done externally (remove chip or use programmer) +- **No JTAG passthrough**: This design doesn't support JTAG programming of FPGA +- **Bitstream format**: Must be .rbf (raw binary), not .sof +- **Bitstream size**: EP1C6 bitstream is ~140 KB, well within flash capacity +- **Configuration time**: ~50-100ms depending on bitstream size + +## Support + +For issues or questions: +- Check the main [README.md](README.md) for detailed technical information +- Review FPGA and flash datasheets in the `docs/` folder +- See existing examples: `cpld_passthrough`, `fpga_blink`, `fpga_vga` + +## Summary + +The CPLD flash loader enables standalone operation of the L130 board: +- No programmer needed after initial setup +- FPGA configures automatically on power-up +- Configuration time: ~50-100ms +- Visual feedback via LED +- Easy to switch back to development mode with `cpld_passthrough` + +This makes the board suitable for embedded applications, demonstrations, and production use cases where JTAG programming is not practical. diff --git a/cpld_flash_loader/README.md b/cpld_flash_loader/README.md new file mode 100644 index 0000000..663240d --- /dev/null +++ b/cpld_flash_loader/README.md @@ -0,0 +1,176 @@ +# CPLD Flash Loader + +This CPLD project enables the NComputing L130 to load the FPGA bitstream from the onboard AT45DB041B serial flash memory on power-up, eliminating the need for external JTAG programming after the initial flash programming. + +## Overview + +The CPLD flash loader: +- Reads the FPGA bitstream from AT45DB041B serial flash (starting at address 0) +- Configures the Cyclone EP1C6 FPGA using Passive Serial (PS) mode +- Provides visual feedback via LED: + - Blinking: Configuration in progress + - Solid ON: Configuration successful + - OFF: Configuration error + +## Architecture + +The design consists of four main components: + +1. **spi_master.vhd** - SPI master controller for communicating with the flash chip +2. **flash_reader.vhd** - High-level controller that reads data from flash using continuous read mode +3. **fpga_config.vhd** - FPGA configuration controller implementing Passive Serial protocol +4. **cpld_flash_loader.vhd** - Top-level module coordinating all components + +## Pin Assignments + +### FPGA Configuration Interface (Passive Serial) +- Pin 68: FPGA DATA0 +- Pin 63: FPGA DCLK +- Pin 67: FPGA nCONFIG +- Pin 16: FPGA CONF_DONE + +Note: FPGA nSTATUS (pin 146) is not connected to the CPLD. The configuration sequence uses fixed timing delays instead of monitoring nSTATUS. + +### Flash Memory Interface (SPI) +- Pin 23: Flash SCK (SPI Clock) +- Pin 22: Flash SI (MOSI - Master Out Slave In) +- Pin 21: Flash SO (MISO - Master In Slave Out) +- Pin 24: Flash CS# (Chip Select, active low) +- Pin 27: Flash RESET# (always high - not in reset) +- Pin 28: Flash WP# (always high - write protect disabled) + +### Other +- Pin 87: 80 MHz system clock input +- Pin 1: Status LED output + +## Programming the Flash + +Before using this loader, the flash memory must be programmed with the FPGA bitstream. The bitstream should be stored starting at address 0x000000. + +**Note:** The current design assumes the flash is pre-programmed via external means. As specified in the requirements, board programming via JTAG is not included in this version - the expectation is that the memory is already loaded with the bitstream manually. + +### Methods to program the flash: + +1. **External Flash Programmer**: + - Remove the flash chip (U5 - AT45DB041B) from the board + - Use a dedicated SPI flash programmer + - Write the FPGA .rbf bitstream starting at address 0x000000 + - Reinstall the flash chip on the board + +2. **In-Circuit Programming with Test Points**: + - If the board has test points for the SPI signals, use them with an external programmer + - Ensure the CPLD is not driving the SPI bus during programming + +3. **Custom CPLD Flash Programming Design**: + - Create a temporary CPLD design that accepts data via JTAG or GPIO + - This design would write to the flash memory + - After programming the flash, replace with the flash_loader design + - This approach requires additional development work + +### Bitstream Preparation: + +1. Generate your FPGA design using Quartus II +2. In Quartus, select Device → Device and Pin Options → Configuration +3. Choose "Passive Serial" as the configuration scheme +4. Compile the design to generate the bitstream file +5. Convert to raw binary format (.rbf) if not already done: + - File → Convert Programming Files + - Select "Raw Binary File (.rbf)" as output format + - Add the .sof file as input + - Generate the .rbf file +6. This .rbf file should be written to flash starting at address 0x000000 + +## Building the Project + +1. Create a new Quartus II project pointing to the `cpld_flash_loader` directory +2. Use "cpld_flash_loader" as both the project name and top-level design entity +3. Add all .vhd files to the project: + - cpld_flash_loader.vhd + - fpga_config.vhd + - flash_reader.vhd + - spi_master.vhd + - blink.vhd +4. Select device: **EPM3128ATC100-10** from the MAX3000A family +5. Set unused pins to "As input tri-stated" (Device > Device and Pin Options > Unused Pins) +6. Compile the project +7. Program the CPLD using JTAG + +## Differences from cpld_passthrough + +Unlike `cpld_passthrough` which allows external JTAG programming of the FPGA: +- The CPLD actively drives the configuration signals (not a simple passthrough) +- Configuration happens automatically on power-up +- No external programmer connection is needed after initial setup +- The FPGA programming header is not used +- In this mode, the FPGA only receives configuration data from the CPLD, not from external pins + +### Alternative: Hybrid Mode + +While this implementation focuses on automatic flash-based configuration, the system could be extended to support a hybrid mode where: +- A GPIO pin selects between flash mode and passthrough mode +- Passthrough mode would work like `cpld_passthrough` for development +- Flash mode enables standalone operation in production + +This would require modifying the CPLD logic to multiplex the configuration signals based on a mode selection pin, but the current implementation keeps it simple with flash-only operation. + +## Timing + +- SPI clock runs at ~5 MHz (80 MHz system clock divided by 16) +- FPGA DCLK runs at ~20 MHz (80 MHz system clock divided by 4) +- Typical configuration time for EP1C6 bitstream (~140 KB): ~56ms + +## Troubleshooting + +**LED is OFF (solid):** +- Configuration error occurred +- Check flash contents +- Verify flash is properly connected +- Check FPGA status signals + +**LED keeps blinking:** +- Configuration never completes +- Flash may not contain valid bitstream +- Flash read command may be failing + +**Configuration seems to work but FPGA doesn't function:** +- Bitstream may be corrupted +- Verify correct .rbf file was written to flash +- Check that bitstream starts at address 0x000000 + +## Technical Notes + +### AT45DB041B Flash +- 4-Mbit (512K x 8-bit) serial flash +- Supports continuous array read (command 0xE8) +- SPI Mode 0 (CPOL=0, CPHA=0) +- Maximum SPI clock: 20 MHz (we use 5 MHz for margin) +- 2048 pages of 264 bytes each (with page size selection) +- Standard 264-byte page mode used for continuous read +- Total usable capacity: ~540 KB (sufficient for EP1C6 bitstream of ~140 KB) + +**Note on Page Size**: The AT45DB041B can operate in two page size modes: +- Binary page size (256 bytes): More standard but requires one-time configuration +- DataFlash page size (264 bytes): Default mode, works with continuous read command + +The current implementation uses the default 264-byte page mode with the continuous array read command (0xE8), which works regardless of page size configuration. + +### Cyclone FPGA Passive Serial Configuration +- MSB-first bit order +- DCLK frequency: up to 40 MHz (we use 20 MHz) +- Typical bitstream size: ~140 KB for EP1C6 +- Requires additional clocks after CONF_DONE for initialization + +## Future Enhancements + +Possible improvements: +- Add support for programming flash via JTAG +- Implement fallback to JTAG mode if flash read fails +- Add CRC checking of bitstream +- Support multiple bitstream images in flash +- Add mode selection via GPIO pins + +## References + +- [AT45DB041B Datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/doc3443.pdf) +- [Configuring Cyclone FPGAs](https://cdrdv2-public.intel.com/655144/cyc_c51013.pdf) +- [Configuration Handbook](https://cdrdv2-public.intel.com/654348/section_3_vol_2.pdf) diff --git a/cpld_flash_loader/blink.vhd b/cpld_flash_loader/blink.vhd new file mode 100644 index 0000000..3cb60a0 --- /dev/null +++ b/cpld_flash_loader/blink.vhd @@ -0,0 +1,33 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity blinky is + generic(clock_freq: integer := 50e6); + + port( + clk_i: in std_logic; + led_o: out std_logic + ); +end entity; + +architecture behavioral of blinky is + signal output : std_logic := '0'; + signal counter : integer range 0 to clock_freq := 0; +begin + led_o <= output; + + process(clk_i) + begin + if (rising_edge(clk_i)) then + if (counter = clock_freq-1) then + counter <= 0; + output <= not output; + else + counter <= counter + 1; + end if; + end if; + end process; + +end; + diff --git a/cpld_flash_loader/cpld_flash_loader.vhd b/cpld_flash_loader/cpld_flash_loader.vhd new file mode 100644 index 0000000..79f5f06 --- /dev/null +++ b/cpld_flash_loader/cpld_flash_loader.vhd @@ -0,0 +1,201 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Top-level module for CPLD flash loader +-- Reads FPGA bitstream from AT45DB041B serial flash on startup +-- and configures the FPGA using Passive Serial mode +entity cpld_flash_loader is + port( + -- System clock (80 MHz) + clk_i : in std_logic; + + -- LED for status indication + led_o : out std_logic; + + -- FPGA configuration interface (Passive Serial) + fpga_conf_done : in std_logic; + fpga_data0 : out std_logic; + fpga_nconfig : out std_logic; + fpga_dclk : out std_logic; + + -- Flash memory interface (SPI) + flash_sck : out std_logic; + flash_si : out std_logic; -- MOSI + flash_so : in std_logic; -- MISO + flash_cs_n : out std_logic; + flash_reset_n : out std_logic; + flash_wp_n : out std_logic + ); +end entity; + +architecture behavioral of cpld_flash_loader is + -- Pin assignments for EPM3128ATC100-10N + attribute chip_pin : string; + attribute chip_pin of clk_i : signal is "87"; -- 80MHz clock + attribute chip_pin of led_o : signal is "1"; -- Status LED + attribute chip_pin of fpga_data0 : signal is "68"; -- FPGA DATA0 + attribute chip_pin of fpga_dclk : signal is "63"; -- FPGA DCLK + attribute chip_pin of fpga_nconfig : signal is "67"; -- FPGA nCONFIG + attribute chip_pin of fpga_conf_done: signal is "16"; -- FPGA CONF_DONE + attribute chip_pin of flash_sck : signal is "23"; -- Flash SCK + attribute chip_pin of flash_si : signal is "22"; -- Flash SI (MOSI) + attribute chip_pin of flash_so : signal is "21"; -- Flash SO (MISO) + attribute chip_pin of flash_cs_n : signal is "24"; -- Flash CS# + attribute chip_pin of flash_reset_n : signal is "27"; -- Flash RESET# + attribute chip_pin of flash_wp_n : signal is "28"; -- Flash WP# + + -- Internal signals + signal reset : std_logic := '1'; + signal reset_counter : integer range 0 to 80000000 := 0; -- ~1 second + signal config_start : std_logic := '0'; + signal config_done : std_logic := '0'; + signal config_error : std_logic := '0'; + + -- Flash reader signals + signal flash_data : std_logic_vector(7 downto 0); + signal flash_data_valid : std_logic; + signal flash_start : std_logic := '0'; + signal flash_done : std_logic; + + -- SPI master signals + signal spi_tx_data : std_logic_vector(7 downto 0); + signal spi_rx_data : std_logic_vector(7 downto 0); + signal spi_start : std_logic; + signal spi_busy : std_logic; + signal spi_rx_valid : std_logic; + signal spi_mosi : std_logic; + signal spi_sck : std_logic; + + -- FPGA config signals + signal data_req : std_logic; + + -- LED control + signal led_state : std_logic := '0'; + + type main_state_t is (RESET_STATE, START_CONFIG, CONFIGURING, DONE_STATE, ERROR_STATE); + signal main_state : main_state_t := RESET_STATE; + +begin + -- Flash control signals (always enabled) + flash_reset_n <= '1'; -- Not in reset + flash_wp_n <= '1'; -- Write protect disabled (not needed for reading) + + -- Route SPI signals + flash_sck <= spi_sck; + flash_si <= spi_mosi; + + -- LED blinker for visual feedback + blink: entity work.blinky + generic map(clock_freq => 80e6) + port map(clk_i => clk_i, led_o => led_state); + + -- LED shows different patterns based on state + led_o <= led_state when main_state = CONFIGURING else + '1' when main_state = DONE_STATE else + '0' when main_state = ERROR_STATE else + not led_state; + + -- SPI Master + spi_master_inst: entity work.spi_master + port map( + clk_i => clk_i, + reset_i => reset, + start_i => spi_start, + busy_o => spi_busy, + tx_data_i => spi_tx_data, + rx_data_o => spi_rx_data, + rx_valid_o => spi_rx_valid, + sck_o => spi_sck, + mosi_o => spi_mosi, + miso_i => flash_so + ); + + -- Flash Reader + flash_reader_inst: entity work.flash_reader + port map( + clk_i => clk_i, + reset_i => reset, + start_i => flash_start, + done_o => flash_done, + data_o => flash_data, + data_valid_o => flash_data_valid, + spi_tx_data_o => spi_tx_data, + spi_rx_data_i => spi_rx_data, + spi_start_o => spi_start, + spi_busy_i => spi_busy, + spi_rx_valid_i => spi_rx_valid, + cs_n_o => flash_cs_n + ); + + -- FPGA Configuration Controller + fpga_config_inst: entity work.fpga_config + port map( + clk_i => clk_i, + reset_i => reset, + start_i => config_start, + done_o => config_done, + error_o => config_error, + data_i => flash_data, + data_valid_i => flash_data_valid, + data_req_o => data_req, + fpga_nconfig_o => fpga_nconfig, + fpga_conf_done_i=> fpga_conf_done, + fpga_data0_o => fpga_data0, + fpga_dclk_o => fpga_dclk + ); + + -- Main state machine + process(clk_i) + begin + if rising_edge(clk_i) then + -- Power-on reset for ~10ms + if reset = '1' then + if reset_counter < 800000 then -- 10ms @ 80MHz + reset_counter <= reset_counter + 1; + else + reset <= '0'; + reset_counter <= 0; + end if; + end if; + + -- Main control logic + case main_state is + when RESET_STATE => + flash_start <= '0'; + config_start <= '0'; + + if reset = '0' then + main_state <= START_CONFIG; + end if; + + when START_CONFIG => + -- Start both flash reader and FPGA config + flash_start <= '1'; + config_start <= '1'; + main_state <= CONFIGURING; + + when CONFIGURING => + flash_start <= '0'; + config_start <= '0'; + + if config_done = '1' then + main_state <= DONE_STATE; + elsif config_error = '1' then + main_state <= ERROR_STATE; + end if; + + when DONE_STATE => + -- Configuration successful + -- Stay here indefinitely + null; + + when ERROR_STATE => + -- Configuration failed + -- Stay here indefinitely (LED will be off) + null; + end case; + end if; + end process; + +end architecture; diff --git a/cpld_flash_loader/flash_reader.vhd b/cpld_flash_loader/flash_reader.vhd new file mode 100644 index 0000000..2748934 --- /dev/null +++ b/cpld_flash_loader/flash_reader.vhd @@ -0,0 +1,165 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Flash reader for AT45DB041B +-- Reads continuous array data starting from address 0 +entity flash_reader is + port( + clk_i : in std_logic; + reset_i : in std_logic; + + -- Control interface + start_i : in std_logic; + done_o : out std_logic; + + -- Data output + data_o : out std_logic_vector(7 downto 0); + data_valid_o: out std_logic; + + -- SPI master interface + spi_tx_data_o : out std_logic_vector(7 downto 0); + spi_rx_data_i : in std_logic_vector(7 downto 0); + spi_start_o : out std_logic; + spi_busy_i : in std_logic; + spi_rx_valid_i : in std_logic; + + -- Flash chip select (active low) + cs_n_o : out std_logic + ); +end entity; + +architecture rtl of flash_reader is + -- AT45DB041B commands + constant CMD_CONTINUOUS_READ : std_logic_vector(7 downto 0) := x"E8"; + + type state_t is (IDLE, CS_ASSERT, SEND_CMD, SEND_ADDR1, SEND_ADDR2, + SEND_ADDR3, SEND_DUMMY, READ_DATA, DONE); + signal state : state_t := IDLE; + signal next_state : state_t := IDLE; + + signal byte_count : unsigned(23 downto 0) := (others => '0'); + signal spi_start_reg : std_logic := '0'; + +begin + cs_n_o <= '0' when (state /= IDLE and state /= DONE) else '1'; + spi_start_o <= spi_start_reg; + + process(clk_i, reset_i) + begin + if reset_i = '1' then + state <= IDLE; + next_state <= IDLE; + done_o <= '0'; + data_valid_o <= '0'; + spi_start_reg <= '0'; + byte_count <= (others => '0'); + + elsif rising_edge(clk_i) then + -- Default outputs + data_valid_o <= '0'; + done_o <= '0'; + + case state is + when IDLE => + spi_start_reg <= '0'; + byte_count <= (others => '0'); + + if start_i = '1' then + state <= CS_ASSERT; + next_state <= SEND_CMD; + end if; + + when CS_ASSERT => + -- Wait one clock for CS to settle + state <= next_state; + + when SEND_CMD => + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= CMD_CONTINUOUS_READ; + spi_start_reg <= '1'; + next_state <= SEND_ADDR1; + elsif spi_rx_valid_i = '1' then + spi_start_reg <= '0'; + state <= next_state; + else + spi_start_reg <= '0'; + end if; + + when SEND_ADDR1 => + -- Address bits 23:16 (all zeros) + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= x"00"; + spi_start_reg <= '1'; + next_state <= SEND_ADDR2; + elsif spi_rx_valid_i = '1' then + spi_start_reg <= '0'; + state <= next_state; + else + spi_start_reg <= '0'; + end if; + + when SEND_ADDR2 => + -- Address bits 15:8 (all zeros) + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= x"00"; + spi_start_reg <= '1'; + next_state <= SEND_ADDR3; + elsif spi_rx_valid_i = '1' then + spi_start_reg <= '0'; + state <= next_state; + else + spi_start_reg <= '0'; + end if; + + when SEND_ADDR3 => + -- Address bits 7:0 (all zeros) + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= x"00"; + spi_start_reg <= '1'; + next_state <= SEND_DUMMY; + elsif spi_rx_valid_i = '1' then + spi_start_reg <= '0'; + state <= next_state; + else + spi_start_reg <= '0'; + end if; + + when SEND_DUMMY => + -- Send dummy bytes (required by continuous read command) + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= x"00"; + spi_start_reg <= '1'; + next_state <= READ_DATA; + elsif spi_rx_valid_i = '1' then + spi_start_reg <= '0'; + state <= next_state; + else + spi_start_reg <= '0'; + end if; + + when READ_DATA => + -- Continuously read data bytes + if spi_busy_i = '0' and spi_start_reg = '0' then + spi_tx_data_o <= x"00"; -- Dummy data for reading + spi_start_reg <= '1'; + elsif spi_rx_valid_i = '1' then + data_o <= spi_rx_data_i; + data_valid_o <= '1'; + byte_count <= byte_count + 1; + spi_start_reg <= '0'; + + -- Continue reading indefinitely until external reset/stop + -- The FPGA will signal CONF_DONE when complete + else + spi_start_reg <= '0'; + end if; + + when DONE => + done_o <= '1'; + state <= IDLE; + end case; + end if; + end process; + +end architecture; diff --git a/cpld_flash_loader/fpga_config.vhd b/cpld_flash_loader/fpga_config.vhd new file mode 100644 index 0000000..155c5d1 --- /dev/null +++ b/cpld_flash_loader/fpga_config.vhd @@ -0,0 +1,172 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- FPGA Configuration Controller for Cyclone EP1C6 +-- Implements Passive Serial (PS) configuration protocol +entity fpga_config is + port( + clk_i : in std_logic; + reset_i : in std_logic; + + -- Control interface + start_i : in std_logic; + done_o : out std_logic; + error_o : out std_logic; + + -- Data input (bitstream from flash) + data_i : in std_logic_vector(7 downto 0); + data_valid_i : in std_logic; + data_req_o : out std_logic; -- Request more data + + -- FPGA configuration interface + fpga_nconfig_o : out std_logic; + fpga_conf_done_i: in std_logic; + fpga_data0_o : out std_logic; + fpga_dclk_o : out std_logic + ); +end entity; + +architecture rtl of fpga_config is + type state_t is (IDLE, ASSERT_NCONFIG, WAIT_RELEASE, + CONFIGURE, STARTUP, DONE_STATE, ERROR_STATE); + signal state : state_t := IDLE; + + signal bit_counter : integer range 0 to 7 := 0; + signal data_reg : std_logic_vector(7 downto 0) := (others => '0'); + signal dclk_int : std_logic := '0'; + signal clk_div : integer range 0 to 3 := 0; -- Divide by 4 for DCLK + signal wait_counter: integer range 0 to 10000 := 0; + + -- Startup counter for initialization sequence + signal startup_counter : integer range 0 to 10000 := 0; + +begin + fpga_dclk_o <= dclk_int; + fpga_data0_o <= data_reg(7); -- MSB first + + process(clk_i, reset_i) + begin + if reset_i = '1' then + state <= IDLE; + fpga_nconfig_o <= '1'; + dclk_int <= '0'; + bit_counter <= 0; + data_reg <= (others => '0'); + done_o <= '0'; + error_o <= '0'; + data_req_o <= '0'; + clk_div <= 0; + wait_counter <= 0; + startup_counter <= 0; + + elsif rising_edge(clk_i) then + case state is + when IDLE => + fpga_nconfig_o <= '1'; + dclk_int <= '0'; + done_o <= '0'; + error_o <= '0'; + data_req_o <= '0'; + startup_counter <= 0; + + if start_i = '1' then + state <= ASSERT_NCONFIG; + wait_counter <= 0; + end if; + + when ASSERT_NCONFIG => + -- Assert nCONFIG low to initiate configuration + fpga_nconfig_o <= '0'; + wait_counter <= wait_counter + 1; + + -- Hold nCONFIG low for at least 500ns (40 clocks @ 80MHz) + -- Extended to be safe: 2us + if wait_counter >= 200 then + state <= WAIT_RELEASE; + wait_counter <= 0; + end if; + + when WAIT_RELEASE => + -- Release nCONFIG and wait for FPGA to be ready + -- Since we don't have access to nSTATUS, we wait a fixed time + -- The FPGA datasheet specifies max 200us for the FPGA to become ready + fpga_nconfig_o <= '1'; + wait_counter <= wait_counter + 1; + + -- Wait 200us @ 80MHz = 16000 clocks (use 20000 for margin) + if wait_counter >= 20000 then + state <= CONFIGURE; + bit_counter <= 0; + data_req_o <= '1'; + clk_div <= 0; + wait_counter <= 0; + end if; + + when CONFIGURE => + -- Send configuration data + data_req_o <= '0'; + + -- Check if configuration is complete + if fpga_conf_done_i = '1' then + -- Configuration complete + state <= STARTUP; + startup_counter <= 0; + else + -- Clock divider for DCLK (80MHz / 4 = 20MHz) + if clk_div = 3 then + clk_div <= 0; + dclk_int <= not dclk_int; + + if dclk_int = '1' then + -- Falling edge of DCLK + if bit_counter = 7 then + bit_counter <= 0; + data_req_o <= '1'; -- Request next byte + else + bit_counter <= bit_counter + 1; + data_reg <= data_reg(6 downto 0) & '0'; + end if; + end if; + else + clk_div <= clk_div + 1; + end if; + + -- Load new data when available + if data_valid_i = '1' then + data_reg <= data_i; + data_req_o <= '0'; + end if; + end if; + + when STARTUP => + -- Send additional clocks for initialization + -- Cyclone requires ~100 clocks after CONF_DONE + if startup_counter < 200 then + startup_counter <= startup_counter + 1; + + if clk_div = 3 then + clk_div <= 0; + dclk_int <= not dclk_int; + else + clk_div <= clk_div + 1; + end if; + else + state <= DONE_STATE; + dclk_int <= '0'; + end if; + + when DONE_STATE => + done_o <= '1'; + dclk_int <= '0'; + -- Stay in this state until reset + + when ERROR_STATE => + error_o <= '1'; + dclk_int <= '0'; + -- Stay in this state until reset + end case; + end if; + end process; + +end architecture; diff --git a/cpld_flash_loader/spi_master.vhd b/cpld_flash_loader/spi_master.vhd new file mode 100644 index 0000000..c46dfc0 --- /dev/null +++ b/cpld_flash_loader/spi_master.vhd @@ -0,0 +1,100 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- SPI Master controller for AT45DB041B flash memory +-- Operates in SPI Mode 0 (CPOL=0, CPHA=0) +entity spi_master is + port( + clk_i : in std_logic; + reset_i : in std_logic; + + -- Command interface + start_i : in std_logic; + busy_o : out std_logic; + tx_data_i : in std_logic_vector(7 downto 0); + rx_data_o : out std_logic_vector(7 downto 0); + rx_valid_o : out std_logic; + + -- SPI interface + sck_o : out std_logic; + mosi_o : out std_logic; + miso_i : in std_logic + ); +end entity; + +architecture rtl of spi_master is + -- SPI clock divider (80MHz / 16 = 5MHz SPI clock) + constant CLK_DIV : integer := 8; + + type state_t is (IDLE, SHIFT); + signal state : state_t := IDLE; + + signal bit_counter : integer range 0 to 7 := 0; + signal clk_counter : integer range 0 to CLK_DIV-1 := 0; + signal shift_reg : std_logic_vector(7 downto 0) := (others => '0'); + signal rx_reg : std_logic_vector(7 downto 0) := (others => '0'); + signal sck_int : std_logic := '0'; + +begin + sck_o <= sck_int; + mosi_o <= shift_reg(7); + rx_data_o <= rx_reg; + + process(clk_i, reset_i) + begin + if reset_i = '1' then + state <= IDLE; + bit_counter <= 0; + clk_counter <= 0; + shift_reg <= (others => '0'); + rx_reg <= (others => '0'); + sck_int <= '0'; + busy_o <= '0'; + rx_valid_o <= '0'; + + elsif rising_edge(clk_i) then + rx_valid_o <= '0'; + + case state is + when IDLE => + busy_o <= '0'; + sck_int <= '0'; + bit_counter <= 0; + clk_counter <= 0; + + if start_i = '1' then + shift_reg <= tx_data_i; + state <= SHIFT; + busy_o <= '1'; + end if; + + when SHIFT => + if clk_counter = CLK_DIV-1 then + clk_counter <= 0; + sck_int <= not sck_int; + + if sck_int = '0' then + -- Rising edge of SCK - shift out data + null; + else + -- Falling edge of SCK - sample input and shift + rx_reg <= rx_reg(6 downto 0) & miso_i; + shift_reg <= shift_reg(6 downto 0) & '0'; + + if bit_counter = 7 then + bit_counter <= 0; + state <= IDLE; + rx_valid_o <= '1'; + else + bit_counter <= bit_counter + 1; + end if; + end if; + else + clk_counter <= clk_counter + 1; + end if; + end case; + end if; + end process; + +end architecture; diff --git a/cpld_flash_loader/tb_spi_master.vhd b/cpld_flash_loader/tb_spi_master.vhd new file mode 100644 index 0000000..c1cb6a0 --- /dev/null +++ b/cpld_flash_loader/tb_spi_master.vhd @@ -0,0 +1,87 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +-- Testbench for SPI Master +entity tb_spi_master is +end entity; + +architecture sim of tb_spi_master is + signal clk : std_logic := '0'; + signal reset : std_logic := '1'; + signal start : std_logic := '0'; + signal busy : std_logic; + signal tx_data : std_logic_vector(7 downto 0) := x"A5"; + signal rx_data : std_logic_vector(7 downto 0); + signal rx_valid : std_logic; + signal sck : std_logic; + signal mosi : std_logic; + signal miso : std_logic := '0'; + + constant CLK_PERIOD : time := 12.5 ns; -- 80 MHz + signal test_done : boolean := false; + +begin + -- Clock generation + clk <= not clk after CLK_PERIOD/2 when not test_done; + + -- DUT + dut: entity work.spi_master + port map( + clk_i => clk, + reset_i => reset, + start_i => start, + busy_o => busy, + tx_data_i => tx_data, + rx_data_o => rx_data, + rx_valid_o => rx_valid, + sck_o => sck, + mosi_o => mosi, + miso_i => miso + ); + + -- Test process + process + begin + -- Reset + reset <= '1'; + wait for 100 ns; + reset <= '0'; + wait for 100 ns; + + -- Test 1: Send 0xA5 + report "Test 1: Sending 0xA5"; + tx_data <= x"A5"; + start <= '1'; + wait for CLK_PERIOD; + start <= '0'; + + -- Wait for transaction to complete + wait until rx_valid = '1'; + report "Transaction complete"; + wait for 100 ns; + + -- Test 2: Send 0x5A + report "Test 2: Sending 0x5A"; + tx_data <= x"5A"; + start <= '1'; + wait for CLK_PERIOD; + start <= '0'; + + wait until rx_valid = '1'; + report "Transaction complete"; + wait for 100 ns; + + report "All tests passed!"; + test_done <= true; + wait; + end process; + + -- MISO simulation (echo back the data) + process + begin + wait until rising_edge(sck); + miso <= mosi; + end process; + +end architecture;