CycloneV library usage
Library structure
The library provides a CycloneV class in the mistral namespace. Information is provided to allow to choose a CycloneV::Model object which represents a sold FPGA variant. Then a CycloneV object can be created from it. That object stores the state of the FPGA configuration and allows to read and modify it.
All the types, enums, functions, methods, arrays etc described in the following paragraph are in the CycloneV class.
Packages
enum package_type_t;
struct CycloneV::package_info_t {
int pin_count;
char type;
int width_in_pins;
int height_in_pins;
int width_in_mm;
int height_in_mm;
};
const package_info_t package_infos[5+3+3];
The FPGAs are sold in 11 different packages, which are named by their type (Fineline BGA, Ultra Fineline BGA or Micro Fineline BGA) and their width in mm.
Enum |
Type |
Pins |
Size in mm |
Size in pins |
---|---|---|---|---|
PKG_F17 |
f |
256 |
16x16 |
17x17 |
PKG_F23 |
f |
484 |
22x22 |
23x23 |
PKG_F27 |
f |
672 |
26x26 |
27x27 |
PKG_F31 |
f |
896 |
30x30 |
31x31 |
PKG_F35 |
f |
1152 |
34x34 |
35x35 |
PKG_U15 |
u |
324 |
18x18 |
15x15 |
PKG_U19 |
u |
484 |
22x22 |
19x19 |
PKG_U23 |
u |
672 |
28x28 |
23x23 |
PKG_M11 |
m |
301 |
21x21 |
11x11 |
PKG_M13 |
m |
383 |
25x25 |
13x13 |
PKG_M15 |
m |
484 |
28x28 |
15x15 |
Model information
enum die_type_t { E50F, GX25F, GT75F, GT150F, GT300F, SX50F, SX120F };
struct Model {
const char *name;
const variant_info &variant;
package_type_t package;
char temperature;
char speed;
char pcie, gxb, hmc;
uint16_t io, gpio;
};
struct variant_info {
const char *name;
const die_info ¨
uint16_t idcode;
int alut, alm, memory, dsp, dpll, dll, hps;
};
struct die_info {
const char *name;
die_type_t type;
uint8_t tile_sx, tile_sy;
// ...
};
const Model models[];
CycloneV *get_model(std::string model_name);
A Model is built from a package, a variant and a temperature/speed grade. A variant selects a die and which hardware is active on it.
The Model fields are:
name - the SKU, for instance 5CSEBA6U23I7
variant - its associated variant_info
package - the packaging used
temperature - the temperature grade, ‘A’ for automotive (-45..125C), ‘I’ for industrial (-40..100C), ‘C’ for commercial (0..85C)
speed - the speed grade, 6-8, smaller is faster
pcie - number of PCIe interfaces (depends on both variant and number of available pins)
gxb - ??? (same)
hmc - number of Memory interfaces (same)
io - number of i/os
gpio - number of fpga-usable gpios
The Variant fields are:
name - name of the variant, for instance se120b
die - its associated die_info
idcode - the IDCODE associated to this variant (not unique per variant at all)
alut - number of LUTs
alm - number of logic elements
memory - bits of memory
dsp - number of dsp blocks
dpll - number of plls
dll - number of delay-locked loops
hps - number of arm cores
The Die usable fields are:
name - name of the die, for instance sx120f
type - the enum value for the die type
tile_sx, tile_sy - size of the tile grid
The limits indicated in the variant structure may be lower than the theoretical die capabilities. We have no idea what happens if these limits are not respected.
To create a CycloneV object, the constructor requires a Model *. Either choose one from the models array, or, in the usual case of selection by sku, the CycloneV::get_model function looks it up and allocates one. The models array ends with a nullptr name pointer.
The get_model function implements the alias “ms” for the 5CSEBA6U23I7 used in the de10-nano, a.k.a MiSTer.
pos, rnode and pnode
using pos_t = uint16_t; // Tile position
static constexpr uint32_t pos2x(pos_t xy);
static constexpr uint32_t pos2y(pos_t xy);
static constexpr pos_t xy2pos(uint32_t x, uint32_t y);
The type pos_t represents a position in the grid. xy2pos allows to create one, pos2x and pos2y extracts the coordinates.
using rnode_t = uint32_t; // Route node id
enum rnode_type_t;
const char *const rnode_type_names[];
rnode_type_t rnode_type_lookup(const std::string &n) const;
constexpr rnode_t rnode(rnode_type_t type, pos_t pos, uint32_t z);
constexpr rnode_t rnode(rnode_type_t type, uint32_t x, uint32_t y, uint32_t z);
constexpr rnode_type_t rn2t(rnode_t rn);
constexpr pos_t rn2p(rnode_t rn);
constexpr uint32_t rn2x(rnode_t rn);
constexpr uint32_t rn2y(rnode_t rn);
constexpr uint32_t rn2z(rnode_t rn);
std::string rn2s(rnode_t rn);
A rnode_t represents a note in the routing network. It is characterized by its type (rnode_type_t) and its coordinates (x, y for the tile, z for the instance number in the tile). Those functions allow to create one and extract the different components. rnode_types_names gives the string representation for every rnode_type_t value, and rnode_type_lookup finds the rnode_type_t for a given name. rn2s provides a string representation of the rnode (TYPE.xxx.yyy.zzzz).
The rnode_type_t value 0 is NONE, and a rnode_t of 0 is guaranteed invalid.
using pnode_t = uint64_t; // Port node id
enum block_type_t;
const char *const block_type_names[];
block_type_t block_type_lookup(const std::string &n) const;
enum port_type_t;
const char *const port_type_names[];
port_type_t port_type_lookup (const std::string &n) const;
constexpr pnode_t pnode(block_type_t bt, pos_t pos, port_type_t pt, int8_t bindex, int16_t pindex);
constexpr pnode_t pnode(block_type_t bt, uint32_t x, uint32_t y, port_type_t pt, int8_t bindex, int16_t pindex);
constexpr block_type_t pn2bt(pnode_t pn);
constexpr port_type_t pn2pt(pnode_t pn);
constexpr pos_t pn2p (pnode_t pn);
constexpr uint32_t pn2x (pnode_t pn);
constexpr uint32_t pn2y (pnode_t pn);
constexpr int8_t pn2bi(pnode_t pn);
constexpr int16_t pn2pi(pnode_t pn);
std::string pn2s(pnode_t pn);
A pnode_t represents a port of a logical block. It is characterized by the block type (block_type_t), the block tile position, the block number instance (when appropriate, -1 when not), the port type (port_type_t) and the bit number in the port (when appropriate, -1 when not). pn2s provides the string representation BLOCK.xxx.yyy(.instance):PORT(.bit)
The block_type_t value 0 is BNONE, the port_type_t value 0 is PNONE, and pnode_t 0 is guaranteed invalid.
rnode_t pnode_to_rnode(pnode_t pn) const;
pnode_t rnode_to_pnode(rnode_t rn) const;
These two methods allow to find the connections between the logic block ports and the routing nodes. It is always 1:1 when there is one.
std::vector<pnode_t> p2p_from(pnode_t pn) const;
pnode_t p2p_to(pnode_t pn) const;
These two methods allow to find the direct connections between logic port nodes of different logic blocks. The connections being 1:N the p2p_from method can give multiple results while p2p_to only answers one node or the value 0.
Routing network management
void rnode_link(rnode_t n1, rnode_t n2);
void rnode_link(pnode_t p1, rnode_t n2);
void rnode_link(rnode_t n1, pnode_t p2);
void rnode_link(pnode_t p1, pnode_t p2);
void rnode_unlink(rnode_t n2);
void rnode_unlink(pnode_t p2);
The method rnode_link links two nodes together with n1 as source and n2 as destination, automatically converting from pnode_t to rnode_t when needed. rnode_unlink disconnects anything connected to the destination n2.
There are two special cases. DCMUX is a 2:1 mux which selects between a data and a clock signal and has no disconnected state. Unlinking it puts in in the default clock position. Most SCLK muxes use a 5-bit vertical configuration where up to 5 inputs can be connected and the all-off configuration is not allowed. Usually at least one input goes to vcc, but in some cases all five are used and unlinking selects the 4th input (the default in that case).
std::vector<std::pair<rnode_t, rnode_t>> route_all_active_links() const;
std::vector<std::pair<rnode_t, rnode_t>> route_frontier_links() const;
route_all_active_links gives all current active connections. route_frontier_links solves these connections to keep only the extremities, giving the inter-logic-block connections directly.
Logic block management
const std::vector<pos_t> &lab_get_pos() const
[etc]
cosst std::vector<block_type_t> &pos_get_bels(pos_t pos) const
The numerous xxx_get_pos() methods gives the list of positions of logic blocks of a given type. The known types are lab, mlab, m10k, dsp, hps, gpio, dqs16, fpll, cmuxc, cmuxv, cmuxh, dll, hssi, cbuf, lvl, ctrl, pma3, serpar, term and hip. A vector is empty when a block type doesn’t exist in the given die.
In the hps case the 37 blocks can be indexed by hps_index_t enum.
Alternatively the pos_get_bels() method gives the (possibly empty) list of logic blocks present in a given tile.
enum { MT_MUX, MT_NUM, MT_BOOL, MT_RAM };
enum bmux_type_t;
const char *const bmux_type_names[];
bmux_type_t bmux_type_lookup(const std::string &n) const;
struct bmux_setting_t {
block_type_t btype;
pos_t pos;
bmux_type_t mux;
int midx;
int type;
bool def;
uint32_t s; // bmux_type_t, or number, or bool value, or count of bits for ram
std::vector<uint8_t> r;
};
int bmux_type(block_type_t btype, pos_t pos, bmux_type_t mux, int midx) const;
bool bmux_get(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, bmux_setting_t &s) const;
bool bmux_set(const bmux_setting_t &s);
bool bmux_m_set(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, bmux_type_t s);
bool bmux_n_set(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, uint32_t s);
bool bmux_b_set(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, bool s);
bool bmux_r_set(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, uint64_t s);
bool bmux_r_set(block_type_t btype, pos_t pos, bmux_type_t mux, int midx, const std::vector<uint8_t> &s);
std::vector<bmux_setting_t> bmux_get() const;
These methods allow to manage the logic blocks muxes configurations. A mux is characterized by its block (type and position), its type (bmux_type_t) and its instance number (0 if there is only one). There are four kinds of muxes, symbolic (MT_MUX), numeric (MT_NUM), booolean (MT_BOOL) and ram (MT_RAM).
bmux_type looks up a mux and returns its MT_* type, or -1 if it doesn’t exist. bmux_get reads the state of a mux and returns it in s and true when found, false otherwise. The def field indicates whether the value is the default. The bmux_set sets a mux generically, and the bmux_*_set sets it per-type.
The no-parameter bmux_get version returns the state of all muxes of the FPGA.
Inverters management
struct inv_setting_t {
rnode_t node;
bool value;
bool def;
};
std::vector<inv_setting_t> inv_get() const;
bool inv_set(rnode_t node, bool value);
inv_get() returns the state of the programmable inverters, and inv_set sets the state of one. The field def is currently very incorrect.
Pin/package management
enum pin_flags_t : uint32_t {
PIN_IO_MASK = 0x00000007,
PIN_DPP = 0x00000001, // Dedicated Programming Pin
PIN_HSSI = 0x00000002, // High Speed Serial Interface input
PIN_JTAG = 0x00000003, // JTAG
PIN_GPIO = 0x00000004, // General-Purpose I/O
PIN_HPS = 0x00000008, // Hardware Processor System
PIN_DIFF_MASK = 0x00000070,
PIN_DM = 0x00000010,
PIN_DQS = 0x00000020,
PIN_DQS_DIS = 0x00000030,
PIN_DQSB = 0x00000040,
PIN_DQSB_DIS = 0x00000050,
PIN_TYPE_MASK = 0x00000f00,
PIN_DO_NOT_USE = 0x00000100,
PIN_GXP_RREF = 0x00000200,
PIN_NC = 0x00000300,
PIN_VCC = 0x00000400,
PIN_VCCL_SENSE = 0x00000500,
PIN_VCCN = 0x00000600,
PIN_VCCPD = 0x00000700,
PIN_VREF = 0x00000800,
PIN_VSS = 0x00000900,
PIN_VSS_SENSE = 0x00000a00,
};
struct pin_info_t {
uint8_t x;
uint8_t y;
uint16_t pad;
uint32_t flags;
const char *name;
const char *function;
const char *io_block;
double r, c, l, length;
int delay_ps;
int index;
};
const pin_info_t *pin_find_pos(pos_t pos, int index) const;
const pin_info_t *pin_find_pnode(pnode_t pn) const;
The pin_info_t structure describes a pin with:
x, y - its coordinates in the package grid (not the fpga grid, the pins one)
pad - either 0xffff (no associated gpio) or (index << 14) | tile_pos, where index indicates which pad of the gpio is connected to the pin
flags - flags describing the pin function
name - pin name, like A1
function - pin function as text, like “GND”
io_block - name of the I/O block for power purposes, like 9A
r, c, l - electrical characteristics of the pin-pad connection wire
length - length of the wire
delay_ps - usual signal transmission delay is ps
index - pin sub-index for hssi_input, hssi_output, dedicated programming pins and jtag
The pin_find_pos method looks up a pin from a gpio tile/index combination. The pin_find_pos method looks up a pin from a gpio or hmc pnode.
Options
struct opt_setting_t {
bmux_type_t mux;
bool def;
int type;
uint32_t s; // bmux_type_t, or number, or bool value, or count of bits for ram
std::vector<uint8_t> r;
};
int opt_type(bmux_type_t mux) const;
bool opt_get(bmux_type_t mux, opt_setting_t &s) const;
bool opt_set(const opt_setting_t &s);
bool opt_m_set(bmux_type_t mux, bmux_type_t s);
bool opt_n_set(bmux_type_t mux, uint32_t s);
bool opt_b_set(bmux_type_t mux, bool s);
bool opt_r_set(bmux_type_t mux, uint64_t s);
bool opt_r_set(bmux_type_t mux, const std::vector<uint8_t> &s);
std::vector<opt_setting_t> opt_get() const;
The options work like the block muxes without a block, tile or instance number. They’re otherwise the same.
Bitstream management
void clear();
void rbf_load(const void *data, uint32_t size);
void rbf_save(std::vector<uint8_t> &data);
The clear method returns the FPGA state to all defaults. rbf_load parses a raw bitstream file from memory and loads the state from it. rbf_save generats a rbf from the current state.
HMC bypass
pnode_t hmc_get_bypass(pnode_t pn) const;
The hmc_get_bypass method gives the associated HMC port to a given one when in bypass mode. Specifically, to find the rnode corresponding to a given GPIO port connected to the HMC in bypass mode do:
Get the port(s) connected to the GPIO with p2p_to (when look for a GOUT) or p2p_from (when looking for a GIN). There should be only one even in the p2p_from case.
Get the associated node when in bypass mode with hmc_get_bypass (the method is direction-independant)
Get the associated routing node with pnode_to_rnode.