import tutorial from yesterday, add mostly working bram core

This commit is contained in:
Fischer Moseley 2023-04-12 11:47:50 -04:00
parent e68ab1dc13
commit ba6100ce30
10 changed files with 471 additions and 97 deletions

View File

@ -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

View File

@ -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

131
doc/tutorial_0.md Normal file
View File

@ -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 :)

View File

@ -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

View File

@ -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}.")

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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())

View File

@ -0,0 +1,11 @@
---
cores:
my_bram:
type: block_memory
width: 18
depth: 256
uart:
port: "auto"
baudrate: 115200
clock_freq: 100000000