From dcea278f10aeb58b42a23b5a2ab2483f4e44e170 Mon Sep 17 00:00:00 2001 From: Steffen Moeller Date: Wed, 14 Sep 2016 15:11:39 +0200 Subject: [PATCH] Second example demonstrating input and output asynchronicity --- .gitignore | 2 + Makefile | 34 ++++++-- README.md | 61 +++++++++++++- uart_adder.v | 207 ++++++++++++++++++++++++++++++++++++++++++++++ uart_adder_host.c | 174 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 471 insertions(+), 7 deletions(-) create mode 100644 uart_adder.v create mode 100644 uart_adder_host.c diff --git a/.gitignore b/.gitignore index a0cf9c9..69f1888 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.bin *.blif *.tiles +*_host +a.out diff --git a/Makefile b/Makefile index 1ae8304..0cb230d 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,17 @@ PROG = iceprog TOP = uart_demo.v PCF = icestick.pcf DEVICE = 1k +PATHTODEVICE = /dev/ttyUSB1 -OUTPUT = $(patsubst %.v,%.bin,$(TOP)) +BITSTREAM = $(patsubst %.v,%.bin,$(TOP)) +HOST = $(patsubst %.v,%_host,$(TOP)) + +ifeq (uart_adder.v,$(TOP)) +all: $(BITSTREAM) $(HOST) +else +all: $(BITSTREAM) +endif -all: $(OUTPUT) %.bin: %.tiles $(GEN) $< $@ @@ -21,9 +28,26 @@ all: $(OUTPUT) $(SYN) -p "read_verilog $<; synth_ice40 -flatten -blif $@" clean: - rm -f *.bin *.blif *.tiles + rm -f *.bin *.blif *.tiles uart_adder_host -flash: $(OUTPUT) +flash: $(BITSTREAM) $(PROG) $< -.PHONY: all clean flash +run: + for i in $$(seq 0 3); do \ + for j in $$(seq 0 3); do \ + sudo ./uart_adder_host $(PATHTODEVICE) $$i $$j ; \ + done ; \ + done + +iverilog: + iverilog $(TOP) + +uart_demo_host: + # No action + +uart_adder_host: uart_adder_host.c + $(CC) $(CFLAGS) -o $@ $< + + +.PHONY: all clean flash uart_demo_host diff --git a/README.md b/README.md index 82344e8..6eb3577 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,27 @@ [![Build Status](https://jenkins.cyrozap.com/job/iCEstick-UART-Demo/badge/icon)](https://jenkins.cyrozap.com/job/iCEstick-UART-Demo/) [![License](http://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +The iCEstick is a low-cost FPGA by Lattice Semiconductors. It +directly attaches to a computer's USB port and is ideal for smallish +(sub-)projects or to just learn about the technology. The community +provides a free pipeline to program the device. A major beginner's +hurdle remains to interact with the device. A common approach is to +implement a serial interface. This project provides such an UART with +a very clear terminology. It chose a straight-forward implementation +that is easily followed by starters, with two examples to lay out +how to embedd this functionality in applications. + + ## Prerequisites - [Yosys][1] - [arachne-pnr][2] - [IceStorm][3] +For a quick setup, the Debian Linux distribution provides respective +[packages][4]. The iCEstick can be used in virtual environments like +VirtualBox. + ## Building git clone https://github.com/cyrozap/iCEstick-UART-Demo.git @@ -18,10 +33,52 @@ ## Flashing -Plug in your iCEstick, then run `make flash`. Depending on your permissions, you -may need to run it with `sudo`. +Plug in your iCEstick, then run `make flash`. Depending on your +permissions, you may need to run it with `sudo`. + +## Running + +This project provides two applications. These share the exact same UART +implementation, but have other differences for whwhich the iCEstick +needs to be reflashed when changinging between the two. + +### uart\_demo + +This implements an "echo": The character sent to the FPGA is immediately +returned. To investigate, start a terminal program like minicom or +teraterm, under Linux the tool screen is exceptionally handy. Check the +respective documentation to learn how to specify the device (/dev/ttyUSB0 +or /dev/ttyUSB1) and the BAUD rate (9600). + +### uart\_adder + +After two bytes have been sent to the FPGA, the FPGA adds the two values +together and returns the input values and the sum. The communication +could still be performed with a terminal tool, but only a subset of +all byte values is displayed as ASCII. A small programm written in C, +uart\_adder\_host, performs the communication with the FPGA, instead. It +takes the path to the device, the first byte and the second as arguments +on the command line. It shows what is sent and what is received, byte +per byte. + +It should be noted that all input data (the two bytes) are stored in a +single C struct and that data structure is copied bytewise under complete +neglect of the underlying data structure. This can be as easily decomposed +on the FPGA side, which would work for arbitrary data structures + +On the FPGA-side, uart\_adder implements two layers of +finite-state-machines (FSM). The upper layer circles between the +receive-compute-send states. The reading and sending have substates for +each byte and the interim time it needs to receive/send it. + +To run, do +``` +make TOP=uart_adder.v flash +sudo ./uart_adder_host /dev/ttyUSB1 4 5 +``` [1]: http://www.clifford.at/yosys/ [2]: https://github.com/cseed/arachne-pnr [3]: http://www.clifford.at/icestorm/ +[4]: http://wiki.debian.org/FPGA/Lattice diff --git a/uart_adder.v b/uart_adder.v new file mode 100644 index 0000000..fffa39c --- /dev/null +++ b/uart_adder.v @@ -0,0 +1,207 @@ +/* + * Copyright 2015 Forest Crossman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +`include "cores/osdvu/uart.v" + +module top( + input iCE_CLK, + input RS232_Rx_TTL, + output RS232_Tx_TTL, + output LED0, + output LED1, + output LED2, + output LED3, + output LED4 + ); + + wire reset = 0; + reg transmit; + reg [7:0] tx_byte; + wire received; + wire [7:0] rx_byte; + wire is_receiving; + wire is_transmitting; + wire recv_error; + + assign LED4 = recv_error; + //assign {LED3, LED2, LED1, LED0} = rx_byte[7:4]; + assign {LED3, LED2, LED1, LED0} = rx_byte[3:0]; + + uart #( + .baud_rate(9600), // The baud rate in kilobits/s + .sys_clk_freq(12000000) // The master clock frequency + ) + uart0( + .clk(iCE_CLK), // The master clock for this module + .rst(reset), // Synchronous reset + .rx(RS232_Rx_TTL), // Incoming serial line + .tx(RS232_Tx_TTL), // Outgoing serial line + .transmit(transmit), // Signal to transmit + .tx_byte(tx_byte), // Byte to transmit + .received(received), // Indicated that a byte has been received + .rx_byte(rx_byte), // Byte received + .is_receiving(is_receiving), // Low when receive line is idle + .is_transmitting(is_transmitting),// Low when transmit line is idle + .recv_error(recv_error) // Indicates error in receiving packet. + ); + + // input and output to be communicated + reg [15:0] vinput=16'b0; // input and output are reserved keywords + reg [23:0] voutput=24'b0; + + + reg [2:0] writecount=write_A; + reg [1:0] readcount =read_A; + + parameter STATE_RECEIVING = 2'd0; + parameter STATE_CALCULATING = 2'd1; + parameter STATE_SENDING = 2'd2; + //parameter STATE_SEND_COMPLETED = 2'b11; + + parameter read_A = 2'd0; + parameter read_A_transition_B = 2'd1; + parameter read_B = 2'd2; + + parameter write_A = 3'd0; + parameter write_A_transit_B = 3'd1; + parameter write_B = 3'd2; + parameter write_B_transit_AplusB = 3'd3; + parameter write_AplusB = 3'd4; + parameter write_done = 3'd5; + + reg ready=1; + + reg [1:0] state=STATE_RECEIVING; + + + + always @(posedge iCE_CLK) begin + + case (state) + + STATE_RECEIVING: begin + transmit <= 0; + case (readcount) + read_A: begin + if(received) begin + vinput[7:0]<=8'b0; + vinput[15:8]<=rx_byte; + readcount <= read_A_transition_B; + end + end + + read_A_transition_B: begin + if(~received) begin + readcount <= read_B; + end + end + + read_B: begin + if(received) begin + vinput[7:0]<=rx_byte; + state<=STATE_CALCULATING; + readcount <= read_A; + end + end + + default: begin + // should not be reached + state<=STATE_CALCULATING; + end + endcase + end + + STATE_CALCULATING: begin + writecount <= write_A; + voutput[7:0] <= vinput[15:8]+vinput[7:0]; + voutput[23:8] <= vinput[15:0]; + state <= STATE_SENDING; + end + + STATE_SENDING: begin + + + case (writecount) + + write_A: begin + if (~ is_transmitting) begin + writecount <= write_A_transit_B; + tx_byte <= voutput[23:16]; + //tx_byte <= vinput[15:8]; + transmit <= 1; + state <= STATE_SENDING; + end + end + + write_A_transit_B: begin + if ( is_transmitting) begin + writecount <= write_B; + transmit <= 0; + end + end + + write_B: begin + if (~ is_transmitting) begin + writecount <= write_B_transit_AplusB; + tx_byte <= voutput[15:8]; + //tx_byte <= vinput[7:0]; + transmit <= 1; + state <= STATE_SENDING; + end + end + + write_B_transit_AplusB: begin + if ( is_transmitting) begin + transmit <= 0; + writecount <= write_AplusB; + end + end + + write_AplusB: begin + if (~ is_transmitting) begin + tx_byte = voutput[7:0]; + transmit <= 1; + writecount <= write_done; + state <= STATE_SENDING; + end + end + + write_done: begin + if (~ is_transmitting) begin + writecount <= write_A; + state <= STATE_RECEIVING; + transmit <= 0; + end + end + + endcase + + end + + default: begin + // should not be reached + state <= STATE_RECEIVING; + readcount <= read_A; + end + + endcase + + end + + + +endmodule diff --git a/uart_adder_host.c b/uart_adder_host.c new file mode 100644 index 0000000..7940f81 --- /dev/null +++ b/uart_adder_host.c @@ -0,0 +1,174 @@ +// tty interface follows StackOverflow.com +// http://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c?rq=1 +// +// Adaptation for interaction with iCEstick HX40 1K FPGA to support the 2-byte-adder example +// by Steffen Moeller, Niendorf/Ostsee 2016 + +#include +#include +#include +#include +#include +#include +#include + +int +set_interface_attribs (int USB, int speed, int parity) +{ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (USB, &tty) != 0) + { + fprintf(stderr,"error %d from tcgetattr", errno); + return(-1); + } + + cfsetospeed (&tty, speed); + cfsetispeed (&tty, speed); + + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars + // disable IGNBRK for mismatched speed tests; otherwise receive break + // as \000 chars + tty.c_iflag &= ~IGNBRK; // disable break processing + tty.c_lflag = 0; // no signaling chars, no echo, + // no canonical processing + tty.c_oflag = 0; // no remapping, no delays + tty.c_cc[VMIN] = 0; // read doesn't block + tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl + + tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls, + // enable reading + tty.c_cflag &= ~(PARENB | PARODD); // shut off parity + tty.c_cflag |= parity; + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CRTSCTS; + + if (tcsetattr (USB, TCSANOW, &tty) != 0) + { + fprintf(stderr,"error %d from tcsetattr", errno); + return(-1); + } + return(0); +} + +void +set_blocking (int USB, int should_block) +{ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (USB, &tty) != 0) + { + fprintf(stderr,"error %d from tggetattr", errno); + return; + } + + tty.c_cc[VMIN] = should_block ? 1 : 0; + tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + if (tcsetattr (USB, TCSANOW, &tty) != 0) + fprintf(stderr,"error %d setting term attributes", errno); +} + +struct __attribute__((packed)) input { + unsigned char a; + unsigned char b; +} input; + +struct __attribute__((packed)) output { + unsigned char a; + unsigned char b; + unsigned char aplusb; +} output; + +unsigned char* const write_n_and_read_m (const int device, + unsigned char const * const writeme, const int n, + unsigned char * const readtome,const int m) { + + int n_written = 0, + spot_w = 0; + + int n_read = 0, + spot_r = 0; + + + //fprintf(stderr,"sizeof(struct input): %d\n",sizeof(struct input)); + //fprintf(stderr,"sizeof(struct output): %d\n",sizeof(struct output)); + + + // Write: + + while( (n>0 && n>spot_w) || (0==n && strlen((char *)writeme)>spot_w)) { + n_written = write( device, writeme+spot_w, 1); + spot_w += n_written; + fprintf(stderr,"Written: %d, n=%d, n_written=%d, spot_w=%d\n", *(writeme+spot_w-n_written), n, n_written, spot_w); + usleep(350); + } + + //fprintf(stderr,"Now reading: n=%d, n_written=%d, spot_w=%d\n", n, n_written, spot_w); + //fprintf(stderr," m=%d, m_written=%d, spot_r=%d\n", n, n_read, spot_r); + + // Read: + + do { + unsigned char buf = '\0'; + n_read = read( device, &buf, 1 ); + readtome[spot_r]=buf; + spot_r += n_read; + fprintf(stderr,"Read character # %d ! m=%d, n_read=%d, spot_r=%d\n",buf,m,n_read,spot_r); + + if (n_read < 0) { + fprintf(stderr,"Error %d reading: %s\n", errno, strerror(errno)); + } + else if (n_read == 0) { + fprintf(stderr,"Read nothing!\n"); + } + } while ( (m>0 && spot_r 0); + } + + + set_blocking (USB, 1); // set blocking + + input.a=(unsigned char) atoi(argv[2]); + input.b=(unsigned char) atoi(argv[3]); + + fprintf(stderr,"I: sending A=%d (from '%s') and B=%d (from '%s') to the device.\n", + input.a, argv[2], input.b, argv[3]); + write_n_and_read_m(USB,(unsigned char *) &input, sizeof (struct input), + (unsigned char *) &output, sizeof (struct output)); + fprintf(stderr,"Input : A=%d, B=%d\n", input.a,input.b); + fprintf(stderr,"Output : A=%d, B=%d, A+B=%d\n",output.a,output.b,output.aplusb); + + close(USB); + + return(0); +}