Conversion examples — MyHDL 0.10 documentation

# Conversion examples

## Introduction

In this chapter, we will demonstrate the conversion process with a number of examples. For the concepts of MyHDL conversion, read the companion chapter Conversion to Verilog and VHDL.

## A small sequential design

Consider the following MyHDL code for an incrementer block:

    from myhdl import block, always_seq

    @block
    def inc(count, enable, clock, reset):
        """ Incrementer with enable.

        count -- output
        enable -- control input, increment when 1
        clock -- clock input
        reset -- asynchronous reset input
        """

        @always_seq(clock.posedge, reset=reset)
        def seq():
            if enable:
                count.next = count + 1

        return seq

This design can be converted to Verilog and VHDL. The first step is to elaborate it, just as we do for simulation. Then we can use the `convert` method on the elaborated instance.

    from myhdl import Signal, ResetSignal, modbv

    from inc import inc

    def convert_inc(hdl):
        """Convert inc block to Verilog or VHDL."""

        m = 8

        count = Signal(modbv(0)[m:])
        enable = Signal(bool(0))
        clock  = Signal(bool(0))
        reset = ResetSignal(0, active=0, async=True)

        inc_1 = inc(count, enable, clock, reset)

        inc_1.convert(hdl=hdl)

    convert_inc(hdl='Verilog')
    convert_inc(hdl='VHDL')

For flexibility, we wrap the conversion in a `convert_inc` function. `inc_1` is an elaborated design instance that provides the conversion method.

The conversion to Verilog generates an equivalent Verilog module in file `inc.v`. The Verilog code looks as follows:

    // File: inc.v
    // Generated by MyHDL 1.0dev
    // Date: Sun May 22 18:46:37 2016

    `timescale 1ns/10ps

    module inc (
        count,
        enable,
        clock,
        reset
    );
    // Incrementer with enable.
    //
    // count -- output
    // enable -- control input, increment when 1
    // clock -- clock input
    // reset -- asynchronous reset input

    output [7:0] count;
    reg [7:0] count;
    input enable;
    input clock;
    input reset;

    always @(posedge clock, negedge reset) begin: INC_SEQ
        if (reset == 0) begin
            count <= 0;
        end
        else begin
            if (enable) begin
                count <= (count + 1);
            end
        end
    end

    endmodule

The convertor infers a proper Verilog module interface and maps the MyHDL generator to a Verilog always block.

Similarly, the conversion to VHDL generates a file `inc.vhd` with the following content:

    -- File: inc.vhd
    -- Generated by MyHDL 1.0dev
    -- Date: Sun May 22 18:46:37 2016

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    use std.textio.all;

    use work.pck_myhdl_10.all;

    entity inc is
        port (
            count: inout unsigned(7 downto 0);
            enable: in std_logic;
            clock: in std_logic;
            reset: in std_logic
        );
    end entity inc;
    -- Incrementer with enable.
    --
    -- count -- output
    -- enable -- control input, increment when 1
    -- clock -- clock input
    -- reset -- asynchronous reset input

    architecture MyHDL of inc is

    begin

    INC_SEQ: process (clock, reset) is
    begin
        if (reset = '0') then
            count <= to_unsigned(0, 8);
        elsif rising_edge(clock) then
            if bool(enable) then
                count <= (count + 1);
            end if;
        end if;
    end process INC_SEQ;

    end architecture MyHDL;

The MyHDL generator is mapped to a VHDL process in this case.

Note that the VHDL file refers to a VHDL package called `pck_myhdl_<version>`. This package contains a number of convenience functions that make the conversion easier.

Note also the use of an `inout` in the interface. This is not recommended VHDL design practice, but it is required here to have a valid VHDL design that matches the behavior of the MyHDL design. As this is only an issue for ports and as the convertor output is non-hierarchical, the issue is not very common and has an easy workaround.

## A small combinatorial design

The second example is a small combinatorial design, more specifically the binary to Gray code converter from previous chapters:

    from myhdl import block, always_comb

    @block
    def bin2gray(B, G):
        """ Gray encoder.

        B -- binary input
        G -- Gray encoded output
        """

        @always_comb
        def logic():
            G.next = (B>>1) ^ B

        return logic

As before, you can create an instance and convert to Verilog and VHDL as follows:

    from myhdl import Signal, intbv

    from bin2gray import bin2gray

    def convert(hdl, width=8):

        B = Signal(intbv(0)[width:])
        G = Signal(intbv(0)[width:])

        inst = bin2gray(B, G)
        inst.convert(hdl=hdl)

    convert(hdl='Verilog')
    convert(hdl='VHDL')

The generated Verilog code looks as follows:

    // File: bin2gray.v
    // Generated by MyHDL 1.0dev
    // Date: Mon May 23 16:09:27 2016

    `timescale 1ns/10ps

    module bin2gray (
        B,
        G
    );
    // Gray encoder.
    //
    // B -- binary input
    // G -- Gray encoded output

    input [7:0] B;
    output [7:0] G;
    wire [7:0] G;

    assign G = ((B >>> 1) ^ B);

    endmodule

The generated VHDL code looks as follows:

    -- File: bin2gray.vhd
    -- Generated by MyHDL 1.0dev
    -- Date: Mon May 23 16:09:27 2016

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    use std.textio.all;

    use work.pck_myhdl_10.all;

    entity bin2gray is
        port (
            B: in unsigned(7 downto 0);
            G: out unsigned(7 downto 0)
        );
    end entity bin2gray;
    -- Gray encoder.
    --
    -- B -- binary input
    -- G -- Gray encoded output

    architecture MyHDL of bin2gray is

    begin

    G <= (shift_right(B, 1) xor B);

    end architecture MyHDL;

## A hierarchical design

The converter can handle designs with an arbitrarily deep hierarchy.

For example, suppose we want to design an incrementer with Gray code output. Using the designs from previous sections, we can proceed as follows:

    from myhdl import block, Signal, modbv

    from bin2gray import bin2gray
    from inc import inc

    @block
    def gray_inc(graycnt, enable, clock, reset, width):

        bincnt = Signal(modbv(0)[width:])

        inc_0 = inc(bincnt, enable, clock, reset)
        bin2gray_0 = bin2gray(B=bincnt, G=graycnt)

        return inc_0, bin2gray_0

According to Gray code properties, only a single bit will change in consecutive values. However, as the `bin2gray` module is combinatorial, the output bits may have transient glitches, which may not be desirable. To solve this, let’s create an additional level of hierarchy and add an output register to the design. (This will create an additional latency of a clock cycle, which may not be acceptable, but we will ignore that here.)

    from myhdl import block, always_seq, Signal, modbv

    from gray_inc import gray_inc

    @block
    def gray_inc_reg(graycnt, enable, clock, reset, width):

        graycnt_comb = Signal(modbv(0)[width:])

        gray_inc_0 = gray_inc(graycnt_comb, enable, clock, reset, width)

        @always_seq(clock.posedge, reset=reset)
        def reg_0():
            graycnt.next = graycnt_comb

        return gray_inc_0, reg_0

We can convert this hierarchical design as follows:

    from myhdl import Signal, ResetSignal, modbv

    from gray_inc_reg import gray_inc_reg

    def convert_gray_inc_reg(hdl, width=8):
        graycnt = Signal(modbv(0)[width:])
        enable = Signal(bool())
        clock = Signal(bool())
        reset = ResetSignal(0, active=0, async=True)

        inst = gray_inc_reg(graycnt, enable, clock, reset, width)
        inst.convert(hdl)

    convert_gray_inc_reg(hdl='Verilog')
    convert_gray_inc_reg(hdl='VHDL')

The Verilog output code looks as follows:

    // File: gray_inc_reg.v
    // Generated by MyHDL 1.0dev
    // Date: Thu Jun 23 19:06:43 2016

    `timescale 1ns/10ps

    module gray_inc_reg (
        graycnt,
        enable,
        clock,
        reset
    );

    output [7:0] graycnt;
    reg [7:0] graycnt;
    input enable;
    input clock;
    input reset;

    wire [7:0] graycnt_comb;
    reg [7:0] gray_inc_1_bincnt;

    always @(posedge clock, negedge reset) begin: GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ
        if (reset == 0) begin
            gray_inc_1_bincnt <= 0;
        end
        else begin
            if (enable) begin
                gray_inc_1_bincnt <= (gray_inc_1_bincnt + 1);
            end
        end
    end

    assign graycnt_comb = ((gray_inc_1_bincnt >>> 1) ^ gray_inc_1_bincnt);

    always @(posedge clock, negedge reset) begin: GRAY_INC_REG_REG_0
        if (reset == 0) begin
            graycnt <= 0;
        end
        else begin
            graycnt <= graycnt_comb;
        end
    end

    endmodule

The VHDL output code looks as follows:

    -- File: gray_inc_reg.vhd
    -- Generated by MyHDL 1.0dev
    -- Date: Thu Jun 23 19:06:43 2016

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    use std.textio.all;

    use work.pck_myhdl_10.all;

    entity gray_inc_reg is
        port (
            graycnt: out unsigned(7 downto 0);
            enable: in std_logic;
            clock: in std_logic;
            reset: in std_logic
        );
    end entity gray_inc_reg;

    architecture MyHDL of gray_inc_reg is

    signal graycnt_comb: unsigned(7 downto 0);
    signal gray_inc_1_bincnt: unsigned(7 downto 0);

    begin

    GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ: process (clock, reset) is
    begin
        if (reset = '0') then
            gray_inc_1_bincnt <= to_unsigned(0, 8);
        elsif rising_edge(clock) then
            if bool(enable) then
                gray_inc_1_bincnt <= (gray_inc_1_bincnt + 1);
            end if;
        end if;
    end process GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ;

    graycnt_comb <= (shift_right(gray_inc_1_bincnt, 1) xor gray_inc_1_bincnt);

    GRAY_INC_REG_REG_0: process (clock, reset) is
    begin
        if (reset = '0') then
            graycnt <= to_unsigned(0, 8);
        elsif rising_edge(clock) then
            graycnt <= graycnt_comb;
        end if;
    end process GRAY_INC_REG_REG_0;

    end architecture MyHDL;

Note that the output is a flat “net list of blocks”, and that hierarchical signal names are generated as necessary.

## Optimizations for finite state machines

As often in hardware design, finite state machines deserve special attention.

In Verilog and VHDL, finite state machines are typically described using case statements. Python doesn’t have a case statement, but the converter recognizes particular if-then-else structures and maps them to case statements. This optimization occurs when a variable whose type is an enumerated type is sequentially tested against enumeration items in an if-then-else structure. Also, the appropriate synthesis pragmas for efficient synthesis are generated in the Verilog code.

As a further optimization, function `enum` was enhanced to support alternative encoding schemes elegantly, using an additional parameter encoding. For example:

    t_State = enum('SEARCH', 'CONFIRM', 'SYNC', encoding='one_hot')

The default encoding is `'binary'`; the other possibilities are `'one_hot'` and `'one_cold'`. This parameter only affects the conversion output, not the behavior of the type. The generated Verilog code for case statements is optimized for an efficient implementation according to the encoding. Note that in contrast, a Verilog designer has to make nontrivial code changes to implement a different encoding scheme.

As an example, consider the following finite state machine, whose state variable uses the enumeration type defined above:

    ACTIVE_LOW = bool(0)
    FRAME_SIZE = 8
    t_State = enum('SEARCH', 'CONFIRM', 'SYNC', encoding="one_hot")

    def FramerCtrl(SOF, state, syncFlag, clk, reset_n):

        """ Framing control FSM.

        SOF -- start-of-frame output bit
        state -- FramerState output
        syncFlag -- sync pattern found indication input
        clk -- clock input
        reset_n -- active low reset

        """

        index = Signal(intbv(0)[8:]) # position in frame

        @always(clk.posedge, reset_n.negedge)
        def FSM():
            if reset_n == ACTIVE_LOW:
                SOF.next = 0
                index.next = 0
                state.next = t_State.SEARCH
            else:
                index.next = (index + 1) % FRAME_SIZE
                SOF.next = 0
                if state == t_State.SEARCH:
                    index.next = 1
                    if syncFlag:
                        state.next = t_State.CONFIRM
                elif state == t_State.CONFIRM:
                    if index == 0:
                        if syncFlag:
                            state.next = t_State.SYNC
                        else:
                            state.next = t_State.SEARCH
                elif state == t_State.SYNC:
                    if index == 0:
                        if not syncFlag:
                            state.next = t_State.SEARCH
                    SOF.next = (index == FRAME_SIZE-1)
                else:
                    raise ValueError("Undefined state")

        return FSM

The conversion is done as before:

    SOF = Signal(bool(0))
    syncFlag = Signal(bool(0))
    clk = Signal(bool(0))
    reset_n = Signal(bool(1))
    state = Signal(t_State.SEARCH)
    toVerilog(FramerCtrl, SOF, state, syncFlag, clk, reset_n)
    toVHDL(FramerCtrl, SOF, state, syncFlag, clk, reset_n)

The Verilog output looks as follows:

    module FramerCtrl (
        SOF,
        state,
        syncFlag,
        clk,
        reset_n
    );

    output SOF;
    reg SOF;
    output [2:0] state;
    reg [2:0] state;
    input syncFlag;
    input clk;
    input reset_n;

    reg [7:0] index;

    always @(posedge clk, negedge reset_n) begin: FRAMERCTRL_FSM
        if ((reset_n == 0)) begin
            SOF <= 0;
            index <= 0;
            state <= 3'b001;
        end
        else begin
            index <= ((index + 1) % 8);
            SOF <= 0;
            // synthesis parallel_case full_case
            casez (state)
                3'b??1: begin
                    index <= 1;
                    if (syncFlag) begin
                        state <= 3'b010;
                    end
                end
                3'b?1?: begin
                    if ((index == 0)) begin
                        if (syncFlag) begin
                            state <= 3'b100;
                        end
                        else begin
                            state <= 3'b001;
                        end
                    end
                end
                3'b1??: begin
                    if ((index == 0)) begin
                        if ((!syncFlag)) begin
                            state <= 3'b001;
                        end
                    end
                    SOF <= (index == (8 - 1));
                end
                default: begin
                    $finish;
                end
            endcase
        end
    end

    endmodule

The VHDL output looks as follows:

    package pck_FramerCtrl is

        type t_enum_t_State_1 is (
        SEARCH,
        CONFIRM,
        SYNC
    );
    attribute enum_encoding of t_enum_t_State_1: type is "001 010 100";

    end package pck_FramerCtrl;

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    use std.textio.all;

    use work.pck_myhdl_06.all;

    use work.pck_FramerCtrl.all;

    entity FramerCtrl is
        port (
            SOF: out std_logic;
            state: inout t_enum_t_State_1;
            syncFlag: in std_logic;
            clk: in std_logic;
            reset_n: in std_logic
        );
    end entity FramerCtrl;

    architecture MyHDL of FramerCtrl is

    signal index: unsigned(7 downto 0);

    begin

    FRAMERCTRL_FSM: process (clk, reset_n) is
    begin
        if (reset_n = '0') then
            SOF <= '0';
            index <= "00000000";
            state <= SEARCH;
        elsif rising_edge(clk) then
            index <= ((index + 1) mod 8);
            SOF <= '0';
            case state is
                when SEARCH =>
                    index <= "00000001";
                    if to_boolean(syncFlag) then
                        state <= CONFIRM;
                    end if;
                when CONFIRM =>
                    if (index = 0) then
                        if to_boolean(syncFlag) then
                            state <= SYNC;
                        else
                            state <= SEARCH;
                        end if;
                    end if;
                when SYNC =>
                    if (index = 0) then
                        if (not to_boolean(syncFlag)) then
                            state <= SEARCH;
                        end if;
                    end if;
                    SOF <= to_std_logic(signed(resize(index, 9)) = (8 - 1));
                when others =>
                    assert False report "End of Simulation" severity Failure;
            end case;
        end if;
    end process FRAMERCTRL_FSM;

    end architecture MyHDL;

## RAM inference

Certain synthesis tools can infer RAM structures. To support this feature, the converter maps lists of signals in MyHDL to Verilog memories and VHDL arrays.

The following MyHDL example is a ram model that uses a list of signals to model the internal memory.

    def RAM(dout, din, addr, we, clk, depth=128):
        """  Ram model """

        mem = [Signal(intbv(0)[8:]) for i in range(depth)]

        @always(clk.posedge)
        def write():
            if we:
                mem[addr].next = din

        @always_comb
        def read():
            dout.next = mem[addr]

        return write, read

With the appropriate signal definitions for the interface ports, it is converted to the following Verilog code. Note how the list of signals `mem` is mapped to a Verilog memory.

    module ram (
        dout,
        din,
        addr,
        we,
        clk
    );

    output [7:0] dout;
    wire [7:0] dout;
    input [7:0] din;
    input [6:0] addr;
    input we;
    input clk;

    reg [7:0] mem [0:128-1];

    always @(posedge clk) begin: RAM_1_WRITE
        if (we) begin
            mem[addr] <= din;
        end
    end

    assign dout = mem[addr];

    endmodule

In VHDL, the list of MyHDL signals is modeled as a VHDL array signal:

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;

    use work.pck_myhdl_06.all;

    entity ram is
        port (
            dout: out unsigned(7 downto 0);
            din: in unsigned(7 downto 0);
            addr: in unsigned(6 downto 0);
            we: in std_logic;
            clk: in std_logic
        );
    end entity ram;

    architecture MyHDL of ram is

    type t_array_mem is array(0 to 128-1) of unsigned(7 downto 0);
    signal mem: t_array_mem;

    begin

    RAM_WRITE: process (clk) is
    begin
        if rising_edge(clk) then
            if to_boolean(we) then
                mem(to_integer(addr)) <= din;
            end if;
        end if;
    end process RAM_WRITE;

    dout <= mem(to_integer(addr));

    end architecture MyHDL;

## ROM inference

Some synthesis tools can infer a ROM memory from a case statement. The Verilog converter can perform the expansion into a case statement automatically, based on a higher level description. The ROM access is described in a single line, by indexing into a tuple of integers. The tuple can be described manually, but also by programmatical means. Note that a tuple is used instead of a list to stress the read-only character of the memory.

The following example illustrates this functionality. ROM access is described as follows:

    def rom(dout, addr, CONTENT):

        @always_comb
        def read():
            dout.next = CONTENT[int(addr)]

        return read

The ROM content is described as a tuple of integers. When the ROM content is defined, the conversion can be performed:

    CONTENT = (17, 134, 52, 9)
    dout = Signal(intbv(0)[8:])
    addr = Signal(intbv(0)[4:])

    toVerilog(rom, dout, addr, CONTENT)
    toVHDL(rom, dout, addr, CONTENT)

The Verilog output code is as follows:

    module rom (
        dout,
        addr
    );

    output [7:0] dout;
    reg [7:0] dout;
    input [3:0] addr;

    always @(addr) begin: ROM_READ
        // synthesis parallel_case full_case
        case (addr)
            0: dout <= 17;
            1: dout <= 134;
            2: dout <= 52;
            default: dout <= 9;
        endcase
    end

    endmodule

The VHDL output code is as follows:

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;
    use std.textio.all;

    use work.pck_myhdl_06.all;

    entity rom is
        port (
            dout: out unsigned(7 downto 0);
            addr: in unsigned(3 downto 0)
        );
    end entity rom;

    architecture MyHDL of rom is

    begin

    ROM_READ: process (addr) is
    begin
        case to_integer(addr) is
            when 0 => dout <= "00010001";
            when 1 => dout <= "10000110";
            when 2 => dout <= "00110100";
            when others => dout <= "00001001";
        end case;
    end process ROM_READ;

    end architecture MyHDL;

## User-defined code

MyHDL provides a way to include user-defined code during the conversion process, using the special function attributes `vhdl_code` and `verilog_code`.

For example:

    def inc_comb(nextCount, count, n):

        @always(count)
        def logic():
            # do nothing here
            pass

        nextCount.driven = "wire"

        return logic

    inc_comb.verilog_code =\
    """
    assign $nextCount = ($count + 1) % $n;
    """

    inc_comb.vhdl_code =\
    """
    $nextCount <= ($count + 1) mod $n;
    """

The converted code looks as follows in Verilog:

    module inc_comb (
        nextCount,
        count
    );

    output [7:0] nextCount;
    wire [7:0] nextCount;
    input [7:0] count;

    assign nextCount = (count + 1) % 256;

    endmodule

and as follows in VHDL:

    library IEEE;
    use IEEE.std_logic_1164.all;
    use IEEE.numeric_std.all;

    use work.pck_myhdl_06.all;

    entity inc_comb is
        port (
            nextCount: out unsigned(7 downto 0);
            count: in unsigned(7 downto 0)
        );
    end entity inc_comb;

    architecture MyHDL of inc_comb is

    begin

    nextCount <= (count + 1) mod 256;

    end architecture MyHDL;

In this example, conversion of the `inc_comb` function is bypassed and the user-defined code is inserted instead. The user-defined code is a Python template string that can refer to signals and parameters in the MyHDL context through `$`-based substitutions. During conversion, the appropriate hierarchical names and parameter values will be substituted.

The MyHDL code contains the following assignment:

    nextCount.driven = "wire"

This specifies that the nextCount signal is driven as a Verilog wire from this module.

For more info about user-defined code, see User-defined code.