pete > courses > CS 202 Spring 24 > Tier 3, Phase 1, Problem 01: associative memory


Tier 3, Phase 1, Problem 01: associative memory

This is a long description. Be sure to read all of it, as there are important details throughout, and especially at the very end.

The goals of this assignment are for you to combine the components we’ve looked at so far (logic gates, combinatorial logic, storage elements) into a much more complex circuit. You’ll have to understand the larger problem, break it down into smaller parts, figure out how to implement those smaller parts using components we’ve played with in class, and figure out how to connect those smaller parts together. In doing so, you will exercise and solidify your knowledge of the various circuit elements (your toolbox) and get practice applying them to problems.

This is an abstraction of computer science in general: know your toolbox, build stuff with those tools. It applies to any problem you’re faced with. In the intro courses, you get to know the tools Python gives you to solve problems and then apply them; in 201, you get to know the tools Java gives you and then apply them; in a job, you’ll have to get to know the preferred tools and apply them; in your recreational programming, you get to find (or build) tools for yourself, judge on your own whether they’re applicable, and apply them.


In class, we discussed traditional memory, in which data is stored and retrieved according to its location (ie, its address). For this assignment, you will implement an associative memory instead, in which data is stored and retrieved by an associated key (hence the name).

Just like the 4-by-3 memory shown in lecture 08, your associative memory will store a bunch of values (in this case, eight). Also like that memory, yours will have inputs to select which value to read and which value to write but, unlike that example, your memory for this assignment won’t select values by location. Recall that the 4-by-3 memory allowed you to pick which value you read and, separately, which value you wrote, by providing an address. In your associative memory, every value will be associated with a key—you will insert them as a pair, and you will need to provide a key to select a value to output.

The desired behavior is similar to dictionaries in Python, which allow you to store a value by key and later retrieve it by key. In the following Python example, the dictionary my_dict stores two values: the key "hello" is associated with value 12 and the key 19 is associated with value True. The value 12 is retrieved by providing its key.

>>> my_dict = {}
>>> my_dict["hello"] = 12
>>> my_dict[19] = True
>>> print(my_dict["hello"])
12

Many programming languages include data structures that behave similarly; the generic term is associative array (because it behaves like an array, except that arbitrary keys are associated with each value, rather than numeric array indexes). You’re going to implement an associative memory, which is a memory that identifies stored data according to a key rather than its location. Therefore, instead of the implicit addresses we saw in the first lecture on memory, you’re going to have explicit keys.

There are two primary differences between the dictionary you’ve used in Python and the associative memory you will build. Both are due to the (seemingly) infinite facilities provided you by Python which are not available to us lowly hardware engineers who have to deal with the pesky limitations of "reality". First, the keys in your associative memory will be 6-bit values. Second, your associative memory will only store up to 8 different key/value pairs.

This second limitation raises an interesting question: what should your associative memory do when a ninth item is written to it? The answer is that it should cause the least recently written key/value pair to disappear before storing this new item. (The method of deciding which item has to go when a memory is full is called an eviction policy; we’ll see more on this topic near the end of the semester.)

To sum up, you are to implement an associative memory that

Your associative memory should have the following interface (ie, input and output pins):

name width direction
write_key 6 bits in
write_value 8 bits in
clock 1 bit in
read_key 6 bits in
read_value 8 bits out

On the rising edge of "clock", the memory should store "write_value" associated with the key "write_key". If the memory is full, it should evict the least recently written key/value pair to make room for this one.

At all times, the memory should show on its "read_value" output the value associated with the key on its "read_key" input. If there is no value stored with that key, the memory output should be floating (ie, Us in Logisim).

Complication: resetting the circuit sets all register contents to zero

So if you’re storing the keys in registers, how do you tell the difference between a register storing the value 000000 and a register that has just been reset? I suggest you actually use a larger register to store keys and use an additional bit within each to indicate whether or not the contents are valid.


Advice

Getting from the components we’ve seen to a fully-functional associative memory is a big leap. If you recall how we developed the idea of the register file and ALU (lecture 11), we started with a sequence of examples; I suggest you do the same here. Write down a sequence of inputs to the associative memory and figure out what the output should be at each step. Then, figure out what hardware is necessary to achieve this behavior.

What follows is a more detailed description of this process, specifically tailored to this assignment. For each step, I’ve also noted how the advice applies to computer sciencey problem-solving in general.

  1. Start by figuring out what data your memory needs to store to do its job (which is, ultimately, making sure the output is correct). Setting aside the eviction policy for a moment, what information do you need to remember to make sure you can later retrieve the value associated with a particular key? What storage elements can you use to remember these data? (General lesson: understand what data you need to solve the problem at hand.)

  2. In the "normal" memory I showed in class, we used the notion of addresses to identify a given value; these addresses were assigned implicitly based on the physical location of the value within the memory. The associative memory has no notion of addresses, so you can decide how to physically organize it to your advantage. I suggest you organize it such that the items are ordered according to when they were written. (General lesson: be aware of what is and, possibly more importantly, what is not required to solve your problem and jettison the unnecessary parts if it serves your purposes.)

  3. On a low-tech piece of paper, sketch out the storage elements you decided on in step 1 and run through scenarios in which you insert values with various keys, maintaining the property that all items are ordered according to the order in which they were written. Under most circumstances, items are going to move around your memory in a regular, predictable fashion. There are, however, a couple situations where this does not happen. Identify them. (General lesson: figure out the desired behavior of your project both under common inputs as well as uncommon, potentially tricky inputs—the latter are called corner cases.)

  4. Given the behavior you just sketched out, how can you test to make sure your circuit does the right thing under all circumstances? What exact inputs can you give it and what outputs do you expect? Write this down. This is your test vector. You might have some test(s) that check your circuit’s behavior under normal circumstances and you might have other test(s) that verify the corner cases; that’s okay. (General lesson: figure out how you’re going to know you won before you start building so you can verify your solution as you build it.)

  5. As a corollary to this, in the practical realm of being a student, you’re anticipating how we’re going to grade your assignment. What aspects of its behavior are we going to test? How do you think we’re going to test them? (General lesson: learn to read your graders’ minds.)

  6. Consider the simple behavior you identified in step 3 (ie, ignore the corner cases for now). Figure out how you can connect together circuit elements to achieve this behavior. Start small: maybe only support storing 2 values instead of 8 at the beginning. Use the applicable tests from your test vector to make sure it works. Add the rest of the circuitry to support 8 values. Run the tests again. (General lessons: solve the easy part first. Test early and test often.)

  7. Now consider the corner cases above. Under what circumstances does your circuit behave differently than the common case? Can you come up with logic that describes these circumstances? Can you implement that logic as gates and integrate it into your existing circuit? If you can, great. If you can’t, consider how you can modify your circuit to accommodate such a solution. (General lesson: try to fit your corner-case solutions into your solution to the easy part, but don’t be afraid to reconsider your solution to the latter.)

  8. Build it. Test it. (General lesson: test. Test. Test. Test.)

  9. If any of these things are unclear, ask. If you want to talk through your test vector, ask. If you want to talk through your ideas for implementing the logic you’re considering, ask. If you’re stuck and you don’t even know why, ask. If you have no clue how to proceed, ask. Often, when we keep our uncertainties bottled up in our head, we’re not even sure what the uncertainties are! (General lesson: the process of putting our question into words is often enlightening all by itself. See rubber duck debugging.)

Submission Requirements

You will submit a single Logisim-Evolution circuit file and a single text file containing tests that verify your circuit’s correctness. The name of the circuit file must be associative_memory.circ and the name of the test file must be test-vector.txt.

The main circuit within associative_memory.circ must exactly the set of inputs and outputs enumerated above, and no more. Because this is not a combinational circuit, your test-vector.txt file will not be runnable as in most of the circuits you’ve created so far. Instead, your test-vector.txt should follow the pattern described in t2p1p08.

You are limited to components from the following Logisim libraries:

You may add as many subcircuits as you deem appropriate.

Submission Instructions

Copy the associative_memory.circ and test-vector.txt files to weathertop and then run:

$ 202 submit t3p1p01 associative_memory.circ
$ 202 submit t3p1p01 test-vector.txt

Submissions will not be accepted after 2pm on Wednesday, 27 March Friday, 29 March.

Flexibility with this deadline will only be considered with a dean’s note.

Last modified: