Bit vector fields

Register fields can be of the type bit vector. Meaning, an array of logic bits.

This page will show you how the set up bit vector fields in a register, and will showcase all the code that can be generated from it.

Usage in TOML

The TOML file below shows how to set up a register with two bit vector fields. See comments for rules about the different properties.

TOML that sets up a register with bit vector fields.
 1[config]
 2
 3mode = "r_w"
 4description = "Configuration register."
 5
 6# This will allocate a bit vector field named "tuser" in the "config" register.
 7[config.tuser]
 8
 9# The "type" property MUST be present and set to "bit_vector".
10type = "bit_vector"
11
12# The "width" property MUST be present for a bit vector field.
13# The value specified MUST be a positive integer.
14width = 4
15
16# The "description" property is OPTIONAL for a bit vector field.
17# Will default to "" if not specified.
18# The value specified MUST be a string.
19description = "Value to set for **TUSER** in the data stream."
20
21# The "default_value" property is OPTIONAL for a bit vector field.
22# Will default to all zeros if not specified.
23# The value specified MUST be a string whose length is the same as the
24# specified 'width' property value.
25# The value specified MUST contain only ones and zeros.
26default_value = "0101"
27
28
29[config.tid]
30
31type = "bit_vector"
32width = 8
33description = "Value to set for **TID** in the data stream."

Note that the second field does not have any default value specified, meaning it will default to all zeros.

Below you will see how you can parse this TOML file and generate artifacts from it.

Usage with Python API

The Python code below shows

  1. How to parse the TOML file listed above.

  2. How to create an identical register list when instead using the Python API.

  3. How to generate register artifacts.

Note that the result of the create_from_api call is identical to that of the parse_toml call. Meaning that using a TOML file or using the Python API is completely equivalent. You choose yourself which method you want to use in your code base.

Python code that sets up a register with bit vector fields.
 1# Standard libraries
 2import sys
 3from pathlib import Path
 4
 5# First party libraries
 6from hdl_registers.generator.c.header import CHeaderGenerator
 7from hdl_registers.generator.cpp.implementation import CppImplementationGenerator
 8from hdl_registers.generator.cpp.interface import CppInterfaceGenerator
 9from hdl_registers.generator.html.page import HtmlPageGenerator
10from hdl_registers.generator.vhdl.record_package import VhdlRecordPackageGenerator
11from hdl_registers.generator.vhdl.register_package import VhdlRegisterPackageGenerator
12from hdl_registers.parser.toml import from_toml
13from hdl_registers.register_list import RegisterList
14from hdl_registers.register_modes import REGISTER_MODES
15
16THIS_DIR = Path(__file__).parent
17
18
19def parse_toml() -> RegisterList:
20    """
21    Create the register list by parsing a TOML data file.
22    """
23    return from_toml(name="caesar", toml_file=THIS_DIR.parent / "toml" / "field_bit_vector.toml")
24
25
26def create_from_api() -> RegisterList:
27    """
28    Alternative method: Create the register list by using the Python API.
29    """
30    register_list = RegisterList(name="caesar")
31
32    register = register_list.append_register(
33        name="config", mode=REGISTER_MODES["r_w"], description="Configuration register."
34    )
35
36    register.append_bit_vector(
37        name="tuser",
38        description="Value to set for **TUSER** in the data stream.",
39        width=4,
40        default_value="0101",
41    )
42
43    register.append_bit_vector(
44        name="tid",
45        description="Value to set for **TID** in the data stream.",
46        width=8,
47        default_value="00000000",
48    )
49
50    return register_list
51
52
53def generate(register_list: RegisterList, output_folder: Path):
54    """
55    Generate the artifacts that we are interested in.
56    """
57    CHeaderGenerator(register_list=register_list, output_folder=output_folder).create()
58
59    CppImplementationGenerator(register_list=register_list, output_folder=output_folder).create()
60    CppInterfaceGenerator(register_list=register_list, output_folder=output_folder).create()
61
62    HtmlPageGenerator(register_list=register_list, output_folder=output_folder).create()
63
64    VhdlRegisterPackageGenerator(register_list=register_list, output_folder=output_folder).create()
65    VhdlRecordPackageGenerator(register_list=register_list, output_folder=output_folder).create()
66
67
68def main(output_folder: Path):
69    generate(register_list=parse_toml(), output_folder=output_folder / "toml")
70    generate(register_list=create_from_api(), output_folder=output_folder / "api")
71
72
73if __name__ == "__main__":
74    main(output_folder=Path(sys.argv[1]))

See Register.append_bit_vector() for more Python API details.

Generated code

See below for a description of the code that can be generated when using bit vector fields.

HTML page

See HTML file below for the human-readable documentation that is produced by the generate() call in the Python example above. Each bit vector field is documented with its range, default value and description.

See HTML code generator for more details about the HTML generator and its capabilities.

HTML page

VHDL package

The VHDL code below is produced by the generate() call in the Python example above. Click the button to expand and view the code. See VHDL code generator for instructions on how it can be used in your VHDL project.

Base register package

Some interesting things to notice:

  1. There is only one register, at index 0.

  2. The first field is four bits wide, occupying bits 3 down to 0, while the second one is eight bits wide, occupying but 11 down to 4.

  3. For each bit vector field there is a named integer subtype that defines the fields’s bit range within the register.

  4. In VHDL, slicing out a range from the register value will yield a value of type std_ulogic_vector, meaning that typically no casting is needed. Hence there are no conversion functions for bit vector fields, the way there are for e.g. enumeration fields.

Click to expand/collapse code.
Generated VHDL register package.
 1-- This file is automatically generated by hdl-registers version 6.0.2-dev.
 2-- Code generator VhdlRegisterPackageGenerator version 1.0.0.
 3-- Generated 2024-10-03 20:52 at commit 32af2301489cab59.
 4-- Register hash 81d8209b61d8521a21cdb30a8c428ed6a2d9db3d.
 5
 6library ieee;
 7use ieee.std_logic_1164.all;
 8use ieee.numeric_std.all;
 9use ieee.fixed_pkg.all;
10
11library reg_file;
12use reg_file.reg_file_pkg.all;
13
14
15package caesar_regs_pkg is
16
17  -- ---------------------------------------------------------------------------
18  -- The valid range of register indexes.
19  subtype caesar_reg_range is natural range 0 to 0;
20
21  -- Register indexes, within the list of registers.
22  constant caesar_config : natural := 0;
23
24  -- Declare 'reg_map' and 'regs_init' constants here but define them in body (deferred constants).
25  -- So that functions have been elaborated when they are called.
26  -- Needed for ModelSim compilation to pass.
27
28  -- To be used as the 'regs' generic of 'axi_lite_reg_file.vhd'.
29  constant caesar_reg_map : reg_definition_vec_t(caesar_reg_range);
30
31  -- To be used for the 'regs_up' and 'regs_down' ports of 'axi_lite_reg_file.vhd'.
32  subtype caesar_regs_t is reg_vec_t(caesar_reg_range);
33  -- To be used as the 'default_values' generic of 'axi_lite_reg_file.vhd'.
34  constant caesar_regs_init : caesar_regs_t;
35
36  -- To be used for the 'reg_was_read' and 'reg_was_written' ports of 'axi_lite_reg_file.vhd'.
37  subtype caesar_reg_was_accessed_t is std_ulogic_vector(caesar_reg_range);
38
39  -- -----------------------------------------------------------------------------
40  -- Fields in the 'config' register.
41  -- Range of the 'tuser' field.
42  subtype caesar_config_tuser is natural range 3 downto 0;
43  -- Width of the 'tuser' field.
44  constant caesar_config_tuser_width : positive := 4;
45  -- Type for the 'tuser' field.
46  subtype caesar_config_tuser_t is u_unsigned(3 downto 0);
47  -- Default value of the 'tuser' field.
48  constant caesar_config_tuser_init : caesar_config_tuser_t := "0101";
49
50  -- Range of the 'tid' field.
51  subtype caesar_config_tid is natural range 11 downto 4;
52  -- Width of the 'tid' field.
53  constant caesar_config_tid_width : positive := 8;
54  -- Type for the 'tid' field.
55  subtype caesar_config_tid_t is u_unsigned(7 downto 0);
56  -- Default value of the 'tid' field.
57  constant caesar_config_tid_init : caesar_config_tid_t := "00000000";
58
59end package;
60
61package body caesar_regs_pkg is
62
63  constant caesar_reg_map : reg_definition_vec_t(caesar_reg_range) := (
64    0 => (idx => caesar_config, reg_type => r_w)
65  );
66
67  constant caesar_regs_init : caesar_regs_t := (
68    0 => "00000000000000000000000000000101"
69  );
70
71end package body;

Record package

The caesar_regs_down_t type is a record with a member config, the only register in this example. The type of the config member is another record with the two bit vectors set up in our example: tuser and tid. These are of unsigned vector types defined in the base register package above.

In our VHDL code we can access a field value for example like this:

result_tuser <= regs_down.config.tuser;
Click to expand/collapse code.
Generated VHDL record package.
  1-- This file is automatically generated by hdl-registers version 6.0.2-dev.
  2-- Code generator VhdlRecordPackageGenerator version 1.0.0.
  3-- Generated 2024-10-03 20:52 at commit 32af2301489cab59.
  4-- Register hash 81d8209b61d8521a21cdb30a8c428ed6a2d9db3d.
  5
  6library ieee;
  7use ieee.fixed_pkg.all;
  8use ieee.std_logic_1164.all;
  9use ieee.numeric_std.all;
 10
 11library reg_file;
 12use reg_file.reg_file_pkg.reg_t;
 13
 14use work.caesar_regs_pkg.all;
 15
 16
 17package caesar_register_record_pkg is
 18
 19  -- -----------------------------------------------------------------------------
 20  -- Record with correctly-typed members for each field in each register.
 21  -- Fields in the 'config' register as a record.
 22  type caesar_config_t is record
 23    tuser : caesar_config_tuser_t;
 24    tid : caesar_config_tid_t;
 25  end record;
 26  -- Default value for the 'config' register as a record.
 27  constant caesar_config_init : caesar_config_t := (
 28    tuser => caesar_config_tuser_init,
 29    tid => caesar_config_tid_init
 30  );
 31  -- Convert a record of the 'config' register to SLV.
 32  function to_slv(data : caesar_config_t) return reg_t;
 33  -- Convert an SLV register value to the record for the 'config' register.
 34  function to_caesar_config(data : reg_t) return caesar_config_t;
 35
 36  -- -----------------------------------------------------------------------------
 37  -- Below is a record with correctly typed and ranged members for all registers, register arrays
 38  -- and fields that are in the 'down' direction.
 39  -- Record with everything in the 'down' direction.
 40  type caesar_regs_down_t is record
 41    config : caesar_config_t;
 42  end record;
 43  -- Default value of the above record.
 44  constant caesar_regs_down_init : caesar_regs_down_t := (
 45    config => caesar_config_init
 46  );
 47  -- Convert SLV register list to record with everything in the 'down' direction.
 48  function to_caesar_regs_down(data : caesar_regs_t) return caesar_regs_down_t;
 49
 50  -- ---------------------------------------------------------------------------
 51  -- Below is a record with a status bit for each readable register in the register map.
 52  -- It can be used for the 'reg_was_read' port of a register file wrapper.
 53  -- Combined status mask record for all readable register.
 54  type caesar_reg_was_read_t is record
 55    config : std_ulogic;
 56  end record;
 57  -- Default value for the above record.
 58  constant caesar_reg_was_read_init : caesar_reg_was_read_t := (
 59    others => '0'
 60  );
 61  -- Convert an SLV 'reg_was_read' from generic register file to the record above.
 62  function to_caesar_reg_was_read(
 63    data : caesar_reg_was_accessed_t
 64  ) return caesar_reg_was_read_t;
 65
 66  -- ---------------------------------------------------------------------------
 67  -- Below is a record with a status bit for each writeable register in the register map.
 68  -- It can be used for the 'reg_was_written' port of a register file wrapper.
 69  -- Combined status mask record for all writeable register.
 70  type caesar_reg_was_written_t is record
 71    config : std_ulogic;
 72  end record;
 73  -- Default value for the above record.
 74  constant caesar_reg_was_written_init : caesar_reg_was_written_t := (
 75    others => '0'
 76  );
 77  -- Convert an SLV 'reg_was_written' from generic register file to the record above.
 78  function to_caesar_reg_was_written(
 79    data : caesar_reg_was_accessed_t
 80  ) return caesar_reg_was_written_t;
 81
 82end package;
 83
 84package body caesar_register_record_pkg is
 85
 86  function to_slv(data : caesar_config_t) return reg_t is
 87    variable result : reg_t := (others => '-');
 88  begin
 89    result(caesar_config_tuser) := std_logic_vector(data.tuser);
 90    result(caesar_config_tid) := std_logic_vector(data.tid);
 91
 92    return result;
 93  end function;
 94
 95  function to_caesar_config(data : reg_t) return caesar_config_t is
 96    variable result : caesar_config_t := caesar_config_init;
 97  begin
 98    result.tuser := caesar_config_tuser_t(data(caesar_config_tuser));
 99    result.tid := caesar_config_tid_t(data(caesar_config_tid));
100
101    return result;
102  end function;
103
104  function to_caesar_regs_down(data : caesar_regs_t) return caesar_regs_down_t is
105    variable result : caesar_regs_down_t := caesar_regs_down_init;
106  begin
107    result.config := to_caesar_config(data(caesar_config));
108
109    return result;
110  end function;
111
112  function to_caesar_reg_was_read(
113    data : caesar_reg_was_accessed_t
114  ) return caesar_reg_was_read_t is
115    variable result : caesar_reg_was_read_t := caesar_reg_was_read_init;
116  begin
117    result.config := data(caesar_config);
118
119    return result;
120  end function;
121
122  function to_caesar_reg_was_written(
123    data : caesar_reg_was_accessed_t
124  ) return caesar_reg_was_written_t is
125    variable result : caesar_reg_was_written_t := caesar_reg_was_written_init;
126  begin
127    result.config := data(caesar_config);
128
129    return result;
130  end function;
131
132end package body;

C++

The C++ interface header and implementation code below is produced by the generate() call in the Python example above. Click the button to expand and view each code block.

The class header is skipped here, since its inclusion would make this page very long. See C++ code generator for more details and an example of how the excluded file might look.

C++ interface header

Note the setters and getters for each individual field value.

Click to expand/collapse code.
Generated C++ class interface code.
 1// This file is automatically generated by hdl-registers version 6.0.2-dev.
 2// Code generator CppInterfaceGenerator version 1.0.0.
 3// Generated 2024-10-03 20:52 at commit 32af2301489cab59.
 4// Register hash 81d8209b61d8521a21cdb30a8c428ed6a2d9db3d.
 5
 6#pragma once
 7
 8#include <cassert>
 9#include <cstdint>
10#include <cstdlib>
11
12namespace fpga_regs
13{
14
15  // Attributes for the 'tuser' field in the 'config' register.
16  namespace caesar::config::tuser
17  {
18    static const auto width = 4;
19    static const auto default_value = 0b0101;
20  }
21  // Attributes for the 'tid' field in the 'config' register.
22  namespace caesar::config::tid
23  {
24    static const auto width = 8;
25    static const auto default_value = 0b00000000;
26  }
27
28  class ICaesar
29  {
30  public:
31    // Number of registers within this register map.
32    static const size_t num_registers = 1uL;
33
34    virtual ~ICaesar() {}
35
36    // -------------------------------------------------------------------------
37    // Methods for the 'config' register. Mode 'Read, Write'.
38
39    // Getter that will read the whole register's value over the register bus.
40    virtual uint32_t get_config() const = 0;
41
42    // Setter that will write the whole register's value over the register bus.
43    virtual void set_config(
44      uint32_t register_value
45    ) const = 0;
46
47    // Getter for the 'tuser' field in the 'config' register,
48    // which will read register value over the register bus.
49    virtual uint32_t get_config_tuser() const = 0;
50    // Getter for the 'tuser' field in the 'config' register,
51    // given a register value.
52    virtual uint32_t get_config_tuser_from_value(
53      uint32_t register_value
54    ) const = 0;
55    // Setter for the 'tuser' field in the 'config' register,
56    // which will read-modify-write over the register bus.
57    virtual void set_config_tuser(
58      uint32_t field_value
59    ) const = 0;
60    // Setter for the 'tuser' field in the 'config' register,
61    // given a register value, which will return an updated value.
62    virtual uint32_t set_config_tuser_from_value(
63      uint32_t register_value,
64      uint32_t field_value
65    ) const = 0;
66
67    // Getter for the 'tid' field in the 'config' register,
68    // which will read register value over the register bus.
69    virtual uint32_t get_config_tid() const = 0;
70    // Getter for the 'tid' field in the 'config' register,
71    // given a register value.
72    virtual uint32_t get_config_tid_from_value(
73      uint32_t register_value
74    ) const = 0;
75    // Setter for the 'tid' field in the 'config' register,
76    // which will read-modify-write over the register bus.
77    virtual void set_config_tid(
78      uint32_t field_value
79    ) const = 0;
80    // Setter for the 'tid' field in the 'config' register,
81    // given a register value, which will return an updated value.
82    virtual uint32_t set_config_tid_from_value(
83      uint32_t register_value,
84      uint32_t field_value
85    ) const = 0;
86
87  };
88
89} /* namespace fpga_regs */

C++ implementation

Note that each setter performs assertions that the supplied argument is withing the legal range of the field. This will catch calculation errors during testing and at run-time.

Click to expand/collapse code.
Generated C++ class implementation code.
  1// This file is automatically generated by hdl-registers version 6.0.2-dev.
  2// Code generator CppImplementationGenerator version 1.0.0.
  3// Generated 2024-10-03 20:52 at commit 32af2301489cab59.
  4// Register hash 81d8209b61d8521a21cdb30a8c428ed6a2d9db3d.
  5
  6#include "include/caesar.h"
  7
  8namespace fpga_regs
  9{
 10
 11  Caesar::Caesar(volatile uint8_t *base_address)
 12      : m_registers(reinterpret_cast<volatile uint32_t *>(base_address))
 13  {
 14    // Empty
 15  }
 16
 17  // ---------------------------------------------------------------------------
 18  // Methods for the 'config' register.
 19  // See interface header for documentation.
 20
 21  uint32_t Caesar::get_config() const
 22  {
 23    const size_t index = 0;
 24    const uint32_t result = m_registers[index];
 25
 26    return result;
 27  }
 28
 29  uint32_t Caesar::get_config_tuser() const
 30  {
 31    const uint32_t register_value = get_config();
 32    const uint32_t field_value = get_config_tuser_from_value(register_value);
 33
 34    return field_value;
 35  }
 36
 37  uint32_t Caesar::get_config_tuser_from_value(
 38    uint32_t register_value
 39  ) const
 40  {
 41    const uint32_t shift = 0uL;
 42    const uint32_t mask_at_base = 0b1111uL;
 43    const uint32_t mask_shifted = mask_at_base << shift;
 44
 45    const uint32_t result_masked = register_value & mask_shifted;
 46    const uint32_t result_shifted = result_masked >> shift;
 47
 48    uint32_t field_value;
 49
 50    // No casting needed.
 51    field_value = result_shifted;
 52
 53    return field_value;
 54  }
 55
 56  uint32_t Caesar::get_config_tid() const
 57  {
 58    const uint32_t register_value = get_config();
 59    const uint32_t field_value = get_config_tid_from_value(register_value);
 60
 61    return field_value;
 62  }
 63
 64  uint32_t Caesar::get_config_tid_from_value(
 65    uint32_t register_value
 66  ) const
 67  {
 68    const uint32_t shift = 4uL;
 69    const uint32_t mask_at_base = 0b11111111uL;
 70    const uint32_t mask_shifted = mask_at_base << shift;
 71
 72    const uint32_t result_masked = register_value & mask_shifted;
 73    const uint32_t result_shifted = result_masked >> shift;
 74
 75    uint32_t field_value;
 76
 77    // No casting needed.
 78    field_value = result_shifted;
 79
 80    return field_value;
 81  }
 82
 83  void Caesar::set_config(
 84    uint32_t register_value
 85  ) const
 86  {
 87    const size_t index = 0;
 88    m_registers[index] = register_value;
 89  }
 90
 91  void Caesar::set_config_tuser(
 92    uint32_t field_value
 93  ) const
 94  {
 95    // Get the current value of other fields by reading register on the bus.
 96    const uint32_t current_register_value = get_config();
 97    const uint32_t result_register_value = set_config_tuser_from_value(current_register_value, field_value);
 98    set_config(result_register_value);
 99  }
100
101  uint32_t Caesar::set_config_tuser_from_value(
102    uint32_t register_value,
103    uint32_t field_value
104  ) const  {
105    const uint32_t shift = 0uL;
106    const uint32_t mask_at_base = 0b1111uL;
107    const uint32_t mask_shifted = mask_at_base << shift;
108
109    // Check that field value is within the legal range.
110    const uint32_t mask_at_base_inverse = ~mask_at_base;
111    assert((field_value & mask_at_base_inverse) == 0);
112
113    const uint32_t field_value_masked = field_value & mask_at_base;
114    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
115
116    const uint32_t mask_shifted_inverse = ~mask_shifted;
117    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
118
119    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
120
121    return result_register_value;
122  }
123
124  void Caesar::set_config_tid(
125    uint32_t field_value
126  ) const
127  {
128    // Get the current value of other fields by reading register on the bus.
129    const uint32_t current_register_value = get_config();
130    const uint32_t result_register_value = set_config_tid_from_value(current_register_value, field_value);
131    set_config(result_register_value);
132  }
133
134  uint32_t Caesar::set_config_tid_from_value(
135    uint32_t register_value,
136    uint32_t field_value
137  ) const  {
138    const uint32_t shift = 4uL;
139    const uint32_t mask_at_base = 0b11111111uL;
140    const uint32_t mask_shifted = mask_at_base << shift;
141
142    // Check that field value is within the legal range.
143    const uint32_t mask_at_base_inverse = ~mask_at_base;
144    assert((field_value & mask_at_base_inverse) == 0);
145
146    const uint32_t field_value_masked = field_value & mask_at_base;
147    const uint32_t field_value_masked_and_shifted = field_value_masked << shift;
148
149    const uint32_t mask_shifted_inverse = ~mask_shifted;
150    const uint32_t register_value_masked = register_value & mask_shifted_inverse;
151
152    const uint32_t result_register_value = register_value_masked | field_value_masked_and_shifted;
153
154    return result_register_value;
155  }
156
157} /* namespace fpga_regs */

C header

The C code below is produced by the generate() call in the Python example above. The range and mask of the each field are available as constants.

Click to expand/collapse code.
Generated C code.
 1// This file is automatically generated by hdl-registers version 6.0.2-dev.
 2// Code generator CHeaderGenerator version 1.0.0.
 3// Generated 2024-10-03 20:52 at commit 32af2301489cab59.
 4// Register hash 81d8209b61d8521a21cdb30a8c428ed6a2d9db3d.
 5
 6#ifndef CAESAR_REGS_H
 7#define CAESAR_REGS_H
 8
 9
10// Number of registers within this register map.
11#define CAESAR_NUM_REGS (1u)
12
13// Type for this register map.
14typedef struct caesar_regs_t
15{
16  // Mode "Read, Write".
17  uint32_t config;
18} caesar_regs_t;
19
20// Address of the 'config' register.
21// Mode 'Read, Write'.
22#define CAESAR_CONFIG_INDEX (0u)
23#define CAESAR_CONFIG_ADDR (4u * CAESAR_CONFIG_INDEX)
24// Attributes for the 'tuser' field in the 'config' register.
25#define CAESAR_CONFIG_TUSER_SHIFT (0u)
26#define CAESAR_CONFIG_TUSER_MASK (0b1111u << 0u)
27#define CAESAR_CONFIG_TUSER_MASK_INVERSE (~CAESAR_CONFIG_TUSER_MASK)
28// Attributes for the 'tid' field in the 'config' register.
29#define CAESAR_CONFIG_TID_SHIFT (4u)
30#define CAESAR_CONFIG_TID_MASK (0b11111111u << 4u)
31#define CAESAR_CONFIG_TID_MASK_INVERSE (~CAESAR_CONFIG_TID_MASK)
32
33#endif // CAESAR_REGS_H