├── .gitignore ├── firmware ├── isr.c ├── main.c ├── linker-xip.ld └── Makefile ├── README.md └── tinyfpga_litex.py /.gitignore: -------------------------------------------------------------------------------- 1 | soc_tinylitex_tinyfpga_bx/ 2 | __pycache__/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /firmware/isr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void isr(void); 6 | void isr(void) 7 | { 8 | unsigned int irqs; 9 | 10 | irqs = irq_pending() & irq_getmask(); 11 | 12 | if(irqs & (1 << UART_INTERRUPT)) 13 | uart_isr(); 14 | } 15 | -------------------------------------------------------------------------------- /firmware/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | extern uint32_t _fbss, _ebss; 13 | 14 | int main(void) 15 | { 16 | irq_setmask(0); 17 | irq_setie(1); 18 | uart_init(); 19 | 20 | puts("Hello World\n"); 21 | 22 | // blink the user LED 23 | uint32_t led_timer = 0; 24 | 25 | while (1) { 26 | leds_out_write(led_timer >> 17); 27 | led_timer = led_timer + 1; 28 | } 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /firmware/linker-xip.ld: -------------------------------------------------------------------------------- 1 | INCLUDE generated/output_format.ld 2 | ENTRY(_start) 3 | 4 | __DYNAMIC = 0; 5 | 6 | INCLUDE generated/regions.ld 7 | 8 | SECTIONS 9 | { 10 | .fbi : 11 | { 12 | . = . + 8; /* Firmware Base Image format (len/crc) data comes first. */ 13 | } > user_flash 14 | 15 | .text : 16 | { 17 | _ftext = .; 18 | *(.text .stub .text.* .gnu.linkonce.t.*) 19 | _etext = .; 20 | } > user_flash 21 | 22 | .rodata : 23 | { 24 | . = ALIGN(4); 25 | _frodata = .; 26 | *(.rodata .rodata.* .gnu.linkonce.r.*) 27 | *(.rodata1) 28 | _erodata = .; 29 | } > user_flash 30 | 31 | .data : AT (ADDR(.rodata) + SIZEOF (.rodata)) 32 | { 33 | . = ALIGN(4); 34 | _fdata = .; 35 | *(.data .data.* .gnu.linkonce.d.*) 36 | *(.data1) 37 | _gp = ALIGN(16); 38 | *(.sdata .sdata.* .gnu.linkonce.s.*) 39 | _edata = ALIGN(16); /* Make sure _edata is >= _gp. */ 40 | } > sram 41 | 42 | .bss : 43 | { 44 | . = ALIGN(4); 45 | _fbss = .; 46 | *(.dynsbss) 47 | *(.sbss .sbss.* .gnu.linkonce.sb.*) 48 | *(.scommon) 49 | *(.dynbss) 50 | *(.bss .bss.* .gnu.linkonce.b.*) 51 | *(COMMON) 52 | . = ALIGN(4); 53 | _ebss = .; 54 | _end = .; 55 | } > sram 56 | } 57 | 58 | PROVIDE(_fstack = ORIGIN(sram) + LENGTH(sram) - 4); 59 | -------------------------------------------------------------------------------- /firmware/Makefile: -------------------------------------------------------------------------------- 1 | include ../include/generated/variables.mak 2 | include $(SOC_DIRECTORY)/software/common.mak 3 | 4 | CFLAGS += -I$(FIRMWARE_DIRECTORY) 5 | CFLAGS += \ 6 | -Wall \ 7 | -Werror \ 8 | -Wno-error=unused-function \ 9 | -Wno-error=unused-variable 10 | 11 | CRT0 = ../libbase/crt0-$(CPU)-xip.o 12 | LINKER_LD = $(FIRMWARE_DIRECTORY)/linker-xip.ld 13 | 14 | OBJECTS = main.o isr.o 15 | 16 | # The output file will be the combined bios and user firmware 17 | all: userdata 18 | 19 | userdata: firmware.fbi 20 | dd if=../bios/bios.bin of=../userdata.bin bs=32768 count=1 21 | dd if=firmware.fbi of=../userdata.bin bs=32768 seek=1 count=21 22 | 23 | %.fbi: %.bin 24 | ifeq ($(CPUENDIANNESS),little) 25 | $(PYTHON) -m litex.soc.software.mkmscimg -f --little $< -o $@ 26 | else 27 | $(PYTHON) -m litex.soc.software.mkmscimg -f $< -o $@ 28 | endif 29 | 30 | %.bin: %.elf 31 | $(OBJCOPY) -O binary $< $@ 32 | ifneq ($(OS),Windows_NT) 33 | chmod -x $@ 34 | endif 35 | 36 | firmware.elf: $(LINKER_LD) $(OBJECTS) 37 | 38 | %.elf: $(CRT0) ../libbase/libbase-nofloat.a ../libcompiler_rt/libcompiler_rt.a 39 | $(LD) $(LDFLAGS) -T $(LINKER_LD) -N -o $@ \ 40 | $(CRT0) \ 41 | $(OBJECTS) \ 42 | -L../libbase \ 43 | -L../libcompiler_rt \ 44 | -lbase-nofloat -lcompiler_rt 45 | ifneq ($(OS),Windows_NT) 46 | chmod -x $@ 47 | endif 48 | 49 | # pull in dependency info for *existing* .o files 50 | -include $(OBJECTS:.o=.d) 51 | 52 | %.o: $(FIRMWARE_DIRECTORY)/%.c 53 | $(compile) 54 | 55 | clean: 56 | $(RM) $(OBJECTS) $(OBJECTS:.o=.d) *.elf *.bin *.fbi .*~ *~ 57 | 58 | .PHONY: all clean userdata 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyFPGA LiteX 2 | 3 | A minimal LiteX SoC definition for the TinyFPGA. Currently, only TinyFPGA BX is supported, but more boards may be added in the future. 4 | 5 | ## Quick Start 6 | 7 | 1. Install LiteX according to the Advanced or Medium quickstart guide on [LiteX GitHub](https://github.com/enjoy-digital/litex). Note that depending on what CPU you would like to use (see Caveats below) your target compiler may be available in your system's package manager! 8 | 9 | 2. Install LiteX-Boards (depending on your workflow) 10 | 11 | pip3 install git+https://github.com/litex-hub/litex-boards 12 | 13 | - or - 14 | 15 | git clone https://github.com/litex-hub/litex-boards 16 | cd litex-boards 17 | python3 setup.py develop 18 | cd .. 19 | 20 | 3. Install tinyprog, if you don't already have it 21 | 22 | pip3 install tinyprog 23 | 24 | 4. Clone this repository 25 | 26 | git clone https://github.com/kekiefer/tinyfpga-litex 27 | cd tinyfpga-litex 28 | 29 | 5. Build the SoC: 30 | 31 | python3 tinyfpga_litex.py --cpu-type vexriscv --cpu-variant=lite 32 | 33 | 6. Program the gateware bitstream and the firmware: 34 | 35 | tinyprog -p soc_tinylitex_tinyfpga_bx/gateware/top.bin -u soc_tinylitex_tinyfpga_bx/software/userdata.bin 36 | 37 | If everything works, you should see output on the UART and a flashing LED. 38 | 39 | ## Footprint `--cpu-type vexriscv --cpu-variant=lite` 40 | 41 | Info: Device utilisation: 42 | Info: ICESTORM_LC: 4565/ 7680 59% 43 | Info: ICESTORM_RAM: 32/ 32 100% 44 | Info: SB_IO: 10/ 256 3% 45 | Info: SB_GB: 8/ 8 100% 46 | Info: ICESTORM_PLL: 0/ 2 0% 47 | Info: SB_WARMBOOT: 0/ 1 0% 48 | 49 | ## UART configuration 50 | 51 | **Make sure to connect an UART adapter to the TinyFPGA BX.** Floating serial signals result in undefined behavior which can prevent the SoC to boot. I have an FTDI serial cable that plugs directly into pins soldered to the TinyFPGA BX, so I have configured tinyfpga_bx platform definition to add the serial peripheral with the following pins: 52 | 53 | ( 54 | "serial", 0, 55 | Subsignal("tx", Pins("C2")), 56 | Subsignal("rx", Pins("B1")), 57 | IOStandard("LVCMOS33") 58 | ) 59 | 60 | This could be easily reconfigured in any way that suits your application 61 | 62 | ## Caveats 63 | 64 | I have tried (not very hard) and failed to run the picorv32 CPU and also failed to run vexriscv variants other than lite. It is probably possible to get these to work, as well as lm32 or other CPUs, as long as the footprint isn't too large. 65 | -------------------------------------------------------------------------------- /tinyfpga_litex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import importlib 5 | import os 6 | 7 | from migen import Cat 8 | from migen.genlib.io import CRG 9 | 10 | from litex.build.generic_platform import Subsignal, IOStandard, Pins 11 | 12 | from litex.soc.integration.soc_core import * 13 | from litex.soc.integration.builder import * 14 | from litex.soc.cores import spi_flash 15 | from litex.soc.cores.gpio import GPIOOut 16 | 17 | from litex_boards.partner.platforms import tinyfpga_bx 18 | 19 | # Peripherals ------------------------------------------------------------------ 20 | 21 | # Comment out to use default pins (tx=GPIO:0 (A2), rx=GPIO:1 (A1)) 22 | tinyfpga_bx.serial = [ 23 | ( 24 | "serial", 0, 25 | Subsignal("tx", Pins("C2")), 26 | Subsignal("rx", Pins("B1")), 27 | IOStandard("LVCMOS33") 28 | ), 29 | ] 30 | 31 | # TinyFPGASoC ------------------------------------------------------------------ 32 | 33 | class TinyLiteX(SoCCore): 34 | """ 35 | Basic SoC for TinyFPGA BX 36 | """ 37 | 38 | # Add SPI flash to the Control and Status Register memory space 39 | csr_map = { 40 | "spiflash": 16, 41 | "leds": None, # csr_id not provided: allocate csr to the first available id 42 | } 43 | csr_map.update(SoCCore.csr_map) 44 | 45 | # Add SPI flash to the memory map 46 | mem_map = { 47 | "spiflash": 0x20000000, # (default shadow @0xa0000000) 48 | } 49 | mem_map.update(SoCCore.mem_map) 50 | 51 | def __init__(self, platform, **kwargs): 52 | 53 | # We need at least a serial port peripheral 54 | platform.add_extension(tinyfpga_bx.serial) 55 | 56 | sys_clk_freq = int(1e9/platform.default_clk_period) 57 | SoCCore.__init__(self, platform, clk_freq=sys_clk_freq, 58 | integrated_rom_size=0, 59 | integrated_main_ram_size=0, 60 | integrated_sram_size=10*1024, 61 | cpu_reset_address=0x20050000, 62 | **kwargs) 63 | 64 | # Configure the clock and reset generator 65 | self.submodules.crg = CRG(platform.request(platform.default_clk_name)) 66 | 67 | # Configure the TinyFPGA BX SPI flash 68 | self.submodules.spiflash = spi_flash.SpiFlash( 69 | platform.request("spiflash"), 70 | dummy=8, div=2, endianness="little" 71 | ) 72 | self.config["SPIFLASH_PAGE_SIZE"] = 256 73 | self.config["SPIFLASH_SECTOR_SIZE"] = 0x10000 74 | 75 | # Map the entire SPI flash 76 | spiflash_total_size = int((8/8)*1024*1024) 77 | self.register_mem("spiflash", self.mem_map["spiflash"], 78 | self.spiflash.bus, size=spiflash_total_size) 79 | 80 | # Configure a special region for the ROM (bootloader/bios) 81 | self.flash_boot_address = 0x20050000 + 0x8000 82 | self.add_memory_region("rom", self.cpu_reset_address, 0x8000) 83 | 84 | # Configure a special region for our user code (firmware) 85 | self.add_memory_region( 86 | "user_flash", 87 | self.flash_boot_address, 88 | # Leave a grace area- possible one-by-off bug in add_memory_region? 89 | # Possible fix: addr < origin + length - 1 90 | spiflash_total_size - (self.flash_boot_address - self.mem_map["spiflash"]) - 0x100 91 | ) 92 | 93 | # Configure the board LED 94 | leds = platform.request("user_led", 0) 95 | self.submodules.leds = GPIOOut(leds) 96 | 97 | # Need to specify '-s' flash isn't put into deep-sleep power down after bitstream loads 98 | platform.toolchain.nextpnr_build_template[2] = "icepack -s {build_name}.txt {build_name}.bin" 99 | 100 | # Build ------------------------------------------------------------------------ 101 | 102 | def main(): 103 | """ 104 | Program to build the TinyLiteX SoC 105 | """ 106 | 107 | # Configure command line arguments 108 | parser = argparse.ArgumentParser(description="TinyFPGA BX LiteX SoC") 109 | builder_args(parser) 110 | soc_core_args(parser) 111 | parser.add_argument("--platform", 112 | help="module name of the platform to build for", 113 | default="litex_boards.partner.platforms.tinyfpga_bx") 114 | parser.add_argument("--gateware-toolchain", default=None, 115 | help="FPGA gateware toolchain used for build") 116 | args = parser.parse_args() 117 | 118 | # Pick the platform module from litex 119 | platform_module = importlib.import_module(args.platform) 120 | 121 | # Load the platform module and toolchain 122 | if args.gateware_toolchain is not None: 123 | platform = platform_module.Platform(toolchain=args.gateware_toolchain) 124 | else: 125 | platform = platform_module.Platform() 126 | 127 | # Instantiate our custom SoC 128 | soc = TinyLiteX(platform, **soc_core_argdict(args)) 129 | 130 | # Set up the LiteX SoC Builder 131 | builder = Builder(soc, **builder_argdict(args)) 132 | 133 | # Add our runtime code package, relative to this SoC definition 134 | builder.add_software_package( 135 | "firmware", 136 | src_dir=os.path.abspath( 137 | os.path.join(os.path.dirname(__file__), "firmware") 138 | ) 139 | ) 140 | 141 | # Kick off the build 142 | builder.build() 143 | 144 | 145 | if __name__ == "__main__": 146 | main() 147 | --------------------------------------------------------------------------------