{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example 6: Memories in PyRTL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One important part of many circuits is the **ability to have data in\n", "locations that are persistent over clock cycles**. In previous examples,\n", "we have shown the register wirevector, which is great for storing\n", "a small amount of data for a single clock cycle. However, PyRTL also\n", "has other ways to store data, namely **memories and ROMs.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import random\n", "\n", "import pyrtl\n", "from pyrtl import *\n", "\n", "pyrtl.reset_working_block()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 1: Memories" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Memories is a way to store multiple sets of data for extended periods of\n", "time. Below we will make two instances of the same memory to test using\n", "that the same thing happens to two different memories using the same\n", "inputs" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "mem1 = MemBlock(bitwidth=32, addrwidth=3, name='mem')\n", "mem2 = MemBlock(32, 3, 'mem')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One memory will receive the **write address** from an input, the other, a **register**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "waddr = Input(3, 'waddr')\n", "count = Register(3, 'count')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to make sure that the **two memories take the same inputs**,\n", "we will use same write data, write enable, and read addr values" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "wdata = Input(32, 'wdata')\n", "we = Input(1, 'we')\n", "raddr = Input(3, 'raddr')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will be **grabbing data from each of the two memory blocks** so we need\n", "two different output wires to see the results" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "rdata1 = Output(32, 'rdata1')\n", "rdata2 = Output(32, 'rdata2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ports" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The way of sending data to and from a memory block is through the\n", "use of a port. There are two types of ports, **read ports** and **write ports**.\n", "Each memory can have multiple read and write ports, but it doesn't make\n", "sense for one to have either 0 read ports or 0 write ports. Below, we\n", "will make **one read port for each of the two memories**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "rdata1 <<= mem1[raddr]\n", "rdata2 <<= mem2[raddr]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Write Enable Bit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the write ports, we will do something different. Sometimes you don't\n", "want the memories to always accept the data and address on the write port.\n", "The **write enable bit allows us to disable the write port as long as the\n", "value is zero**, giving us complete control over whether to accept the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "WE = MemBlock.EnabledWrite\n", "mem1[waddr] <<= WE(wdata, we) # Uses input wire\n", "mem2[count] <<= WE(wdata, we) # Uses count register" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will finish up the circuit.\n", "We will **increment count register on each write.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "count.next <<= select(we, falsecase=count, truecase=count + 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will also **verify that the two write addresses are always the same**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "validate = Output(1, 'validate')\n", "validate <<= waddr == count" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is time to **simulate the circuit.** First we will set up the values\n", "for all of the inputs.\n", "Write 1 through 8 into the eight registers, then read back out" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "simvals = {\n", " 'we': \"00111111110000000000000000\",\n", " 'waddr': \"00012345670000000000000000\",\n", " 'wdata': \"00123456789990000000000000\",\n", " 'raddr': \"00000000000000000123456777\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For simulation purposes, we can give the spots in memory an initial value\n", "note that in the actual circuit, the values are initially undefined\n", "below, we are building the data with which to initialize memory" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "mem1_init = {addr: 9 for addr in range(8)}\n", "mem2_init = {addr: 9 for addr in range(8)}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__The simulation only recognizes initial values of memories when they are in a\n", "dictionary composing of memory : *mem_values pairs.*__" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "memvals = {mem1: mem1_init, mem2: mem2_init}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now run the simulation like before. Note the adding of the **memory\n", "value map**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim_trace = pyrtl.SimulationTrace()\n", "sim = pyrtl.Simulation(tracer=sim_trace, memory_value_map=memvals)\n", "for cycle in range(len(simvals['we'])):\n", " sim.step({k: int(v[cycle]) for k, v in simvals.items()})\n", "sim_trace.render_trace()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# cleanup in preparation for the rom example\n", "pyrtl.reset_working_block()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 2: ROMs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ROMs are another type of memory. Unlike normal memories, ROMs are read only\n", "and therefore **only have read ports**. They are used to **store predefined data.**\n", "\n", "There are two different ways to **define the data stored in the ROMs**\n", "either through passing a function or though a list or tuple." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def rom_data_func(address):\n", " return 31 - 2 * address\n", "\n", "rom_data_array = [rom_data_func(a) for a in range(16)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will make the **ROM blocks**. ROM blocks are similar to memory blocks\n", "but because they are read only, they also need to be passed in a set of\n", "data to be initialized as." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# FIXME: rework how memassigns work to account for more read ports\n", "rom1 = RomBlock(bitwidth=5, addrwidth=4, romdata=rom_data_func, max_read_ports=10)\n", "rom2 = RomBlock(5, 4, rom_data_array, max_read_ports=10)\n", "\n", "rom_add_1, rom_add_2 = Input(4, \"rom_in\"), Input(4, \"rom_in_2\")\n", "\n", "rom_out_1, rom_out_2 = Output(5, \"rom_out_1\"), Output(5, \"rom_out_2\")\n", "rom_out_3, cmp_out = Output(5, \"rom_out_3\"), Output(1, \"cmp_out\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because output wirevectors **cannot be used as the source for other nets**,\n", "in order to use the rom outputs in two different places, we must instead\n", "assign them to a **temporary variable**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "temp1 = rom1[rom_add_1]\n", "temp2 = rom2[rom_add_1]\n", "\n", "rom_out_3 <<= rom2[rom_add_2]\n", "\n", "# now we will connect the rest of the outputs together\n", "\n", "rom_out_1 <<= temp1\n", "rom_out_2 <<= temp2\n", "\n", "cmp_out <<= temp1 == temp2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the things that is useful to have is repeatability, However, we\n", "also don't want the hassle of typing out a set of values to test. One\n", "solution in this case is to **seed random** and then pulling out 'random'\n", "numbers from it." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "random.seed(4839483)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will **create a new set of simulation values**. In this case, since we\n", "want to use simulation values that are larger than 9 we cannot use the\n", "trick used in previous examples to parse values. The two ways we are doing\n", "it below are both valid ways of making larger values" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "simvals = {\n", " 'rom_in': [1, 11, 4, 2, 7, 8, 2, 4, 5, 13, 15, 3, 4, 4, 4, 8, 12, 13, 2, 1],\n", " 'rom_in_2': [random.randrange(0, 16) for i in range(20)]\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now run the simulation like before. Note that for ROMs, we **do not\n", "supply a memory value map** because ROMs are defined with the values\n", "predefined." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sim_trace = pyrtl.SimulationTrace()\n", "sim = pyrtl.Simulation(tracer=sim_trace)\n", "for cycle in range(len(simvals['rom_in'])):\n", " sim.step({k: v[cycle] for k, v in simvals.items()})\n", "sim_trace.render_trace()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }