**Starter Lab With the Block Diagram** **Home Page** In this brief lab we'll build up a basic "full" Zynq[^caps] system that contains both the Processing System (PS) and the Programmable Logic (PL). The final result should be something similar to the video below, which shows a system which takes both hardware buttons and computational inputs:
!!! WARNING This lab is fresh, friends....there's probably some typos and things. Post issues on Piazza. [^caps] I'm not sure if it is ZYNQ or Zynq. I'll probably use both. It isn't an acronym, but I see it in all caps a lot Prior to Getting Started ================================ Because we'll be working on both the PS and the PL, we're going to need to make sure you have the system all set up in regards to the Pynq Board Image that will go on the SD card. If you're in the lab, most stuff is setup. If you're doing this offsite, you'll need to go to this page first and make sure that is working before starting below. Getting Started ================================ Let's open up Vivado and create a new project. 1. Click **`Next`** on Create a New Project 1. Name it something you'll remember like `led_controller_1` or something and click **`Next`** 1. Select it as an RTL Project and click **`Next`** 1. Don't worry about adding Verilog sources right now so click **`Next`** 1. Add the `base.xdc` (and make sure to specify "Copy Constraints File Into Project") 1. When prompted to choose a device do one of the following (and after that click **`Next`**): * Go to the boards page and choose the Pynq Z2 board (if that file is installed...it'll probably show an image of the Pynq Board). This is the preferred method since it will associate some important other constraints with your project that have to do with timing issues. * OR and this may be the case if board files aren't installed, manually find the `XC7Z020-1CLG400-1` from the list of available chips (-1, -2, and -3 refer to speed grades and I honestly have no idea what we have. I've been using -1 and it works fine.). Be cautious doing this though since you can end up with timing issues later without board information present. 1. Click **`Finish`** You should be all set now with a project targeted for the correct chipset. Starting Our Block Diagram =========================== We want to eventually build this block diagram: ![](./resources/block_diagram_overall.png) OK so if you're coming from 6.111, this would be the point where we create/add a top-level Verilog file. (we'll eventually do that but we'll have Vivado generate it automatically). We'll instead go the block diagram route so for right now do the following: 1. Under IP Integrator on the Left side, click on **`Create Block Diagram`**. Call it something you want (I usually just do `design_1`, but you do you). Keep file locations as the defaults and/or locals 1. A blank `Diagram` window should appear to the right. It is blank and we want to eventually fill it so that it will look like the first image below (our final system for this lab). To do that we're going to add IP and modules. We're essentially doing what we've always done in 6.111 with straight-up Verilog, but instead we'll be doing it graphically and this can help in a lot of ways. 1. Add a piece of IP to the design by clicking on the **`Add IP`** button (or press control-I). 1. Search for and add the **`ZYNQ7 PROCESSING SYSTEM`**. Click on this. 1. After it is added, you should almost immediately see a green option window at the top that says **`Run Block Automation`**. * Often times Vivado will suggest things to connect for you. Be careful with this since it is stupid sometimes, but really helpful other times. This first one, we can just trust it, so let it do its thing. Click on it. * In the window that pops up, it'll tell you what it is trying to auto-connect. Always review this! It'll say something about making DDR and Default IO external for us. This is good. Let it do that. Click **`OK`** * Some new wires should appear as well as some **`External Interfaces`** labeled `DDR` and `FIXED_IO` which correspond to output pins (a subset of the output pins can be accessed by the PL, and a subset can be accessed by the PS) 6. We now need to customize the Zynq PS for our purposes. To do this double click on the module. First off we're going to disable the AXI ports that connect the PS to the PL directly. (We'll use those later in other labs or in projects). Go to the **`PS-PL`** configuration tab and make sure no options are checked. It should look similar to below: ![](./resources/no_periph.png) 7. Next we want to enable some GPIO pins on the PS. To do this click on the **`Peripheral I/O Pins`** tab and scroll to the bottom, selecting **`GPIO MIO`** and **`GPIO EMIO`** like shown below (you'll note the pins will turn green indicating they are in use) At the same time make sure to disable all other uses for these pins (Flash SPI, UART, etc) ![](./resources/io_setup.png) 8. Finally let's enable a clock of a particular frequency. Go to the **`Clock Configuration`** tab and under **`PL Fabric Clocks`**, select **`FCLK_CLK0`** and set it to generate a 50 MHz frequency like shown: ![](./resources/clock_setup.png) 9. When the above three things have been done, click on **`OK`** and the Zynq system will update itself (some pins will appear, others will dissapear). It should look like the following when done. Make sure it matches, since unused pins will cause issues: ![](./resources/ending_stage_1.png) Adding Our Own Interfaces =========================== We now want to interface to some outside connections that the PL is connected to. In particular we want to interface to the buttons and to the LEDs on the PYNQ board. 1. Open up your `base.xdc` file (find it under `Sources>Constraints`) and make sure that everything is commented out except for the set of lines referring to the LEDs and the buttons[^xdc]. Depending on the version of your XDC, they may have different names. Plurality and Capitalization matter so pay attention to what they are called. Your XDC should look similar to below (make sure to save the file): ![](./resources/xdc.png) 2. Next, go back to the block diagram, and **`Right Click > Create Port`** or press Control-K. This will bring up the option to create inputs and outputs to the PL. Create two ports, an input for the buttons and an output for the LEDs. Should make sense: * Create one with the exact name of your LED pins. Make it an **`Output`** and a **`Vector`** from **`3 to 0`** (since there are four LEDs) * Next create another port with the exact name of your button pins. Make it an **`Input`** and a **`Vector`** from **`3 to 0`** as well. ![](./resources/pin_naming.png) When all finished, you should have an input and an output port.

[^xdc] The XDC file essentially maps the cryptically named pins on the FPGA (names like D19 and R14) to meaningful names that we can use in our designing. When building the bit file up, Each active pin also requires some setup/interfacing to be built around it so make sure to only uncomment pins that you're using otherwise you'll make your build times unnecessarily long (and they are already long as it is). Integrating Verilog Modules ============================ We now want to create two modules in Verilog. The first module will be a clock divider (takes in clock of one frequency and creates a clock of a much lower frequency). To make a new module go to **`PROJECT MANAGER >> Add Sources >> Add or Create Design Sources`**, and then **`Create File`**[^alreadymade] Name it whatever you want, just keep it local to your project and avoid spaces in the name, otherwise it'll be hell. **`Finish`** and then move on. Vivaod will then try to "help" you by allowing you to declare the Verilog module you want to write. Just skip this part unless you like wasting time. You can type it out way faster. ## Divider We'll give you the clock divider for starters: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ linenumbers module divider(clk,rst,clk_out); input clk,rst; output clk_out; reg[32:0] counter; //1 to 1 million clock divider always @(posedge clk or negedge rst) begin if(!rst) counter<=16'd0; else begin if(counter==32'd1000000) counter<=16'd0; else counter<=counter+1; end end assign clk_out = (counter == 32'd1000000); endmodule ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Add this code into your Verilog file. Save your code. Assuming you have a working module, we now need to integrate it into the block diagram. To do this: 1. Right click on the background of your block diagram and click on **`Add Module`**. Select your `divider` module and it will appear! 1. Drag the module to somewhere convenient !!! Tip At this point you can potentially run some connection automation (it will likely prompt you at the top). If you do that, the system will attempt to wire the fabric clock from the Zynq processor to the clock divider input. In the process it'll add a clocked reset module. You can either let it do this or skip the clocked reset module and wire the Zynq Processor clock to the divider clock and its reset to the reset of the divider manually. It is up to you. For this lab it won't matter. If you don't let it automate/do it manually, you'll get a warning about an asynchronous/non-clocked reset signal, but you can ignore it. In more complicated designs you should probably let it do its thing here. 3. If you don't let Vivado automatically connect your divider, connect, the fabric clock to your clock input and the Zynq reset to the reset on your divider module. You can connect stuff by hovering over the port/input/output of interest on a module and a little pencil symbol will appear. Click and drag/move your mouse to where you want to connect it. It'll snap and auto-route for you. ## LED Controller We want to make another module now that will sort of strobe the four LEDs using our divided clock and go either left or right (or stop them) based on three different control signals: * One signal (Stop signal) will come from the Zynq PS. We'll control this through Python * One signal (Go left signal) will come from the button connected to the PL * One signal (Go right signal) will come from a second button connected to the PL You can include the module's Verilog in the same file you put your `divider` module in. (Is up to you). A starter skeleton is shown below, but you need to write this one. Watch the video again for how we want it to work. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ linenumbers module jc ( input go_up, input go_down, input stop, input clk, output reg [3:0] q ); //your Verilog here endmodule ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you get this module working, do the same thing you did for your `divider` up above. Include it in the block diagram, and wire its output to the LED external interface. This should be no problem. You will run into a problem with the inputs on this module, however. Each input pin on the this module is one-wide whereas the buttons come in as a four-wide vector, and the GPIO out from the Zynq processor is a 64-wide vector. This will cause issues. Damn. Well there is a solution. We can use some slice modules to select/de-mux down. To do this go to the **`Add IP`** window and search for **`Slice`**. Add one and double click on it. We will need three Slices: * For the one that slices the Zynq PS GPIO, make its input width (**`Din`**) be 64. Then make both **`Din From`** and **`Din Down To`** have a value of 2. This is telling the slicer to take in all 64 values and only put out value on pin 2. ![](./resources/pin_sizing.png) * Make another slicer that slices the four button inputs down to 0 to 0. * Make another slider that slices the four button inputs down to 1 to 1. !!! WARNING To connect to the GPIO pins, hover over them and a down-down arrow symbol will appear. Click this to expand out the GPIO OUTPUT, INPUT, and T pins (used with high-impedance paths). Then connect to Output! You can name your Slicer (or any module) through the **`Block Properties`** box that should come up when you click on it. Name your one coming from the GPIO thing, **`stop`**. Run the appropriate wires/connections between all these modules. When completed you should have a systme that looks like the following (shown earlier, but again here for clarity) ![](./resources/block_diagram_overall.png) !!! WARNING Double check that things match up! If you let Vivado wire up your divider automatically, you'll also have a reset module in there. No worries either way. [^alreadymade]Note if you already have Verilog files written somewhere else, you could be importing them at this point. Regardless, makes sure you always keep copies of the actual files in your project directory and just let Vivado manage it. Otherwise, you'll maybe get weird errors/permission issues. Putting It All Together ============================ OK if everything is good so far, you can do a couple things: 1. Click on the **`Optimize Routing`** button (search for it...it is like a checkmark and a right-angle wire) in the top bar of the Diagram window. This will clean up everything for you (and generally does a good job at it). 1. Then click on **`Validate Design`**. This will do a first-pass (very surface-level) sanity check of your system and flag any potential problems (unconnected wires, incompatible pins, etc). Depending on if you added the reset module or not, you'll get one warning about an asynchronous reset. For this lab, don't worry. If you get other warnings/issues, read them through. They will be telling you information! Adjust your design accordingly. 1. If all is good, under the **`Sources`** menu, right click on the block diagram file (the .bd file which has a symbol that looks like a stack of gold bars), and click on **`Create HDL Wrapper`**. Let Vivado manage this thing for you. In the future maybe you will manage it yourself, but we're not there yet emotionally. This will create a high-level Verilog file for us/converting the block diagram into it. 1. If *still* no errors pop up, then go and click on **`Generate Bit Stream`**. This will start the whole build process of Synthesis, then Implementation, and then Generating the Bitstream. Depending on your computer, this could take up to ten minutes for this project (and potentially longer if you forgot to comment out unused things in your XDC). Errors could appear at any point in this build process so pay attention to the error logs and ask for help if needed. 1. Cross your fingers while it runs. 1. If errors come up as it builds, note what they are and either ask for help, or look them up. 1. When the Build is complete, **MAKE SURE YOUR BLOCK DIAGRAM DESIGN IS OPEN** and then go up to **`File >> Export >> Export Block Design`**. This will generate a `.tcl` file (pronounced "tickle" since that sounds less daunting than "T.C.L." file) with the name of your block diagram design. We'll need it, and the bit file you just generated in the next section. Moving it to the Zynq PS ============================ If everything went ok in the previous section we need to move two files to your Zynq over the network. These files will live inside of your project folder for what you were just building. Assuming your project was called `cool_project` and your block diagram was called `design_1` you'll find: * `design_1.tcl` in the root of the `cool_project` folder. * `design_1_wrapper.bit` in `cool_project/cool_project.runs/impl_1/design_1_wrapper.bit` We need to copy both of these files over to the Zynq. The grown-up way is to use a terminal and scp, but any sort of file transfer program will work. If you're working in the lab, we've set it up so that the PYNQ board's are set up on isolated banks of wireless routers. On each work station associated with a Pynq board in lab, there should be a sticky note specifying the WiFi network that your PYNQ board is associated with and its IP address on that local network. These should remain relatively stable and should allow you to talk to and work with the Pynq Board. In order to transfer files from a lab computer you will need a go-between computer such as your laptop (which should join the WiFi network that your Pynq is on). If you're using Vivado on a lab computer what you can do is: * From your laptop, transfer files **FROM** your Athena account to your laptop (for example targeting `jodalyst@athena.dialup.mit.edu` in my case) * From your laptop, transfer files **TO** the board using its IP address on the local network you're on. For example you might be targeting `xilinx@10.0.0.152` since that is the IP address on my Pynq board on my home router. Yours will be different. If I had Vivado running on my laptop and my username is `jodalyst` and my project was located in my home directory. When they live on the Zynq they need to be the same root name so in moving them over I'll name them `ledc`, keeping their appropriate extensions of course. Here's a record of what I put in my terminal, noting that the IP address of my Pynq board on the local network is `10.0.0.152` (note the password is `xilinx` on these things...and that's what I entered when it prompted me. yep I know super secure...don't put your SS number on it.) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ scp /home/jodalyst/cool_project/design_1.tcl xilinx@10.0.0.152:~/ledc.tcl xilinx@10.0.0.152's password: design_1.tcl 100% 3951KB 10.9MB/s 00:00 cd /home/jodalyst/cool_project scp cool_project.runs/impl_1/design_1_wrapper.bit xilinx@10.0.0.152:~/ledc.bit xilinx@10.0.0.152's password: design_1_wrapper.bit 100% 22KB 2.7MB/s 00:00 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! tip I know this can be confusing if you've not done this before (I've been there, please believe). Feel free to ask for help about this or post on Piazza or something. Once you get the hang of it, it is fine, but before it can seem daunting. If these got placed on the machine with no errors we now need to go to the Jupyter Notebook to do something with them. Running it on the Zynq PS ============================ We can interact with our system via Python using two different methods. The first is you can just ssh into the Pynq board, and write a Python file and run it right from the terminal. If you're comfortable doing that, then great. I tend to do this since I wasn't raised on Jupyter notebooks, but I gotta say I do see the point of Jupyter notebooks and that approach so totally go with that if you'd like. The Jupyter notebooks also allow an easy way to get some graphical feedback. If you do the Jupyter approach, in a web browser (on the same network as your router that the Zynq is connected to), type in the IP address of your board. It'll prompt you to log in. Again, the password is `xilinx`. When there, feel free to make a new directory and/or move folders and files around. I made a directory called `led_counter` and put the `ledc.tcl` and `ledc.bit` file into it. Then I moved into that directory and created a new Python3 notebook. ![](./resources/python_notebook_overall.png) Into that notebook I put the following chunks of code and ran them in sequence: ![](./resources/python_notebook.png) The first one is the following: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python from pynq import Overlay ol = Overlay("./ledc.bit") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This code imports the `Overlay` library which we then use to load the bit file we just generated. When you do this, the library looks for the similarly named tcl file, which should be located in the same directory (you moved that in right?). When that gets done and checked, you should be good to move on. If you run this blob, you should see your LEDs come on (but probably not be visible sweeping left or right)...or they might be just barely visibly flickering...the problem is our clock is still too high in frequency. We'll fix that in a little bit. Next I upload the Python API to interface with the GPIO pins. I set pin 2 (remember we Sliced that one down to be our "stop") signal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python from pynq import GPIO stop = GPIO(GPIO.get_gpio_pin(2),'out') ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I next added a line to make sure the "stop" signal is off for right now. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python stop.write(0) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Finally we can change the frequency of our fabric clocks from Python. This is really nice since it can let you get some debugging capabilities without having to rebuild your file. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python from pynq import Clocks Clocks.fclk0_mhz = 100 print(f"FCLK0: {Clocks.fclk0_mhz:.6f}MHz") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These lines of code should ensure a visible LED chase phenomenon based off of the button presses like shown in the video... If you'd like to turn off your LED chase, you can do it via Python! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python stop.write(1) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Then to turn it back on (in my implementation anyways) I have to: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python stop.write(0) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ and then press either the left or right buttons. And with this we're done! You should be able to interact directly with your Verilog code from Python and from your buttons to make a real-world change. This is really powerfull since it demonstrates a very basic way for information to go from your computational element (the Zynq Processor cores) down to a custom circuit living in the FPGA fabric. There are more advanced and efficient means of sending data between these two environments which we'll cover in a future readthrough/lab thing, but for now we can just revel in this thing. Python has a lot of cool libraries...you could potentially have Python access online resources, scrape them, then send data down to the FPGA to be processed quickly, then send its result back up. Crazy. Feel free to set up some GPIO inputs on the PS (to read the buttons) if you want. GPIO API docs are here.
We'll add some future work-through notes in this week and next about other ways to work with this integrated system!!! !!! note If you'd like to do more, go back to the Nexys 4 DDR lab (here) and try to build it with a block diagram instead of the high level Verilog module explicitely. *Lab initially inspired by video here*