diff --git a/comm/iCE40/.gitignore b/comm/iCE40/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9bc6787c2c1112e1864425cc2fcf769159c197e3
--- /dev/null
+++ b/comm/iCE40/.gitignore
@@ -0,0 +1,6 @@
+/*.asc
+/*.bin
+/*.json
+/*.rpt
+/*.nplog
+/*.yslog
diff --git a/comm/iCE40/Makefile b/comm/iCE40/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..5ac6cd2b6e3a7a0d3c98e8e96f87def65d8aa156
--- /dev/null
+++ b/comm/iCE40/Makefile
@@ -0,0 +1,29 @@
+PROJ = ring
+
+all: $(PROJ).rpt $(PROJ).bin
+
+$(PROJ).json: $(PROJ).v
+	yosys -ql $(PROJ).yslog -p 'synth_ice40 -top top -json $@' $<
+
+$(PROJ).asc: $(PROJ).json icebreaker.pcf
+	nextpnr-ice40 -ql $(PROJ).nplog --up5k --package sg48 --freq 39.75 --asc $@ --pcf icebreaker.pcf --json $<
+
+$(PROJ).bin: $(PROJ).asc
+	icepack $< $@
+
+$(PROJ).rpt: $(PROJ).asc
+	icetime -d up5k -c 39.75 -mtr $@ $<
+
+prog: $(PROJ).bin
+	iceprog $<
+
+sudo-prog: $(PROJ).bin
+	@echo 'Executing prog as root!!!'
+	sudo iceprog $<
+
+clean:
+	rm -f $(PROJ).yslog $(PROJ).nplog $(PROJ).json $(PROJ).asc $(PROJ).rpt $(PROJ).bin
+	rm -f $(PROJ)_tb $(PROJ)_tb.vcd $(PROJ)_syn.v $(PROJ)_syntb $(PROJ)_syntb.vcd
+
+.SECONDARY:
+.PHONY: all prog clean
diff --git a/comm/iCE40/icebreaker.pcf b/comm/iCE40/icebreaker.pcf
new file mode 100644
index 0000000000000000000000000000000000000000..03088f26b53104d494d4d1df7fac29e09164d503
--- /dev/null
+++ b/comm/iCE40/icebreaker.pcf
@@ -0,0 +1,54 @@
+# 12 MHz clock
+set_io -nowarn CLK_12MHZ  35
+
+# RS232
+set_io -nowarn RX          6
+set_io -nowarn TX          9
+
+# LEDs and Button
+set_io -nowarn BTN_N      10
+set_io -nowarn LEDR_N     11
+set_io -nowarn LEDG_N     37
+
+# RGB LED Driver
+set_io -nowarn LED_RED_N  39
+set_io -nowarn LED_GRN_N  40
+set_io -nowarn LED_BLU_N  41
+
+# SPI Flash
+set_io -nowarn FLASH_SCK  15
+set_io -nowarn FLASH_SSB  16
+set_io -nowarn FLASH_IO0  14
+set_io -nowarn FLASH_IO1  17
+set_io -nowarn FLASH_IO2  12
+set_io -nowarn FLASH_IO3  13
+
+# PMOD 1A
+set_io -nowarn P1A1        4
+set_io -nowarn P1A2        2
+set_io -nowarn P1A3       47
+set_io -nowarn P1A4       45
+set_io -nowarn P1A7        3
+set_io -nowarn P1A8       48
+set_io -nowarn P1A9       46
+set_io -nowarn P1A10      44
+
+# PMOD 1B
+set_io -nowarn P1B1       43
+set_io -nowarn P1B2       38
+set_io -nowarn P1B3       34
+set_io -nowarn P1B4       31
+set_io -nowarn P1B7       42
+set_io -nowarn P1B8       36
+set_io -nowarn P1B9       32
+set_io -nowarn P1B10      28
+
+# LEDs and Buttons (PMOD 2)
+set_io -nowarn LED1       26
+set_io -nowarn LED2       27
+set_io -nowarn LED3       25
+set_io -nowarn LED4       23
+set_io -nowarn LED5       21
+set_io -nowarn BTN1       20
+set_io -nowarn BTN2       19
+set_io -nowarn BTN3       18
diff --git a/comm/iCE40/ring.v b/comm/iCE40/ring.v
new file mode 100644
index 0000000000000000000000000000000000000000..d4b0d02fe7864180df013a5543723a3a8541cb49
--- /dev/null
+++ b/comm/iCE40/ring.v
@@ -0,0 +1,144 @@
+// ring.v
+//
+// iCE40 FPGA communication ring test
+//
+// Erik Strand 9/30/21
+//
+// This work may be reproduced, modified, distributed,
+// performed, and displayed for any purpose, but must
+// acknowledge this project. Copyright is retained and
+// must be preserved. The work is provided as is; no
+// warranty is provided, and users accept all liability.
+//
+
+// Cause yosys to throw an error when we implicitly declare nets
+`default_nettype none
+
+`include "uart.v"
+
+module top (
+    input CLK_12MHZ,
+    output P1A1,
+    input P1A2,
+    input BTN_N,
+    output P1A3,
+    output P1A4,
+    output LEDR_N,
+    output LEDG_N
+);
+    // We use the PLL to generate a 39.75 MHz clock.
+    // Any faster than this and icetime complains.
+    wire clk;
+    wire clk_lock;
+    SB_PLL40_PAD #(
+        .FEEDBACK_PATH("SIMPLE"),
+        .PLLOUT_SELECT("GENCLK"),
+        .DIVR(4'b0000),
+        .DIVF(7'b0110100),
+        .DIVQ(3'b100),
+        .FILTER_RANGE(3'b001)
+    ) pll_clk (
+        .PACKAGEPIN(CLK_12MHZ),
+        .PLLOUTGLOBAL(clk),
+        .LOCK(clk_lock),
+        .RESETB(1'b1),
+        .BYPASS(1'b0)
+    );
+
+    // We use BTN_N to start the loop.
+    // BTN_N is low when pressed.
+    // This code produces a 1 clock cycle pulse on btn_up_pulse whenever the button is released.
+    wire btn_pressed = !BTN_N;
+    reg btn_sample_1 = 1'b0;
+    reg btn_sample_2 = 1'b0;
+    reg btn_sample_3 = 1'b0;
+    reg btn_sample_4 = 1'b0;
+    reg btn_up_pulse = 1'b0;
+    assign LEDR_N = !btn_pressed;
+    always @(posedge clk or negedge clk_lock) begin
+        if (!clk_lock) begin
+            btn_sample_1 <= 1'b0;
+            btn_sample_2 <= 1'b0;
+            btn_sample_3 <= 1'b0;
+            btn_sample_4 <= 1'b0;
+        end else begin
+            btn_sample_1 <= btn_sample_2;
+            btn_sample_2 <= btn_sample_3;
+            btn_sample_3 <= btn_sample_4;
+            btn_sample_4 <= btn_pressed;
+
+            btn_up_pulse <= 1'b0;
+            if (btn_sample_1 && btn_sample_2 && !btn_sample_3 && !btn_sample_4) begin
+                btn_up_pulse <= 1'b1;
+            end
+        end
+    end
+
+    // This is the UART module we use to send byte data.
+    // For receiving data, we use 8x oversampling.
+    // This restricts our baud rate to one eight the clock rate.
+    reg respond = 1'b0;
+    wire is_transmitting;
+    wire transmit = (btn_up_pulse || respond) && !is_transmitting;
+    reg [7:0] tx_byte = 8'd0;
+    wire received;
+    wire [7:0] rx_byte;
+    wire is_receiving;
+    wire receive_error;
+    uart #(
+        // We're running a little slower than this.
+        // But it's only the ratio sys_clk_freq / baud_rate that matters anyway.
+        .baud_rate(5000000),
+        .sys_clk_freq(40000000)
+    ) instance_name(
+        .clk(clk),                        // The master clock for this module
+        .rst(!clk_lock),                  // Synchronous reset
+        .rx(P1A2),                        // Incoming serial line
+        .tx(P1A1),                        // 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(receive_error)        // Indicates error in receiving packet.
+    );
+
+    // We use pins P1A3 and P1A4 to monitor the loop.
+    // P1A3 goes high for one clock cycle when we receive a byte.
+    // It's a helpful scope trigger if you want to catch every round trip.
+    // P1A4 goes high for one clock cycle whenever we reset the sent byte.
+    // It's a helpful scope trigger if you want to catch every 256 round trips.
+    reg tx_byte_overflow_pulse = 1'b0;
+    assign P1A3 = received;
+    assign P1A4 = tx_byte_overflow_pulse;
+
+    // We toggle the LED every time we complete 256 round trips.
+    // If things are going well this isn't all that useful, since it happens nearly 1,000 times per
+    // second. But if you try to overclock it you'll know when it stops working since you'll see it
+    // blink sporadically.
+	reg led_reg = 1'b0;
+	assign LEDG_N = led_reg;
+
+    // This is the main loop.
+    always @(posedge clk or negedge clk_lock) begin
+        // These are pulses that should always be reset.
+        respond <= 1'b0;
+        tx_byte_overflow_pulse <= 1'b0;
+
+        if (!clk_lock) begin
+            led_reg <= 1'b0;
+        end else begin
+            if (received) begin
+                tx_byte <= rx_byte + 1;
+                if (tx_byte == 8'd255) begin
+                    tx_byte <= 8'd0;
+                    led_reg <= !led_reg;
+                    tx_byte_overflow_pulse <= 1'b1;
+                end
+                respond <= 1'b1;
+            end
+        end
+    end
+
+endmodule
diff --git a/comm/iCE40/uart.v b/comm/iCE40/uart.v
new file mode 100644
index 0000000000000000000000000000000000000000..6cc9c088c2ea2d52082153b056c529a06f673c99
--- /dev/null
+++ b/comm/iCE40/uart.v
@@ -0,0 +1,319 @@
+// Documented Verilog UART
+// Copyright (C) 2010 Timothy Goddard (tim@goddard.net.nz)
+//               2013 Aaron Dahlen
+// Distributed under the MIT licence.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+//** INSTANTIATION ********************************************
+//
+//  To instantiate this module copy this section to your main code...
+//
+//    uart #(
+//        .baud_rate(baud_rate),            // default is 9600
+//        .sys_clk_freq(sys_clk_freq)       // default is 100000000
+//     )
+//    instance_name(
+//        .clk(clk),                        // The master clock for this module
+//        .rst(rst),                        // Synchronous reset
+//        .rx(rx),                          // Incoming serial line
+//        .tx(tx),                          // 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.
+//      //.recv_state(recv_state),          // for test bench
+//      //.tx_state(tx_state)               // for test bench
+//    );
+//
+
+`ifndef _dice_uart_v_
+`define _dice_uart_v_
+
+module uart(
+    input clk,                  // The master clock for this module
+    input rst,                  // Synchronous reset
+    input rx,                   // Incoming serial line
+    output tx,                  // Outgoing serial line
+    input transmit,             // Assert to begin transmission
+    input [7:0] tx_byte,        // Byte to transmit
+    output received,            // Indicates that a byte has been received
+    output [7:0] rx_byte,       // Byte received
+    output wire is_receiving,   // Low when receive line is idle.
+    output wire is_transmitting,// Low when transmit line is idle.
+    output wire recv_error,      // Indicates error in receiving packet.
+    output reg [3:0] rx_samples,
+    output reg [3:0] rx_sample_countdown
+);
+
+// The clock_divider is calculated using baud_rate and sys_clk_freq.
+// To modify baud rate you can modify the defaults shown below or instantiate
+// the module using the template shown in the INSTANTIATION section above.
+// For aditional information about instantiation please see:
+// http://www.sunburst-design.com/papers/CummingsHDLCON2002_Parameters_rev1_2.pdf
+
+    parameter baud_rate = 9600;
+    parameter sys_clk_freq = 100000000;
+
+    localparam one_baud_cnt = sys_clk_freq / (baud_rate);
+
+//** SYMBOLIC STATE DECLARATIONS ******************************
+
+    localparam [2:0]
+        RX_IDLE             = 3'd0,
+        RX_CHECK_START      = 3'd1,
+        RX_SAMPLE_BITS      = 3'd2,
+        RX_READ_BITS        = 3'd3,
+        RX_CHECK_STOP       = 3'd4,
+        RX_DELAY_RESTART    = 3'd5,
+        RX_ERROR            = 3'd6,
+        RX_RECEIVED         = 3'd7;
+
+    localparam [1:0]
+        TX_IDLE             = 2'd0,
+        TX_SENDING          = 2'd1,
+        TX_DELAY_RESTART    = 2'd2,
+        TX_RECOVER          = 2'd3;
+
+
+//** SIGNAL DECLARATIONS **************************************
+
+    reg [log2(one_baud_cnt * 16)-1:0] rx_clk;
+    reg [log2(one_baud_cnt * 2)-1:0] tx_clk;
+
+    reg [2:0] recv_state = RX_IDLE;
+    reg [3:0] rx_bits_remaining;
+    reg [7:0] rx_data;
+
+    reg tx_out = 1'b1;
+    reg [1:0] tx_state = TX_IDLE;
+    reg [3:0] tx_bits_remaining;
+    reg [7:0] tx_data;
+
+
+//** ASSIGN STATEMENTS ****************************************
+
+    assign received = recv_state == RX_RECEIVED;
+    assign recv_error = recv_state == RX_ERROR;
+    assign is_receiving = recv_state != RX_IDLE;
+    assign rx_byte = rx_data;
+
+    assign tx = tx_out;
+    assign is_transmitting = tx_state != TX_IDLE;
+
+
+//** TASKS / FUNCTIONS ****************************************
+
+    function integer log2(input integer M);
+        integer i;
+    begin
+        log2 = 1;
+        for (i = 0; 2**i <= M; i = i + 1)
+            log2 = i + 1;
+    end endfunction
+
+
+//** Body *****************************************************
+
+    always @(posedge clk) begin
+        if (rst) begin
+            recv_state = RX_IDLE;
+            tx_state = TX_IDLE;
+        end
+
+        // Countdown timers for the receiving and transmitting
+        // state machines are decremented.
+
+        if(rx_clk) begin
+            rx_clk = rx_clk - 1'd1;
+        end
+
+        if(tx_clk) begin
+            tx_clk = tx_clk - 1'd1;
+        end
+
+
+//** Receive state machine ************************************
+
+        case (recv_state)
+            RX_IDLE: begin
+                // A low pulse on the receive line indicates the
+                // start of data.
+                if (!rx) begin
+                    // Wait 1/2 of the bit period
+                    rx_clk = one_baud_cnt / 2;
+                    recv_state = RX_CHECK_START;
+                end
+            end
+
+            RX_CHECK_START: begin
+                if (!rx_clk) begin
+                    // Check the pulse is still there
+                    if (!rx) begin
+                        // Pulse still there - good
+                        // Wait the bit period plus 3/8 of the next
+                        rx_clk = (one_baud_cnt / 2) + (one_baud_cnt * 3) / 8;
+                        rx_bits_remaining = 8;
+                        recv_state = RX_SAMPLE_BITS;
+                        rx_samples = 0;
+                        rx_sample_countdown = 5;
+                    end else begin
+                        // Pulse lasted less than half the period -
+                        // not a valid transmission.
+                        recv_state = RX_ERROR;
+                    end
+                end
+            end
+
+            RX_SAMPLE_BITS: begin
+                // sample the rx line multiple times
+                if (!rx_clk) begin
+                    if (rx) begin
+                        rx_samples =  rx_samples + 1'd1;
+                    end
+                    rx_clk = one_baud_cnt / 8;
+                    rx_sample_countdown = rx_sample_countdown -1'd1;
+                    recv_state = rx_sample_countdown ? RX_SAMPLE_BITS : RX_READ_BITS;
+                end
+            end
+
+            RX_READ_BITS: begin
+                if (!rx_clk) begin
+                    // Should be finished sampling the pulse here.
+                    // Update and prep for next
+                    if (rx_samples > 3) begin
+                        rx_data = {1'd1, rx_data[7:1]};
+                    end else begin
+                        rx_data = {1'd0, rx_data[7:1]};
+                    end
+
+                    rx_clk = (one_baud_cnt * 3) / 8;
+                    rx_samples = 0;
+                    rx_sample_countdown = 5;
+                    rx_bits_remaining = rx_bits_remaining - 1'd1;
+
+                    if(rx_bits_remaining)begin
+                        recv_state = RX_SAMPLE_BITS;
+                    end else begin
+                        recv_state = RX_CHECK_STOP;
+                        rx_clk = one_baud_cnt / 2;
+                    end
+                end
+            end
+
+            RX_CHECK_STOP: begin
+                if (!rx_clk) begin
+                    // Should resume half-way through the stop bit
+                    // This should be high - if not, reject the
+                    // transmission and signal an error.
+                    recv_state = rx ? RX_RECEIVED : RX_ERROR;
+                end
+            end
+
+
+
+            RX_ERROR: begin
+                // There was an error receiving.
+                // Raises the recv_error flag for one clock
+                // cycle while in this state and then waits
+                // 2 bit periods before accepting another
+                // transmission.
+                rx_clk = 8 * sys_clk_freq / (baud_rate);
+                recv_state = RX_DELAY_RESTART;
+            end
+
+    // why is this state needed?  Why not go to idle and wait for next?
+
+            RX_DELAY_RESTART: begin
+                // Waits a set number of cycles before accepting
+                // another transmission.
+                recv_state = rx_clk ? RX_DELAY_RESTART : RX_IDLE;
+            end
+
+
+            RX_RECEIVED: begin
+                // Successfully received a byte.
+                // Raises the received flag for one clock
+                // cycle while in this state.
+                recv_state = RX_IDLE;
+            end
+
+        endcase
+
+
+//** Transmit state machine ***********************************
+
+        case (tx_state)
+            TX_IDLE: begin
+                if (transmit) begin
+                    // If the transmit flag is raised in the idle
+                    // state, start transmitting the current content
+                    // of the tx_byte input.
+                    tx_data = tx_byte;
+                    // Send the initial, low pulse of 1 bit period
+                    // to signal the start, followed by the data
+                  //  tx_clk_divider =  clock_divide;
+                    tx_clk = one_baud_cnt;
+                    tx_out = 0;
+                    tx_bits_remaining = 8;
+                    tx_state = TX_SENDING;
+                end
+            end
+
+            TX_SENDING: begin
+                if (!tx_clk) begin
+                    if (tx_bits_remaining) begin
+                        tx_bits_remaining = tx_bits_remaining - 1'd1;
+                        tx_out = tx_data[0];
+                        tx_data = {1'b0, tx_data[7:1]};
+                        tx_clk = one_baud_cnt;
+                        tx_state = TX_SENDING;
+                    end else begin
+                        // Set delay to send out 2 stop bits.
+                        tx_out = 1;
+                        tx_clk = 2 * one_baud_cnt;
+                        tx_state = TX_DELAY_RESTART;
+                    end
+                end
+            end
+
+            TX_DELAY_RESTART: begin
+                // Wait until tx_countdown reaches the end before
+                // we send another transmission. This covers the
+                // "stop bit" delay.
+                if (!tx_clk) begin
+                    tx_state = TX_RECOVER;
+                end
+            end
+
+            TX_RECOVER: begin
+                // Wait unitil the transmit line is deactivated.  This prevents repeated characters
+                tx_state = transmit ? TX_RECOVER : TX_IDLE;
+
+            end
+
+        endcase
+    end
+
+endmodule
+
+`endif