0%

SDRAM控制器实现

SDRAM控制器的实现与优化,包括配置切换、时序控制、突发传输等功能的详细说明。

sdram配置切换

1
2
3
4
5
6
trait SDRAMConfig {
val SDRAM_2BYTE_NUM: Int = 1
val SDRAM_WORD_NUM: Int = 1

val awidth = if (SDRAM_WORD_NUM == 2) 14 else 13
}
1
2
parameter SDRAM_2BYTE_NUM             = 1;
parameter SDRAM_WORD_NUM = 1;

只有在使用sdram_axi时,才可以突发访问

协议

设置寄存器

寄存器设置
寄存器配置
写入BurstLength和CASLatency,完全是硬件自动写入,代码没管
硬件写入

突发读时序

突发读时序
16位写入,原始实例化了1个dsramhelper。burstLength设置成为了,这里就突发的读入了两个16bit。因为无论是lb、lh、lw都是读入32bit

1
2
// 步骤 2:测试16位写入和读取
read_memory_16bit((uint16_t *)start, size);

16位读取

突发写时序

尤其注意传col的时候也是传bank的。所以sdram支持4个bank的开放,active时基于其中一个bank,read/write时选择一个bank。取巧的做法是锁存row地址而不是锁存row中的数据,等到read/write时使用bank索引锁存的row,bank,col一起访存
突发写时序
16位写入,原始实例化了1个dsramhelper。burstLength很早就设置成1了,为的是32写入时可以突发,虽然后面的col写入需要放弃,但col该加还得加。

1
2
// 步骤 2:测试16位写入和读取
write_memory_16bit((uint16_t *)start, size);

16位写入
32位写入,突发的写入两个16bit
32位写入

row hit

acitve的时候激活,如果rowhit了就不用active了,但是目前没实现row的寄存器(4bank*512col),所以只好禁止row hit了
以下图是因为row hit了所以没有active状态传入row和way和bank,就读错了
Row Hit问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Open row hit
if (row_open_q[addr_bank_w] && addr_row_w == active_row_q[addr_bank_w])
begin
if (!ram_rd_w)
next_state_r = STATE_WRITE0;
else
next_state_r = STATE_READ;
end
// Row miss, close row, open new row
else if (row_open_q[addr_bank_w])
begin
next_state_r = STATE_PRECHARGE;

if (!ram_rd_w)
target_state_r = STATE_WRITE0;
else
target_state_r = STATE_READ;
end
// No open row, open row
else
begin
next_state_r = STATE_ACTIVATE;

if (!ram_rd_w)
target_state_r = STATE_WRITE0;
else
target_state_r = STATE_READ;
end

位扩展

还需要修改sdram控制器,最不想做的事情
位扩展示意
col在一次传输中都是32bit对齐的,是0,2,4这样的数,因为在sdram的视角下,即使对col_1写16bit也是对col_0写32bit。只不过在第二次传输是将strb和wen置为有效

1
2
3
4
// Address bits
wire [SDRAM_ROW_W-1:0] addr_col_w = (SDRAM_2BYTE_NUM == 1) ?
{{(SDRAM_ROW_W-SDRAM_COL_W){1'b0}}, ram_addr_w[SDRAM_COL_W:2], 1'b0} :
{{(SDRAM_ROW_W-SDRAM_COL_W){1'b0}}, 1'b0,ram_addr_w[SDRAM_COL_W:2]};

位扩展时序1
位扩展时序2

位扩展后32位读取不需要突发了,因此不用到state_write1,但state_write0仍需要检测突发以应对64位或更高位的突发访问。此时要精确控制ack_q->ram_ack_i(axi_core)-> ram_ack_i(axi_pmem) -> push_i(u_response) 的拍数。多一拍的话会多压入一个data,当axi_bvalid/axi_rvalid->resp_accept_w->pop_i(u_response)时会弹出提前压入栈的数据。

位扩展控制

字扩展

先来算一下一共能代表多少字节。

原先2**25 = 32MB

地址空间位数 地址位数 way row bank col 字节偏移
25 24 - 13 2 9 1

在最不常用的地方扩展,在地址最前面加上一bit的way,2**27 = 128MB

地址空间位数 地址位数 way row bank col 字节偏移
27 25 1 13 2 9 2

但是ysyxsoc分配给sdram了2**29 = 512MB空间

chisel

vec

1
val sdram = VecInit(Seq(sdram0.io, sdram1.io))

Exception in thread “main” java.lang.IllegalArgumentException: requirement failed: can’t create Vec with heterogeneous types class ysyx.SDRAMHelper0$$anon$3 and class ysyx.SDRAMHelper1$$anon$4

问题出在使用 VecInit 创建 Vec 时,传入的 Seq 包含了不同类型的元素:sdram0.io 和 sdram1.io,分别是 SDRAMHelper0 和 SDRAMHelper1 的 IO 类型。即使这两个IO内部元素相同也不算同一个类型。
解决方法:
统一使用SDRAMHelperIO

1
val io = IO(new SDRAMHelperIO)

(实例化的模板都是SDRAMHelper,verilator找不到SDRAMHelper_${instanceId})

1
2
3
4
class SDRAMHelper(instanceId: Int)
setInline(s"SDRAMHelper_${instanceId}.v",
s"""
|module SDRAMHelper_${instanceId}(

类型转换

1
2
val rdata = Reg(Vec(SDRAMNum,UInt(16.W)))
dqout := rdata.asTypeOf(dqout)

Exception in thread “main” chisel3.package$ChiselException: Connection between sink (sdramChisel.dqout: Wire[UInt<32>]) and source (sdramChisel.rdata: Reg[UInt<16>[2]]) failed @: Sink (UInt<32>) and Source (UInt<16>[2]) have different types.

asTypeOf 并不能直接将 Vec[UInt] 转换为 UInt,因为它们在结构上不兼容。如果需要将整个 Vec 转换为一个宽度为 32 位的 UInt,可以使用 asUInt。相反,UInt转换成vec就可以asTypeOf。
解决方法:

1
2
3
4
5
6
val rdata = Reg(Vec(SDRAMNum,UInt(16.W)))
dqout := rdata.asUInt
val wdata = Reg(Vec(SDRAMNum,UInt(16.W)))
wdata := dqin.asTypeOf(wdata)
val mask = Reg(Vec(SDRAMNum,UInt(2.W)))
mask := (~io.dqm).asTypeOf(mask)

for循环

1
2
3
for (i <- 0 until SDRAMNum) {

}
1
2
3
  sdram.foreach { sdram =>
sdram.row := 0.U
}

logic

看似command是wire,但是却综合出了Reg

1
2
3
val command =
MuxCase(COMMAND_INHIBIT, Seq(
(!io.cs && io.ras && io.cas && io.we) -> NOP,

Logic综合1
Logic综合2
automatic:
在 SystemVerilog 中,automatic 表示这个变量是 自动变量,即每次进入 always 块时重新创建,而不是在模块级别共享。
logic 是 SystemVerilog 中的新数据类型,类似于 Verilog 中的 reg
跨时钟域的always中,就会有这样的在新时钟域赋值的wire

运算优先级大于移位

运算优先级
要使用

1
(1.U << BurstLength).asUInt -1.U 

make ARCH=riscv32e-ysyxsoc run ALL=printf | tee sdram

make ARCH=riscv32e-ysyxsoc run ALL=printf | tee >(grep “sdram” >> sdram)

sdram在最高地址开辟栈时,写入sp报错,因为做了字和位扩展会增大4被sdram空间
栈空间问题