The Cx Programming Language

Cx aims to bring digital hardware design within reach of any developer.

The language focuses on being intuitive, easy to learn and use, and elegant; we try to honor the principle of least surprise. An application written in Cx is described as a set of sequential tasks connected together and executed concurrently, which is probably the most natural way to describe parallelism that corresponds to how hardware behaves. The language has a familiar C-like syntax (but is not based on C), features strong bit-accurate static typing, and allows cycle-accurate behavior to be implicitly expressed with structured code. Cx was created by Synflow.

Example of a Cx task

To illustrate how Cx works, we'll see a simple real-world implementation of the classic serial communication protocol present in RS-232 and others. This protocol defines how bytes are exchanged between UARTs (Universal Asynchronous Receiver/Transmitter). Each byte is transmitted as a frame that starts with one logic low start bit, followed by eight data bits, and ends with one or two logic high stop bits.

We have implemented in a Cx single task a UART receiver that repeatedly reads a frame composed of individual bits and outputs a byte. The task can be configured at compile-time to use one or two stop bits. The code is written to handle bits that are sent at a slower pace than the clock at which the receiver is running. This is because legacy UARTs are often quite slow; think of a 56k modem, transmitting 56,000 bits per second when even low-end microcontrollers run at a few MHz.

package uart;

task UartSerialRx {
  sync { in bool bit; out u8 dout; }

  u8 data;
  u4 count;

  u2 countStop;
  const u2 numStopBits = 1;

  void loop() {
    if (!bit.read()) {
      while (count != 8) {
        data >>= 1;
        data[7] = bit.read();
        count++;
      }

      while (countStop != numStopBits) {
        if (bit.available()) {
          countStop++;
        }
      }

      count = 0;
      countStop = 0;
      dout.write(data);
    }
  }

}

Here is the state machine created by this code:

The first loop comes from the if (!bit.read()) test: if the condition is false, control reaches the end of the loop function, and as its name indicates, it loops back to the initial state. The following two loops correspond to the two while loops. The last edge from 2 to 0 executes when there are no more stop bits to read, counters are reset, the task writes data to the output port, and control reaches the end of the loop function, and loops back to the initial state.

The task declares an input port bit and an output port dout, both of which are synchronized (keyword sync) which means that there is an additional boolean signal indicating the presence or absence of data on the port. Synchronized ports have a blocking read behavior, in other words reading from a port when there is no data on it will stall the task; it is possible to check whether data is available with the available property.

Comparison with VHDL

The code below shows an equivalent implementation written in VHDL, which is one of the two main Hardware Description Languages. Most of things that are implicit in Cx must be made explicit in VHDL, such as the state machine and reset behavior. While the Cx code was structured, the VHDL code is laid out as a big switch/case where each when clause takes a cycle; if you need to update the code for example to insert an idle cycle in a state, you must add a new state and update transitions. This is not specific to VHDL, in fact all HDLs in general suffer from the same problem.

-------------------------------------------------------------------------------
-- UART receiver
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

library std;
use std.textio.all;

library work;
use work.Helper_functions.all;

-------------------------------------------------------------------------------
entity UartSerialRx is
  port (
    clock        : in  std_logic;
    reset_n      : in  std_logic;
    receive      : in  std_logic;
    receive_send : in  std_logic;
    data_o       : out std_logic_vector(7 downto 0);
    data_o_send  : out std_logic);
end UartSerialRx;

-------------------------------------------------------------------------------
architecture rtl_UartSerialRx of UartSerialRx is

  signal data            : std_logic_vector(7 downto 0);
  signal count           : std_logic_vector(3 downto 0);
  signal count_stop_bits : std_logic_vector(1 downto 0);
  constant n_stop_bits   : std_logic_vector(1 downto 0) := "10";

  type FSM_type is (START_BIT, DATA_BIT, STOP_BIT);
  signal FSM : FSM_type;

begin

  UartSerialRx_execute : process (reset_n, clock) is
    variable local_data_4 : unsigned(7 downto 0);
  begin
    if reset_n = '0' then
      data            <= (others => '0');
      count           <= x"0";
      count_stop_bits <= "00";
      data_o          <= (others => '0');
      data_o_send     <= '0';
      FSM             <= START_BIT;
      --
    elsif rising_edge(clock) then
      --
      case FSM is
        when START_BIT =>
          data_o_send <= '0';
          if (receive_send = '1' and receive = '0') then
            FSM <= DATA_BIT;
          elsif (receive_send = '1' and receive = '1') then
            FSM <= STOP_BIT;
          end if;
          
        when DATA_BIT =>
          if (receive_send = '1' and count /= x"8") then
            data(to_integer(unsigned(count))) <= receive;
            count       <= count + x"1";
          elsif (count = x"8") then
            FSM   <= STOP_BIT;
            count <= x"0";
          end if;
          
        when STOP_BIT =>
          if (count_stop_bits = n_stop_bits) then
            count_stop_bits <= "00";
            data_o          <= data;
            data_o_send     <= '1';
            FSM             <= START_BIT;
          elsif (receive_send = '1') then
            count_stop_bits <= count_stop_bits + "01";
          end if;
      end case;
    end if;
  end process UartSerialRx_execute;

end architecture rtl_UartSerialRx;

How to get started with Cx?

We invite you to follow our online course with everything that you need if you want to get started with the Cx programming language, even if you've never designed hardware before.

Why should I learn Cx?

If you want to get started with hardware design and FPGAs, Cx is the language that is the most intuitive and the easiest to learn and use. If you already know hardware design and are looking for better solutions than VHDL and Verilog, Cx will allow you to get more work done with much less pain. You may even have tried various HLS C/C++/SystemC-to-gates/RTL/HDL "magical" tools but were disappointed? Rest assured, this is not HLS; Cx lets you design in peace.

If you have any other questions, feel free to contact us!