import tutorial from yesterday, add mostly working bram core
This commit is contained in:
parent
e68ab1dc13
commit
ba6100ce30
9
Makefile
9
Makefile
|
|
@ -31,10 +31,11 @@ auto_gen:
|
|||
# Functional Simulation
|
||||
functional_sim: io_core_tb logic_analyzer_tb bit_fifo_tb bridge_rx_tb bridge_tx_tb lut_ram_tb
|
||||
|
||||
bram_core_tb:
|
||||
iverilog -g2012 -o sim.out -y src/manta \
|
||||
test/functional_sim/bram_core_tb/bram_core_tb.sv \
|
||||
test/functional_sim/bram_core_tb/bram_core.v
|
||||
block_memory_tb:
|
||||
cd test/functional_sim/block_memory_tb/ && python3 foo.py
|
||||
iverilog -g2012 -o sim.out -y src/manta \
|
||||
test/functional_sim/block_memory_tb/block_memory_tb.sv \
|
||||
test/functional_sim/block_memory_tb/block_memory.v
|
||||
vvp sim.out
|
||||
rm sim.out
|
||||
|
||||
|
|
|
|||
22
doc/todo.md
22
doc/todo.md
|
|
@ -1,12 +1,28 @@
|
|||
# ToDo
|
||||
|
||||
# Deadlines
|
||||
- _04/07/23_ - rework HDL tests to test on autogenerated stuff. dork around with persistent server for funsies. IO and LA cores working for >16 bit inputs/outputs, with working examples
|
||||
- _04/14/23_ - Ethernet (if scapy/python support allows), mainline pip release. bonus if we have the BRAM core working too.
|
||||
- _04/21/23_ - CDC for LA and IO cores. verify windows support. perhaps make persistent server for running examples
|
||||
- _04/12_ - BRAM Core
|
||||
- _04/13_ - Logic Analyzer working with >16 bit inputs. Currently held up by the sample memory, but will be resolved once BRAM core is ruggedized.
|
||||
- _04/14_ - CDC in the Logic Analyzer. Should be handled automatically, but just need to test it - probably with a SD card example.
|
||||
- _04/15_ - _fischer's day off_
|
||||
- _04/16_ - VCD export and .mem export for hardware-in-the-loop
|
||||
|
||||
__release to PyPI lists - manta v0.0.1 out__
|
||||
|
||||
- _04/17_ - write logic analyzer lab
|
||||
- _04/18_ - beta tester round 2, run logic analyzer lab. ethernet tx if there's time
|
||||
- _04/19_ - Ethernet TX
|
||||
- _04/20_ - Ethernet TX
|
||||
- _04/21_ - Ethernet TX
|
||||
- _04/22_ - _fischer's day off_
|
||||
- _04/23_ - _fischer's day off_
|
||||
|
||||
- _04/28/23_ - start writing docs site, move appropriate bits into thesis
|
||||
- _05/05/23_ - slack week
|
||||
- _05/12/23_ - thesis due
|
||||
- _05/19/23_ - thesis actually due
|
||||
|
||||
|
||||
|
||||
## IO Core
|
||||
- add logic for ports >16 bits in width
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
# Background
|
||||
|
||||
We've got a couple things we're hoping to get out of today:
|
||||
- Say hi! Been a little while since we've seen ya'll, wanna see how you're dooooinnnnnnn
|
||||
- Get your initial reactions to some tools we've cooked up. We've spent a _lot_ of time trying to nail the ergonomics of these and we want to see how comfortable it is to use them.
|
||||
- See if they work. Because if there's any major bugs you're probably going to run into them __real__ quick.
|
||||
- See where they lead your imagination to - these tools were designed to improve 6.205's trademark _I'm Sitting At A Terminal For Twenty Hours_ experience, but we have some ideas about these could fit in designing programmable hardware in a more general sense.
|
||||
|
||||
We'll be asking you a bunch of questions as you work through things, and we'd love to hear your thoughts as they come up. Fundamentally, ya'll are the people we're trying to help with these projects, so if something sucks, tell us. And if something's great, tell us that too :)
|
||||
|
||||
We've designed today's playtest to take about an hour, with the first half devoted to new tools for generating Verilog, and the last half devoted to new tools for building it. We'll be running some battle-tested 6.205 content through these tools, so this should hopefully feel nice and familiar - or at least familiar.
|
||||
|
||||
|
||||
## Install Manta
|
||||
|
||||
Go ahead and install Manta with:
|
||||
|
||||
`pip install git+https://github.com/fischermoseley/manta.git`
|
||||
|
||||
You're also welcome to install Manta in a virtual environment using `venv` or `conda`, if that's your jam. Manta has very loose dependency requirements (just needs pySerial, yaml, and pyVCD), so it ~~won't~~ shouldn't break your system Python if you choose not to use a virtual environment. That's how I have it set up on all my machine and ~~it~~ that part of it hasn't broken yet.
|
||||
|
||||
If that doesn't work, just run the install script that's in this repo:
|
||||
|
||||
`git clone git@github.com:fischermoseley/lab_x_template.git`
|
||||
|
||||
And when you need to update your installation when I inevitably have to push a hotfix in the next half hour, you can do so with:
|
||||
|
||||
`pip install --upgrade --force-reinstall mantaray`
|
||||
|
||||
And today's template code is in the following repo, if you haven't cloned it already:
|
||||
|
||||
`git clone git@github.com:fischermoseley/lab_x_template.git`
|
||||
|
||||
## What Exactly is This Going To Do lmao
|
||||
|
||||
Glad you asked. Manta's what I've been working on for the last little bit, and it's basically a set of tools that helps you get data onto (and off of) the FPGA in a handful of ways. The immediate goal is to help with debugging designs on your FPGA, but it's also useful for getting data into and out of your design in a more general sense. What do I mean? Let's say you're:
|
||||
|
||||
- Making an accelerator for matrix multiplication, but you want to focus on the accelerator and don't want to bother writing an ethernet interface to get data into/out of it.
|
||||
- Making a musical instrument out of stepper motors, and you want to tweak your audio filters, but don't want to rebuild your Verilog every time you do so.
|
||||
- Making a UDP layer, and you want to see what your ARP table looks like when you plug your FPGA into MITnet.
|
||||
- Making a music player, and you want to interface with a SD card controller that You Didn't Write And Nobody Understands, and you'd like to know why your requests to read from the card don't return any data.
|
||||
- Making a L2 Ethernet layer, and your Verilog passes all of our testbenches, but doesn't work when the staff comes to check it off, and has no errors in the build logs.
|
||||
- ...
|
||||
|
||||
Everything above could benefit from being able to peer into your FPGA fabric and inspect/modify values in real time. If you were lucky enough last semester, we brought out the _Integrated Logic Analyzer_ (or ILA) to do this, but that required firing up Vivado's GUI, and listening to a bunch of loathsome comments from the TAs. Manta superceeds the ILA, but it also has some other operating modes that we'll poke with later. But for now, this is the intent.
|
||||
|
||||
Manta's a glorified python script that takes a configuration file that describes a bunch of __cores__, and then spices and dices a bunch of Verilog source files together to produce a module that contains the cores you asked for. This module also grabs onto the UART interface on your FPGA, letting your computer interact with these cores over UART. Manta provides a nice Python API for this too, so you can write your own scripts. We'll try our hand at some of this today 🤠
|
||||
|
||||
### Configuring Manta
|
||||
|
||||
For today's test, we'll be configuring a __IO Core__, which is exactly what it sounds like. It's a core with a set of inputs and outputs that you can write to or get the values of. For simplicity (and because we expect everything else to break lol), we'll be connecting it to the switches and LEDs on your board, and then asking you to write some kind of python that manipulates the LEDs in response to the switches. Simple enough.
|
||||
|
||||
Manta gets configured with a YAML file, which looks about like this:
|
||||
|
||||
```yaml
|
||||
---
|
||||
cores:
|
||||
my_io_core:
|
||||
type: io
|
||||
|
||||
inputs:
|
||||
spike: 1
|
||||
jet: 12
|
||||
valentine: 6
|
||||
ed: 9
|
||||
ein: 16
|
||||
|
||||
outputs:
|
||||
shepherd: 10
|
||||
wrex: 1
|
||||
tali: 5
|
||||
garrus: 3
|
||||
|
||||
uart:
|
||||
port: "auto"
|
||||
baudrate: 115200
|
||||
clock_freq: 100000000
|
||||
```
|
||||
|
||||
This defines an IO Core, and then adds a few signals to it, specifying their widths along the way. Go ahead and make a `manta.yaml` file that controls `led[15:0]`, along with the RGB leds on 16 and 17. And also add the switches to your IO core too.
|
||||
|
||||
Manta itself actually doesn't care what you name your nets (it's not going to try to automatically connect them to the pins or anything) but naming them with the signal they're supposed to connect to makes things a bit easier for us.
|
||||
|
||||
Once you've got all the signals added to your IO core, we'll want to generate the Verilog that implements the core. Go ahead and run:
|
||||
|
||||
`manta gen <path_to_config_file> <path_to_output_verilog>`
|
||||
|
||||
which in our case is:
|
||||
|
||||
`manta gen manta.yaml src/manta.v`
|
||||
|
||||
And if you're confused about how this command works, just run `manta help`.
|
||||
|
||||
Go ahead and have a look at the Verilog file it just spat out - it contains a definition for a module called `manta`, which we'll instantiate in our `top_level` module. There might be something in the autogenerated Verilog that might be useful for this ;)
|
||||
|
||||
Once you've got the ports on the `manta` module wired up to the ports on your board, go ahead and build it. If you've still got it, pay an old friend a visit and build your Verilog with lab-bc. There's a copy of it here if you need it. We've got tissues available if you get a little teary-eyed seeing it faithfully munching through your code again - we know we did.
|
||||
|
||||
If you don't have lab-bc available, you can just build with your local copy of Vivado, because you're sitting in front of lab computer:
|
||||
|
||||
`vivado -mode batch -source build.tcl`
|
||||
|
||||
Flash the bitstream once it's done building. Lemme know if you get any weird errors in the build logs.
|
||||
|
||||
### Using the Python API
|
||||
|
||||
Excellent, you've put Manta on your FPGA! Let's try talking to it from our computers - go ahead and run the following python file:
|
||||
|
||||
```python
|
||||
from manta import Manta
|
||||
m = Manta('manta.yaml')
|
||||
m.my_io_core.led.set(1)
|
||||
|
||||
print(my_io_core.btnc.get())
|
||||
```
|
||||
|
||||
This should change the value of the LED, and print out the logical value of the button on your board. If this works - congratulations! Go ahead and see if you can write a python script that does something interesting with your IO core - maybe it makes the LED bounce back and forth, maybe it counts up and down when you push `btnu` and `btnd`, maybe it submits RFPs for your CPW events. I'll let you guess the syntax on how to work with the API - lemme know if you run into troubles, and show me what you have once it's working.
|
||||
|
||||
|
||||
### Fischer's Filosophical Feelings
|
||||
|
||||
This most likely wasn't super awe-inspiring - after all we just twiddled some registers on the chip. But hopefully the utility of something like this is apparent. We'll check out other cores manta has to offer in the future, but it also has:
|
||||
|
||||
- A _Logic Analyzer_ core, which does pretty much exactly the same thing as the ILA - it dumps whatever signals you'd like into a .vcd file, which you can poke around with in GTKWave. But it also exports those to a .mem file, which you can use to load those same signals into your iVerilog simulations. This lets you run your code through the same signals that it'll see once it's on the FPGA, and debug it in simulation instead of hardware. Imagine doing this for your PS/2 keyboards in lab02 - things passed the online checker and seemed to work in simulation, but didn't work with the actual keyboard.
|
||||
|
||||
- A _Block Memory_ core, which lets you set the contents of a BRAM from your machine. Want to change the contents of your image sprites in near real-time? Want to do your lab04 image processing from your laptop's webcam instead of a potato-quality camera?
|
||||
|
||||
- An _Ethernet Interface_ if you need to do any of the above, but _super speedy_. Today we just ran things over UART, but that's slow as mollasses and sometimes you just [gotta go fast](https://www.youtube.com/watch?v=LIfgMI8qLBk).
|
||||
|
||||
### SWAG DISTRIBUTION
|
||||
|
||||
Congrats on being an alpha tester!! I've got stickers and we've got pizza for ya to say thank you :)
|
||||
|
|
@ -52,6 +52,8 @@ nav:
|
|||
- Getting Started: getting_started.md
|
||||
- Logic Analyzer Core: logic_analyzer_core.md
|
||||
- IO Core: io_core.md
|
||||
- Tutorials:
|
||||
- Tutorial 0 (Setup + IO Core): tutorial_0.md
|
||||
- Developer Reference:
|
||||
- System Architecture: system_architecture.md
|
||||
- Tools Used: tools_used.md
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class VerilogManipulator:
|
|||
self.hdl = self.hdl.replace("`default_nettype none", "")
|
||||
self.hdl = self.hdl.replace("`default_nettype wire", "")
|
||||
self.hdl = self.hdl.replace("`timescale 1ns/1ps", "")
|
||||
self.hdl = self.hdl.strip()
|
||||
|
||||
else:
|
||||
self.hdl = None
|
||||
|
|
@ -643,6 +644,77 @@ class LogicAnalyzerCore:
|
|||
writer.change(probe, timestamp, val)
|
||||
vcd_file.close()
|
||||
|
||||
class BlockMemoryCore:
|
||||
def __init__(self, config, name, base_addr, interface):
|
||||
self.name = name
|
||||
self.base_addr = base_addr
|
||||
self.interface = interface
|
||||
|
||||
# Determine if we expose the BRAM's second port to the top of the module
|
||||
if "expose_port" in config:
|
||||
assert isinstance(config["expose_port"], bool), "Configuring BRAM exposure must be done with a boolean."
|
||||
self.expose_port = config["expose_port"]
|
||||
|
||||
else:
|
||||
self.expose_port = True
|
||||
|
||||
# Get depth
|
||||
assert "depth" in config, "Depth not specified for Block Memory core."
|
||||
assert config["depth"] > 0, "Block Memory core must have positive depth."
|
||||
assert isinstance(config["depth"], int), "Block Memory core must have integer depth."
|
||||
self.depth = config["depth"]
|
||||
|
||||
# Get width
|
||||
assert "width" in config, "Width not specified for Block Memory core."
|
||||
assert config["width"] > 0, "Block Memory core must have positive width."
|
||||
assert isinstance(config["width"], int), "Block Memory core must have integer width."
|
||||
self.width = config["width"]
|
||||
|
||||
from math import ceil, floor, log2
|
||||
self.addr_width = ceil(log2(self.depth))
|
||||
self.n_brams = ceil(self.width / 16)
|
||||
self.n_full_width_brams = floor(self.width / 16)
|
||||
self.partial_bram_width = self.width - (16 * self.n_full_width_brams)
|
||||
self.max_addr = self.base_addr + (self.depth * self.n_brams)
|
||||
|
||||
def hdl_inst(self):
|
||||
inst = VerilogManipulator("block_memory_inst_tmpl.v")
|
||||
# inst.sub(self.name, "/* INST_NAME */")
|
||||
# inst.sub(self.depth, "/* DEPTH */")
|
||||
# inst.sub(self.width, "/* WIDTH */")
|
||||
return inst.get_hdl()
|
||||
|
||||
def hdl_def(self):
|
||||
bram_core = VerilogManipulator("block_memory_tmpl.v")
|
||||
bram_core.sub(self.name, "/* NAME */")
|
||||
bram_core.sub(self.width, "/* WIDTH */")
|
||||
bram_core.sub(self.depth, "/* DEPTH */")
|
||||
bram_core.sub(self.max_addr, "/* MAX_ADDR */")
|
||||
bram_core.sub(self.n_brams, "/* N_BRAMS */")
|
||||
bram_core.sub(self.n_full_width_brams, "/* N_FULL_WIDTH_BRAMS */")
|
||||
bram_core.sub(self.partial_bram_width, "/* PARTIAL_BRAM_WIDTH */")
|
||||
return bram_core.get_hdl()
|
||||
|
||||
|
||||
def hdl_top_level_ports(self):
|
||||
if not self.expose_port:
|
||||
return ""
|
||||
|
||||
tlp = []
|
||||
tlp.append(f"input wire {self.name}_clk,")
|
||||
tlp.append(f"input wire [{self.addr_width-1}:0] {self.name}_addr")
|
||||
tlp.append(f"input wire [{self.addr_width-1}:0] {self.name}_din")
|
||||
tlp.append(f"input wire [{self.addr_width-1}:0] {self.name}_dout")
|
||||
tlp.append(f"input wire [{self.addr_width-1}:0] {self.name}_we")
|
||||
return tlp
|
||||
|
||||
def read(self, addr):
|
||||
return self.interface.read_register(addr + self.base_addr)
|
||||
|
||||
def write(self, addr, data):
|
||||
return self.interface.write_register(addr + self.base_addr, data)
|
||||
|
||||
|
||||
|
||||
class Manta:
|
||||
def __init__(self, config_filepath):
|
||||
|
|
@ -677,6 +749,9 @@ class Manta:
|
|||
elif core["type"] == "lut_ram":
|
||||
new_core = LUTRAMCore(core, core_name, base_addr, self.interface)
|
||||
|
||||
elif core["type"] == "block_memory":
|
||||
new_core = BlockMemoryCore(core, core_name, base_addr, self.interface)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unrecognized core type specified for {core_name}.")
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
`default_nettype none
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module /* NAME */ (
|
||||
input wire clk,
|
||||
|
||||
// input port
|
||||
input wire [15:0] addr_i,
|
||||
input wire [15:0] wdata_i,
|
||||
input wire [15:0] rdata_i,
|
||||
input wire rw_i,
|
||||
input wire valid_i,
|
||||
|
||||
// output port
|
||||
output reg [15:0] addr_o,
|
||||
output reg [15:0] wdata_o,
|
||||
output reg [15:0] rdata_o,
|
||||
output reg rw_o,
|
||||
output reg valid_o,
|
||||
|
||||
// BRAM itself
|
||||
input wire user_clk,
|
||||
input wire [ADDR_WIDTH-1:0] user_addr,
|
||||
input wire [BRAM_WIDTH-1:0] user_din,
|
||||
output reg [BRAM_WIDTH-1:0] user_dout,
|
||||
input wire user_we);
|
||||
|
||||
parameter BASE_ADDR = 0;
|
||||
localparam BRAM_WIDTH = /* WIDTH */;
|
||||
localparam BRAM_DEPTH = /* DEPTH */;
|
||||
localparam ADDR_WIDTH = $clog2(BRAM_DEPTH);
|
||||
|
||||
localparam MAX_ADDR = /* MAX_ADDR */;
|
||||
localparam N_BRAMS = /* N_BRAMS */;
|
||||
localparam N_FULL_WIDTH_BRAMS = /* N_FULL_WIDTH_BRAMS */;
|
||||
localparam PARTIAL_BRAM_WIDTH = /* PARTIAL_BRAM_WIDTH */;
|
||||
|
||||
// Bus-Controlled side of BRAMs
|
||||
reg [N_BRAMS-1:0][ADDR_WIDTH-1:0] addra = 0;
|
||||
reg [N_BRAMS-1:0][15:0] dina = 0;
|
||||
reg [N_BRAMS-1:0][15:0] douta;
|
||||
reg [N_BRAMS-1:0] wea = 0;
|
||||
|
||||
// User-Controlled Side of BRAMs
|
||||
reg [N_BRAMS-1:0][15:0] dinb = 0;
|
||||
reg [N_BRAMS-1:0][15:0] doutb;
|
||||
assign dout = {doutb[1], doutb[0]};
|
||||
|
||||
// Pipelining
|
||||
reg [3:0][15:0] addr_pipe = 0;
|
||||
reg [3:0][15:0] wdata_pipe = 0;
|
||||
reg [3:0][15:0] rdata_pipe = 0;
|
||||
reg [3:0] valid_pipe = 0;
|
||||
reg [3:0] rw_pipe = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
addr_pipe[0] <= addr_i;
|
||||
wdata_pipe[0] <= wdata_i;
|
||||
rdata_pipe[0] <= rdata_i;
|
||||
valid_pipe[0] <= valid_i;
|
||||
rw_pipe[0] <= rw_i;
|
||||
|
||||
addr_o <= addr_pipe[2];
|
||||
wdata_o <= wdata_pipe[2];
|
||||
rdata_o <= rdata_pipe[2];
|
||||
valid_o <= valid_pipe[2];
|
||||
rw_o <= rw_pipe[2];
|
||||
|
||||
for(int i=1; i<4; i=i+1) begin
|
||||
addr_pipe[i] <= addr_pipe[i-1];
|
||||
wdata_pipe[i] <= wdata_pipe[i-1];
|
||||
rdata_pipe[i] <= rdata_pipe[i-1];
|
||||
valid_pipe[i] <= valid_pipe[i-1];
|
||||
rw_pipe[i] <= rw_pipe[i-1];
|
||||
end
|
||||
|
||||
// throw BRAM operations into the front of the pipeline
|
||||
wea <= 0;
|
||||
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= MAX_ADDR)) begin
|
||||
wea[addr_i % N_BRAMS] <= rw_i;
|
||||
addra[addr_i % N_BRAMS] <= (addr_i - BASE_ADDR) / N_BRAMS;
|
||||
dina[addr_i % N_BRAMS] <= wdata_i;
|
||||
end
|
||||
|
||||
// pull BRAM reads from the back of the pipeline
|
||||
if( (valid_pipe[2]) && (addr_pipe[2] >= BASE_ADDR) && (addr_pipe[2] <= MAX_ADDR)) begin
|
||||
rdata_o <= douta[addr_pipe[2] % N_BRAMS];
|
||||
end
|
||||
end
|
||||
|
||||
// generate the full-width BRAMs
|
||||
genvar i;
|
||||
generate
|
||||
for(i=0; i<N_FULL_WIDTH_BRAMS; i=i+1) begin
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(16),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_full_width_i (
|
||||
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[i]),
|
||||
.dina(dina[i]),
|
||||
.douta(douta[i]),
|
||||
.wea(wea[i]),
|
||||
|
||||
// port B is exposed to the user
|
||||
.clkb(user_clk),
|
||||
.addrb(user_addr),
|
||||
.dinb(dinb[i]),
|
||||
.doutb(doutb[i]),
|
||||
.web(user_we));
|
||||
end
|
||||
|
||||
if(PARTIAL_BRAM_WIDTH > 0) begin
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(PARTIAL_BRAM_WIDTH),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_partial_width (
|
||||
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[N_BRAMS-1]),
|
||||
.dina(dina[N_BRAMS-1][PARTIAL_BRAM_WIDTH-1:0]),
|
||||
.douta(douta[N_BRAMS-1]),
|
||||
.wea(wea[N_BRAMS-1]),
|
||||
|
||||
// port B is exposed to the user
|
||||
.clkb(user_clk),
|
||||
.addrb(user_addr),
|
||||
.dinb(dinb[N_BRAMS-1][PARTIAL_BRAM_WIDTH-1:0]),
|
||||
.doutb(doutb[N_BRAMS-1]),
|
||||
.web(user_we));
|
||||
end
|
||||
endgenerate
|
||||
endmodule
|
||||
`default_nettype wire
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
`default_nettype none
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module bram_core (
|
||||
module my_bram (
|
||||
input wire clk,
|
||||
|
||||
// input port
|
||||
|
|
@ -19,28 +16,33 @@ module bram_core (
|
|||
output reg valid_o,
|
||||
|
||||
// BRAM itself
|
||||
input wire bram_clk,
|
||||
input wire [ADDR_WIDTH-1:0] addr,
|
||||
input wire [BRAM_WIDTH-1:0] din,
|
||||
output reg [BRAM_WIDTH-1:0] dout,
|
||||
input wire we);
|
||||
input wire user_clk,
|
||||
input wire [ADDR_WIDTH-1:0] user_addr,
|
||||
input wire [BRAM_WIDTH-1:0] user_din,
|
||||
output reg [BRAM_WIDTH-1:0] user_dout,
|
||||
input wire user_we);
|
||||
|
||||
// parameter BRAM_WIDTH = 0;
|
||||
// parameter BRAM_DEPTH = 0;
|
||||
parameter BRAM_WIDTH = 18;
|
||||
parameter BRAM_DEPTH = 256;
|
||||
parameter BASE_ADDR = 0;
|
||||
|
||||
localparam N_BRAMS = 2;
|
||||
localparam MAX_ADDR = BASE_ADDR + (2*N_BRAMS);
|
||||
localparam BRAM_WIDTH = 18;
|
||||
localparam BRAM_DEPTH = 256;
|
||||
localparam ADDR_WIDTH = $clog2(BRAM_DEPTH);
|
||||
|
||||
localparam MAX_ADDR = 512;
|
||||
localparam N_BRAMS = 2;
|
||||
localparam N_FULL_WIDTH_BRAMS = 1;
|
||||
localparam PARTIAL_BRAM_WIDTH = 2;
|
||||
|
||||
// Bus-Controlled side of BRAMs
|
||||
reg [N_BRAMS-1:0][ADDR_WIDTH-1:0] addra = 0;
|
||||
reg [N_BRAMS-1:0][15:0] dina = 0;
|
||||
reg [N_BRAMS-1:0][15:0] douta;
|
||||
reg [N_BRAMS-1:0] wea = 0;
|
||||
|
||||
// User-Controlled Side of BRAMs
|
||||
reg [N_BRAMS-1:0][15:0] dinb = 0;
|
||||
reg [N_BRAMS-1:0][15:0] doutb;
|
||||
assign dout = {doutb[1], doutb[0]};
|
||||
|
||||
// Pipelining
|
||||
reg [3:0][15:0] addr_pipe = 0;
|
||||
reg [3:0][15:0] wdata_pipe = 0;
|
||||
|
|
@ -83,56 +85,49 @@ module bram_core (
|
|||
end
|
||||
end
|
||||
|
||||
// generate the full-width BRAMs
|
||||
genvar i;
|
||||
generate
|
||||
for(i=0; i<N_FULL_WIDTH_BRAMS; i=i+1) begin
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(16),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_full_width_i (
|
||||
|
||||
// User-Controlled Side of BRAMs
|
||||
reg [15:0] dinb_0;
|
||||
reg [15:0] doutb_0;
|
||||
reg [1:0] dinb_1;
|
||||
reg [1:0] doutb_1;
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[i]),
|
||||
.dina(dina[i]),
|
||||
.douta(douta[i]),
|
||||
.wea(wea[i]),
|
||||
|
||||
assign dinb_0 = din[15:0];
|
||||
assign dinb_1 = din[17:16];
|
||||
assign dout = {doutb_1, doutb_0};
|
||||
// port B is exposed to the user
|
||||
.clkb(user_clk),
|
||||
.addrb(user_addr),
|
||||
.dinb(dinb[i]),
|
||||
.doutb(doutb[i]),
|
||||
.web(user_we));
|
||||
end
|
||||
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(16),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_0 (
|
||||
if(PARTIAL_BRAM_WIDTH > 0) begin
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(PARTIAL_BRAM_WIDTH),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_partial_width (
|
||||
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[0]),
|
||||
.dina(dina[0]),
|
||||
.douta(douta[0]),
|
||||
.wea(wea[0]),
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[N_BRAMS-1]),
|
||||
.dina(dina[N_BRAMS-1][PARTIAL_BRAM_WIDTH-1:0]),
|
||||
.douta(douta[N_BRAMS-1]),
|
||||
.wea(wea[N_BRAMS-1]),
|
||||
|
||||
// port B is exposed to the user
|
||||
.clkb(bram_clk),
|
||||
.addrb(addr),
|
||||
.dinb(dinb_0),
|
||||
.doutb(doutb_0),
|
||||
.web(we));
|
||||
|
||||
reg [1:0] stub_bram_douta;
|
||||
assign douta[N_BRAMS-1] = {14'b0, stub_bram_douta};
|
||||
|
||||
dual_port_bram #(
|
||||
.RAM_WIDTH(2),
|
||||
.RAM_DEPTH(BRAM_DEPTH)
|
||||
) bram_1 (
|
||||
|
||||
// port A is controlled by the bus
|
||||
.clka(clk),
|
||||
.addra(addra[1]),
|
||||
.dina(dina[1][1:0]),
|
||||
.douta(stub_bram_douta),
|
||||
.wea(wea[1]),
|
||||
|
||||
// port B is exposed to the user
|
||||
.clkb(bram_clk),
|
||||
.addrb(addr),
|
||||
.dinb(dinb_1),
|
||||
.doutb(doutb_1),
|
||||
.web(we));
|
||||
endmodule
|
||||
`default_nettype wire
|
||||
// port B is exposed to the user
|
||||
.clkb(user_clk),
|
||||
.addrb(user_addr),
|
||||
.dinb(dinb[N_BRAMS-1][PARTIAL_BRAM_WIDTH-1:0]),
|
||||
.doutb(doutb[N_BRAMS-1]),
|
||||
.web(user_we));
|
||||
end
|
||||
endgenerate
|
||||
endmodule
|
||||
|
|
@ -9,14 +9,14 @@ task read_reg (
|
|||
input string desc
|
||||
);
|
||||
|
||||
bram_core_tb.tb_bc_addr = addr;
|
||||
bram_core_tb.tb_bc_rw = 0;
|
||||
bram_core_tb.tb_bc_valid = 1;
|
||||
block_memory_tb.tb_bc_addr = addr;
|
||||
block_memory_tb.tb_bc_rw = 0;
|
||||
block_memory_tb.tb_bc_valid = 1;
|
||||
#`CP
|
||||
bram_core_tb.tb_bc_rw = 0;
|
||||
bram_core_tb.tb_bc_valid = 0;
|
||||
while (!bram_core_tb.bc_tb_valid) #`CP;
|
||||
data = bram_core_tb.bc_tb_rdata;
|
||||
block_memory_tb.tb_bc_rw = 0;
|
||||
block_memory_tb.tb_bc_valid = 0;
|
||||
while (!block_memory_tb.bc_tb_valid) #`CP;
|
||||
data = block_memory_tb.bc_tb_rdata;
|
||||
|
||||
$display(" -> read 0x%h from addr 0x%h (%s)", data, addr, desc);
|
||||
endtask
|
||||
|
|
@ -27,14 +27,14 @@ task write_reg(
|
|||
input string desc
|
||||
);
|
||||
|
||||
bram_core_tb.tb_bc_addr = addr;
|
||||
bram_core_tb.tb_bc_wdata = data;
|
||||
bram_core_tb.tb_bc_rw = 1;
|
||||
bram_core_tb.tb_bc_valid = 1;
|
||||
block_memory_tb.tb_bc_addr = addr;
|
||||
block_memory_tb.tb_bc_wdata = data;
|
||||
block_memory_tb.tb_bc_rw = 1;
|
||||
block_memory_tb.tb_bc_valid = 1;
|
||||
#`CP
|
||||
bram_core_tb.tb_bc_rw = 0;
|
||||
bram_core_tb.tb_bc_valid = 0;
|
||||
while (!bram_core_tb.bc_tb_valid) #`CP;
|
||||
block_memory_tb.tb_bc_rw = 0;
|
||||
block_memory_tb.tb_bc_valid = 0;
|
||||
while (!block_memory_tb.bc_tb_valid) #`CP;
|
||||
|
||||
$display(" -> wrote 0x%h to addr 0x%h (%s)", data, addr, desc);
|
||||
endtask
|
||||
|
|
@ -52,7 +52,7 @@ task write_and_verify(
|
|||
assert(read_data == write_data) else $error("data read does not match data written!");
|
||||
endtask
|
||||
|
||||
module bram_core_tb;
|
||||
module block_memory_tb;
|
||||
|
||||
// boilerplate
|
||||
logic clk;
|
||||
|
|
@ -79,29 +79,26 @@ module bram_core_tb;
|
|||
logic [17:0] bram_user_dout;
|
||||
logic bram_user_we = 0;
|
||||
|
||||
bram_core bc (
|
||||
my_bram my_bram_inst(
|
||||
.clk(clk),
|
||||
|
||||
// bus input port
|
||||
.addr_i(tb_bc_addr),
|
||||
.wdata_i(tb_bc_wdata),
|
||||
.rdata_i(tb_bc_rdata),
|
||||
.rw_i(tb_bc_rw),
|
||||
.valid_i(tb_bc_valid),
|
||||
|
||||
// bus output port
|
||||
.addr_o(bc_tb_addr),
|
||||
.wdata_o(bc_tb_wdata),
|
||||
.rdata_o(bc_tb_rdata),
|
||||
.rw_o(bc_tb_rw),
|
||||
.valid_o(bc_tb_valid),
|
||||
|
||||
// bram itself
|
||||
.bram_clk(clk),
|
||||
.addr(bram_user_addr),
|
||||
.din(bram_user_din),
|
||||
.dout(bram_user_dout),
|
||||
.we(bram_user_we));
|
||||
.user_clk(clk),
|
||||
.user_addr(bram_user_addr),
|
||||
.user_din(bram_user_din),
|
||||
.user_dout(bram_user_dout),
|
||||
.user_we(bram_user_we));
|
||||
|
||||
always begin
|
||||
#`HCP
|
||||
|
|
@ -109,8 +106,8 @@ module bram_core_tb;
|
|||
end
|
||||
|
||||
initial begin
|
||||
$dumpfile("bram_core_tb.vcd");
|
||||
$dumpvars(0, bram_core_tb);
|
||||
$dumpfile("block_memory_tb.vcd");
|
||||
$dumpvars(0, block_memory_tb);
|
||||
|
||||
// setup and reset
|
||||
clk = 0;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from manta import Manta
|
||||
m = Manta('manta.yaml')
|
||||
|
||||
bram_def = m.my_bram.hdl_def()
|
||||
|
||||
with open("block_memory.v", "w") as f:
|
||||
f.write(bram_def)
|
||||
|
||||
print(m.my_bram.hdl_top_level_ports())
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
cores:
|
||||
my_bram:
|
||||
type: block_memory
|
||||
width: 18
|
||||
depth: 256
|
||||
|
||||
uart:
|
||||
port: "auto"
|
||||
baudrate: 115200
|
||||
clock_freq: 100000000
|
||||
Loading…
Reference in New Issue