Custom SoC made with an ICE40 FPGA, and an Assembler - Page 1
Forum section: electronics / projects

author: Omega Glory
author_rank: Regular Contributor
timestamp: September 02, 2020, 01:41:43 am
content: This summer between semesters I decided to learn more about FPGAs so I began working on a simple SoC. It consists of an 8-bit CPU, an 80 column VGA graphics card, GPIO and counter/timer peripherals, and a UART, all implemented on an ice40lp8k FPGA. It also comes with an assembler and utilities for loading programs into the internal block memory without having to rerun synthesis and place-and-route.The CPU is an 8-bit RISC core, with a Harvard architecture. It has a 16-bit wide instruction memory, an 8-bit wide data memory, and both have a 16-bit address. The CPU has 16 general purpose 8-bit registers along with a 4-bit status register. The processor is not fully pipelined, but does fetch the next instruction while executing the current one. Most instructions execute in a single clock cycle, but a few take two or three. Nextpnr says that it can be clocked up to about 17 or 18MHz, but I've only tested it at 16Mhz.The GPU operates in a monochrome 80 column text mode, and outputs a VGA signal at a resolution of 640 by 480 at 60 frames per second. The GPU contains an ASCII buffer which the user can write to in order to display messages on the screen. A control register allows the user to set the text to one of 7 colors, and to enable an interrupt to the CPU which fires every time a frame finishes and enters the blanking period. The GPU uses a 25Mhz clock, because it's to derive the synchronization signals from it.The assembler is written in Python, and generates code in two passes of the source file. It can handle things like symbol definitions, and has expression evaluation, and some helpful directives.Documentation is in progress, but I made some colorful diagrams of the CPU data-path and the GPU, as well as some diagrams showing how the assembly instructions are encoded in binary. If you're curious you can find more info on my GitHub at:https://github.com/ept221/tinySoC. I would love to hear what you guys think, good or bad. I'm especially interested in what you think are good features or flaws of the instruction set, and how I might improve if I were to make a design in the future. Also, this was my first serious project in Verilog (or FPGAs for that matter), so if you happen to see something terrible while browsing the source, your criticism would be appreciated.

author: Omega Glory
author_rank: Regular Contributor
timestamp: September 02, 2020, 02:03:59 am
content: Here's a picture of the SoC running a simple demo program, along with the source file, and the debugging output of the assembler. The program polls the UART, and when a char is received, it displays it on the screen, while handling backspaces as delete and displaying a cursor. It also displays each char in binary on the LEDs attached to the GPIO, and echos the char back over the UART. This is a simple demo of most of the features, except it doesn't include the use of counter/timers, or the interrupt system. The Arduino that you see in the picture is only acting as a USB to serial bridge for the FPGA.Code:;******************************************************************************.define dir_reg, 0x00.define port_reg, 0x01.define pin_reg, 0x02.define uart_baud, 0x0A.define uart_ctrl, 0x0B.define uart_buffer, 0x0C.define gpu_addr, 0x2000.define gpu_ctrl_reg, 0x80;******************************************************************************.codeldi r0, 0xff ; set all gpio to outputout r0, dir_regldi r0, 0b00011100 ; setup the gpuout r0, gpu_ctrl_regldi r2, gpu_addr ; setup the pointer to the v-ramldi r3, gpu_addrstable: in r0, gpu_ctrl_reg ; wait for gpu clock to become stableani r0, 0x80jz stableldi r0, 103 ; set the baud rate to 9600out r0, uart_baudloop1: in r0, uart_ctrl ; poll for full rx bufferani r0, 1jz loop1in r0, uart_buffer ; capture the dataloop2: in r1, uart_ctrl ; poll for empty tx bufferani r1, 2jz loop2out r0, uart_buffer ; echo the char back over the uartout r0, port_reg ; write the data to the gpio portcpi r0, 8 ; check if delete was sentjnz normalldi r0, 32srd r0, p2 ; print space to clear cursor, and move backldi r0, 95str r0, p2 ; delete char and print cursorjmp loop1normal: sri r0, p2 ; write the data to the screenldi r0, 95str r0, p2jmp loop1 ; go get another char;******************************************************************************

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 02, 2020, 10:18:10 am
content: Big project, well done!

author: voltsandjolts
author_rank: Unknown
timestamp: September 02, 2020, 10:34:02 am
content: Nice project, very well done.I predict a bright future and an interesting career ahead of you!

author: rstofer
author_rank: Super Contributor
timestamp: September 02, 2020, 11:41:31 pm
content: This is a great project! I wonder if there should be a link to this thread from the FPGA forum.

author: lintweaker
author_rank: Contributor
timestamp: September 03, 2020, 06:57:01 am
content: Cool project! Impressive for a first FPGA project.

author: chickenHeadKnob
author_rank: Super Contributor
timestamp: September 03, 2020, 08:37:43 am
content: Nice, you put some love into this. I did not easily see which ice40 and what kind clock rate you are achieving. I would recommend making that prominent in your post and github landing page. I would assume ice40up5k for the built in memory. I understand this is a slow chip.

author: Omega Glory
author_rank: Regular Contributor
timestamp: September 03, 2020, 10:24:43 pm
content: Okay, good to know, I'll add that information. Right now I'm synthesizing it on an ice40lp8k. The main processor runs at 16MHz, though nextpnr's timing says that I could clock it at about 17 or 18 MHz. I'm not entirely sure what the critical path is, (or how to find it), but I suspect that my control logic is likely the source of some of the longer propagation delays. The GPU is running at 25MHz, because it's convenient for generating the VGA signals. The fact that I had two clocks in the design made it tricky when I had to pass signals from one clock domain to another, but I needed to learn how to do that sometime anyway.