Tutorial_Yellow_Block - david-macmahon/wiki_convert_test GitHub Wiki

This tutorial aims to provide a very entry level introduction to yellow block creation. A number of other tutorials and guides exist, for example Dave George's guide to yellow blocking the KATADC and Yellow block wiki page. This tutorial attempts be more of a guided tour around the inner workings of the toolflow, in which you will make an extremely simple new yellow block.

In this tutorial, you will create a yellow block for a bidirectional GPIO 8-bit interface for ROACH 1.

The Toolflow in a nutshell

Henry Chen gives a good introduction to the toolflow here. An abridged overview is the following:

1. Compiles of CASPER firmware are based around Xilinx EDK (Embedded Development Kit) projects.

2. These projects are specified at the top level by an HDL-like file, which instantiates a number of interconnected modules (in EDK these are called peripheral cores, or pcores) which specify the complete system.

3. Some pcores are present in all designs -- eg, cores which encode the PowerPC communications bus, or those which specify some of the default registers (like sys_clkcounter, or sys_scratchpad) in all designs. Other pcores are instantiated by the toolflow when you put specific yellow blocks in your design -- eg, ADC/QDR interfaces, software registers.

4. Your Simulink model -- that is, all the circuitry you create in Simulink that lies between "Gateway in" blocks and "Gateway out" blocks -- is compiled by system generator and added to your model as a pcore.

5. The toolflow's job is simply to make sure that the right pcores are added to your EDK project based on the yellow blocks you put in a design, and ensure they are connected properly to the rest of your design. The toolflow is also required to manage user constraints (e.g. FPGA pin connections) based on the blocks used in Simulink.

An Example

It helps to get some perspective of what the above all means by generating a simple model and looking under the hood at some of the files in the compile directory.

tut7_edk_demo.slx is a toy model containing a bunch of software registers and gpio blocks. The model simply wires an input software register to an output software register, and connects together an 8-bit bus from GPIO A to GPIO B.

Tut7_edk_demo_ss.png

If you look underneath any of the yellow blocks in this model, you'll notice that they essentially don't do anything. This model just wires a load of gateway in blocks to gateway out blocks.

Fire up casper_xps, but don't start a compile yet. Untick the stages of the "IP Elaboration", "Software Generation", and "EDK/ISE/BitGen" options. Now start the compile.

Casper_xps_ss.png

Your compile should complete within a couple of minutes. The stages we've selected are explained here, but essentially we've asked the toolflow to generate a complete EDK project, but not to bother compiling it to gateware. Once the compile has finished, we can have a look at the state of our design. You can find the top-level description of this project in your build directory -- /tut7_edk_demo/XPS_ROACH_base/system.mhs.

system.mhs is the top-level Microprocessor Hardware Specification (mhs) file. Its syntax is defined in the Xilinx EDK reference manual, but it is relatively straightforward to interpret. You'll see a bunch of module (pcore) instantiations, sitting between BEGIN and END blocks, each of which contains some port and parameter definitions. You can probably guess that these map to the ports and parameters in underlying HDL, which we'll explore momentarily.

Looking through system.mhs you'll see a number of pcores. Some are recognisably the result of what we put in our model:

############################ # Simulink interfaces      # ############################

# tut7_edk_demo/XSG core config

# tut7_edk_demo/gpio_a_in BEGIN gpio_ext2simulink  PARAMETER INSTANCE = tut7_edk_demo_gpio_a_in  PARAMETER HW_VER = 1.00.a  PARAMETER DDR = 0  PARAMETER WIDTH = 8  PARAMETER CLK_PHASE = 0  PARAMETER REG_IOB = true  PORT gateway = tut7_edk_demo_gpio_a_in_gateway  PORT io_pad = tut7_edk_demo_gpio_a_in_ext  PORT clk = sys_clk2x  PORT clk90 = sys_clk2x90 END PORT tut7_edk_demo_gpio_a_in_ext = tut7_edk_demo_gpio_a_in_ext, DIR = in, VEC = [7:0]

# tut7_edk_demo/gpio_b_out BEGIN gpio_simulink2ext  PARAMETER INSTANCE = tut7_edk_demo_gpio_b_out  PARAMETER HW_VER = 1.00.a  PARAMETER DDR = 0  PARAMETER WIDTH = 8  PARAMETER CLK_PHASE = 0  PARAMETER REG_IOB = true  PORT gateway = tut7_edk_demo_gpio_b_out_gateway  PORT io_pad = tut7_edk_demo_gpio_b_out_ext  PORT clk = sys_clk2x  PORT clk90 = sys_clk2x90 END PORT tut7_edk_demo_gpio_b_out_ext = tut7_edk_demo_gpio_b_out_ext, DIR = out, VEC = [7:0] 

# tut7_edk_demo/sw_reg_in BEGIN opb_register_ppc2simulink  PARAMETER INSTANCE = tut7_edk_demo_sw_reg_in  PARAMETER HW_VER = 1.00.a  PARAMETER C_BASEADDR = 0x01000000  PARAMETER C_HIGHADDR = 0x010000FF  BUS_INTERFACE SOPB = opb0  PORT OPB_Clk = epb_clk  PORT user_data_out = tut7_edk_demo_sw_reg_in_user_data_out  PORT user_clk = sys_clk2x END

# tut7_edk_demo/sw_reg_out BEGIN opb_register_simulink2ppc  PARAMETER INSTANCE = tut7_edk_demo_sw_reg_out  PARAMETER HW_VER = 1.00.a  PARAMETER C_BASEADDR = 0x01000100  PARAMETER C_HIGHADDR = 0x010001FF  BUS_INTERFACE SOPB = opb0  PORT OPB_Clk = epb_clk  PORT user_data_in = tut7_edk_demo_sw_reg_out_user_data_in  PORT user_clk = sys_clk2x END

Another pcore has the name of our model itself:

############################################## # User XSG IP core                           # ##############################################

BEGIN tut7_edk_demo  PARAMETER INSTANCE = tut7_edk_demo_XSG_core_config  PARAMETER HW_VER = 1.00.athe Xilinx`` ``EDK`` ``reference`` ``manual  PORT clk = sys_clk2x  PORT tut7_edk_demo_gpio_a_in_gateway = tut7_edk_demo_gpio_a_in_gateway  PORT tut7_edk_demo_gpio_b_out_gateway = tut7_edk_demo_gpio_b_out_gateway  PORT tut7_edk_demo_sw_reg_in_user_data_out = tut7_edk_demo_sw_reg_in_user_data_out  PORT tut7_edk_demo_sw_reg_out_user_data_in = tut7_edk_demo_sw_reg_out_user_data_in END

And a bunch of other pcores have been put in system.mhs which aren't user specified. These are the cores associated with the ROACH "base package", a skeleton EDK project to which all other modules are added. You can see the base system.mhs file in mlib_devel/xps_base/XPS_ROACH_base/system.mhs. You'll see the cores there which have remained in your system.mhs. (You'll also find a bundle of cores wrapped in #IF# statements. As you might guess, these cores are only included if certain conditions are met (i.e. certain yellow blocks are found. But more on that later...)

First, consider the "User XSG IP pcore". Look at the names of the ports, and then return to your simulink model. Looking under the yellow blocks, you will see that the names of the gateways match the names of the pcore ports. This is the case for any compile. The System Generator portion of the toolflow is responsible for compiling your simulink model into some HDL with a bunch of inputs and outputs corresponding to gateways in the simulink design. This module can then be instantiated in your EDK project as a single pcore.

Follow the signals connected to each of the ports of your tut7_edk_demo pcore. You will see that each is connected to one of the "Simulink interfaces" cores. The two software register ports of the system generator pcore mate with software register cores, and the gpio ports connect to gpio pcores. You'll notice that the gpio pcore instances are also followed by port declarations:

PORT tut7_edk_demo_gpio_a_in_ext = tut7_edk_demo_gpio_a_in_ext, DIR = in, VEC = [7:0]

PORT tut7_edk_demo_gpio_b_out_ext = tut7_edk_demo_gpio_b_out_ext, DIR = out, VEC = [7:0]

As you might expect, these declarations refer to external ports -- FPGA pins, to which the simulink-side signals are eventually connected. These map directly to pins, and you can see the assignments in /tut7_edk_demo/XPS_ROACH_base/data/system.ucf

If you're wondering how the software registers end up being accessible from the power PC, follow the opb0 bus from the software register blocks:

 BUS_INTERFACE SOPB = opb0

This bus (which is really just a neat syntax for routing multiple signals) connects to the epb_opb_bridge

BEGIN epb_opb_bridge   PARAMETER INSTANCE = epb_opb_bridge_inst   PARAMETER HW_VER   = 1.00.a   BUS_INTERFACE MOPB = opb0   PORT OPB_Clk       = epb_clk   PORT epb_data_oe_n = epb_data_oe_n   PORT epb_cs_n      = epb_cs_n_int   PORT epb_oe_n      = epb_oe_n_int   PORT epb_r_w_n     = epb_r_w_n_int   PORT epb_be_n      = epb_be_n_int   PORT epb_addr      = epb_addr_int   PORT epb_addr_gp   = epb_addr_gp_int   PORT epb_data_i    = epb_data_i   PORT epb_data_o    = epb_data_o   PORT epb_rdy       = epb_rdy_buf   PORT epb_rdy_oe    = epb_rdy_oe END

Which in turn connects to a bunch of external ports:

# EPB Ports PORT epb_clk_in  = epb_clk_in,  DIR = I PORT epb_data    = epb_data,    DIR = IO, VEC = [15:0] PORT epb_addr    = epb_addr,    DIR = I,  VEC = [22:0] PORT epb_addr_gp = epb_addr_gp, DIR = I,  VEC =  [5:0] PORT epb_cs_n    = epb_cs_n,    DIR = I PORT epb_be_n    = epb_be_n,    DIR = I,  VEC =  [1:0] PORT epb_r_w_n   = epb_r_w_n,   DIR = I PORT epb_oe_n    = epb_oe_n,    DIR = I PORT epb_rdy     = epb_rdy,     DIR = O

These external EPB (External Peripheral Bus) ports connect to the PowerPC, and ultimately provide the FPGA-PowerPC interface. Of course, the pcores do not simply route the opb straight to the epb bus, but rather perform a variety of tasks to ensure that communication can be established.

Having seen how system.mhs specifies our complete design, we now look at how each pcore itself is actually described by HDL.

You will see that each pcore instantiation begins with "BEGIN <core_name>", where <core_name> matches a directory in XPS_ROACH_base/pcores. In each such pcore directory you'll find a data folder, and (assuming the core is described by HDL, rather than a netlist) an hdl folder.

The specification of pcores is covered in detail in the Xilinx EDK reference manual, but for this tutorial we'll give very brief explanations of the most important files.

data/<pcore_name>.mpd This is the Microprocessor Peripheral Definition. It defines the ports and parameters of a pcore.

data/<pcore_name>.pao This is the Peripheral Analysis Order. It contains a list of HDL files necessary to compile the pcore, and defines the order they are analysed.

hdl/[verilog|vhdl]/<hdl_files>.[v|vhd] Here you put all your actual HDL files.

You can also use coregen cores, or other non-hdl specified modules. For that you should refer to the Xilinx guide or use some of the other pcores in the xps library as examples (eg. the adc_interface_v1_01_a).

Making a bidirectional GPIO pcore

So we want to design a biderctional GPIO interface. That means we need to create a GPIO bidirectional pcore, and convince the toolflow to instantiate it in system.mhs. First let's start with the pcore design, because that's easy.

The simplest version of a bidirectional GPIO module that can be created is simply a wrapper around a Xilinx IOBUF instance. An IOBUF (see [www.xilinx.com/itp/xilinx10/books/docs/virtex5_hdl/virtex5_hdl.pdf the Virtex 5 library guide] is a Xilinx module used to connect signals to a bi-directional external pin. It has the following ports, which are described (using slightly loose terminology) below:

I: the input (i.e., from the FPGA to the GPIO pin)

O: the output (i.e., from the GPIO pin to the FPGA)

IO: the GPIO pin (defined by the user constraints file)

T: The control signal, which configures the interface as an input (i.e. IO ---> O) when T=1, and an output (i.e. I ---> IO) when T=0.

We construct a module "gpio_bidir" which wraps 8 such IOBUF instances (i.e., an 8 bit wide buffer) and also registers the output signal. This simple module will form the entirety of the interface we will turn into a yellow block. Save your module description in gpio_bidir.v.

module gpio_bidir(     input            clk,     inout      [7:0] dio_buf, //inout, NOT input(!)     input      [7:0] din_i,     output reg [7:0] dout_o,     input            in_not_out_i   );      // A wire for the output data stream   wire [7:0] dout_wire; 

  // Buffer the in-out data line   IOBUF iob_data[7:0] (     .O (dout_wire),  //the data output     .IO(dio_buf),    //the external in-out signal     .I(din_i),       //the data input     .T(in_not_out_i) //The control signal. 1 for input, 0 for output   );      //register the data output   always @(posedge clk) begin     dout_o <= dout_wire;   end    endmodule

We then make a corresponding gpio_bidir.mpd:

################################################################### ## ## File     : gpio_bidir_v2_1_0.mpd ## Desc     : Microprocessor Peripheral Description ## ## ################################################################### 

BEGIN gpio_bidir 

## Peripheral Options OPTION IPTYPE = PERIPHERAL OPTION IMP_NETLIST = TRUE OPTION HDL = VERILOG OPTION ARCH_SUPPORT_MAP = (VIRTEX5=PREFERRED)

## Bus Interfaces # (none) 

## Generics for VHDL or Parameters for Verilog # (none)

## Ports PORT clk           = "", DIR = I PORT dio_buf       = "", DIR = IO, VEC = [7:0], THREE_STATE = FALSE, IOB_STATE = BUF PORT din_i         = "", DIR = I,  VEC = [7:0] PORT dout_o        = "", DIR = O,  VEC = [7:0] PORT in_not_out_i  = "", DIR = I ||

END

and gpio_bidir_v2_1_0.pao

################################################################################ ## ## File         : gpio_bidir_v2_1_0.pao ## Desc         : Peripheral Analysis Order| ## ## ################################################################################ 

lib gpio_bidir_v1_00_a gpio_bidir

which tells the compiler that the relevant verilog file is in the gpio_bidir_v1_00_a pcore directory, and is called gpio_bidir.v. The verilog extension is inferred by EDK, since we have set OPTION HDL = VERILOG.

The files then need to be correctly placed in the pcore subdirectory, organized as

gpio_bidir_v1_00_a  |-- data  |    |-- gpio_bidir_v2_1_0.pao  |    +-- gpio_bidir_v2_1_0.mpd  +-- hdl       +-- verilog            +-- gpio_bidir.v

This directory structure should already exist in your pcores directory, but if you were creating a new block from scratch, you'd have to add it.

There are a number of ways to progress at this point. One is to manually instantiate your new pcore in system.mhs, and run (only) the final steps of the compile that you deselected in casper_xps. Alternatively, you can go back to simulink, create a yellow block and try and convince MATLAB to automatically do this for you when you include the block in your design. In this tutorial we'll go and create the yellow block, but it's good to familiarise yourself with EDK by getting your hands dirty with system.mhs.

Automating the process

The first stage to automating the process of modifying system.mhs and system.ucf to instantiate your yellow block is to create the simulink block itself. This block doesn't need to "do" anything in simulink, it simply needs to provide the inputs and outputs to your simulink model for your pcore to later be connected to.

Our pcore has five inputs/outputs. The only ports which need to appear in the yellow block are those that form connections between your pcore, and the Simulink model. In our case, these are I, O, and T. The clock signal does not come from the Simulink design, but rather some other pcore. Similarly, the IO signal does not connect to the Simulink model, but rather refers to an external GPIO pin.

Therefore, our yellow block should have 3 ports. An 8-bit input (to be connected to I), an 8-bit output (to be connected to O) and a 1-bit input (to be connected to T). Be careful -- an input to the yellow block requires a "Gateway out" of simulink, since the signal needs to go out from the Simulink pcore, in to the new GPIO pcore. Similarly, an output from the yellow block requires a gateway in, since the signal will go out of the GPIO pcore and in to the Simulink design.

The names of the ports should match the names used in your pcore, and gateways out should be preceded by reinterpret and cast blocks to force the correct data types. There is a new yellow block ("1new_yellow_block") in the XPS library ready for you to customise.

Gpio_bidir_ss2.png

The reinterpret blocks should be both set to unsigned, binary point = 0. One convert block should be set to unsigned 8 bit, binary point 0 and the other set to boolean.

Next you need to tell the toolflow that this is a yellow block, by tagging it as an xps block. Open the block properties (right-click, then select properties), and tag the block by entering xps:<pcore_name> in the "tag" field. In out case the block is tagged xps:gpio_bidir.

Block_properties_ss.png

We are now almost finished with Simulink, except for one last modification to the block. As we have seen, the inputs/outputs of the "Simulink" pcore take the names of the gateway blocks that define them. In order that these names always be unique, the toolflow mandates that they follow a heirarchical naming scheme. This is defined by: <model_name><parent_block_name><user_specified_port_name>. Since simulink ensures that no two blocks have the same name, this naming scheme always results in a unique port name, no matter how many times you instantiate your yellow block. Each yellow block has an initialisation script which (amongst other possible functions) must rename gateways in a block according to this convention.

The gpio_bidir block's initialisation script is gpio_bidir_init.m, in the xps_library sub-directory of mlib_devel. It does nothing except find all the gateways in the block (by looking for blocks whose name ends in "<user_specified_port_name>") and renaming them appropriately. As in all things yellow blocky, If in doubt, copy from another block which works. :)

function gpio_bidir_init(cursys)

% We need to rename the gateway blocks in the yellow block % so that they respect the heirarchical naming conventions % required by the toolflow

% find all the gateway in/out blocks gateway_outs = find_system(cursys, ...         'searchdepth', 1, ...         'FollowLinks', 'on', ...         'lookundermasks', 'all', ...         'masktype','Xilinx Gateway Out Block'); 

gateway_ins = find_system(cursys, ...         'searchdepth', 1, ...         'FollowLinks', 'on', ...         'lookundermasks', 'all', ...         'masktype','Xilinx Gateway In Block');

%rename the gateway outs for i =1:length(gateway_outs)     gw = gateway_outs{i};     gw_name = get_param(gw, 'Name');     if regexp(gw_name, 'in_not_out_i$')         set_param(gw, 'Name', clear_name([cursys, '_in_not_out_i']));     elseif regexp(gw_name, 'din_i$')         set_param(gw, 'Name', clear_name([cursys, '_din_i']));     else          parent_name = get_param(gw, 'Parent');         errordlg(['Unknown gateway: ', parent_name, '/', gw_name]);     end end 

%rename the gateway ins for i =1:length(gateway_ins)     gw = gateway_ins{i};     gw_name = get_param(gw, 'Name');     if regexp(gw_name, 'dout_o$')         set_param(gw, 'Name', clear_name([cursys, '_dout_o']));     else          parent_name = get_param(gw, 'Parent');         errordlg(['Unknown gateway: ', parent_name, '/', gw_name]);     end end

We call the gpio_bidir_init.m function by specifiying it in the block's initilization commands in the block mask. I.e., right click the block, create a mask, and add to the initialization section gpio_bidir_init(gcb). This will run the initialization script with the current block (gcb = "get current block") as the input argument. This is exactly the same as the procedure used to call drawing functions for any other (non-yellow) library blocks. We also add the parameter "bank", which allows the user to select between GPIO banks a and b when instantiating the bidirectional IO. This parameter has no effect on the Simulink block, but will be used later when the toolflow starts configuring your EDK project. Force the block to redraw by changing the bank parameter, or running the initilisation script directly from the matlab prompt. Afterwards, if you look under the block mask, you should see the gateways have been renamed.

Gpio_bidir_renamed_ss.png

You've now completed all the Simulink-side configuration of your yellow block. Now would be the time to copy it to your simulink library.

Matlab OOP Magic

So we now have the pcore, and the yellow block. But we need to configure MATLAB so that when it sees the yellow block it correctly instantiates the pcore.

When the toolflow runs, it will look for xps-tagged blocks in your design. For each, it will construct a block belonging to the xps_block class. The xps_block class is documented here, with its individual methods explained here. The methods you will most likely be interested in are:

xps_mod_mhs: This method modifies system.mhs, inserting pcore instantiations and any other declarations required.

xps_mod_ucf: This method modifies system.ucf, specifying new constraints -- i.e. declarations of FPGA pin connections.

drc: This method runs a design rule check on your model. You don't necessarily need to specify any check, but this is a good place to check for misconfigured yellow blocks -- e.g., multiple QDR blocks using the same physical chip, or multiple ADCs assigned the same ZDOK port.

There are a number of ways you can use these methods.

1. Construct your xps_block object with the right attributes, so that the toolflow will be able to use it's default methods.

2. Override xps_mod_mhs and xps_mod_ucf to add code directly to your system.mhs and system.ucf files.

3. Ignore the methods altogether, and directly add entries to the base package of your chosen platform, relying on #IF# statements to include or remove lines of code.

The first option is the "proper" method if it is possible, though it requires getting to grips with some of the xps_block class properties. The second is probably not how the toolflow was intended to be used, but is often the quickest and easiest route to getting a working yellow block. It can also be much simpler if your yellow block requires multiple pcores to be instantiated, or uses EDK buses in it's specification. This is the case for the QDR block, which provides a good example of overriding the xps_mod_mhs method. The third is a disgusting hack (and I say this as someone who is very tolerant of hacks). Only do this if you have a really good reason to. (A real (I believe) example of a good is the katADC yellow block, which requires a controller to configure various ADC functions. Even if two ADCs are used, only one controller pcore is required, a condition which is somewhat fiddly to implement without abusing the base package).

In any case, at a minimum, you need to create a class for your yellow block, by creating a subdirectory named @xps_<block_name> in the xps_library of your mlib_devel. In our case, this is @xps_gpio_bidir. In here you must have a constructor -- <block_name>.m, and files defining any functions you want to override.

In our case, it is relatively simple to use the xps_block class' xps_mod_mhs and xps_mod_ucf methods, so we shall adopt that approach. We thus only need to write the constructor, xps_gpio_bidir.m

function b = xps_gpio(blk_obj)

First we do a quick bit of sanity checking to catch stupid errors

%Is the block under consideration tagged as an xps block? if ~isa(blk_obj,'xps_block')     error('XPS_GPIO class requires a xps_block class object'); end

% Is the block under consideration a gpio_bidir block?  if ~strcmp(get(blk_obj,'type'),'xps_gpio_bidir')     error(['Wrong XPS block type: ',get(blk_obj,'type')]); end

We then grab some parameters, including the block name, hardware platform details, and gpio_bank. Some of these we pull from the gpio_bidir yellow block itself, others we grab from the XSG object.

% Grab the block name. It will be handy when we need to grab parameters blk_name = get(blk_obj,'simulink_name');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Get the hardware platform (i.e. ROACH board, etc, and GPIO bank used by the block xsg_obj = get(blk_obj,'xsg_obj'); hw_sys_full =  get(xsg_obj,'hw_sys'); hw_sys = strtok(hw_sys_full,':') %hw_sys_full is ROACH:SX95t (We only want "ROACH") gpio_bank = get_param(blk_name,'bank')

%set the properties of the XSG object s.hw_sys = hw_sys s.io_group= gpio_bank %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Now we construct the new xps object

%constructor b = class(s,'xps_gpio_bidir',blk_obj);

We set some of the attributes of this object. The 'ip_name' needs to be specified, as it links the yellow block to it's associated pcore

% ip name %The is the name of your pcore which will be instantiated when the toolflow sees the yellow block b = set(b, 'ip_name','gpio_bidir');

External ports are added to automate configuration of connections from the pcore to external FPGA pins. To configure these connections, we need to set a variety of parameters -- pin voltages, io directions and pin numbers.

% external ports % Here we set up the external (i.e. to FPGA pin) connections required by the yellow block pcore

s.hw_sys switch s.hw_sys     case 'ROACH'         switch s.io_group           % The ROACH has GPIO banks operating at different voltages.           case 'gpiob'             iostandard = 'LVCMOS15';           otherwise             iostandard = 'LVCMOS25';         end                    % you might like to insert conditions here for other platforms     otherwise         iostandard = 'LVCMOS25'; end % switch 'hw_sys'

%io direction is inout s.io_dir = 'inout' bus_width = 8

ucf_fields = {}; ucf_values = {};

ucf_constraints = struct('IOSTANDARD',iostandard);

% Give a name to the external port name and buffer names. The iobname should match an entry in the hw_routes table, % allowing the tools to map the name to an FPGA pin number. % the extportname is the name given to the signal connected to the pins. It should be connected somewhere else in your pcore % (see the last few lines of this script for the connection to the pcore) % Be careful to make sure your signal names won't clash if you use multiple copies of the same yellow block in your design. % here we use a modification the block name (which simulink mandates is unique) extportname = [clear_name(blk_name), '_ext']; iobname = [s.hw_sys, '.', s.io_group];

%this string is passed to the external port structure  pin_str = ['{',iobname,'{[',num2str([1:bus_width]),']}}']

%ucf_constraints = cell2struct(ucf_values, ucf_fields, length(ucf_fields));

ext_ports.dio_buf =   {bus_width  s.io_dir  extportname  pin_str  'vector=true'  struct()  ucf_constraints};

b = set(b,'ext_ports',ext_ports);

Next we set parameters required by the HDL in the yellow block's pcore. In our case, there aren't any such parameters to set.

% parameters % These are the parameters that get passed to your pcore, which in turn set the parameters/generics defined in your HDL

%This block has no parameters %b = set(b,'parameters',parameters);

Finally, we specify "miscellaneous ports". Those which neither connect to Simulink, nor external pins. These are the ports that connect to other pcores in the EDK project.

% misc ports % These are ports on your pcore that are not connected within your simulink design via the yellow block gateway ins/outs. % Often a clock will be one of these signals % In our GPIO birectional block, we also need to connect the pcore to the pins we specified above, using the extportname variable

%in our biderectional GPIO block, the block should be clocked by the same clock as the rest of the simulink design.

%find out the simulink clock source set by the user xsg_obj = get(blk_obj,'xsg_obj'); simulink_clock =  get(xsg_obj,'clk_src');

%create the clock port called 'clk' misc_ports.clk = {1 'in' simulink_clock};

b = set(b,'misc_ports',misc_ports);

With this code in place, you're good to go. You should be able to compile your yellow block in a design.

Testing the block

Ideally, you'd do a bunch of simulation to test your HDL code before yellow blocking it. You can't simulate yellow block code within Simulink

  • simply because the yellow blocks don't contain any functional simulink blocks.

A model tut7_skeleton.slx has been prebuilt to test the yellow block. It uses two bidirctional gpio blocks, joining together the ROACHs two GPIO banks.

Tut7_skeleton_ss.png

Notice that the control signals also drive the GPIO buffers on the ROACH. Integrating these extra GPIOs into the gpio_bidir yellow block is left as an exercise to the reader....

A python script -- tut7.py -- is provided to test the interface directly on hardware. The script requires a ROACH to have it's two GPIO banks bridged together, and sends a couple of simple signals (all ones and all zeros) back and forward across the interface.

First the ROACH sends from GPIO_B to GPIO_A. We set the FPGA->GPIO_A register to 0, and FPGA->GPIO_B register to 0xFF.

Since we are setting GPIO_B as output and GPIO_A as input, we expect to readback the GPIO_A value as 0xFF

A: 0 <------- B : 0xff Readback values: A: 01111111, B: 11111111

Hurrah!

Similarly, we can set the FPGA->GPIO_A register to 0xFF, and FPGA->GPIO_B register to 0. Here we expect to readback the GPIO_A value as 0

A: 0xff <------- B: 0x0 Readback values: A: 00000000, B: 00000000 

Hurrah!

We can do the same, reversing the direction of the GPIO interfaces

A: 0 -------> B: 0xff Readback values: A: 00000000, B: 10000000  A: 0xff -------> B: 0x0 Readback values: A: 11111111, B: 11111111

Hurrah!

The yellow block works!

Recap

Hopfully, in doing this tutorial you have a better idea of how the toolflow works, and are better prepared to start constrcuting your own yellow blocks. Here's a quick recap of what we did:

1. Get the HDL to be yellow blocked, and turn it into a pcore, with all the associated pcore specification files demanded by EDK.

2. Create a Simulink yellow blocks, with heirarchically-named gateways representing each connection you need between your pcore and your Simulink model.

3. Tag your yellow block as an xps_block class object.

4. Create a matlab class for your block (i.e., the @xps_ directory and accompanying files) that describes ports of your pcore that are connected to places other than your Simulink design, and any parameters which need setting.

5. Hit compile, and fix bugs as you find them!

Things we have skipped over

One of the key parts of the toolflow is its ability to manage shared memory spaces. Our block doesn't have any shared memory, so we haven't used this feature. Hopefully a future expansion of this tutorial will address this shortcoming, but for now you are directed to other blocks (eg. software registers, or the QDR blocks) which serve as examples of this functionality. Also see the xps_block class documentation for information on how to have the toolflow allocate memory space to your yellow block.

⚠️ **GitHub.com Fallback** ⚠️